Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

第4章:入口与启动流程

本章深入分析 Claude Code 的入口点和启动流程,包括 CLI 参数处理、初始化步骤和 REPL 界面启动。

4.1 cli.tsx 入口解析

src/entrypoints/cli.tsx 是 Claude Code 的顶层入口点,负责快速路径判断和延迟加载。

入口核心结构

// src/entrypoints/cli.tsx
import { feature } from 'bun:bundle';

// 前置副作用:corepack 配置和 CCR 环境变量
process.env.COREPACK_ENABLE_AUTO_PIN = '0';
if (process.env.CLAUDE_CODE_REMOTE === 'true') {
  process.env.NODE_OPTIONS = '--max-old-space-size=8192';
}

// Ablation baseline:实验性环境变量批量设置
if (feature('ABLATION_BASELINE') && process.env.CLAUDE_CODE_ABLATION_BASELINE) {
  for (const k of ['CLAUDE_CODE_SIMPLE', ...]) {
    process.env[k] ??= '1';
  }
}

/**
 * Bootstrap entrypoint - 检查特殊标志后加载完整 CLI
 */
async function main(): Promise<void> {
  const args = process.argv.slice(2);
  
  // Fast-path 判断...
  
  // 正常路径:加载 main.tsx
  const { main: cliMain } = await import('../main.js');
  await cliMain();
}

void main();

快速路径(Fast-Path)判断

graph TD
    A[main 函数入口] --> B[解析 args]
    B --> C{判断快速路径}
    
    C --> |"--version/-v"| D[直接输出版本<br/>零模块加载]
    C --> |"--dump-system-prompt"| E[动态加载配置<br/>输出系统提示]
    C --> |"--claude-in-chrome-mcp"| F[启动 Chrome MCP]
    C --> |"--chrome-native-host"| G[启动 Chrome Native Host]
    C --> |"--daemon-worker"| H[启动 Daemon Worker]
    C --> |"remote-control/rc"| I[启动 Bridge]
    C --> |"daemon"| J[启动 Daemon]
    C --> |"ps/logs/attach/kill"| K[Session 管理]
    C --> |"new/list/reply"| L[Templates 处理]
    C --> |"--tmux --worktree"| M[Exec tmux worktree]
    
    D --> N[return]
    E --> N
    F --> N
    G --> N
    H --> N
    I --> N
    J --> N
    K --> N
    L --> N
    M --> O{handled?}
    O --> |"是"| N
    O --> |"否"| P[继续正常路径]
    
    C --> |"无特殊标志"| P
    P --> Q[加载 main.tsx]
    Q --> R[cliMain]

快速路径代码详解

async function main(): Promise<void> {
  const args = process.argv.slice(2);
  
  // 1. --version:零模块加载
  if (args.length === 1 && (args[0] === '--version' || args[0] === '-v')) {
    console.log(`${MACRO.VERSION} (Claude Code)`);
    return;
  }
  
  // 2. 加载启动 profiler
  const { profileCheckpoint } = await import('../utils/startupProfiler.js');
  profileCheckpoint('cli_entry');
  
  // 3. --dump-system-prompt:动态加载最小模块
  if (feature('DUMP_SYSTEM_PROMPT') && args[0] === '--dump-system-prompt') {
    const { enableConfigs } = await import('../utils/config.js');
    enableConfigs();
    const { getSystemPrompt } = await import('../constants/prompts.js');
    const prompt = await getSystemPrompt([], model);
    console.log(prompt.join('\n'));
    return;
  }
  
  // 4. Chrome MCP / Native Host
  if (process.argv[2] === '--claude-in-chrome-mcp') {
    const { runClaudeInChromeMcpServer } = await import(...);
    await runClaudeInChromeMcpServer();
    return;
  }
  
  // 5. Bridge remote-control
  if (feature('BRIDGE_MODE') && args[0] === 'remote-control' || ...) {
    const { bridgeMain } = await import('../bridge/bridgeMain.js');
    await bridgeMain(args.slice(1));
    return;
  }
  
  // 6. Daemon
  if (feature('DAEMON') && args[0] === 'daemon') {
    const { daemonMain } = await import('../daemon/main.js');
    await daemonMain(args.slice(1));
    return;
  }
  
  // 7. Session management (ps/logs/attach/kill)
  if (feature('BG_SESSIONS') && args[0] === 'ps' || ...) {
    const bg = await import('../cli/bg.js');
    switch (args[0]) {
      case 'ps': await bg.psHandler(args.slice(1)); break;
      case 'logs': await bg.logsHandler(args[1]); break;
      case 'attach': await bg.attachHandler(args[1]); break;
      case 'kill': await bg.killHandler(args[1]); break;
      default: await bg.handleBgFlag(args);
    }
    return;
  }
  
  // 8. --tmux --worktree:提前 exec 到 tmux
  if (hasTmuxFlag && hasWorktreeFlag) {
    const { execIntoTmuxWorktree } = await import('../utils/worktree.js');
    const result = await execIntoTmuxWorktree(args);
    if (result.handled) return;
  }
  
  // 9. 正常路径:加载完整 CLI
  const { startCapturingEarlyInput } = await import('../utils/earlyInput.js');
  startCapturingEarlyInput();
  
  const { main: cliMain } = await import('../main.js');
  await cliMain();
}

