UNPKG

@phren/cli

Version:

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

127 lines (126 loc) • 6.32 kB
/** * `phren preset [status|managed|assisted|manual]` — switch the management preset. * * Switching re-wires MCP/hooks for the new preset and, on a downgrade, tears down * the phren-owned symlinks/wrappers/repo-mirrors the previous preset installed; * on an upgrade it re-creates them. MCP registration stays on across all presets. */ import { debugLog, findPhrenPath, readRootManifest } from "../shared.js"; import { errorMessage } from "../utils.js"; import { configureClaude } from "./config.js"; import { configureMcpTargets, configureHooksIfEnabled } from "./init-configure.js"; import { repairPreexistingInstall } from "./setup.js"; import { getMcpEnabledPreference, getHooksEnabledPreference, setHooksEnabledPreference, setManagementPresetPreference, } from "./preferences.js"; import { MANAGEMENT_PRESETS, getManagementPreset, parseManagementPreset, presetSummaryLines, resolveManagementCapabilities, } from "./management-preset.js"; import { removeGeneratedHomeFiles, removePhrenHomeSymlinks, removePhrenWrappers, sweepAgentSkillSymlinks, sweepProjectMirrors } from "./teardown.js"; import { printSelfWiringSnippet } from "./self-wiring.js"; import { DEFAULT_PHREN_PATH, log, confirmPrompt } from "./shared.js"; function printCapabilityTable(caps) { const row = (label, on) => log(` ${on ? "on " : "off"} ${label}`); row("MCP server registration", true); row("Lifecycle hooks", caps.hooksDefault); row("~/.claude/CLAUDE.md symlink", caps.linkGlobalClaudeMd); row("~/.claude/skills symlinks", caps.installSkillLinks); row("~/.local/bin wrappers", caps.installWrappers); row("SessionStart self-heal", caps.selfHeal); row("Repo mirroring", caps.repoMirroring); row("Lifecycle automations (maintenance, auto-commit)", caps.lifecycleAutomations); } export async function runPreset(arg, opts = {}) { const phrenPath = findPhrenPath() || process.env.PHREN_PATH || DEFAULT_PHREN_PATH; const manifest = readRootManifest(phrenPath); const normalized = arg?.trim().toLowerCase(); if (!normalized || normalized === "status") { const current = getManagementPreset(phrenPath); log(`Management preset: ${current} — ${presetSummaryLines(current)}`); printCapabilityTable(resolveManagementCapabilities(phrenPath)); log(`Change: phren preset <${MANAGEMENT_PRESETS.join("|")}>`); return; } const preset = parseManagementPreset(normalized); if (!preset) { throw new Error(`Invalid preset "${arg}". Use one of: ${MANAGEMENT_PRESETS.join(", ")}`); } if (manifest?.installMode === "project-local") { throw new Error("preset switching is unsupported in project-local mode"); } const previous = getManagementPreset(phrenPath); const prevCaps = resolveManagementCapabilities(phrenPath); if (preset === previous) { log(`Already on the ${preset} preset.`); printCapabilityTable(prevCaps); return; } // Persist first so caps resolution (incl. the self-heal path) sees the new preset. setManagementPresetPreference(phrenPath, preset); const caps = resolveManagementCapabilities(phrenPath); const teardownNeeded = (prevCaps.linkGlobalClaudeMd && !caps.linkGlobalClaudeMd) || (prevCaps.installSkillLinks && !caps.installSkillLinks) || (prevCaps.installWrappers && !caps.installWrappers) || (prevCaps.repoMirroring && !caps.repoMirroring) || (prevCaps.selfHeal && !caps.selfHeal); if (teardownNeeded && !opts.yes) { const ok = await confirmPrompt(`Switching ${previous} → ${preset} will remove phren-owned symlinks/wrappers from your machine and repos. Continue?`); if (!ok) { // Revert the preference — the user declined the teardown. setManagementPresetPreference(phrenPath, previous); log("Aborted. Preset unchanged."); return; } } log(`Switching management preset: ${previous} → ${preset}\n`); // Hooks follow the preset default (manual disables them). const mcpEnabled = getMcpEnabledPreference(phrenPath); const hooksEnabled = caps.hooksDefault && (previous === "manual" ? true : getHooksEnabledPreference(phrenPath)); setHooksEnabledPreference(phrenPath, hooksEnabled); try { configureClaude(phrenPath, { mcpEnabled, hooksEnabled }); } catch (err) { debugLog(`preset: configureClaude failed: ${errorMessage(err)}`); } try { configureMcpTargets(phrenPath, { mcpEnabled, hooksEnabled, caps }, "Updated"); } catch (err) { debugLog(`preset: configureMcpTargets failed: ${errorMessage(err)}`); } configureHooksIfEnabled(phrenPath, hooksEnabled, "Updated", caps); // Teardown of artifacts the previous preset installed but the new one doesn't. if (prevCaps.linkGlobalClaudeMd && !caps.linkGlobalClaudeMd) removePhrenHomeSymlinks(); if (prevCaps.installSkillLinks && !caps.installSkillLinks) sweepAgentSkillSymlinks(phrenPath); if (prevCaps.installWrappers && !caps.installWrappers) removePhrenWrappers(); if (prevCaps.repoMirroring && !caps.repoMirroring) sweepProjectMirrors(phrenPath); if (prevCaps.selfHeal && !caps.selfHeal) removeGeneratedHomeFiles(); // Setup of artifacts the new preset adds but the previous one lacked. const setupNeeded = (!prevCaps.linkGlobalClaudeMd && caps.linkGlobalClaudeMd) || (!prevCaps.installSkillLinks && caps.installSkillLinks) || (!prevCaps.repoMirroring && caps.repoMirroring) || (!prevCaps.selfHeal && caps.selfHeal); if (setupNeeded) { try { repairPreexistingInstall(phrenPath, { caps, preset }); } catch (err) { debugLog(`preset: repair failed: ${errorMessage(err)}`); } try { const { runLink } = await import("../link/link.js"); await runLink(phrenPath, {}); } catch (err) { debugLog(`preset: runLink failed: ${errorMessage(err)}`); } } log(`\nManagement preset set to ${preset}.`); printCapabilityTable(caps); if (preset !== "managed") { printSelfWiringSnippet(phrenPath, preset); } log(`\nRestart your agent to apply changes.`); }