乐于分享
好东西不私藏

OpenCode 源码-CLI 入口与命令路由

OpenCode 源码-CLI 入口与命令路由

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=1OPENCODE=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),适合 runsession 等命令

false:跳过实例加载,适合 modelsserveupgrade 等命令

函数:动态判断,如 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