209 lines
7.5 KiB
TypeScript
209 lines
7.5 KiB
TypeScript
/**
|
|
* Root command for the coding agent CLI.
|
|
*/
|
|
|
|
import { APP_NAME } from "@oh-my-pi/pi-utils";
|
|
import { Args, Command, Flags } from "@oh-my-pi/pi-utils/cli";
|
|
import { type Args as ParsedArgs, parseArgs, reportCliUsageError } from "../cli/args";
|
|
import { runRootCommand } from "../main";
|
|
import { prepareAcpTerminalAuthArgs } from "../modes/acp/terminal-auth";
|
|
import { CLI_THINKING_LEVELS } from "../thinking";
|
|
|
|
export default class Index extends Command {
|
|
static description = "AI coding assistant";
|
|
static hidden = true;
|
|
|
|
static args = {
|
|
messages: Args.string({
|
|
description: "Messages to send (prefix files with @)",
|
|
required: false,
|
|
multiple: true,
|
|
}),
|
|
};
|
|
|
|
static flags = {
|
|
model: Flags.string({
|
|
description: 'Model to use (fuzzy match: "opus", "gpt-5.2", or "openai/gpt-5.2")',
|
|
}),
|
|
smol: Flags.string({
|
|
description: "Smol/fast model for lightweight tasks (or PI_SMOL_MODEL env)",
|
|
}),
|
|
slow: Flags.string({
|
|
description: "Slow/reasoning model for thorough analysis (or PI_SLOW_MODEL env)",
|
|
}),
|
|
plan: Flags.string({
|
|
description: "Plan model for architectural planning (or PI_PLAN_MODEL env)",
|
|
}),
|
|
prewalk: Flags.boolean({
|
|
description:
|
|
"Switch from the active model to a fast/cheap model at the first edit/write after the plan's todo list exists (default off; see prewalk.enabled)",
|
|
}),
|
|
"no-prewalk": Flags.boolean({
|
|
description: "Disable prewalk even if prewalk.enabled is set",
|
|
}),
|
|
"prewalk-into": Flags.string({
|
|
description: 'Target model for prewalk (default the "smol" role)',
|
|
}),
|
|
"plan-yolo": Flags.boolean({
|
|
description:
|
|
"Force read-only plan mode at start, auto-approve the plan on the model's first resolve call, then switch to --plan-yolo-into to implement it",
|
|
}),
|
|
"plan-yolo-into": Flags.string({
|
|
description: 'Target model for plan-yolo execution (default the "smol" role)',
|
|
}),
|
|
provider: Flags.string({
|
|
description: "Provider to use (legacy; prefer --model)",
|
|
}),
|
|
"api-key": Flags.string({
|
|
description: "API key (defaults to env vars)",
|
|
}),
|
|
"system-prompt": Flags.string({
|
|
description: "System prompt (default: coding assistant prompt)",
|
|
}),
|
|
"append-system-prompt": Flags.string({
|
|
description: "Append text or file contents to the system prompt",
|
|
}),
|
|
"allow-home": Flags.boolean({
|
|
description: "Allow starting in ~ without auto-switching to a temp dir",
|
|
}),
|
|
profile: Flags.string({
|
|
description: "Use an isolated profile for auth, sessions, settings, and caches",
|
|
}),
|
|
alias: Flags.string({
|
|
description: "Create a shell shortcut for the selected profile and exit",
|
|
}),
|
|
cwd: Flags.string({
|
|
description: "Directory to start in (overrides the launch cwd)",
|
|
}),
|
|
mode: Flags.string({
|
|
description: "Output mode: text (default), json, rpc, or rpc-ui",
|
|
options: ["text", "json", "rpc", "acp", "rpc-ui"],
|
|
}),
|
|
config: Flags.string({
|
|
description: "Load an extra config.yml-style overlay for this run (repeatable)",
|
|
multiple: true,
|
|
}),
|
|
print: Flags.boolean({
|
|
char: "p",
|
|
description: "Non-interactive mode: process prompt and exit",
|
|
}),
|
|
continue: Flags.boolean({
|
|
char: "c",
|
|
description: "Continue previous session",
|
|
}),
|
|
resume: Flags.string({
|
|
char: "r",
|
|
description: "Resume a session (by ID prefix, path, or picker if omitted)",
|
|
}),
|
|
"session-dir": Flags.string({
|
|
description: "Directory for session storage and lookup",
|
|
}),
|
|
"no-session": Flags.boolean({
|
|
description: "Don't save session (ephemeral)",
|
|
}),
|
|
models: Flags.string({
|
|
description: "Comma-separated model patterns for Ctrl+P cycling",
|
|
}),
|
|
"no-tools": Flags.boolean({
|
|
description: "Disable all built-in tools",
|
|
}),
|
|
"no-lsp": Flags.boolean({
|
|
description: "Disable LSP tools, formatting, and diagnostics",
|
|
}),
|
|
"no-pty": Flags.boolean({
|
|
description: "Disable PTY-based interactive bash execution",
|
|
}),
|
|
tools: Flags.string({
|
|
description: "Comma-separated list of tools to enable (default: all)",
|
|
}),
|
|
thinking: Flags.string({
|
|
description: `Set thinking level: ${CLI_THINKING_LEVELS.join(", ")}`,
|
|
options: [...CLI_THINKING_LEVELS],
|
|
}),
|
|
"hide-thinking": Flags.boolean({
|
|
description: "Hide thinking blocks in TUI output (display only, does not disable model thinking)",
|
|
}),
|
|
advisor: Flags.boolean({
|
|
description: "Enable the advisor runtime (passively reviews each turn and injects notes)",
|
|
}),
|
|
hook: Flags.string({
|
|
description: "Load a hook/extension file (can be used multiple times)",
|
|
multiple: true,
|
|
}),
|
|
extension: Flags.string({
|
|
char: "e",
|
|
description: "Load an extension file (can be used multiple times)",
|
|
multiple: true,
|
|
}),
|
|
"no-extensions": Flags.boolean({
|
|
description: "Disable extension discovery (explicit -e paths still work)",
|
|
}),
|
|
"no-skills": Flags.boolean({
|
|
description: "Disable skills discovery and loading",
|
|
}),
|
|
skills: Flags.string({
|
|
description: "Comma-separated glob patterns to filter skills (e.g., git-*,docker)",
|
|
}),
|
|
"no-rules": Flags.boolean({
|
|
description: "Disable rules discovery and loading",
|
|
}),
|
|
export: Flags.string({
|
|
description: "Export session file to HTML and exit",
|
|
}),
|
|
"no-title": Flags.boolean({
|
|
description: "Disable title auto-generation",
|
|
}),
|
|
"print-thoughts": Flags.boolean({
|
|
description: "Include thinking blocks in print mode text output",
|
|
}),
|
|
"max-time": Flags.string({
|
|
description: "Stop the session after this duration (e.g., 600, 10m, 1h)",
|
|
}),
|
|
// `--auto-approve` / `--yolo`: declared here so oclif's auto-generated `--help` lists it.
|
|
// Runtime parsing happens in `cli/args.ts parseArgs` (line 176 in that file) — `runRootCommand`
|
|
// consumes the manual-parser output, not these oclif flag values. If you rename or remove
|
|
// either form, update both call sites in lockstep.
|
|
"auto-approve": Flags.boolean({
|
|
aliases: ["yolo"],
|
|
description: "Auto-approve all tool calls (skip approval prompts)",
|
|
}),
|
|
// `--approval-mode`: declared here so oclif's auto-generated `--help` lists it; runtime parsing
|
|
// happens in `cli/args.ts parseArgs`. The value is applied via `Settings.override("tools.approvalMode", …)`
|
|
// in `main.ts` after the `Settings` instance is constructed, so every `settings.get("tools.approvalMode")`
|
|
// site (wrapper, `/settings` UI) observes the same value.
|
|
"approval-mode": Flags.string({
|
|
options: ["always-ask", "write", "yolo"],
|
|
description: "Override tools.approvalMode for this session (always-ask|write|yolo)",
|
|
}),
|
|
};
|
|
|
|
static examples = [
|
|
`# Interactive mode\n ${APP_NAME}`,
|
|
`# Interactive mode with initial prompt\n ${APP_NAME} "List all .ts files in src/"`,
|
|
`# Include files in initial message\n ${APP_NAME} @prompt.md @image.png "What color is the sky?"`,
|
|
`# Non-interactive mode (process and exit)\n ${APP_NAME} -p "List all .ts files in src/"`,
|
|
`# Continue previous session\n ${APP_NAME} --continue "What did we discuss?"`,
|
|
`# Create a shell shortcut for a work profile\n ${APP_NAME} --profile work --alias omp-work`,
|
|
`# Use different model (fuzzy matching)\n ${APP_NAME} --model opus "Help me refactor this code"`,
|
|
`# Limit model cycling to specific models\n ${APP_NAME} --models claude-sonnet,claude-haiku,gpt-4o`,
|
|
`# Export a session file to HTML\n ${APP_NAME} --export ~/.omp/agent/sessions/--path--/session.jsonl`,
|
|
];
|
|
|
|
static strict = false;
|
|
|
|
async run(): Promise<void> {
|
|
const { args } = prepareAcpTerminalAuthArgs(this.argv);
|
|
let parsed: ParsedArgs;
|
|
try {
|
|
parsed = parseArgs(args);
|
|
} catch (error) {
|
|
if (reportCliUsageError(error)) {
|
|
process.exitCode = 2;
|
|
return;
|
|
}
|
|
throw error;
|
|
}
|
|
await runRootCommand(parsed, args);
|
|
}
|
|
}
|