快速路径优化

快速路径设计是为了最小化模块加载,提升特殊命令的响应速度。--version 实现零模块加载,其他快速路径只加载必要模块。

4.2 Commander.js 参数处理

main.tsx 使用 Commander.js 定义完整的 CLI 参数和子命令。

Commander.js 配置结构

// main.tsx 核心结构
import { Command as CommanderCommand, Option } from '@commander-js/extra-typings';

async function main() {
  const program = new CommanderCommand();
  
  program
    .name('claude')
    .version(MACRO.VERSION)
    .description('Claude Code - AI coding assistant')
    
    // 全局选项
    .option('-p, --print', 'Headless print mode')
    .option('--model <model>', 'Override model')
    .option('--resume <sessionId>', 'Resume session')
    .option('--dangerously-skip-permissions', 'Skip all permissions')
    .option('-w, --worktree', 'Create worktree for session')
    .option('--tmux', 'Create tmux session')
    .option('--bare', 'Simple/bare mode')
    .option('--disallowed-tools <tools>', 'Disallowed tools')
    .option('--allowedTools <tools>', 'Allowed tools')
    .option('--mcp-config <path>', 'MCP config path')
    .option('--settings <path>', 'Settings path')
    .option('-c, --context <dirs>', 'Additional directories')
    .option('--add-dir <dirs>', 'Add directories to context')
    .option('--output-format <format>', 'Output format (text/json/stream-json)')
    .option('--input-format <format>', 'Input format')
    .option('--max-tokens <n>', 'Max output tokens')
    .option('--verbose', 'Verbose output')
    .option('--help', 'Show help');
  
  // 子命令
  program
    .command('update')
    .description('Update Claude Code')
    .action(() => handleUpdate());
  
  program
    .command('mcp')
    .description('MCP server management')
    .action(() => handleMcp());
  
  program
    .command('config')
    .description('Configure settings')
    .action(() => handleConfig());
  
  // 解析参数
  program.parse();
  
  const options = program.opts();
  // 根据选项选择启动模式...
}

支持的命令行参数

参数说明示例
-p, --print无头模式,单次问答-p "explain this"
--model <model>覆盖默认模型--model claude-3-opus
--resume <id>恢复会话--resume session-123
--dangerously-skip-permissions跳过所有权限检查仅限沙箱环境
-w, --worktree创建 worktree 隔离-w --tmux
--tmux创建 tmux 会话--tmux
--bare简化模式--bare
--disallowed-tools禁用工具列表--disallowed-tools Bash,Glob
--allowedTools允许工具列表--allowedTools Read,Edit
--mcp-configMCP 配置路径--mcp-config ~/.claude/mcp.json
--settings设置文件路径--settings ~/.claude/settings.json
-c, --context附加目录-c ../other-project
--add-dir添加目录到上下文--add-dir ./docs
--output-format输出格式text, json, stream-json
--input-format输入格式text, json
--max-tokens最大输出 token--max-tokens 4096
--verbose详细输出--verbose

