Skip to content

CLI 入口とディスパッチ

源码版本v0.73.1

main.tspi コマンドのプロセス入り口だ。コマンドライン引数を AppMode にパースし、AgentSessionRuntime を組み立て、3 つの実行モード (対話式 TUI、単発 print、JSON-RPC) に振り分ける。モードディスパッチ以外に、マイグレーション、session 解析、モデル選択、ファイル型引数の前処理も担う。「シェル呼び出しを runtime 呼び出しに翻訳する」前段レイヤーと捉えればいい。

責務

  1. パースとディスパッチ:parseArgsArgs を出し、resolveAppModeAppMode を算出し、これに基づいて runRpcMode / InteractiveMode / runPrintMode を選ぶ。packages/coding-agent/src/main.ts:98-109packages/coding-agent/src/main.ts:673-726 参照。
  2. session 解析:--continue--resume--session <id|path>--fork <id>--no-session 間の衝突検証とパス解決で、session 識別子をディスク上の .jsonl に位置付ける。packages/coding-agent/src/main.ts:188-212packages/coding-agent/src/main.ts:147-172 参照。
  3. サービス組み立て:createAgentSessionServices + createAgentSessionRuntime を呼び、cwd、agentDir、authStorage、拡張/スキル/テーマパスを繋ぐ。packages/coding-agent/src/main.ts:522-560 参照。
  4. 実行前ショートサーキット:pi --versionpi --exportpi 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 ファクトリに委譲する。

主要ファイル

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 ファクトリクロージャに翻訳し、3 つのモードのいずれかに渡す。runtime ファクトリ内部の組み立ては createAgentSession 装配、対話モードの詳細は TUI 対話モード、非対話モードは print と rpc モード 参照。