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.
16 kB · 470 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471import chalk from "chalk";import { APP_NAME, SELF_UPDATE_INTERACTIVE_CHILD_ENV } from "../config.js";import { AuthStorage } from "../core/auth-storage.js";import { runMcpManagementCommand } from "../core/mcp/mcp-command.js";import { SettingsManager } from "../core/settings-manager.js";import { handlePackageCommand, isSelfUpdateSource } from "../package-manager-cli.js";import { INTERNAL_RUNTIME_COMMAND_MARKER, parseArgs } from "./args.js";import { findCommandSuggestion, formatCommandHelp, formatTopLevelHelp, getChildCommandSpecs, getCommandSpec, isHelpCommandRequest, PUBLIC_COMMAND_NAMES, REMOVED_COMMAND_NAMES,} from "./command-registry.js";import { handleDaemonCommand } from "./daemon-command.js";import { runPs, runReap, runShutdownAll } from "./daemon-ps.js";import { DAEMON_UPDATE_RESTART_COORDINATOR_FLAG, runDaemonRestart } from "./daemon-update-restart.js";import { extractHelpCommandPath, rotateGlobalFlagsBeforeCommand } from "./global-flags.js";import { runUsageCommand } from "./usage-command.js";
export interface PublicCommandResult { handled: boolean; args: string[]; explicitAgentsView: boolean; attachAgent?: string;}
const HANDLED: PublicCommandResult = { handled: true, args: [], explicitAgentsView: false };
export async function handlePublicCommand(args: string[]): Promise<PublicCommandResult> { try { return await runPublicCommand(args); } catch (error) { return fail(error instanceof Error ? error.message : String(error)); }}
async function runPublicCommand(args: string[]): Promise<PublicCommandResult> { args = rotateGlobalFlagsBeforeCommand(args); // Global run flags are excluded from the help request, not forwarded as help // arguments: `prime-agent --offline help` must print help, not chat the // rotated argv to the model. const helpPath = args[0] === "help" ? extractHelpCommandPath(args, 1) : undefined; if (helpPath !== undefined && isHelpCommandRequest(helpPath)) { return printRequestedHelp(helpPath); }
const command = args[0]; if (!command) { return continueWith(args); }
if (REMOVED_COMMAND_NAMES.has(command)) { if (command === "daemon" && (args[1] === "restart" || args[1] === "--restart")) { return runRestart(args.slice(2)); } return rejectRemovedCommand(args); }
if (!PUBLIC_COMMAND_NAMES.has(command)) { return continueWith(args); } if (command === "update" && process.env[SELF_UPDATE_INTERACTIVE_CHILD_ENV] === "1") { await handlePackageCommand(args); return HANDLED; } if (command === "update" && args.includes(DAEMON_UPDATE_RESTART_COORDINATOR_FLAG)) { await handlePackageCommand(args); return HANDLED; }
const separatorIndex = args.indexOf("--"); const helpIndex = args.findIndex( (arg, index) => index > 0 && (separatorIndex === -1 || index < separatorIndex) && (arg === "--help" || arg === "-h"), ); if (helpIndex !== -1) { return printRequestedHelp(getCommandPath(args.slice(0, helpIndex))); }
switch (command) { case "agents": return { handled: false, args: args.slice(1), explicitAgentsView: true }; case "list": return runInternalAgentCommand("list", args.slice(1)); case "attach": { const rest = args.slice(1); const agent = rest[0]; const options = rest.slice(1); if (!agent || agent.startsWith("-") || hasPositionalArguments(options)) { return fail(`Usage: ${APP_NAME} ${getCommandSpec(["attach"])!.usage}`); } if (hasConflictingAttachOption(options)) { return fail("attach cannot be combined with --resume, --continue, or --fork."); } return { handled: false, args: ["--resume", agent, ...options], explicitAgentsView: false, attachAgent: agent, }; } case "stop": if (!requireOperandCount(args.slice(1), 1, 1, "stop")) return HANDLED; return runInternalAgentCommand("kill", args.slice(1)); case "rename": if (!requireOperandCount(args.slice(1), 2, undefined, "rename")) return HANDLED; return runInternalAgentCommand("rename", args.slice(1)); case "send": return runInternalAgentCommand("send", args.slice(1)); case "schedule": return runNestedAgentCommand("schedule", "cron", args.slice(1)); case "status": return runStatus(args.slice(1)); case "usage": return runUsage(args.slice(1)); case "doctor": return runDoctor(args.slice(1)); case "restart": return runRestart(args.slice(1)); case "shutdown": return runShutdown(args.slice(1)); case "package": return runPackage(args.slice(1)); case "mcp": return runMcp(args.slice(1)); case "update": { const rest = args.slice(1); const hasLegacySelfTarget = rest.some((arg) => arg === "--self" || isSelfUpdateSource(arg)); const hasLegacyPackageTarget = rest.some( (arg) => arg === "--extensions" || arg === "--extension" || (!arg.startsWith("-") && !isSelfUpdateSource(arg)), ); if (hasLegacySelfTarget && hasLegacyPackageTarget) { return fail( "Prime Agent and package updates are now separate.", `Run "${APP_NAME} update [--force]" and "${APP_NAME} package update [source]" separately.`, ); } if (hasLegacySelfTarget) { return fail("An update target is no longer needed.", `Use "${APP_NAME} update [--force]".`); } if (hasLegacyPackageTarget) { return fail("Package updates moved to the package command.", `Use "${APP_NAME} package update [source]".`); } const options = parseBooleanOptions( rest, new Set(["--force", "--rollback", "--nightly", "--stable"]), "update", ); if (!options) return HANDLED; await handlePackageCommand(["update", "--self", ...options]); return HANDLED; } case "model": return rewriteNestedCommand("model", "list", "--list-models", args.slice(1)); case "session": return rewriteNestedCommand("session", "export", "--export", args.slice(1)); case "config": if (!requireArgumentCount(args.slice(1), 0, "config")) return HANDLED; return continueWith(args); default: return continueWith(args); }}
function continueWith(args: string[]): PublicCommandResult { return { handled: false, args, explicitAgentsView: false };}
function printRequestedHelp(path: string[]): PublicCommandResult { if (path.length === 0) { console.log(formatTopLevelHelp()); return HANDLED; } if (REMOVED_COMMAND_NAMES.has(path[0]!)) { return rejectRemovedCommand(path); } const help = formatCommandHelp(path); if (help) { console.log(help); return HANDLED; } const parent = path.slice(0, -1); const candidates = getChildCommandSpecs(parent).map((spec) => spec.path.at(-1)!); const suggestion = findCommandSuggestion(path.at(-1)!, candidates); return fail( `Unknown command: ${path.join(" ")}`, suggestion ? `Did you mean "${APP_NAME} help ${[...parent, suggestion].join(" ")}"?` : undefined, );}
function getCommandPath(args: string[]): string[] { const path: string[] = []; for (const arg of args) { if (!getCommandSpec([...path, arg])) { break; } path.push(arg); } return path;}
function rejectRemovedCommand(args: string[]): PublicCommandResult { const [command, subcommand] = args; let replacement: string | undefined; if (command === "daemon") { replacement = 'Run "prime-agent help" to see the agent commands.'; } else if (command === "app" && subcommand === "update") { replacement = 'Use "prime-agent update".'; } else if (command === "install") { replacement = 'Use "prime-agent package install".'; } else if (command === "remove" || command === "uninstall") { replacement = 'Use "prime-agent package remove".'; } else if (command === "manage") { replacement = 'Use "prime-agent agents".'; } return fail(`Unknown command: ${args.slice(0, 2).join(" ")}`, replacement);}
async function runInternalAgentCommand(command: string, args: string[]): Promise<PublicCommandResult> { await handleDaemonCommand(["daemon", command, ...args]); return HANDLED;}
async function runNestedAgentCommand( parent: string, internalCommand: string, args: string[],): Promise<PublicCommandResult> { const subcommand = args[0]; const children = getChildCommandSpecs([parent]).map((spec) => spec.path.at(-1)!); if (!subcommand || !children.includes(subcommand)) { const suggestion = subcommand ? findCommandSuggestion(subcommand, children) : undefined; return fail( subcommand ? `Unknown ${parent} command: ${subcommand}` : `Missing ${parent} command.`, suggestion ? `Did you mean "${APP_NAME} ${parent} ${suggestion}"?` : `Run "${APP_NAME} help ${parent}" for usage.`, ); } if (parent === "schedule" && !validateScheduleArgs(args)) { return HANDLED; } await handleDaemonCommand(["daemon", internalCommand, ...args]); return HANDLED;}
async function runStatus(args: string[]): Promise<PublicCommandResult> { const options = parseBooleanOptions(args, new Set(["--json"]), "status"); if (!options) return HANDLED; await runPs(options.has("--json")); return HANDLED;}
async function runUsage(args: string[]): Promise<PublicCommandResult> { const options = parseBooleanOptions(args, new Set(["--json"]), "usage"); if (!options) return HANDLED; await runUsageCommand(options.has("--json")); return HANDLED;}
async function runDoctor(args: string[]): Promise<PublicCommandResult> { const options = parseBooleanOptions(args, new Set(["--fix", "--restart", "--json"]), "doctor"); if (!options) return HANDLED; if (options.has("--fix") && options.has("--restart")) { return fail("Cannot combine --fix and --restart", `Run "${APP_NAME} doctor --restart" separately.`); } if (options.has("--restart")) { await runDaemonRestart({ json: options.has("--json") }); } else if (options.has("--fix")) { await runReap(options.has("--json"), false); } else { await runPs(options.has("--json")); } return HANDLED;}
async function runRestart(args: string[]): Promise<PublicCommandResult> { const options = parseBooleanOptions(args, new Set(["--json"]), "restart"); if (!options) return HANDLED; await runDaemonRestart({ json: options.has("--json") }); return HANDLED;}
async function runShutdown(args: string[]): Promise<PublicCommandResult> { const options = parseBooleanOptions(args, new Set(["--force", "--json"]), "shutdown"); if (!options) return HANDLED; await runShutdownAll(options.has("--json"), options.has("--force")); return HANDLED;}
async function runMcp(args: string[]): Promise<PublicCommandResult> { const settingsManager = SettingsManager.create(process.cwd()); const result = await runMcpManagementCommand(args, settingsManager, AuthStorage.create()); console.log(result.message); return HANDLED;}
async function runPackage(args: string[]): Promise<PublicCommandResult> { const subcommand = args[0]; if (subcommand === "uninstall") { return fail("Unknown package command: uninstall", `Use "${APP_NAME} package remove".`); } const children = getChildCommandSpecs(["package"]).map((spec) => spec.path.at(-1)!); if (!subcommand || !children.includes(subcommand)) { const suggestion = subcommand ? findCommandSuggestion(subcommand, children) : undefined; return fail( subcommand ? `Unknown package command: ${subcommand}` : "Missing package command.", suggestion ? `Did you mean "${APP_NAME} package ${suggestion}"?` : `Run "${APP_NAME} help package" for usage.`, ); } const rest = args.slice(1); if (subcommand === "list" && rest.length > 0) { return fail(`Usage: ${APP_NAME} package list`); } if (subcommand === "update") { if ( rest.some((arg) => arg === "--self" || arg === "--extensions" || arg === "--extension" || arg === "--force") ) { return fail( 'Package updates accept only an optional source. Use "prime-agent update --force" to update Prime Agent.', ); } if (rest.length > 1) { return fail(`Usage: ${APP_NAME} package update [source]`); } if (rest[0] && isSelfUpdateSource(rest[0])) { return fail('Use "prime-agent update" to update Prime Agent.'); } await handlePackageCommand(["update", ...(rest.length === 0 ? ["--extensions"] : rest)]); return HANDLED; } await handlePackageCommand([subcommand, ...rest]); return HANDLED;}
function rewriteNestedCommand(parent: string, subcommand: string, flag: string, args: string[]): PublicCommandResult { if (args[0] !== subcommand) { const candidate = args[0]; const suggestion = candidate ? findCommandSuggestion(candidate, [subcommand]) : undefined; return fail( candidate ? `Unknown ${parent} command: ${candidate}` : `Missing ${parent} command.`, suggestion ? `Did you mean "${APP_NAME} ${parent} ${suggestion}"?` : `Run "${APP_NAME} help ${parent}" for usage.`, ); } const splitArgs = splitOperandsAndOptions(args.slice(1)); if (!splitArgs) { return fail(`Usage: ${APP_NAME} ${getCommandSpec([parent, subcommand])?.usage ?? `${parent} ${subcommand}`}`); } const { operands, options } = splitArgs; const validCount = parent === "model" ? operands.length <= 1 : operands.length >= 1 && operands.length <= 2; if (!validCount) { return fail(`Usage: ${APP_NAME} ${getCommandSpec([parent, subcommand])?.usage ?? `${parent} ${subcommand}`}`); } return continueWith([INTERNAL_RUNTIME_COMMAND_MARKER, flag, ...operands, ...options]);}
function parseBooleanOptions(args: string[], allowed: ReadonlySet<string>, command: string): Set<string> | undefined { const options = new Set<string>(); for (const arg of args) { if (!allowed.has(arg)) { fail(`Unknown option for ${command}: ${arg}`, `Run "${APP_NAME} help ${command}" for usage.`); return undefined; } options.add(arg); } return options;}
function requireArgumentCount(args: string[], count: number, command: string): boolean { if (args.length === count) { return true; } fail(`Usage: ${APP_NAME} ${getCommandSpec([command])?.usage ?? command}`); return false;}
function hasPositionalArguments(args: string[]): boolean { const parsed = parseArgs(args); return parsed.messages.length > 0 || parsed.fileArgs.length > 0;}
function hasConflictingAttachOption(args: string[]): boolean { return args.some( (arg) => arg === "--resume" || arg === "-r" || arg.startsWith("--resume=") || arg === "--continue" || arg === "-c" || arg === "--fork", );}
function splitOperandsAndOptions(args: string[]): { operands: string[]; options: string[] } | undefined { const optionsStart = args.findIndex((arg) => arg.startsWith("-")); if (optionsStart === -1) { return { operands: args, options: [] }; } const options = args.slice(optionsStart); if (hasPositionalArguments(options)) { return undefined; } return { operands: args.slice(0, optionsStart), options };}
function requireOperandCount(args: string[], minimum: number, maximum: number | undefined, command: string): boolean { const operands: string[] = []; for (let index = 0; index < args.length; index++) { const arg = args[index]!; if (arg === "--json") { continue; } if (arg === "--socket" || arg === "--daemon-socket") { index++; continue; } if (arg.startsWith("-")) { fail(`Usage: ${APP_NAME} ${getCommandSpec([command])?.usage ?? command}`); return false; } operands.push(arg); } if (operands.length >= minimum && (maximum === undefined || operands.length <= maximum)) { return true; } fail(`Usage: ${APP_NAME} ${getCommandSpec([command])?.usage ?? command}`); return false;}
function validateScheduleArgs(args: string[]): boolean { const subcommand = args[0]; if (subcommand === "list") { let agentCount = 0; for (const arg of args.slice(1)) { if (arg === "--all" || arg === "-a" || arg === "--json") { continue; } if (arg.startsWith("-") || ++agentCount > 1) { fail(`Usage: ${APP_NAME} schedule list [--all] [agent] [--json]`); return false; } } return true; } if (subcommand === "cancel") { const operands = args.slice(1).filter((arg) => arg !== "--json"); if (operands.length === 1 && !operands[0]!.startsWith("-")) { return true; } fail(`Usage: ${APP_NAME} ${getCommandSpec(["schedule", "cancel"])!.usage}`); return false; } return true;}
function fail(message: string, hint?: string): PublicCommandResult { console.error(chalk.red(`Error: ${message}`)); if (hint) { console.error(chalk.dim(hint)); } process.exitCode = 1; return HANDLED;}