@rulemetric/cli 0.6.0 → 0.6.2
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-3AEVHOTS.js +33 -0
- package/dist/chunk-3AEVHOTS.js.map +7 -0
- package/dist/{chunk-WLFX4PYR.js → chunk-6TPJGYFF.js} +4 -4
- package/dist/{chunk-4LHEN2FS.js → chunk-CVMRMSV4.js} +14 -14
- package/dist/{chunk-H3C6EGR2.js → chunk-MUOUV5LY.js} +4 -4
- package/dist/{chunk-OVGP4OGK.js → chunk-OTPC2LXW.js} +6 -4
- package/dist/chunk-OTPC2LXW.js.map +7 -0
- package/dist/{chunk-JCYCAQ6U.js → chunk-TXHL3AUA.js} +86 -93
- package/dist/chunk-TXHL3AUA.js.map +7 -0
- package/dist/{chunk-7K4ILWS7.js → chunk-UPXIO7CG.js} +4 -4
- package/dist/{chunk-IATQXFJA.js → chunk-VBGUNFKY.js} +4 -4
- package/dist/{chunk-BCIUSUEV.js → chunk-VIYUIQHW.js} +4 -4
- package/dist/{chunk-HWGRTIXN.js → chunk-XN23W5HL.js} +9 -6
- package/dist/chunk-XN23W5HL.js.map +7 -0
- package/dist/commands/auth/login.js +4 -0
- package/dist/commands/auth/login.js.map +2 -2
- package/dist/commands/auth/signup.js +81 -0
- package/dist/commands/auth/signup.js.map +7 -0
- package/dist/commands/evals/agent.js +10 -10
- package/dist/commands/gateway/ensure.js +1 -1
- package/dist/commands/hooks/install.js +10 -4
- package/dist/commands/hooks/install.js.map +2 -2
- package/dist/commands/hooks/uninstall.js +2 -2
- package/dist/commands/proxy/env.js +1 -1
- package/dist/commands/proxy/start.js +10 -8
- package/dist/commands/proxy/start.js.map +2 -2
- package/dist/commands/proxy/status.js +1 -1
- package/dist/commands/proxy/stop.js +4 -2
- package/dist/commands/proxy/stop.js.map +2 -2
- package/dist/commands/setup.js +3 -3
- package/dist/dashboard/Dashboard.js +11 -11
- package/dist/dashboard/launcher/LauncherOverlay.js +3 -3
- package/dist/lib/agent-loop.js +10 -10
- package/dist/lib/ensure-api-key.js +10 -0
- package/dist/lib/ensure-api-key.js.map +7 -0
- package/dist/lib/gateway-lifecycle.js +1 -1
- package/dist/lib/handlers/cron-suggest-instructions.js +2 -2
- package/dist/lib/handlers/process-announcement.js +2 -2
- package/dist/lib/handlers/process-run-cleanup.js +2 -2
- package/dist/lib/handlers/process-session-goal.js +2 -2
- package/dist/lib/hooks-config.js +1 -1
- package/dist/lib/manual-tasks.js +2 -2
- package/dist/lib/setup-steps.js +3 -3
- package/oclif.manifest.json +53 -2
- package/package.json +9 -6
- package/dist/chunk-HWGRTIXN.js.map +0 -7
- package/dist/chunk-JCYCAQ6U.js.map +0 -7
- package/dist/chunk-OVGP4OGK.js.map +0 -7
- /package/dist/{chunk-WLFX4PYR.js.map → chunk-6TPJGYFF.js.map} +0 -0
- /package/dist/{chunk-4LHEN2FS.js.map → chunk-CVMRMSV4.js.map} +0 -0
- /package/dist/{chunk-H3C6EGR2.js.map → chunk-MUOUV5LY.js.map} +0 -0
- /package/dist/{chunk-7K4ILWS7.js.map → chunk-UPXIO7CG.js.map} +0 -0
- /package/dist/{chunk-IATQXFJA.js.map → chunk-VBGUNFKY.js.map} +0 -0
- /package/dist/{chunk-BCIUSUEV.js.map → chunk-VIYUIQHW.js.map} +0 -0
|
@@ -0,0 +1,7 @@
|
|
|
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, copyFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join, dirname } 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 hooks\n // file at ~/.gemini/config/hooks.json is written by writeAntigravityUserHooks()\n // ONLY when ~/.gemini already exists (the tool is present) \u2014 see F2.\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(binDir?: string): 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 //\n // binDir: prepended to PATH like the Claude Code hooks \u2014 Cursor is a GUI app\n // launched with launchd's minimal PATH (no nvm/pnpm-global bin), so a bare\n // `rulemetric` silently fails with command-not-found.\n const prefix = binDir ? `PATH=\"${binDir}:$PATH\" ` : '';\n const cmd = (name: string) => ({ command: `${prefix}rulemetric hooks run ${name}` });\n return {\n version: 1,\n hooks: {\n sessionStart: [cmd('session-start')],\n beforeSubmitPrompt: [cmd('user-prompt')],\n afterFileEdit: [cmd('post-tool-use')],\n beforeShellExecution: [cmd('post-tool-use')],\n afterShellExecution: [cmd('post-tool-use')],\n beforeMCPExecution: [cmd('post-tool-use')],\n afterMCPExecution: [cmd('post-tool-use')],\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: [cmd('assistant-response')],\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: [cmd('post-tool-use')],\n sessionEnd: [cmd('session-end')],\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(binDir?: string): object {\n // binDir prepended to PATH \u2014 Copilot Agent runs in the same GUI/minimal-PATH\n // environment class as Cursor, so a bare `rulemetric` silently fails.\n const prefix = binDir ? `PATH=\"${binDir}:$PATH\" ` : '';\n const cmd = (name: string) => ({ type: 'command', bash: `${prefix}rulemetric hooks run ${name}` });\n return {\n version: 1,\n hooks: {\n sessionStart: [cmd('session-start')],\n userPromptSubmitted: [cmd('user-prompt')],\n postToolUse: [cmd('post-tool-use')],\n sessionEnd: [cmd('session-end')],\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(binDir?: string): ClaudeCodeHooksConfig {\n // The literal `rulemetric` is always present: it's the marker that install.ts\n // \u00A71 and mergeClaudeCodeHooks use to dedup/strip our hooks (a resolved\n // node+run.js path omits 'rulemetric' in a dev checkout, which would silently\n // break that dedup).\n //\n // `binDir`, when provided, is prepended to PATH so the command resolves even\n // in Claude Code's hook shell \u2014 which runs `/bin/sh -c` with a minimal PATH\n // that does NOT include nvm/pnpm global bin dirs. Without it, a fresh-install\n // user hits `rulemetric: command not found` on every hook, so capture (and\n // the session-start gateway auto-start that keeps the proxy alive) silently\n // fails. Callers resolve the real bin dir at install time and pass it here.\n const prefix = binDir ? `PATH=\"${binDir}:$PATH\" ` : '';\n const mk = (name: string): ClaudeHookEntry => ({\n type: 'command',\n command: `${prefix}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 * Read a Claude Code `settings.json` for merging, WITHOUT ever crashing the\n * install on a fresh machine. A missing file yields `{}`. A malformed file\n * (hand-edited JSONC, a stray trailing comma, a half-written file) is NOT\n * allowed to throw \u2014 that used to hard-fail `hooks install` with a raw\n * SyntaxError and leave the user with NO capture. Instead we back the\n * unparseable file up (so nothing is silently lost) and proceed on a fresh\n * object so capture still installs.\n *\n * Returns the parsed settings plus `recoveredBackup`: the path the corrupt\n * original was copied to, or `null` when the file was absent/valid.\n */\nexport function readClaudeSettings(configPath: string): {\n settings: Record<string, unknown>;\n recoveredBackup: string | null;\n} {\n if (!existsSync(configPath)) return { settings: {}, recoveredBackup: null };\n const raw = readFileSync(configPath, 'utf-8');\n try {\n return { settings: JSON.parse(raw) as Record<string, unknown>, recoveredBackup: null };\n } catch {\n const recoveredBackup = `${configPath}.corrupt.bak`;\n writeFileSync(recoveredBackup, raw);\n return { settings: {}, recoveredBackup };\n }\n}\n\n/**\n * Write a Claude Code `settings.json`, backing up any prior file first so the\n * change is reversible: the `.bak` holds the previous contents for the user to\n * restore manually if a merge ever goes wrong. Creates the parent dir as needed.\n * Returns `backupPath` (the `.bak` written) or `null` when there was no\n * pre-existing file to back up.\n */\nexport function writeClaudeSettingsWithBackup(\n configPath: string,\n settings: Record<string, unknown>,\n): { backupPath: string | null } {\n mkdirSync(dirname(configPath), { recursive: true });\n let backupPath: string | null = null;\n if (existsSync(configPath)) {\n backupPath = `${configPath}.bak`;\n copyFileSync(configPath, backupPath);\n }\n writeFileSync(configPath, JSON.stringify(settings, null, 2) + '\\n');\n return { backupPath };\n}\n\n/**\n * Read a JSON config file for a safe in-place merge. Returns `{}` when the file\n * is absent, the parsed object when it's valid, and `null` when it EXISTS but is\n * unparseable \u2014 in which case the original is first backed up to `.corrupt.bak`.\n * Callers MUST treat `null` as \"skip, do not write\": editor settings.json files\n * are JSONC (comments + trailing commas are legal and common), so a strict\n * JSON.parse throws on valid user files; blindly resetting to `{}` and writing\n * would clobber the user's ENTIRE editor config down to our few keys. Backing up\n * + skipping is the non-destructive floor (mirrors readClaudeSettings).\n */\nfunction readMergeableJson(configPath: string): Record<string, unknown> | null {\n if (!existsSync(configPath)) return {};\n const raw = readFileSync(configPath, 'utf-8');\n try {\n return JSON.parse(raw) as Record<string, unknown>;\n } catch {\n try {\n writeFileSync(`${configPath}.corrupt.bak`, raw);\n } catch {\n /* best-effort backup */\n }\n return null;\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>, binDir?: string): void {\n const generated = generateClaudeCodeHooksConfig(binDir);\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 * True iff RuleMetric's Claude Code session hooks are present in a settings\n * object \u2014 i.e. capture is actually wired up. This is the honest readiness\n * signal for the onboarding hooks step: capture works when the hooks exist,\n * NOT merely because a proxy env var was written (which greened even when the\n * proxy pointed at a dead port \u2014 the fresh-laptop ConnectionRefused footgun).\n */\nexport function hasRulemetricClaudeHooks(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 return Object.values(hooks).some(\n (blocks) =>\n Array.isArray(blocks) &&\n blocks.some((b) =>\n b?.hooks?.some((h) => typeof h?.command === 'string' && h.command.includes('rulemetric')),\n ),\n );\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, binDir?: string): string | null {\n if (!existsSync(cursorDir)) {\n return null;\n }\n\n const configPath = join(cursorDir, 'hooks.json');\n const generated = generateCursorHooksConfig(binDir) as {\n version: number;\n hooks: Record<string, Array<{ command: string }>>;\n };\n\n const parsed = readMergeableJson(configPath);\n if (parsed === null) return null; // unparseable \u2014 backed up, don't drop sibling hooks\n const existing = parsed as { version?: number; hooks?: Record<string, unknown[]> };\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, binDir?: string): string | null {\n return writeCursorHooksToDir(join(projectPath, '.cursor'), binDir);\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(binDir?: string, home: string = homedir()): string | null {\n // Only wire user-level Cursor hooks if Cursor is actually present \u2014 never\n // manufacture ~/.cursor for a user who doesn't use it (undisclosed scope,\n // onboarding field report 2026-07-07 F2). `home` is injectable for tests.\n const cursorHome = join(home, '.cursor');\n if (!existsSync(cursorHome)) return null;\n return writeCursorHooksToDir(cursorHome, binDir);\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, binDir?: 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(binDir);\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 const settings = readMergeableJson(settingsPath);\n if (settings === null) return false; // unparseable JSONC \u2014 backed up, don't clobber\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 // Only touch projects that already use VS Code \u2014 do NOT manufacture a\n // `.vscode/` in every repo (matches writeCursorProxyConfig's contract and this\n // function's own docstring). Creating it pollutes Claude-Code-only repos with\n // an untracked, proxy-pinning settings.json.\n if (!existsSync(vscodeDir)) return false;\n\n const settingsPath = join(vscodeDir, 'settings.json');\n const settings = readMergeableJson(settingsPath);\n if (settings === null) return false; // unparseable JSONC \u2014 backed up, don't clobber\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 // LIVENESS-GUARDED boot command: at every login this re-pins HTTPS_PROXY\n // machine-wide, but the gateway on :8787 is not a supervised service on the\n // end-user path \u2014 it dies on logout and nothing restarts it. Pinning a dead\n // port breaks HTTPS for every GUI app. So probe :8787 first and only setenv\n // when the gateway actually answers; otherwise clear it (the session-start\n // hook will re-pin once Claude Code brings the gateway back up).\n const bootCmd =\n `if nc -z -w 1 127.0.0.1 ${gatewayPort} 2>/dev/null; then ` +\n `launchctl setenv HTTPS_PROXY ${proxyUrl};${certLine} ` +\n `else launchctl unsetenv HTTPS_PROXY; launchctl unsetenv NODE_EXTRA_CA_CERTS; fi`;\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>${bootCmd}</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 const settings = readMergeableJson(settingsPath);\n if (settings === null) return false; // unparseable JSONC \u2014 backed up, don't clobber user config\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 const settings = readMergeableJson(settingsPath);\n if (settings === null) return false; // unparseable JSONC \u2014 backed up, don't clobber user config\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 const existing = readMergeableJson(configPath);\n if (existing === null) return null; // unparseable \u2014 backed up, don't drop sibling blocks\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(home: string = homedir()): string | null {\n // Only wire user-level Antigravity/Gemini hooks if the tool is actually\n // present \u2014 never manufacture ~/.gemini for a user who doesn't use it\n // (undisclosed scope, onboarding field report 2026-07-07 F2). `home` is\n // injectable for tests.\n const geminiHome = join(home, '.gemini');\n if (!existsSync(geminiHome)) return null;\n return writeAntigravityHooksToDir(join(geminiHome, 'config'));\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,3 +1,6 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ensureDeviceApiKey
|
|
3
|
+
} from "../../chunk-3AEVHOTS.js";
|
|
1
4
|
import {
|
|
2
5
|
BaseCommand
|
|
3
6
|
} from "../../chunk-SZ7VDCD6.js";
|
|
@@ -64,6 +67,7 @@ var AuthLogin = class _AuthLogin extends BaseCommand {
|
|
|
64
67
|
});
|
|
65
68
|
saveEnvFile(data.accessToken, process.env.RULEMETRIC_API_URL);
|
|
66
69
|
await bootstrapActiveOrg();
|
|
70
|
+
await ensureDeviceApiKey(this.log.bind(this), this.warn.bind(this));
|
|
67
71
|
this.log(`Logged in as ${data.email}`);
|
|
68
72
|
}
|
|
69
73
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/commands/auth/login.ts"],
|
|
4
|
-
"sourcesContent": ["import { Flags } from '@oclif/core';\nimport { BaseCommand } from '../../base-command.js';\nimport { saveToken, saveEnvFile } from '../../lib/auth.js';\nimport { apiPost, ApiError } from '../../lib/api-client.js';\nimport { bootstrapActiveOrg } from '../../lib/active-org-refresh.js';\n\ninterface LoginResponse {\n accessToken: string;\n refreshToken: string;\n expiresAt: string | null;\n email: string;\n}\n\nexport default class AuthLogin extends BaseCommand {\n static override description = 'Log in to RuleMetric';\n\n static override examples = [\n '<%= config.bin %> auth login',\n '<%= config.bin %> auth login -e user@example.com',\n ];\n\n static override flags = {\n email: Flags.string({ char: 'e', description: 'Email address' }),\n password: Flags.string({ char: 'p', description: 'Password (for non-interactive use)' }),\n };\n\n async run(): Promise<void> {\n const { flags } = await this.parse(AuthLogin);\n\n let email = flags.email;\n let password = flags.password;\n\n if (!email || !password) {\n // Interactive prompts hang forever with no output in a non-TTY (CI, SSH\n // pipe, an agent driving the CLI). Fail fast with actionable guidance\n // instead of appearing to freeze.\n if (!process.stdin.isTTY) {\n this.error(\n 'Missing credentials in a non-interactive shell. Pass --email/-e and --password/-p, ' +\n 'or set RULEMETRIC_API_KEY (create a key at https://rulemetric.com/settings).',\n );\n }\n const { input, password: passwordPrompt } = await import('@inquirer/prompts');\n\n if (!email) {\n email = await input({ message: 'Email:' });\n }\n\n if (!password) {\n password = await passwordPrompt({ message: 'Password:' });\n }\n }\n\n let data: LoginResponse;\n try {\n data = await apiPost<LoginResponse>('/auth/login', { email, password });\n } catch (err) {\n // A 401 HERE means the email/password were rejected \u2014 not that a prior\n // session expired. base-command's generic catch maps every 401 to\n // \"Session expired. Run auth login\", which is nonsensical mid-login and\n // loops the user. Give the real reason.\n if (err instanceof ApiError && err.statusCode === 401) {\n this.error('Invalid email or password.');\n }\n throw err;\n }\n\n const expiresAt = data.expiresAt ?? new Date(Date.now() + 3600 * 1000).toISOString();\n\n saveToken({\n accessToken: data.accessToken,\n refreshToken: data.refreshToken,\n expiresAt,\n email: data.email,\n });\n\n saveEnvFile(data.accessToken, process.env.RULEMETRIC_API_URL);\n\n // Bootstrap the active-org cache now that auth is available. BaseCommand\n // fired the guarded refresh BEFORE login completed (no creds \u2192 401 \u2192\n // swallowed); without this explicit retry, the cache stays empty until\n // the user runs another CLI command, and any Claude Code session that\n // fires in the meantime lands with org_id = NULL.\n await bootstrapActiveOrg();\n\n this.log(`Logged in as ${data.email}`);\n }\n}\n"],
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["import { Flags } from '@oclif/core';\nimport { BaseCommand } from '../../base-command.js';\nimport { saveToken, saveEnvFile } from '../../lib/auth.js';\nimport { apiPost, ApiError } from '../../lib/api-client.js';\nimport { bootstrapActiveOrg } from '../../lib/active-org-refresh.js';\nimport { ensureDeviceApiKey } from '../../lib/ensure-api-key.js';\n\ninterface LoginResponse {\n accessToken: string;\n refreshToken: string;\n expiresAt: string | null;\n email: string;\n}\n\nexport default class AuthLogin extends BaseCommand {\n static override description = 'Log in to RuleMetric';\n\n static override examples = [\n '<%= config.bin %> auth login',\n '<%= config.bin %> auth login -e user@example.com',\n ];\n\n static override flags = {\n email: Flags.string({ char: 'e', description: 'Email address' }),\n password: Flags.string({ char: 'p', description: 'Password (for non-interactive use)' }),\n };\n\n async run(): Promise<void> {\n const { flags } = await this.parse(AuthLogin);\n\n let email = flags.email;\n let password = flags.password;\n\n if (!email || !password) {\n // Interactive prompts hang forever with no output in a non-TTY (CI, SSH\n // pipe, an agent driving the CLI). Fail fast with actionable guidance\n // instead of appearing to freeze.\n if (!process.stdin.isTTY) {\n this.error(\n 'Missing credentials in a non-interactive shell. Pass --email/-e and --password/-p, ' +\n 'or set RULEMETRIC_API_KEY (create a key at https://rulemetric.com/settings).',\n );\n }\n const { input, password: passwordPrompt } = await import('@inquirer/prompts');\n\n if (!email) {\n email = await input({ message: 'Email:' });\n }\n\n if (!password) {\n password = await passwordPrompt({ message: 'Password:' });\n }\n }\n\n let data: LoginResponse;\n try {\n data = await apiPost<LoginResponse>('/auth/login', { email, password });\n } catch (err) {\n // A 401 HERE means the email/password were rejected \u2014 not that a prior\n // session expired. base-command's generic catch maps every 401 to\n // \"Session expired. Run auth login\", which is nonsensical mid-login and\n // loops the user. Give the real reason.\n if (err instanceof ApiError && err.statusCode === 401) {\n this.error('Invalid email or password.');\n }\n throw err;\n }\n\n const expiresAt = data.expiresAt ?? new Date(Date.now() + 3600 * 1000).toISOString();\n\n saveToken({\n accessToken: data.accessToken,\n refreshToken: data.refreshToken,\n expiresAt,\n email: data.email,\n });\n\n saveEnvFile(data.accessToken, process.env.RULEMETRIC_API_URL);\n\n // Bootstrap the active-org cache now that auth is available. BaseCommand\n // fired the guarded refresh BEFORE login completed (no creds \u2192 401 \u2192\n // swallowed); without this explicit retry, the cache stays empty until\n // the user runs another CLI command, and any Claude Code session that\n // fires in the meantime lands with org_id = NULL.\n await bootstrapActiveOrg();\n\n // Auto-mint a hooks/proxy API key so create-key isn't a separate manual\n // step (best-effort; no-ops if a key already exists, never fails login).\n await ensureDeviceApiKey(this.log.bind(this), this.warn.bind(this));\n\n this.log(`Logged in as ${data.email}`);\n }\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;;AAAA,SAAS,aAAa;AActB,IAAqB,YAArB,MAAqB,mBAAkB,YAAY;AAAA,EACjD,OAAgB,cAAc;AAAA,EAE9B,OAAgB,WAAW;AAAA,IACzB;AAAA,IACA;AAAA,EACF;AAAA,EAEA,OAAgB,QAAQ;AAAA,IACtB,OAAO,MAAM,OAAO,EAAE,MAAM,KAAK,aAAa,gBAAgB,CAAC;AAAA,IAC/D,UAAU,MAAM,OAAO,EAAE,MAAM,KAAK,aAAa,qCAAqC,CAAC;AAAA,EACzF;AAAA,EAEA,MAAM,MAAqB;AACzB,UAAM,EAAE,MAAM,IAAI,MAAM,KAAK,MAAM,UAAS;AAE5C,QAAI,QAAQ,MAAM;AAClB,QAAI,WAAW,MAAM;AAErB,QAAI,CAAC,SAAS,CAAC,UAAU;AAIvB,UAAI,CAAC,QAAQ,MAAM,OAAO;AACxB,aAAK;AAAA,UACH;AAAA,QAEF;AAAA,MACF;AACA,YAAM,EAAE,OAAO,UAAU,eAAe,IAAI,MAAM,OAAO,mBAAmB;AAE5E,UAAI,CAAC,OAAO;AACV,gBAAQ,MAAM,MAAM,EAAE,SAAS,SAAS,CAAC;AAAA,MAC3C;AAEA,UAAI,CAAC,UAAU;AACb,mBAAW,MAAM,eAAe,EAAE,SAAS,YAAY,CAAC;AAAA,MAC1D;AAAA,IACF;AAEA,QAAI;AACJ,QAAI;AACF,aAAO,MAAM,QAAuB,eAAe,EAAE,OAAO,SAAS,CAAC;AAAA,IACxE,SAAS,KAAK;AAKZ,UAAI,eAAe,YAAY,IAAI,eAAe,KAAK;AACrD,aAAK,MAAM,4BAA4B;AAAA,MACzC;AACA,YAAM;AAAA,IACR;AAEA,UAAM,YAAY,KAAK,aAAa,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,GAAI,EAAE,YAAY;AAEnF,cAAU;AAAA,MACR,aAAa,KAAK;AAAA,MAClB,cAAc,KAAK;AAAA,MACnB;AAAA,MACA,OAAO,KAAK;AAAA,IACd,CAAC;AAED,gBAAY,KAAK,aAAa,QAAQ,IAAI,kBAAkB;AAO5D,UAAM,mBAAmB;AAIzB,UAAM,mBAAmB,KAAK,IAAI,KAAK,IAAI,GAAG,KAAK,KAAK,KAAK,IAAI,CAAC;AAElE,SAAK,IAAI,gBAAgB,KAAK,KAAK,EAAE;AAAA,EACvC;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ensureDeviceApiKey
|
|
3
|
+
} from "../../chunk-3AEVHOTS.js";
|
|
4
|
+
import {
|
|
5
|
+
BaseCommand
|
|
6
|
+
} from "../../chunk-SZ7VDCD6.js";
|
|
7
|
+
import "../../chunk-R56PH7YE.js";
|
|
8
|
+
import {
|
|
9
|
+
bootstrapActiveOrg
|
|
10
|
+
} from "../../chunk-SLL3OBKN.js";
|
|
11
|
+
import "../../chunk-EKJ2CABV.js";
|
|
12
|
+
import {
|
|
13
|
+
ApiError,
|
|
14
|
+
apiPost
|
|
15
|
+
} from "../../chunk-ZRLYHBPN.js";
|
|
16
|
+
import {
|
|
17
|
+
saveEnvFile,
|
|
18
|
+
saveToken
|
|
19
|
+
} from "../../chunk-W2YERO7E.js";
|
|
20
|
+
import "../../chunk-NSBPE2FW.js";
|
|
21
|
+
|
|
22
|
+
// src/commands/auth/signup.ts
|
|
23
|
+
import { Flags } from "@oclif/core";
|
|
24
|
+
var AuthSignup = class _AuthSignup extends BaseCommand {
|
|
25
|
+
static description = "Create a RuleMetric account from the CLI";
|
|
26
|
+
static examples = [
|
|
27
|
+
"<%= config.bin %> auth signup",
|
|
28
|
+
"<%= config.bin %> auth signup -e user@example.com"
|
|
29
|
+
];
|
|
30
|
+
static flags = {
|
|
31
|
+
email: Flags.string({ char: "e", description: "Email address" }),
|
|
32
|
+
password: Flags.string({ char: "p", description: "Password (min 8 chars; for non-interactive use)" })
|
|
33
|
+
};
|
|
34
|
+
async run() {
|
|
35
|
+
const { flags } = await this.parse(_AuthSignup);
|
|
36
|
+
let email = flags.email;
|
|
37
|
+
let password = flags.password;
|
|
38
|
+
if (!email || !password) {
|
|
39
|
+
if (!process.stdin.isTTY) {
|
|
40
|
+
this.error("Missing credentials in a non-interactive shell. Pass --email/-e and --password/-p.");
|
|
41
|
+
}
|
|
42
|
+
const { input, password: passwordPrompt } = await import("@inquirer/prompts");
|
|
43
|
+
if (!email) {
|
|
44
|
+
email = await input({ message: "Email:" });
|
|
45
|
+
}
|
|
46
|
+
if (!password) {
|
|
47
|
+
password = await passwordPrompt({ message: "Password (min 8 chars):" });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
let data;
|
|
51
|
+
try {
|
|
52
|
+
data = await apiPost("/auth/signup", { email, password });
|
|
53
|
+
} catch (err) {
|
|
54
|
+
if (err instanceof ApiError && err.statusCode === 409) {
|
|
55
|
+
this.error(`An account with ${email} already exists. Run: rulemetric auth login -e ${email}`);
|
|
56
|
+
}
|
|
57
|
+
throw err;
|
|
58
|
+
}
|
|
59
|
+
if (data.confirmationRequired || !data.accessToken || !data.refreshToken) {
|
|
60
|
+
this.log(`Account created for ${data.email}.`);
|
|
61
|
+
this.log("Check your email and click the confirmation link, then run:");
|
|
62
|
+
this.log(` rulemetric auth login -e ${data.email}`);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const expiresAt = data.expiresAt ?? new Date(Date.now() + 3600 * 1e3).toISOString();
|
|
66
|
+
saveToken({
|
|
67
|
+
accessToken: data.accessToken,
|
|
68
|
+
refreshToken: data.refreshToken,
|
|
69
|
+
expiresAt,
|
|
70
|
+
email: data.email
|
|
71
|
+
});
|
|
72
|
+
saveEnvFile(data.accessToken, process.env.RULEMETRIC_API_URL);
|
|
73
|
+
await bootstrapActiveOrg();
|
|
74
|
+
await ensureDeviceApiKey(this.log.bind(this), this.warn.bind(this));
|
|
75
|
+
this.log(`Signed up and logged in as ${data.email}`);
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
export {
|
|
79
|
+
AuthSignup as default
|
|
80
|
+
};
|
|
81
|
+
//# sourceMappingURL=signup.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/commands/auth/signup.ts"],
|
|
4
|
+
"sourcesContent": ["import { Flags } from '@oclif/core';\nimport { BaseCommand } from '../../base-command.js';\nimport { saveToken, saveEnvFile } from '../../lib/auth.js';\nimport { apiPost, ApiError } from '../../lib/api-client.js';\nimport { bootstrapActiveOrg } from '../../lib/active-org-refresh.js';\nimport { ensureDeviceApiKey } from '../../lib/ensure-api-key.js';\n\ninterface SignupResponse {\n confirmationRequired: boolean;\n accessToken?: string;\n refreshToken?: string;\n expiresAt?: string | null;\n email: string;\n}\n\nexport default class AuthSignup extends BaseCommand {\n static override description = 'Create a RuleMetric account from the CLI';\n\n static override examples = [\n '<%= config.bin %> auth signup',\n '<%= config.bin %> auth signup -e user@example.com',\n ];\n\n static override flags = {\n email: Flags.string({ char: 'e', description: 'Email address' }),\n password: Flags.string({ char: 'p', description: 'Password (min 8 chars; for non-interactive use)' }),\n };\n\n async run(): Promise<void> {\n const { flags } = await this.parse(AuthSignup);\n\n let email = flags.email;\n let password = flags.password;\n\n if (!email || !password) {\n // Interactive prompts hang forever with no output in a non-TTY (CI, SSH\n // pipe, an agent driving the CLI). Fail fast with actionable guidance.\n if (!process.stdin.isTTY) {\n this.error('Missing credentials in a non-interactive shell. Pass --email/-e and --password/-p.');\n }\n const { input, password: passwordPrompt } = await import('@inquirer/prompts');\n\n if (!email) {\n email = await input({ message: 'Email:' });\n }\n if (!password) {\n password = await passwordPrompt({ message: 'Password (min 8 chars):' });\n }\n }\n\n let data: SignupResponse;\n try {\n data = await apiPost<SignupResponse>('/auth/signup', { email, password });\n } catch (err) {\n // 409 = the email is already registered \u2014 steer to login, not a retry loop.\n if (err instanceof ApiError && err.statusCode === 409) {\n this.error(`An account with ${email} already exists. Run: rulemetric auth login -e ${email}`);\n }\n throw err;\n }\n\n // Email confirmation is ON: no session yet. Tell the user exactly what to do\n // next instead of leaving them at a dead end.\n if (data.confirmationRequired || !data.accessToken || !data.refreshToken) {\n this.log(`Account created for ${data.email}.`);\n this.log('Check your email and click the confirmation link, then run:');\n this.log(` rulemetric auth login -e ${data.email}`);\n return;\n }\n\n // Confirmation OFF: a session came back \u2014 log in immediately.\n const expiresAt = data.expiresAt ?? new Date(Date.now() + 3600 * 1000).toISOString();\n saveToken({\n accessToken: data.accessToken,\n refreshToken: data.refreshToken,\n expiresAt,\n email: data.email,\n });\n saveEnvFile(data.accessToken, process.env.RULEMETRIC_API_URL);\n\n await bootstrapActiveOrg();\n await ensureDeviceApiKey(this.log.bind(this), this.warn.bind(this));\n\n this.log(`Signed up and logged in as ${data.email}`);\n }\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;;AAAA,SAAS,aAAa;AAetB,IAAqB,aAArB,MAAqB,oBAAmB,YAAY;AAAA,EAClD,OAAgB,cAAc;AAAA,EAE9B,OAAgB,WAAW;AAAA,IACzB;AAAA,IACA;AAAA,EACF;AAAA,EAEA,OAAgB,QAAQ;AAAA,IACtB,OAAO,MAAM,OAAO,EAAE,MAAM,KAAK,aAAa,gBAAgB,CAAC;AAAA,IAC/D,UAAU,MAAM,OAAO,EAAE,MAAM,KAAK,aAAa,kDAAkD,CAAC;AAAA,EACtG;AAAA,EAEA,MAAM,MAAqB;AACzB,UAAM,EAAE,MAAM,IAAI,MAAM,KAAK,MAAM,WAAU;AAE7C,QAAI,QAAQ,MAAM;AAClB,QAAI,WAAW,MAAM;AAErB,QAAI,CAAC,SAAS,CAAC,UAAU;AAGvB,UAAI,CAAC,QAAQ,MAAM,OAAO;AACxB,aAAK,MAAM,oFAAoF;AAAA,MACjG;AACA,YAAM,EAAE,OAAO,UAAU,eAAe,IAAI,MAAM,OAAO,mBAAmB;AAE5E,UAAI,CAAC,OAAO;AACV,gBAAQ,MAAM,MAAM,EAAE,SAAS,SAAS,CAAC;AAAA,MAC3C;AACA,UAAI,CAAC,UAAU;AACb,mBAAW,MAAM,eAAe,EAAE,SAAS,0BAA0B,CAAC;AAAA,MACxE;AAAA,IACF;AAEA,QAAI;AACJ,QAAI;AACF,aAAO,MAAM,QAAwB,gBAAgB,EAAE,OAAO,SAAS,CAAC;AAAA,IAC1E,SAAS,KAAK;AAEZ,UAAI,eAAe,YAAY,IAAI,eAAe,KAAK;AACrD,aAAK,MAAM,mBAAmB,KAAK,kDAAkD,KAAK,EAAE;AAAA,MAC9F;AACA,YAAM;AAAA,IACR;AAIA,QAAI,KAAK,wBAAwB,CAAC,KAAK,eAAe,CAAC,KAAK,cAAc;AACxE,WAAK,IAAI,uBAAuB,KAAK,KAAK,GAAG;AAC7C,WAAK,IAAI,6DAA6D;AACtE,WAAK,IAAI,8BAA8B,KAAK,KAAK,EAAE;AACnD;AAAA,IACF;AAGA,UAAM,YAAY,KAAK,aAAa,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,GAAI,EAAE,YAAY;AACnF,cAAU;AAAA,MACR,aAAa,KAAK;AAAA,MAClB,cAAc,KAAK;AAAA,MACnB;AAAA,MACA,OAAO,KAAK;AAAA,IACd,CAAC;AACD,gBAAY,KAAK,aAAa,QAAQ,IAAI,kBAAkB;AAE5D,UAAM,mBAAmB;AACzB,UAAM,mBAAmB,KAAK,IAAI,KAAK,IAAI,GAAG,KAAK,KAAK,KAAK,IAAI,CAAC;AAElE,SAAK,IAAI,8BAA8B,KAAK,KAAK,EAAE;AAAA,EACrD;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
import {
|
|
2
2
|
runAgentLoop
|
|
3
|
-
} from "../../chunk-
|
|
3
|
+
} from "../../chunk-CVMRMSV4.js";
|
|
4
4
|
import "../../chunk-JEJ3J5J6.js";
|
|
5
|
+
import "../../chunk-RCMXTLQF.js";
|
|
6
|
+
import "../../chunk-N5A2IQAK.js";
|
|
5
7
|
import "../../chunk-OTOWLUOJ.js";
|
|
6
|
-
import "../../chunk-
|
|
8
|
+
import "../../chunk-6TPJGYFF.js";
|
|
7
9
|
import "../../chunk-Y4BJXXYV.js";
|
|
8
10
|
import "../../chunk-W53GKIZQ.js";
|
|
9
|
-
import "../../chunk-
|
|
11
|
+
import "../../chunk-MUOUV5LY.js";
|
|
12
|
+
import "../../chunk-XZXS2W24.js";
|
|
10
13
|
import "../../chunk-RQ2TMLKG.js";
|
|
11
|
-
import "../../chunk-
|
|
12
|
-
import "../../chunk-
|
|
14
|
+
import "../../chunk-VBGUNFKY.js";
|
|
15
|
+
import "../../chunk-FZKLLNDS.js";
|
|
16
|
+
import "../../chunk-VIYUIQHW.js";
|
|
17
|
+
import "../../chunk-DGHWRQXL.js";
|
|
13
18
|
import "../../chunk-E3BIT53W.js";
|
|
14
19
|
import "../../chunk-QSN77T7C.js";
|
|
15
20
|
import {
|
|
16
21
|
closeDb
|
|
17
22
|
} from "../../chunk-QNSVCSHG.js";
|
|
18
23
|
import "../../chunk-3POYC5NA.js";
|
|
19
|
-
import "../../chunk-RCMXTLQF.js";
|
|
20
|
-
import "../../chunk-N5A2IQAK.js";
|
|
21
|
-
import "../../chunk-XZXS2W24.js";
|
|
22
|
-
import "../../chunk-FZKLLNDS.js";
|
|
23
|
-
import "../../chunk-DGHWRQXL.js";
|
|
24
24
|
import "../../chunk-DR5WLIOU.js";
|
|
25
25
|
import "../../chunk-J7N3DLH6.js";
|
|
26
26
|
import "../../chunk-52WIYRZH.js";
|
|
@@ -22,14 +22,14 @@ import {
|
|
|
22
22
|
writeCursorUserProxyConfig,
|
|
23
23
|
writeVSCodeProxyConfig,
|
|
24
24
|
writeVSCodeUserProxyConfig
|
|
25
|
-
} from "../../chunk-
|
|
26
|
-
import "../../chunk-KRBQLMOP.js";
|
|
25
|
+
} from "../../chunk-XN23W5HL.js";
|
|
27
26
|
import {
|
|
28
27
|
GATEWAY_PORT,
|
|
29
28
|
isGatewayRunning,
|
|
30
29
|
spawnGateway,
|
|
31
30
|
waitForGatewayListening
|
|
32
|
-
} from "../../chunk-
|
|
31
|
+
} from "../../chunk-OTPC2LXW.js";
|
|
32
|
+
import "../../chunk-KRBQLMOP.js";
|
|
33
33
|
import {
|
|
34
34
|
BaseCommand
|
|
35
35
|
} from "../../chunk-SZ7VDCD6.js";
|
|
@@ -101,6 +101,7 @@ var HooksInstall = class _HooksInstall extends BaseCommand {
|
|
|
101
101
|
const configDir = join(projectPath, ".claude");
|
|
102
102
|
const configPath = join(configDir, "settings.json");
|
|
103
103
|
const noProxy = flags["no-proxy"];
|
|
104
|
+
let proxyActive = false;
|
|
104
105
|
if (isGlobal) {
|
|
105
106
|
this.log(`Installing hooks globally \u2192 ${configPath}`);
|
|
106
107
|
}
|
|
@@ -190,6 +191,7 @@ var HooksInstall = class _HooksInstall extends BaseCommand {
|
|
|
190
191
|
env.NODE_EXTRA_CA_CERTS = CERT_PATH;
|
|
191
192
|
}
|
|
192
193
|
settings.env = env;
|
|
194
|
+
proxyActive = true;
|
|
193
195
|
this.log(`[OK] HTTPS_PROXY set to gateway on :${GATEWAY_PORT} (verified live)`);
|
|
194
196
|
const cursorProxy = writeCursorProxyConfig(projectPath, GATEWAY_PORT, CERT_PATH);
|
|
195
197
|
if (cursorProxy) {
|
|
@@ -268,7 +270,11 @@ var HooksInstall = class _HooksInstall extends BaseCommand {
|
|
|
268
270
|
this.log("The API service starts on login and restarts on crash.");
|
|
269
271
|
} else if (!noProxy) {
|
|
270
272
|
this.log(" 1. Start Claude Code \u2014 sessions are captured by hooks immediately");
|
|
271
|
-
|
|
273
|
+
if (proxyActive) {
|
|
274
|
+
this.log(" 2. Full-depth capture is ON \u2014 the proxy is live on :" + GATEWAY_PORT + ".");
|
|
275
|
+
} else {
|
|
276
|
+
this.log(" 2. Enable full depth (proxy not yet live): rulemetric proxy start");
|
|
277
|
+
}
|
|
272
278
|
this.log("");
|
|
273
279
|
this.log("Hooks capture every session (lifecycle + transcript). The proxy adds the");
|
|
274
280
|
this.log("rendered system prompt (instruction-effectiveness linking) + cross-tool capture.");
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/commands/hooks/install.ts"],
|
|
4
|
-
"sourcesContent": ["import { Flags } from '@oclif/core';\nimport { execFileSync, execSync, spawnSync } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join, dirname } from 'node:path';\nimport { BaseCommand } from '../../base-command.js';\nimport { isGatewayRunning, waitForGatewayListening, spawnGateway, GATEWAY_PORT } from '../../lib/gateway-lifecycle.js';\n\n// Resolve the directory holding the `rulemetric` executable, so hook commands\n// can prepend it to PATH. Claude Code runs hooks via `/bin/sh -c` with a\n// minimal PATH that omits nvm/pnpm global bin dirs \u2014 so a bare `rulemetric`\n// hits \"command not found\" and capture (plus the session-start gateway\n// auto-start) silently fails. We resolve it here, in the install process's\n// full PATH, and bake the dir into the hook command. Returns undefined on\n// failure \u2192 callers fall back to the bare `rulemetric` (dev/back-compat).\nfunction resolveHookBinDir(): string | undefined {\n try {\n const out = execFileSync('/bin/sh', ['-c', 'command -v rulemetric'], {\n encoding: 'utf-8',\n }).trim();\n if (out && existsSync(out)) return dirname(out);\n } catch {\n /* not resolvable \u2014 fall back to bare `rulemetric` */\n }\n return undefined;\n}\n\n// True iff `rulemetric service install` has wired the gateway up as a launchd\n// agent. When this returns true, hooks install must NOT spawn its own\n// detached gateway \u2014 two processes fighting for :8787 means one crash-loops\n// and Claude Code's HTTPS_PROXY breaks.\nfunction isGatewayLaunchdManaged(): boolean {\n try {\n execFileSync('launchctl', ['list', 'com.rulemetric.gateway'], {\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n return true;\n } catch {\n // Non-zero exit = label not loaded\n return false;\n }\n}\nimport {\n writeStatuslineShim,\n readCurrentStatusLine,\n isStatuslineShimInstalled,\n STATUSLINE_SETTINGS_VALUE,\n} from '../../lib/statusline-shim.js';\nimport {\n mergeClaudeCodeHooks,\n removeClaudeCodeHooks,\n readClaudeSettings,\n writeClaudeSettingsWithBackup,\n writeCursorProxyConfig,\n writeCursorUserProxyConfig,\n writeVSCodeProxyConfig,\n installMacOSProxyEnv,\n writeVSCodeUserProxyConfig,\n writeCursorHooks,\n writeCursorUserHooks,\n writeCopilotHooks,\n writeAntigravityHooks,\n writeAntigravityUserHooks,\n} from '../../lib/hooks-config.js';\nimport { bootstrapActiveOrg } from '../../lib/active-org-refresh.js';\nimport { detectTmux } from '../../lib/detect-tmux.js';\n\nconst CERT_PATH = join(homedir(), '.mitmproxy', 'mitmproxy-ca-cert.pem');\n\nexport default class HooksInstall extends BaseCommand {\n static override description = 'Install session tracking hooks and context capture proxy for Claude Code';\n\n static override examples = [\n '<%= config.bin %> hooks install',\n '<%= config.bin %> hooks install --global',\n '<%= config.bin %> hooks install --no-proxy',\n ];\n\n static override flags = {\n 'no-proxy': Flags.boolean({\n default: false,\n description: 'Skip proxy/gateway configuration (hooks only)',\n }),\n 'project-dir': Flags.string({\n description: 'Project directory to install hooks in (defaults to cwd)',\n }),\n global: Flags.boolean({\n char: 'g',\n default: false,\n description: 'Install hooks globally (~/.claude/settings.json) so all projects are tracked',\n }),\n 'statusline-usage': Flags.boolean({\n default: false,\n description: 'Install the statusline shim so Claude Code pushes live rate_limits on every refresh (makes /usage rings update in real time without needing the proxy)',\n }),\n };\n\n async run(): Promise<void> {\n const { flags } = await this.parse(HooksInstall);\n const isGlobal = flags.global;\n const projectPath = isGlobal ? homedir() : (flags['project-dir'] ?? process.cwd());\n const configDir = join(projectPath, '.claude');\n const configPath = join(configDir, 'settings.json');\n const noProxy = flags['no-proxy'];\n\n if (isGlobal) {\n this.log(`Installing hooks globally \u2192 ${configPath}`);\n }\n\n // Read or create .claude/settings.json. Never crash on a malformed file \u2014\n // a fresh user's hand-edited settings.json must not block capture install.\n const { settings, recoveredBackup } = readClaudeSettings(configPath);\n if (recoveredBackup) {\n this.log(`[!!] ${configPath} was not valid JSON \u2014 backed it up to ${recoveredBackup} and continued with a fresh file.`);\n }\n\n // \u2500\u2500 1. Install Claude Code session-tracking hooks (hooks-first, zero-proxy\n // capture). Strip any stale rulemetric entries first \u2014 handles old command\n // formats, preserves the user's own hooks \u2014 then merge the current canonical\n // set. Runs regardless of --no-proxy: this is the whole point of the hooks\n // path, which captures full sessions (lifecycle + SessionEnd transcript\n // reimport) without mitmproxy / CA trust / gateway. The proxy adds the\n // rendered system prompt (instruction-linking) + cross-tool capture on top.\n const refreshedHooks = removeClaudeCodeHooks(settings);\n // Resolve once; every hook writer (Claude Code, Cursor, Copilot) needs the\n // same PATH prefix so a GUI-launched tool with a minimal PATH can find the CLI.\n const hookBinDir = resolveHookBinDir();\n mergeClaudeCodeHooks(settings, hookBinDir);\n this.log(\n refreshedHooks\n ? '[OK] Refreshed RuleMetric Claude Code session hooks \u2192 .claude/settings.json'\n : '[OK] Installed RuleMetric Claude Code session hooks \u2192 .claude/settings.json',\n );\n\n // \u2500\u2500 2. Configure gateway + proxy \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n if (!noProxy) {\n // Ensure mitmproxy is available\n const hasMitmproxy = this.findBinary('mitmdump');\n if (!hasMitmproxy) {\n this.log('');\n this.log('[!!] mitmproxy not found \u2014 installing...');\n const installed = this.installMitmproxy();\n if (!installed) {\n this.log('[!!] Could not install mitmproxy. Skipping proxy config.');\n this.log(' Install manually: pipx install mitmproxy');\n this.log(' Then re-run: rulemetric hooks install');\n }\n }\n\n // Generate CA cert if missing\n if (!existsSync(CERT_PATH)) {\n this.log('[..] Generating CA certificate...');\n const mitmdump = this.findBinary('mitmdump');\n if (mitmdump) {\n spawnSync(mitmdump, ['--set', 'listen_port=0', '-q'], {\n timeout: 5000,\n stdio: 'ignore',\n });\n if (existsSync(CERT_PATH)) {\n this.log(`[OK] CA certificate generated at ${CERT_PATH}`);\n } else {\n this.log('[!!] Could not generate CA cert');\n }\n }\n } else {\n this.log('[OK] CA certificate found');\n }\n\n // Trust CA cert in system store (if not already trusted). Track the\n // result: whether the CA is trusted decides if we may route MACHINE-WIDE /\n // native-app traffic through the MITM proxy (native apps validate against\n // the system keychain, so an untrusted CA breaks their TLS).\n const caGenerated = existsSync(CERT_PATH);\n let caTrusted = false;\n if (caGenerated && !this.isCACertTrusted()) {\n this.log('[..] Installing CA certificate into system trust store (requires admin)...');\n caTrusted = this.trustCACert();\n if (caTrusted) {\n this.log('[OK] CA certificate trusted by system');\n } else {\n this.log('[!!] Could not install CA cert automatically.');\n if (process.platform === 'darwin') {\n this.log(` Run manually: sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain ${CERT_PATH}`);\n } else if (process.platform === 'linux') {\n this.log(` Run manually: sudo cp ${CERT_PATH} /usr/local/share/ca-certificates/mitmproxy-ca.crt && sudo update-ca-certificates`);\n }\n }\n } else if (caGenerated) {\n this.log('[OK] CA certificate already trusted');\n caTrusted = true;\n }\n\n // Start the gateway BEFORE pinning HTTPS_PROXY. Three possible owners (in\n // order of precedence):\n // 1. launchd (`rulemetric service install` plist) \u2014 authoritative;\n // hooks must NOT spawn a competing copy that would fight for :8787\n // 2. legacy PID-file (this same code path from a previous run)\n // 3. nothing \u2014 spawn a fresh detached process\n if (isGatewayLaunchdManaged()) {\n this.log('[OK] Gateway managed by launchd (com.rulemetric.gateway)');\n } else if (isGatewayRunning()) {\n this.log('[OK] Gateway already running');\n } else {\n try {\n const pid = spawnGateway(this.config.version);\n this.log(`[OK] Gateway started (PID ${pid})`);\n } catch (err) {\n this.log(`[!!] Could not start gateway: ${(err as Error).message}`);\n }\n }\n\n // Liveness guard: ONLY pin HTTPS_PROXY once :8787 actually accepts\n // connections. Writing it against a dead port is the fresh-laptop\n // ConnectionRefused footgun \u2014 it breaks all of Claude Code's HTTPS. If the\n // gateway isn't answering, we leave Claude Code talking to Anthropic\n // directly; capture still works via the hooks installed in \u00A71.\n // Poll (not a one-shot probe): a just-spawned gateway needs a moment to\n // bind :8787, and a single immediate probe would fail-close every fresh\n // install and silently disable deep capture.\n const gatewayLive = await waitForGatewayListening(GATEWAY_PORT, { timeoutMs: 4000 });\n if (!caGenerated) {\n // No CA \u2192 mitmproxy will regenerate one and intercept, but nothing\n // trusts it, so pinning HTTPS_PROXY would break even Claude Code's TLS.\n // Stay hooks-only rather than pin a proxy nothing can validate.\n this.log('[!!] No CA certificate \u2014 NOT setting HTTPS_PROXY (capture stays hooks-only).');\n this.log(' Install mitmproxy, then re-run: rulemetric hooks install');\n } else if (!gatewayLive) {\n this.log(`[!!] Gateway is not answering on :${GATEWAY_PORT} \u2014 NOT setting HTTPS_PROXY.`);\n this.log(' Capture still works via hooks. To enable deep (proxy) capture later:');\n this.log(' rulemetric proxy start && rulemetric hooks install');\n } else {\n // Set permanent HTTPS_PROXY and NODE_EXTRA_CA_CERTS in settings.json.\n // These point to the gateway (verified live above), not mitmproxy directly.\n const env = (settings.env ?? {}) as Record<string, string>;\n env.HTTPS_PROXY = `http://localhost:${GATEWAY_PORT}`;\n // Bypass proxy for non-Anthropic traffic (Supabase auth, npm, etc.)\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 settings.env = env;\n this.log(`[OK] HTTPS_PROXY set to gateway on :${GATEWAY_PORT} (verified live)`);\n\n // Configure Cursor proxy (if .cursor/ exists)\n const cursorProxy = writeCursorProxyConfig(projectPath, GATEWAY_PORT, CERT_PATH);\n if (cursorProxy) {\n this.log('[OK] Cursor proxy configured \u2192 .cursor/settings.json');\n }\n\n // Configure VS Code proxy for Copilot capture (workspace settings)\n const vscodeProxy = writeVSCodeProxyConfig(projectPath, GATEWAY_PORT, CERT_PATH);\n if (vscodeProxy) {\n this.log('[OK] VS Code workspace proxy configured \u2192 .vscode/settings.json');\n }\n\n // Set http.proxy in VS Code user settings (workspace-level is silently ignored\n // for http.proxy due to APPLICATION scope \u2014 microsoft/vscode#236932)\n const vscodeUserProxy = writeVSCodeUserProxyConfig(GATEWAY_PORT);\n if (vscodeUserProxy) {\n this.log('[OK] VS Code user proxy configured \u2192 ~/Library/Application Support/Code/User/settings.json');\n }\n\n // Same for Cursor (VS Code fork inherits the APPLICATION-scope restriction).\n const cursorUserProxy = writeCursorUserProxyConfig(GATEWAY_PORT);\n if (cursorUserProxy) {\n this.log('[OK] Cursor user proxy configured \u2192 ~/Library/Application Support/Cursor/User/settings.json');\n }\n\n // Set HTTPS_PROXY for all GUI apps via launchctl + LaunchAgent (macOS)\n // Copilot reads process.env.HTTPS_PROXY directly \u2014 VS Code settings alone\n // are insufficient because workspace http.proxy is ignored and extensions\n // may bypass vscode-proxy-agent (microsoft/vscode#12588).\n //\n // GATED ON CA TRUST: this routes EVERY GUI/native app's HTTPS through the\n // MITM proxy machine-wide. Native apps validate against the system\n // keychain, so if the CA isn't trusted (sudo declined / non-admin) this\n // would break their TLS with an unknown-CA error attributed to nothing\n // obvious. Claude Code + Node apps are unaffected either way (they trust\n // NODE_EXTRA_CA_CERTS), so we keep that path and only skip the\n // machine-wide native layer.\n if (caTrusted) {\n const macProxy = installMacOSProxyEnv(GATEWAY_PORT, CERT_PATH);\n if (macProxy) {\n this.log('[OK] HTTPS_PROXY set for GUI apps (launchctl + LaunchAgent)');\n }\n } else {\n this.log('[!!] CA not trusted \u2014 skipping machine-wide GUI proxy (would break native-app TLS).');\n this.log(' Claude Code capture is unaffected. Trust the CA + re-run to enable cross-app capture.');\n }\n }\n }\n\n // \u2500\u2500 3. Write Cursor + Copilot hook configs \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // These are independent of the proxy (they capture editor lifecycle\n // events, not LLM traffic), so they run regardless of --no-proxy. Both\n // helpers no-op when the target directory (.cursor/ or .github/)\n // doesn't exist.\n if (!isGlobal) {\n const cursorHooks = writeCursorHooks(projectPath, hookBinDir);\n if (cursorHooks) {\n this.log(`[OK] Cursor hooks configured \u2192 ${cursorHooks}`);\n }\n\n const copilotHooks = writeCopilotHooks(projectPath, hookBinDir);\n if (copilotHooks) {\n this.log(`[OK] Copilot Agent hooks configured \u2192 ${copilotHooks}`);\n }\n\n const antigravityHooks = writeAntigravityHooks(projectPath);\n if (antigravityHooks) {\n this.log(`[OK] Antigravity workspace hooks configured \u2192 ${antigravityHooks}`);\n }\n }\n\n // User-level Antigravity hooks at ~/.gemini/config/hooks.json. Required\n // because the GUI Antigravity process is launched once per machine and\n // covers any workspace the user opens, not just the install-time cwd.\n const antigravityUserHooks = writeAntigravityUserHooks();\n if (antigravityUserHooks) {\n this.log(`[OK] Antigravity user hooks configured \u2192 ${antigravityUserHooks}`);\n }\n\n // User-level Cursor hooks (~/.cursor/hooks.json) are required for global\n // capture because Cursor's chat traffic bypasses HTTP proxies (HTTP/2\n // multiplexed persistent connection \u2014 see cursor.com/docs/hooks). Without\n // hooks, we get zero Cursor visibility for sessions outside this project.\n // Runs in both --global and per-project modes since the user file is\n // machine-wide either way.\n const cursorUserHooks = writeCursorUserHooks(hookBinDir);\n if (cursorUserHooks) {\n this.log(`[OK] Cursor user hooks configured \u2192 ${cursorUserHooks}`);\n }\n\n // \u2500\u2500 4. Statusline shim (rate_limits freshness) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // When --statusline-usage is set, write ~/.claude/statusline-rulemetric.sh\n // and point settings.statusLine at it. If the user already has a statusLine\n // command configured, chain it so their prompt text is preserved.\n if (flags['statusline-usage']) {\n if (isStatuslineShimInstalled(settings)) {\n this.log('[OK] Statusline shim already installed');\n } else {\n const prevCmd = readCurrentStatusLine(settings);\n writeStatuslineShim(prevCmd);\n settings.statusLine = STATUSLINE_SETTINGS_VALUE;\n if (prevCmd) {\n this.log(`[OK] Statusline shim installed \u2192 ${STATUSLINE_SETTINGS_VALUE.command} (chains to: ${prevCmd})`);\n } else {\n this.log(`[OK] Statusline shim installed \u2192 ${STATUSLINE_SETTINGS_VALUE.command}`);\n }\n this.log(' Rate-limit rings will now update on Claude Code\\'s statusline refresh cadence');\n }\n }\n\n // \u2500\u2500 5. Write Claude Code settings (backup prior file \u2192 reversible) \u2500\u2500\u2500\u2500\u2500\n const { backupPath } = writeClaudeSettingsWithBackup(configPath, settings);\n if (backupPath) {\n this.log(`[OK] Backed up previous settings \u2192 ${backupPath}`);\n }\n\n this.log('');\n this.log('Setup complete! Next steps:');\n if (isGlobal) {\n this.log(' 1. Install always-on API: rulemetric service install');\n this.log(' 2. Start Claude Code in any project \u2014 all sessions tracked automatically');\n this.log('');\n this.log('The API service starts on login and restarts on crash.');\n } else if (!noProxy) {\n this.log(' 1. Start Claude Code \u2014 sessions are captured by hooks immediately');\n this.log(' 2. Start the capture proxy for full depth: rulemetric proxy start');\n this.log('');\n this.log('Hooks capture every session (lifecycle + transcript). The proxy adds the');\n this.log('rendered system prompt (instruction-effectiveness linking) + cross-tool capture.');\n this.log('');\n this.log('To capture traffic from other clients (Python, curl, etc.):');\n this.log(' eval \"$(rulemetric proxy env --all)\"');\n } else {\n // --no-proxy still installs the Claude Code session hooks above, so full\n // sessions ARE captured (lifecycle + SessionEnd transcript reimport) with\n // no mitmproxy / CA trust / gateway. Only the proxy-exclusive layer\n // (rendered system prompt \u2192 instruction-linking, cross-tool) is skipped.\n this.log(' 1. Start Claude Code \u2014 sessions are captured by the hooks just installed');\n this.log('');\n this.log('No proxy means no mitmproxy / CA cert needed. The hooks capture full');\n this.log('sessions on their own; re-run without --no-proxy to also capture system');\n this.log('prompts for instruction-effectiveness linking.');\n }\n\n // Ensure the active-org cache is populated before the user starts a\n // Claude Code session. BaseCommand fired the guarded refresh earlier\n // but may have raced with auth init or hit a transient error; this\n // explicit retry guarantees the cache reflects the user's current\n // active_org_id at the end of `hooks install`.\n await bootstrapActiveOrg();\n\n // Soft tmux capability check \u2014 live message send needs tmux on the\n // user's machine. We do not auto-install; just surface one line.\n const tmux = await detectTmux();\n this.log('');\n if (tmux.available) {\n this.log(`[OK] tmux found${tmux.version ? ` (v${tmux.version})` : ''} \u2014 live send enabled`);\n } else {\n this.log('[!!] tmux not found \u2014 live send will fall back to opening Terminal. Install with: brew install tmux');\n }\n }\n\n private findBinary(name: string): string | null {\n try {\n return execSync(`which ${name} 2>/dev/null`, { encoding: 'utf-8' }).trim() || null;\n } catch {\n return null;\n }\n }\n\n private installMitmproxy(): boolean {\n for (const [cmd, args] of [\n ['pipx', ['install', 'mitmproxy']],\n ['uv', ['tool', 'install', 'mitmproxy']],\n ['brew', ['install', 'mitmproxy']],\n ] as const) {\n if (this.findBinary(cmd)) {\n this.log(` Installing via ${cmd}...`);\n const result = spawnSync(cmd, [...args], { stdio: 'inherit' });\n if (result.status === 0) return true;\n }\n }\n return false;\n }\n\n private isCACertTrusted(): boolean {\n if (process.platform === 'darwin') {\n const result = spawnSync('security', ['verify-cert', '-c', CERT_PATH], {\n stdio: 'pipe',\n });\n return result.status === 0;\n }\n\n if (process.platform === 'linux') {\n return existsSync('/usr/local/share/ca-certificates/mitmproxy-ca.crt');\n }\n\n if (process.platform === 'win32') {\n // On Windows, check if cert is in the Root store\n const result = spawnSync('certutil', ['-verify', CERT_PATH], {\n stdio: 'pipe',\n });\n return result.status === 0;\n }\n\n return false;\n }\n\n private trustCACert(): boolean {\n if (process.platform === 'darwin') {\n const result = spawnSync('sudo', [\n 'security', 'add-trusted-cert', '-d', '-r', 'trustRoot',\n '-k', '/Library/Keychains/System.keychain', CERT_PATH,\n ], { stdio: 'inherit' });\n return result.status === 0;\n }\n\n if (process.platform === 'linux') {\n const cp = spawnSync('sudo', [\n 'cp', CERT_PATH, '/usr/local/share/ca-certificates/mitmproxy-ca.crt',\n ], { stdio: 'inherit' });\n if (cp.status !== 0) return false;\n const update = spawnSync('sudo', ['update-ca-certificates'], { stdio: 'inherit' });\n return update.status === 0;\n }\n\n if (process.platform === 'win32') {\n const result = spawnSync('certutil', [\n '-addstore', '-f', 'Root', CERT_PATH,\n ], { stdio: 'inherit' });\n return result.status === 0;\n }\n\n return false;\n }\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,SAAS,aAAa;AACtB,SAAS,cAAc,UAAU,iBAAiB;AAClD,SAAS,kBAAkB;AAC3B,SAAS,eAAe;AACxB,SAAS,MAAM,eAAe;AAW9B,SAAS,oBAAwC;AAC/C,MAAI;AACF,UAAM,MAAM,aAAa,WAAW,CAAC,MAAM,uBAAuB,GAAG;AAAA,MACnE,UAAU;AAAA,IACZ,CAAC,EAAE,KAAK;AACR,QAAI,OAAO,WAAW,GAAG,EAAG,QAAO,QAAQ,GAAG;AAAA,EAChD,QAAQ;AAAA,EAER;AACA,SAAO;AACT;AAMA,SAAS,0BAAmC;AAC1C,MAAI;AACF,iBAAa,aAAa,CAAC,QAAQ,wBAAwB,GAAG;AAAA,MAC5D,OAAO,CAAC,UAAU,QAAQ,MAAM;AAAA,IAClC,CAAC;AACD,WAAO;AAAA,EACT,QAAQ;AAEN,WAAO;AAAA,EACT;AACF;AA0BA,IAAM,YAAY,KAAK,QAAQ,GAAG,cAAc,uBAAuB;AAEvE,IAAqB,eAArB,MAAqB,sBAAqB,YAAY;AAAA,EACpD,OAAgB,cAAc;AAAA,EAE9B,OAAgB,WAAW;AAAA,IACzB;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA,EAEA,OAAgB,QAAQ;AAAA,IACtB,YAAY,MAAM,QAAQ;AAAA,MACxB,SAAS;AAAA,MACT,aAAa;AAAA,IACf,CAAC;AAAA,IACD,eAAe,MAAM,OAAO;AAAA,MAC1B,aAAa;AAAA,IACf,CAAC;AAAA,IACD,QAAQ,MAAM,QAAQ;AAAA,MACpB,MAAM;AAAA,MACN,SAAS;AAAA,MACT,aAAa;AAAA,IACf,CAAC;AAAA,IACD,oBAAoB,MAAM,QAAQ;AAAA,MAChC,SAAS;AAAA,MACT,aAAa;AAAA,IACf,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,MAAqB;AACzB,UAAM,EAAE,MAAM,IAAI,MAAM,KAAK,MAAM,aAAY;AAC/C,UAAM,WAAW,MAAM;AACvB,UAAM,cAAc,WAAW,QAAQ,IAAK,MAAM,aAAa,KAAK,QAAQ,IAAI;AAChF,UAAM,YAAY,KAAK,aAAa,SAAS;AAC7C,UAAM,aAAa,KAAK,WAAW,eAAe;AAClD,UAAM,UAAU,MAAM,UAAU;
|
|
4
|
+
"sourcesContent": ["import { Flags } from '@oclif/core';\nimport { execFileSync, execSync, spawnSync } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join, dirname } from 'node:path';\nimport { BaseCommand } from '../../base-command.js';\nimport { isGatewayRunning, waitForGatewayListening, spawnGateway, GATEWAY_PORT } from '../../lib/gateway-lifecycle.js';\n\n// Resolve the directory holding the `rulemetric` executable, so hook commands\n// can prepend it to PATH. Claude Code runs hooks via `/bin/sh -c` with a\n// minimal PATH that omits nvm/pnpm global bin dirs \u2014 so a bare `rulemetric`\n// hits \"command not found\" and capture (plus the session-start gateway\n// auto-start) silently fails. We resolve it here, in the install process's\n// full PATH, and bake the dir into the hook command. Returns undefined on\n// failure \u2192 callers fall back to the bare `rulemetric` (dev/back-compat).\nfunction resolveHookBinDir(): string | undefined {\n try {\n const out = execFileSync('/bin/sh', ['-c', 'command -v rulemetric'], {\n encoding: 'utf-8',\n }).trim();\n if (out && existsSync(out)) return dirname(out);\n } catch {\n /* not resolvable \u2014 fall back to bare `rulemetric` */\n }\n return undefined;\n}\n\n// True iff `rulemetric service install` has wired the gateway up as a launchd\n// agent. When this returns true, hooks install must NOT spawn its own\n// detached gateway \u2014 two processes fighting for :8787 means one crash-loops\n// and Claude Code's HTTPS_PROXY breaks.\nfunction isGatewayLaunchdManaged(): boolean {\n try {\n execFileSync('launchctl', ['list', 'com.rulemetric.gateway'], {\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n return true;\n } catch {\n // Non-zero exit = label not loaded\n return false;\n }\n}\nimport {\n writeStatuslineShim,\n readCurrentStatusLine,\n isStatuslineShimInstalled,\n STATUSLINE_SETTINGS_VALUE,\n} from '../../lib/statusline-shim.js';\nimport {\n mergeClaudeCodeHooks,\n removeClaudeCodeHooks,\n readClaudeSettings,\n writeClaudeSettingsWithBackup,\n writeCursorProxyConfig,\n writeCursorUserProxyConfig,\n writeVSCodeProxyConfig,\n installMacOSProxyEnv,\n writeVSCodeUserProxyConfig,\n writeCursorHooks,\n writeCursorUserHooks,\n writeCopilotHooks,\n writeAntigravityHooks,\n writeAntigravityUserHooks,\n} from '../../lib/hooks-config.js';\nimport { bootstrapActiveOrg } from '../../lib/active-org-refresh.js';\nimport { detectTmux } from '../../lib/detect-tmux.js';\n\nconst CERT_PATH = join(homedir(), '.mitmproxy', 'mitmproxy-ca-cert.pem');\n\nexport default class HooksInstall extends BaseCommand {\n static override description = 'Install session tracking hooks and context capture proxy for Claude Code';\n\n static override examples = [\n '<%= config.bin %> hooks install',\n '<%= config.bin %> hooks install --global',\n '<%= config.bin %> hooks install --no-proxy',\n ];\n\n static override flags = {\n 'no-proxy': Flags.boolean({\n default: false,\n description: 'Skip proxy/gateway configuration (hooks only)',\n }),\n 'project-dir': Flags.string({\n description: 'Project directory to install hooks in (defaults to cwd)',\n }),\n global: Flags.boolean({\n char: 'g',\n default: false,\n description: 'Install hooks globally (~/.claude/settings.json) so all projects are tracked',\n }),\n 'statusline-usage': Flags.boolean({\n default: false,\n description: 'Install the statusline shim so Claude Code pushes live rate_limits on every refresh (makes /usage rings update in real time without needing the proxy)',\n }),\n };\n\n async run(): Promise<void> {\n const { flags } = await this.parse(HooksInstall);\n const isGlobal = flags.global;\n const projectPath = isGlobal ? homedir() : (flags['project-dir'] ?? process.cwd());\n const configDir = join(projectPath, '.claude');\n const configPath = join(configDir, 'settings.json');\n const noProxy = flags['no-proxy'];\n // Whether the capture proxy actually went live this run (HTTPS_PROXY pinned\n // against a verified-live gateway). Drives the \"next steps\" copy so we never\n // tell the user to start a proxy that is already running.\n let proxyActive = false;\n\n if (isGlobal) {\n this.log(`Installing hooks globally \u2192 ${configPath}`);\n }\n\n // Read or create .claude/settings.json. Never crash on a malformed file \u2014\n // a fresh user's hand-edited settings.json must not block capture install.\n const { settings, recoveredBackup } = readClaudeSettings(configPath);\n if (recoveredBackup) {\n this.log(`[!!] ${configPath} was not valid JSON \u2014 backed it up to ${recoveredBackup} and continued with a fresh file.`);\n }\n\n // \u2500\u2500 1. Install Claude Code session-tracking hooks (hooks-first, zero-proxy\n // capture). Strip any stale rulemetric entries first \u2014 handles old command\n // formats, preserves the user's own hooks \u2014 then merge the current canonical\n // set. Runs regardless of --no-proxy: this is the whole point of the hooks\n // path, which captures full sessions (lifecycle + SessionEnd transcript\n // reimport) without mitmproxy / CA trust / gateway. The proxy adds the\n // rendered system prompt (instruction-linking) + cross-tool capture on top.\n const refreshedHooks = removeClaudeCodeHooks(settings);\n // Resolve once; every hook writer (Claude Code, Cursor, Copilot) needs the\n // same PATH prefix so a GUI-launched tool with a minimal PATH can find the CLI.\n const hookBinDir = resolveHookBinDir();\n mergeClaudeCodeHooks(settings, hookBinDir);\n this.log(\n refreshedHooks\n ? '[OK] Refreshed RuleMetric Claude Code session hooks \u2192 .claude/settings.json'\n : '[OK] Installed RuleMetric Claude Code session hooks \u2192 .claude/settings.json',\n );\n\n // \u2500\u2500 2. Configure gateway + proxy \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n if (!noProxy) {\n // Ensure mitmproxy is available\n const hasMitmproxy = this.findBinary('mitmdump');\n if (!hasMitmproxy) {\n this.log('');\n this.log('[!!] mitmproxy not found \u2014 installing...');\n const installed = this.installMitmproxy();\n if (!installed) {\n this.log('[!!] Could not install mitmproxy. Skipping proxy config.');\n this.log(' Install manually: pipx install mitmproxy');\n this.log(' Then re-run: rulemetric hooks install');\n }\n }\n\n // Generate CA cert if missing\n if (!existsSync(CERT_PATH)) {\n this.log('[..] Generating CA certificate...');\n const mitmdump = this.findBinary('mitmdump');\n if (mitmdump) {\n spawnSync(mitmdump, ['--set', 'listen_port=0', '-q'], {\n timeout: 5000,\n stdio: 'ignore',\n });\n if (existsSync(CERT_PATH)) {\n this.log(`[OK] CA certificate generated at ${CERT_PATH}`);\n } else {\n this.log('[!!] Could not generate CA cert');\n }\n }\n } else {\n this.log('[OK] CA certificate found');\n }\n\n // Trust CA cert in system store (if not already trusted). Track the\n // result: whether the CA is trusted decides if we may route MACHINE-WIDE /\n // native-app traffic through the MITM proxy (native apps validate against\n // the system keychain, so an untrusted CA breaks their TLS).\n const caGenerated = existsSync(CERT_PATH);\n let caTrusted = false;\n if (caGenerated && !this.isCACertTrusted()) {\n this.log('[..] Installing CA certificate into system trust store (requires admin)...');\n caTrusted = this.trustCACert();\n if (caTrusted) {\n this.log('[OK] CA certificate trusted by system');\n } else {\n this.log('[!!] Could not install CA cert automatically.');\n if (process.platform === 'darwin') {\n this.log(` Run manually: sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain ${CERT_PATH}`);\n } else if (process.platform === 'linux') {\n this.log(` Run manually: sudo cp ${CERT_PATH} /usr/local/share/ca-certificates/mitmproxy-ca.crt && sudo update-ca-certificates`);\n }\n }\n } else if (caGenerated) {\n this.log('[OK] CA certificate already trusted');\n caTrusted = true;\n }\n\n // Start the gateway BEFORE pinning HTTPS_PROXY. Three possible owners (in\n // order of precedence):\n // 1. launchd (`rulemetric service install` plist) \u2014 authoritative;\n // hooks must NOT spawn a competing copy that would fight for :8787\n // 2. legacy PID-file (this same code path from a previous run)\n // 3. nothing \u2014 spawn a fresh detached process\n if (isGatewayLaunchdManaged()) {\n this.log('[OK] Gateway managed by launchd (com.rulemetric.gateway)');\n } else if (isGatewayRunning()) {\n this.log('[OK] Gateway already running');\n } else {\n try {\n const pid = spawnGateway(this.config.version);\n this.log(`[OK] Gateway started (PID ${pid})`);\n } catch (err) {\n this.log(`[!!] Could not start gateway: ${(err as Error).message}`);\n }\n }\n\n // Liveness guard: ONLY pin HTTPS_PROXY once :8787 actually accepts\n // connections. Writing it against a dead port is the fresh-laptop\n // ConnectionRefused footgun \u2014 it breaks all of Claude Code's HTTPS. If the\n // gateway isn't answering, we leave Claude Code talking to Anthropic\n // directly; capture still works via the hooks installed in \u00A71.\n // Poll (not a one-shot probe): a just-spawned gateway needs a moment to\n // bind :8787, and a single immediate probe would fail-close every fresh\n // install and silently disable deep capture.\n const gatewayLive = await waitForGatewayListening(GATEWAY_PORT, { timeoutMs: 4000 });\n if (!caGenerated) {\n // No CA \u2192 mitmproxy will regenerate one and intercept, but nothing\n // trusts it, so pinning HTTPS_PROXY would break even Claude Code's TLS.\n // Stay hooks-only rather than pin a proxy nothing can validate.\n this.log('[!!] No CA certificate \u2014 NOT setting HTTPS_PROXY (capture stays hooks-only).');\n this.log(' Install mitmproxy, then re-run: rulemetric hooks install');\n } else if (!gatewayLive) {\n this.log(`[!!] Gateway is not answering on :${GATEWAY_PORT} \u2014 NOT setting HTTPS_PROXY.`);\n this.log(' Capture still works via hooks. To enable deep (proxy) capture later:');\n this.log(' rulemetric proxy start && rulemetric hooks install');\n } else {\n // Set permanent HTTPS_PROXY and NODE_EXTRA_CA_CERTS in settings.json.\n // These point to the gateway (verified live above), not mitmproxy directly.\n const env = (settings.env ?? {}) as Record<string, string>;\n env.HTTPS_PROXY = `http://localhost:${GATEWAY_PORT}`;\n // Bypass proxy for non-Anthropic traffic (Supabase auth, npm, etc.)\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 settings.env = env;\n proxyActive = true;\n this.log(`[OK] HTTPS_PROXY set to gateway on :${GATEWAY_PORT} (verified live)`);\n\n // Configure Cursor proxy (if .cursor/ exists)\n const cursorProxy = writeCursorProxyConfig(projectPath, GATEWAY_PORT, CERT_PATH);\n if (cursorProxy) {\n this.log('[OK] Cursor proxy configured \u2192 .cursor/settings.json');\n }\n\n // Configure VS Code proxy for Copilot capture (workspace settings)\n const vscodeProxy = writeVSCodeProxyConfig(projectPath, GATEWAY_PORT, CERT_PATH);\n if (vscodeProxy) {\n this.log('[OK] VS Code workspace proxy configured \u2192 .vscode/settings.json');\n }\n\n // Set http.proxy in VS Code user settings (workspace-level is silently ignored\n // for http.proxy due to APPLICATION scope \u2014 microsoft/vscode#236932)\n const vscodeUserProxy = writeVSCodeUserProxyConfig(GATEWAY_PORT);\n if (vscodeUserProxy) {\n this.log('[OK] VS Code user proxy configured \u2192 ~/Library/Application Support/Code/User/settings.json');\n }\n\n // Same for Cursor (VS Code fork inherits the APPLICATION-scope restriction).\n const cursorUserProxy = writeCursorUserProxyConfig(GATEWAY_PORT);\n if (cursorUserProxy) {\n this.log('[OK] Cursor user proxy configured \u2192 ~/Library/Application Support/Cursor/User/settings.json');\n }\n\n // Set HTTPS_PROXY for all GUI apps via launchctl + LaunchAgent (macOS)\n // Copilot reads process.env.HTTPS_PROXY directly \u2014 VS Code settings alone\n // are insufficient because workspace http.proxy is ignored and extensions\n // may bypass vscode-proxy-agent (microsoft/vscode#12588).\n //\n // GATED ON CA TRUST: this routes EVERY GUI/native app's HTTPS through the\n // MITM proxy machine-wide. Native apps validate against the system\n // keychain, so if the CA isn't trusted (sudo declined / non-admin) this\n // would break their TLS with an unknown-CA error attributed to nothing\n // obvious. Claude Code + Node apps are unaffected either way (they trust\n // NODE_EXTRA_CA_CERTS), so we keep that path and only skip the\n // machine-wide native layer.\n if (caTrusted) {\n const macProxy = installMacOSProxyEnv(GATEWAY_PORT, CERT_PATH);\n if (macProxy) {\n this.log('[OK] HTTPS_PROXY set for GUI apps (launchctl + LaunchAgent)');\n }\n } else {\n this.log('[!!] CA not trusted \u2014 skipping machine-wide GUI proxy (would break native-app TLS).');\n this.log(' Claude Code capture is unaffected. Trust the CA + re-run to enable cross-app capture.');\n }\n }\n }\n\n // \u2500\u2500 3. Write Cursor + Copilot hook configs \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // These are independent of the proxy (they capture editor lifecycle\n // events, not LLM traffic), so they run regardless of --no-proxy. Both\n // helpers no-op when the target directory (.cursor/ or .github/)\n // doesn't exist.\n if (!isGlobal) {\n const cursorHooks = writeCursorHooks(projectPath, hookBinDir);\n if (cursorHooks) {\n this.log(`[OK] Cursor hooks configured \u2192 ${cursorHooks}`);\n }\n\n const copilotHooks = writeCopilotHooks(projectPath, hookBinDir);\n if (copilotHooks) {\n this.log(`[OK] Copilot Agent hooks configured \u2192 ${copilotHooks}`);\n }\n\n const antigravityHooks = writeAntigravityHooks(projectPath);\n if (antigravityHooks) {\n this.log(`[OK] Antigravity workspace hooks configured \u2192 ${antigravityHooks}`);\n }\n }\n\n // User-level Antigravity hooks at ~/.gemini/config/hooks.json \u2014 covers any\n // workspace the user opens, not just the install-time cwd. Written ONLY when\n // ~/.gemini already exists (the tool is present); returns null otherwise so\n // we never manufacture config for a tool the user doesn't have (F2).\n const antigravityUserHooks = writeAntigravityUserHooks();\n if (antigravityUserHooks) {\n this.log(`[OK] Antigravity user hooks configured \u2192 ${antigravityUserHooks}`);\n }\n\n // User-level Cursor hooks (~/.cursor/hooks.json) are required for global\n // capture because Cursor's chat traffic bypasses HTTP proxies (HTTP/2\n // multiplexed persistent connection \u2014 see cursor.com/docs/hooks). Without\n // hooks, we get zero Cursor visibility for sessions outside this project.\n // Runs in both --global and per-project modes since the user file is\n // machine-wide either way.\n const cursorUserHooks = writeCursorUserHooks(hookBinDir);\n if (cursorUserHooks) {\n this.log(`[OK] Cursor user hooks configured \u2192 ${cursorUserHooks}`);\n }\n\n // \u2500\u2500 4. Statusline shim (rate_limits freshness) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // When --statusline-usage is set, write ~/.claude/statusline-rulemetric.sh\n // and point settings.statusLine at it. If the user already has a statusLine\n // command configured, chain it so their prompt text is preserved.\n if (flags['statusline-usage']) {\n if (isStatuslineShimInstalled(settings)) {\n this.log('[OK] Statusline shim already installed');\n } else {\n const prevCmd = readCurrentStatusLine(settings);\n writeStatuslineShim(prevCmd);\n settings.statusLine = STATUSLINE_SETTINGS_VALUE;\n if (prevCmd) {\n this.log(`[OK] Statusline shim installed \u2192 ${STATUSLINE_SETTINGS_VALUE.command} (chains to: ${prevCmd})`);\n } else {\n this.log(`[OK] Statusline shim installed \u2192 ${STATUSLINE_SETTINGS_VALUE.command}`);\n }\n this.log(' Rate-limit rings will now update on Claude Code\\'s statusline refresh cadence');\n }\n }\n\n // \u2500\u2500 5. Write Claude Code settings (backup prior file \u2192 reversible) \u2500\u2500\u2500\u2500\u2500\n const { backupPath } = writeClaudeSettingsWithBackup(configPath, settings);\n if (backupPath) {\n this.log(`[OK] Backed up previous settings \u2192 ${backupPath}`);\n }\n\n this.log('');\n this.log('Setup complete! Next steps:');\n if (isGlobal) {\n this.log(' 1. Install always-on API: rulemetric service install');\n this.log(' 2. Start Claude Code in any project \u2014 all sessions tracked automatically');\n this.log('');\n this.log('The API service starts on login and restarts on crash.');\n } else if (!noProxy) {\n this.log(' 1. Start Claude Code \u2014 sessions are captured by hooks immediately');\n if (proxyActive) {\n // The proxy IS live (pinned against a verified gateway above) \u2014 don't\n // tell the user to start something that's already running.\n this.log(' 2. Full-depth capture is ON \u2014 the proxy is live on :' + GATEWAY_PORT + '.');\n } else {\n this.log(' 2. Enable full depth (proxy not yet live): rulemetric proxy start');\n }\n this.log('');\n this.log('Hooks capture every session (lifecycle + transcript). The proxy adds the');\n this.log('rendered system prompt (instruction-effectiveness linking) + cross-tool capture.');\n this.log('');\n this.log('To capture traffic from other clients (Python, curl, etc.):');\n this.log(' eval \"$(rulemetric proxy env --all)\"');\n } else {\n // --no-proxy still installs the Claude Code session hooks above, so full\n // sessions ARE captured (lifecycle + SessionEnd transcript reimport) with\n // no mitmproxy / CA trust / gateway. Only the proxy-exclusive layer\n // (rendered system prompt \u2192 instruction-linking, cross-tool) is skipped.\n this.log(' 1. Start Claude Code \u2014 sessions are captured by the hooks just installed');\n this.log('');\n this.log('No proxy means no mitmproxy / CA cert needed. The hooks capture full');\n this.log('sessions on their own; re-run without --no-proxy to also capture system');\n this.log('prompts for instruction-effectiveness linking.');\n }\n\n // Ensure the active-org cache is populated before the user starts a\n // Claude Code session. BaseCommand fired the guarded refresh earlier\n // but may have raced with auth init or hit a transient error; this\n // explicit retry guarantees the cache reflects the user's current\n // active_org_id at the end of `hooks install`.\n await bootstrapActiveOrg();\n\n // Soft tmux capability check \u2014 live message send needs tmux on the\n // user's machine. We do not auto-install; just surface one line.\n const tmux = await detectTmux();\n this.log('');\n if (tmux.available) {\n this.log(`[OK] tmux found${tmux.version ? ` (v${tmux.version})` : ''} \u2014 live send enabled`);\n } else {\n this.log('[!!] tmux not found \u2014 live send will fall back to opening Terminal. Install with: brew install tmux');\n }\n }\n\n private findBinary(name: string): string | null {\n try {\n return execSync(`which ${name} 2>/dev/null`, { encoding: 'utf-8' }).trim() || null;\n } catch {\n return null;\n }\n }\n\n private installMitmproxy(): boolean {\n for (const [cmd, args] of [\n ['pipx', ['install', 'mitmproxy']],\n ['uv', ['tool', 'install', 'mitmproxy']],\n ['brew', ['install', 'mitmproxy']],\n ] as const) {\n if (this.findBinary(cmd)) {\n this.log(` Installing via ${cmd}...`);\n const result = spawnSync(cmd, [...args], { stdio: 'inherit' });\n if (result.status === 0) return true;\n }\n }\n return false;\n }\n\n private isCACertTrusted(): boolean {\n if (process.platform === 'darwin') {\n const result = spawnSync('security', ['verify-cert', '-c', CERT_PATH], {\n stdio: 'pipe',\n });\n return result.status === 0;\n }\n\n if (process.platform === 'linux') {\n return existsSync('/usr/local/share/ca-certificates/mitmproxy-ca.crt');\n }\n\n if (process.platform === 'win32') {\n // On Windows, check if cert is in the Root store\n const result = spawnSync('certutil', ['-verify', CERT_PATH], {\n stdio: 'pipe',\n });\n return result.status === 0;\n }\n\n return false;\n }\n\n private trustCACert(): boolean {\n if (process.platform === 'darwin') {\n const result = spawnSync('sudo', [\n 'security', 'add-trusted-cert', '-d', '-r', 'trustRoot',\n '-k', '/Library/Keychains/System.keychain', CERT_PATH,\n ], { stdio: 'inherit' });\n return result.status === 0;\n }\n\n if (process.platform === 'linux') {\n const cp = spawnSync('sudo', [\n 'cp', CERT_PATH, '/usr/local/share/ca-certificates/mitmproxy-ca.crt',\n ], { stdio: 'inherit' });\n if (cp.status !== 0) return false;\n const update = spawnSync('sudo', ['update-ca-certificates'], { stdio: 'inherit' });\n return update.status === 0;\n }\n\n if (process.platform === 'win32') {\n const result = spawnSync('certutil', [\n '-addstore', '-f', 'Root', CERT_PATH,\n ], { stdio: 'inherit' });\n return result.status === 0;\n }\n\n return false;\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -14,10 +14,10 @@ import {
|
|
|
14
14
|
removeVSCodeUserProxyConfig,
|
|
15
15
|
uninstallMacOSProxyEnv,
|
|
16
16
|
writeClaudeSettingsWithBackup
|
|
17
|
-
} from "../../chunk-
|
|
17
|
+
} from "../../chunk-XN23W5HL.js";
|
|
18
18
|
import {
|
|
19
19
|
stopGateway
|
|
20
|
-
} from "../../chunk-
|
|
20
|
+
} from "../../chunk-OTPC2LXW.js";
|
|
21
21
|
import {
|
|
22
22
|
BaseCommand
|
|
23
23
|
} from "../../chunk-SZ7VDCD6.js";
|
|
@@ -18,7 +18,9 @@ import { Flags } from "@oclif/core";
|
|
|
18
18
|
import { execSync, spawn, spawnSync } from "node:child_process";
|
|
19
19
|
import { appendFileSync, existsSync, mkdirSync, openSync, readFileSync, writeFileSync } from "node:fs";
|
|
20
20
|
import { homedir } from "node:os";
|
|
21
|
-
import { join, resolve } from "node:path";
|
|
21
|
+
import { dirname, join, resolve } from "node:path";
|
|
22
|
+
import { fileURLToPath } from "node:url";
|
|
23
|
+
var MODULE_DIR = dirname(fileURLToPath(import.meta.url));
|
|
22
24
|
var DEFAULT_PORT = 8788;
|
|
23
25
|
var CONFIG_DIR = join(homedir(), ".config", "rulemetric");
|
|
24
26
|
var PID_FILE = join(CONFIG_DIR, "proxy.pid");
|
|
@@ -27,9 +29,9 @@ var DOCKER_IMAGE = "rulemetric/proxy";
|
|
|
27
29
|
var DOCKER_CONTAINER = "rulemetric-proxy";
|
|
28
30
|
function findAddonPath() {
|
|
29
31
|
const candidates = [
|
|
30
|
-
resolve(
|
|
31
|
-
resolve(
|
|
32
|
-
resolve(
|
|
32
|
+
resolve(MODULE_DIR, "../../../../../packages/proxy/addon/rulemetric_addon.py"),
|
|
33
|
+
resolve(MODULE_DIR, "../../../node_modules/@rulemetric/proxy/addon/rulemetric_addon.py"),
|
|
34
|
+
resolve(MODULE_DIR, "../../../../../node_modules/@rulemetric/proxy/addon/rulemetric_addon.py")
|
|
33
35
|
];
|
|
34
36
|
for (const p of candidates) {
|
|
35
37
|
if (existsSync(p)) return p;
|
|
@@ -40,9 +42,9 @@ function findAddonPath() {
|
|
|
40
42
|
}
|
|
41
43
|
function findDockerfilePath() {
|
|
42
44
|
const candidates = [
|
|
43
|
-
resolve(
|
|
44
|
-
resolve(
|
|
45
|
-
resolve(
|
|
45
|
+
resolve(MODULE_DIR, "../../../../../packages/proxy"),
|
|
46
|
+
resolve(MODULE_DIR, "../../../node_modules/@rulemetric/proxy"),
|
|
47
|
+
resolve(MODULE_DIR, "../../../../../node_modules/@rulemetric/proxy")
|
|
46
48
|
];
|
|
47
49
|
for (const p of candidates) {
|
|
48
50
|
if (existsSync(join(p, "Dockerfile"))) return p;
|
|
@@ -178,7 +180,7 @@ var ProxyStart = class _ProxyStart extends BaseCommand {
|
|
|
178
180
|
findComposeFile() {
|
|
179
181
|
const candidates = [
|
|
180
182
|
join(process.cwd(), "docker-compose.yml"),
|
|
181
|
-
resolve(
|
|
183
|
+
resolve(MODULE_DIR, "../../../../../docker-compose.yml")
|
|
182
184
|
];
|
|
183
185
|
for (const p of candidates) {
|
|
184
186
|
if (existsSync(p)) return p;
|