UNPKG

@phren/cli

Version:

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

141 lines (124 loc) • 4.65 kB
#!/usr/bin/env node /** * npm preuninstall hook — clean up hooks and MCP server entries from agent * config files so they don't become orphaned when the package is removed * via `npm uninstall -g @phren/cli` (bypassing `phren uninstall`). * * Only removes config/hooks — does NOT touch ~/.phren data. */ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; import { fileURLToPath } from "node:url"; const home = os.homedir(); const homePath = (...parts) => path.join(home, ...parts); /** Read JSON, apply mutator, write back. */ function patchJson(filePath, mutator) { if (!fs.existsSync(filePath)) return; try { const data = JSON.parse(fs.readFileSync(filePath, "utf8")); mutator(data); fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n"); } catch { // best-effort — don't fail the uninstall } } /** Check if a hook command string belongs to phren. */ function isPhrenCommand(cmd) { return ( cmd.includes("@phren/cli") || cmd.includes("phren/cli") || /\bhook-(prompt|stop|session-start|tool|context)\b/.test(cmd) ); } // ── Claude Code settings.json: remove hooks + MCP server ── const claudeSettings = homePath(".claude", "settings.json"); patchJson(claudeSettings, (data) => { // Remove MCP server if (data.mcpServers?.phren) delete data.mcpServers.phren; // Remove phren hooks if (data.hooks && typeof data.hooks === "object") { for (const event of ["UserPromptSubmit", "Stop", "SessionStart", "PostToolUse"]) { const entries = data.hooks[event]; if (!Array.isArray(entries)) continue; data.hooks[event] = entries.filter( (entry) => !entry.hooks?.some((h) => typeof h.command === "string" && isPhrenCommand(h.command)) ); if (data.hooks[event].length === 0) delete data.hooks[event]; } if (Object.keys(data.hooks).length === 0) delete data.hooks; } }); // ── Claude Code ~/.claude.json: remove MCP server ── const claudeJson = homePath(".claude.json"); patchJson(claudeJson, (data) => { if (data.mcpServers?.phren) delete data.mcpServers.phren; }); // ── VS Code MCP configs ── const vscodeMcpCandidates = [ homePath(".vscode-server", "data", "User", "mcp.json"), homePath("AppData", "Roaming", "Code", "User", "mcp.json"), homePath(".config", "Code", "User", "mcp.json"), ]; for (const mcpFile of vscodeMcpCandidates) { patchJson(mcpFile, (data) => { // servers array format if (Array.isArray(data.servers)) { data.servers = data.servers.filter((s) => s.name !== "phren"); } // mcpServers object format if (data.mcpServers?.phren) delete data.mcpServers.phren; }); } // ── Cursor MCP config ── const cursorMcpCandidates = [ homePath(".cursor", "mcp.json"), ]; for (const mcpFile of cursorMcpCandidates) { patchJson(mcpFile, (data) => { if (data.mcpServers?.phren) delete data.mcpServers.phren; }); } // ── Cursor hooks ── const cursorHooks = homePath(".cursor", "hooks.json"); patchJson(cursorHooks, (data) => { for (const key of ["sessionStart", "beforeSubmitPrompt", "stop"]) { if (data[key]?.command && typeof data[key].command === "string" && isPhrenCommand(data[key].command)) { delete data[key]; } } }); // ── Copilot hooks ── const copilotHooks = homePath(".github", "hooks", "phren.json"); if (fs.existsSync(copilotHooks)) { try { fs.unlinkSync(copilotHooks); } catch { /* best-effort */ } } // ── Codex MCP config (TOML + JSON) ── const codexToml = homePath(".codex", "config.toml"); if (fs.existsSync(codexToml)) { try { const content = fs.readFileSync(codexToml, "utf8"); // Remove [mcp_servers.phren] section const cleaned = content.replace(/\[mcp_servers\.phren\][^\[]*/, "").trim(); if (cleaned !== content.trim()) fs.writeFileSync(codexToml, cleaned + "\n"); } catch { /* best-effort */ } } // ── Session wrapper scripts ── const localBinDir = homePath(".local", "bin"); for (const tool of ["copilot", "cursor", "codex"]) { const wrapperPath = path.join(localBinDir, tool); try { if (fs.existsSync(wrapperPath)) { const content = fs.readFileSync(wrapperPath, "utf8"); if (content.includes("PHREN_PATH") && content.includes("phren")) { fs.unlinkSync(wrapperPath); } } } catch { /* best-effort */ } } // ── Machine context file ── const contextFile = homePath(".phren-context.md"); if (fs.existsSync(contextFile)) { try { fs.unlinkSync(contextFile); } catch { /* best-effort */ } } console.log("phren: cleaned up hooks and MCP config from agent settings.");