0. 本章学习目标
理解 opencode 进程如何启动,yargs 如何注册子命令,run 如何处理参数、stdin、文件附件、session 和事件订阅。
1. 一句话讲明白
OpenCode 的 CLI 层不是 agent 本体,而是入口适配层:把命令行输入整理成 session API 请求,再交给 runtime。来源:packages/opencode/src/index.ts:70-180、packages/opencode/src/cli/cmd/run.ts:768-879。
2. 它在 OpenCode agent 中的位置
index.ts 是进程 main;RunCommand 是 agent 学习第一入口;effectCmd 是命令运行时外壳。来源:packages/opencode/src/index.ts:158-180、packages/opencode/src/cli/effect-cmd.ts:70-93。
3. 生活类比
CLI 像服务台:确认目录、附件、旧会话、交互模式,然后把工单交给 agent 服务系统。
4. Java 开发者类比
index.ts类似main()+ Picocli 根命令。RunCommand类似@Commandhandler。effectCmd类似拦截器,负责加载项目上下文和 finally 清理。- 本地
Server.Default().app.fetch类似 in-process controller 调用。
5. 最小源码路径
packages/opencode/src/index.ts:58-91packages/opencode/src/index.ts:158-180packages/opencode/src/cli/effect-cmd.ts:70-93packages/opencode/src/cli/cmd/run.ts:127-245packages/opencode/src/cli/cmd/run.ts:246-360packages/opencode/src/cli/cmd/run.ts:396-516packages/opencode/src/cli/cmd/run.ts:768-879
6. 用户输入到 agent 行动的整体链路
process.argv
-> hideBin
-> yargs(args)
-> RunCommand handler
-> resolve message/stdin/files/session
-> createOpencodeClient
-> client.session.prompt
-> SessionPrompt.prompt7. 核心源码逐段讲解
7.1 yargs 入口
const args = hideBin(process.argv)
const cli = yargs(args)
.scriptName("opencode")
.option("pure", { describe: "run without external plugins", type: "boolean" })来源:packages/opencode/src/index.ts:58-91。
7.2 effectCmd
const useInstance = typeof opts.instance === "function" ? opts.instance(args) : opts.instance !== false
if (!useInstance) {
await AppRuntime.runPromise(opts.handler(args))
return
}
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))
}来源:packages/opencode/src/cli/effect-cmd.ts:70-93。
7.3 run 命令发送 prompt
const result = await client.session.prompt({
sessionID,
agent,
model,
variant: args.variant,
parts: [...files, { type: "text", text: message }],
})来源:packages/opencode/src/cli/cmd/run.ts:791-798。
7.4 事件渲染
CLI 监听 message.part.updated、session.error、permission.asked,把 runtime 状态渲染到 stdout/UI。来源:packages/opencode/src/cli/cmd/run.ts:637-759。
8. 关键 TypeScript 语法复习
- default import:
import yargs from "yargs"。 - namespace import:
import * as Log。 - 泛型函数:
effectCmd = <Args, A>(...)。 - object spread/array spread:
parts: [...files, {...}]。 - dynamic import:
await import("@/server/server")。
9. 设计模式和架构思想
- Command Pattern
- Adapter
- Interceptor
- Event-driven UI
- Single runtime path
10. 模块协作
CLI 负责 cwd、附件、session 和输出;Provider/Tool/Session runtime 在后续服务中处理。关键边界:CLI 只解析 provider/model,不直接调用模型。来源:packages/opencode/src/cli/cmd/run.ts:31-41。
11. mini agent 对应代码
async function main(argv: string[]) {
const args = parseArgs(argv)
const files = await resolveFiles(args.file, args.cwd)
const client = createMiniAgentClient({ cwd: args.cwd })
const sessionID = await getOrCreateSession(client, args)
renderEvents(client.subscribe(sessionID))
await client.prompt(sessionID, { parts: [...files, { type: "text", text: args.message }] })
}12. 费曼复述区
- 为什么 CLI 不是 agent 核心?
effectCmd解决了什么重复问题?- 为什么本地模式也走
createOpencodeClient?
13. 练习题
- 找到
RunCommand的--model和--agent参数。 - 解释
run --attach为什么不需要本地 instance。 - 实现一个支持
run "hello"和--file的 mini CLI。
14. 源码追踪任务
index.ts:158-180->RunCommandeffect-cmd.ts:70-93->InstanceStore.Service.loadrun.ts:791-798-> session API handlerrun.ts:637-759-> 事件渲染
15. 面试式自测
- CLI 层该不该直接调用模型?
- 为什么要支持 session resume/fork?
--file在 CLI 层和 runtime 层分别做什么?