启动模式选择

graph TD
    A[解析 Commander 选项] --> B{判断启动模式}
    
    B --> |"--print"| C[Headless Print Mode]
    B --> |"--resume"| D[Resume Session]
    B --> |"--worktree"| E[Worktree Session]
    B --> |"LOCAL_RECOVERY"| F[Recovery CLI]
    B --> |"默认"| G[Interactive TUI]
    
    C --> H[cli/print.ts]
    D --> I[loadConversationForResume]
    E --> J[createWorktreeForSession]
    F --> K[localRecoveryCli.ts]
    G --> L[launchRepl]

权限跳过限制

--dangerously-skip-permissions 仅允许在 Docker/Bubblewrap 沙箱中使用,且不能有互联网访问。root/sudo 用户禁止使用。

4.3 setup.ts 初始化流程

setup.ts 执行启动前的必要初始化,包括环境检测、配置加载和后台任务启动。

setup 函数签名

// src/setup.ts
export async function setup(
  cwd: string,
  permissionMode: PermissionMode,
  allowDangerouslySkipPermissions: boolean,
  worktreeEnabled: boolean,
  worktreeName: string | undefined,
  tmuxEnabled: boolean,
  customSessionId?: string | null,
  worktreePRNumber?: number,
  messagingSocketPath?: string,
): Promise<void> {
  // 初始化步骤...
}

初始化流程图

graph TD
    subgraph "前置检查"
        A[Node.js 版本检查] --> B{>= 18?}
        B --> |"否"| C[退出报错]
        B --> |"是"| D[设置 customSessionId]
    end
    
    subgraph "UDS Messaging"
        D --> E{非 bare 模式?}
        E --> |"是"| F[startUdsMessaging]
        F --> G[绑定 Socket]
    end
    
    subgraph "Teammate Snapshot"
        G --> H{Agent Swarms 启用?}
        H --> |"是"| I[captureTeammateModeSnapshot]
    end
    
    subgraph "终端备份恢复"
        I --> J{非非交互?}
        J --> |"是"| K[checkAndRestoreITerm2Backup]
        K --> L[checkAndRestoreTerminalBackup]
    end
    
    subgraph "路径设置"
        L --> M[setCwd]
        M --> N[setOriginalCwd]
        N --> O[setProjectRoot]
    end
    
    subgraph "本地恢复模式"
        O --> P{LOCAL_RECOVERY?}
        P --> |"是"| Q[早返回]
    end
    
    subgraph "Hooks 配置"
        P --> |"否"| R[captureHooksConfigSnapshot]
        R --> S[initializeFileChangedWatcher]
    end
    
    subgraph "Worktree 处理"
        S --> T{worktreeEnabled?}
        T --> |"是"| U[createWorktreeForSession]
        U --> V[createTmuxSessionForWorktree]
        V --> W[setCwd worktreePath]
    end
    
    subgraph "后台任务"
        W --> X[initSessionMemory]
        X --> Y[lockCurrentVersion]
        Y --> Z[getCommands prefetch]
        Z --> AA[loadPluginHooks]
        AA --> AB[initSinks]
    end
    
    subgraph "权限检查"
        AB --> AC{bypassPermissions?}
        AC --> |"是"| AD[环境安全检查]
        AD --> AE{沙箱 + 无网络?}
        AE --> |"否"| AF[退出报错]
    end
    
    subgraph "完成"
        AE --> |"是"| AG[setup 完成]
        AC --> |"否"| AG
    end

setup 核心代码

