@rulemetric/cli 0.6.4 → 0.6.6

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.
@@ -0,0 +1,32 @@
1
+ // src/lib/which.ts
2
+ import { existsSync } from "node:fs";
3
+ import { homedir } from "node:os";
4
+ import { join, delimiter } from "node:path";
5
+ function wellKnownBinDirs(platform, home) {
6
+ if (platform === "darwin") {
7
+ return ["/opt/homebrew/bin", "/opt/homebrew/sbin", "/usr/local/bin", join(home, ".local/bin")];
8
+ }
9
+ if (platform === "linux") {
10
+ return ["/usr/local/bin", "/usr/bin", join(home, ".local/bin")];
11
+ }
12
+ return [];
13
+ }
14
+ function findBinary(name, opts = {}) {
15
+ const platform = opts.platform ?? process.platform;
16
+ const home = opts.home ?? homedir();
17
+ const pathDirs = (opts.pathEnv ?? process.env.PATH ?? "").split(delimiter).filter(Boolean);
18
+ const dirs = [.../* @__PURE__ */ new Set([...pathDirs, ...wellKnownBinDirs(platform, home)])];
19
+ const exts = platform === "win32" ? ["", ...(process.env.PATHEXT || ".EXE;.CMD;.BAT;.COM").split(";").filter(Boolean)] : [""];
20
+ for (const dir of dirs) {
21
+ for (const ext of exts) {
22
+ const candidate = join(dir, name + ext);
23
+ if (existsSync(candidate)) return candidate;
24
+ }
25
+ }
26
+ return null;
27
+ }
28
+
29
+ export {
30
+ findBinary
31
+ };
32
+ //# sourceMappingURL=chunk-42GFSAJP.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/lib/which.ts"],
4
+ "sourcesContent": ["import { existsSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join, delimiter } from 'node:path';\n\ninterface FindBinaryOptions {\n /** Override the platform (defaults to process.platform). Used in tests. */\n platform?: NodeJS.Platform;\n /** Override $PATH (defaults to process.env.PATH). Used in tests. */\n pathEnv?: string;\n /** Override the home dir (for the well-known-dir fallback). Used in tests. */\n home?: string;\n}\n\n/**\n * Standard install locations to probe IN ADDITION to $PATH \u2014 GUI/agent-launched\n * processes (and Claude Code's minimal-PATH hook shells) often omit these, so a\n * brew/pipx/mitmdump that's plainly installed reads as \"not found\". This is the\n * same minimal-PATH class as the hook-command PATH prefixing (PR #111); here it\n * makes mitmproxy auto-install actually fire on a Mac with Homebrew. Real $PATH\n * entries are searched first, so this only ever ADDS reach, never overrides.\n */\nfunction wellKnownBinDirs(platform: NodeJS.Platform, home: string): string[] {\n if (platform === 'darwin') {\n return ['/opt/homebrew/bin', '/opt/homebrew/sbin', '/usr/local/bin', join(home, '.local/bin')];\n }\n if (platform === 'linux') {\n return ['/usr/local/bin', '/usr/bin', join(home, '.local/bin')];\n }\n return [];\n}\n\n/**\n * Cross-platform executable lookup \u2014 the portable replacement for `which`/`where`.\n *\n * Scans the directories on `$PATH` for an executable named `name`, returning the\n * first full path that exists or `null`. On Windows it also probes the\n * `PATHEXT` extensions (`.EXE`, `.CMD`, ...) so `findBinary('mitmdump')` resolves\n * `mitmdump.exe`. Pure filesystem reads \u2014 no subprocess \u2014 so it works identically\n * on macOS, Linux, and Windows (the old `execSync('which \u2026')` path threw on\n * Windows, which has no `which`, silently breaking mitmproxy detection there).\n */\nexport function findBinary(name: string, opts: FindBinaryOptions = {}): string | null {\n const platform = opts.platform ?? process.platform;\n const home = opts.home ?? homedir();\n const pathDirs = (opts.pathEnv ?? process.env.PATH ?? '').split(delimiter).filter(Boolean);\n // $PATH first (a real entry wins), then the well-known install dirs \u2014 deduped.\n const dirs = [...new Set([...pathDirs, ...wellKnownBinDirs(platform, home)])];\n\n // On Windows, try the bare name first (in case it already carries an extension),\n // then each PATHEXT suffix. Elsewhere, only the bare name is meaningful.\n const exts =\n platform === 'win32'\n ? ['', ...(process.env.PATHEXT || '.EXE;.CMD;.BAT;.COM').split(';').filter(Boolean)]\n : [''];\n\n for (const dir of dirs) {\n for (const ext of exts) {\n const candidate = join(dir, name + ext);\n if (existsSync(candidate)) return candidate;\n }\n }\n return null;\n}\n"],
5
+ "mappings": ";AAAA,SAAS,kBAAkB;AAC3B,SAAS,eAAe;AACxB,SAAS,MAAM,iBAAiB;AAmBhC,SAAS,iBAAiB,UAA2B,MAAwB;AAC3E,MAAI,aAAa,UAAU;AACzB,WAAO,CAAC,qBAAqB,sBAAsB,kBAAkB,KAAK,MAAM,YAAY,CAAC;AAAA,EAC/F;AACA,MAAI,aAAa,SAAS;AACxB,WAAO,CAAC,kBAAkB,YAAY,KAAK,MAAM,YAAY,CAAC;AAAA,EAChE;AACA,SAAO,CAAC;AACV;AAYO,SAAS,WAAW,MAAc,OAA0B,CAAC,GAAkB;AACpF,QAAM,WAAW,KAAK,YAAY,QAAQ;AAC1C,QAAM,OAAO,KAAK,QAAQ,QAAQ;AAClC,QAAM,YAAY,KAAK,WAAW,QAAQ,IAAI,QAAQ,IAAI,MAAM,SAAS,EAAE,OAAO,OAAO;AAEzF,QAAM,OAAO,CAAC,GAAG,oBAAI,IAAI,CAAC,GAAG,UAAU,GAAG,iBAAiB,UAAU,IAAI,CAAC,CAAC,CAAC;AAI5E,QAAM,OACJ,aAAa,UACT,CAAC,IAAI,IAAI,QAAQ,IAAI,WAAW,uBAAuB,MAAM,GAAG,EAAE,OAAO,OAAO,CAAC,IACjF,CAAC,EAAE;AAET,aAAW,OAAO,MAAM;AACtB,eAAW,OAAO,MAAM;AACtB,YAAM,YAAY,KAAK,KAAK,OAAO,GAAG;AACtC,UAAI,WAAW,SAAS,EAAG,QAAO;AAAA,IACpC;AAAA,EACF;AACA,SAAO;AACT;",
6
+ "names": []
7
+ }
@@ -105,6 +105,8 @@ var HooksInstall = class _HooksInstall extends BaseCommand {
105
105
  const configPath = join(configDir, "settings.json");
106
106
  const noProxy = flags["no-proxy"];
107
107
  let proxyActive = false;
108
+ let proxyReason = noProxy ? "no_proxy_flag" : void 0;
109
+ let caTrusted = false;
108
110
  if (isGlobal) {
109
111
  this.log(`Installing hooks globally \u2192 ${configPath}`);
110
112
  }
@@ -119,12 +121,12 @@ var HooksInstall = class _HooksInstall extends BaseCommand {
119
121
  refreshedHooks ? "[OK] Refreshed RuleMetric Claude Code session hooks \u2192 .claude/settings.json" : "[OK] Installed RuleMetric Claude Code session hooks \u2192 .claude/settings.json"
120
122
  );
121
123
  if (!noProxy) {
122
- const hasMitmproxy = this.findBinary("mitmdump");
123
- if (!hasMitmproxy) {
124
+ let mitmAvailable = Boolean(this.findBinary("mitmdump"));
125
+ if (!mitmAvailable) {
124
126
  this.log("");
125
127
  this.log("[!!] mitmproxy not found \u2014 installing...");
126
- const installed = this.installMitmproxy();
127
- if (!installed) {
128
+ mitmAvailable = this.installMitmproxy();
129
+ if (!mitmAvailable) {
128
130
  this.log("[!!] Could not install mitmproxy. Skipping proxy config.");
129
131
  this.log(" Install manually: pipx install mitmproxy");
130
132
  this.log(" Then re-run: rulemetric hooks install");
@@ -148,7 +150,6 @@ var HooksInstall = class _HooksInstall extends BaseCommand {
148
150
  this.log("[OK] CA certificate found");
149
151
  }
150
152
  const caGenerated = existsSync(CERT_PATH);
151
- let caTrusted = false;
152
153
  if (caGenerated && !this.isCACertTrusted()) {
153
154
  this.log("[..] Installing CA certificate into system trust store (requires admin)...");
154
155
  caTrusted = this.trustCACert();
@@ -180,9 +181,11 @@ var HooksInstall = class _HooksInstall extends BaseCommand {
180
181
  }
181
182
  const gatewayLive = await waitForGatewayListening(GATEWAY_PORT, { timeoutMs: 4e3 });
182
183
  if (!caGenerated) {
184
+ proxyReason = mitmAvailable ? "ca_not_generated" : "mitmproxy_unavailable";
183
185
  this.log("[!!] No CA certificate \u2014 NOT setting HTTPS_PROXY (capture stays hooks-only).");
184
186
  this.log(" Install mitmproxy, then re-run: rulemetric hooks install");
185
187
  } else if (!gatewayLive) {
188
+ proxyReason = "gateway_not_live";
186
189
  this.log(`[!!] Gateway is not answering on :${GATEWAY_PORT} \u2014 NOT setting HTTPS_PROXY.`);
187
190
  this.log(" Capture still works via hooks. To enable deep (proxy) capture later:");
188
191
  this.log(" rulemetric proxy start && rulemetric hooks install");
@@ -218,6 +221,7 @@ var HooksInstall = class _HooksInstall extends BaseCommand {
218
221
  this.log("[OK] HTTPS_PROXY set for GUI apps (launchctl + LaunchAgent)");
219
222
  }
220
223
  } else {
224
+ proxyReason = "ca_untrusted";
221
225
  this.log("[!!] CA not trusted \u2014 skipping machine-wide GUI proxy (would break native-app TLS).");
222
226
  this.log(" Claude Code capture is unaffected. Trust the CA + re-run to enable cross-app capture.");
223
227
  }
@@ -292,7 +296,11 @@ var HooksInstall = class _HooksInstall extends BaseCommand {
292
296
  this.log("prompts for instruction-effectiveness linking.");
293
297
  }
294
298
  await bootstrapActiveOrg();
295
- void emitSetupEvent("hooks_installed", { proxy: proxyActive, global: isGlobal }, this.config.version);
299
+ void emitSetupEvent(
300
+ "hooks_installed",
301
+ { proxy: proxyActive, caTrusted, reason: proxyReason, global: isGlobal },
302
+ this.config.version
303
+ );
296
304
  const tmux = await detectTmux();
297
305
  this.log("");
298
306
  if (tmux.available) {
@@ -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 { emitSetupEvent } from '../../lib/telemetry.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 // Onboarding telemetry (disclosed + opt-outable) \u2014 records that hooks\n // landed and whether the proxy actually went live, so a remote install's\n // capture depth is visible.\n void emitSetupEvent('hooks_installed', { proxy: proxyActive, global: isGlobal }, this.config.version);\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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",
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 { emitSetupEvent } from '../../lib/telemetry.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 // Why the proxy is NOT fully active (undefined = fully active + CA trusted).\n // Reported via telemetry so a remote install's outcome is debuggable without\n // a relayed log: 'no_proxy_flag' | 'mitmproxy_unavailable' | 'ca_not_generated'\n // | 'gateway_not_live' | 'ca_untrusted' (proxy pinned, Claude-Code-only).\n let proxyReason: string | undefined = noProxy ? 'no_proxy_flag' : undefined;\n // Whether the mitmproxy CA is trusted in the system store (gates machine-wide\n // capture). Hoisted from \u00A72 so telemetry can report the partial state.\n let caTrusted = 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 let mitmAvailable = Boolean(this.findBinary('mitmdump'));\n if (!mitmAvailable) {\n this.log('');\n this.log('[!!] mitmproxy not found \u2014 installing...');\n mitmAvailable = this.installMitmproxy();\n if (!mitmAvailable) {\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 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. The usual\n // cause is mitmproxy being absent (CA generation needs mitmdump).\n proxyReason = mitmAvailable ? 'ca_not_generated' : 'mitmproxy_unavailable';\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 proxyReason = 'gateway_not_live';\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 // Partial success: HTTPS_PROXY IS pinned (proxyActive) and Claude Code\n // capture works via NODE_EXTRA_CA_CERTS, but the CA isn't system-trusted\n // so cross-tool/machine-wide capture is skipped. Report it so a remote\n // install shows \"proxy live but Claude-Code-only\" rather than looking\n // fully green.\n proxyReason = 'ca_untrusted';\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 // Onboarding telemetry (disclosed + opt-outable) \u2014 records that hooks\n // landed, whether the proxy went live, whether the CA is system-trusted, and\n // (when not fully green) WHY \u2014 so a remote install's outcome is debuggable\n // without a relayed log: e.g. proxy:false reason:mitmproxy_unavailable, or\n // proxy:true reason:ca_untrusted (live but Claude-Code-only).\n void emitSetupEvent(\n 'hooks_installed',\n { proxy: proxyActive, caTrusted, reason: proxyReason, global: isGlobal },\n this.config.version,\n );\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
  }
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  findBinary
3
- } from "../../chunk-ERPMDAO3.js";
3
+ } from "../../chunk-42GFSAJP.js";
4
4
  import {
5
5
  BaseCommand
6
6
  } from "../../chunk-SZ7VDCD6.js";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  findBinary
3
- } from "../../chunk-ERPMDAO3.js";
3
+ } from "../../chunk-42GFSAJP.js";
4
4
  import {
5
5
  BaseCommand
6
6
  } from "../../chunk-SZ7VDCD6.js";
package/dist/lib/which.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  findBinary
3
- } from "../chunk-ERPMDAO3.js";
3
+ } from "../chunk-42GFSAJP.js";
4
4
  import "../chunk-NSBPE2FW.js";
5
5
  export {
6
6
  findBinary
@@ -4066,5 +4066,5 @@
4066
4066
  ]
4067
4067
  }
