Something went wrong. Try again.
fork of prime agent with changes i want but are not upstreamable probqbly (and some fixes)
Something went wrong. Try again.
15 kB · 426 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427/** * CLI argument parsing and help display */
import type { ThinkingLevel } from "@earendil-works/pi-agent-core";import { APP_NAME } from "../config.js";import { THINKING_LEVELS } from "../core/thinking-levels.js";
export type Mode = "text" | "json" | "rpc" | "acp" | "daemon";
export interface Args { provider?: string; model?: string; apiKey?: string; cwd?: string; systemPrompt?: string; appendSystemPrompt?: string[]; thinking?: ThinkingLevel; continue?: boolean; resume?: true | string; help?: boolean; version?: boolean; mode?: Mode; daemonSocket?: string; noSession?: boolean; fork?: string; sessionDir?: string; models?: string[]; tools?: string[]; noTools?: boolean; noBuiltinTools?: boolean; extensions?: string[]; noExtensions?: boolean; print?: boolean; export?: string; noSkills?: boolean; skills?: string[]; promptTemplates?: string[]; noPromptTemplates?: boolean; themes?: string[]; noThemes?: boolean; noContextFiles?: boolean; autonomous?: boolean; autonomousGates?: string[]; autonomousGateRetries?: number; autonomousGateTimeoutMs?: number; autonomousMaxContinuations?: number; autonomousMaxTurns?: number; autonomousMaxTokens?: number; autonomousTimeoutMs?: number; goal?: string; goalTokenBudget?: number; listModels?: string | true; offline?: boolean; verbose?: boolean; remotePython?: string; remotePythonHost?: string; messages: string[]; fileArgs: string[]; /** Unknown flags (potentially extension flags) - map of flag name to value */ unknownFlags: Map<string, boolean | string>; diagnostics: Array<{ type: "warning" | "error"; message: string }>;}
const REMOVED_BUILTIN_TOOL_NAMES = new Set(["read", "write", "grep", "find", "ls"]);const BUILTIN_TOOL_NAMES = ["ipython"];/** Value flags whose free-form text (an objective, a gate command) may legitimately start with a dash. */const FREEFORM_VALUE_FLAGS = new Set(["--goal", "--autonomous-gate"]);/** Prompt value flags whose text may start with a long-option-looking token, e.g. YAML frontmatter ("---"). */const PROMPT_VALUE_FLAGS = new Set(["--system-prompt", "--append-system-prompt"]);
export const INTERNAL_RUNTIME_COMMAND_MARKER = "\0prime-agent-runtime-command";
export function isValidThinkingLevel(level: string): level is ThinkingLevel { return THINKING_LEVELS.includes(level as ThinkingLevel);}
export function parseArgs(args: string[]): Args { const result: Args = { messages: [], fileArgs: [], unknownFlags: new Map(), diagnostics: [], };
let endOfOptions = false; const internalRuntimeCommand = args[0] === INTERNAL_RUNTIME_COMMAND_MARKER; const firstArgIndex = internalRuntimeCommand ? 1 : 0;
for (let i = firstArgIndex; i < args.length; i++) { const arg = args[i];
// POSIX end-of-options: everything after a standalone "--" is a positional // message, even if it starts with a dash (e.g. a Markdown-bullet prompt). if (endOfOptions) { result.messages.push(arg); continue; } if (arg === "--") { endOfOptions = true; continue; }
if (arg === "--help" || arg === "-h") { result.help = true; } else if (arg === "--version" || arg === "-v") { result.version = true; } else if (arg === "--mode") { if (hasRequiredOptionValue(args, i, arg, result)) { const mode = args[++i]; if (mode === "text" || mode === "json" || mode === "rpc" || mode === "acp" || mode === "daemon") { result.mode = mode; } else { result.diagnostics.push({ type: "error", message: `Invalid --mode "${mode}". Valid values: text, json, rpc, acp, daemon`, }); } } } else if (arg === "--daemon-socket") { if (hasRequiredOptionValue(args, i, arg, result)) { result.daemonSocket = args[++i]; } } else if (arg === "--continue" || arg === "-c") { result.continue = true; } else if (arg === "--resume" || arg === "-r") { const next = args[i + 1]; if (next !== undefined && !next.startsWith("-") && !next.startsWith("@")) { if (next === "") { result.resume = true; i++; } else { result.resume = next; i++; } } else { result.resume = true; } } else if (arg.startsWith("--resume=")) { const value = arg.slice("--resume=".length); if (!value) { result.resume = true; } else { result.resume = value; } } else if (arg === "--provider") { if (hasRequiredOptionValue(args, i, arg, result)) { result.provider = args[++i]; } } else if (arg === "--model") { if (hasRequiredOptionValue(args, i, arg, result)) { result.model = args[++i]; } } else if (arg === "--api-key") { if (hasRequiredOptionValue(args, i, arg, result)) { result.apiKey = args[++i]; } } else if (arg === "--cwd") { if (hasRequiredOptionValue(args, i, arg, result)) { result.cwd = args[++i]; } } else if (arg === "--system-prompt") { if (hasRequiredOptionValue(args, i, arg, result)) { result.systemPrompt = args[++i]; } } else if (arg === "--append-system-prompt") { if (hasRequiredOptionValue(args, i, arg, result)) { result.appendSystemPrompt = result.appendSystemPrompt ?? []; result.appendSystemPrompt.push(args[++i]); } } else if (arg === "--no-session") { result.noSession = true; } else if (arg === "--fork") { if (hasRequiredOptionValue(args, i, arg, result)) { result.fork = args[++i]; } } else if (arg === "--session-dir") { if (hasRequiredOptionValue(args, i, arg, result)) { result.sessionDir = args[++i]; } } else if (arg === "--models") { if (hasRequiredOptionValue(args, i, arg, result)) { result.models = args[++i].split(",").map((s) => s.trim()); } } else if (arg === "--no-tools" || arg === "-nt") { result.noTools = true; } else if (arg === "--no-builtin-tools" || arg === "-nbt") { result.noBuiltinTools = true; } else if (arg === "--tools" || arg === "-t") { if (hasRequiredOptionValue(args, i, arg, result)) { result.tools = args[++i] .split(",") .map((s) => s.trim()) .filter((name) => name.length > 0); const removedTools = result.tools.filter((name) => REMOVED_BUILTIN_TOOL_NAMES.has(name)); if (removedTools.length > 0) { result.diagnostics.push({ type: "error", message: `Unknown built-in tool(s): ${removedTools.join(", ")}. Available built-in tools: ${BUILTIN_TOOL_NAMES.join(", ")}`, }); } } } else if (arg === "--thinking") { if (hasRequiredOptionValue(args, i, arg, result)) { const level = args[++i]; if (isValidThinkingLevel(level)) { result.thinking = level; } else { result.diagnostics.push({ type: "error", message: `Invalid thinking level "${level}". Valid values: ${THINKING_LEVELS.join(", ")}`, }); } } } else if (arg === "--print" || arg === "-p") { result.print = true; const next = args[i + 1]; if (next !== undefined && !next.startsWith("@") && (!next.startsWith("-") || next.startsWith("---"))) { result.messages.push(next); i++; } } else if (arg === "--export") { if (!internalRuntimeCommand) { result.diagnostics.push({ type: "error", message: `--export was removed. Use "${APP_NAME} session export <file> [output]".`, }); if (i + 1 < args.length && !args[i + 1].startsWith("-")) i++; } else if (i + 1 < args.length) { result.export = args[++i]; } else { result.diagnostics.push({ type: "error", message: "--export requires a value" }); } } else if (arg.startsWith("--export=")) { result.diagnostics.push({ type: "error", message: `--export was removed. Use "${APP_NAME} session export <file> [output]".`, }); } else if (arg === "--extension" || arg === "-e") { if (hasRequiredOptionValue(args, i, arg, result)) { result.extensions = result.extensions ?? []; result.extensions.push(args[++i]); } } else if (arg === "--no-extensions" || arg === "-ne") { result.noExtensions = true; } else if (arg === "--skill") { if (hasRequiredOptionValue(args, i, arg, result)) { result.skills = result.skills ?? []; result.skills.push(args[++i]); } } else if (arg === "--prompt-template") { if (hasRequiredOptionValue(args, i, arg, result)) { result.promptTemplates = result.promptTemplates ?? []; result.promptTemplates.push(args[++i]); } } else if (arg === "--theme") { if (hasRequiredOptionValue(args, i, arg, result)) { result.themes = result.themes ?? []; result.themes.push(args[++i]); } } else if (arg === "--no-skills" || arg === "-ns") { result.noSkills = true; } else if (arg === "--no-prompt-templates" || arg === "-np") { result.noPromptTemplates = true; } else if (arg === "--no-themes") { result.noThemes = true; } else if (arg === "--no-context-files" || arg === "-nc") { result.noContextFiles = true; } else if (arg === "--autonomous") { result.autonomous = true; } else if (arg === "--autonomous-gate") { result.autonomous = true; if (hasRequiredOptionValue(args, i, arg, result)) { result.autonomousGates = result.autonomousGates ?? []; result.autonomousGates.push(args[++i]); } } else if (arg === "--autonomous-gate-retries") { result.autonomous = true; if (hasRequiredOptionValue(args, i, arg, result)) { result.autonomousGateRetries = parsePositiveInt(args[++i], "--autonomous-gate-retries", result); } } else if (arg === "--autonomous-gate-timeout-ms") { result.autonomous = true; if (hasRequiredOptionValue(args, i, arg, result)) { result.autonomousGateTimeoutMs = parsePositiveInt(args[++i], "--autonomous-gate-timeout-ms", result); } } else if (arg === "--autonomous-max-continuations") { result.autonomous = true; if (hasRequiredOptionValue(args, i, arg, result)) { result.autonomousMaxContinuations = parsePositiveInt(args[++i], "--autonomous-max-continuations", result); } } else if (arg === "--autonomous-max-turns") { result.autonomous = true; if (hasRequiredOptionValue(args, i, arg, result)) { result.autonomousMaxTurns = parsePositiveInt(args[++i], "--autonomous-max-turns", result); } } else if (arg === "--autonomous-max-tokens") { result.autonomous = true; if (hasRequiredOptionValue(args, i, arg, result)) { result.autonomousMaxTokens = parsePositiveInt(args[++i], "--autonomous-max-tokens", result); } } else if (arg === "--autonomous-timeout-ms") { result.autonomous = true; if (hasRequiredOptionValue(args, i, arg, result)) { result.autonomousTimeoutMs = parsePositiveInt(args[++i], "--autonomous-timeout-ms", result); } } else if (arg === "--goal") { if (hasRequiredOptionValue(args, i, arg, result)) { const value = args[++i]; if (!value.trim()) { result.diagnostics.push({ type: "error", message: "--goal requires a non-empty objective" }); } else { result.goal = value; } } } else if (arg === "--goal-token-budget") { if (hasRequiredOptionValue(args, i, arg, result)) { result.goalTokenBudget = parsePositiveInt(args[++i], "--goal-token-budget", result); } } else if (arg === "--remote-python") { if (hasRequiredOptionValue(args, i, arg, result)) { result.remotePython = args[++i]; process.env.PRIME_AGENT_REMOTE_PYTHON = result.remotePython; } } else if (arg === "--remote-python-host" || arg === "--remote-host") { if (hasRequiredOptionValue(args, i, arg, result)) { result.remotePythonHost = args[++i]; process.env.PRIME_AGENT_REMOTE_PYTHON_HOST = result.remotePythonHost; } } else if (arg === "--list-models") { const hasSearch = i + 1 < args.length && !args[i + 1].startsWith("-") && !args[i + 1].startsWith("@"); if (!internalRuntimeCommand) { result.diagnostics.push({ type: "error", message: `--list-models was removed. Use "${APP_NAME} model list [search]".`, }); if (hasSearch) i++; } else if (hasSearch) { result.listModels = args[++i]; } else { result.listModels = true; } } else if (arg.startsWith("--list-models=")) { result.diagnostics.push({ type: "error", message: `--list-models was removed. Use "${APP_NAME} model list [search]".`, }); } else if (arg === "--verbose") { result.verbose = true; } else if (arg === "--offline") { result.offline = true; } else if (arg.startsWith("@")) { result.fileArgs.push(arg.slice(1)); // Remove @ prefix } else if (arg.startsWith("--")) { const eqIndex = arg.indexOf("="); if (eqIndex !== -1) { result.unknownFlags.set(arg.slice(2, eqIndex), arg.slice(eqIndex + 1)); } else { const flagName = arg.slice(2); const next = args[i + 1]; if (next !== undefined && !next.startsWith("-") && !next.startsWith("@")) { result.unknownFlags.set(flagName, next); i++; } else { result.unknownFlags.set(flagName, true); } } } else if (arg.startsWith("-") && !arg.startsWith("--")) { result.diagnostics.push({ type: "error", message: `Unknown option: ${arg}` }); } else if (!arg.startsWith("-")) { result.messages.push(arg); } }
if (result.goalTokenBudget !== undefined && !result.goal) { result.diagnostics.push({ type: "error", message: "--goal-token-budget requires --goal", }); }
if (!result.remotePythonHost) { const envHost = process.env.PRIME_AGENT_REMOTE_PYTHON_HOST ?? process.env.PRIME_AGENT_REMOTE_HOST; if (envHost?.trim()) { result.remotePythonHost = envHost.trim(); } } if (!result.remotePython) { const envPython = process.env.PRIME_AGENT_REMOTE_PYTHON; if (envPython?.trim()) { result.remotePython = envPython.trim(); } }
return result;}
function hasRequiredOptionValue(args: string[], index: number, flag: string, result: Args): boolean { const next = args[index + 1]; // Prompt values are arbitrary text, even long-option-looking text such as // YAML frontmatter ("---"); free-form value flags accept dash-prefixed // text; every other value flag treats an option-looking token as a missing // value so it still parses as a flag. The standalone "--" delimiter is // never a value; the main loop consumes it so the remaining tokens stay // positional. const valueMayStartWithDash = FREEFORM_VALUE_FLAGS.has(flag); const valueIsArbitraryPromptText = PROMPT_VALUE_FLAGS.has(flag); if ( next === undefined || next === "--" || (!valueIsArbitraryPromptText && next.startsWith(valueMayStartWithDash ? "--" : "-")) ) { result.diagnostics.push({ type: "error", message: `${flag} requires a value` }); return false; } return true;}
function parsePositiveInt(value: string, flag: string, result: Args): number | undefined { const parsed = Number(value); if (!Number.isInteger(parsed) || parsed <= 0) { result.diagnostics.push({ type: "error", message: `${flag} must be a positive integer` }); return undefined; } return parsed;}