Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
168 changes: 120 additions & 48 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,99 +3,171 @@
<picture>
<source srcset="packages/console/app/src/asset/logo-ornate-dark.svg" media="(prefers-color-scheme: dark)">
<source srcset="packages/console/app/src/asset/logo-ornate-light.svg" media="(prefers-color-scheme: light)">
<img src="packages/console/app/src/asset/logo-ornate-light.svg" alt="OpenCode logo">
<img src="packages/console/app/src/asset/logo-ornate-light.svg" alt="OpenCode logo" width="400">
</picture>
</a>
</p>
<p align="center">The AI coding agent built for the terminal.</p>

<h3 align="center">The open-source AI coding agent for your terminal</h3>

<p align="center">
<a href="https://opencode.ai/discord"><img alt="Discord" src="https://img.shields.io/discord/1391832426048651334?style=flat-square&label=Discord&color=5865F2" /></a>
<a href="https://www.npmjs.com/package/opencode-ai"><img alt="npm" src="https://img.shields.io/npm/v/opencode-ai?style=flat-square&label=npm&color=CB3837" /></a>
<a href="https://github.com/sst/opencode/actions/workflows/publish.yml"><img alt="Build status" src="https://img.shields.io/github/actions/workflow/status/sst/opencode/publish.yml?style=flat-square&branch=dev&label=Build" /></a>
<a href="https://github.com/sst/opencode/blob/dev/LICENSE"><img alt="License" src="https://img.shields.io/github/license/sst/opencode?style=flat-square&label=License" /></a>
</p>

<p align="center">
<a href="https://opencode.ai/discord"><img alt="Discord" src="https://img.shields.io/discord/1391832426048651334?style=flat-square&label=discord" /></a>
<a href="https://www.npmjs.com/package/opencode-ai"><img alt="npm" src="https://img.shields.io/npm/v/opencode-ai?style=flat-square" /></a>
<a href="https://github.com/sst/opencode/actions/workflows/publish.yml"><img alt="Build status" src="https://img.shields.io/github/actions/workflow/status/sst/opencode/publish.yml?style=flat-square&branch=dev" /></a>
<a href="https://opencode.ai/docs">Documentation</a> |
<a href="https://opencode.ai/docs/agents">Agents</a> |
<a href="https://opencode.ai/zen">OpenCode Zen</a> |
<a href="https://opencode.ai/discord">Community</a>
</p>

---

