OpenCode 源码解析
第 2 讲:CLI 入口与命令路由
基于 dev 分支源码 · 2026-07-07
一、CLI 入口:index.ts
OpenCode 的 CLI 入口是 packages/opencode/src/index.ts(142 行),使用 yargs 构建命令行框架。整个文件结构清晰:导入命令 → 配置 yargs → 注册命令 → 解析执行。
📦 源码仓库
https://github.com/opencode-ai/opencode
本讲核心文件:packages/opencode/src/index.ts(142 行)
二、入口代码逐行拆解
1. 命令导入
文件开头导入 20+ 个命令模块,每个命令对应一个独立文件:
📄 packages/opencode/src/index.ts(第 1-30 行)
import yargs from "yargs"
import { hideBin } from "yargs/helpers"
import { RunCommand } from "./cli/cmd/run"
import { GenerateCommand } from "./cli/cmd/generate"
import { ServeCommand } from "./cli/cmd/serve"
import { DebugCommand } from "./cli/cmd/debug"
import { StatsCommand } from "./cli/cmd/stats"
import { McpCommand } from "./cli/cmd/mcp"
import { GithubCommand } from "./cli/cmd/github"
import { ExportCommand } from "./cli/cmd/export"
import { ImportCommand } from "./cli/cmd/import"
import { AttachCommand } from "./cli/cmd/attach"
import { TuiThreadCommand } from "./cli/cmd/tui"
import { AcpCommand } from "./cli/cmd/acp"
import { WebCommand } from "./cli/cmd/web"
import { PrCommand } from "./cli/cmd/pr"
import { SessionCommand } from "./cli/cmd/session"
import { DbCommand } from "./cli/cmd/db"
import { PluginCommand } from "./cli/cmd/plug"
// ... 共 20+ 个命令模块
2. yargs 配置与全局中间件
yargs 实例配置了帮助、版本、全局选项,并通过 .middleware() 在每个命令执行前设置环境变量:
📄 packages/opencode/src/index.ts(第 32-70 行)
const cli = yargs(args)
.parserConfiguration({ "populate--": true })
.scriptName("opencode")
.wrap(100)
.help("help", "show help")
.alias("help", "h")
.version("version", "show version", InstallationVersion)
.alias("version", "v")
.option("print-logs", {
describe: "print logs to stderr",
type: "boolean",
})
.option("log-level", {
describe: "log level",
type: "string",
choices: ["DEBUG", "INFO", "WARN", "ERROR"],
})
.option("pure", {
describe: "run without external plugins",
type: "boolean",
})
.middleware(async (opts) => {
if (opts.printLogs) process.env.OPENCODE_PRINT_LOGS = "1"
if (opts.logLevel) process.env.OPENCODE_LOG_LEVEL = opts.logLevel
if (opts.pure) process.env.OPENCODE_PURE = "1"
Heap.start()
process.env.AGENT = "1"
process.env.OPENCODE = "1"
process.env.OPENCODE_PID = String(process.pid)
})
关键设计:.pure 选项可以禁用外部插件,Heap.start() 启动内存监控,环境变量 AGENT=1 和 OPENCODE=1 用于运行时检测。
3. 命令注册与执行
所有命令通过 .command() 注册,最后通过 .command() 链式调用完成注册:
📄 packages/opencode/src/index.ts(第 72-142 行)
.command(RunCommand)
.command(ServeCommand)
.command(DebugCommand)
.command(StatsCommand)
.command(McpCommand)
.command(GithubCommand)
.command(ExportCommand)
.command(ImportCommand)
.command(AttachCommand)
.command(TuiThreadCommand)
.command(AcpCommand)
.command(WebCommand)
.command(PrCommand)
.command(SessionCommand)
.command(DbCommand)
.command(PluginCommand)
.fail((msg, err) => {
if (err) throw err
process.exit(1)
})
.strict()
try {
if (args.includes("-h") || args.includes("--help")) {
await cli.parse(args, (err, _argv, out) => {
if (err) throw err
if (!out) return
show(out)
})
} else {
await cli.parse()
}
} catch (e) {
const formatted = FormatError(e)
if (formatted) UI.error(formatted)
if (formatted === undefined) {
UI.error("Unexpected error" + EOL)
process.stderr.write(errorMessage(e) + EOL)
}
process.exitCode = 1
} finally {
process.exit()
}
三、effectCmd:命令构建器
所有命令通过 effectCmd 构建,将 yargs 命令包装为 Effect 可执行单元。这是 OpenCode 命令系统的核心抽象。
1. EffectCmdOpts 接口
EffectCmdOpts 定义了命令的结构,其中最关键的是 instance 选项:
📄 packages/opencode/src/cli/effect-cmd.ts(第 20-50 行)
interface EffectCmdOpts<Args, A> {
command: string | readonly string[]
aliases?: string | readonly string[]
describe: string | false
builder?: (yargs: Argv) => Argv<Args>
/**
* Whether the command needs a project InstanceContext.
* true (default): wraps handler in InstanceStore.Service.provide
* false: skip instance entirely
* Function: (args) => boolean per-invocation
*/
instance?: boolean | ((args: Args) => boolean)
directory?: (args: Args) => string
handler: (args: WithDoubleDash<Args>) =>
Effect.Effect<A, CliError, AppServices | InstanceStore.Service>
}
instance 选项的三种模式:
true:加载项目实例上下文(InstanceBootstrap),适合 run、session 等命令
false:跳过实例加载,适合 models、serve、upgrade 等命令
函数:动态判断,如 run --attach 连接远程服务器时不需要本地实例
2. effectCmd 执行流程
effectCmd 内部通过 AppRuntime.runPromise 执行 Effect 处理函数:
📄 packages/opencode/src/cli/effect-cmd.ts(第 69-96 行)
export const effectCmd = <Args, A>(opts: EffectCmdOpts<Args, A>) =>
cmd<{}, Args>({
command: opts.command,
aliases: opts.aliases,
describe: opts.describe,
builder: opts.builder as never,
async handler(rawArgs) {
const { AppRuntime } = await import("@/effect/app-runtime")
const args = rawArgs as unknown as WithDoubleDash<Args>
const useInstance = typeof opts.instance === "function"
? opts.instance(args) : opts.instance !== false
if (!useInstance) {
await AppRuntime.runPromise(opts.handler(args))
return
}
const { InstanceStore } = await import("@/project/instance-store")
const { InstanceRef } = await import("@/effect/instance-ref")
const directory = opts.directory?.(args) ?? process.cwd()
const { store, ctx } = await AppRuntime.runPromise(
InstanceStore.Service.use((store) =>
store.load({ directory }).pipe(
Effect.map((ctx) => ({ store, ctx }))
)
)
)
try {
await AppRuntime.runPromise(
opts.handler(args).pipe(
Effect.provideService(InstanceRef, ctx)
)
)
} finally {
await AppRuntime.runPromise(store.dispose(ctx))
}
},
})
执行流程:判断是否需要实例 → 加载实例上下文 → 执行命令 handler → 清理资源。整个流程通过 try-finally 确保资源正确释放。
四、RunCommand:核心命令
opencode run 是最常用的命令,1011 行代码,支持三种运行模式。
1. 三种运行模式
┌─────────────────────────────────────────────────┐ │ RunCommand (1011 行) │ │ │ │ 模式 1: 非交互模式 (默认) │ │ • 发送消息 → 流式输出 → 会话空闲后退出 │ │ • 示例: opencode run "fix the bug" │ │ │ │ 模式 2: 交互本地模式 (--mini) │ │ • 启动 split-footer 直接模式 │ │ • 进程内服务器,无外部 HTTP │ │ • 示例: opencode --mini │ │ │ │ 模式 3: 交互远程模式 (--mini --attach) │ │ • 连接已运行的 opencode server │ │ • 示例: opencode --mini --attach │ │ http://localhost:4096 │ └─────────────────────────────────────────────────┘
2. 命令选项定义
RunCommand 通过 .builder() 定义了大量选项:
📄 packages/opencode/src/cli/cmd/run.ts(第 126-260 行)
export const RunCommand = effectCmd({
command: "run [message..]",
describe: "run opencode with a message",
instance: (args) => !args.attach,
directory: (args) => (args.dir && !args.attach
? path.resolve(process.cwd(), args.dir)
: process.cwd()),
builder: (yargs: Argv) =>
yargs
.positional("message", {
describe: "message to send",
type: "string",
array: true,
default: [],
})
.option("command", {
describe: "the command to run",
type: "string",
})
.option("continue", {
alias: ["c"],
describe: "continue the last session",
type: "boolean",
})
.option("session", {
alias: ["s"],
describe: "session id to continue",
type: "string",
})
.option("model", {
type: "string",
alias: ["m"],
describe: "model (provider/model)",
})
.option("file", {
alias: ["f"],
type: "string",
array: true,
describe: "file(s) to attach",
})
.option("attach", {
type: "string",
describe: "attach to running server",
})
.option("dir", {
type: "string",
describe: "directory to run in",
})
.option("interactive", {
alias: ["i"],
type: "boolean",
describe: "direct interactive mode",
default: false,
})
.option("yolo", {
type: "boolean",
hidden: true,
default: false,
})
五、ServeCommand:服务器模式
opencode serve 启动无头服务器,仅 24 行代码:
📄 packages/opencode/src/cli/cmd/serve.ts(24 行)
export const ServeCommand = effectCmd({
command: "serve",
builder: (yargs) => withNetworkOptions(yargs),
describe: "starts a headless opencode server",
instance: false, // 不需要项目实例
handler: Effect.fn("Cli.serve")(function* (args) {
const { Server } = yield* Effect.promise(() =>
import("../../server/server"))
if (!Flag.OPENCODE_SERVER_PASSWORD) {
console.log("Warning: no password set")
}
const opts = yield* resolveNetworkOptions(args)
const server = yield* Effect.promise(
() => Server.listen(opts)
)
console.log(`opencode server listening on
http://${server.hostname}:${server.port}`)
yield* Effect.never // 阻塞保持运行
}),
})
关键设计:instance: false 表示服务器命令不需要加载项目实例。yield* Effect.never 让服务器持续运行,直到被中断。
六、UI 样式系统
CLI 输出通过 UI 类统一格式化,支持 ANSI 颜色码和 Logo 绘制:
📄 packages/opencode/src/cli/ui.ts(第 14-29 行)
export const Style = {
TEXT_HIGHLIGHT: "\x1b[96m",
TEXT_DIM: "\x1b[90m",
TEXT_WARNING: "\x1b[93m",
TEXT_DANGER: "\x1b[91m",
TEXT_SUCCESS: "\x1b[92m",
TEXT_INFO: "\x1b[94m",
TEXT_NORMAL: "\x1b[0m",
}
export function println(...message: string[]) {
print(...message)
process.stderr.write(EOL)
}
Logo 使用 Unicode Block Elements 绘制,在 TTY 环境下显示彩色 ASCII Art:
📄 packages/opencode/src/cli/ui.ts(第 5-10 行)
const wordmark = [
` ▄ `,
`█▀▀█ █▀▀█ █▀▀█ █▀▀▄ █▀▀▀ █▀▀█ █▀▀█ █▀▀█`,
`█ █ █ █ █▀▀▀ █ █ █ █ █ █ █ █▀▀▀`,
`▀▀▀▀ █▀▀▀ ▀▀▀▀ ▀ ▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀`,
]
七、完整命令列表
packages/opencode/src/cli/cmd/ 包含 67 个命令文件,覆盖对话、服务器、会话、模型等场景:
| 命令 | 说明 |
|---|---|
run |
运行对话(核心命令) |
serve |
启动无头服务器 |
session |
会话管理 |
models |
查看可用模型 |
providers |
查看提供商列表 |
mcp |
MCP 工具管理 |
acp |
ACP 子代理 |
github |
GitHub 集成 |
db |
数据库操作 |
upgrade |
版本升级 |
八、总结
yargs构建 CLI 框架,142 行入口文件effectCmd将命令包装为 Effect 可执行单元- 三种运行模式:非交互、交互本地、交互远程
- 67 个命令文件,覆盖对话、服务器、会话、模型等
- 实例上下文按需加载,非项目命令可跳过
← 系列导航 →
第 1 讲:整体架构与 Monorepo 设计
第 3 讲:Session 会话核心
关注公众号获取更多 OpenCode 源码解析干货
源码:https://github.com/opencode-ai/opencode
夜雨聆风