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

9.4 KiB
Raw Blame History

核心模块设计 — CLI 启动与解析

所属项目: free-code .NET 10 重写 原始代码来源: ../../src/entrypoints/cli.tsx, ../../src/screens/REPL.tsx 原始设计意图: 解析命令行参数,区分交互式 REPL 模式和一次性 prompt 模式,并处理 daemon/bridge 等特殊运行模式 上级文档: 核心模块设计总览 交叉参考: 总体概述与技术选型


概述

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 管理生命周期。

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 容器构建之前执行。

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 上下文可用(Mainawait 路径已被 Phase 1 短路)。


5.3 CLI 命令定义 (CliCommandBuilder)

CliCommandBuilder 对应原始 cli.tsx 中的 Commander.js 配置,负责定义全局选项和根命令的处理逻辑。

原始设计意图: Commander.js 的 program.option(...).action(...) 链式 API 被映射到 System.CommandLineRootCommand + Option<T> 模式。两者在概念上完全对应,只是 API 风格不同。

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

参考资料