export async function setup(...): Promise<void> {
  logForDiagnosticsNoPII('info', 'setup_started');
  
  // 1. Node.js 版本检查
  const nodeVersion = process.version.match(/^v(\d+)\./)?.[1];
  if (!nodeVersion || parseInt(nodeVersion) < 18) {
    console.error('Error: Claude Code requires Node.js version 18 or higher.');
    process.exit(1);
  }
  
  // 2. 设置 custom session ID
  if (customSessionId) {
    switchSession(asSessionId(customSessionId));
  }
  
  // 3. UDS Messaging Server(Mac/Linux)
  if (!isBareMode() || messagingSocketPath !== undefined) {
    if (feature('UDS_INBOX')) {
      const m = await import('./utils/udsMessaging.js');
      await m.startUdsMessaging(messagingSocketPath ?? m.getDefaultUdsSocketPath());
    }
  }
  
  // 4. Teammate snapshot
  if (!isBareMode() && isAgentSwarmsEnabled()) {
    const { captureTeammateModeSnapshot } = await import('./utils/swarm/backends/teammateModeSnapshot.js');
    captureTeammateModeSnapshot();
  }
  
  // 5. 终端备份恢复
  if (!getIsNonInteractiveSession()) {
    if (isAgentSwarmsEnabled()) {
      await checkAndRestoreITerm2Backup();
    }
    await checkAndRestoreTerminalBackup();
  }
  
  // 6. 设置工作目录
  setCwd(cwd);
  setOriginalCwd(cwd);
  setProjectRoot(cwd);
  
  // 7. 本地恢复模式早返回
  if (process.env.CLAUDE_CODE_LOCAL_RECOVERY === '1') {
    return;
  }
  
  // 8. Hooks 配置快照
  captureHooksConfigSnapshot();
  initializeFileChangedWatcher(cwd);
  
  // 9. Worktree 创建
  if (worktreeEnabled) {
    const worktreeSession = await createWorktreeForSession(...);
    setCwd(worktreeSession.worktreePath);
    setProjectRoot(getCwd());
    saveWorktreeState(worktreeSession);
    clearMemoryFileCaches();
    updateHooksConfigSnapshot();
  }
  
  // 10. 后台任务
  if (!isBareMode()) {
    initSessionMemory();
  }
  void lockCurrentVersion();
  void getCommands(getProjectRoot());
  
  // 11. 权限安全检查
  if (permissionMode === 'bypassPermissions' || allowDangerouslySkipPermissions) {
    if (process.platform !== 'win32' && process.getuid() === 0) {
      console.error('Cannot use --dangerously-skip-permissions with root');
      process.exit(1);
    }
    const [isDocker, hasInternet] = await Promise.all([
      envDynamic.getIsDocker(),
      env.hasInternetAccess(),
    ]);
    if (!isSandboxed || hasInternet) {
      console.error('Cannot use bypass in non-sandbox with internet');
      process.exit(1);
    }
  }
}

setup 时机

setup() 在 main.tsx 的 init() 之后调用,确保配置已加载。setup 完成后,Ink TUI 开始渲染。

4.4 main.tsx 主逻辑入口

main.tsx 是 CLI 的主逻辑入口,负责 Commander.js 配置、启动模式选择和 REPL 启动。

main.tsx 入口函数结构

// main.tsx(简化结构)
async function main() {
  // 1. 前置初始化(side-effects)
  profileCheckpoint('main_tsx_entry');
  startMdmRawRead();      // MDM 配置预读取
  startKeychainPrefetch(); // macOS keychain 预读取
  
  // 2. 导入大量模块(延迟加载优化)
  // ...约 135ms 的模块加载
  
  // 3. 迁移函数调用
  migrateAutoUpdatesToSettings();
  migrateBypassPermissionsAcceptedToSettings();
  migrateFennecToOpus();
  // ...其他迁移
  
  // 4. 构建 Commander program
  const program = buildCommanderProgram();
  
  // 5. 解析参数
  program.parse();
  const options = program.opts();
  const args = program.args;
  
  // 6. 根据选项选择启动路径
  if (options.print) {
    // --print 无头模式
    await runHeadlessPrint(options, args);
  } else if (options.resume) {
    // --resume 恢复会话
    await runResumeSession(options);
  } else if (getIsNonInteractiveSession()) {
    // 非交互模式
    await runNonInteractive();
  } else {
    // 交互 TUI 模式
    await init();              // 初始化配置
    await setup(...);          // 环境初始化
    await launchRepl(...);     // 启动 REPL
  }
}