[![OpenCode Terminal UI](packages/web/src/assets/lander/screenshot.png)](https://opencode.ai)

## Features

- **100% Open Source** - Fully transparent, community-driven development
- **Provider Agnostic** - Works with Claude, OpenAI, Google, Azure, local models, and more
- **Built-in LSP Support** - Language Server Protocol integration out of the box
- **Terminal-First Design** - Crafted by Neovim enthusiasts for power users
- **Client/Server Architecture** - Run locally, control remotely from any device
- **Multiple Agents** - Switch between build and plan modes for different workflows

---

### Installation
## Quick Start

```bash
# YOLO
# One-line install
curl -fsSL https://opencode.ai/install | bash

# Package managers
npm i -g opencode-ai@latest # or bun/pnpm/yarn
scoop bucket add extras; scoop install extras/opencode # Windows
choco install opencode # Windows
brew install opencode # macOS and Linux
paru -S opencode-bin # Arch Linux
mise use --pin -g ubi:sst/opencode # Any OS
nix run nixpkgs#opencode # or github:sst/opencode for latest dev branch
# Then run
opencode
```

## Installation

Choose your preferred installation method:

### Package Managers

| Platform | Command |
|----------|---------|
| **npm/bun/pnpm/yarn** | `npm i -g opencode-ai@latest` |
| **Homebrew** (macOS/Linux) | `brew install opencode` |
| **Scoop** (Windows) | `scoop bucket add extras && scoop install extras/opencode` |
| **Chocolatey** (Windows) | `choco install opencode` |
| **Arch Linux** | `paru -S opencode-bin` |
| **mise** | `mise use --pin -g ubi:sst/opencode` |
| **Nix** | `nix run nixpkgs#opencode` |

> [!TIP]
> Remove versions older than 0.1.x before installing.

#### Installation Directory
### Custom Installation Directory

The install script respects the following priority order for the installation path:
The install script respects these paths in order of priority:

1. `$OPENCODE_INSTALL_DIR` - Custom installation directory
2. `$XDG_BIN_DIR` - XDG Base Directory Specification compliant path
3. `$HOME/bin` - Standard user binary directory (if exists or can be created)
1. `$OPENCODE_INSTALL_DIR` - Custom directory
2. `$XDG_BIN_DIR` - XDG compliant path
3. `$HOME/bin` - User binary directory
4. `$HOME/.opencode/bin` - Default fallback

```bash
# Examples
# Custom install examples
OPENCODE_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bash
XDG_BIN_DIR=$HOME/.local/bin curl -fsSL https://opencode.ai/install | bash
```

### Agents
---

## Agents

OpenCode provides multiple agents optimized for different workflows. Switch between them using the `Tab` key.

OpenCode includes two built-in agents you can switch between,
you can switch between these using the `Tab` key.
| Agent | Description | Best For |
|-------|-------------|----------|
| **build** | Full access agent (default) | Active development, writing code, running commands |
| **plan** | Read-only analysis agent | Exploring codebases, planning changes, code review |
| **general** | Complex task subagent | Multi-step searches, invoked via `@general` |

- **build** - Default, full access agent for development work
- **plan** - Read-only agent for analysis and code exploration
- Denies file edits by default
- Asks permission before running bash commands
- Ideal for exploring unfamiliar codebases or planning changes
The **plan** agent is particularly useful when:
- Exploring unfamiliar codebases safely
- Planning architectural changes before implementation
- Reviewing code without accidental modifications

Also, included is a **general** subagent for complex searches and multi-step tasks.
This is used internally and can be invoked using `@general` in messages.
Learn more about [agents in our documentation](https://opencode.ai/docs/agents).

Learn more about [agents](https://opencode.ai/docs/agents).
---

## Configuration

### Documentation
OpenCode can be configured via:
- `opencode.json` in your project root
- `~/.config/opencode/config.json` for global settings

For more info on how to configure OpenCode [**head over to our docs**](https://opencode.ai/docs).
For detailed configuration options, see our [documentation](https://opencode.ai/docs).

---

### Contributing
## Contributing

If you're interested in contributing to OpenCode, please read our [contributing docs](./CONTRIBUTING.md) before submitting a pull request.
We welcome contributions! Please read our [contributing guide](./CONTRIBUTING.md) before submitting a pull request.

### Building on OpenCode

If you are working on a project that's related to OpenCode and is using "opencode" as a part of its name; for example, "opencode-dashboard" or "opencode-mobile", please add a note to your README to clarify that it is not built by the OpenCode team and is not affiliated with us in anyway.
If you're creating a project related to OpenCode (e.g., "opencode-dashboard", "opencode-mobile"), please clarify in your README that it's a community project and not officially affiliated with the OpenCode team.

---

## FAQ

<details>
<summary><strong>What makes OpenCode different from other AI coding tools?</strong></summary>

### FAQ
OpenCode stands out through:

#### How is this different than Claude Code?
- **Complete transparency** - 100% open source codebase
- **Provider flexibility** - Use any LLM provider or local models via [OpenCode Zen](https://opencode.ai/zen)
- **Native LSP integration** - Language-aware assistance out of the box
- **Terminal-first philosophy** - Built by terminal power users for terminal power users
- **Extensible architecture** - Client/server design enables remote control and custom integrations

It's very similar to Claude Code in terms of capability. Here are the key differences:
</details>

- 100% open source
- Not coupled to any provider. Although we recommend the models we provide through [OpenCode Zen](https://opencode.ai/zen); OpenCode can be used with Claude, OpenAI, Google or even local models. As models evolve the gaps between them will close and pricing will drop so being provider-agnostic is important.
- Out of the box LSP support
- A focus on TUI. OpenCode is built by neovim users and the creators of [terminal.shop](https://terminal.shop); we are going to push the limits of what's possible in the terminal.
- A client/server architecture. This for example can allow OpenCode to run on your computer, while you can drive it remotely from a mobile app. Meaning that the TUI frontend is just one of the possible clients.
<details>
<summary><strong>Which AI providers are supported?</strong></summary>

#### What's the other repo?
OpenCode supports a wide range of providers:
- Anthropic Claude
- OpenAI
- Google (Gemini, Vertex AI)
- Azure OpenAI
- Amazon Bedrock
- OpenRouter
- Local models (via OpenAI-compatible APIs)

The other confusingly named repo has no relation to this one. You can [read the story behind it here](https://x.com/thdxr/status/1933561254481666466).
</details>

<details>
<summary><strong>What's the story behind the name?</strong></summary>

There's another repository with a similar name - you can [read about it here](https://x.com/thdxr/status/1933561254481666466).

</details>

---

**Join our community** [Discord](https://discord.gg/opencode) | [X.com](https://x.com/opencode)
## Community

Join our growing community of developers:

<p align="center">
<a href="https://discord.gg/opencode"><img src="https://img.shields.io/badge/Discord-Join%20Server-5865F2?style=for-the-badge&logo=discord&logoColor=white" alt="Discord" /></a>
<a href="https://x.com/opencode"><img src="https://img.shields.io/badge/X.com-Follow-000000?style=for-the-badge&logo=x&logoColor=white" alt="X.com" /></a>
</p>

57 changes: 29 additions & 28 deletions packages/opencode/src/cli/cmd/stats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { bootstrap } from "../bootstrap"
import { Storage } from "../../storage/storage"
import { Project } from "../../project/project"
import { Instance } from "../../project/instance"
import { UI } from "../ui"

interface SessionStats {
totalSessions: number
Expand Down Expand Up @@ -123,7 +124,7 @@ async function aggregateSessionStats(days?: number, projectFilter?: string): Pro
}

if (filteredSessions.length > 1000) {
console.log(`Large dataset detected (${filteredSessions.length} sessions). This may take a while...`)
UI.println(`Large dataset detected (${filteredSessions.length} sessions). This may take a while...`)
}

if (filteredSessions.length === 0) {
Expand Down Expand Up @@ -230,42 +231,42 @@ export function displayStats(stats: SessionStats, toolLimit?: number) {
}

// Overview section
console.log("┌────────────────────────────────────────────────────────┐")
console.log("│ OVERVIEW │")
console.log("├────────────────────────────────────────────────────────┤")
console.log(renderRow("Sessions", stats.totalSessions.toLocaleString()))
console.log(renderRow("Messages", stats.totalMessages.toLocaleString()))
console.log(renderRow("Days", stats.days.toString()))
console.log("└────────────────────────────────────────────────────────┘")
console.log()
UI.println("┌────────────────────────────────────────────────────────┐")
UI.println("│ OVERVIEW │")
UI.println("├────────────────────────────────────────────────────────┤")
UI.println(renderRow("Sessions", stats.totalSessions.toLocaleString()))
UI.println(renderRow("Messages", stats.totalMessages.toLocaleString()))
UI.println(renderRow("Days", stats.days.toString()))
UI.println("└────────────────────────────────────────────────────────┘")
UI.empty()

// Cost & Tokens section
console.log("┌────────────────────────────────────────────────────────┐")
console.log("│ COST & TOKENS │")
console.log("├────────────────────────────────────────────────────────┤")
UI.println("┌────────────────────────────────────────────────────────┐")
UI.println("│ COST & TOKENS │")
UI.println("├────────────────────────────────────────────────────────┤")
const cost = isNaN(stats.totalCost) ? 0 : stats.totalCost
const costPerDay = isNaN(stats.costPerDay) ? 0 : stats.costPerDay
const tokensPerSession = isNaN(stats.tokensPerSession) ? 0 : stats.tokensPerSession
console.log(renderRow("Total Cost", `$${cost.toFixed(2)}`))
console.log(renderRow("Avg Cost/Day", `$${costPerDay.toFixed(2)}`))
console.log(renderRow("Avg Tokens/Session", formatNumber(Math.round(tokensPerSession))))
UI.println(renderRow("Total Cost", `$${cost.toFixed(2)}`))
UI.println(renderRow("Avg Cost/Day", `$${costPerDay.toFixed(2)}`))
UI.println(renderRow("Avg Tokens/Session", formatNumber(Math.round(tokensPerSession))))
const medianTokensPerSession = isNaN(stats.medianTokensPerSession) ? 0 : stats.medianTokensPerSession
console.log(renderRow("Median Tokens/Session", formatNumber(Math.round(medianTokensPerSession))))
console.log(renderRow("Input", formatNumber(stats.totalTokens.input)))
console.log(renderRow("Output", formatNumber(stats.totalTokens.output)))
console.log(renderRow("Cache Read", formatNumber(stats.totalTokens.cache.read)))
console.log(renderRow("Cache Write", formatNumber(stats.totalTokens.cache.write)))
console.log("└────────────────────────────────────────────────────────┘")
console.log()
UI.println(renderRow("Median Tokens/Session", formatNumber(Math.round(medianTokensPerSession))))
UI.println(renderRow("Input", formatNumber(stats.totalTokens.input)))
UI.println(renderRow("Output", formatNumber(stats.totalTokens.output)))
UI.println(renderRow("Cache Read", formatNumber(stats.totalTokens.cache.read)))
UI.println(renderRow("Cache Write", formatNumber(stats.totalTokens.cache.write)))
UI.println("└────────────────────────────────────────────────────────┘")
UI.empty()

// Tool Usage section
if (Object.keys(stats.toolUsage).length > 0) {
const sortedTools = Object.entries(stats.toolUsage).sort(([, a], [, b]) => b - a)
const toolsToDisplay = toolLimit ? sortedTools.slice(0, toolLimit) : sortedTools

console.log("┌────────────────────────────────────────────────────────┐")
console.log("│ TOOL USAGE │")
console.log("├────────────────────────────────────────────────────────┤")
UI.println("┌────────────────────────────────────────────────────────┐")
UI.println("│ TOOL USAGE │")
UI.println("├────────────────────────────────────────────────────────┤")

const maxCount = Math.max(...toolsToDisplay.map(([, count]) => count))
const totalToolUsage = Object.values(stats.toolUsage).reduce((a, b) => a + b, 0)
Expand All @@ -281,11 +282,11 @@ export function displayStats(stats: SessionStats, toolLimit?: number) {

const content = ` ${toolName} ${bar.padEnd(20)} ${count.toString().padStart(3)} (${percentage.padStart(4)}%)`
const padding = Math.max(0, width - content.length - 1)
console.log(`│${content}${" ".repeat(padding)} │`)
UI.println(`│${content}${" ".repeat(padding)} │`)
}
console.log("└────────────────────────────────────────────────────────┘")
UI.println("└────────────────────────────────────────────────────────┘")
}
console.log()
UI.empty()
}

function formatNumber(num: number): string {
Expand Down
4 changes: 2 additions & 2 deletions packages/opencode/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,8 @@ const cli = yargs(hideBin(process.argv))

try {
await cli.parse()
} catch (e) {
let data: Record<string, any> = {}
} catch (e: unknown) {
let data: Record<string, unknown> = {}
if (e instanceof NamedError) {
const obj = e.toObject()
Object.assign(data, {
Expand Down
8 changes: 2 additions & 6 deletions packages/opencode/src/plugin/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@ export namespace Plugin {
const state = Instance.state(async () => {
const client = createOpencodeClient({
baseUrl: "http://localhost:4096",
// @ts-ignore - fetch type incompatibility
fetch: async (...args) => Server.App().fetch(...args),
fetch: (async (...args: Parameters<typeof fetch>) => Server.App().fetch(...args)) as typeof fetch,
})
const config = await Config.get()
const hooks = []
Expand Down Expand Up @@ -59,11 +58,8 @@ export namespace Plugin {
>(name: Name, input: Input, output: Output): Promise<Output> {
if (!name) return output
for (const hook of await state().then((x) => x.hooks)) {
const fn = hook[name]
const fn = hook[name] as ((input: Input, output: Output) => Promise<void>) | undefined
if (!fn) continue
// @ts-expect-error if you feel adventurous, please fix the typing, make sure to bump the try-counter if you
// give up.
// try-counter: 2
await fn(input, output)
}
return output
Expand Down
5 changes: 2 additions & 3 deletions packages/opencode/src/provider/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import { createOpenaiCompatible as createGitHubCopilotOpenAICompatible } from ".
export namespace Provider {
const log = Log.create({ service: "provider" })

const BUNDLED_PROVIDERS: Record<string, (options: any) => SDK> = {
const BUNDLED_PROVIDERS: Record<string, (options: Record<string, unknown>) => SDK> = {
"@ai-sdk/amazon-bedrock": createAmazonBedrock,
"@ai-sdk/anthropic": createAnthropic,
"@ai-sdk/azure": createAzure,
Expand All @@ -38,8 +38,7 @@ export namespace Provider {
"@ai-sdk/openai": createOpenAI,
"@ai-sdk/openai-compatible": createOpenAICompatible,
"@openrouter/ai-sdk-provider": createOpenRouter,
// @ts-ignore (TODO: kill this code so we dont have to maintain it)
"@ai-sdk/github-copilot": createGitHubCopilotOpenAICompatible,
"@ai-sdk/github-copilot": createGitHubCopilotOpenAICompatible as (options: Record<string, unknown>) => SDK,
}

type CustomLoader = (provider: ModelsDev.Provider) => Promise<{
Expand Down
Loading