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 模式。