UNPKG

@phren/cli

Version:

Knowledge layer for AI agents - CLI, MCP server, and data layer

262 lines (261 loc) • 11 kB
/** * Native handler implementations for top-level CLI commands. * * These were the inline if/else branches inside entrypoint.ts's runTopLevelCommand. * They moved here so the command registry can dispatch to them without * eagerly pulling init/init.js (which is heavy) into every phren invocation. * * Loaded only when a native command actually runs, via dynamic import from cli-registry.ts. */ import * as fs from "fs"; import * as path from "path"; import { defaultPhrenPath, findPhrenPath, isInstallMode, } from "./shared.js"; import { parseProactivityLevel } from "./proactivity.js"; import { errorMessage, getOptionValue, getPositionalArgs } from "./utils.js"; import { addProjectFromPath } from "./core/project.js"; import { PROJECT_OWNERSHIP_MODES, getProjectOwnershipDefault, parseProjectOwnershipMode, } from "./project-config.js"; import { VALID_TASK_MODES } from "./governance/policy.js"; function parseTaskModeFlag(raw) { if (!raw) return undefined; const normalized = raw.trim().toLowerCase(); return VALID_TASK_MODES.includes(normalized) ? normalized : undefined; } function parseInstallModeFlag(raw) { if (!raw) return undefined; const normalized = raw.trim(); return isInstallMode(normalized) ? normalized : undefined; } async function promptProjectOwnership(phrenPath, fallback) { if (!process.stdin.isTTY || !process.stdout.isTTY) return fallback; const readline = await import("readline"); const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); return new Promise((resolve) => { rl.question(`Project ownership [${PROJECT_OWNERSHIP_MODES.join("/")}] (${fallback}): `, (input) => { rl.close(); resolve(parseProjectOwnershipMode(input.trim()) ?? fallback); }); }); } // ── Native handlers ───────────────────────────────────────────────────────── export async function runAddCommand(args) { const positional = getPositionalArgs(args, ["--ownership"]); const targetPath = positional[0] || process.cwd(); const ownershipArg = getOptionValue(args, "--ownership"); const phrenPath = defaultPhrenPath(); const profile = (process.env.PHREN_PROFILE) || undefined; if (!fs.existsSync(phrenPath) || !fs.existsSync(path.join(phrenPath, ".config"))) { console.log("phren is not set up yet. Run: phren init"); return 1; } const ownership = ownershipArg ? parseProjectOwnershipMode(ownershipArg) : await promptProjectOwnership(phrenPath, getProjectOwnershipDefault(phrenPath)); if (ownershipArg && !ownership) { console.error(`Invalid --ownership value "${ownershipArg}". Use one of: ${PROJECT_OWNERSHIP_MODES.join(", ")}`); return 1; } try { const added = addProjectFromPath(phrenPath, path.resolve(targetPath), profile, ownership); if (!added.ok) { console.error(added.error); return 1; } console.log(`Added project "${added.data.project}" (${added.data.ownership})`); if (added.data.files.claude) console.log(` ${added.data.files.claude}`); console.log(` ${added.data.files.findings}`); console.log(` ${added.data.files.task}`); console.log(` ${added.data.files.summary}`); } catch (e) { console.error(`Could not add project: ${e instanceof Error ? e.message : String(e)}`); return 1; } return 0; } export async function runInitCommand(args) { const { parseMcpMode, runInit } = await import("./init/init.js"); const { parseManagementPreset, MANAGEMENT_PRESETS } = await import("./init/management-preset.js"); const machineIdx = args.indexOf("--machine"); const profileIdx = args.indexOf("--profile"); const mcpIdx = args.indexOf("--mcp"); const templateIdx = args.indexOf("--template"); const modeArg = getOptionValue(args, "--mode"); const installMode = parseInstallModeFlag(modeArg); if (modeArg && !installMode) { console.error(`Invalid --mode value "${modeArg}". Use "shared" or "project-local".`); return 1; } const ownershipMode = parseProjectOwnershipMode(getOptionValue(args, "--project-ownership")); const taskMode = parseTaskModeFlag(getOptionValue(args, "--task-mode")); const findingsProactivity = parseProactivityLevel(getOptionValue(args, "--findings-proactivity")); const taskProactivity = parseProactivityLevel(getOptionValue(args, "--task-proactivity")); const mcpMode = mcpIdx !== -1 ? parseMcpMode(args[mcpIdx + 1]) : undefined; if (mcpIdx !== -1 && !mcpMode) { console.error(`Invalid --mcp value "${args[mcpIdx + 1] || ""}". Use "on" or "off".`); return 1; } const ownershipArg = getOptionValue(args, "--project-ownership"); if (ownershipArg && !ownershipMode) { console.error(`Invalid --project-ownership value "${ownershipArg}". Use one of: ${PROJECT_OWNERSHIP_MODES.join(", ")}`); return 1; } const taskModeArg = getOptionValue(args, "--task-mode"); if (taskModeArg && !taskMode) { console.error(`Invalid --task-mode value "${taskModeArg}". Use one of: off, manual, suggest, auto.`); return 1; } const findingsArg = getOptionValue(args, "--findings-proactivity"); if (findingsArg && !findingsProactivity) { console.error(`Invalid --findings-proactivity value "${findingsArg}". Use one of: high, medium, low.`); return 1; } const taskArg = getOptionValue(args, "--task-proactivity"); if (taskArg && !taskProactivity) { console.error(`Invalid --task-proactivity value "${taskArg}". Use one of: high, medium, low.`); return 1; } const presetArg = getOptionValue(args, "--preset") ?? getOptionValue(args, "--management"); const managementPreset = parseManagementPreset(presetArg); if (presetArg && !managementPreset) { console.error(`Invalid --preset value "${presetArg}". Use one of: ${MANAGEMENT_PRESETS.join(", ")}`); return 1; } const hooksMode = parseMcpMode(getOptionValue(args, "--hooks") ?? ""); const hooksArg = getOptionValue(args, "--hooks"); if (hooksArg && !hooksMode) { console.error(`Invalid --hooks value "${hooksArg}". Use "on" or "off".`); return 1; } const clankerArg = getOptionValue(args, "--clanker"); const clankerMode = parseMcpMode(clankerArg ?? ""); if (clankerArg && !clankerMode) { console.error(`Invalid --clanker value "${clankerArg}". Use "on" or "off".`); return 1; } const cloneUrl = getOptionValue(args, "--clone-url"); try { await runInit({ mode: installMode, machine: machineIdx !== -1 ? args[machineIdx + 1] : undefined, profile: profileIdx !== -1 ? args[profileIdx + 1] : undefined, mcp: mcpMode, hooks: hooksMode, clanker: clankerMode, projectOwnershipDefault: ownershipMode, managementPreset, taskMode, findingsProactivity, taskProactivity, template: templateIdx !== -1 ? args[templateIdx + 1] : undefined, applyStarterUpdate: args.includes("--apply-starter-update"), dryRun: args.includes("--dry-run"), yes: args.includes("--yes") || args.includes("-y"), express: args.includes("--express"), advanced: args.includes("--advanced"), force: args.includes("--force"), _walkthroughCloneUrl: cloneUrl, }); } catch (err) { // Ctrl-C inside a prompt: say so plainly instead of Inquirer's internals. if (err instanceof Error && err.name === "ExitPromptError") { console.error("\nSetup cancelled. Run phren init again any time."); return 130; } console.error(errorMessage(err)); return 1; } return 0; } export async function runUninstallCommand(args) { const { runUninstall } = await import("./init/init.js"); const skipConfirm = args.includes("--yes") || args.includes("-y"); await runUninstall({ yes: skipConfirm }); return 0; } export async function runStatusCommand(_args) { const { runStatus } = await import("./status.js"); await runStatus(); return 0; } export async function runVerifyCommand(_args) { const { runPostInitVerify, getVerifyOutcomeNote } = await import("./init/init.js"); const { getWorkflowPolicy } = await import("./shared/governance.js"); const phrenPath = findPhrenPath() || defaultPhrenPath(); const result = runPostInitVerify(phrenPath); console.log(`phren verify: ${result.ok ? "ok" : "issues found"}`); console.log(` tasks: ${getWorkflowPolicy(phrenPath).taskMode} mode`); for (const check of result.checks) { console.log(` ${check.ok ? "pass" : "FAIL"} ${check.name}: ${check.detail}`); if (!check.ok && check.fix) { console.log(` fix: ${check.fix}`); } } if (!result.ok) { const note = getVerifyOutcomeNote(phrenPath, result.checks); if (note) console.log(`\nNote: ${note}`); console.log(`\nRun \`phren init\` to fix setup issues.`); } return result.ok ? 0 : 1; } export async function runMcpModeCommand(args) { const { runMcpMode } = await import("./init/init.js"); try { await runMcpMode(args[0]); return 0; } catch (err) { console.error(errorMessage(err)); return 1; } } export async function runHooksModeCommand(args) { const { runHooksMode } = await import("./init/init.js"); try { await runHooksMode(args[0]); return 0; } catch (err) { console.error(errorMessage(err)); return 1; } } export async function runPresetCommand(args) { const { runPreset } = await import("./init/init.js"); const positional = args.filter((a) => !a.startsWith("-")); const yes = args.includes("--yes") || args.includes("-y"); try { await runPreset(positional[0], { yes }); return 0; } catch (err) { console.error(errorMessage(err)); return 1; } } export async function runSnippetCommand(_args) { const { printSelfWiringSnippet } = await import("./init/init.js"); const phrenPath = findPhrenPath() || defaultPhrenPath(); try { printSelfWiringSnippet(phrenPath); return 0; } catch (err) { console.error(errorMessage(err)); return 1; } } export async function runLinkRemovedNotice(args) { if (args.length) { console.error("The old `phren link` option interface has been removed. Use `phren init` instead."); return 1; } return runInitCommand(args); }