CLI 入口与分发
源码版本v0.73.1
main.ts 是 pi 命令的进程入口。它把命令行参数解析成 AppMode,装配 AgentSessionRuntime,再分派给三种运行模式:交互式 TUI、一次性 print、JSON-RPC。除了模式分派,它还负责迁移、session 解析、模型选择、文件型参数预处理。可以理解成「把 shell 调用翻译成 runtime 调用」的前置层。
职责
- 解析与分派:
parseArgs出Args,resolveAppMode算出AppMode,据此选runRpcMode/InteractiveMode/runPrintMode。见packages/coding-agent/src/main.ts:98-109、packages/coding-agent/src/main.ts:673-726。 - session 解析:
--continue、--resume、--session <id|path>、--fork <id>、--no-session之间的冲突校验和路径解析,把 session 标识符定位到磁盘上的.jsonl。见packages/coding-agent/src/main.ts:188-212、packages/coding-agent/src/main.ts:147-172。 - 服务装配:调
createAgentSessionServices+createAgentSessionRuntime,把 cwd、agentDir、authStorage、扩展/技能/主题路径串起来。见packages/coding-agent/src/main.ts:522-560。 - 预运行短路:
pi --version、pi --export、pi package ...、pi config ...不进 runtime,直接process.exit。见packages/coding-agent/src/main.ts:431-478。
设计动机
为什么把入口单独成文件?因为 CLI 解析、session 选择、迁移这些逻辑只发生一次,且只在进程启动时发生。把这些塞进 createAgentSession(SDK 入口)会污染 SDK 的复用性——SDK 调用方通常是已有 runtime 上下文的程序,不需要解析 argv,也不需要进程级 stdout 接管。main.ts 因此只承担「进程级杂事」:stdout 接管、信号处理、migrations、版本检查、fork 校验,剩下的都委托给 SDK 和 runtime 工厂。
关键文件
packages/coding-agent/src/main.ts:54-71—readPipedStdin,非 TTY 时读管道 stdin,作为 print 模式的初始消息。packages/coding-agent/src/main.ts:98-109—resolveAppMode,把Args.mode/Args.print加上stdin.isTTY算出四种AppMode。packages/coding-agent/src/main.ts:423-478—main函数开头:migrations、offline、package/config 短路、参数诊断。packages/coding-agent/src/main.ts:522-560—createRuntime工厂闭包,捕获 CLI 解析出的扩展/技能/主题路径,传给createAgentSessionServices。packages/coding-agent/src/main.ts:673-726— 模式分派三岔口:runRpcMode/new InteractiveMode/runPrintMode。packages/coding-agent/src/cli/— CLI 子模块目录,包含args.ts、file-processor.ts、initial-message.ts、session-picker.ts、list-models.ts。
resolveAppMode 优先看 --mode 标志,再退化到 stdin 是否 TTY,这样管道用法 echo x | pi 自动走 print 模式:
typescript
// packages/coding-agent/src/main.ts:98-109
function resolveAppMode(parsed: Args, stdinIsTTY: boolean): AppMode {
if (parsed.mode === "rpc") {
return "rpc";
}
if (parsed.mode === "json") {
return "json";
}
if (parsed.print || !stdinIsTTY) {
return "print";
}
return "interactive";
}三岔分派处直接用 appMode 选分支,print 与 json 共用 runPrintMode,差异在 toPrintOutputMode(appMode):
typescript
// packages/coding-agent/src/main.ts:673-726
if (appMode === "rpc") {
printTimings();
await runRpcMode(runtime);
} else if (appMode === "interactive") {
// ...
const interactiveMode = new InteractiveMode(runtime, {
migratedProviders,
modelFallbackMessage,
initialMessage,
initialImages,
initialMessages: parsed.messages,
verbose: parsed.verbose,
});
// ...
await interactiveMode.run();
} else {
printTimings();
const exitCode = await runPrintMode(runtime, {
mode: toPrintOutputMode(appMode),
messages: parsed.messages,
initialMessage,
initialImages,
});
// ...
}数据流
argv 进来后的分派路径:
边界与失败
- fork 冲突:
--fork不能与--session、--continue、--resume、--no-session同时用,见packages/coding-agent/src/main.ts:188-202。 - stdout 接管:非 interactive 模式调
takeOverStdout(),把console.log重定向到内部管道,避免 TUI 转义污染 print/rpc 输出,退出时restoreStdout。 - session cwd 缺失:
--session指向别的项目时,interactive 会弹 prompt 让用户选,print/rpc 直接报错退出,见packages/coding-agent/src/main.ts:502-514。 - benchmark 模式:
startupBenchmark时只跑interactiveMode.init()后立刻stop(),不进主循环。
小结
main.ts 是薄壳:把 argv 和 stdin 翻译成 runtime 工厂闭包,再交给三种模式之一。runtime 工厂内部怎么装配看 createAgentSession 装配,交互模式细节看 TUI 交互模式,非交互模式看 print 与 rpc 模式。