Skip to content

OpenCode CLI

Official Documentation

Core docs:

Extended capabilities:

Deployment & CI/CD:

Project info:


Core Positioning Difference

OpenCode is not an "IDE companion CLI" like Cursor/Claude Code — it's a standalone open-source AI coding agent. It directly calls LLM APIs (via AI SDK + Models.dev), implements its own tool system (bash/read/write/edit/grep/glob, etc.), and manages sessions and context independently.

Key differences from Cursor Agent and Claude Code:

Cursor AgentClaude CodeOpenCode
NatureCLI mode of Cursor IDEAnthropic's CLI AgentStandalone open-source Agent
LLMCursor backend (with routing)Anthropic API75+ Providers to choose from
ToolsCursor built-inClaude Code built-inCustom-built + MCP + custom

Installation

Prerequisites: Node.js >= 18 (for npm) or Go >= 1.22 (for building from source)

Install via npm (recommended):

bash
npm install -g opencode-ai

Alternative — install via Go:

bash
go install github.com/anomalyco/opencode@latest

Verify installation:

bash
opencode --version

Actual Invocation Method (Verified in GolemBot)

Binary path: Depends on the Node version manager, e.g., ~/.nvm/versions/node/v22.10.0/bin/opencode

bash
opencode run "user message" \
  --format json \
  --model provider/model \
  [--session <sessionId>] \
  [--continue] \
  [--agent <agentName>] \
  [--attach http://localhost:4096]

PTY is not needed. OpenCode is a standard CLI; a regular child_process.spawn() works (same as Claude Code).

Key parameter descriptions:

ParameterEffectNotes
--format jsonOutput raw JSON events (NDJSON)Replaces the default formatted text output
--model provider/modelSpecify model (e.g., anthropic/claude-sonnet-4-5)Format is provider/model, unlike Claude Code's aliases
--session <id>Resume a specific sessionSession ID format: ses_XXXXXXXX
--continue / -cResume most recent session
--forkFork session (preserves history but with new ID)Must be combined with --session or --continue
--agent <name>Specify Agent (e.g., build, plan)Default is build (full-featured)
--attach <url>Connect to a running serve instanceAvoids cold start, recommended for production
--port <n>Specify local server portDefault is random port

JSON Output Format (--format json)

opencode run --format json outputs NDJSON. The event structure is completely different from Cursor/Claude Code's stream-json.

Observed Event Types

Error events:

json
{
  "type": "error",
  "timestamp": 1772335804867,
  "sessionID": "ses_3588dd885ffeJynG8QZsSrpPiL",
  "error": {
    "name": "APIError",
    "data": {
      "message": "Your credit balance is too low...",
      "statusCode": 400,
      "isRetryable": false
    }
  }
}

Session data structure (full format obtained via opencode export <sessionId>):

json
{
  "info": {
    "id": "ses_XXX",
    "title": "...",
    "time": { "created": 1772335636895, "updated": 1772335640665 }
  },
  "messages": [
    {
      "info": {
        "id": "msg_XXX",
        "role": "user|assistant",
        "agent": "build",
        "model": { "providerID": "...", "modelID": "..." },
        "cost": 0,
        "tokens": {
          "input": 11103, "output": 35, "reasoning": 33,
          "cache": { "read": 397, "write": 0 }
        },
        "finish": "stop"
      },
      "parts": [
        { "type": "text", "text": "..." },
        { "type": "step-start" },
        { "type": "reasoning", "text": "...", "time": { "start": 0, "end": 0 } },
        { "type": "step-finish", "reason": "stop", "cost": 0, "tokens": {} }
      ]
    }
  ]
}

Message parts type overview:

part.typeMeaningKey Fields
textText contenttext, time
step-startReasoning step started
step-finishReasoning step endedreason, cost, tokens
reasoningReasoning process (chain of thought)text, time
tool-invocationTool calltoolName, args, result

Format differences from Cursor/Claude Code:

AspectCursorClaude CodeOpenCode
Streaming format--output-format stream-json--output-format stream-json--format json
Text eventstype:"assistant"type:"assistant" + content[].type:"text"part.type: text
Tool callstype:"tool_call" + started/completedtype:"assistant" + tool_use blockpart.type: tool-invocation
End eventstype:"result"type:"result"step-finish (with cost/tokens)
Error eventstype:"result" + is_error:truetype:"result" + is_error:truetype:"error" + error object
Metadataduration_msduration_ms, total_cost_usd, num_turnscost, tokens (with reasoning + cache breakdown)
ANSINo (clean stdout since 2026.02+)NoNo

Note: The streaming event structure above has been verified through real-world testing with OpenRouter + Anthropic models. The OpenCodeEngine in GolemBot has been fully implemented and passes e2e tests. Key observation: OpenCode sends text content in full chunks (not character-level deltas), similar to Claude Code's behavior without --include-partial-messages.


Alternative: Integration via HTTP Server API

OpenCode provides a full HTTP Server (OpenAPI 3.1 spec), giving GolemBot two integration approaches:

Approach A: CLI mode (same as Cursor/Claude Code)

bash
opencode run --format json "prompt"

Approach B: HTTP Server mode (OpenCode exclusive)

bash
opencode serve --port 4096
# → POST /session/:id/message { parts: [{ type: "text", text: "prompt" }] }
# → GET /event (SSE stream)

Key HTTP Server API endpoints:

MethodPathPurpose
POST/sessionCreate new session
POST/session/:id/messageSend message (synchronous, waits for completion)
POST/session/:id/prompt_asyncSend message asynchronously
POST/session/:id/abortAbort a running session
GET/session/:id/messageGet message list
GET/eventSSE event stream
GET/global/healthHealth check
DELETE/session/:idDelete session
POST/session/:id/forkFork session
POST/session/:id/shareShare session

Advantages of HTTP mode: avoids the cold start of each opencode run (5-10s), reusing a single server instance for multiple conversations.


Session Management

OperationCLI CommandDescription
List sessionsopencode session list --format jsonReturns JSON array
Resume sessionopencode run --session <id> "message"
Resume most recentopencode run --continue "message"
Fork sessionopencode run --session <id> --fork "message"
Export sessionopencode export <id>Full JSON (all messages and parts)
Import sessionopencode import <file|url>
Delete sessionHTTP: DELETE /session/:idNo direct CLI command yet
View statisticsopencode statsToken usage and cost statistics

Session ID format: ses_XXXXXXXXXXXXXXXX (different from Cursor/Claude Code's UUID format)


Authentication Methods

OpenCode supports 75+ LLM Providers; the authentication method depends on the chosen Provider:

MethodUse CaseSetup
opencode auth login / /connectLocal developmentInteractive within TUI, credentials stored to ~/.local/share/opencode/auth.json
Provider environment variablesCI/CD, scriptsANTHROPIC_API_KEY, OPENAI_API_KEY, OPENROUTER_API_KEY, etc.
OpenCode Zen / GoOfficial hosted ProviderUnified API Key, verified by the OpenCode team
.env fileProject-level configOpenCode auto-loads .env from the project directory at startup

Common Provider environment variables:

ProviderEnvironment VariableModel Format Example
AnthropicANTHROPIC_API_KEYanthropic/claude-sonnet-4-5
OpenAIOPENAI_API_KEYopenai/gpt-5
GoogleGOOGLE_GENERATIVE_AI_API_KEYgoogle/gemini-2.5-pro
OpenRouterOPENROUTER_API_KEYopenrouter/anthropic/claude-sonnet-4-5
Amazon BedrockAWS_* seriesamazon-bedrock/...

Difference from Cursor/Claude Code: Cursor only needs CURSOR_API_KEY, Claude Code only needs ANTHROPIC_API_KEY. Because OpenCode supports multiple Providers, you must set the environment variable corresponding to the chosen Provider. When integrating with GolemBot's InvokeOpts.apiKey, you need to know the target Provider to set the correct environment variable name.


Skill Mechanism

OpenCode's Skill system is highly compatible with Claude Code. Search paths:

LocationScopeDescription
.opencode/skills/*/SKILL.mdProject-levelOpenCode native path
.claude/skills/*/SKILL.mdProject-levelClaude Code compatible (can be disabled via OPENCODE_DISABLE_CLAUDE_CODE_SKILLS=1)
.agents/skills/*/SKILL.mdProject-levelUniversal standard path
~/.config/opencode/skills/*/SKILL.mdGlobalUser-level
~/.claude/skills/*/SKILL.mdGlobalClaude Code compatible
~/.agents/skills/*/SKILL.mdGlobalUniversal standard

Skill discovery mechanism: OpenCode traverses upward from the current directory to the git worktree root, loading all matching skills/*/SKILL.md along the way.

On-demand loading: At Agent startup, only Skill names and descriptions are visible (injected into the skill tool description); full content is loaded when the Agent decides to use it via the skill({ name: "xxx" }) tool call.

SKILL.md frontmatter requirements:

yaml
---
name: git-release          # Required, must match directory name, lowercase + hyphens
description: Create releases  # Required, 1-1024 characters
license: MIT               # Optional
compatibility: opencode    # Optional
metadata:                  # Optional, string-to-string map
  audience: maintainers
---

GolemBot's injection strategy options:

  • Option 1: symlink to .opencode/skills/ (most canonical)
  • Option 2: symlink to .agents/skills/ (universal standard, other Agents can read it in the future)
  • Option 3: reuse Claude Code's .claude/skills/ symlink (OpenCode reads it compatibly)

Rules / AGENTS.md

OpenCode's rules system is perfectly compatible with GolemBot's AGENTS.md generation mechanism:

LocationPriorityDescription
AGENTS.md (project root)HighOpenCode native, takes precedence over CLAUDE.md
CLAUDE.md (project root)LowOnly used when there is no AGENTS.md
~/.config/opencode/AGENTS.mdGlobalUser-level rules
~/.claude/CLAUDE.mdGlobal fallbackOnly used when there is no global AGENTS.md

Additional instruction files: The instructions field in opencode.json can reference extra files (supports globs and remote URLs):

json
{ "instructions": ["CONTRIBUTING.md", "docs/guidelines.md", ".cursor/rules/*.md"] }

Implications for GolemBot: The AGENTS.md generated by GolemBot during init is automatically consumed by OpenCode — no additional configuration needed.


Permissions System

OpenCode permissions are configured via opencode.json, with finer granularity than Cursor/Claude Code:

json
{
  "permission": {
    "*": "allow",
    "bash": { "*": "ask", "git *": "allow", "rm *": "deny" },
    "edit": { "*": "allow", "*.env": "deny" }
  }
}

Three levels: "allow" (auto-execute), "ask" (request approval), "deny" (forbidden)

Default permissions: Most operations default to "allow"; only .env files default to "deny". No parameter equivalent to --dangerously-skip-permissions is needed.

Headless mode status (v1.1.28):

  • opencode run in non-interactive mode has known bugs (PR #14607, not yet merged)
  • Bug 1: question tool hangs in non-interactive mode (session deny rules not propagated to tool filter layer)
  • Bug 2: Permissions configured as "ask" auto-reject in non-interactive mode, causing tool failures
  • Fix (in PR): "ask" permissions auto-approve in non-interactive mode; adds --no-auto-approve flag
  • Current workaround: Set all permissions to allow via OPENCODE_PERMISSION='{"*":"allow"}' or opencode.json

Agent System

OpenCode has a built-in Agent hierarchy (GolemBot can leverage it via the --agent parameter):

Primary Agents:

  • build — Default, full-featured (can read/write files, execute commands)
  • plan — Read-only mode, analyze and plan but don't modify files

Subagents:

  • general — General purpose, can execute multiple tasks in parallel
  • explore — Read-only, fast code search

Custom Agents are supported: define via the agent field in opencode.json or .opencode/agents/*.md files.


MCP Support

Configured via opencode.json (not .cursor/mcp.json or .claude/mcp.json):

json
{
  "mcp": {
    "my-server": {
      "type": "local",
      "command": ["npx", "-y", "my-mcp-command"],
      "enabled": true
    },
    "remote-server": {
      "type": "remote",
      "url": "https://mcp.example.com/mcp"
    }
  }
}

Supports two types: local (command spawn) and remote (URL + optional OAuth).


Plugin System

OpenCode provides a full plugin hook mechanism (neither Cursor nor Claude Code has this capability):

typescript
export const MyPlugin = async ({ project, client, $ }) => ({
  "tool.execute.before": async (input, output) => { /* Before tool execution */ },
  "tool.execute.after": async (input, output) => { /* After tool execution */ },
  event: async ({ event }) => { /* Event listener */ },
});

