import { Command, CommanderError, Option } from "commander"; import { version } from "../../package.json"; import { CLI_COMPACTION_MODE_OPTION_DESCRIPTION, parseCliCompactionMode, } from "../utils/compaction-mode"; import type { ParsedArgs } from "../utils/types"; export { CommanderError }; function normalizeAutoApproveValue( value: string | boolean | undefined, ): string { if (value === undefined || value === true) { return "true"; } return String(value); } /** * Add the shared root-level options to any command. */ export function addRootOptions(cmd: Command): Command { return ( cmd .option("-p, --plan", "Run in plan mode") .option("--json", "Output messages as JSON instead of styled text") .option( "--auto-approve ", "Set tool auto-approval for all tools (default: true)", normalizeAutoApproveValue, ) .option("-c, --cwd ", "Working directory") .option( "--thinking ", "Set reasoning effort: none|low|medium|high|xhigh. Bare --thinking uses medium; omitted leaves provider default.", ) .option("--compaction ", CLI_COMPACTION_MODE_OPTION_DESCRIPTION) .option( "-i, --tui", "Open the terminal user interface (TUI) for interactive sessions", ) .option("--id ", "Resume an existing session by ID") .option("-P, --provider ", "Provider id (default: cline)") .option("-k, --key ", "API key override for this run") .option( "-m, --model ", "Model to use for the session with the selected provider", ) .option( "-s, --system ", "Override the default system prompt", ) .option("-z, --zen", "Start a session that runs in the background hub") .option( "--retries [value]", "Number of maximum consecutive mistakes (retries) before exiting (default: 6)", ) .option( "-t, --timeout ", "Optional timeout in seconds (default: 0 for no timeout)", ) .option( "--acp", "Run in Agent Client Protocol (ACP) mode for editor integration", ) .option("--config ", "Configuration directory (default: ~/.cline)") .option( "--data-dir ", "Use isolated local state at this directory path (default: ~/.cline/data)", ) .option( "--hooks-dir ", "Directory path to additional hooks for runtime hook injection (default: ~/.cline/hooks)", ) .option( "--worktree", "Auto-create a detached git worktree under ~/.cline/worktrees/ and run the task there", ) .option("--update", "Check for updates and install if available") .option("--kanban", "Run the kanban app") .option("-v, --verbose", "Show verbose output") // HIDDEN/LEGACY OPTIONS BELOW .addOption( // Act mode is the default. Keep the legacy flags accepted for users who // still pass them, but do not advertise them in help output. new Option("-a, --act", "Run in act mode").hideHelp(), ) .addOption( // `-y, --yolo` is still accepted (and behaves the same as before) but // hidden from `--help` output. new Option( "-y, --yolo", "Enable yolo mode where agents can use tools without approval with only a small set of tools available.", ).hideHelp(), ) .addOption( // TODO: Refactor teams to resume session without team name new Option( "--team-name ", "Override the runtime team state name", ).hideHelp(), ) ); } export function createProgram(): Command { const program = new Command("cline") .description("Cline CLI - AI coding assistant in your terminal") .version(version, "-V, --version", "Output the version number") .exitOverride() // don't call process.exit .configureOutput({ writeOut: () => {}, // suppress by default; main.ts re-enables for routing writeErr: () => {}, }) .allowExcessArguments() .enablePositionalOptions() .argument( "[prompt]", "Your prompt. Default to start in act mode with auto-approve enabled.", ); addRootOptions(program); return program; } export function commanderToParsedArgs(program: Command): ParsedArgs { const opts = program.opts(); const result: ParsedArgs = { verbose: !!opts.verbose, interactive: !!opts.tui, outputMode: opts.json ? "json" : "text", mode: opts.plan ? "plan" : opts.yolo ? "yolo" : opts.zen ? "zen" : "act", modeExplicitlySet: !!(opts.plan || opts.act || opts.yolo || opts.zen), sandbox: !!opts.dataDir, acpMode: !!opts.acp, thinking: false, reasoningEffort: undefined, defaultToolAutoApprove: true, id: opts.id, }; // Approval: last-wins semantics if (opts.autoApprove !== undefined) { const raw = String(opts.autoApprove).trim().toLowerCase(); if (raw === "true") { result.defaultToolAutoApprove = true; result.autoApproveOverride = true; } else if (raw === "false") { result.defaultToolAutoApprove = false; result.autoApproveOverride = false; } else if (raw) { result.invalidAutoApprove = raw; } } if (opts.yolo) { result.defaultToolAutoApprove = true; result.autoApproveOverride = true; } // Timeout validation if (opts.timeout !== undefined) { const raw = opts.timeout.trim(); const parsed = Number.parseInt(raw, 10); if (raw && Number.isInteger(parsed) && parsed >= 1) { result.timeoutSeconds = parsed; } else if (raw) { result.invalidTimeoutSeconds = raw; } } if (opts.thinking !== undefined) { const effort = String(opts.thinking).trim().toLowerCase(); if ( effort === "none" || effort === "low" || effort === "medium" || effort === "high" || effort === "xhigh" ) { result.thinkingExplicitlySet = true; if (effort === "none") { result.thinking = false; result.reasoningEffort = undefined; } else { result.thinking = true; result.reasoningEffort = effort; } } else if (effort) { result.invalidThinkingLevel = effort; } } if (opts.compaction !== undefined) { const mode = String(opts.compaction).trim().toLowerCase(); const compactionMode = parseCliCompactionMode(mode); if (compactionMode) { result.compactionMode = compactionMode; } else if (mode) { result.invalidCompactionMode = mode; } } // Retries (max consecutive mistakes) validation if (opts.retries !== undefined) { const raw = opts.retries.trim(); const parsed = Number.parseInt(raw, 10); if (raw && Number.isInteger(parsed) && parsed >= 1) { result.retries = parsed; } else if (raw) { result.invalidRetries = raw; } } // Simple string/number options if (opts.dataDir !== undefined) result.dataDir = opts.dataDir; if (opts.config !== undefined) result.configDir = opts.config; if (opts.hooksDir !== undefined) result.hooksDir = opts.hooksDir; if (opts.worktree !== undefined) result.worktree = !!opts.worktree; if (opts.cwd !== undefined) result.cwd = opts.cwd; if (opts.teamName !== undefined) result.teamName = opts.teamName; if (opts.system !== undefined) result.systemPrompt = opts.system; if (opts.model !== undefined) result.model = opts.model; if (opts.provider !== undefined) result.provider = opts.provider; if (opts.key !== undefined) result.key = opts.key; else if (opts.apiKey !== undefined) result.key = opts.apiKey; if (opts.id !== undefined) result.id = opts.id; // Positional args → prompt const positional = program.args; if (positional.length > 0) { result.prompt = positional.join(" "); } return result; }