main.tsx 执行流程

graph TD
    subgraph "前置阶段"
        A[profileCheckpoint] --> B[startMdmRawRead]
        B --> C[startKeychainPrefetch]
        C --> D[模块导入<br/>~135ms]
    end
    
    subgraph "迁移阶段"
        D --> E[runMigrations]
        E --> F[migrateAutoUpdates]
        F --> G[migrateBypassPermissions]
        G --> H[migrateModelDefaults]
    end
    
    subgraph "Commander 配置"
        H --> I[buildCommanderProgram]
        I --> J[定义选项]
        J --> K[定义子命令]
        K --> L[program.parse]
    end
    
    subgraph "启动路径选择"
        L --> M{options.print?}
        M --> |"是"| N[runHeadlessPrint]
        M --> |"否"| O{options.resume?}
        O --> |"是"| P[runResumeSession]
        O --> |"否"| Q{非交互?}
        Q --> |"是"| R[runNonInteractive]
        Q --> |"否"| S[交互模式]
        
        S --> T[init 配置加载]
        T --> U[setup 环境初始化]
        U --> V[initBuiltinPlugins]
        V --> W[initBundledSkills]
        W --> X[launchRepl]
    end

init() 初始化函数

// src/entrypoints/init.ts
export async function init(): Promise<void> {
  // 1. 启用配置
  enableConfigs();
  
  // 2. 初始化分析 sinks
  initSinks();
  
  // 3. 初始化遥测
  initializeTelemetryAfterTrust();
  
  // 4. 初始化 GrowthBook
  await initializeGrowthBook();
  
  // 5. 加载策略限制
  await loadPolicyLimits();
  
  // 6. 加载远程管理设置
  await loadRemoteManagedSettings();
  
  // 7. 应用安全环境变量
  applySafeConfigEnvironmentVariables();
}

主入口函数调用顺序

// main.tsx 主入口调用顺序
async function main() {
  // Phase 1: 前置副作用
  profileCheckpoint('main_tsx_entry');
  startMdmRawRead();
  startKeychainPrefetch();
  
  // Phase 2: 大量模块导入
  // ... imports
  
  // Phase 3: 迁移
  runMigrations();
  
  // Phase 4: Commander 配置
  const program = buildProgram();
  program.parse();
  
  // Phase 5: 获取选项
  const options = program.opts();
  const args = program.args;
  
  // Phase 6: 初始化(交互模式)
  await init();
  
  // Phase 7: setup
  await setup(
    getCwd(),
    permissionMode,
    options.dangerouslySkipPermissions,
    options.worktree,
    options.worktreeName,
    options.tmux,
    options.customSessionId,
  );
  
  // Phase 8: 初始化内置插件和 Skills
  initBuiltinPlugins();
  initBundledSkills();
  
  // Phase 9: 启动 REPL
  await launchRepl(root, appProps, replProps, renderAndRun);
}

启动性能优化

启动流程包含多项性能优化:MDM/keychain 预读取并行执行;模块延迟加载;快速路径零/最小加载。实测启动时间约 200-300ms。

4.5 REPL 界面启动

replLauncher.tsx 负责 REPL 界面的启动,加载 App 和 REPL 组件后渲染。

replLauncher 结构

// src/replLauncher.tsx
import React from 'react';
import type { StatsStore } from './context/stats.js';
import type { Root } from './ink.js';
import type { Props as REPLProps } from './screens/REPL.js';
import type { AppState } from './state/AppStateStore.js';
import type { FpsMetrics } from './utils/fpsTracker.js';

type AppWrapperProps = {
  getFpsMetrics: () => FpsMetrics | undefined;
  stats?: StatsStore;
  initialState: AppState;
};

export async function launchRepl(
  root: Root,
  appProps: AppWrapperProps,
  replProps: REPLProps,
  renderAndRun: (root: Root, element: React.ReactNode) => Promise<void>,
): Promise<void> {
  // 动态加载组件
  const { App } = await import('./components/App.js');
  const { REPL } = await import('./screens/REPL.js');
  
  // 渲染并运行
  await renderAndRun(
    root,
    <App {...appProps}>
      <REPL {...replProps} />
    </App>
  );
}