4068
4068
  },
4069
- "version": "0.6.4"
4069
+ "version": "0.6.6"
4070
4070
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rulemetric/cli",
3
- "version": "0.6.4",
3
+ "version": "0.6.6",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -73,9 +73,9 @@
73
73
  "postgres": "^3.4.0",
74
74
  "react": "^19.0.0",
75
75
  "zod": "^3.24.0",
76
- "@rulemetric/proxy": "0.6.4",
77
- "@rulemetric/skills-registry": "0.6.4",
78
- "@rulemetric/hooks": "0.6.4"
76
+ "@rulemetric/hooks": "0.6.6",
77
+ "@rulemetric/skills-registry": "0.6.6",
78
+ "@rulemetric/proxy": "0.6.6"
79
79
  },
80
80
  "//deps": "Worker (`evals agent`) runtime deps: the @opentelemetry/* set, @sentry/node, and postgres are STATIC top-level imports loaded at worker startup (via bundled @rulemetric/{telemetry,db}) — they MUST be runtime deps or a clean `npm i -g` crashes with ModuleLoadError. `better-sqlite3` is optionalDependencies (below): it's a native module, lazy `require()`'d inside a try/catch (packages/session providers cursor/opencode) so a missing binary degrades gracefully — making it a hard dep would break `npm i -g` on platforms without prebuilt binaries. `resend` stays a devDep: lazy `await import('resend')` guarded by RESEND_API_KEY, which end-user workers never set (provider falls back to noop). Classification verified via import-graph scan of dist/commands/evals/agent.js + per-importer static/lazy check.",
