cli.tsx 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  1. // Runtime polyfill for bun:bundle (build-time macros)
  2. const feature = (_name: string) => false;
  3. if (typeof globalThis.MACRO === "undefined") {
  4. (globalThis as any).MACRO = {
  5. VERSION: "2.1.888",
  6. BUILD_TIME: new Date().toISOString(),
  7. FEEDBACK_CHANNEL: "",
  8. ISSUES_EXPLAINER: "",
  9. NATIVE_PACKAGE_URL: "",
  10. PACKAGE_URL: "",
  11. VERSION_CHANGELOG: "",
  12. };
  13. }
  14. // Bugfix for corepack auto-pinning, which adds yarnpkg to peoples' package.jsons
  15. // eslint-disable-next-line custom-rules/no-top-level-side-effects
  16. process.env.COREPACK_ENABLE_AUTO_PIN = "0";
  17. // Set max heap size for child processes in CCR environments (containers have 16GB)
  18. // eslint-disable-next-line custom-rules/no-top-level-side-effects, custom-rules/no-process-env-top-level, custom-rules/safe-env-boolean-check
  19. if (process.env.CLAUDE_CODE_REMOTE === "true") {
  20. // eslint-disable-next-line custom-rules/no-top-level-side-effects, custom-rules/no-process-env-top-level
  21. const existing = process.env.NODE_OPTIONS || "";
  22. // eslint-disable-next-line custom-rules/no-top-level-side-effects, custom-rules/no-process-env-top-level
  23. process.env.NODE_OPTIONS = existing
  24. ? `${existing} --max-old-space-size=8192`
  25. : "--max-old-space-size=8192";
  26. }
  27. // Harness-science L0 ablation baseline. Inlined here (not init.ts) because
  28. // BashTool/AgentTool/PowerShellTool capture DISABLE_BACKGROUND_TASKS into
  29. // module-level consts at import time — init() runs too late. feature() gate
  30. // DCEs this entire block from external builds.
  31. // eslint-disable-next-line custom-rules/no-top-level-side-effects, custom-rules/no-process-env-top-level
  32. if (feature("ABLATION_BASELINE") && process.env.CLAUDE_CODE_ABLATION_BASELINE) {
  33. for (const k of [
  34. "CLAUDE_CODE_SIMPLE",
  35. "CLAUDE_CODE_DISABLE_THINKING",
  36. "DISABLE_INTERLEAVED_THINKING",
  37. "DISABLE_COMPACT",
  38. "DISABLE_AUTO_COMPACT",
  39. "CLAUDE_CODE_DISABLE_AUTO_MEMORY",
  40. "CLAUDE_CODE_DISABLE_BACKGROUND_TASKS",
  41. ]) {
  42. // eslint-disable-next-line custom-rules/no-top-level-side-effects, custom-rules/no-process-env-top-level
  43. process.env[k] ??= "1";
  44. }
  45. }
  46. /**
  47. * Bootstrap entrypoint - checks for special flags before loading the full CLI.
  48. * All imports are dynamic to minimize module evaluation for fast paths.
  49. * Fast-path for --version has zero imports beyond this file.
  50. */
  51. async function main(): Promise<void> {
  52. const args = process.argv.slice(2);
  53. // Fast-path for --version/-v: zero module loading needed
  54. if (
  55. args.length === 1 &&
  56. (args[0] === "--version" || args[0] === "-v" || args[0] === "-V")
  57. ) {
  58. // MACRO.VERSION is inlined at build time
  59. // biome-ignore lint/suspicious/noConsole:: intentional console output
  60. console.log(`${MACRO.VERSION} (Claude Code)`);
  61. return;
  62. }
  63. // For all other paths, load the startup profiler
  64. const { profileCheckpoint } = await import("../utils/startupProfiler.js");
  65. profileCheckpoint("cli_entry");
  66. // Fast-path for --dump-system-prompt: output the rendered system prompt and exit.
  67. // Used by prompt sensitivity evals to extract the system prompt at a specific commit.
  68. // Ant-only: eliminated from external builds via feature flag.
  69. if (feature("DUMP_SYSTEM_PROMPT") && args[0] === "--dump-system-prompt") {
  70. profileCheckpoint("cli_dump_system_prompt_path");
  71. const { enableConfigs } = await import("../utils/config.js");
  72. enableConfigs();
  73. const { getMainLoopModel } = await import("../utils/model/model.js");
  74. const modelIdx = args.indexOf("--model");
  75. const model =
  76. (modelIdx !== -1 && args[modelIdx + 1]) || getMainLoopModel();
  77. const { getSystemPrompt } = await import("../constants/prompts.js");
  78. const prompt = await getSystemPrompt([], model);
  79. // biome-ignore lint/suspicious/noConsole:: intentional console output
  80. console.log(prompt.join("\n"));
  81. return;
  82. }
  83. if (process.argv[2] === "--claude-in-chrome-mcp") {
  84. profileCheckpoint("cli_claude_in_chrome_mcp_path");
  85. const { runClaudeInChromeMcpServer } =
  86. await import("../utils/claudeInChrome/mcpServer.js");
  87. await runClaudeInChromeMcpServer();
  88. return;
  89. } else if (process.argv[2] === "--chrome-native-host") {
  90. profileCheckpoint("cli_chrome_native_host_path");
  91. const { runChromeNativeHost } =
  92. await import("../utils/claudeInChrome/chromeNativeHost.js");
  93. await runChromeNativeHost();
  94. return;
  95. } else if (
  96. feature("CHICAGO_MCP") &&
  97. process.argv[2] === "--computer-use-mcp"
  98. ) {
  99. profileCheckpoint("cli_computer_use_mcp_path");
  100. const { runComputerUseMcpServer } =
  101. await import("../utils/computerUse/mcpServer.js");
  102. await runComputerUseMcpServer();
  103. return;
  104. }
  105. // Fast-path for `--daemon-worker=<kind>` (internal — supervisor spawns this).
  106. // Must come before the daemon subcommand check: spawned per-worker, so
  107. // perf-sensitive. No enableConfigs(), no analytics sinks at this layer —
  108. // workers are lean. If a worker kind needs configs/auth (assistant will),
  109. // it calls them inside its run() fn.
  110. if (feature("DAEMON") && args[0] === "--daemon-worker") {
  111. const { runDaemonWorker } = await import("../daemon/workerRegistry.js");
  112. await runDaemonWorker(args[1]);
  113. return;
  114. }
  115. // Fast-path for `claude remote-control` (also accepts legacy `claude remote` / `claude sync` / `claude bridge`):
  116. // serve local machine as bridge environment.
  117. // feature() must stay inline for build-time dead code elimination;
  118. // isBridgeEnabled() checks the runtime GrowthBook gate.
  119. if (
  120. feature("BRIDGE_MODE") &&
  121. (args[0] === "remote-control" ||
  122. args[0] === "rc" ||
  123. args[0] === "remote" ||
  124. args[0] === "sync" ||
  125. args[0] === "bridge")
  126. ) {
  127. profileCheckpoint("cli_bridge_path");
  128. const { enableConfigs } = await import("../utils/config.js");
  129. enableConfigs();
  130. const { getBridgeDisabledReason, checkBridgeMinVersion } =
  131. await import("../bridge/bridgeEnabled.js");
  132. const { BRIDGE_LOGIN_ERROR } = await import("../bridge/types.js");
  133. const { bridgeMain } = await import("../bridge/bridgeMain.js");
  134. const { exitWithError } = await import("../utils/process.js");
  135. // Auth check must come before the GrowthBook gate check — without auth,
  136. // GrowthBook has no user context and would return a stale/default false.
  137. // getBridgeDisabledReason awaits GB init, so the returned value is fresh
  138. // (not the stale disk cache), but init still needs auth headers to work.
  139. const { getClaudeAIOAuthTokens } = await import("../utils/auth.js");
  140. if (!getClaudeAIOAuthTokens()?.accessToken) {
  141. exitWithError(BRIDGE_LOGIN_ERROR);
  142. }
  143. const disabledReason = await getBridgeDisabledReason();
  144. if (disabledReason) {
  145. exitWithError(`Error: ${disabledReason}`);
  146. }
  147. const versionError = checkBridgeMinVersion();
  148. if (versionError) {
  149. exitWithError(versionError);
  150. }
  151. // Bridge is a remote control feature - check policy limits
  152. const { waitForPolicyLimitsToLoad, isPolicyAllowed } =
  153. await import("../services/policyLimits/index.js");
  154. await waitForPolicyLimitsToLoad();
  155. if (!isPolicyAllowed("allow_remote_control")) {
  156. exitWithError(
  157. "Error: Remote Control is disabled by your organization's policy.",
  158. );
  159. }
  160. await bridgeMain(args.slice(1));
  161. return;
  162. }
  163. // Fast-path for `claude daemon [subcommand]`: long-running supervisor.
  164. if (feature("DAEMON") && args[0] === "daemon") {
  165. profileCheckpoint("cli_daemon_path");
  166. const { enableConfigs } = await import("../utils/config.js");
  167. enableConfigs();
  168. const { initSinks } = await import("../utils/sinks.js");
  169. initSinks();
  170. const { daemonMain } = await import("../daemon/main.js");
  171. await daemonMain(args.slice(1));
  172. return;
  173. }
  174. // Fast-path for `claude ps|logs|attach|kill` and `--bg`/`--background`.
  175. // Session management against the ~/.claude/sessions/ registry. Flag
  176. // literals are inlined so bg.js only loads when actually dispatching.
  177. if (
  178. feature("BG_SESSIONS") &&
  179. (args[0] === "ps" ||
  180. args[0] === "logs" ||
  181. args[0] === "attach" ||
  182. args[0] === "kill" ||
  183. args.includes("--bg") ||
  184. args.includes("--background"))
  185. ) {
  186. profileCheckpoint("cli_bg_path");
  187. const { enableConfigs } = await import("../utils/config.js");
  188. enableConfigs();
  189. const bg = await import("../cli/bg.js");
  190. switch (args[0]) {
  191. case "ps":
  192. await bg.psHandler(args.slice(1));
  193. break;
  194. case "logs":
  195. await bg.logsHandler(args[1]);
  196. break;
  197. case "attach":
  198. await bg.attachHandler(args[1]);
  199. break;
  200. case "kill":
  201. await bg.killHandler(args[1]);
  202. break;
  203. default:
  204. await bg.handleBgFlag(args);
  205. }
  206. return;
  207. }
  208. // Fast-path for template job commands.
  209. if (
  210. feature("TEMPLATES") &&
  211. (args[0] === "new" || args[0] === "list" || args[0] === "reply")
  212. ) {
  213. profileCheckpoint("cli_templates_path");
  214. const { templatesMain } =
  215. await import("../cli/handlers/templateJobs.js");
  216. await templatesMain(args);
  217. // process.exit (not return) — mountFleetView's Ink TUI can leave event
  218. // loop handles that prevent natural exit.
  219. // eslint-disable-next-line custom-rules/no-process-exit
  220. process.exit(0);
  221. }
  222. // Fast-path for `claude environment-runner`: headless BYOC runner.
  223. // feature() must stay inline for build-time dead code elimination.
  224. if (
  225. feature("BYOC_ENVIRONMENT_RUNNER") &&
  226. args[0] === "environment-runner"
  227. ) {
  228. profileCheckpoint("cli_environment_runner_path");
  229. const { environmentRunnerMain } =
  230. await import("../environment-runner/main.js");
  231. await environmentRunnerMain(args.slice(1));
  232. return;
  233. }
  234. // Fast-path for `claude self-hosted-runner`: headless self-hosted-runner
  235. // targeting the SelfHostedRunnerWorkerService API (register + poll; poll IS
  236. // heartbeat). feature() must stay inline for build-time dead code elimination.
  237. if (feature("SELF_HOSTED_RUNNER") && args[0] === "self-hosted-runner") {
  238. profileCheckpoint("cli_self_hosted_runner_path");
  239. const { selfHostedRunnerMain } =
  240. await import("../self-hosted-runner/main.js");
  241. await selfHostedRunnerMain(args.slice(1));
  242. return;
  243. }
  244. // Fast-path for --worktree --tmux: exec into tmux before loading full CLI
  245. const hasTmuxFlag =
  246. args.includes("--tmux") || args.includes("--tmux=classic");
  247. if (
  248. hasTmuxFlag &&
  249. (args.includes("-w") ||
  250. args.includes("--worktree") ||
  251. args.some((a) => a.startsWith("--worktree=")))
  252. ) {
  253. profileCheckpoint("cli_tmux_worktree_fast_path");
  254. const { enableConfigs } = await import("../utils/config.js");
  255. enableConfigs();
  256. const { isWorktreeModeEnabled } =
  257. await import("../utils/worktreeModeEnabled.js");
  258. if (isWorktreeModeEnabled()) {
  259. const { execIntoTmuxWorktree } =
  260. await import("../utils/worktree.js");
  261. const result = await execIntoTmuxWorktree(args);
  262. if (result.handled) {
  263. return;
  264. }
  265. // If not handled (e.g., error), fall through to normal CLI
  266. if (result.error) {
  267. const { exitWithError } = await import("../utils/process.js");
  268. exitWithError(result.error);
  269. }
  270. }
  271. }
  272. // Redirect common update flag mistakes to the update subcommand
  273. if (
  274. args.length === 1 &&
  275. (args[0] === "--update" || args[0] === "--upgrade")
  276. ) {
  277. process.argv = [process.argv[0]!, process.argv[1]!, "update"];
  278. }
  279. // --bare: set SIMPLE early so gates fire during module eval / commander
  280. // option building (not just inside the action handler).
  281. if (args.includes("--bare")) {
  282. process.env.CLAUDE_CODE_SIMPLE = "1";
  283. }
  284. // No special flags detected, load and run the full CLI
  285. const { startCapturingEarlyInput } = await import("../utils/earlyInput.js");
  286. startCapturingEarlyInput();
  287. profileCheckpoint("cli_before_main_import");
  288. const { main: cliMain } = await import("../main.jsx");
  289. profileCheckpoint("cli_after_main_import");
  290. await cliMain();
  291. profileCheckpoint("cli_after_main_complete");
  292. }
  293. // eslint-disable-next-line custom-rules/no-top-level-side-effects
  294. void main();