free-code-dotnet/docs/核心模块设计/核心模块设计-CLI启动与解析.md
应文浩wenhao.ying@xiaobao100.com e25ac591a7 init easy-code
2026-04-06 07:24:24 +08:00

246 lines
9.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 核心模块设计 — CLI 启动与解析
> 所属项目: free-code .NET 10 重写
> 原始代码来源: `../../src/entrypoints/cli.tsx`, `../../src/screens/REPL.tsx`
> 原始设计意图: 解析命令行参数,区分交互式 REPL 模式和一次性 prompt 模式,并处理 daemon/bridge 等特殊运行模式
> 上级文档: [核心模块设计总览](核心模块设计.md)
> 交叉参考: [总体概述与技术选型](../总体概述与技术选型/总体概述与技术选型.md)
---
## 概述
CLI 启动模块是整个应用的入口,对应原始 TypeScript 项目的 `cli.tsx`。它的核心职责是在加载任何业务逻辑之前,先判断这次调用属于哪种运行模式,并将控制权交给对应的处理器。
.NET 重写将原始代码中混合在一个文件里的逻辑拆分为三个独立的类:
- `Program` — 四阶段启动协调器
- `QuickPathHandler` — 无需 DI 容器的快速路径处理
- `CliCommandBuilder` — 基于 `System.CommandLine` 的命令行定义
---
## 5.1 入口点 Program.cs
`Program.cs` 组织为四个顺序执行的阶段,每一阶段都有明确的失败边界。
**原始设计意图:** 原始 `cli.tsx` 在模块顶层直接调用 Commander.js 解析参数,然后根据 flag 条件决定走哪条路径。.NET 版本将这一隐式流程显式化为四个阶段,并统一使用 `Microsoft.Extensions.Hosting` 管理生命周期。
```csharp
public static class Program
{
public static async Task<int> Main(string[] args)
{
// Phase 1: 快速路径不加载DI容器零开销
if (QuickPathHandler.TryHandle(args, out var exitCode))
return exitCode;
// Phase 2: 构建Host
var host = Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration(cfg => cfg
.AddJsonFile("appsettings.json", optional: true)
.AddJsonFile(Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
".free-code", "settings.json"), optional: true, reloadOnChange: true))
.ConfigureServices((ctx, services) =>
{
services.AddCoreServices();
services.AddEngine();
services.AddTools();
services.AddCommands();
services.AddApiProviders();
services.AddMcp();
services.AddLsp();
services.AddBridge();
services.AddBusinessServices();
services.AddTasks();
services.AddSkills();
services.AddPlugins();
services.AddFeatures();
services.AddState();
services.AddTerminalUI();
})
.ConfigureLogging(logging => logging
.AddConsole()
.SetMinimumLevel(LogLevel.Information))
.Build();
// Phase 3: 初始化核心服务
await host.Services.GetRequiredService<IAppInitializer>().InitializeAsync();
// Phase 4: 启动REPL或执行一次性命令
var runner = host.Services.GetRequiredService<IAppRunner>();
return await runner.RunAsync(args);
}
}
```
### 四阶段说明
| 阶段 | 职责 | 失败处理 |
|------|------|----------|
| Phase 1 | 快速路径检测,无 DI 开销 | 匹配则直接返回,不进入后续阶段 |
| Phase 2 | 构建 Host注册所有服务 | `HostBuilder` 抛出则进程退出 |
| Phase 3 | 异步初始化OAuth 刷新、MCP 连接等) | `InitializeAsync` 可抛出 `AppInitializationException` |
| Phase 4 | 启动 REPL 或执行一次性 prompt | `RunAsync` 返回退出码 |
`reloadOnChange: true` 设置在 `~/.free-code/settings.json` 上,允许用户在 REPL 运行期间修改配置后立即生效,无需重启。
---
## 5.2 快速路径处理器
`QuickPathHandler` 对应原始 `cli.tsx` 中散布在文件顶部的 fast-path 条件判断。
**原始设计意图:** 原始代码在 Commander.js 解析之前,用一系列 `process.argv.includes()` 检查来处理特殊 flag避免触发 React/Ink 的初始化开销。.NET 版本将这些检查集中到一个静态类,并且同样在 DI 容器构建之前执行。
```csharp
public static class QuickPathHandler
{
public static bool TryHandle(string[] args, out int exitCode)
{
exitCode = 0;
// --version
if (args.Contains("--version"))
{
var ver = Assembly.GetEntryAssembly()!
.GetCustomAttribute<AssemblyInformationalVersionAttribute>()!
.InformationalVersion;
Console.WriteLine($"free-code {ver}");
return true;
}
// --dump-system-prompt
if (args.Contains("--dump-system-prompt"))
{
var builder = new SystemPromptBuilder(/* minimal config */);
Console.WriteLine(builder.BuildDefaultPrompt());
return true;
}
// MCP daemon mode
if (args.Contains("--mcp-daemon"))
{
exitCode = McpDaemon.Run(args).GetAwaiter().GetResult();
return true;
}
// Bridge mode
if (args.Contains("--bridge"))
{
exitCode = BridgeMain.Run(args).GetAwaiter().GetResult();
return true;
}
// Background session mode
if (args.Contains("--background-session"))
{
exitCode = BackgroundSessionRunner.Run(args).GetAwaiter().GetResult();
return true;
}
// Template mode
if (args.Contains("--template"))
{
exitCode = TemplateRunner.Run(args).GetAwaiter().GetResult();
return true;
}
return false;
}
}
```
### 快速路径 flag 列表
| Flag | 处理器 | 说明 |
|------|--------|------|
| `--version` | 直接打印 | 从程序集元数据读取版本号 |
| `--dump-system-prompt` | `SystemPromptBuilder` | 打印默认 System Prompt用于调试 |
| `--mcp-daemon` | `McpDaemon.Run` | 以 MCP 服务器模式运行,供 IDE 插件调用 |
| `--bridge` | `BridgeMain.Run` | IDE 远程控制桥接模式 |
| `--background-session` | `BackgroundSessionRunner.Run` | 后台会话模式,供 Agent 工具启动子进程 |
| `--template` | `TemplateRunner.Run` | 模板执行模式 |
注意 `--mcp-daemon``--bridge` 的处理器使用 `.GetAwaiter().GetResult()` 同步等待,因为此时还没有 `async` 上下文可用(`Main``await` 路径已被 Phase 1 短路)。
---
## 5.3 CLI 命令定义 (CliCommandBuilder)
`CliCommandBuilder` 对应原始 `cli.tsx` 中的 Commander.js 配置,负责定义全局选项和根命令的处理逻辑。
**原始设计意图:** Commander.js 的 `program.option(...).action(...)` 链式 API 被映射到 `System.CommandLine``RootCommand` + `Option<T>` 模式。两者在概念上完全对应,只是 API 风格不同。
```csharp
public class CliCommandBuilder
{
public RootCommand Build()
{
var root = new RootCommand("free-code - The free build of Claude Code");
// 全局选项
var modelOption = new Option<string?>("--model", "Override default model");
var verboseOption = new Option<bool>("--verbose", "Verbose output");
var resumeOption = new Option<string?>("--resume", "Resume session ID");
root.AddGlobalOption(modelOption);
root.AddGlobalOption(verboseOption);
root.AddGlobalOption(resumeOption);
// 一次性 prompt 模式 (-p)
var promptOption = new Option<string?>("-p", "One-shot prompt (non-interactive)");
root.AddOption(promptOption);
root.SetHandler(async (string? prompt, string? model, bool verbose, string? resume) =>
{
if (prompt != null)
return await OneShotMode.ExecuteAsync(prompt, model);
return await REPLMode.StartAsync(model, verbose, resume);
}, promptOption, modelOption, verboseOption, resumeOption);
return root;
}
}
```
### 选项说明
| 选项 | 类型 | 作用域 | 说明 |
|------|------|--------|------|
| `--model` | `string?` | 全局 | 覆盖配置文件中的默认模型 |
| `--verbose` | `bool` | 全局 | 开启详细日志输出 |
| `--resume` | `string?` | 全局 | 恢复指定 session ID 的对话历史 |
| `-p` | `string?` | 根命令 | 非交互式一次性 prompt 模式 |
`--model``--verbose``--resume` 作为全局选项注册,子命令也可以访问。`-p` 仅在根命令级别有效,因为子命令有各自的参数结构。
`-p` 存在时,走 `OneShotMode.ExecuteAsync` 路径(非交互式,执行完毕后退出)。否则走 `REPLMode.StartAsync` 启动交互式终端 UI。
---
## 模块间依赖
```
Program.cs
├── QuickPathHandler (静态,无依赖)
│ ├── SystemPromptBuilder (仅 --dump-system-prompt 路径)
│ ├── McpDaemon
│ ├── BridgeMain
│ ├── BackgroundSessionRunner
│ └── TemplateRunner
└── CliCommandBuilder (由 IAppRunner 使用)
├── OneShotMode
└── REPLMode
```
---
## 参考资料
- [核心模块设计总览](核心模块设计.md)
- [查询引擎 (QueryEngine)](核心模块设计-查询引擎-QueryEngine.md)
- [原始代码映射 — 核心模块](reference/原始代码映射-核心模块.md)
- [总体概述与技术选型](../总体概述与技术选型/总体概述与技术选型.md)