81
81
  "optionalDependencies": {
@@ -97,8 +97,8 @@
97
97
  "@rulemetric/core": "0.0.1",
98
98
  "@rulemetric/db": "0.0.1",
99
99
  "@rulemetric/email": "0.0.1",
100
- "@rulemetric/telemetry": "0.0.1",
101
100
  "@rulemetric/session": "0.2.1",
101
+ "@rulemetric/telemetry": "0.0.1",
102
102
  "@rulemetric/typescript-config": "0.0.0"
103
103
  },
104
104
  "scripts": {
@@ -1,20 +0,0 @@
1
- // src/lib/which.ts
2
- import { existsSync } from "node:fs";
3
- import { join, delimiter } from "node:path";
4
- function findBinary(name, opts = {}) {
5
- const platform = opts.platform ?? process.platform;
6
- const dirs = (process.env.PATH || "").split(delimiter).filter(Boolean);
7
- const exts = platform === "win32" ? ["", ...(process.env.PATHEXT || ".EXE;.CMD;.BAT;.COM").split(";").filter(Boolean)] : [""];
8
- for (const dir of dirs) {
9
- for (const ext of exts) {
10
- const candidate = join(dir, name + ext);
11
- if (existsSync(candidate)) return candidate;
12
- }
13
- }
14
- return null;
15
- }
16
-
17
- export {
18
- findBinary
19
- };
20
- //# sourceMappingURL=chunk-ERPMDAO3.js.map
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../src/lib/which.ts"],
4
- "sourcesContent": ["import { existsSync } from 'node:fs';\nimport { join, delimiter } from 'node:path';\n\ninterface FindBinaryOptions {\n /** Override the platform (defaults to process.platform). Used in tests. */\n platform?: NodeJS.Platform;\n}\n\n/**\n * Cross-platform executable lookup \u2014 the portable replacement for `which`/`where`.\n *\n * Scans the directories on `$PATH` for an executable named `name`, returning the\n * first full path that exists or `null`. On Windows it also probes the\n * `PATHEXT` extensions (`.EXE`, `.CMD`, ...) so `findBinary('mitmdump')` resolves\n * `mitmdump.exe`. Pure filesystem reads \u2014 no subprocess \u2014 so it works identically\n * on macOS, Linux, and Windows (the old `execSync('which \u2026')` path threw on\n * Windows, which has no `which`, silently breaking mitmproxy detection there).\n */\nexport function findBinary(name: string, opts: FindBinaryOptions = {}): string | null {\n const platform = opts.platform ?? process.platform;\n const dirs = (process.env.PATH || '').split(delimiter).filter(Boolean);\n\n // On Windows, try the bare name first (in case it already carries an extension),\n // then each PATHEXT suffix. Elsewhere, only the bare name is meaningful.\n const exts =\n platform === 'win32'\n ? ['', ...(process.env.PATHEXT || '.EXE;.CMD;.BAT;.COM').split(';').filter(Boolean)]\n : [''];\n\n for (const dir of dirs) {\n for (const ext of exts) {\n const candidate = join(dir, name + ext);\n if (existsSync(candidate)) return candidate;\n }\n }\n return null;\n}\n"],
5
- "mappings": ";AAAA,SAAS,kBAAkB;AAC3B,SAAS,MAAM,iBAAiB;AAiBzB,SAAS,WAAW,MAAc,OAA0B,CAAC,GAAkB;AACpF,QAAM,WAAW,KAAK,YAAY,QAAQ;AAC1C,QAAM,QAAQ,QAAQ,IAAI,QAAQ,IAAI,MAAM,SAAS,EAAE,OAAO,OAAO;AAIrE,QAAM,OACJ,aAAa,UACT,CAAC,IAAI,IAAI,QAAQ,IAAI,WAAW,uBAAuB,MAAM,GAAG,EAAE,OAAO,OAAO,CAAC,IACjF,CAAC,EAAE;AAET,aAAW,OAAO,MAAM;AACtB,eAAW,OAAO,MAAM;AACtB,YAAM,YAAY,KAAK,KAAK,OAAO,GAAG;AACtC,UAAI,WAAW,SAAS,EAAG,QAAO;AAAA,IACpC;AAAA,EACF;AACA,SAAO;AACT;",
6
- "names": []
7
- }