25 KiB
| name | description | version | author | license | metadata | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| opencode | Delegate coding to OpenCode CLI (features, PR review). | 1.3.0 | Hermes Agent | MIT |
|
OpenCode CLI
Use OpenCode as an autonomous coding worker orchestrated by Hermes terminal/process tools. OpenCode is a provider-agnostic, open-source AI coding agent with a TUI and CLI.
When to Use — THE FIRST RULE
CRITICAL RULE: Default to opencode for ALL coding work. Do NOT self-execute (patch/sed/manual edit) unless the user explicitly says "你直接改" or the change is trivially mechanical (<3 lines). The user called this out explicitly: every time I think "it's small enough" and patch code myself instead of delegating to opencode, it's wrong. Load this skill at session start to reinforce this rule.
When to use opencode
- ALL coding tasks (features, bug fixes, refactoring, tests, code review/inspection)
- Code audit/inspection — Use
opencode run '全面审查代码...'for systematic code review. Works well for finding security issues, bugs, code quality problems. - Long-running coding sessions with progress checks
- Parallel task execution in isolated workdirs
When NOT to use opencode
- User explicitly says "你直接改" or "直接改吧"
- Trivially mechanical changes (<3 lines, no logic changed)
- Config-only changes (e.g. editing YAML/JSON configs)
Installation
When the user asks to install/update OpenCode, always run the install command — do not just display docs. Execute immediately.
Install / Upgrade (npm)
# npm may not be in PATH — use nvm-managed node's npm
export NVM_DIR=~/.config/nvm
source $NVM_DIR/nvm.sh
nvm use v22.22.2
# Install to custom prefix (avoids /usr/lib/node_modules permission issues)
mkdir -p ~/.npm-global
npm config set prefix ~/.npm-global
~/.npm-global/bin/npm install -g opencode-ai@latest --registry https://registry.npmmirror.com
Timeout: 300s — the install can take several minutes.
Wrapper script (required for custom provider injection)
Create /home/muc/.local/bin/opencode as a wrapper that patches models.json and injects the API key on every call:
cat > ~/.local/bin/opencode << 'WRAPPER'
#!/bin/bash
export PATH="$HOME/.npm-global/bin:$HOME/.opencode/bin:$PATH"
python3 -c "
import json, os
p = os.path.expanduser('~/.cache/opencode/models.json')
try:
d = json.load(open(p))
if 'qwen' not in d:
d['qwen'] = {
'id': 'qwen',
'env': ['QWEN_API_KEY'],
'npm': '@ai-sdk/openai-compatible',
'api': 'http://127.0.0.1:3000/v1',
'name': 'Qwen',
'doc': 'http://127.0.0.1:3000',
'models': {
'qwen3-coder-480b-a35b-instruct': {
'id': 'qwen/qwen3-coder-480b-a35b-instruct',
'name': 'Qwen3-Coder-480B',
'family': 'qwen',
'attachment': True,
'reasoning': True,
'tool_call': True,
'temperature': True,
'knowledge': '2025-04',
'modalities': {'input': ['text'], 'output': ['text']},
'open_weights': True,
'limit': {'context': 1000000, 'output': 65536}
}
}
}
json.dump(d, open(p, 'w'), indent=2)
except: pass
"
export QWEN_API_KEY=sk-YOUR-KEY-HERE
exec /home/muc/.npm-global/bin/opencode "$@"
WRAPPER
chmod +x ~/.local/bin/opencode
Then ensure ~/.local/bin is first in PATH (add to ~/.bashrc if needed):
export PATH="$HOME/.local/bin:$PATH"
Verify
opencode --version # should print e.g. 1.15.10
DeepSeek Provider (This System — Verified 2026-06-20)
Binary: /home/muc/bin/opencode (standalone ELF, v1.15.10, NOT npm-installed)
Setup in ~/.bashrc:
export DEEPSEEK_API_KEY="sk-b12...6bbd"
alias opencode='DEEPSEEK_API_KEY="sk-b12...6bbd" opencode -m deepseek/deepseek-v4-flash'
Available DeepSeek models:
deepseek/deepseek-chat
deepseek/deepseek-reasoner
deepseek/deepseek-v4-flash # alias default
deepseek/deepseek-v4-pro
Auth check:
opencode providers list
# → Credentials: 0
# → Environment: DEEPSEEK_API_KEY ✓ (1 env var detected automatically)
Important: The binary is at /home/muc/bin/opencode, which takes PATH precedence over ~/.local/bin/opencode (the old npm-installed wrapper). No wrapper script needed — DeepSeek is a built-in recognized provider. The alias handles env var injection + default model.
If opencode models deepseek returns "Error: Provider not found", the env var isn't reaching the binary. Explicitly pass: DEEPSEEK_API_KEY="sk-b12...6bbd" opencode models deepseek to verify.
Custom Providers (Local / OpenAI-Compatible APIs)
OpenCode has a cached provider registry at ~/.cache/opencode/models.json. To add a custom OpenAI-compatible API (e.g. a local LLM server, a proxy, or a provider not natively supported), add an entry to this file.
Step 1: Add the provider to models.json
import json
data = json.load(open(os.path.expanduser("~/.cache/opencode/models.json")))
data["provider-id"] = {
"id": "provider-id",
"env": ["PROVIDER_ID_API_KEY"], # env var name OpenCode will look for
"npm": "@ai-sdk/openai-compatible",
"api": "https://your-api-base-url/v1", # must include /v1 suffix
"name": "Provider Display Name",
"doc": "https://provider.docs.url",
"models": {
"model-slug": {
"id": "provider-id/model-slug", # full id shown in `opencode models`
"name": "Model Display Name",
"family": "model-family",
"attachment": True,
"reasoning": True, # or False
"tool_call": True,
"temperature": True,
"knowledge": "2025-04",
"release_date": "2025-04-29",
"modalities": {"input": ["text"], "output": ["text"]},
"open_weights": True,
"limit": {"context": 1000000, "output": 65536}
}
}
}
json.dump(data, open(os.path.expanduser("~/.cache/opencode/models.json"), "w"), indent=2)
Step 2: Set the environment variable and verify
export PROVIDER_ID_API_KEY=your-api-key-here
opencode models provider-id # should list your model(s)
opencode run 'Respond with exactly: OK' --model provider-id/model-slug # smoke test
Step 3 (optional): Make it permanent
Add to ~/.bashrc or ~/.zshrc:
export PROVIDER_ID_API_KEY=your-api-key-here
What does NOT work
~/.config/opencode/config.jsonwith aproviderskey — throws"Unrecognized key: providers".opencode providers loginwith a custom base URL — times out in non-interactive shells; also throwsundefined is not an objectwhen a URL is passed as a positional argument.provider/modelformat for models not registered inmodels.json—ProviderModelNotFoundError.
Verified working pattern
For a local server at http://127.0.0.1:3000/v1 with a Qwen model:
env var: QWEN_API_KEY
provider: qwen (from models.json entry)
model: qwen/qwen3-coder-480b-a35b-instruct (full ID including provider prefix)
api: http://127.0.0.1:3000/v1
npm: @ai-sdk/openai-compatible
A reference implementation is saved at references/custom-provider-models-json.md.
Permanent Setup: Wrapper Script (Recommended)
Because models.json is reset on every opencode run (not just interactive mode), and interactive login doesn't work in non-interactive shells, the only reliable approach for automation/script environments is a wrapper script.
On this system, the wrapper is already installed at /home/muc/.local/bin/opencode. It:
- Patches
~/.cache/opencode/models.jsonto add theqwenprovider on every invocation - Injects
QWEN_API_KEYinto the environment - Delegates to the real binary at
/home/muc/.local/bin/opencode-bin
To reinstall or inspect:
# Backup original binary
mv /home/muc/.local/bin/opencode /home/muc/.local/bin/opencode-bin
# Create wrapper at /home/muc/.local/bin/opencode
cat > /home/muc/.local/bin/opencode << 'WRAPPER'
#!/bin/bash
python3 -c "
import json,os
p=os.path.expanduser('~/.cache/opencode/models.json')
d=json.load(open(p))
if 'qwen' not in d:
d['qwen']={'id':'qwen','env':['QWEN_API_KEY'],'npm':'@ai-sdk/openai-compatible','api':'http://127.0.0.1:3000/v1','name':'Qwen','doc':'http://127.0.0.1:3000','models':{'qwen3-coder-480b-a35b-instruct':{'id':'qwen/qwen3-coder-480b-a35b-instruct','name':'Qwen3-Coder-480B','family':'qwen','attachment':True,'reasoning':True,'tool_call':True,'temperature':True,'knowledge':'2025-04','modalities':{'input':['text'],'output':['text']},'open_weights':True,'limit':{'context':1000000,'output':65536}}}
json.dump(d,open(p,'w'),indent=2)
" 2>/dev/null
export QWEN_API_KEY=YOUR_KEY_HERE
exec /home/muc/.local/bin/opencode-bin "$@"
WRAPPER
chmod +x /home/muc/.local/bin/opencode
After installing, usage is simply:
opencode run 'Your task here' --model qwen/qwen3-coder-480b-a35b-instruct
# No env var setup needed — wrapper handles it
Provider Setup — Local / Custom API Endpoints
OpenCode syncs ~/.cache/opencode/models.json from network on every run, overwriting any custom provider entries. To register a custom provider (e.g. local LLM endpoint):
- Patch models.json before each opencode run. Use a wrapper script (see below).
- Provider must use
@ai-sdk/openai-compatibleas the npm package. - Environment variable name must match the
envfield in the provider config.
Critical: Wrapper Script Required
Because models.json is re-synced on every run, manual edits are never persistent. The fix is a wrapper script at ~/.local/bin/opencode that:
- Patches models.json to add the custom provider entry
- Sets the required API key environment variable
- Delegates to the real binary
Example wrapper for a local Qwen endpoint:
#!/bin/bash
# Patch custom provider into models.json (survives re-sync)
python3 -c "
import json, os
p = os.path.expanduser('~/.cache/opencode/models.json')
d = json.load(open(p))
if 'qwen' not in d:
d['qwen'] = {
'id': 'qwen',
'env': ['QWEN_API_KEY'],
'npm': '@ai-sdk/openai-compatible',
'api': 'http://127.0.0.1:3000/v1',
'name': 'Qwen',
'doc': 'http://127.0.0.1:3000',
'models': {
'qwen3-coder-480b-a35b-instruct': {
'id': 'qwen/qwen3-coder-480b-a35b-instruct',
'name': 'Qwen3-Coder-480B',
'family': 'qwen',
'attachment': True,
'reasoning': True,
'tool_call': True,
'temperature': True,
'knowledge': '2025-04',
'modalities': {'input': ['text'], 'output': ['text']},
'open_weights': True,
'limit': {'context': 1000000, 'output': 65536}
}
}
}
json.dump(d, open(p, 'w'), indent=2)
" 2>/dev/null
export QWEN_API_KEY=sk-YOUR-KEY-HERE
exec /path/to/opencode-bin "$@"
The real binary at ~/.local/bin/opencode-bin (renamed from original) is called via exec to replace the wrapper process.
Dual Binary Conflict (This System)
There are TWO opencode binaries on this system:
/home/muc/.local/bin/opencode— npm-installed (1.14.39), wrapper script/home/muc/.opencode/bin/opencode— older standalone ELF binary (2025-06-27)
The older binary takes precedence if it appears first in PATH, causing:
"Error: agent coder not found"in TUI mode- Custom provider missing even with wrapper
- Wrapper never invoked
Fix: Remove the older binary or ensure ~/.local/bin precedes ~/.opencode/bin in PATH:
export PATH=$HOME/.local/bin:$HOME/.opencode/bin:$PATH
If still broken, rename/remove the old binary:
mv /home/muc/.opencode/bin/opencode /home/muc/.opencode/bin/opencode.bak
PATH Ordering Pitfall
If /home/muc/.opencode/bin appears before ~/.local/bin in PATH, the old opencode binary is invoked instead of the wrapper. This causes "Error: agent coder not found" in TUI mode or missing custom providers. Fix: ensure ~/.local/bin comes first in PATH (see Dual Binary Conflict above).
TUI Mode Usage
启动并选模型
# 方法1:命令行直接指定(推荐)
opencode -m qwen/qwen3-coder-480b-a35b-instruct /path/to/project
# 方法2:启动后再选
opencode /path/to/project
# 按 Ctrl+X M → 输入 qwen/qwen3-coder-480b-a35b-instruct → 回车
TUI 启动后如果提示 "No provider selected",按 Ctrl+X M 切换 model。
退出 TUI
Ctrl+C(不要输入/exit,那是无效命令)
Troubleshooting
"Error: agent coder not found" — Two Distinct Causes
Cause 1 (documented): Old binary at /home/muc/.opencode/bin/opencode takes precedence over wrapper. Fix: remove or rename it.
Cause 2 (v0.0.55 architecture bug): The agent subsystem itself fails to initialize even with the correct binary. This happens in ALL modes (TUI, -p, run). The error fires before any provider/model resolution.
Diagnosis:
# Any of these failing with "agent coder not found" = Cause 2
opencode --version # prints 0.0.55
opencode -p "hello"
opencode run "hello"
opencode # TUI mode
Fix for Cause 2: Update opencode to a newer version. The bug is in v0.0.55's agent initialization logic. Reinstall:
curl -LsSf https://ast.opencode.ai/opencode-installer.sh | sh
# or
npm install -g opencode-ai@latest
Workaround: None confirmed working for v0.0.55. The user (牧尘) must update opencode before delegation can proceed.
Direct Orchestration Workflow (No Feishu Bridge)
When the user says "you directly control opencode" — run opencode as a subprocess via terminal() with monitoring via process(). No Feishu or ACP bridge required.
Pattern:
# Start in background with pty, get session_id
terminal(command="opencode -c /path/to/project", background=true, pty=true, notify_on_complete=True)
# → Returns session_id
# Monitor progress
process(action="poll", session_id="<id>")
process(action="log", session_id="<id>")
# Send input
process(action="submit", session_id="<id>", data="task description")
# Exit
process(action="write", session_id="<id>", data="\x03")
Timeout: 300s for most tasks. For opencode v0.0.55 with "agent coder not found" — no timeout will help; must update opencode first.
User preference: OpenCode has no Feishu integration. I (小唯) control opencode directly as subprocess orchestrator — no intermediary channel needed.
Common Flags
| Flag | Use |
|---|---|
-m, --model provider/model |
Model to use (TUI and run mode) |
opencode run 'prompt' |
One-shot execution, exit when done |
-p, --prompt 'prompt' |
Non-interactive prompt (TUI mode) |
-c, --cwd DIR |
Working directory |
-c, --continue |
Continue last session |
-s, --session ID |
Continue specific session |
-d, --debug |
Debug logging |
--thinking |
Show model thinking blocks |
Pitfalls
- ⚠️ Behavioral trap: self-executing instead of delegating — When a coding task comes up, the default reaction is to fix it myself (patch, sed, manual edit). This is WRONG. Always ask: "Is this coding work? → opencode run." The user explicitly expects opencode to be the default for code changes. Exception: user says "你直接改" or the change is purely mechanical (<3 lines).
- ⚠️ Task splitting pattern for complex tasks — A single
opencode runwith a multi-part task (e.g. "检查 A, B, C 系统") will time out at 300s. Fix: split into focused sub-tasks and run in parallel viadelegate_task:
Each sub-task should be focused enough to complete within 180s. If a sub-task still times out, split further.# ❌ Don't: single opencode run with 4 sub-tasks opencode run "检查 A, B, C, D 四个系统" # → times out after 300s, partial results # ✅ Do: split into parallel focused tasks delegate_task [goal="opencode run '检查系统A'", goal="opencode run '检查系统B'"] - Never use
--model— it's-mfor both TUI and run modes. - models.json is re-synced on every run — custom providers require wrapper patching.
/exitis not valid in TUI — useCtrl+Cto exit.- Enter may need to be pressed twice to submit in TUI.
- PATH mismatch: if
opencode --helpshows different flags thanopencode -m, the wrong binary is being called. Checkwhich -a opencodeand PATH order. - Interactive TUI requires no pty flag — just run
opencodeoropencode /path/to/project. - models.json location:
~/.cache/opencode/models.json— not in config dir. - delegate_task 超时(600s):复杂任务(如多文件 TUI 应用)容易超时,超时后直接用
opencode run执行,不要重试 delegate。 - ⚠️ Workdir 不支持中文字符:
workdir路径含中文(如/home/muc/mc/小唯/)会报Blocked: workdir contains disallowed character。用--cwd指定不含中文的路径,或在无中文目录执行。 - ⚠️ Go build: 必须在模块目录内编译 —
cd /tmp/memoryweave/go && go build -o zhiyid-new ./cmd/zhiyid。从根目录go build ./...会报no required module provides package那是 lint 的假阳性;只在模块目录内的编译报错才是真正的编译错误。 - ⚠️ Go: 跨包调用需导出函数 — 私有函数(如
computeBM25Score)在自身包内可用,被其他包调用时必须导出为ComputeBM25Score并用package.ComputeBM25Score()语法。新增sort.Slice需要 import "sort"。 - ⚠️ Go: switch/case 预声明变量 — 如果 switch 内多个 case 需要读/写同一变量且 case 有 return,必须
var results Type在 switch 前预声明;不能在 case 内用:=。 - ⚠️ 数据库优先于启发式分析:当修改数据验证/分类逻辑时,先检查数据库是否已有该字段。
Workflow Selection
| 场景 | 推荐方式 |
|---|---|
| 单次 bounded 任务(写文件/跑脚本) | opencode run '...' |
| 迭代式开发(TUI 交互) | opencode 交互模式 + process() |
| 复杂多文件项目 | opencode run + 分步任务拆分 |
| delegate_task 超时 | 切换 opencode run,不要重试 |
| 远程服务器部署(代码在远端) | 1) sync remote→local 2) opencode edit 3) scp back 4) restart service 5) test via SSH |
Remote Deployment Workflow
When the target codebase runs on a remote server (not local), use this five-step pattern:
# Step 1: Sync files from remote to local workdir
sshpass -p '$PASS' ssh root@$HOST "cat /path/to/file.py" > ./file.py
# Step 2: opencode edits local files
opencode run 'fix bug in file.py' -m deepseek/deepseek-v4-flash
# Step 3: Deploy back to server
sshpass -p '$PASS' scp ./file.py root@$HOST:/remote/path/
sshpass -p '$PASS' ssh root@$HOST "chown www:www /remote/path/file.py"
# Step 4: Restart the server process
sshpass -p '$PASS' ssh root@$HOST "pkill -f gunicorn && sleep 2 && cd /remote/path && nohup ... &"
# Step 5: Test via SSH curl
sshpass -p '$PASS' ssh root@$HOST "curl -s -X POST http://127.0.0.1:8080/api/endpoint ..."
Key differences from local development:
opencode runedits local copies, not remote files — after fix, must scp back- Service restart is manual (no hot reload on most production servers)
- Test via SSH pipe, not local curl
- Python test scripts: write to
/tmp/test.py, thencat /tmp/test.py | sshpass -p '$PASS' ssh host "python3"to avoid heredoc quoting issues with f-strings/emojis
路径规范
- 工作目录避免中文(
--cwd不支持中文路径,会报disallowed character) - 涉及中文路径时:cd 到
/home/muc再执行,用绝对路径访问目标 - 例:
opencode run 'task' --cwd /home/muc(而不是/home/muc/mc/小唯/)
One-Shot Tasks
Use opencode run for bounded, non-interactive tasks.
Timeout: Set timeout=300 for most tasks. For complex multi-file tasks, split into smaller sub-tasks (see Pitfalls) rather than raising timeout — a 300s timeout for a focused task is usually enough. If a task regularly needs more time, it's too broad.
terminal(command="opencode run 'Add retry logic to API calls and update tests' --model qwen/qwen3-coder-480b-a35b-instruct", workdir="~/project")
Attach context files with -f:
terminal(command="opencode run 'Review this config for security issues' -f config.yaml -f .env.example", workdir="~/project")
Show model thinking with --thinking:
terminal(command="opencode run 'Debug why tests fail in CI' --thinking", workdir="~/project")
Force a specific model:
terminal(command="opencode run 'Refactor auth module' --model openrouter/anthropic/claude-sonnet-4", workdir="~/project")
Interactive Sessions (Background)
For iterative work requiring multiple exchanges, start the TUI in background:
terminal(command="opencode", workdir="~/project", background=true, pty=true)
# Returns session_id
# Send a prompt
process(action="submit", session_id="<id>", data="Implement OAuth refresh flow and add tests")
# Monitor progress
process(action="poll", session_id="<id>")
process(action="log", session_id="<id>")
# Send follow-up input
process(action="submit", session_id="<id>", data="Now add error handling for token expiry")
# Exit cleanly — Ctrl+C
process(action="write", session_id="<id>", data="\x03")
# Or just kill the process
process(action="kill", session_id="<id>")
Important: Do NOT use /exit — it is not a valid OpenCode command and will open an agent selector dialog instead. Use Ctrl+C (\x03) or process(action="kill") to exit.
TUI Keybindings
| Key | Action |
|---|---|
Enter |
Submit message (press twice if needed) |
Tab |
Switch between agents (build/plan) |
Ctrl+P |
Open command palette |
Ctrl+X L |
Switch session |
Ctrl+X M |
Switch model |
Ctrl+X N |
New session |
Ctrl+X E |
Open editor |
Ctrl+C |
Exit OpenCode |
Resuming Sessions
After exiting, OpenCode prints a session ID. Resume with:
terminal(command="opencode -c", workdir="~/project", background=true, pty=true) # Continue last session
terminal(command="opencode -s ses_abc123", workdir="~/project", background=true, pty=true) # Specific session
Procedure
- Verify tool readiness:
terminal(command="opencode --version")terminal(command="opencode providers list")
- For bounded tasks, use
opencode run '...'(no pty needed). - For iterative tasks, start
opencodewithbackground=true, pty=true. - Monitor long tasks with
process(action="poll"|"log"). - If OpenCode asks for input, respond via
process(action="submit", ...). - Exit with
process(action="write", data="\x03")orprocess(action="kill"). - Summarize file changes, test results, and next steps back to user.
PR Review Workflow
OpenCode has a built-in PR command:
terminal(command="opencode pr 42", workdir="~/project", pty=true)
Or review in a temporary clone for isolation:
terminal(command="REVIEW=$(mktemp -d) && git clone https://github.com/user/repo.git $REVIEW && cd $REVIEW && opencode run 'Review this PR vs main. Report bugs, security risks, test gaps, and style issues.' -f $(git diff origin/main --name-only | head -20 | tr '\n' ' ')", pty=true)
Parallel Work Pattern
Use separate workdirs/worktrees to avoid collisions:
terminal(command="opencode run 'Fix issue #101 and commit'", workdir="/tmp/issue-101", background=true, pty=true)
terminal(command="opencode run 'Add parser regression tests and commit'", workdir="/tmp/issue-102", background=true, pty=true)
process(action="list")
Session & Cost Management
List past sessions:
terminal(command="opencode session list")
Check token usage and costs:
terminal(command="opencode stats")
terminal(command="opencode stats --days 7 --models anthropic/claude-sonnet-4")
Smoke test:
terminal(command="opencode run 'Respond with exactly: OPENCODE_SMOKE_OK'")
Success criteria:
- Output includes
OPENCODE_SMOKE_OK - Command exits without provider/model errors
- For code tasks: expected files changed and tests pass
- If authentication was via environment variables,
opencode providers listshould show "1 environment variable"
Rules
- Prefer
opencode runfor one-shot automation — it's simpler and doesn't need pty. - Use interactive background mode only when iteration is needed.
- Always scope OpenCode sessions to a single repo/workdir.
- For long tasks, provide progress updates from
processlogs. - Report concrete outcomes (files changed, tests, remaining risks).
- Exit interactive sessions with Ctrl+C or kill, never
/exit.