REPL 启动流程

graph TD
    A[launchRepl 调用] --> B[动态加载 App.js]
    B --> C[动态加载 REPL.js]
    C --> D[构建 React 组件树]
    
    D --> E[App 组件]
    E --> F[REPL 组件]
    F --> G[PromptInput]
    F --> H[Messages]
    F --> I[Spinner]
    
    G --> J[renderAndRun]
    H --> J
    I --> J
    
    J --> K[Ink reconciler]
    K --> L[终端渲染]
    L --> M[事件循环启动]

Ink App 组件结构

// src/ink/components/App.tsx(简化)
class App extends PureComponent<Props, State> {
  // 终端事件处理
  handleKeyPress = (key: ParsedKey) => { ... }
  handleClick = (col: number, row: number) => { ... }
  
  // 生命周期
  componentDidMount() {
    // 启动 stdin 事件监听
    // 设置终端模式(mouse tracking, extended keys)
  }
  
  render() {
    return (
      <AppContext.Provider value={...}>
        <StdinContext.Provider value={stdin}>
          <TerminalSizeContext.Provider value={...}>
            <ClockProvider>
              <TerminalFocusProvider>
                {this.props.children}
              </TerminalFocusProvider>
            </ClockProvider>
          </TerminalSizeContext.Provider>
        </StdinContext.Provider>
      </AppContext.Provider>
    );
  }
}

REPL.tsx 核心结构

// src/screens/REPL.tsx(简化结构)
interface REPLProps {
  commands: Command[];
  tools: Tools;
  initialState: AppState;
  // ...其他属性
}

function REPL(props: REPLProps) {
  // 状态管理
  const [messages, setMessages] = useState<Message[]>([]);
  const [input, setInput] = useState('');
  const [isProcessing, setIsProcessing] = useState(false);
  
  // 提交处理
  const handleSubmit = async (userInput: string) => {
    setMessages(prev => [...prev, createUserMessage(userInput)]);
    setIsProcessing(true);
    
    try {
      // 调用 QueryEngine
      const result = await queryEngine.process(userInput);
      setMessages(prev => [...prev, result]);
    } finally {
      setIsProcessing(false);
    }
  };
  
  // 渲染
  return (
    <Box flexDirection="column">
      {/* 消息列表 */}
      <Messages messages={messages} />
      
      {/* 加载指示器 */}
      {isProcessing && <Spinner />}
      
      {/* 输入组件 */}
      <PromptInput
        value={input}
        onChange={setInput}
        onSubmit={handleSubmit}
        commands={props.commands}
      />
      
      {/* Companion 伴侣 */}
      <CompanionSprite />
    </Box>
  );
}

组件树结构

graph TD
    subgraph "App 组件上下文"
        APP[App.tsx]
        APP_CTX[AppContext]
        STDIN_CTX[StdinContext]
        SIZE_CTX[TerminalSizeContext]
        CLOCK[ClockProvider]
        FOCUS[TerminalFocusProvider]
    end
    
    subgraph "REPL 组件"
        REPL[REPL.tsx]
        MSGS[Messages.tsx]
        INPUT[PromptInput]
        SPIN[Spinner]
        COMP_SPRITE[CompanionSprite]
    end
    
    subgraph "消息子组件"
        USER_MSG[UserPromptMessage]
        ASSIST_MSG[AssistantMessage]
        TOOL_MSG[ToolUseMessage]
        ERROR_MSG[ErrorMessage]
    end
    
    APP --> APP_CTX
    APP_CTX --> STDIN_CTX
    STDIN_CTX --> SIZE_CTX
    SIZE_CTX --> CLOCK
    CLOCK --> FOCUS
    FOCUS --> REPL
    
    REPL --> MSGS
    REPL --> INPUT
    REPL --> SPIN
    REPL --> COMP_SPRITE
    
    MSGS --> USER_MSG
    MSGS --> ASSIST_MSG
    MSGS --> TOOL_MSG
    MSGS --> ERROR_MSG