Plugins are placed in .opencode/plugins/ (project-level) or ~/.config/opencode/plugins/ (global), and can also be installed as npm packages.


GitHub Actions Integration

yaml
- uses: anomalyco/opencode/github@latest
  with:
    model: anthropic/claude-sonnet-4-20250514
    # prompt: "optional custom prompt"
    # agent: "build"
  env:
    ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}

Supported trigger events: issue_comment (/opencode or /oc), pull_request_review_comment, issues, pull_request, schedule, workflow_dispatch


Configuration Files

FileLocationContent
opencode.jsonProject root directoryProject-level config (models, permissions, MCP, Agents, tools, etc.)
opencode.json~/.config/opencode/Global config
auth.json~/.local/share/opencode/Provider credentials
.opencode/agents/*.mdProject-levelCustom Agents
.opencode/plugins/*.tsProject-levelCustom plugins
.opencode/tools/*.tsProject-levelCustom tools
.opencode/skills/*/SKILL.mdProject-levelSkill definitions

Configuration precedence (later overrides earlier): remote config → global → project → custom path → OPENCODE_CONFIG_CONTENT environment variable


Known Pitfalls & GolemBot Adaptation Notes

  1. Slow cold start (5-10s) — OpenCode loads Provider configs, MCP servers, etc. at startup, much slower than Cursor/Claude Code. For production, use opencode serve + --attach mode to reuse a server instance
  2. --format json event structure is completely different from Cursor/Claude Code — Cannot reuse parseStreamLine() or parseClaudeStreamLine(); requires an independent parseOpenCodeStreamLine()
  3. Headless mode has known bugs — In v1.1.28, opencode run's question tool may hang, and "ask" permissions auto-reject. Recommend explicitly setting permission: "allow" as a workaround
  4. Multi-Provider authentication is complex — Unlike Cursor/Claude Code which each need only one environment variable, OpenCode requires the API Key corresponding to the chosen Provider. When integrating with GolemBot's InvokeOpts.apiKey, you need to know the target Provider to set the correct environment variable name
  5. Skill multi-path auto-discovery — OpenCode simultaneously reads .opencode/skills/, .claude/skills/, .agents/skills/. If GolemBot injects skills for both Claude Code and OpenCode, there's no conflict (identical Skills are only loaded once)
  6. AGENTS.md auto-consumption — The AGENTS.md generated by GolemBot during init is automatically consumed by OpenCode — a positive compatibility feature
  7. Session ID format is differentses_XXXXXXXX instead of UUID; GolemBot's session storage layer needs to accommodate this
  8. HTTP Server API is a better integration approach — Compared to CLI spawn mode, HTTP mode eliminates cold start, supports abort operations (POST /session/:id/abort), and may be a better Engine implementation
  9. opencode.json needs to be generated during init — Similar to Cursor's .cursor/cli.json, OpenCode's project config needs to be generated during workspace initialization
  10. OpenCode iterates extremely fast — As of 2026-03, it's at v1.1.28; the API may change frequently, so keep an eye on the changelog

Released under the MIT License.