CLI 入口とディスパッチ
main.ts は pi コマンドのプロセス入り口だ。コマンドライン引数を AppMode にパースし、AgentSessionRuntime を組み立て、3 つの実行モード (対話式 TUI、単発 print、JSON-RPC) に振り分ける。モードディスパッチ以外に、マイグレーション、session 解析、モデル選択、ファイル型引数の前処理も担う。「シェル呼び出しを 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から 4 種の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 モードに進む:
// 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) に出る:
// 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 ファクトリクロージャに翻訳し、3 つのモードのいずれかに渡す。runtime ファクトリ内部の組み立ては createAgentSession 装配、対話モードの詳細は TUI 対話モード、非対話モードは print と rpc モード 参照。