@rulemetric/cli 0.5.4 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-ANOWI23I.js → chunk-HWGRTIXN.js} +98 -105
- package/dist/chunk-HWGRTIXN.js.map +7 -0
- package/dist/{chunk-LTT2YMYY.js → chunk-JCYCAQ6U.js} +125 -32
- package/dist/chunk-JCYCAQ6U.js.map +7 -0
- package/dist/{chunk-JNFIYBFL.js → chunk-N2X35ITW.js} +7 -1
- package/dist/{chunk-JNFIYBFL.js.map → chunk-N2X35ITW.js.map} +2 -2
- package/dist/{chunk-FTJRMUID.js → chunk-OVGP4OGK.js} +25 -1
- package/dist/chunk-OVGP4OGK.js.map +7 -0
- package/dist/commands/auth/login.js +15 -1
- package/dist/commands/auth/login.js.map +2 -2
- package/dist/commands/gateway/ensure.js +1 -1
- package/dist/commands/hooks/install.js +84 -51
- package/dist/commands/hooks/install.js.map +2 -2
- package/dist/commands/hooks/run.js +5 -3
- package/dist/commands/hooks/run.js.map +2 -2
- package/dist/commands/hooks/uninstall.js +53 -7
- package/dist/commands/hooks/uninstall.js.map +2 -2
- package/dist/commands/proxy/env.js +1 -1
- package/dist/commands/proxy/status.js +1 -1
- package/dist/commands/service/install.js +8 -2
- package/dist/commands/service/install.js.map +2 -2
- package/dist/commands/setup.js +6 -6
- package/dist/commands/setup.js.map +2 -2
- package/dist/lib/gateway-lifecycle.js +7 -3
- package/dist/lib/hooks-config.js +7 -1
- package/dist/lib/setup-steps.js +7 -3
- package/dist/lib/statusline-shim.js +3 -1
- package/oclif.manifest.json +2 -2
- package/package.json +6 -6
- package/dist/chunk-ANOWI23I.js.map +0 -7
- package/dist/chunk-FTJRMUID.js.map +0 -7
- package/dist/chunk-LTT2YMYY.js.map +0 -7
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../src/lib/hooks-config.ts"],
|
|
4
|
-
"sourcesContent": ["import { execSync } from 'node:child_process';\nimport { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\n\nexport type DetectedTool = 'claude_code' | 'cursor' | 'copilot_agent' | 'antigravity';\n\n/**\n * Detect which AI coding tools are installed in the project by checking\n * for their config directories.\n */\nexport function detectInstalledTools(projectPath: string): DetectedTool[] {\n const tools: DetectedTool[] = ['claude_code'];\n\n if (existsSync(join(projectPath, '.cursor'))) {\n tools.push('cursor');\n }\n\n if (existsSync(join(projectPath, '.github'))) {\n tools.push('copilot_agent');\n }\n\n // Antigravity's workspace config lives at `.agents/`. The user-level\n // hooks file at ~/.gemini/config/hooks.json is installed unconditionally\n // by writeAntigravityUserHooks() so we don't gate on that here.\n if (existsSync(join(projectPath, '.agents'))) {\n tools.push('antigravity');\n }\n\n return tools;\n}\n\n/**\n * Generate Cursor hooks config object.\n *\n * Cursor uses event names like beforeSubmitPrompt, afterFileEdit, etc.\n * Each event maps to an array of hook objects with a `command` field.\n */\nexport function generateCursorHooksConfig(): object {\n // Event coverage matches Cursor 1.7+ hook API (cursor.com/docs/hooks).\n // `sessionStart` is required so the registry file is written with\n // conversation_id before any tool runs; without it the proxy-based linker\n // path would have nothing to map and we'd drop captures. `sessionEnd`\n // emits the session-end signal once per chat conversation (vs `stop`\n // which fires per-turn \u2014 those go through post-tool-use).\n return {\n version: 1,\n hooks: {\n sessionStart: [\n { command: 'rulemetric hooks run session-start' },\n ],\n beforeSubmitPrompt: [\n { command: 'rulemetric hooks run user-prompt' },\n ],\n afterFileEdit: [\n { command: 'rulemetric hooks run post-tool-use' },\n ],\n beforeShellExecution: [\n { command: 'rulemetric hooks run post-tool-use' },\n ],\n afterShellExecution: [\n { command: 'rulemetric hooks run post-tool-use' },\n ],\n beforeMCPExecution: [\n { command: 'rulemetric hooks run post-tool-use' },\n ],\n afterMCPExecution: [\n { command: 'rulemetric hooks run post-tool-use' },\n ],\n // `afterAgentResponse` carries the full assistant response text +\n // token usage. Verified empirically via a probe hook on\n // 2026-05-12 \u2014 Cursor passes `text`, `input_tokens`,\n // `output_tokens`, `cache_read_tokens`, `cache_write_tokens`. This\n // is the cleanest path to per-turn assistant content for Cursor\n // (chat traffic itself bypasses the proxy via HTTP/2 \u2014 see\n // docs/audits/2026-05-11-cursor-capture-architecture.md).\n afterAgentResponse: [\n { command: 'rulemetric hooks run assistant-response' },\n ],\n // `stop` fires when the agent loop ends for a single turn (status:\n // completed|aborted|error). Per-turn, NOT per-session \u2014 route it to\n // post-tool-use, not session-end. The real session boundary is\n // `sessionEnd` below.\n stop: [\n { command: 'rulemetric hooks run post-tool-use' },\n ],\n sessionEnd: [\n { command: 'rulemetric hooks run session-end' },\n ],\n },\n };\n}\n\n/**\n * Generate Copilot Agent hooks config object.\n *\n * Copilot uses event names like sessionStart, userPromptSubmitted, etc.\n * Each event maps to an array of hook objects with `type` and `bash` fields.\n */\nexport function generateCopilotHooksConfig(): object {\n return {\n version: 1,\n hooks: {\n sessionStart: [\n { type: 'command', bash: 'rulemetric hooks run session-start' },\n ],\n userPromptSubmitted: [\n { type: 'command', bash: 'rulemetric hooks run user-prompt' },\n ],\n postToolUse: [\n { type: 'command', bash: 'rulemetric hooks run post-tool-use' },\n ],\n sessionEnd: [\n { type: 'command', bash: 'rulemetric hooks run session-end' },\n ],\n },\n };\n}\n\ninterface ClaudeHookEntry {\n type: 'command';\n command: string;\n}\n\ninterface ClaudeHookBlock {\n matcher?: string;\n hooks: ClaudeHookEntry[];\n}\n\n/** The `settings.hooks` map RuleMetric installs into a project's `.claude/settings.json`. */\nexport type ClaudeCodeHooksConfig = Record<\n 'SessionStart' | 'UserPromptSubmit' | 'PostToolUse' | 'Stop' | 'SessionEnd',\n ClaudeHookBlock[]\n>;\n\n/**\n * Generate the Claude Code hooks map for `.claude/settings.json`.\n *\n * Claude Code keys hook blocks by native event name, each block being\n * `{ matcher?, hooks: [{ type: 'command', command }] }`. Unlike Antigravity\n * these carry NO `RULEMETRIC_HOOK_TOOL` prefix \u2014 `_normalize.sh` defaults to\n * the `claude_code` branch (session_id + transcript_path payload shape).\n *\n * This is the hooks-first, zero-proxy capture path: SessionEnd runs\n * `session-end.sh`, which reimports the whole transcript via\n * `POST /api/sessions/:id/reimport` \u2014 no mitmproxy / CA trust / gateway needed.\n * The rendered system prompt is still proxy-only, so instruction-effectiveness\n * linking is handled separately (Phase 2 synthetic snapshot).\n */\nexport function generateClaudeCodeHooksConfig(): ClaudeCodeHooksConfig {\n // Bare `rulemetric` (NOT getRulemetricCommand): Claude Code runs hooks in the\n // user's shell where the CLI is on PATH, and the literal 'rulemetric' substring\n // is the marker that install.ts \u00A71 and mergeClaudeCodeHooks use to dedup/strip\n // our hooks. A resolved node+run.js path omits 'rulemetric' in a dev checkout,\n // which would silently break that dedup.\n const mk = (name: string): ClaudeHookEntry => ({\n type: 'command',\n command: `rulemetric hooks run ${name}`,\n });\n return {\n SessionStart: [{ hooks: [mk('session-start')] }],\n UserPromptSubmit: [{ hooks: [mk('user-prompt')] }],\n PostToolUse: [{ matcher: '.*', hooks: [mk('post-tool-use')] }],\n Stop: [{ hooks: [mk('assistant-response')] }],\n SessionEnd: [{ hooks: [mk('session-end')] }],\n };\n}\n\n/**\n * Merge RuleMetric's Claude Code hooks into an in-memory `.claude/settings.json`\n * object. Claude Code hooks share the same settings.json that install.ts reads,\n * strips (\u00A71), and writes (\u00A75), so this mutates `settings.hooks` in place rather\n * than doing its own file I/O. Idempotent + non-destructive: per event it skips\n * adding when a rulemetric command is already present and otherwise appends,\n * preserving the user's own hooks.\n */\nexport function mergeClaudeCodeHooks(settings: Record<string, unknown>): void {\n const generated = generateClaudeCodeHooksConfig();\n const hooks = (settings.hooks ?? {}) as Record<string, ClaudeHookBlock[]>;\n for (const [event, blocks] of Object.entries(generated)) {\n const current = (hooks[event] ?? []) as ClaudeHookBlock[];\n const alreadyPresent = current.some((b) =>\n b?.hooks?.some((h) => typeof h?.command === 'string' && h.command.includes('rulemetric')),\n );\n if (!alreadyPresent) {\n hooks[event] = [...current, ...blocks];\n }\n }\n settings.hooks = hooks;\n}\n\n/**\n * Strip RuleMetric's Claude Code hooks from an in-memory settings object while\n * preserving the user's own hook entries. Mirrors install.ts \u00A71 but is reusable\n * for uninstall. Removes emptied event arrays and deletes `settings.hooks` if it\n * ends up empty. Returns true iff anything was removed.\n */\nexport function removeClaudeCodeHooks(settings: Record<string, unknown>): boolean {\n if (!settings.hooks || typeof settings.hooks !== 'object') return false;\n const hooks = settings.hooks as Record<string, ClaudeHookBlock[]>;\n let removed = 0;\n for (const event of Object.keys(hooks)) {\n const entries = hooks[event];\n if (!Array.isArray(entries)) continue;\n const kept = entries.filter((block) => {\n const isRulemetric = block?.hooks?.some(\n (h) => typeof h?.command === 'string' && h.command.includes('rulemetric'),\n );\n if (isRulemetric) removed += 1;\n return !isRulemetric;\n });\n if (kept.length === 0) delete hooks[event];\n else hooks[event] = kept;\n }\n if (Object.keys(hooks).length === 0) delete settings.hooks;\n return removed > 0;\n}\n\n/**\n * Merge rulemetric hooks into a Cursor `hooks.json` at the given directory.\n *\n * Used by both the project (`<repo>/.cursor/`) and user (`~/.cursor/`) writers.\n * Returns the config file path, or null if the cursor dir doesn't exist.\n */\nfunction writeCursorHooksToDir(cursorDir: string): string | null {\n if (!existsSync(cursorDir)) {\n return null;\n }\n\n const configPath = join(cursorDir, 'hooks.json');\n const generated = generateCursorHooksConfig() as {\n version: number;\n hooks: Record<string, Array<{ command: string }>>;\n };\n\n let existing: { version?: number; hooks?: Record<string, unknown[]> } = {};\n if (existsSync(configPath)) {\n try {\n existing = JSON.parse(readFileSync(configPath, 'utf-8'));\n } catch {\n existing = {};\n }\n }\n\n const merged: Record<string, unknown[]> = { ...(existing.hooks ?? {}) };\n\n for (const [event, hooks] of Object.entries(generated.hooks)) {\n const current = merged[event] ?? [];\n const hasRulemetric = current.some(\n (h: unknown) =>\n typeof h === 'object' &&\n h !== null &&\n 'command' in h &&\n typeof (h as { command: string }).command === 'string' &&\n (h as { command: string }).command.includes('rulemetric'),\n );\n if (!hasRulemetric) {\n merged[event] = [...current, ...hooks];\n }\n }\n\n const result = {\n version: existing.version ?? generated.version,\n hooks: merged,\n };\n\n writeFileSync(configPath, JSON.stringify(result, null, 2) + '\\n');\n return configPath;\n}\n\n/**\n * Write Cursor hooks config to `<projectPath>/.cursor/hooks.json`.\n * Merges with existing config \u2014 appends rulemetric hooks without duplicating.\n * Returns the config file path, or null if `.cursor/` doesn't exist.\n */\nexport function writeCursorHooks(projectPath: string): string | null {\n return writeCursorHooksToDir(join(projectPath, '.cursor'));\n}\n\n/**\n * Write Cursor hooks config to user-level `~/.cursor/hooks.json` so capture\n * works for every Cursor conversation without per-project setup.\n *\n * Returns the config file path, or null if `~/.cursor/` doesn't exist\n * (i.e., Cursor has never been launched on this machine).\n */\nexport function writeCursorUserHooks(): string | null {\n return writeCursorHooksToDir(join(homedir(), '.cursor'));\n}\n\n/**\n * Write Copilot Agent hooks config to `.github/hooks/rulemetric.json`.\n * Creates the hooks directory if needed.\n * Returns the config file path, or null if `.github/` doesn't exist.\n */\nexport function writeCopilotHooks(projectPath: string): string | null {\n const githubDir = join(projectPath, '.github');\n if (!existsSync(githubDir)) {\n return null;\n }\n\n const hooksDir = join(githubDir, 'hooks');\n mkdirSync(hooksDir, { recursive: true });\n\n const configPath = join(hooksDir, 'rulemetric.json');\n const config = generateCopilotHooksConfig();\n\n writeFileSync(configPath, JSON.stringify(config, null, 2) + '\\n');\n return configPath;\n}\n\n/**\n * Strip rulemetric hooks from a Cursor `hooks.json` at the given path.\n * Preserves other hooks and removes empty event arrays.\n * Returns true if changes were made.\n */\nfunction removeCursorHooksFromFile(configPath: string): boolean {\n if (!existsSync(configPath)) {\n return false;\n }\n\n let config: { version?: number; hooks?: Record<string, unknown[]> };\n try {\n config = JSON.parse(readFileSync(configPath, 'utf-8'));\n } catch {\n return false;\n }\n\n if (!config.hooks) {\n return false;\n }\n\n let changed = false;\n for (const [event, hooks] of Object.entries(config.hooks)) {\n const filtered = hooks.filter((h: unknown) => {\n if (typeof h !== 'object' || h === null || !('command' in h)) return true;\n const cmd = (h as { command: string }).command;\n if (typeof cmd === 'string' && cmd.includes('rulemetric')) {\n changed = true;\n return false;\n }\n return true;\n });\n\n if (filtered.length === 0) {\n delete config.hooks[event];\n } else {\n config.hooks[event] = filtered;\n }\n }\n\n if (changed) {\n writeFileSync(configPath, JSON.stringify(config, null, 2) + '\\n');\n }\n\n return changed;\n}\n\n/**\n * Remove rulemetric hooks from `<projectPath>/.cursor/hooks.json`.\n */\nexport function removeCursorHooks(projectPath: string): boolean {\n return removeCursorHooksFromFile(join(projectPath, '.cursor', 'hooks.json'));\n}\n\n/**\n * Remove rulemetric hooks from user-level `~/.cursor/hooks.json`.\n */\nexport function removeCursorUserHooks(): boolean {\n return removeCursorHooksFromFile(join(homedir(), '.cursor', 'hooks.json'));\n}\n\n/**\n * Write Cursor proxy config to `.cursor/settings.json`.\n * Sets `http.proxy` and `http.proxyStrictSSL` while preserving existing settings.\n * Returns true if `.cursor/` exists and config was written, false otherwise.\n */\nexport function writeCursorProxyConfig(projectPath: string, gatewayPort: number, certPath: string): boolean {\n const cursorDir = join(projectPath, '.cursor');\n if (!existsSync(cursorDir)) return false;\n\n const settingsPath = join(cursorDir, 'settings.json');\n let settings: Record<string, unknown> = {};\n if (existsSync(settingsPath)) {\n try { settings = JSON.parse(readFileSync(settingsPath, 'utf-8')); } catch { /* empty */ }\n }\n\n settings['http.proxy'] = `http://localhost:${gatewayPort}`;\n settings['http.proxyStrictSSL'] = existsSync(certPath);\n writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\\n');\n return true;\n}\n\n/**\n * Write VS Code proxy config to `.vscode/settings.json` for Copilot capture.\n * Sets `http.proxy`, `http.proxyStrictSSL`, and Copilot-specific override\n * while preserving existing settings.\n * Returns true if config was written, false if `.vscode/` doesn't exist.\n */\nexport function writeVSCodeProxyConfig(projectPath: string, gatewayPort: number, certPath: string): boolean {\n const vscodeDir = join(projectPath, '.vscode');\n mkdirSync(vscodeDir, { recursive: true });\n\n const settingsPath = join(vscodeDir, 'settings.json');\n let settings: Record<string, unknown> = {};\n if (existsSync(settingsPath)) {\n try { settings = JSON.parse(readFileSync(settingsPath, 'utf-8')); } catch { /* empty */ }\n }\n\n settings['http.proxy'] = `http://localhost:${gatewayPort}`;\n settings['http.proxyStrictSSL'] = existsSync(certPath);\n\n // Copilot-specific override for surgical proxy targeting\n const copilotAdvanced = (settings['github.copilot.advanced'] ?? {}) as Record<string, unknown>;\n copilotAdvanced['debug.overrideProxyUrl'] = `http://localhost:${gatewayPort}`;\n settings['github.copilot.advanced'] = copilotAdvanced;\n\n writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\\n');\n return true;\n}\n\n/**\n * Remove VS Code proxy config from `.vscode/settings.json`.\n * Removes `http.proxy`, `http.proxyStrictSSL`, and Copilot proxy override.\n * Returns true if changes were made.\n */\nexport function removeVSCodeProxyConfig(projectPath: string): boolean {\n const settingsPath = join(projectPath, '.vscode', 'settings.json');\n if (!existsSync(settingsPath)) return false;\n\n let settings: Record<string, unknown>;\n try { settings = JSON.parse(readFileSync(settingsPath, 'utf-8')); } catch { return false; }\n\n let changed = false;\n\n if (settings['http.proxy']) {\n delete settings['http.proxy'];\n delete settings['http.proxyStrictSSL'];\n changed = true;\n }\n\n const copilotAdvanced = settings['github.copilot.advanced'] as Record<string, unknown> | undefined;\n if (copilotAdvanced?.['debug.overrideProxyUrl']) {\n delete copilotAdvanced['debug.overrideProxyUrl'];\n if (Object.keys(copilotAdvanced).length === 0) {\n delete settings['github.copilot.advanced'];\n }\n changed = true;\n }\n\n if (changed) {\n writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\\n');\n }\n\n return changed;\n}\n\n/**\n * Remove Copilot Agent hooks by deleting `.github/hooks/rulemetric.json`.\n * Returns true if the file was removed.\n */\nexport function removeCopilotHooks(projectPath: string): boolean {\n const configPath = join(projectPath, '.github', 'hooks', 'rulemetric.json');\n if (!existsSync(configPath)) {\n return false;\n }\n\n unlinkSync(configPath);\n return true;\n}\n\n// ---------------------------------------------------------------------------\n// macOS LaunchAgent for GUI app env vars (HTTPS_PROXY, NODE_EXTRA_CA_CERTS)\n// ---------------------------------------------------------------------------\n\nconst LAUNCH_AGENT_LABEL = 'com.rulemetric.env';\n\nfunction getLaunchAgentPath(): string {\n return join(homedir(), 'Library', 'LaunchAgents', `${LAUNCH_AGENT_LABEL}.plist`);\n}\n\n/**\n * Set HTTPS_PROXY and NODE_EXTRA_CA_CERTS for all GUI apps on macOS.\n *\n * Two layers:\n * 1. `launchctl setenv` \u2014 immediate effect for newly launched apps\n * 2. LaunchAgent plist \u2014 persists across reboots\n *\n * This ensures VS Code / Copilot / Cursor route LLM traffic through the\n * gateway even when launched from Dock or Spotlight (not a terminal).\n *\n * References:\n * - anthropics/claude-code#30318 (same pattern for Claude Desktop)\n * - Snyk IDE docs recommend launchctl setenv for macOS\n * - Apple QA1067 (archived) \u2014 original env var mechanism removed in 10.10\n */\nexport function installMacOSProxyEnv(gatewayPort: number, certPath: string): boolean {\n if (process.platform !== 'darwin') return false;\n\n const proxyUrl = `http://localhost:${gatewayPort}`;\n\n // Layer 1: immediate effect via launchctl setenv\n try {\n execSync(`launchctl setenv HTTPS_PROXY ${proxyUrl}`, { stdio: 'pipe' });\n if (existsSync(certPath)) {\n execSync(`launchctl setenv NODE_EXTRA_CA_CERTS ${certPath}`, { stdio: 'pipe' });\n }\n } catch {\n return false;\n }\n\n // Layer 2: LaunchAgent plist for persistence across reboots\n const plistPath = getLaunchAgentPath();\n const agentsDir = join(homedir(), 'Library', 'LaunchAgents');\n mkdirSync(agentsDir, { recursive: true });\n\n const certLine = existsSync(certPath)\n ? `launchctl setenv NODE_EXTRA_CA_CERTS ${certPath};`\n : '';\n\n const plist = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\"\n \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n <key>Label</key>\n <string>${LAUNCH_AGENT_LABEL}</string>\n <key>ProgramArguments</key>\n <array>\n <string>/bin/sh</string>\n <string>-c</string>\n <string>launchctl setenv HTTPS_PROXY ${proxyUrl};${certLine}</string>\n </array>\n <key>RunAtLoad</key>\n <true/>\n</dict>\n</plist>\n`;\n\n writeFileSync(plistPath, plist);\n return true;\n}\n\n/**\n * Remove the RuleMetric LaunchAgent and unset env vars.\n */\nexport function uninstallMacOSProxyEnv(): boolean {\n if (process.platform !== 'darwin') return false;\n\n let changed = false;\n\n // Unset env vars for current session\n try {\n execSync('launchctl unsetenv HTTPS_PROXY', { stdio: 'pipe' });\n execSync('launchctl unsetenv NODE_EXTRA_CA_CERTS', { stdio: 'pipe' });\n } catch { /* may not be set */ }\n\n // Remove LaunchAgent plist\n const plistPath = getLaunchAgentPath();\n if (existsSync(plistPath)) {\n unlinkSync(plistPath);\n changed = true;\n }\n\n return changed;\n}\n\n// ---------------------------------------------------------------------------\n// VS Code user-level proxy settings\n// ---------------------------------------------------------------------------\n\nfunction getVSCodeUserSettingsPath(): string {\n if (process.platform === 'darwin') {\n return join(homedir(), 'Library', 'Application Support', 'Code', 'User', 'settings.json');\n }\n if (process.platform === 'win32') {\n return join(process.env.APPDATA ?? '', 'Code', 'User', 'settings.json');\n }\n return join(homedir(), '.config', 'Code', 'User', 'settings.json');\n}\n\n/**\n * Set http.proxy in VS Code user settings (not workspace).\n *\n * Workspace-level http.proxy is silently ignored because VS Code restricts\n * it to APPLICATION scope (microsoft/vscode#236932). User-level settings\n * are the only reliable way to configure http.proxy.\n */\nexport function writeVSCodeUserProxyConfig(gatewayPort: number): boolean {\n const settingsPath = getVSCodeUserSettingsPath();\n if (!existsSync(join(settingsPath, '..'))) return false;\n\n let settings: Record<string, unknown> = {};\n if (existsSync(settingsPath)) {\n try { settings = JSON.parse(readFileSync(settingsPath, 'utf-8')); } catch { /* empty */ }\n }\n\n // Only set if not already configured by the user\n if (settings['http.proxy'] && !(settings['http.proxy'] as string).includes('localhost')) {\n return false; // user has a custom proxy, don't overwrite\n }\n\n settings['http.proxy'] = `http://localhost:${gatewayPort}`;\n settings['http.proxyStrictSSL'] = false;\n\n writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\\n');\n return true;\n}\n\n/**\n * Remove RuleMetric proxy config from VS Code user settings.\n */\nexport function removeVSCodeUserProxyConfig(): boolean {\n const settingsPath = getVSCodeUserSettingsPath();\n if (!existsSync(settingsPath)) return false;\n\n let settings: Record<string, unknown>;\n try { settings = JSON.parse(readFileSync(settingsPath, 'utf-8')); } catch { return false; }\n\n const proxy = settings['http.proxy'] as string | undefined;\n if (!proxy?.includes('localhost')) return false;\n\n delete settings['http.proxy'];\n delete settings['http.proxyStrictSSL'];\n writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\\n');\n return true;\n}\n\n// ---------------------------------------------------------------------------\n// Cursor user-level proxy settings\n// ---------------------------------------------------------------------------\n//\n// Cursor is a VS Code fork and uses the same settings.json shape, but reads\n// from `Cursor/User/settings.json` instead of `Code/User/settings.json`. The\n// `.cursor/settings.json` (workspace) path can't reach Cursor's Electron main\n// process for the same reason VS Code's workspace path can't reach VS Code's\n// \u2014 the http.proxy setting is restricted to APPLICATION scope and only the\n// user-level file is honored.\n\nfunction getCursorUserSettingsPath(): string {\n if (process.platform === 'darwin') {\n return join(homedir(), 'Library', 'Application Support', 'Cursor', 'User', 'settings.json');\n }\n if (process.platform === 'win32') {\n return join(process.env.APPDATA ?? '', 'Cursor', 'User', 'settings.json');\n }\n return join(homedir(), '.config', 'Cursor', 'User', 'settings.json');\n}\n\n/**\n * Set http.proxy in Cursor user settings (not workspace). Mirrors\n * `writeVSCodeUserProxyConfig` \u2014 workspace-level http.proxy is silently\n * ignored due to APPLICATION-scope restriction (Cursor inherits this from\n * VS Code, see microsoft/vscode#236932).\n *\n * Returns true if config was written, false if Cursor isn't installed\n * (User dir absent) or the user already has a non-localhost proxy set.\n */\nexport function writeCursorUserProxyConfig(gatewayPort: number): boolean {\n const settingsPath = getCursorUserSettingsPath();\n if (!existsSync(join(settingsPath, '..'))) return false;\n\n let settings: Record<string, unknown> = {};\n if (existsSync(settingsPath)) {\n try { settings = JSON.parse(readFileSync(settingsPath, 'utf-8')); } catch { /* empty */ }\n }\n\n if (settings['http.proxy'] && !(settings['http.proxy'] as string).includes('localhost')) {\n return false;\n }\n\n settings['http.proxy'] = `http://localhost:${gatewayPort}`;\n settings['http.proxyStrictSSL'] = false;\n\n writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\\n');\n return true;\n}\n\n/**\n * Remove RuleMetric proxy config from Cursor user settings.\n */\nexport function removeCursorUserProxyConfig(): boolean {\n const settingsPath = getCursorUserSettingsPath();\n if (!existsSync(settingsPath)) return false;\n\n let settings: Record<string, unknown>;\n try { settings = JSON.parse(readFileSync(settingsPath, 'utf-8')); } catch { return false; }\n\n const proxy = settings['http.proxy'] as string | undefined;\n if (!proxy?.includes('localhost')) return false;\n\n delete settings['http.proxy'];\n delete settings['http.proxyStrictSSL'];\n writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\\n');\n return true;\n}\n\n// \u2500\u2500\u2500 Antigravity (Google's Gemini-based agentic IDE) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n//\n// Antigravity hooks live in `.agents/hooks.json` (workspace) and\n// `~/.gemini/config/hooks.json` (user-level). Events follow the\n// SessionStart / UserPromptSubmit / PreToolUse / PostToolUse / Stop /\n// SessionEnd vocabulary. Each hook command sets `RULEMETRIC_HOOK_TOOL=\n// antigravity` so `_normalize.sh` routes through the Antigravity branch.\n\ninterface AntigravityHookEntry {\n type: 'command';\n command: string;\n timeout?: number;\n}\n\ninterface AntigravityHookBlock {\n matcher?: string;\n hooks: AntigravityHookEntry[];\n}\n\ninterface AntigravityRuleMetricBlock {\n enabled: boolean;\n SessionStart: AntigravityHookBlock[];\n UserPromptSubmit: AntigravityHookBlock[];\n PreToolUse: AntigravityHookBlock[];\n PostToolUse: AntigravityHookBlock[];\n Stop: AntigravityHookBlock[];\n SessionEnd: AntigravityHookBlock[];\n}\n\nexport type AntigravityHooksConfig = { rulemetric: AntigravityRuleMetricBlock };\n\n/** Resolve the binary string to invoke for a hook command. */\nfunction getRulemetricCommand(): string {\n // Prefer the local Node binary + bin/run.js so the hook works even when\n // `rulemetric` isn't on PATH for the GUI Antigravity process.\n const nodeBin = process.execPath;\n // bin/run.js lives next to dist/ in the installed CLI; resolve from\n // import.meta isn't available in a portable way here, so we use the\n // process argv1 if it's pointing at the CLI entry, else fall back to\n // the bare command name. In practice this function is called inside\n // `rulemetric hooks install`, where process.argv[1] is bin/run.js.\n const cliEntry = process.argv[1];\n if (cliEntry && cliEntry.endsWith('run.js')) {\n return `${nodeBin} ${cliEntry}`;\n }\n return 'rulemetric';\n}\n\nexport function generateAntigravityHooksConfig(): AntigravityHooksConfig {\n const cmd = getRulemetricCommand();\n const mk = (name: string): AntigravityHookEntry => ({\n type: 'command',\n command: `RULEMETRIC_HOOK_TOOL=antigravity ${cmd} hooks run ${name}`,\n timeout: 30,\n });\n return {\n rulemetric: {\n enabled: true,\n SessionStart: [{ hooks: [mk('session-start')] }],\n UserPromptSubmit: [{ hooks: [mk('user-prompt')] }],\n PreToolUse: [{ matcher: '.*', hooks: [mk('post-tool-use')] }],\n PostToolUse: [{ matcher: '.*', hooks: [mk('post-tool-use')] }],\n Stop: [{ hooks: [mk('assistant-response')] }],\n SessionEnd: [{ hooks: [mk('session-end')] }],\n },\n };\n}\n\nfunction writeAntigravityHooksToDir(dir: string): string | null {\n const configPath = join(dir, 'hooks.json');\n let existing: Record<string, unknown> = {};\n if (existsSync(configPath)) {\n try { existing = JSON.parse(readFileSync(configPath, 'utf-8')); } catch { existing = {}; }\n }\n const generated = generateAntigravityHooksConfig();\n // Antigravity keys hook blocks by name; RuleMetric owns its own block\n // and never touches sibling blocks the user may have added.\n const merged = { ...existing, rulemetric: generated.rulemetric };\n mkdirSync(dir, { recursive: true });\n writeFileSync(configPath, JSON.stringify(merged, null, 2) + '\\n');\n return configPath;\n}\n\nexport function writeAntigravityHooks(projectPath: string): string | null {\n return writeAntigravityHooksToDir(join(projectPath, '.agents'));\n}\n\nexport function writeAntigravityUserHooks(): string | null {\n const configDir = join(homedir(), '.gemini', 'config');\n return writeAntigravityHooksToDir(configDir);\n}\n\nfunction removeAntigravityHooksFromFile(configPath: string): boolean {\n if (!existsSync(configPath)) return false;\n let existing: Record<string, unknown>;\n try { existing = JSON.parse(readFileSync(configPath, 'utf-8')); } catch { return false; }\n if (!('rulemetric' in existing)) return false;\n delete existing.rulemetric;\n if (Object.keys(existing).length === 0) {\n // No other blocks \u2014 remove the file entirely.\n try { unlinkSync(configPath); } catch { /* ignore */ }\n } else {\n writeFileSync(configPath, JSON.stringify(existing, null, 2) + '\\n');\n }\n return true;\n}\n\nexport function removeAntigravityHooks(projectPath: string): boolean {\n return removeAntigravityHooksFromFile(join(projectPath, '.agents', 'hooks.json'));\n}\n\nexport function removeAntigravityUserHooks(): boolean {\n const modern = removeAntigravityHooksFromFile(join(homedir(), '.gemini', 'config', 'hooks.json'));\n const legacy = removeAntigravityHooksFromFile(join(homedir(), '.gemini', 'hooks.json'));\n return modern || legacy;\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
6
|
-
"names": []
|
|
7
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../src/lib/gateway-lifecycle.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Gateway process lifecycle helpers \u2014 spawn, stop, check status.\n * Used by hooks install/uninstall, session-start hook, and proxy status.\n */\nimport { spawn } from 'node:child_process';\nimport { existsSync, mkdirSync, openSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join, resolve } from 'node:path';\n\nconst CONFIG_DIR = join(homedir(), '.config', 'rulemetric');\nexport const GATEWAY_PID_FILE = join(CONFIG_DIR, 'gateway.pid');\nexport const GATEWAY_LOG_FILE = join(CONFIG_DIR, 'gateway.log');\nexport const GATEWAY_PORT = 8787;\nexport const MITMPROXY_PORT = 8788;\n\nexport function isGatewayRunning(): boolean {\n if (!existsSync(GATEWAY_PID_FILE)) return false;\n\n const content = readFileSync(GATEWAY_PID_FILE, 'utf-8').trim();\n const pid = parseInt(content, 10);\n if (isNaN(pid)) return false;\n\n try {\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\n }\n}\n\nexport function getGatewayPid(): number | null {\n if (!existsSync(GATEWAY_PID_FILE)) return null;\n const content = readFileSync(GATEWAY_PID_FILE, 'utf-8').trim();\n const pid = parseInt(content, 10);\n return isNaN(pid) ? null : pid;\n}\n\nfunction findGatewayEntry(): string {\n // Look for the compiled gateway-entry.js relative to this file's location\n const candidates = [\n resolve(import.meta.dirname, './gateway-entry.js'),\n resolve(import.meta.dirname, '../lib/gateway-entry.js'),\n ];\n for (const p of candidates) {\n if (existsSync(p)) return p;\n }\n throw new Error(\n 'Could not find gateway-entry.js. Is the CLI built?\\n' +\n 'Searched:\\n' + candidates.map(c => ` ${c}`).join('\\n'),\n );\n}\n\nexport function spawnGateway(cliVersion?: string): number {\n if (isGatewayRunning()) {\n const pid = getGatewayPid();\n if (pid) return pid;\n }\n\n mkdirSync(CONFIG_DIR, { recursive: true });\n\n const entryPath = findGatewayEntry();\n const logFd = openSync(GATEWAY_LOG_FILE, 'a');\n\n const child = spawn(process.execPath, [entryPath], {\n detached: true,\n stdio: ['ignore', logFd, logFd],\n // The gateway inherits the spawning CLI's env. RULEMETRIC_CLI_VERSION\n // identifies the CLI version that started this gateway \u2014 used to\n // detect drift when a newer CLI starts mitmproxy with a different\n // version. RULEMETRIC_CLI_ENTRY is the path to the oclif entry point\n // (process.argv[1]) so the gateway can re-invoke the CLI for\n // `proxy restart` without needing the binary in PATH. Falls back to\n // \"unknown\" if the caller didn't pass them.\n env: {\n ...process.env,\n RULEMETRIC_CLI_VERSION: cliVersion ?? 'unknown',\n RULEMETRIC_CLI_ENTRY: process.argv[1] ?? '',\n },\n });\n\n child.unref();\n\n if (!child.pid) {\n throw new Error('Failed to spawn gateway process');\n }\n\n writeFileSync(GATEWAY_PID_FILE, String(child.pid));\n return child.pid;\n}\n\nexport function stopGateway(): boolean {\n const pid = getGatewayPid();\n if (pid === null) return false;\n\n try {\n process.kill(pid, 'SIGTERM');\n } catch {\n // Process already gone\n }\n\n try { unlinkSync(GATEWAY_PID_FILE); } catch { /* already gone */ }\n return true;\n}\n"],
|
|
5
|
-
"mappings": ";AAIA,SAAS,aAAa;AACtB,SAAS,YAAY,WAAW,UAAU,cAAc,YAAY,qBAAqB;AACzF,SAAS,eAAe;AACxB,SAAS,MAAM,eAAe;AAE9B,IAAM,aAAa,KAAK,QAAQ,GAAG,WAAW,YAAY;AACnD,IAAM,mBAAmB,KAAK,YAAY,aAAa;AACvD,IAAM,mBAAmB,KAAK,YAAY,aAAa;AACvD,IAAM,eAAe;AACrB,IAAM,iBAAiB;AAEvB,SAAS,mBAA4B;AAC1C,MAAI,CAAC,WAAW,gBAAgB,EAAG,QAAO;AAE1C,QAAM,UAAU,aAAa,kBAAkB,OAAO,EAAE,KAAK;AAC7D,QAAM,MAAM,SAAS,SAAS,EAAE;AAChC,MAAI,MAAM,GAAG,EAAG,QAAO;AAEvB,MAAI;AACF,YAAQ,KAAK,KAAK,CAAC;AACnB,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEO,SAAS,gBAA+B;AAC7C,MAAI,CAAC,WAAW,gBAAgB,EAAG,QAAO;AAC1C,QAAM,UAAU,aAAa,kBAAkB,OAAO,EAAE,KAAK;AAC7D,QAAM,MAAM,SAAS,SAAS,EAAE;AAChC,SAAO,MAAM,GAAG,IAAI,OAAO;AAC7B;AAEA,SAAS,mBAA2B;AAElC,QAAM,aAAa;AAAA,IACjB,QAAQ,YAAY,SAAS,oBAAoB;AAAA,IACjD,QAAQ,YAAY,SAAS,yBAAyB;AAAA,EACxD;AACA,aAAW,KAAK,YAAY;AAC1B,QAAI,WAAW,CAAC,EAAG,QAAO;AAAA,EAC5B;AACA,QAAM,IAAI;AAAA,IACR,oEACgB,WAAW,IAAI,OAAK,KAAK,CAAC,EAAE,EAAE,KAAK,IAAI;AAAA,EACzD;AACF;AAEO,SAAS,aAAa,YAA6B;AACxD,MAAI,iBAAiB,GAAG;AACtB,UAAM,MAAM,cAAc;AAC1B,QAAI,IAAK,QAAO;AAAA,EAClB;AAEA,YAAU,YAAY,EAAE,WAAW,KAAK,CAAC;AAEzC,QAAM,YAAY,iBAAiB;AACnC,QAAM,QAAQ,SAAS,kBAAkB,GAAG;AAE5C,QAAM,QAAQ,MAAM,QAAQ,UAAU,CAAC,SAAS,GAAG;AAAA,IACjD,UAAU;AAAA,IACV,OAAO,CAAC,UAAU,OAAO,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAQ9B,KAAK;AAAA,MACH,GAAG,QAAQ;AAAA,MACX,wBAAwB,cAAc;AAAA,MACtC,sBAAsB,QAAQ,KAAK,CAAC,KAAK;AAAA,IAC3C;AAAA,EACF,CAAC;AAED,QAAM,MAAM;AAEZ,MAAI,CAAC,MAAM,KAAK;AACd,UAAM,IAAI,MAAM,iCAAiC;AAAA,EACnD;AAEA,gBAAc,kBAAkB,OAAO,MAAM,GAAG,CAAC;AACjD,SAAO,MAAM;AACf;AAEO,SAAS,cAAuB;AACrC,QAAM,MAAM,cAAc;AAC1B,MAAI,QAAQ,KAAM,QAAO;AAEzB,MAAI;AACF,YAAQ,KAAK,KAAK,SAAS;AAAA,EAC7B,QAAQ;AAAA,EAER;AAEA,MAAI;AAAE,eAAW,gBAAgB;AAAA,EAAG,QAAQ;AAAA,EAAqB;AACjE,SAAO;AACT;",
|
|
6
|
-
"names": []
|
|
7
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../src/lib/setup-steps.ts"],
|
|
4
|
-
"sourcesContent": ["import { execSync } from 'node:child_process';\nimport { existsSync, readFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { isAuthenticated, getDefaultConfigDir } from './auth.js';\nimport { apiGet } from './api-client.js';\nimport { GATEWAY_PORT } from './gateway-lifecycle.js';\nimport { detectInstalledTools } from './hooks-config.js';\n\n/**\n * Data model for the four core onboarding steps, shared (by step-ID list +\n * install-monotonicity) with the web reducer `deriveSetupProgress()`.\n *\n * The two surfaces are separate implementations by necessity, but they no\n * longer read *different sources* for the same fact:\n * - Server-observable signals (cli-worker heartbeat / captured session) are\n * unified through the shared `GET /api/setup/status` endpoint \u2014 the CLI's\n * `workerActive()` and the web's `use-setup-progress.ts` both bottom out on\n * that one server-side authority, so they cannot drift on it.\n * - Local signals (auth = `~/.config/rulemetric` creds via `isAuthenticated()`,\n * hooks = cwd `.claude/settings.json`) stay machine-local: the browser\n * cannot read the user's filesystem, so these legitimately differ from the\n * web's account-scoped proxies for the same step (see the design doc's\n * \"Shared step model, two implementations\").\n * Each step exposes:\n * - detect(): is it done? (reads a local/remote signal, never mutates)\n * - plan(): what would apply() write? (read-only, returns diffs/artifacts)\n * - apply(): perform the step (thin \u2014 shells out to existing commands)\n */\n\nexport const CORE_STEP_IDS = ['install', 'auth', 'hooks', 'worker'] as const;\nexport type StepId = (typeof CORE_STEP_IDS)[number];\n\nexport type ArtifactKind = 'file' | 'command' | 'service';\n\nexport interface Artifact {\n path: string;\n kind: ArtifactKind;\n before: string | null;\n after: string | null;\n /** True when `after` contains a masked secret (never the raw value). */\n masked?: boolean;\n}\n\nexport interface StepPlan {\n id: StepId;\n title: string;\n command?: string;\n artifacts: Artifact[];\n}\n\nexport interface SetupContext {\n projectPath: string;\n configDir: string;\n}\n\nexport interface SetupStep {\n id: StepId;\n title: string;\n command?: string;\n detect(ctx: SetupContext): Promise<boolean>;\n plan(ctx: SetupContext): Promise<StepPlan>;\n apply(ctx: SetupContext): Promise<void>;\n}\n\nexport interface StepStatus {\n id: StepId;\n done: boolean;\n}\n\nexport interface SetupStatus {\n steps: StepStatus[];\n coreComplete: boolean;\n currentStepId: StepId | null;\n}\n\n/** Build the default context from the current process (cwd + config dir). */\nexport function defaultContext(): SetupContext {\n return {\n projectPath: process.cwd(),\n configDir: getDefaultConfigDir(),\n };\n}\n\nconst CERT_PATH = join(homedir(), '.mitmproxy', 'mitmproxy-ca-cert.pem');\n\n/**\n * Mask a secret: keep the first 4 and last 4 characters, replace the middle\n * with asterisks. Strings too short to reveal safely are fully masked.\n */\nexport function maskToken(s: string): string {\n if (!s) return '';\n if (s.length <= 8) return '*'.repeat(s.length);\n const head = s.slice(0, 4);\n const tail = s.slice(-4);\n return `${head}${'*'.repeat(Math.max(4, s.length - 8))}${tail}`;\n}\n\n/**\n * Simple line-oriented +/- diff for terminal display. Not a real LCS \u2014 good\n * enough to show a human/agent exactly which lines an artifact would change.\n */\nexport function renderDiff(before: string | null, after: string | null): string {\n const beforeLines = before === null ? [] : before.replace(/\\n$/, '').split('\\n');\n const afterLines = after === null ? [] : after.replace(/\\n$/, '').split('\\n');\n const out: string[] = [];\n const max = Math.max(beforeLines.length, afterLines.length);\n for (let i = 0; i < max; i++) {\n const b = beforeLines[i];\n const a = afterLines[i];\n if (b === a) {\n out.push(` ${b}`);\n } else {\n if (b !== undefined) out.push(`-${b}`);\n if (a !== undefined) out.push(`+${a}`);\n }\n }\n return out.join('\\n');\n}\n\n/** Read a single KEY=value from the env file, or null if absent. */\nfunction readEnvValue(configDir: string, key: string): string | null {\n const envPath = join(configDir, 'env');\n if (!existsSync(envPath)) return null;\n try {\n const content = readFileSync(envPath, 'utf-8');\n const match = content.match(new RegExp(`^${key}=(.+)$`, 'm'));\n return match ? match[1] : null;\n } catch {\n return null;\n }\n}\n\n/**\n * Is a CLI worker heartbeat active for the caller? (best-effort, network)\n *\n * Reads the shared `GET /api/setup/status` endpoint's `workerActive` field\n * rather than re-deriving it from `/api/workers/status`. That endpoint computes\n * the cli-worker heartbeat signal server-side using the SAME window +\n * staleness thresholds + `workerType === 'cli-worker'` filter the web strip\n * uses, so the CLI and web can't drift on this fact by construction (they used\n * to each filter/threshold client-side, which is exactly how they could\n * disagree). Fast-fails after 2.5s so `setup --json` / `setup plan` degrade to\n * \"not running\" instead of blocking on a stalled connection \u2014 detection is a\n * UX signal, not a correctness gate; offline \u2192 false.\n */\nasync function workerActive(): Promise<boolean> {\n if (!isAuthenticated()) return false;\n try {\n const status = await Promise.race([\n apiGet<{\n cliEverConnected?: boolean;\n anyCapturedSession?: boolean;\n workerActive?: boolean;\n }>('/api/setup/status'),\n new Promise<never>((_, reject) =>\n setTimeout(() => reject(new Error('setup/status timeout')), 2500),\n ),\n ]);\n return status?.workerActive === true;\n } catch {\n return false;\n }\n}\n\n/**\n * Compute the in-memory `.claude/settings.json` after the hooks env block is\n * merged, WITHOUT touching disk. Replicates the read-only shape of\n * `hooks install` \u00A72 (env.HTTPS_PROXY / NO_PROXY / NODE_EXTRA_CA_CERTS).\n */\nfunction projectHooksSettings(projectPath: string): { before: string | null; after: string } {\n const settingsPath = join(projectPath, '.claude', 'settings.json');\n let settings: Record<string, unknown> = {};\n let before: string | null = null;\n if (existsSync(settingsPath)) {\n try {\n before = readFileSync(settingsPath, 'utf-8').replace(/\\n$/, '');\n settings = JSON.parse(before) as Record<string, unknown>;\n } catch {\n settings = {};\n }\n }\n const env = { ...((settings.env as Record<string, string>) ?? {}) };\n env.HTTPS_PROXY = `http://localhost:${GATEWAY_PORT}`;\n env.NO_PROXY = 'localhost,127.0.0.1,*.supabase.co,*.supabase.in';\n if (existsSync(CERT_PATH)) {\n env.NODE_EXTRA_CA_CERTS = CERT_PATH;\n }\n const after = JSON.stringify({ ...settings, env }, null, 2);\n return { before, after };\n}\n\nconst WORKER_PLIST_PATH = join(homedir(), 'Library', 'LaunchAgents', 'com.rulemetric.worker.plist');\nconst WORKER_SYSTEMD_PATH = join(homedir(), '.config', 'systemd', 'user', 'rulemetric-worker.service');\n\n/** Run a command with inherited stdio (interactive-friendly). Thin apply(). */\nfunction runCommand(cmd: string): void {\n execSync(cmd, { stdio: 'inherit' });\n}\n\nexport const STEPS: SetupStep[] = [\n {\n id: 'install',\n title: 'Install the CLI',\n command: 'npm install -g @rulemetric/cli',\n // Monotonic: the CLI must exist to produce any downstream signal, so it\n // greens off auth or worker presence (nothing local phones home before).\n async detect(): Promise<boolean> {\n return isAuthenticated() || (await workerActive());\n },\n async plan(): Promise<StepPlan> {\n return {\n id: 'install',\n title: 'Install the CLI',\n command: 'npm install -g @rulemetric/cli',\n artifacts: [\n {\n path: 'npm install -g @rulemetric/cli',\n kind: 'command',\n before: null,\n after: 'rulemetric --version # verifies the binary is on PATH',\n },\n ],\n };\n },\n async apply(): Promise<void> {\n runCommand('npm install -g @rulemetric/cli');\n },\n },\n {\n id: 'auth',\n title: 'Authenticate',\n command: 'rulemetric auth login',\n async detect(): Promise<boolean> {\n return isAuthenticated();\n },\n async plan(ctx): Promise<StepPlan> {\n const envPath = join(ctx.configDir, 'env');\n // Guard the read like the sibling helpers (readEnvValue /\n // projectHooksSettings) do: a TOCTOU race or permission error between\n // existsSync and readFileSync must NOT throw out of plan(). Fall back to\n // a secret-free marker rather than crashing `setup plan`/`--show`.\n let before: string | null = null;\n if (existsSync(envPath)) {\n try {\n before = maskEnvFile(readFileSync(envPath, 'utf-8'));\n } catch {\n before = '(unreadable)';\n }\n }\n const rawToken = readEnvValue(ctx.configDir, 'RULEMETRIC_ACCESS_TOKEN');\n const tokenDisplay = rawToken ? maskToken(rawToken) : maskToken('<token-from-login>');\n // Reconstruct EXACTLY what `auth login`'s saveEnvFile writes so the diff\n // doesn't show phantom changes: line order [TOKEN, KEY?, URL?], the key\n // preserved (masked) when present, and the URL sourced from\n // process.env.RULEMETRIC_API_URL \u2014 the same value login passes \u2014 and OMITTED\n // when unset (login writes no URL line then). All masking preserved; the raw\n // token/key are never emitted.\n const afterLines = [`RULEMETRIC_ACCESS_TOKEN=${tokenDisplay}`];\n const rawApiKey = readEnvValue(ctx.configDir, 'RULEMETRIC_API_KEY');\n if (rawApiKey) {\n afterLines.push(`RULEMETRIC_API_KEY=${maskToken(rawApiKey)}`);\n }\n const loginUrl = process.env.RULEMETRIC_API_URL;\n if (loginUrl) {\n afterLines.push(`RULEMETRIC_API_URL=${loginUrl}`);\n }\n const after = afterLines.join('\\n');\n return {\n id: 'auth',\n title: 'Authenticate',\n command: 'rulemetric auth login',\n artifacts: [\n {\n path: envPath,\n kind: 'file',\n before,\n after,\n masked: true,\n },\n ],\n };\n },\n async apply(): Promise<void> {\n runCommand('rulemetric auth login');\n },\n },\n {\n id: 'hooks',\n title: 'Install hooks',\n command: 'rulemetric hooks install',\n async detect(ctx): Promise<boolean> {\n const settingsPath = join(ctx.projectPath, '.claude', 'settings.json');\n if (!existsSync(settingsPath)) return false;\n try {\n const settings = JSON.parse(readFileSync(settingsPath, 'utf-8')) as Record<string, unknown>;\n const env = (settings.env ?? {}) as Record<string, unknown>;\n return typeof env.HTTPS_PROXY === 'string';\n } catch {\n return false;\n }\n },\n async plan(ctx): Promise<StepPlan> {\n const settingsPath = join(ctx.projectPath, '.claude', 'settings.json');\n const { before, after } = projectHooksSettings(ctx.projectPath);\n const artifacts: Artifact[] = [\n { path: settingsPath, kind: 'file', before, after },\n ];\n // Note additional tool surfaces the real install would also touch.\n const tools = detectInstalledTools(ctx.projectPath).filter((t) => t !== 'claude_code');\n for (const tool of tools) {\n artifacts.push({\n path: `${ctx.projectPath} (${tool} proxy config)`,\n kind: 'file',\n before: null,\n after: `proxy \u2192 http://localhost:${GATEWAY_PORT}`,\n });\n }\n return {\n id: 'hooks',\n title: 'Install hooks',\n command: 'rulemetric hooks install',\n artifacts,\n };\n },\n async apply(): Promise<void> {\n runCommand('rulemetric hooks install');\n },\n },\n {\n id: 'worker',\n title: 'Run the worker',\n command: 'rulemetric service install --worker-only',\n async detect(): Promise<boolean> {\n return workerActive();\n },\n async plan(): Promise<StepPlan> {\n const isMac = process.platform === 'darwin';\n const servicePath = isMac ? WORKER_PLIST_PATH : WORKER_SYSTEMD_PATH;\n // SECURITY: never read the installed plist/systemd unit into `before` \u2014\n // those files embed RULEMETRIC_ACCESS_TOKEN / RULEMETRIC_API_KEY verbatim\n // (service/install.ts writes them from ALLOWED_ENV_VARS), so echoing the\n // file contents would leak the raw secret through `plan`, `--show`, and\n // `--json`. Use a synthetic, secret-free marker for the current state.\n const before = existsSync(servicePath)\n ? `(service already installed at ${servicePath})`\n : null;\n // Concise representation (path + ExecStart node/cli invocation) rather\n // than reconstructing the full plist \u2014 keep read-only + legible.\n const after = isMac\n ? [\n `Label: com.rulemetric.worker`,\n `ProgramArguments: <node> <cli>/bin/run.js evals agent`,\n `RunAtLoad: true, KeepAlive: true`,\n ].join('\\n')\n : [\n `[Service]`,\n `ExecStart=<node> <cli>/bin/run.js evals agent`,\n `Restart=on-failure`,\n ].join('\\n');\n return {\n id: 'worker',\n title: 'Run the worker',\n command: 'rulemetric service install --worker-only',\n artifacts: [\n { path: servicePath, kind: 'service', before, after },\n ],\n };\n },\n async apply(): Promise<void> {\n runCommand('rulemetric service install --worker-only');\n },\n },\n];\n\nconst STEP_BY_ID = new Map<StepId, SetupStep>(STEPS.map((s) => [s.id, s]));\n\nexport function getStep(id: StepId): SetupStep {\n const step = STEP_BY_ID.get(id);\n if (!step) throw new Error(`Unknown setup step: ${id}`);\n return step;\n}\n\nexport function isStepId(value: string): value is StepId {\n return (CORE_STEP_IDS as readonly string[]).includes(value);\n}\n\n/**\n * Keys whose values are safe to show verbatim in the env-file diff. Everything\n * NOT in this set is masked. Deny-by-default is deliberate: `~/.config/rulemetric/env`\n * can legitimately hold DATABASE_URL, SUPABASE_SERVICE_ROLE_KEY, SUPABASE_JWT_SECRET,\n * GITHUB_TOKEN, RESEND_API_KEY, etc. (see ALLOWED_ENV_VARS in service/install.ts),\n * so an allowlist-to-hide would leak any secret we forgot to enumerate. Revealing\n * only known-safe keys means a future secret added to the file is masked automatically.\n */\nconst SAFE_ENV_KEYS = new Set([\n 'RULEMETRIC_API_URL',\n 'RULEMETRIC_USER_ID',\n 'RULEMETRIC_CLIENT_NAME',\n 'RESEND_FROM_EMAIL',\n 'SENTRY_DSN', // a DSN is not a secret\n 'PG_POOL_MAX',\n 'PG_IDLE_TIMEOUT',\n 'PG_STATEMENT_TIMEOUT_MS',\n 'NODE_ENV',\n]);\n\n/**\n * Mask secret-bearing lines in a raw env file for display. Deny-by-default:\n * every `KEY=value` line has its value masked unless KEY is in SAFE_ENV_KEYS.\n * Non-assignment lines (comments, blanks) pass through untouched.\n */\nfunction maskEnvFile(content: string): string {\n return content\n .replace(/\\n$/, '')\n .split('\\n')\n .map((line) => {\n const match = line.match(/^([A-Z0-9_]+)=(.+)$/);\n if (!match) return line; // comment / blank / non-assignment\n const [, key, value] = match;\n return SAFE_ENV_KEYS.has(key) ? line : `${key}=${maskToken(value)}`;\n })\n .join('\\n');\n}\n\n/**\n * Run every step's detect() and derive the overall status. `currentStepId` is\n * the first incomplete step in core order (mirrors the web reducer).\n */\nexport async function computeStatus(ctx: SetupContext = defaultContext()): Promise<SetupStatus> {\n const steps: StepStatus[] = [];\n for (const step of STEPS) {\n // Sequential: detect() may hit the network; order is stable + cheap.\n // eslint-disable-next-line no-await-in-loop\n const done = await step.detect(ctx);\n steps.push({ id: step.id, done });\n }\n // Monotonicity (mirrors the web reducer's `cliDetected`): the CLI binary must\n // exist to produce ANY downstream signal, so `install` greens whenever auth,\n // hooks, or worker is detected \u2014 even if `install.detect()` alone (which only\n // sees auth/worker, not hooks) came back false. Without this, a user with\n // hooks installed but no live worker sees \"Install the CLI: waiting\" while\n // \"Install hooks: done\" \u2014 a contradiction.\n const install = steps.find((s) => s.id === 'install');\n if (install && !install.done) {\n install.done = steps.some((s) => s.id !== 'install' && s.done);\n }\n\n const currentStepId = steps.find((s) => !s.done)?.id ?? null;\n const coreComplete = steps.every((s) => s.done);\n return { steps, coreComplete, currentStepId };\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
6
|
-
"names": []
|
|
7
|
-
}
|