事件处理流程

sequenceDiagram
    participant T as Terminal
    participant APP as App.tsx
    participant INK as Ink Engine
    participant REPL as REPL.tsx
    participant QE as QueryEngine
    
    T->>APP: stdin 数据
    APP->>APP: parseMultipleKeypresses
    APP->>INK: dispatchKeyboardEvent
    
    alt Enter 按键
        INK->>REPL: onSubmit
        REPL->>QE: processUserInput
        QE->>REPL: AssistantMessage
        REPL->>INK: 更新状态
        INK->>T: 渲染输出
    else 其他按键
        INK->>REPL: onChange
        REPL->>INK: 更新输入框
    end

Ink 渲染机制

Ink 使用自定义 React reconciler,将 React 组件树渲染到终端 ANSI 序列。支持 mouse tracking、extended key reporting、OSC 8 hyperlinks 等终端特性。

4.6 Recovery CLI 降级模式

当完整 TUI 出现问题时,可以使用 Recovery CLI 降级模式。

Recovery CLI 入口

// src/localRecoveryCli.ts
import readline from 'readline';

export async function runRecoveryCli() {
  const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout,
  });
  
  // 简化的 readline 交互
  rl.question('> ', async (input) => {
    if (input.trim() === 'exit') {
      rl.close();
      return;
    }
    
    try {
      // 简化的查询处理
      const response = await simpleQuery(input);
      console.log(response);
    } catch (error) {
      console.error('Error:', error.message);
    }
    
    rl.prompt();
  });
  
  rl.prompt();
}

启动 Recovery CLI

# 通过环境变量启用
CLAUDE_CODE_FORCE_RECOVERY_CLI=1 ./bin/claude-haha

# 或通过 LOCAL_RECOVERY
CLAUDE_CODE_LOCAL_RECOVERY=1 ./bin/claude-haha
graph TD
    A[启动] --> B{LOCAL_RECOVERY<br/>or FORCE_RECOVERY_CLI?}
    B --> |"是"| C[跳过 setup]
    C --> D[localRecoveryCli]
    D --> E[readline 交互]
    B --> |"否"| F[正常 TUI 流程]

Recovery CLI 限制

Recovery CLI 不支持:Ink 组件渲染、Companion 伴侣、进度指示器、鼠标交互。仅提供基本的 readline 问答功能。

4.7 启动流程完整图

sequenceDiagram
    participant BIN as bin/claude-haha
    participant CLI as cli.tsx
    participant MAIN as main.tsx
    participant INIT as init.ts
    participant SETUP as setup.ts
    participant REPL as replLauncher
    participant INK as Ink/App
    participant QE as QueryEngine
    
    BIN->>CLI: Bun 启动
    CLI->>CLI: 快速路径判断
    
    alt 快速路径
        CLI->>CLI: 处理并返回
    else 正常路径
        CLI->>MAIN: import main.js
        MAIN->>MAIN: 前置副作用
        MAIN->>MAIN: 模块导入
        MAIN->>MAIN: 迁移
        MAIN->>MAIN: Commander parse
        
        MAIN->>INIT: init()
        INIT->>INIT: enableConfigs
        INIT->>INIT: initSinks
        INIT->>INIT: loadPolicyLimits
        
        MAIN->>SETUP: setup()
        SETUP->>SETUP: Node 版本检查
        SETUP->>SETUP: UDS Messaging
        SETUP->>SETUP: setCwd
        SETUP->>SETUP: Hooks 快照
        SETUP->>SETUP: 后台任务
        
        MAIN->>MAIN: initPlugins/Skills
        MAIN->>REPL: launchRepl()
        REPL->>INK: App + REPL
        INK->>INK: componentDidMount
        INK->>INK: stdin 监听
        
        loop 用户交互
            INK->>QE: handleSubmit
            QE->>INK: updateMessages
        end
    end

下一章将深入探讨 QueryEngine 查询引擎 的核心实现。