@rulemetric/cli 0.7.0 → 0.7.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.
Files changed (37) hide show
  1. package/dist/chunk-3S7223UR.js +81 -0
  2. package/dist/chunk-3S7223UR.js.map +7 -0
  3. package/dist/{chunk-7UDETV3P.js → chunk-7DULPSFU.js} +15 -15
  4. package/dist/{chunk-HZK43UCF.js → chunk-DUJSRBLP.js} +5 -5
  5. package/dist/chunk-EARWG4EV.js +22 -0
  6. package/dist/chunk-EARWG4EV.js.map +7 -0
  7. package/dist/{chunk-4KRMZLQV.js → chunk-LXKPNATC.js} +3 -1
  8. package/dist/{chunk-4KRMZLQV.js.map → chunk-LXKPNATC.js.map} +2 -2
  9. package/dist/{chunk-4YAP7RT7.js → chunk-OK3I555A.js} +4 -4
  10. package/dist/{chunk-POFTPB33.js → chunk-OKWCKCPE.js} +2 -2
  11. package/dist/{chunk-3LKVS6W5.js → chunk-RMTT4KDY.js} +4 -4
  12. package/dist/commands/evals/agent.js +13 -13
  13. package/dist/commands/gateway/ensure.js +18 -1
  14. package/dist/commands/gateway/ensure.js.map +2 -2
  15. package/dist/commands/hooks/install.js +1 -1
  16. package/dist/commands/hooks/uninstall.js +1 -1
  17. package/dist/commands/proxy/start.js +32 -11
  18. package/dist/commands/proxy/start.js.map +2 -2
  19. package/dist/commands/setup.js +2 -2
  20. package/dist/lib/agent-loop.js +11 -11
  21. package/dist/lib/capture-hosts.js +8 -0
  22. package/dist/lib/capture-hosts.js.map +7 -0
  23. package/dist/lib/editor-proxy-pins.js +14 -0
  24. package/dist/lib/editor-proxy-pins.js.map +7 -0
  25. package/dist/lib/handlers/process-changelog.js +2 -2
  26. package/dist/lib/handlers/process-eval.js +2 -2
  27. package/dist/lib/handlers/process-insights.js +2 -2
  28. package/dist/lib/hooks-config.js +5 -1
  29. package/dist/lib/manual-tasks.js +3 -3
  30. package/dist/lib/setup-steps.js +2 -2
  31. package/oclif.manifest.json +1 -1
  32. package/package.json +7 -7
  33. /package/dist/{chunk-7UDETV3P.js.map → chunk-7DULPSFU.js.map} +0 -0
  34. /package/dist/{chunk-HZK43UCF.js.map → chunk-DUJSRBLP.js.map} +0 -0
  35. /package/dist/{chunk-4YAP7RT7.js.map → chunk-OK3I555A.js.map} +0 -0
  36. /package/dist/{chunk-POFTPB33.js.map → chunk-OKWCKCPE.js.map} +0 -0
  37. /package/dist/{chunk-3LKVS6W5.js.map → chunk-RMTT4KDY.js.map} +0 -0
@@ -0,0 +1,81 @@
1
+ import {
2
+ getCursorUserSettingsPath,
3
+ getVSCodeUserSettingsPath
4
+ } from "./chunk-LXKPNATC.js";
5
+
6
+ // src/lib/editor-proxy-pins.ts
7
+ import { existsSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
8
+ import { homedir } from "node:os";
9
+ import { join } from "node:path";
10
+ function defaultEditorPinPaths() {
11
+ return {
12
+ vscode: getVSCodeUserSettingsPath(),
13
+ cursor: getCursorUserSettingsPath(),
14
+ marker: join(homedir(), ".config", "rulemetric", "editor-proxy-unpinned.json")
15
+ };
16
+ }
17
+ function isOurProxyValue(value, gatewayPort) {
18
+ return value === `http://localhost:${gatewayPort}` || value === `http://127.0.0.1:${gatewayPort}`;
19
+ }
20
+ function readSettings(path) {
21
+ if (!existsSync(path)) return null;
22
+ try {
23
+ return JSON.parse(readFileSync(path, "utf-8"));
24
+ } catch {
25
+ return null;
26
+ }
27
+ }
28
+ function unpinEditorProxies(gatewayPort, paths = defaultEditorPinPaths()) {
29
+ const unpinned = [];
30
+ for (const editor of ["vscode", "cursor"]) {
31
+ const path = paths[editor];
32
+ const settings = readSettings(path);
33
+ if (!settings || !isOurProxyValue(settings["http.proxy"], gatewayPort)) continue;
34
+ delete settings["http.proxy"];
35
+ if (settings["http.proxyStrictSSL"] === false) {
36
+ delete settings["http.proxyStrictSSL"];
37
+ }
38
+ try {
39
+ writeFileSync(path, JSON.stringify(settings, null, 2) + "\n");
40
+ unpinned.push(editor);
41
+ } catch {
42
+ }
43
+ }
44
+ if (unpinned.length > 0) {
45
+ const prior = readSettings(paths.marker);
46
+ const editors = [.../* @__PURE__ */ new Set([...prior?.editors ?? [], ...unpinned])];
47
+ try {
48
+ writeFileSync(
49
+ paths.marker,
50
+ JSON.stringify({ editors, gatewayPort, unpinnedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2) + "\n"
51
+ );
52
+ } catch {
53
+ }
54
+ }
55
+ return unpinned;
56
+ }
57
+ function repinEditorProxies(writePin, paths = defaultEditorPinPaths()) {
58
+ const marker = readSettings(paths.marker);
59
+ if (!marker || !Array.isArray(marker.editors)) return [];
60
+ const repinned = [];
61
+ for (const editor of marker.editors) {
62
+ if (editor !== "vscode" && editor !== "cursor") continue;
63
+ try {
64
+ writePin(editor);
65
+ repinned.push(editor);
66
+ } catch {
67
+ }
68
+ }
69
+ try {
70
+ unlinkSync(paths.marker);
71
+ } catch {
72
+ }
73
+ return repinned;
74
+ }
75
+
76
+ export {
77
+ defaultEditorPinPaths,
78
+ unpinEditorProxies,
79
+ repinEditorProxies
80
+ };
81
+ //# sourceMappingURL=chunk-3S7223UR.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/lib/editor-proxy-pins.ts"],
4
+ "sourcesContent": ["import { existsSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { getVSCodeUserSettingsPath, getCursorUserSettingsPath } from './hooks-config.js';\n\n/**\n * Editor proxy pin lifecycle for a DEAD/unownable gateway.\n *\n * `hooks install` pins `http.proxy` (+ `http.proxyStrictSSL: false`) into\n * VS Code and Cursor USER-level settings so Copilot/Cursor traffic routes\n * through the capture gateway. Those pins outlive the gateway: after a crash\n * that can't self-heal \u2014 most importantly a FOREIGN process owning :8787 \u2014\n * the editors keep routing HTTP at a port we don't control, with strictSSL\n * off. `gateway ensure` (run on every session start) calls `unpin` in that\n * state and `repin` once the gateway is verifiably ours again.\n *\n * Ownership predicate is strict: `http.proxy` is only ever removed when it\n * points at OUR gateway port on localhost \u2014 a user-owned corporate proxy is\n * never touched (same discipline as removeRulemetricProxyEnv). What was\n * unpinned is recorded in a marker file so repin restores exactly that set,\n * and never manufactures a pin the user removed themselves.\n */\n\nexport interface EditorPinPaths {\n vscode: string;\n cursor: string;\n marker: string;\n}\n\nexport function defaultEditorPinPaths(): EditorPinPaths {\n return {\n vscode: getVSCodeUserSettingsPath(),\n cursor: getCursorUserSettingsPath(),\n marker: join(homedir(), '.config', 'rulemetric', 'editor-proxy-unpinned.json'),\n };\n}\n\nfunction isOurProxyValue(value: unknown, gatewayPort: number): boolean {\n return (\n value === `http://localhost:${gatewayPort}` ||\n value === `http://127.0.0.1:${gatewayPort}`\n );\n}\n\nfunction readSettings(path: string): Record<string, unknown> | null {\n if (!existsSync(path)) return null;\n try {\n return JSON.parse(readFileSync(path, 'utf-8')) as Record<string, unknown>;\n } catch {\n return null; // JSONC/corrupt \u2014 never risk clobbering a user's editor config\n }\n}\n\n/**\n * Remove our gateway pin from each editor's user settings. Returns the list\n * of editors actually unpinned and records them in the marker (merging with\n * a prior marker so repeated unpins don't lose state).\n */\nexport function unpinEditorProxies(\n gatewayPort: number,\n paths: EditorPinPaths = defaultEditorPinPaths(),\n): Array<'vscode' | 'cursor'> {\n const unpinned: Array<'vscode' | 'cursor'> = [];\n for (const editor of ['vscode', 'cursor'] as const) {\n const path = paths[editor];\n const settings = readSettings(path);\n if (!settings || !isOurProxyValue(settings['http.proxy'], gatewayPort)) continue;\n delete settings['http.proxy'];\n // strictSSL rides along with our pin; only strip it when the pin was ours\n // (checked above) AND it holds the value we wrote.\n if (settings['http.proxyStrictSSL'] === false) {\n delete settings['http.proxyStrictSSL'];\n }\n try {\n writeFileSync(path, JSON.stringify(settings, null, 2) + '\\n');\n unpinned.push(editor);\n } catch {\n /* editor settings not writable \u2014 leave as-is */\n }\n }\n if (unpinned.length > 0) {\n const prior = readSettings(paths.marker) as { editors?: string[] } | null;\n const editors = [...new Set([...(prior?.editors ?? []), ...unpinned])];\n try {\n writeFileSync(\n paths.marker,\n JSON.stringify({ editors, gatewayPort, unpinnedAt: new Date().toISOString() }, null, 2) + '\\n',\n );\n } catch {\n /* marker write failure only costs a future auto-repin */\n }\n }\n return unpinned;\n}\n\n/**\n * Restore pins for exactly the editors a previous unpin removed (per the\n * marker), then clear the marker. `writePin` is injected by the caller\n * (writeVSCodeUserProxyConfig / writeCursorUserProxyConfig) \u2014 those writers\n * already refuse to overwrite a user-owned non-localhost proxy.\n */\nexport function repinEditorProxies(\n writePin: (editor: 'vscode' | 'cursor') => void,\n paths: EditorPinPaths = defaultEditorPinPaths(),\n): Array<'vscode' | 'cursor'> {\n const marker = readSettings(paths.marker) as { editors?: unknown } | null;\n if (!marker || !Array.isArray(marker.editors)) return [];\n const repinned: Array<'vscode' | 'cursor'> = [];\n for (const editor of marker.editors) {\n if (editor !== 'vscode' && editor !== 'cursor') continue;\n try {\n writePin(editor);\n repinned.push(editor);\n } catch {\n /* best-effort \u2014 a failed repin keeps the marker below */\n }\n }\n try {\n unlinkSync(paths.marker);\n } catch {\n /* already gone */\n }\n return repinned;\n}\n"],
5
+ "mappings": ";;;;;;AAAA,SAAS,YAAY,cAAc,YAAY,qBAAqB;AACpE,SAAS,eAAe;AACxB,SAAS,YAAY;AA2Bd,SAAS,wBAAwC;AACtD,SAAO;AAAA,IACL,QAAQ,0BAA0B;AAAA,IAClC,QAAQ,0BAA0B;AAAA,IAClC,QAAQ,KAAK,QAAQ,GAAG,WAAW,cAAc,4BAA4B;AAAA,EAC/E;AACF;AAEA,SAAS,gBAAgB,OAAgB,aAA8B;AACrE,SACE,UAAU,oBAAoB,WAAW,MACzC,UAAU,oBAAoB,WAAW;AAE7C;AAEA,SAAS,aAAa,MAA8C;AAClE,MAAI,CAAC,WAAW,IAAI,EAAG,QAAO;AAC9B,MAAI;AACF,WAAO,KAAK,MAAM,aAAa,MAAM,OAAO,CAAC;AAAA,EAC/C,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAOO,SAAS,mBACd,aACA,QAAwB,sBAAsB,GAClB;AAC5B,QAAM,WAAuC,CAAC;AAC9C,aAAW,UAAU,CAAC,UAAU,QAAQ,GAAY;AAClD,UAAM,OAAO,MAAM,MAAM;AACzB,UAAM,WAAW,aAAa,IAAI;AAClC,QAAI,CAAC,YAAY,CAAC,gBAAgB,SAAS,YAAY,GAAG,WAAW,EAAG;AACxE,WAAO,SAAS,YAAY;AAG5B,QAAI,SAAS,qBAAqB,MAAM,OAAO;AAC7C,aAAO,SAAS,qBAAqB;AAAA,IACvC;AACA,QAAI;AACF,oBAAc,MAAM,KAAK,UAAU,UAAU,MAAM,CAAC,IAAI,IAAI;AAC5D,eAAS,KAAK,MAAM;AAAA,IACtB,QAAQ;AAAA,IAER;AAAA,EACF;AACA,MAAI,SAAS,SAAS,GAAG;AACvB,UAAM,QAAQ,aAAa,MAAM,MAAM;AACvC,UAAM,UAAU,CAAC,GAAG,oBAAI,IAAI,CAAC,GAAI,OAAO,WAAW,CAAC,GAAI,GAAG,QAAQ,CAAC,CAAC;AACrE,QAAI;AACF;AAAA,QACE,MAAM;AAAA,QACN,KAAK,UAAU,EAAE,SAAS,aAAa,aAAY,oBAAI,KAAK,GAAE,YAAY,EAAE,GAAG,MAAM,CAAC,IAAI;AAAA,MAC5F;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AACA,SAAO;AACT;AAQO,SAAS,mBACd,UACA,QAAwB,sBAAsB,GAClB;AAC5B,QAAM,SAAS,aAAa,MAAM,MAAM;AACxC,MAAI,CAAC,UAAU,CAAC,MAAM,QAAQ,OAAO,OAAO,EAAG,QAAO,CAAC;AACvD,QAAM,WAAuC,CAAC;AAC9C,aAAW,UAAU,OAAO,SAAS;AACnC,QAAI,WAAW,YAAY,WAAW,SAAU;AAChD,QAAI;AACF,eAAS,MAAM;AACf,eAAS,KAAK,MAAM;AAAA,IACtB,QAAQ;AAAA,IAER;AAAA,EACF;AACA,MAAI;AACF,eAAW,MAAM,MAAM;AAAA,EACzB,QAAQ;AAAA,EAER;AACA,SAAO;AACT;",
6
+ "names": []
7
+ }
@@ -1,9 +1,21 @@
1
1
  import {
2
2
  startTerminalTargetHeartbeat
3
3
  } from "./chunk-GIUBARWS.js";
4
+ import {
5
+ processRunCleanup
6
+ } from "./chunk-6NBS5XFS.js";
7
+ import {
8
+ processSendMessage
9
+ } from "./chunk-W53GKIZQ.js";
4
10
  import {
5
11
  processSessionGoal
6
12
  } from "./chunk-YOBA3NAM.js";
13
+ import {
14
+ cronRefreshSkills
15
+ } from "./chunk-RQ2TMLKG.js";
16
+ import {
17
+ cronSuggestInstructions
18
+ } from "./chunk-PMI56ED5.js";
7
19
  import {
8
20
  processAnnouncement
9
21
  } from "./chunk-OO7JDFS4.js";
@@ -12,28 +24,16 @@ import {
12
24
  } from "./chunk-XK42ZBDE.js";
13
25
  import {
14
26
  processEval
15
- } from "./chunk-3LKVS6W5.js";
27
+ } from "./chunk-RMTT4KDY.js";
16
28
  import {
17
29
  processInsights
18
- } from "./chunk-4YAP7RT7.js";
30
+ } from "./chunk-OK3I555A.js";
19
31
  import {
20
32
  processLaunch
21
33
  } from "./chunk-JULOLTZS.js";
22
- import {
23
- processRunCleanup
24
- } from "./chunk-6NBS5XFS.js";
25
- import {
26
- processSendMessage
27
- } from "./chunk-W53GKIZQ.js";
28
34
  import {
29
35
  isPermanentJobError
30
36
  } from "./chunk-DGHWRQXL.js";
31
- import {
32
- cronRefreshSkills
33
- } from "./chunk-RQ2TMLKG.js";
34
- import {
35
- cronSuggestInstructions
36
- } from "./chunk-PMI56ED5.js";
37
37
  import {
38
38
  ApiError,
39
39
  apiGet,
@@ -296,4 +296,4 @@ export {
296
296
  pollForWork,
297
297
  runAgentLoop
298
298
  };
299
- //# sourceMappingURL=chunk-7UDETV3P.js.map
299
+ //# sourceMappingURL=chunk-7DULPSFU.js.map
@@ -1,3 +1,7 @@
1
+ import {
2
+ announceableEntries,
3
+ parseChangelog
4
+ } from "./chunk-E3BIT53W.js";
1
5
  import {
2
6
  renderChangelogAnnouncement
3
7
  } from "./chunk-QSN77T7C.js";
@@ -5,10 +9,6 @@ import {
5
9
  announcements,
6
10
  getDb
7
11
  } from "./chunk-EI5BHWXA.js";
8
- import {
9
- announceableEntries,
10
- parseChangelog
11
- } from "./chunk-E3BIT53W.js";
12
12
 
13
13
  // src/lib/handlers/process-changelog.ts
14
14
  import * as fs from "node:fs/promises";
@@ -119,4 +119,4 @@ async function processChangelog(deps = {}) {
119
119
  export {
120
120
  processChangelog
121
121
  };
122
- //# sourceMappingURL=chunk-HZK43UCF.js.map
122
+ //# sourceMappingURL=chunk-DUJSRBLP.js.map
@@ -0,0 +1,22 @@
1
+ // src/lib/capture-hosts.ts
2
+ import { readFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ function buildAllowHostsArgs(addonDir, env = process.env) {
5
+ if (env.RULEMETRIC_PROXY_ALL_HOSTS === "1") return [];
6
+ let patterns;
7
+ try {
8
+ patterns = JSON.parse(readFileSync(join(addonDir, "allow_hosts.json"), "utf-8")).allow;
9
+ } catch {
10
+ return [];
11
+ }
12
+ if (!Array.isArray(patterns) || patterns.length === 0) return [];
13
+ const valid = patterns.filter((p) => typeof p === "string" && p.length > 0);
14
+ if (valid.length === 0) return [];
15
+ const extra = (env.RULEMETRIC_PROXY_EXTRA_HOSTS ?? "").split(",").map((s) => s.trim()).filter(Boolean);
16
+ return [...valid, ...extra].flatMap((p) => ["--allow-hosts", p]);
17
+ }
18
+
19
+ export {
20
+ buildAllowHostsArgs
21
+ };
22
+ //# sourceMappingURL=chunk-EARWG4EV.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/lib/capture-hosts.ts"],
4
+ "sourcesContent": ["import { readFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\n/**\n * Build the `--allow-hosts` args that scope mitmproxy interception to the\n * hosts the addon can actually capture (LLM provider APIs). Everything else\n * is TUNNELED untouched \u2014 no TLS interception, no CA involvement \u2014 so\n * bundled-CA tools (pip/certifi, gcloud, node without NODE_EXTRA_CA_CERTS)\n * keep working while the machine-wide HTTPS_PROXY is pinned.\n *\n * The canonical pattern list lives in the addon package\n * (`addon/allow_hosts.json`) so the pytest sync-guard there can enforce it\n * covers every `detect_provider()` host \u2014 the CLI only transports it.\n *\n * Escape hatches:\n * RULEMETRIC_PROXY_ALL_HOSTS=1 \u2192 intercept everything (required for the\n * generic OpenAI-compatible catch-all on arbitrary self-hosted domains)\n * RULEMETRIC_PROXY_EXTRA_HOSTS=a,b \u2192 extra host:port regexes appended\n *\n * Failure mode: a missing/corrupt manifest returns [] (intercept everything,\n * the pre-scoping behavior). Tunneling-by-default on a broken read would\n * silently kill ALL capture \u2014 strictly worse than the old blast radius.\n */\nexport function buildAllowHostsArgs(\n addonDir: string,\n env: NodeJS.ProcessEnv = process.env,\n): string[] {\n if (env.RULEMETRIC_PROXY_ALL_HOSTS === '1') return [];\n\n let patterns: unknown;\n try {\n patterns = (\n JSON.parse(readFileSync(join(addonDir, 'allow_hosts.json'), 'utf-8')) as {\n allow?: unknown;\n }\n ).allow;\n } catch {\n return [];\n }\n if (!Array.isArray(patterns) || patterns.length === 0) return [];\n const valid = patterns.filter((p): p is string => typeof p === 'string' && p.length > 0);\n if (valid.length === 0) return [];\n\n const extra = (env.RULEMETRIC_PROXY_EXTRA_HOSTS ?? '')\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean);\n\n return [...valid, ...extra].flatMap((p) => ['--allow-hosts', p]);\n}\n"],
5
+ "mappings": ";AAAA,SAAS,oBAAoB;AAC7B,SAAS,YAAY;AAsBd,SAAS,oBACd,UACA,MAAyB,QAAQ,KACvB;AACV,MAAI,IAAI,+BAA+B,IAAK,QAAO,CAAC;AAEpD,MAAI;AACJ,MAAI;AACF,eACE,KAAK,MAAM,aAAa,KAAK,UAAU,kBAAkB,GAAG,OAAO,CAAC,EAGpE;AAAA,EACJ,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACA,MAAI,CAAC,MAAM,QAAQ,QAAQ,KAAK,SAAS,WAAW,EAAG,QAAO,CAAC;AAC/D,QAAM,QAAQ,SAAS,OAAO,CAAC,MAAmB,OAAO,MAAM,YAAY,EAAE,SAAS,CAAC;AACvF,MAAI,MAAM,WAAW,EAAG,QAAO,CAAC;AAEhC,QAAM,SAAS,IAAI,gCAAgC,IAChD,MAAM,GAAG,EACT,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EACnB,OAAO,OAAO;AAEjB,SAAO,CAAC,GAAG,OAAO,GAAG,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC;AACjE;",
6
+ "names": []
7
+ }
@@ -609,8 +609,10 @@ export {
609
609
  removeCopilotHooks,
610
610
  installMacOSProxyEnv,
611
611
  uninstallMacOSProxyEnv,
612
+ getVSCodeUserSettingsPath,
612
613
  writeVSCodeUserProxyConfig,
613
614
  removeVSCodeUserProxyConfig,
615
+ getCursorUserSettingsPath,
614
616
  writeCursorUserProxyConfig,
615
617
  removeCursorUserProxyConfig,
616
618
  generateAntigravityHooksConfig,
@@ -619,4 +621,4 @@ export {
619
621
  removeAntigravityHooks,
620
622
  removeAntigravityUserHooks
621
623
  };
622
- //# sourceMappingURL=chunk-4KRMZLQV.js.map
624
+ //# sourceMappingURL=chunk-LXKPNATC.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
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';\nimport { removeStatuslineShimAnyHome } from './statusline-shim.js';\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// Machine-local Claude Code settings (settings.local.json) + shared-file healing\n// ---------------------------------------------------------------------------\n\n/** The NO_PROXY value `hooks install` writes alongside HTTPS_PROXY. */\nexport const RULEMETRIC_NO_PROXY = 'localhost,127.0.0.1,*.supabase.co,*.supabase.in';\n\n/**\n * The settings file `hooks install` owns for RuleMetric's machine-local state.\n * Project installs write `settings.local.json`: the shared `settings.json` is\n * typically checked into git, and everything we write is machine-local by\n * nature \u2014 hook commands carry a per-user PATH prefix, and the proxy env pins\n * HTTPS_PROXY + a per-user CA path that BREAK Claude Code's HTTPS on any clone\n * without a live gateway on that port. Claude Code merges settings.local.json\n * on top of settings.json, runs hooks from both, and auto-gitignores the local\n * file. Global installs keep writing `~/.claude/settings.json` \u2014 the home file\n * is not in a repo, so machine-local state is safe there.\n */\nexport function claudeSettingsInstallPath(configDir: string, isGlobal: boolean): string {\n return join(configDir, isGlobal ? 'settings.json' : 'settings.local.json');\n}\n\n/**\n * Remove the gateway proxy env keys `hooks install` writes, and ONLY those:\n * each key is deleted solely when its value is one RuleMetric itself produces,\n * so a user-owned corporate proxy / custom CA is never touched.\n * - HTTPS_PROXY: points at the local gateway port (localhost or 127.0.0.1)\n * - NO_PROXY: exactly RULEMETRIC_NO_PROXY\n * - NODE_EXTRA_CA_CERTS: the mitmproxy default CA path \u2014 suffix-matched, not\n * exact, because a shared file poisoned by a teammate's install carries\n * THEIR $HOME prefix\n * Drops the `env` block entirely when the removal empties it. Returns true iff\n * anything was removed.\n */\nexport function removeRulemetricProxyEnv(\n settings: Record<string, unknown>,\n gatewayPort: number,\n): boolean {\n const env = settings.env as Record<string, unknown> | undefined;\n if (!env || typeof env !== 'object') return false;\n let removed = false;\n const proxy = env.HTTPS_PROXY;\n if (proxy === `http://localhost:${gatewayPort}` || proxy === `http://127.0.0.1:${gatewayPort}`) {\n delete env.HTTPS_PROXY;\n removed = true;\n }\n if (env.NO_PROXY === RULEMETRIC_NO_PROXY) {\n delete env.NO_PROXY;\n removed = true;\n }\n const ca = env.NODE_EXTRA_CA_CERTS;\n if (typeof ca === 'string' && ca.includes('.mitmproxy') && ca.endsWith('mitmproxy-ca-cert.pem')) {\n delete env.NODE_EXTRA_CA_CERTS;\n removed = true;\n }\n if (removed && Object.keys(env).length === 0) delete settings.env;\n return removed;\n}\n\nexport interface RulemetricStateRemoval {\n removedHooks: boolean;\n removedProxyEnv: boolean;\n removedStatusline: boolean;\n /** True iff any of the three categories removed something. */\n removed: boolean;\n}\n\n/**\n * Strip ALL RuleMetric-owned machine-local state from an in-memory settings\n * object: session hooks, gateway proxy env keys, and the statusline shim. Each\n * remover has its own user-preserving predicate, so anything the user owns\n * survives untouched. Shared by uninstall and the shared-file migration.\n */\nexport function removeRulemetricMachineState(\n settings: Record<string, unknown>,\n gatewayPort: number,\n): RulemetricStateRemoval {\n const removedHooks = removeClaudeCodeHooks(settings);\n const removedProxyEnv = removeRulemetricProxyEnv(settings, gatewayPort);\n const removedStatusline = removeStatuslineShimAnyHome(settings);\n return {\n removedHooks,\n removedProxyEnv,\n removedStatusline,\n removed: removedHooks || removedProxyEnv || removedStatusline,\n };\n}\n\nexport interface SharedSettingsMigration extends RulemetricStateRemoval {\n /** True iff something was removed and the file was rewritten. */\n migrated: boolean;\n backupPath: string | null;\n}\n\n/**\n * Heal a shared `.claude/settings.json` that an older `hooks install` (this\n * machine's or a teammate's) filled with machine-local state: PATH-prefixed,\n * username-leaking hook commands, the gateway proxy env keys (an HTTPS_PROXY\n * pin breaks Claude Code's HTTPS entirely on any clone without a live gateway\n * on that port), and the $HOME-anchored statusline shim. User-owned hooks/env/\n * statusLine are never touched. The file is rewritten (with the standard\n * backup) ONLY when something was actually removed; an unparseable file is\n * left exactly as-is \u2014 we cannot safely edit what we cannot parse.\n */\nexport function migrateSharedClaudeSettings(\n sharedPath: string,\n gatewayPort: number,\n): SharedSettingsMigration {\n const none: SharedSettingsMigration = {\n migrated: false,\n backupPath: null,\n removedHooks: false,\n removedProxyEnv: false,\n removedStatusline: false,\n removed: false,\n };\n if (!existsSync(sharedPath)) return none;\n let settings: Record<string, unknown>;\n try {\n settings = JSON.parse(readFileSync(sharedPath, 'utf-8')) as Record<string, unknown>;\n } catch {\n return none;\n }\n const removal = removeRulemetricMachineState(settings, gatewayPort);\n if (!removal.removed) return none;\n const { backupPath } = writeClaudeSettingsWithBackup(sharedPath, settings);\n return { ...removal, migrated: true, backupPath };\n}\n\nexport interface ClaudeSettingsCleanup extends RulemetricStateRemoval {\n configPath: string;\n /** `settings.json` or `settings.local.json` \u2014 for log lines. */\n fileName: string;\n recoveredBackup: string | null;\n /** True iff the file was rewritten. */\n changed: boolean;\n}\n\n/**\n * Remove all RuleMetric-owned state from BOTH Claude Code settings files in a\n * project: the shared `settings.json` (legacy installs wrote there) and\n * `settings.local.json` (where current installs put machine-local state). Each\n * file is rewritten (with backup) only when something was actually removed.\n * Returns one entry per file that exists, with per-category flags so the\n * caller can log precisely what was cleaned where.\n */\nexport function cleanClaudeSettingsFiles(\n claudeDir: string,\n gatewayPort: number,\n): ClaudeSettingsCleanup[] {\n const results: ClaudeSettingsCleanup[] = [];\n for (const fileName of ['settings.json', 'settings.local.json'] as const) {\n const configPath = join(claudeDir, fileName);\n if (!existsSync(configPath)) continue;\n const { settings, recoveredBackup } = readClaudeSettings(configPath);\n const removal = removeRulemetricMachineState(settings, gatewayPort);\n if (removal.removed) {\n writeClaudeSettingsWithBackup(configPath, settings);\n }\n results.push({\n ...removal,\n configPath,\n fileName,\n recoveredBackup,\n changed: removal.removed,\n });\n }\n return results;\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 const isRulemetricHook = (h: unknown): boolean =>\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 for (const [event, hooks] of Object.entries(generated.hooks)) {\n const current = merged[event] ?? [];\n // REPLACE prior rulemetric entries, never skip-if-present: a stale command\n // (old format, moved binary, outdated PATH prefix) must be repairable by\n // re-running `hooks install` \u2014 skip-if-present froze broken Cursor capture\n // forever. Sibling (non-rulemetric) hooks are preserved untouched.\n const siblings = current.filter((h: unknown) => !isRulemetricHook(h));\n merged[event] = [...siblings, ...hooks];\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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",
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';\nimport { removeStatuslineShimAnyHome } from './statusline-shim.js';\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// Machine-local Claude Code settings (settings.local.json) + shared-file healing\n// ---------------------------------------------------------------------------\n\n/** The NO_PROXY value `hooks install` writes alongside HTTPS_PROXY. */\nexport const RULEMETRIC_NO_PROXY = 'localhost,127.0.0.1,*.supabase.co,*.supabase.in';\n\n/**\n * The settings file `hooks install` owns for RuleMetric's machine-local state.\n * Project installs write `settings.local.json`: the shared `settings.json` is\n * typically checked into git, and everything we write is machine-local by\n * nature \u2014 hook commands carry a per-user PATH prefix, and the proxy env pins\n * HTTPS_PROXY + a per-user CA path that BREAK Claude Code's HTTPS on any clone\n * without a live gateway on that port. Claude Code merges settings.local.json\n * on top of settings.json, runs hooks from both, and auto-gitignores the local\n * file. Global installs keep writing `~/.claude/settings.json` \u2014 the home file\n * is not in a repo, so machine-local state is safe there.\n */\nexport function claudeSettingsInstallPath(configDir: string, isGlobal: boolean): string {\n return join(configDir, isGlobal ? 'settings.json' : 'settings.local.json');\n}\n\n/**\n * Remove the gateway proxy env keys `hooks install` writes, and ONLY those:\n * each key is deleted solely when its value is one RuleMetric itself produces,\n * so a user-owned corporate proxy / custom CA is never touched.\n * - HTTPS_PROXY: points at the local gateway port (localhost or 127.0.0.1)\n * - NO_PROXY: exactly RULEMETRIC_NO_PROXY\n * - NODE_EXTRA_CA_CERTS: the mitmproxy default CA path \u2014 suffix-matched, not\n * exact, because a shared file poisoned by a teammate's install carries\n * THEIR $HOME prefix\n * Drops the `env` block entirely when the removal empties it. Returns true iff\n * anything was removed.\n */\nexport function removeRulemetricProxyEnv(\n settings: Record<string, unknown>,\n gatewayPort: number,\n): boolean {\n const env = settings.env as Record<string, unknown> | undefined;\n if (!env || typeof env !== 'object') return false;\n let removed = false;\n const proxy = env.HTTPS_PROXY;\n if (proxy === `http://localhost:${gatewayPort}` || proxy === `http://127.0.0.1:${gatewayPort}`) {\n delete env.HTTPS_PROXY;\n removed = true;\n }\n if (env.NO_PROXY === RULEMETRIC_NO_PROXY) {\n delete env.NO_PROXY;\n removed = true;\n }\n const ca = env.NODE_EXTRA_CA_CERTS;\n if (typeof ca === 'string' && ca.includes('.mitmproxy') && ca.endsWith('mitmproxy-ca-cert.pem')) {\n delete env.NODE_EXTRA_CA_CERTS;\n removed = true;\n }\n if (removed && Object.keys(env).length === 0) delete settings.env;\n return removed;\n}\n\nexport interface RulemetricStateRemoval {\n removedHooks: boolean;\n removedProxyEnv: boolean;\n removedStatusline: boolean;\n /** True iff any of the three categories removed something. */\n removed: boolean;\n}\n\n/**\n * Strip ALL RuleMetric-owned machine-local state from an in-memory settings\n * object: session hooks, gateway proxy env keys, and the statusline shim. Each\n * remover has its own user-preserving predicate, so anything the user owns\n * survives untouched. Shared by uninstall and the shared-file migration.\n */\nexport function removeRulemetricMachineState(\n settings: Record<string, unknown>,\n gatewayPort: number,\n): RulemetricStateRemoval {\n const removedHooks = removeClaudeCodeHooks(settings);\n const removedProxyEnv = removeRulemetricProxyEnv(settings, gatewayPort);\n const removedStatusline = removeStatuslineShimAnyHome(settings);\n return {\n removedHooks,\n removedProxyEnv,\n removedStatusline,\n removed: removedHooks || removedProxyEnv || removedStatusline,\n };\n}\n\nexport interface SharedSettingsMigration extends RulemetricStateRemoval {\n /** True iff something was removed and the file was rewritten. */\n migrated: boolean;\n backupPath: string | null;\n}\n\n/**\n * Heal a shared `.claude/settings.json` that an older `hooks install` (this\n * machine's or a teammate's) filled with machine-local state: PATH-prefixed,\n * username-leaking hook commands, the gateway proxy env keys (an HTTPS_PROXY\n * pin breaks Claude Code's HTTPS entirely on any clone without a live gateway\n * on that port), and the $HOME-anchored statusline shim. User-owned hooks/env/\n * statusLine are never touched. The file is rewritten (with the standard\n * backup) ONLY when something was actually removed; an unparseable file is\n * left exactly as-is \u2014 we cannot safely edit what we cannot parse.\n */\nexport function migrateSharedClaudeSettings(\n sharedPath: string,\n gatewayPort: number,\n): SharedSettingsMigration {\n const none: SharedSettingsMigration = {\n migrated: false,\n backupPath: null,\n removedHooks: false,\n removedProxyEnv: false,\n removedStatusline: false,\n removed: false,\n };\n if (!existsSync(sharedPath)) return none;\n let settings: Record<string, unknown>;\n try {\n settings = JSON.parse(readFileSync(sharedPath, 'utf-8')) as Record<string, unknown>;\n } catch {\n return none;\n }\n const removal = removeRulemetricMachineState(settings, gatewayPort);\n if (!removal.removed) return none;\n const { backupPath } = writeClaudeSettingsWithBackup(sharedPath, settings);\n return { ...removal, migrated: true, backupPath };\n}\n\nexport interface ClaudeSettingsCleanup extends RulemetricStateRemoval {\n configPath: string;\n /** `settings.json` or `settings.local.json` \u2014 for log lines. */\n fileName: string;\n recoveredBackup: string | null;\n /** True iff the file was rewritten. */\n changed: boolean;\n}\n\n/**\n * Remove all RuleMetric-owned state from BOTH Claude Code settings files in a\n * project: the shared `settings.json` (legacy installs wrote there) and\n * `settings.local.json` (where current installs put machine-local state). Each\n * file is rewritten (with backup) only when something was actually removed.\n * Returns one entry per file that exists, with per-category flags so the\n * caller can log precisely what was cleaned where.\n */\nexport function cleanClaudeSettingsFiles(\n claudeDir: string,\n gatewayPort: number,\n): ClaudeSettingsCleanup[] {\n const results: ClaudeSettingsCleanup[] = [];\n for (const fileName of ['settings.json', 'settings.local.json'] as const) {\n const configPath = join(claudeDir, fileName);\n if (!existsSync(configPath)) continue;\n const { settings, recoveredBackup } = readClaudeSettings(configPath);\n const removal = removeRulemetricMachineState(settings, gatewayPort);\n if (removal.removed) {\n writeClaudeSettingsWithBackup(configPath, settings);\n }\n results.push({\n ...removal,\n configPath,\n fileName,\n recoveredBackup,\n changed: removal.removed,\n });\n }\n return results;\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 const isRulemetricHook = (h: unknown): boolean =>\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 for (const [event, hooks] of Object.entries(generated.hooks)) {\n const current = merged[event] ?? [];\n // REPLACE prior rulemetric entries, never skip-if-present: a stale command\n // (old format, moved binary, outdated PATH prefix) must be repairable by\n // re-running `hooks install` \u2014 skip-if-present froze broken Cursor capture\n // forever. Sibling (non-rulemetric) hooks are preserved untouched.\n const siblings = current.filter((h: unknown) => !isRulemetricHook(h));\n merged[event] = [...siblings, ...hooks];\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\nexport function 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\nexport function 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
6
  "names": []
7
7
  }
@@ -3,14 +3,14 @@ import {
3
3
  FACET_GENERATION_PROMPT,
4
4
  RECOMMENDATION_PROMPTS
5
5
  } from "./chunk-XZXS2W24.js";
6
- import {
7
- PermanentJobError
8
- } from "./chunk-DGHWRQXL.js";
9
6
  import {
10
7
  detectLlmBackend,
11
8
  runLlmPrompt,
12
9
  runToolPrompt
13
10
  } from "./chunk-FZKLLNDS.js";
11
+ import {
12
+ PermanentJobError
13
+ } from "./chunk-DGHWRQXL.js";
14
14
  import {
15
15
  apiGet,
16
16
  apiPatch,
@@ -240,4 +240,4 @@ async function buildDataSummaryFromApi(projectPath, projectId) {
240
240
  export {
241
241
  processInsights
242
242
  };
243
- //# sourceMappingURL=chunk-4YAP7RT7.js.map
243
+ //# sourceMappingURL=chunk-OK3I555A.js.map
@@ -4,7 +4,7 @@ import {
4
4
  hasRulemetricClaudeHooks,
5
5
  mergeClaudeCodeHooks,
6
6
  removeRulemetricMachineState
7
- } from "./chunk-4KRMZLQV.js";
7
+ } from "./chunk-LXKPNATC.js";
8
8
  import {
9
9
  GATEWAY_PORT
10
10
  } from "./chunk-4LYCDS2N.js";
@@ -411,4 +411,4 @@ export {
411
411
  isStepId,
412
412
  computeStatus
413
413
  };
414
- //# sourceMappingURL=chunk-POFTPB33.js.map
414
+ //# sourceMappingURL=chunk-OKWCKCPE.js.map
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  PermanentJobError
3
3
  } from "./chunk-DGHWRQXL.js";
4
- import {
5
- createEvalWorktree
6
- } from "./chunk-52WIYRZH.js";
7
4
  import {
8
5
  judgeEvalQuality
9
6
  } from "./chunk-J7N3DLH6.js";
7
+ import {
8
+ createEvalWorktree
9
+ } from "./chunk-52WIYRZH.js";
10
10
  import {
11
11
  gradeEvalOutput
12
12
  } from "./chunk-YNIH3KMU.js";
@@ -188,4 +188,4 @@ function buildSummary(results) {
188
188
  export {
189
189
  processEval
190
190
  };
191
- //# sourceMappingURL=chunk-3LKVS6W5.js.map
191
+ //# sourceMappingURL=chunk-RMTT4KDY.js.map
@@ -1,29 +1,29 @@
1
1
  import {
2
2
  runAgentLoop
3
- } from "../../chunk-7UDETV3P.js";
3
+ } from "../../chunk-7DULPSFU.js";
4
4
  import "../../chunk-GIUBARWS.js";
5
+ import "../../chunk-6NBS5XFS.js";
6
+ import "../../chunk-W53GKIZQ.js";
5
7
  import "../../chunk-YOBA3NAM.js";
8
+ import "../../chunk-RQ2TMLKG.js";
9
+ import "../../chunk-PMI56ED5.js";
6
10
  import "../../chunk-OO7JDFS4.js";
11
+ import "../../chunk-E3BIT53W.js";
7
12
  import "../../chunk-QSN77T7C.js";
8
- import "../../chunk-XK42ZBDE.js";
9
- import "../../chunk-3LKVS6W5.js";
10
- import "../../chunk-4YAP7RT7.js";
11
- import "../../chunk-XZXS2W24.js";
12
- import "../../chunk-JULOLTZS.js";
13
- import "../../chunk-6NBS5XFS.js";
14
13
  import {
15
14
  closeDb
16
15
  } from "../../chunk-EI5BHWXA.js";
16
+ import "../../chunk-XK42ZBDE.js";
17
+ import "../../chunk-RMTT4KDY.js";
18
+ import "../../chunk-OK3I555A.js";
19
+ import "../../chunk-XZXS2W24.js";
20
+ import "../../chunk-FZKLLNDS.js";
21
+ import "../../chunk-JULOLTZS.js";
17
22
  import "../../chunk-Y4BJXXYV.js";
18
- import "../../chunk-W53GKIZQ.js";
19
23
  import "../../chunk-DGHWRQXL.js";
24
+ import "../../chunk-J7N3DLH6.js";
20
25
  import "../../chunk-52WIYRZH.js";
21
- import "../../chunk-RQ2TMLKG.js";
22
- import "../../chunk-PMI56ED5.js";
23
- import "../../chunk-E3BIT53W.js";
24
- import "../../chunk-FZKLLNDS.js";
25
26
  import "../../chunk-KJKBDQ4D.js";
26
- import "../../chunk-J7N3DLH6.js";
27
27
  import "../../chunk-OQSQC7VB.js";
28
28
  import "../../chunk-EKP32DLN.js";
29
29
  import "../../chunk-F5N6CLJJ.js";
@@ -1,3 +1,12 @@
1
+ import {
2
+ repinEditorProxies,
3
+ unpinEditorProxies
4
+ } from "../../chunk-3S7223UR.js";
5
+ import {
6
+ writeCursorUserProxyConfig,
7
+ writeVSCodeUserProxyConfig
8
+ } from "../../chunk-LXKPNATC.js";
9
+ import "../../chunk-OQBBVTDG.js";
1
10
  import {
2
11
  GATEWAY_PORT,
3
12
  isGatewayRunning,
@@ -38,12 +47,20 @@ var GatewayEnsure = class _GatewayEnsure extends BaseCommand {
38
47
  this.maybeRestartStaleProxy();
39
48
  const identity = await isRulemetricGatewayListening(GATEWAY_PORT);
40
49
  if (identity.status === "ours-live") {
50
+ const repinned = repinEditorProxies((editor) => {
51
+ if (editor === "vscode") writeVSCodeUserProxyConfig(GATEWAY_PORT);
52
+ else writeCursorUserProxyConfig(GATEWAY_PORT);
53
+ });
54
+ if (repinned.length > 0) {
55
+ console.error(`[RuleMetric] Gateway healthy again \u2014 restored ${repinned.join(", ")} proxy settings.`);
56
+ }
41
57
  return;
42
58
  }
43
59
  if (identity.status === "foreign") {
44
60
  if (isGatewayRunning()) return;
61
+ const unpinned = unpinEditorProxies(GATEWAY_PORT);
45
62
  console.error(
46
- `[RuleMetric] A non-RuleMetric process holds :${GATEWAY_PORT} \u2014 cannot start gateway. Run \`rulemetric diagnostics\` if Claude Code HTTPS requests fail.`
63
+ `[RuleMetric] A non-RuleMetric process holds :${GATEWAY_PORT} \u2014 cannot start gateway. ` + (unpinned.length > 0 ? `Unpinned ${unpinned.join(", ")} proxy settings so editors are not routed through it. ` : "") + "Run `rulemetric diagnostics` if Claude Code HTTPS requests fail."
47
64
  );
48
65
  return;
49
66
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/commands/gateway/ensure.ts"],
4
- "sourcesContent": ["import { spawn } from 'node:child_process';\nimport { existsSync, readFileSync, statSync, writeFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { BaseCommand } from '../../base-command.js';\nimport {\n isGatewayRunning,\n isRulemetricGatewayListening,\n spawnGateway,\n GATEWAY_PORT,\n} from '../../lib/gateway-lifecycle.js';\nimport { isPidLiveAndMatching, MITMPROXY_COMMAND_PATTERN } from '../../lib/pid-identity.js';\n\nconst CONFIG_DIR = join(homedir(), '.config', 'rulemetric');\nconst ADDON_VERSION_FILE = join(CONFIG_DIR, 'proxy-addon.version');\nconst PROXY_PID_FILE = join(CONFIG_DIR, 'proxy.pid');\nconst DRIFT_RESTART_MARKER = join(CONFIG_DIR, 'proxy-drift-restart.at');\nconst DRIFT_RESTART_COOLDOWN_MS = 10 * 60_000;\n\nexport default class GatewayEnsure extends BaseCommand {\n static override description = 'Ensure the gateway proxy is running (called by hooks, not for direct use)';\n static override hidden = true;\n\n async run(): Promise<void> {\n await this.parse(GatewayEnsure);\n\n // Addon staleness: the gateway\u2194addon version-drift restart is CIRCULAR \u2014\n // both sides compare versions frozen at their own spawn, so after\n // `npm i -g` upgrades the CLI, a healthy old proxy keeps running old addon\n // code forever (published capture fixes never reach the machine). THIS\n // process is exec'd fresh by every session-start hook, so its\n // config.version is always the on-disk truth \u2014 making it the one component\n // that can detect the drift. Debounced, detached, never blocks the hook.\n this.maybeRestartStaleProxy();\n\n // Port truth first: the identity handshake distinguishes our live gateway\n // from a foreign process squatting on :8787. Spawning against a held port\n // just produces an EADDRINUSE child; spawning is only useful on 'dead'.\n const identity = await isRulemetricGatewayListening(GATEWAY_PORT);\n\n if (identity.status === 'ours-live') {\n return; // Already running, silent success\n }\n\n if (identity.status === 'foreign') {\n // A gateway from an older CLI build answers TCP but not the handshake;\n // the PID-identity check on the pidfile confirms it's ours \u2014 leave it be.\n if (isGatewayRunning()) return;\n // Genuinely foreign: warn (don't crash the hook) \u2014 HTTPS_PROXY may be\n // pinned at a port we don't own, which breaks Claude Code's HTTPS.\n console.error(\n `[RuleMetric] A non-RuleMetric process holds :${GATEWAY_PORT} \u2014 cannot start gateway. ` +\n 'Run `rulemetric diagnostics` if Claude Code HTTPS requests fail.',\n );\n return;\n }\n\n try {\n spawnGateway(this.config.version);\n } catch (err) {\n // Don't crash the hook \u2014 just warn\n console.error(`[RuleMetric] Could not start gateway: ${(err as Error).message}`);\n }\n }\n\n private maybeRestartStaleProxy(): void {\n try {\n if (!existsSync(ADDON_VERSION_FILE)) return;\n const runningVersion = readFileSync(ADDON_VERSION_FILE, 'utf-8').trim();\n if (!runningVersion || runningVersion === this.config.version) return;\n\n // Only restart a proxy that is actually OURS and alive \u2014 a stale version\n // file next to a dead (or docker-marked) proxy is proxy start's problem.\n const pidRaw = readFileSync(PROXY_PID_FILE, 'utf-8').trim();\n const pid = Number(pidRaw);\n if (!Number.isFinite(pid) || !isPidLiveAndMatching(pid, MITMPROXY_COMMAND_PATTERN)) {\n return;\n }\n\n // Cooldown: session starts can cluster, and `proxy restart` needs time to\n // rewrite the version file before the drift signal clears.\n try {\n const age = Date.now() - statSync(DRIFT_RESTART_MARKER).mtimeMs;\n if (age < DRIFT_RESTART_COOLDOWN_MS) return;\n } catch {\n /* no marker yet \u2014 proceed */\n }\n writeFileSync(DRIFT_RESTART_MARKER, new Date().toISOString());\n\n // process.argv[1] is this CLI's entry script (bin/run.js \u2014 dev or global\n // install alike), so the restarted proxy runs the NEW on-disk code and\n // re-stamps the version file, converging the drift.\n const child = spawn(process.execPath, [process.argv[1], 'proxy', 'restart'], {\n detached: true,\n stdio: 'ignore',\n });\n child.unref();\n console.error(\n `[RuleMetric] Capture proxy is running CLI ${runningVersion} but ${this.config.version} is installed \u2014 restarting it in the background.`,\n );\n } catch {\n /* never break the session-start hook */\n }\n }\n}\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;;;;;;AAAA,SAAS,aAAa;AACtB,SAAS,YAAY,cAAc,UAAU,qBAAqB;AAClE,SAAS,eAAe;AACxB,SAAS,YAAY;AAUrB,IAAM,aAAa,KAAK,QAAQ,GAAG,WAAW,YAAY;AAC1D,IAAM,qBAAqB,KAAK,YAAY,qBAAqB;AACjE,IAAM,iBAAiB,KAAK,YAAY,WAAW;AACnD,IAAM,uBAAuB,KAAK,YAAY,wBAAwB;AACtE,IAAM,4BAA4B,KAAK;AAEvC,IAAqB,gBAArB,MAAqB,uBAAsB,YAAY;AAAA,EACrD,OAAgB,cAAc;AAAA,EAC9B,OAAgB,SAAS;AAAA,EAEzB,MAAM,MAAqB;AACzB,UAAM,KAAK,MAAM,cAAa;AAS9B,SAAK,uBAAuB;AAK5B,UAAM,WAAW,MAAM,6BAA6B,YAAY;AAEhE,QAAI,SAAS,WAAW,aAAa;AACnC;AAAA,IACF;AAEA,QAAI,SAAS,WAAW,WAAW;AAGjC,UAAI,iBAAiB,EAAG;AAGxB,cAAQ;AAAA,QACN,gDAAgD,YAAY;AAAA,MAE9D;AACA;AAAA,IACF;AAEA,QAAI;AACF,mBAAa,KAAK,OAAO,OAAO;AAAA,IAClC,SAAS,KAAK;AAEZ,cAAQ,MAAM,yCAA0C,IAAc,OAAO,EAAE;AAAA,IACjF;AAAA,EACF;AAAA,EAEQ,yBAA+B;AACrC,QAAI;AACF,UAAI,CAAC,WAAW,kBAAkB,EAAG;AACrC,YAAM,iBAAiB,aAAa,oBAAoB,OAAO,EAAE,KAAK;AACtE,UAAI,CAAC,kBAAkB,mBAAmB,KAAK,OAAO,QAAS;AAI/D,YAAM,SAAS,aAAa,gBAAgB,OAAO,EAAE,KAAK;AAC1D,YAAM,MAAM,OAAO,MAAM;AACzB,UAAI,CAAC,OAAO,SAAS,GAAG,KAAK,CAAC,qBAAqB,KAAK,yBAAyB,GAAG;AAClF;AAAA,MACF;AAIA,UAAI;AACF,cAAM,MAAM,KAAK,IAAI,IAAI,SAAS,oBAAoB,EAAE;AACxD,YAAI,MAAM,0BAA2B;AAAA,MACvC,QAAQ;AAAA,MAER;AACA,oBAAc,uBAAsB,oBAAI,KAAK,GAAE,YAAY,CAAC;AAK5D,YAAM,QAAQ,MAAM,QAAQ,UAAU,CAAC,QAAQ,KAAK,CAAC,GAAG,SAAS,SAAS,GAAG;AAAA,QAC3E,UAAU;AAAA,QACV,OAAO;AAAA,MACT,CAAC;AACD,YAAM,MAAM;AACZ,cAAQ;AAAA,QACN,6CAA6C,cAAc,QAAQ,KAAK,OAAO,OAAO;AAAA,MACxF;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AACF;",
4
+ "sourcesContent": ["import { spawn } from 'node:child_process';\nimport { existsSync, readFileSync, statSync, writeFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { BaseCommand } from '../../base-command.js';\nimport {\n isGatewayRunning,\n isRulemetricGatewayListening,\n spawnGateway,\n GATEWAY_PORT,\n} from '../../lib/gateway-lifecycle.js';\nimport { isPidLiveAndMatching, MITMPROXY_COMMAND_PATTERN } from '../../lib/pid-identity.js';\nimport { unpinEditorProxies, repinEditorProxies } from '../../lib/editor-proxy-pins.js';\nimport { writeVSCodeUserProxyConfig, writeCursorUserProxyConfig } from '../../lib/hooks-config.js';\n\nconst CONFIG_DIR = join(homedir(), '.config', 'rulemetric');\nconst ADDON_VERSION_FILE = join(CONFIG_DIR, 'proxy-addon.version');\nconst PROXY_PID_FILE = join(CONFIG_DIR, 'proxy.pid');\nconst DRIFT_RESTART_MARKER = join(CONFIG_DIR, 'proxy-drift-restart.at');\nconst DRIFT_RESTART_COOLDOWN_MS = 10 * 60_000;\n\nexport default class GatewayEnsure extends BaseCommand {\n static override description = 'Ensure the gateway proxy is running (called by hooks, not for direct use)';\n static override hidden = true;\n\n async run(): Promise<void> {\n await this.parse(GatewayEnsure);\n\n // Addon staleness: the gateway\u2194addon version-drift restart is CIRCULAR \u2014\n // both sides compare versions frozen at their own spawn, so after\n // `npm i -g` upgrades the CLI, a healthy old proxy keeps running old addon\n // code forever (published capture fixes never reach the machine). THIS\n // process is exec'd fresh by every session-start hook, so its\n // config.version is always the on-disk truth \u2014 making it the one component\n // that can detect the drift. Debounced, detached, never blocks the hook.\n this.maybeRestartStaleProxy();\n\n // Port truth first: the identity handshake distinguishes our live gateway\n // from a foreign process squatting on :8787. Spawning against a held port\n // just produces an EADDRINUSE child; spawning is only useful on 'dead'.\n const identity = await isRulemetricGatewayListening(GATEWAY_PORT);\n\n if (identity.status === 'ours-live') {\n // Restore any editor pins a prior dead/foreign window removed \u2014 the\n // gateway is verifiably ours again. The writers refuse to overwrite a\n // user-owned proxy, so this can never clobber a corporate setting.\n const repinned = repinEditorProxies((editor) => {\n if (editor === 'vscode') writeVSCodeUserProxyConfig(GATEWAY_PORT);\n else writeCursorUserProxyConfig(GATEWAY_PORT);\n });\n if (repinned.length > 0) {\n console.error(`[RuleMetric] Gateway healthy again \u2014 restored ${repinned.join(', ')} proxy settings.`);\n }\n return; // Already running, silent success\n }\n\n if (identity.status === 'foreign') {\n // A gateway from an older CLI build answers TCP but not the handshake;\n // the PID-identity check on the pidfile confirms it's ours \u2014 leave it be.\n if (isGatewayRunning()) return;\n // Genuinely foreign: warn (don't crash the hook) \u2014 HTTPS_PROXY may be\n // pinned at a port we don't own, which breaks Claude Code's HTTPS. The\n // editor user-level pins are worse still (http.proxy + strictSSL=false\n // routing at a port someone else owns), and nothing else can heal them\n // \u2014 unpin now; the marker restores them once the port is ours again.\n const unpinned = unpinEditorProxies(GATEWAY_PORT);\n console.error(\n `[RuleMetric] A non-RuleMetric process holds :${GATEWAY_PORT} \u2014 cannot start gateway. ` +\n (unpinned.length > 0\n ? `Unpinned ${unpinned.join(', ')} proxy settings so editors are not routed through it. `\n : '') +\n 'Run `rulemetric diagnostics` if Claude Code HTTPS requests fail.',\n );\n return;\n }\n\n try {\n spawnGateway(this.config.version);\n } catch (err) {\n // Don't crash the hook \u2014 just warn\n console.error(`[RuleMetric] Could not start gateway: ${(err as Error).message}`);\n }\n }\n\n private maybeRestartStaleProxy(): void {\n try {\n if (!existsSync(ADDON_VERSION_FILE)) return;\n const runningVersion = readFileSync(ADDON_VERSION_FILE, 'utf-8').trim();\n if (!runningVersion || runningVersion === this.config.version) return;\n\n // Only restart a proxy that is actually OURS and alive \u2014 a stale version\n // file next to a dead (or docker-marked) proxy is proxy start's problem.\n const pidRaw = readFileSync(PROXY_PID_FILE, 'utf-8').trim();\n const pid = Number(pidRaw);\n if (!Number.isFinite(pid) || !isPidLiveAndMatching(pid, MITMPROXY_COMMAND_PATTERN)) {\n return;\n }\n\n // Cooldown: session starts can cluster, and `proxy restart` needs time to\n // rewrite the version file before the drift signal clears.\n try {\n const age = Date.now() - statSync(DRIFT_RESTART_MARKER).mtimeMs;\n if (age < DRIFT_RESTART_COOLDOWN_MS) return;\n } catch {\n /* no marker yet \u2014 proceed */\n }\n writeFileSync(DRIFT_RESTART_MARKER, new Date().toISOString());\n\n // process.argv[1] is this CLI's entry script (bin/run.js \u2014 dev or global\n // install alike), so the restarted proxy runs the NEW on-disk code and\n // re-stamps the version file, converging the drift.\n const child = spawn(process.execPath, [process.argv[1], 'proxy', 'restart'], {\n detached: true,\n stdio: 'ignore',\n });\n child.unref();\n console.error(\n `[RuleMetric] Capture proxy is running CLI ${runningVersion} but ${this.config.version} is installed \u2014 restarting it in the background.`,\n );\n } catch {\n /* never break the session-start hook */\n }\n }\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,SAAS,aAAa;AACtB,SAAS,YAAY,cAAc,UAAU,qBAAqB;AAClE,SAAS,eAAe;AACxB,SAAS,YAAY;AAYrB,IAAM,aAAa,KAAK,QAAQ,GAAG,WAAW,YAAY;AAC1D,IAAM,qBAAqB,KAAK,YAAY,qBAAqB;AACjE,IAAM,iBAAiB,KAAK,YAAY,WAAW;AACnD,IAAM,uBAAuB,KAAK,YAAY,wBAAwB;AACtE,IAAM,4BAA4B,KAAK;AAEvC,IAAqB,gBAArB,MAAqB,uBAAsB,YAAY;AAAA,EACrD,OAAgB,cAAc;AAAA,EAC9B,OAAgB,SAAS;AAAA,EAEzB,MAAM,MAAqB;AACzB,UAAM,KAAK,MAAM,cAAa;AAS9B,SAAK,uBAAuB;AAK5B,UAAM,WAAW,MAAM,6BAA6B,YAAY;AAEhE,QAAI,SAAS,WAAW,aAAa;AAInC,YAAM,WAAW,mBAAmB,CAAC,WAAW;AAC9C,YAAI,WAAW,SAAU,4BAA2B,YAAY;AAAA,YAC3D,4BAA2B,YAAY;AAAA,MAC9C,CAAC;AACD,UAAI,SAAS,SAAS,GAAG;AACvB,gBAAQ,MAAM,sDAAiD,SAAS,KAAK,IAAI,CAAC,kBAAkB;AAAA,MACtG;AACA;AAAA,IACF;AAEA,QAAI,SAAS,WAAW,WAAW;AAGjC,UAAI,iBAAiB,EAAG;AAMxB,YAAM,WAAW,mBAAmB,YAAY;AAChD,cAAQ;AAAA,QACN,gDAAgD,YAAY,oCACzD,SAAS,SAAS,IACf,YAAY,SAAS,KAAK,IAAI,CAAC,2DAC/B,MACJ;AAAA,MACJ;AACA;AAAA,IACF;AAEA,QAAI;AACF,mBAAa,KAAK,OAAO,OAAO;AAAA,IAClC,SAAS,KAAK;AAEZ,cAAQ,MAAM,yCAA0C,IAAc,OAAO,EAAE;AAAA,IACjF;AAAA,EACF;AAAA,EAEQ,yBAA+B;AACrC,QAAI;AACF,UAAI,CAAC,WAAW,kBAAkB,EAAG;AACrC,YAAM,iBAAiB,aAAa,oBAAoB,OAAO,EAAE,KAAK;AACtE,UAAI,CAAC,kBAAkB,mBAAmB,KAAK,OAAO,QAAS;AAI/D,YAAM,SAAS,aAAa,gBAAgB,OAAO,EAAE,KAAK;AAC1D,YAAM,MAAM,OAAO,MAAM;AACzB,UAAI,CAAC,OAAO,SAAS,GAAG,KAAK,CAAC,qBAAqB,KAAK,yBAAyB,GAAG;AAClF;AAAA,MACF;AAIA,UAAI;AACF,cAAM,MAAM,KAAK,IAAI,IAAI,SAAS,oBAAoB,EAAE;AACxD,YAAI,MAAM,0BAA2B;AAAA,MACvC,QAAQ;AAAA,MAER;AACA,oBAAc,uBAAsB,oBAAI,KAAK,GAAE,YAAY,CAAC;AAK5D,YAAM,QAAQ,MAAM,QAAQ,UAAU,CAAC,QAAQ,KAAK,CAAC,GAAG,SAAS,SAAS,GAAG;AAAA,QAC3E,UAAU;AAAA,QACV,OAAO;AAAA,MACT,CAAC;AACD,YAAM,MAAM;AACZ,cAAQ;AAAA,QACN,6CAA6C,cAAc,QAAQ,KAAK,OAAO,OAAO;AAAA,MACxF;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AACF;",
6
6
  "names": []
7
7
  }
@@ -22,7 +22,7 @@ import {
22
22
  writeCursorUserProxyConfig,
23
23
  writeVSCodeProxyConfig,
24
24
  writeVSCodeUserProxyConfig
25
- } from "../../chunk-4KRMZLQV.js";
25
+ } from "../../chunk-LXKPNATC.js";
26
26
  import {
27
27
  STATUSLINE_SETTINGS_VALUE,
28
28
  STATUSLINE_SHIM_PATH,
@@ -9,7 +9,7 @@ import {
9
9
  removeVSCodeProxyConfig,
10
10
  removeVSCodeUserProxyConfig,
11
11
  uninstallMacOSProxyEnv
12
- } from "../../chunk-4KRMZLQV.js";
12
+ } from "../../chunk-LXKPNATC.js";
13
13
  import "../../chunk-OQBBVTDG.js";
14
14
  import {
15
15
  GATEWAY_PORT,
@@ -1,3 +1,6 @@
1
+ import {
2
+ buildAllowHostsArgs
3
+ } from "../../chunk-EARWG4EV.js";
1
4
  import {
2
5
  findBinary
3
6
  } from "../../chunk-42GFSAJP.js";
@@ -22,6 +25,7 @@ import "../../chunk-NSBPE2FW.js";
22
25
  import { Flags } from "@oclif/core";
23
26
  import { execSync, spawn, spawnSync } from "node:child_process";
24
27
  import { appendFileSync, existsSync, mkdirSync, openSync, readFileSync, writeFileSync } from "node:fs";
28
+ import { createRequire } from "node:module";
25
29
  import { homedir } from "node:os";
26
30
  import { dirname, join, resolve } from "node:path";
27
31
  import { fileURLToPath } from "node:url";
@@ -32,28 +36,38 @@ var PID_FILE = join(CONFIG_DIR, "proxy.pid");
32
36
  var LOG_FILE = join(CONFIG_DIR, "proxy.log");
33
37
  var DOCKER_IMAGE = "rulemetric/proxy";
34
38
  var DOCKER_CONTAINER = "rulemetric-proxy";
39
+ function resolveProxyFile(subpath, fallbacks) {
40
+ try {
41
+ const req = createRequire(import.meta.url);
42
+ const p = req.resolve(`@rulemetric/proxy/${subpath}`);
43
+ if (existsSync(p)) return p;
44
+ } catch {
45
+ }
46
+ for (const p of fallbacks) {
47
+ if (existsSync(p)) return p;
48
+ }
49
+ return null;
50
+ }
35
51
  function findAddonPath() {
36
52
  const candidates = [
37
53
  resolve(MODULE_DIR, "../../../../../packages/proxy/addon/rulemetric_addon.py"),
38
54
  resolve(MODULE_DIR, "../../../node_modules/@rulemetric/proxy/addon/rulemetric_addon.py"),
39
55
  resolve(MODULE_DIR, "../../../../../node_modules/@rulemetric/proxy/addon/rulemetric_addon.py")
40
56
  ];
41
- for (const p of candidates) {
42
- if (existsSync(p)) return p;
43
- }
57
+ const found = resolveProxyFile("addon/rulemetric_addon.py", candidates);
58
+ if (found) return found;
44
59
  throw new Error(
45
- "Could not find rulemetric_addon.py. Is @rulemetric/proxy installed?\nSearched:\n" + candidates.map((c) => ` ${c}`).join("\n")
60
+ "Could not find rulemetric_addon.py. Is @rulemetric/proxy installed?\nSearched (after module resolution):\n" + candidates.map((c) => ` ${c}`).join("\n")
46
61
  );
47
62
  }
48
63
  function findDockerfilePath() {
49
64
  const candidates = [
50
- resolve(MODULE_DIR, "../../../../../packages/proxy"),
51
- resolve(MODULE_DIR, "../../../node_modules/@rulemetric/proxy"),
52
- resolve(MODULE_DIR, "../../../../../node_modules/@rulemetric/proxy")
65
+ resolve(MODULE_DIR, "../../../../../packages/proxy/Dockerfile"),
66
+ resolve(MODULE_DIR, "../../../node_modules/@rulemetric/proxy/Dockerfile"),
67
+ resolve(MODULE_DIR, "../../../../../node_modules/@rulemetric/proxy/Dockerfile")
53
68
  ];
54
- for (const p of candidates) {
55
- if (existsSync(join(p, "Dockerfile"))) return p;
56
- }
69
+ const found = resolveProxyFile("Dockerfile", candidates);
70
+ if (found) return dirname(found);
57
71
  throw new Error("Could not find proxy Dockerfile. Is @rulemetric/proxy installed?");
58
72
  }
59
73
  function dockerImageExists() {
@@ -271,13 +285,20 @@ Or install mitmproxy locally:
271
285
  --- Proxy started at ${(/* @__PURE__ */ new Date()).toISOString()} (port ${port}) ---
272
286
  `);
273
287
  const logFd = openSync(LOG_FILE, "a");
288
+ const allowHostsArgs = buildAllowHostsArgs(dirname(addonPath));
289
+ if (allowHostsArgs.length > 0) {
290
+ this.log(`[OK] Interception scoped to ${allowHostsArgs.length / 2} LLM host patterns (RULEMETRIC_PROXY_ALL_HOSTS=1 to intercept everything)`);
291
+ } else {
292
+ this.log("[!!] Interception UNSCOPED \u2014 all HTTPS hosts pass through mitmproxy");
293
+ }
274
294
  const args = [
275
295
  "--listen-port",
276
296
  String(port),
277
297
  "-s",
278
298
  addonPath,
279
299
  "--set",
280
- "console_eventlog_verbosity=info"
300
+ "console_eventlog_verbosity=info",
301
+ ...allowHostsArgs
281
302
  ];
282
303
  const child = spawn(binary, args, {
283
304
  env,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/commands/proxy/start.ts"],
4
- "sourcesContent": ["import { Flags } from '@oclif/core';\nimport { execSync, spawn, spawnSync } from 'node:child_process';\nimport { appendFileSync, existsSync, mkdirSync, openSync, readFileSync, writeFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { dirname, join, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { BaseCommand } from '../../base-command.js';\nimport { isAuthenticated } from '../../lib/auth.js';\nimport { isPidLiveAndMatching, MITMPROXY_COMMAND_PATTERN } from '../../lib/pid-identity.js';\nimport { findBinary } from '../../lib/which.js';\n\n// import.meta.dirname is undefined on Node < 20.11 (breaks resolve()); derive\n// portably. See onboarding field report 2026-07-07 F1.\nconst MODULE_DIR = dirname(fileURLToPath(import.meta.url));\n\nconst DEFAULT_PORT = 8788;\nconst CONFIG_DIR = join(homedir(), '.config', 'rulemetric');\nconst PID_FILE = join(CONFIG_DIR, 'proxy.pid');\nconst LOG_FILE = join(CONFIG_DIR, 'proxy.log');\nconst DOCKER_IMAGE = 'rulemetric/proxy';\nconst DOCKER_CONTAINER = 'rulemetric-proxy';\n\nfunction findAddonPath(): string {\n const candidates = [\n resolve(MODULE_DIR, '../../../../../packages/proxy/addon/rulemetric_addon.py'),\n resolve(MODULE_DIR, '../../../node_modules/@rulemetric/proxy/addon/rulemetric_addon.py'),\n resolve(MODULE_DIR, '../../../../../node_modules/@rulemetric/proxy/addon/rulemetric_addon.py'),\n ];\n for (const p of candidates) {\n if (existsSync(p)) return p;\n }\n throw new Error(\n 'Could not find rulemetric_addon.py. Is @rulemetric/proxy installed?\\n' +\n 'Searched:\\n' + candidates.map(c => ` ${c}`).join('\\n'),\n );\n}\n\nfunction findDockerfilePath(): string {\n const candidates = [\n resolve(MODULE_DIR, '../../../../../packages/proxy'),\n resolve(MODULE_DIR, '../../../node_modules/@rulemetric/proxy'),\n resolve(MODULE_DIR, '../../../../../node_modules/@rulemetric/proxy'),\n ];\n for (const p of candidates) {\n if (existsSync(join(p, 'Dockerfile'))) return p;\n }\n throw new Error('Could not find proxy Dockerfile. Is @rulemetric/proxy installed?');\n}\n\nfunction dockerImageExists(): boolean {\n const result = spawnSync('docker', ['image', 'inspect', DOCKER_IMAGE], { stdio: 'ignore' });\n return result.status === 0;\n}\n\nfunction dockerContainerRunning(): boolean {\n const result = spawnSync('docker', ['container', 'inspect', '-f', '{{.State.Running}}', DOCKER_CONTAINER], {\n encoding: 'utf-8',\n stdio: ['ignore', 'pipe', 'ignore'],\n });\n return result.status === 0 && result.stdout?.trim() === 'true';\n}\n\nexport default class ProxyStart extends BaseCommand {\n static override description = 'Start context capture proxy';\n\n static override examples = [\n '<%= config.bin %> proxy start',\n '<%= config.bin %> proxy start --docker',\n '<%= config.bin %> proxy start --port 9090',\n '<%= config.bin %> proxy start --web',\n ];\n\n static override flags = {\n port: Flags.integer({ char: 'p', default: DEFAULT_PORT, description: 'Proxy listen port' }),\n docker: Flags.boolean({ char: 'd', default: false, description: 'Run proxy in Docker (no local mitmproxy needed)' }),\n web: Flags.boolean({ default: false, description: 'Launch mitmweb debug UI instead of mitmdump (local mode only)' }),\n 'skip-checks': Flags.boolean({ default: false, description: 'Skip prerequisite checks' }),\n build: Flags.boolean({ default: false, description: 'Force rebuild Docker image' }),\n };\n\n async run(): Promise<void> {\n const { flags } = await this.parse(ProxyStart);\n const port = flags.port;\n\n if (flags.docker) {\n await this.startDocker(port, flags.build);\n } else {\n await this.startLocal(port, flags.web, flags['skip-checks']);\n }\n }\n\n private async startDocker(port: number, forceBuild: boolean): Promise<void> {\n // Check Docker is available\n try {\n execSync('docker info', { stdio: 'ignore' });\n } catch {\n this.error('Docker is not running. Start Docker Desktop and try again.');\n }\n\n // Check if already running\n if (dockerContainerRunning()) {\n this.error(`Proxy container already running. Stop it first: rulemetric proxy stop`);\n }\n\n // Find the compose file (monorepo root or project root)\n const composeFile = this.findComposeFile();\n\n // Ensure mount dirs exist\n const certDir = join(homedir(), '.mitmproxy');\n mkdirSync(certDir, { recursive: true });\n const tmpDir = process.env.TMPDIR || '/tmp';\n mkdirSync(join(tmpDir, 'rulemetric'), { recursive: true });\n\n // Load auth from env file if not in environment\n const envOverrides: Record<string, string> = {\n PROXY_PORT: String(port),\n RULEMETRIC_PROJECT_PATH: process.cwd(),\n };\n\n if (!process.env.RULEMETRIC_API_KEY && !process.env.RULEMETRIC_ACCESS_TOKEN) {\n const envFile = join(homedir(), '.config', 'rulemetric', 'env');\n if (existsSync(envFile)) {\n for (const line of readFileSync(envFile, 'utf-8').split('\\n')) {\n const trimmed = line.trim();\n if (trimmed && !trimmed.startsWith('#') && trimmed.includes('=')) {\n const [key, ...rest] = trimmed.split('=');\n envOverrides[key] = rest.join('=');\n }\n }\n }\n }\n\n if (composeFile) {\n // Use docker compose\n const composeEnv = { ...process.env, ...envOverrides };\n const buildFlag = forceBuild ? '--build' : '';\n const args = ['compose', '-f', composeFile, 'up', '-d', 'proxy'];\n if (forceBuild) args.splice(4, 0, '--build');\n\n this.log(forceBuild ? 'Building and starting proxy via Docker Compose...' : 'Starting proxy via Docker Compose...');\n const result = spawnSync('docker', args, {\n env: composeEnv,\n stdio: 'inherit',\n });\n\n if (result.status !== 0) {\n this.error('Docker Compose failed. Try: docker compose -f docker-compose.yml up proxy');\n }\n } else {\n // Fallback: raw docker run\n if (forceBuild || !dockerImageExists()) {\n const proxyDir = findDockerfilePath();\n this.log('Building proxy Docker image...');\n const build = spawnSync('docker', ['build', '-t', DOCKER_IMAGE, proxyDir], { stdio: 'inherit' });\n if (build.status !== 0) {\n this.error('Docker build failed');\n }\n }\n\n spawnSync('docker', ['rm', '-f', DOCKER_CONTAINER], { stdio: 'ignore' });\n\n const envArgs: string[] = [];\n for (const [key, val] of Object.entries(envOverrides)) {\n envArgs.push('-e', `${key}=${val}`);\n }\n // Pass through from current env\n for (const key of ['RULEMETRIC_API_URL', 'RULEMETRIC_API_KEY', 'RULEMETRIC_ACCESS_TOKEN']) {\n if (process.env[key]) envArgs.push('-e', `${key}=${process.env[key]}`);\n }\n\n const dockerArgs = [\n 'run', '-d',\n '--name', DOCKER_CONTAINER,\n '-p', `${port}:8787`,\n '-v', `${certDir}:/root/.mitmproxy`,\n '-v', `${join(tmpDir, 'rulemetric')}:/tmp/rulemetric:ro`,\n ...envArgs,\n DOCKER_IMAGE,\n ];\n\n const result = spawnSync('docker', dockerArgs, { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'] });\n if (result.status !== 0) {\n this.error(`Failed to start container: ${result.stderr}`);\n }\n }\n\n // Write marker so proxy stop knows it's Docker\n mkdirSync(CONFIG_DIR, { recursive: true });\n writeFileSync(PID_FILE, `docker:${DOCKER_CONTAINER}`);\n\n // Wait for container to be ready\n await new Promise(resolve => setTimeout(resolve, 2000));\n\n const inspectResult = spawnSync('docker', ['container', 'inspect', '-f', '{{.Id}}', DOCKER_CONTAINER], {\n encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'],\n });\n const containerId = inspectResult.stdout?.trim().slice(0, 12) || 'unknown';\n\n this.log(`Proxy running in Docker on localhost:${port} (container: ${containerId})`);\n\n this.log('');\n this.log('View logs: rulemetric proxy logs -f');\n }\n\n private findComposeFile(): string | null {\n const candidates = [\n join(process.cwd(), 'docker-compose.yml'),\n resolve(MODULE_DIR, '../../../../../docker-compose.yml'),\n ];\n for (const p of candidates) {\n if (existsSync(p)) return p;\n }\n return null;\n }\n\n private async startLocal(port: number, useWeb: boolean, skipChecks: boolean): Promise<void> {\n // Check if already running\n if (existsSync(PID_FILE)) {\n const content = readFileSync(PID_FILE, 'utf-8').trim();\n if (content.startsWith('docker:')) {\n this.error('Proxy is running in Docker. Stop it first: rulemetric proxy stop');\n }\n // PID files survive reboots, and the OS may hand the recorded PID to an\n // unrelated process \u2014 bare `kill -0` would then block every restart with\n // \"already running\". Only a live PID whose command line is actually\n // mitmproxy counts; anything else is a stale file we overwrite below.\n const pid = Number.parseInt(content, 10);\n if (!Number.isNaN(pid) && isPidLiveAndMatching(pid, MITMPROXY_COMMAND_PATTERN)) {\n this.error(`Proxy already running (PID ${pid}). Stop it first: rulemetric proxy stop`);\n }\n }\n\n // Check mitmproxy is installed (portable PATH scan \u2014 Windows has no `which`)\n // and spawn the RESOLVED path, never the bare name: findBinary probes\n // well-known install dirs beyond $PATH, so under a GUI/minimal-PATH launch\n // the check can succeed while spawn('mitmdump') would ENOENT \u2014 the exact\n // detection-vs-spawn split that made the 0.6.6 mitmproxy fix a false fix.\n const binaryName = useWeb ? 'mitmweb' : 'mitmdump';\n const binary = findBinary(binaryName);\n if (!binary) {\n this.error(\n `${binaryName} not found. Use Docker mode instead:\\n` +\n ' rulemetric proxy start --docker\\n\\n' +\n 'Or install mitmproxy locally:\\n' +\n ' pipx install mitmproxy\\n' +\n ' brew install mitmproxy',\n );\n }\n\n // Prerequisite warnings (non-blocking)\n if (!skipChecks) {\n const warnings: string[] = [];\n\n if (!isAuthenticated() && !process.env.RULEMETRIC_API_KEY) {\n const envFile = join(homedir(), '.config', 'rulemetric', 'env');\n let hasEnvAuth = false;\n if (existsSync(envFile)) {\n const content = readFileSync(envFile, 'utf-8');\n hasEnvAuth = content.includes('RULEMETRIC_API_KEY') || content.includes('RULEMETRIC_ACCESS_TOKEN');\n }\n if (!hasEnvAuth) {\n warnings.push('Not authenticated \u2014 snapshots cannot be stored. Run `rulemetric auth login`.');\n }\n }\n\n const certPath = join(homedir(), '.mitmproxy', 'mitmproxy-ca-cert.pem');\n if (!existsSync(certPath)) {\n warnings.push('CA cert not found \u2014 HTTPS interception may fail. Run `rulemetric proxy setup`.');\n }\n\n // Hooks may live in settings.local.json (current installs write\n // machine-local state there) or the shared settings.json (legacy\n // installs) \u2014 either file counts as installed.\n const hasClaudeSettings = ['settings.local.json', 'settings.json'].some((fileName) =>\n existsSync(join(process.cwd(), '.claude', fileName)),\n );\n if (!hasClaudeSettings) {\n warnings.push('Session hooks not installed \u2014 snapshots won\\'t link to sessions. Run `rulemetric hooks install`.');\n }\n\n if (warnings.length > 0) {\n for (const w of warnings) {\n this.log(` Warning: ${w}`);\n }\n this.log('');\n }\n }\n\n // Find addon\n const addonPath = findAddonPath();\n\n // Build env \u2014 pass through auth credentials for the reporter\n const env: Record<string, string | undefined> = {\n ...process.env,\n RULEMETRIC_PROJECT_PATH: process.cwd(),\n // Bound to this CLI binary's version. The addon writes this to a\n // version file the gateway watches; on drift the gateway restarts\n // mitmproxy so an upgraded CLI doesn't sit next to a stale proxy.\n RULEMETRIC_CLI_VERSION: this.config.version,\n };\n\n // Load auth from env file if not already in environment\n if (!env.RULEMETRIC_API_KEY && !env.RULEMETRIC_ACCESS_TOKEN) {\n const envFile = join(homedir(), '.config', 'rulemetric', 'env');\n if (existsSync(envFile)) {\n for (const line of readFileSync(envFile, 'utf-8').split('\\n')) {\n const trimmed = line.trim();\n if (trimmed && !trimmed.startsWith('#') && trimmed.includes('=')) {\n const [key, ...rest] = trimmed.split('=');\n env[key] = rest.join('=');\n }\n }\n }\n }\n\n // Ensure config dir exists\n mkdirSync(CONFIG_DIR, { recursive: true });\n\n // Open log file for proxy output (fd for spawn stdio)\n appendFileSync(LOG_FILE, `\\n--- Proxy started at ${new Date().toISOString()} (port ${port}) ---\\n`);\n const logFd = openSync(LOG_FILE, 'a');\n\n // Spawn mitmproxy with output redirected to log file\n const args = [\n '--listen-port', String(port),\n '-s', addonPath,\n '--set', 'console_eventlog_verbosity=info',\n ];\n const child = spawn(binary, args, {\n env,\n stdio: ['ignore', logFd, logFd],\n detached: true,\n });\n\n child.unref();\n\n if (!child.pid) {\n this.error('Failed to start proxy process');\n }\n\n // Brief wait to check if process crashes immediately\n await new Promise<void>((resolve) => {\n let exited = false;\n child.once('exit', (code) => {\n exited = true;\n this.error(`Proxy exited immediately with code ${code}. Check logs:\\n ${LOG_FILE}`);\n });\n setTimeout(() => {\n if (!exited) resolve();\n }, 500);\n });\n\n // Write PID file\n writeFileSync(PID_FILE, String(child.pid));\n\n this.log(`Proxy running on localhost:${port} (PID ${child.pid})`);\n this.log(`Log file: ${LOG_FILE}`);\n\n if (useWeb) {\n this.log('');\n this.log('Debug UI: http://localhost:8081');\n }\n\n this.log('');\n this.log('View logs: rulemetric proxy logs -f');\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { Flags } from '@oclif/core';\nimport { execSync, spawn, spawnSync } from 'node:child_process';\nimport { appendFileSync, existsSync, mkdirSync, openSync, readFileSync, writeFileSync } from 'node:fs';\nimport { createRequire } from 'node:module';\nimport { homedir } from 'node:os';\nimport { dirname, join, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { BaseCommand } from '../../base-command.js';\nimport { isAuthenticated } from '../../lib/auth.js';\nimport { isPidLiveAndMatching, MITMPROXY_COMMAND_PATTERN } from '../../lib/pid-identity.js';\nimport { findBinary } from '../../lib/which.js';\nimport { buildAllowHostsArgs } from '../../lib/capture-hosts.js';\n\n// import.meta.dirname is undefined on Node < 20.11 (breaks resolve()); derive\n// portably. See onboarding field report 2026-07-07 F1.\nconst MODULE_DIR = dirname(fileURLToPath(import.meta.url));\n\nconst DEFAULT_PORT = 8788;\nconst CONFIG_DIR = join(homedir(), '.config', 'rulemetric');\nconst PID_FILE = join(CONFIG_DIR, 'proxy.pid');\nconst LOG_FILE = join(CONFIG_DIR, 'proxy.log');\nconst DOCKER_IMAGE = 'rulemetric/proxy';\nconst DOCKER_CONTAINER = 'rulemetric-proxy';\n\n// Resolve a file inside @rulemetric/proxy via Node's OWN resolver first \u2014 it\n// handles every install layout (npm-flat global, pnpm virtual store, monorepo\n// workspace) that the hardcoded relative candidates cannot. The path guesses\n// missed pnpm-global installs entirely (deps live at `.pnpm/<pkg>/node_modules/\n// @rulemetric/proxy`, a SIBLING of the CLI, not under its node_modules), so\n// `proxy start` was dead for every pnpm user while `hooks run` \u2014 which already\n// uses import resolution \u2014 worked fine. Candidates remain as a fallback for\n// unbundled dev layouts where the workspace dep isn't installed.\nfunction resolveProxyFile(subpath: string, fallbacks: string[]): string | null {\n try {\n const req = createRequire(import.meta.url);\n const p = req.resolve(`@rulemetric/proxy/${subpath}`);\n if (existsSync(p)) return p;\n } catch {\n /* fall through to path candidates */\n }\n for (const p of fallbacks) {\n if (existsSync(p)) return p;\n }\n return null;\n}\n\nfunction findAddonPath(): string {\n const candidates = [\n resolve(MODULE_DIR, '../../../../../packages/proxy/addon/rulemetric_addon.py'),\n resolve(MODULE_DIR, '../../../node_modules/@rulemetric/proxy/addon/rulemetric_addon.py'),\n resolve(MODULE_DIR, '../../../../../node_modules/@rulemetric/proxy/addon/rulemetric_addon.py'),\n ];\n const found = resolveProxyFile('addon/rulemetric_addon.py', candidates);\n if (found) return found;\n throw new Error(\n 'Could not find rulemetric_addon.py. Is @rulemetric/proxy installed?\\n' +\n 'Searched (after module resolution):\\n' + candidates.map(c => ` ${c}`).join('\\n'),\n );\n}\n\nfunction findDockerfilePath(): string {\n const candidates = [\n resolve(MODULE_DIR, '../../../../../packages/proxy/Dockerfile'),\n resolve(MODULE_DIR, '../../../node_modules/@rulemetric/proxy/Dockerfile'),\n resolve(MODULE_DIR, '../../../../../node_modules/@rulemetric/proxy/Dockerfile'),\n ];\n const found = resolveProxyFile('Dockerfile', candidates);\n if (found) return dirname(found);\n throw new Error('Could not find proxy Dockerfile. Is @rulemetric/proxy installed?');\n}\n\nfunction dockerImageExists(): boolean {\n const result = spawnSync('docker', ['image', 'inspect', DOCKER_IMAGE], { stdio: 'ignore' });\n return result.status === 0;\n}\n\nfunction dockerContainerRunning(): boolean {\n const result = spawnSync('docker', ['container', 'inspect', '-f', '{{.State.Running}}', DOCKER_CONTAINER], {\n encoding: 'utf-8',\n stdio: ['ignore', 'pipe', 'ignore'],\n });\n return result.status === 0 && result.stdout?.trim() === 'true';\n}\n\nexport default class ProxyStart extends BaseCommand {\n static override description = 'Start context capture proxy';\n\n static override examples = [\n '<%= config.bin %> proxy start',\n '<%= config.bin %> proxy start --docker',\n '<%= config.bin %> proxy start --port 9090',\n '<%= config.bin %> proxy start --web',\n ];\n\n static override flags = {\n port: Flags.integer({ char: 'p', default: DEFAULT_PORT, description: 'Proxy listen port' }),\n docker: Flags.boolean({ char: 'd', default: false, description: 'Run proxy in Docker (no local mitmproxy needed)' }),\n web: Flags.boolean({ default: false, description: 'Launch mitmweb debug UI instead of mitmdump (local mode only)' }),\n 'skip-checks': Flags.boolean({ default: false, description: 'Skip prerequisite checks' }),\n build: Flags.boolean({ default: false, description: 'Force rebuild Docker image' }),\n };\n\n async run(): Promise<void> {\n const { flags } = await this.parse(ProxyStart);\n const port = flags.port;\n\n if (flags.docker) {\n await this.startDocker(port, flags.build);\n } else {\n await this.startLocal(port, flags.web, flags['skip-checks']);\n }\n }\n\n private async startDocker(port: number, forceBuild: boolean): Promise<void> {\n // Check Docker is available\n try {\n execSync('docker info', { stdio: 'ignore' });\n } catch {\n this.error('Docker is not running. Start Docker Desktop and try again.');\n }\n\n // Check if already running\n if (dockerContainerRunning()) {\n this.error(`Proxy container already running. Stop it first: rulemetric proxy stop`);\n }\n\n // Find the compose file (monorepo root or project root)\n const composeFile = this.findComposeFile();\n\n // Ensure mount dirs exist\n const certDir = join(homedir(), '.mitmproxy');\n mkdirSync(certDir, { recursive: true });\n const tmpDir = process.env.TMPDIR || '/tmp';\n mkdirSync(join(tmpDir, 'rulemetric'), { recursive: true });\n\n // Load auth from env file if not in environment\n const envOverrides: Record<string, string> = {\n PROXY_PORT: String(port),\n RULEMETRIC_PROJECT_PATH: process.cwd(),\n };\n\n if (!process.env.RULEMETRIC_API_KEY && !process.env.RULEMETRIC_ACCESS_TOKEN) {\n const envFile = join(homedir(), '.config', 'rulemetric', 'env');\n if (existsSync(envFile)) {\n for (const line of readFileSync(envFile, 'utf-8').split('\\n')) {\n const trimmed = line.trim();\n if (trimmed && !trimmed.startsWith('#') && trimmed.includes('=')) {\n const [key, ...rest] = trimmed.split('=');\n envOverrides[key] = rest.join('=');\n }\n }\n }\n }\n\n if (composeFile) {\n // Use docker compose\n const composeEnv = { ...process.env, ...envOverrides };\n const buildFlag = forceBuild ? '--build' : '';\n const args = ['compose', '-f', composeFile, 'up', '-d', 'proxy'];\n if (forceBuild) args.splice(4, 0, '--build');\n\n this.log(forceBuild ? 'Building and starting proxy via Docker Compose...' : 'Starting proxy via Docker Compose...');\n const result = spawnSync('docker', args, {\n env: composeEnv,\n stdio: 'inherit',\n });\n\n if (result.status !== 0) {\n this.error('Docker Compose failed. Try: docker compose -f docker-compose.yml up proxy');\n }\n } else {\n // Fallback: raw docker run\n if (forceBuild || !dockerImageExists()) {\n const proxyDir = findDockerfilePath();\n this.log('Building proxy Docker image...');\n const build = spawnSync('docker', ['build', '-t', DOCKER_IMAGE, proxyDir], { stdio: 'inherit' });\n if (build.status !== 0) {\n this.error('Docker build failed');\n }\n }\n\n spawnSync('docker', ['rm', '-f', DOCKER_CONTAINER], { stdio: 'ignore' });\n\n const envArgs: string[] = [];\n for (const [key, val] of Object.entries(envOverrides)) {\n envArgs.push('-e', `${key}=${val}`);\n }\n // Pass through from current env\n for (const key of ['RULEMETRIC_API_URL', 'RULEMETRIC_API_KEY', 'RULEMETRIC_ACCESS_TOKEN']) {\n if (process.env[key]) envArgs.push('-e', `${key}=${process.env[key]}`);\n }\n\n const dockerArgs = [\n 'run', '-d',\n '--name', DOCKER_CONTAINER,\n '-p', `${port}:8787`,\n '-v', `${certDir}:/root/.mitmproxy`,\n '-v', `${join(tmpDir, 'rulemetric')}:/tmp/rulemetric:ro`,\n ...envArgs,\n DOCKER_IMAGE,\n ];\n\n const result = spawnSync('docker', dockerArgs, { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'] });\n if (result.status !== 0) {\n this.error(`Failed to start container: ${result.stderr}`);\n }\n }\n\n // Write marker so proxy stop knows it's Docker\n mkdirSync(CONFIG_DIR, { recursive: true });\n writeFileSync(PID_FILE, `docker:${DOCKER_CONTAINER}`);\n\n // Wait for container to be ready\n await new Promise(resolve => setTimeout(resolve, 2000));\n\n const inspectResult = spawnSync('docker', ['container', 'inspect', '-f', '{{.Id}}', DOCKER_CONTAINER], {\n encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'],\n });\n const containerId = inspectResult.stdout?.trim().slice(0, 12) || 'unknown';\n\n this.log(`Proxy running in Docker on localhost:${port} (container: ${containerId})`);\n\n this.log('');\n this.log('View logs: rulemetric proxy logs -f');\n }\n\n private findComposeFile(): string | null {\n const candidates = [\n join(process.cwd(), 'docker-compose.yml'),\n resolve(MODULE_DIR, '../../../../../docker-compose.yml'),\n ];\n for (const p of candidates) {\n if (existsSync(p)) return p;\n }\n return null;\n }\n\n private async startLocal(port: number, useWeb: boolean, skipChecks: boolean): Promise<void> {\n // Check if already running\n if (existsSync(PID_FILE)) {\n const content = readFileSync(PID_FILE, 'utf-8').trim();\n if (content.startsWith('docker:')) {\n this.error('Proxy is running in Docker. Stop it first: rulemetric proxy stop');\n }\n // PID files survive reboots, and the OS may hand the recorded PID to an\n // unrelated process \u2014 bare `kill -0` would then block every restart with\n // \"already running\". Only a live PID whose command line is actually\n // mitmproxy counts; anything else is a stale file we overwrite below.\n const pid = Number.parseInt(content, 10);\n if (!Number.isNaN(pid) && isPidLiveAndMatching(pid, MITMPROXY_COMMAND_PATTERN)) {\n this.error(`Proxy already running (PID ${pid}). Stop it first: rulemetric proxy stop`);\n }\n }\n\n // Check mitmproxy is installed (portable PATH scan \u2014 Windows has no `which`)\n // and spawn the RESOLVED path, never the bare name: findBinary probes\n // well-known install dirs beyond $PATH, so under a GUI/minimal-PATH launch\n // the check can succeed while spawn('mitmdump') would ENOENT \u2014 the exact\n // detection-vs-spawn split that made the 0.6.6 mitmproxy fix a false fix.\n const binaryName = useWeb ? 'mitmweb' : 'mitmdump';\n const binary = findBinary(binaryName);\n if (!binary) {\n this.error(\n `${binaryName} not found. Use Docker mode instead:\\n` +\n ' rulemetric proxy start --docker\\n\\n' +\n 'Or install mitmproxy locally:\\n' +\n ' pipx install mitmproxy\\n' +\n ' brew install mitmproxy',\n );\n }\n\n // Prerequisite warnings (non-blocking)\n if (!skipChecks) {\n const warnings: string[] = [];\n\n if (!isAuthenticated() && !process.env.RULEMETRIC_API_KEY) {\n const envFile = join(homedir(), '.config', 'rulemetric', 'env');\n let hasEnvAuth = false;\n if (existsSync(envFile)) {\n const content = readFileSync(envFile, 'utf-8');\n hasEnvAuth = content.includes('RULEMETRIC_API_KEY') || content.includes('RULEMETRIC_ACCESS_TOKEN');\n }\n if (!hasEnvAuth) {\n warnings.push('Not authenticated \u2014 snapshots cannot be stored. Run `rulemetric auth login`.');\n }\n }\n\n const certPath = join(homedir(), '.mitmproxy', 'mitmproxy-ca-cert.pem');\n if (!existsSync(certPath)) {\n warnings.push('CA cert not found \u2014 HTTPS interception may fail. Run `rulemetric proxy setup`.');\n }\n\n // Hooks may live in settings.local.json (current installs write\n // machine-local state there) or the shared settings.json (legacy\n // installs) \u2014 either file counts as installed.\n const hasClaudeSettings = ['settings.local.json', 'settings.json'].some((fileName) =>\n existsSync(join(process.cwd(), '.claude', fileName)),\n );\n if (!hasClaudeSettings) {\n warnings.push('Session hooks not installed \u2014 snapshots won\\'t link to sessions. Run `rulemetric hooks install`.');\n }\n\n if (warnings.length > 0) {\n for (const w of warnings) {\n this.log(` Warning: ${w}`);\n }\n this.log('');\n }\n }\n\n // Find addon\n const addonPath = findAddonPath();\n\n // Build env \u2014 pass through auth credentials for the reporter\n const env: Record<string, string | undefined> = {\n ...process.env,\n RULEMETRIC_PROJECT_PATH: process.cwd(),\n // Bound to this CLI binary's version. The addon writes this to a\n // version file the gateway watches; on drift the gateway restarts\n // mitmproxy so an upgraded CLI doesn't sit next to a stale proxy.\n RULEMETRIC_CLI_VERSION: this.config.version,\n };\n\n // Load auth from env file if not already in environment\n if (!env.RULEMETRIC_API_KEY && !env.RULEMETRIC_ACCESS_TOKEN) {\n const envFile = join(homedir(), '.config', 'rulemetric', 'env');\n if (existsSync(envFile)) {\n for (const line of readFileSync(envFile, 'utf-8').split('\\n')) {\n const trimmed = line.trim();\n if (trimmed && !trimmed.startsWith('#') && trimmed.includes('=')) {\n const [key, ...rest] = trimmed.split('=');\n env[key] = rest.join('=');\n }\n }\n }\n }\n\n // Ensure config dir exists\n mkdirSync(CONFIG_DIR, { recursive: true });\n\n // Open log file for proxy output (fd for spawn stdio)\n appendFileSync(LOG_FILE, `\\n--- Proxy started at ${new Date().toISOString()} (port ${port}) ---\\n`);\n const logFd = openSync(LOG_FILE, 'a');\n\n // Scope interception to capturable LLM hosts \u2014 everything else tunnels\n // untouched (no TLS interception), so bundled-CA tools survive the\n // machine-wide HTTPS_PROXY. See capture-hosts.ts for the escape hatches.\n const allowHostsArgs = buildAllowHostsArgs(dirname(addonPath));\n if (allowHostsArgs.length > 0) {\n this.log(`[OK] Interception scoped to ${allowHostsArgs.length / 2} LLM host patterns (RULEMETRIC_PROXY_ALL_HOSTS=1 to intercept everything)`);\n } else {\n this.log('[!!] Interception UNSCOPED \u2014 all HTTPS hosts pass through mitmproxy');\n }\n\n // Spawn mitmproxy with output redirected to log file\n const args = [\n '--listen-port', String(port),\n '-s', addonPath,\n '--set', 'console_eventlog_verbosity=info',\n ...allowHostsArgs,\n ];\n const child = spawn(binary, args, {\n env,\n stdio: ['ignore', logFd, logFd],\n detached: true,\n });\n\n child.unref();\n\n if (!child.pid) {\n this.error('Failed to start proxy process');\n }\n\n // Brief wait to check if process crashes immediately\n await new Promise<void>((resolve) => {\n let exited = false;\n child.once('exit', (code) => {\n exited = true;\n this.error(`Proxy exited immediately with code ${code}. Check logs:\\n ${LOG_FILE}`);\n });\n setTimeout(() => {\n if (!exited) resolve();\n }, 500);\n });\n\n // Write PID file\n writeFileSync(PID_FILE, String(child.pid));\n\n this.log(`Proxy running on localhost:${port} (PID ${child.pid})`);\n this.log(`Log file: ${LOG_FILE}`);\n\n if (useWeb) {\n this.log('');\n this.log('Debug UI: http://localhost:8081');\n }\n\n this.log('');\n this.log('View logs: rulemetric proxy logs -f');\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["resolve"]
7
7
  }
@@ -6,8 +6,8 @@ import {
6
6
  getStep,
7
7
  isStepId,
8
8
  renderDiff
9
- } from "../chunk-POFTPB33.js";
10
- import "../chunk-4KRMZLQV.js";
9
+ } from "../chunk-OKWCKCPE.js";
10
+ import "../chunk-LXKPNATC.js";
11
11
  import "../chunk-OQBBVTDG.js";
12
12
  import "../chunk-4LYCDS2N.js";
13
13
  import "../chunk-7LVVKQMZ.js";
@@ -4,28 +4,28 @@ import {
4
4
  availableCapabilities,
5
5
  pollForWork,
6
6
  runAgentLoop
7
- } from "../chunk-7UDETV3P.js";
7
+ } from "../chunk-7DULPSFU.js";
8
8
  import "../chunk-GIUBARWS.js";
9
+ import "../chunk-6NBS5XFS.js";
10
+ import "../chunk-W53GKIZQ.js";
9
11
  import "../chunk-YOBA3NAM.js";
12
+ import "../chunk-RQ2TMLKG.js";
13
+ import "../chunk-PMI56ED5.js";
10
14
  import "../chunk-OO7JDFS4.js";
15
+ import "../chunk-E3BIT53W.js";
11
16
  import "../chunk-QSN77T7C.js";
17
+ import "../chunk-EI5BHWXA.js";
12
18
  import "../chunk-XK42ZBDE.js";
13
- import "../chunk-3LKVS6W5.js";
14
- import "../chunk-4YAP7RT7.js";
19
+ import "../chunk-RMTT4KDY.js";
20
+ import "../chunk-OK3I555A.js";
15
21
  import "../chunk-XZXS2W24.js";
22
+ import "../chunk-FZKLLNDS.js";
16
23
  import "../chunk-JULOLTZS.js";
17
- import "../chunk-6NBS5XFS.js";
18
- import "../chunk-EI5BHWXA.js";
19
24
  import "../chunk-Y4BJXXYV.js";
20
- import "../chunk-W53GKIZQ.js";
21
25
  import "../chunk-DGHWRQXL.js";
26
+ import "../chunk-J7N3DLH6.js";
22
27
  import "../chunk-52WIYRZH.js";
23
- import "../chunk-RQ2TMLKG.js";
24
- import "../chunk-PMI56ED5.js";
25
- import "../chunk-E3BIT53W.js";
26
- import "../chunk-FZKLLNDS.js";
27
28
  import "../chunk-KJKBDQ4D.js";
28
- import "../chunk-J7N3DLH6.js";
29
29
  import "../chunk-OQSQC7VB.js";
30
30
  import "../chunk-EKP32DLN.js";
31
31
  import "../chunk-F5N6CLJJ.js";
@@ -0,0 +1,8 @@
1
+ import {
2
+ buildAllowHostsArgs
3
+ } from "../chunk-EARWG4EV.js";
4
+ import "../chunk-NSBPE2FW.js";
5
+ export {
6
+ buildAllowHostsArgs
7
+ };
8
+ //# sourceMappingURL=capture-hosts.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": [],
4
+ "sourcesContent": [],
5
+ "mappings": "",
6
+ "names": []
7
+ }
@@ -0,0 +1,14 @@
1
+ import {
2
+ defaultEditorPinPaths,
3
+ repinEditorProxies,
4
+ unpinEditorProxies
5
+ } from "../chunk-3S7223UR.js";
6
+ import "../chunk-LXKPNATC.js";
7
+ import "../chunk-OQBBVTDG.js";
8
+ import "../chunk-NSBPE2FW.js";
9
+ export {
10
+ defaultEditorPinPaths,
11
+ repinEditorProxies,
12
+ unpinEditorProxies
13
+ };
14
+ //# sourceMappingURL=editor-proxy-pins.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": [],
4
+ "sourcesContent": [],
5
+ "mappings": "",
6
+ "names": []
7
+ }
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  processChangelog
3
- } from "../../chunk-HZK43UCF.js";
3
+ } from "../../chunk-DUJSRBLP.js";
4
+ import "../../chunk-E3BIT53W.js";
4
5
  import "../../chunk-QSN77T7C.js";
5
6
  import "../../chunk-EI5BHWXA.js";
6
- import "../../chunk-E3BIT53W.js";
7
7
  import "../../chunk-NSBPE2FW.js";
8
8
  export {
9
9
  processChangelog
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  processEval
3
- } from "../../chunk-3LKVS6W5.js";
3
+ } from "../../chunk-RMTT4KDY.js";
4
4
  import "../../chunk-DGHWRQXL.js";
5
- import "../../chunk-52WIYRZH.js";
6
5
  import "../../chunk-J7N3DLH6.js";
6
+ import "../../chunk-52WIYRZH.js";
7
7
  import "../../chunk-YNIH3KMU.js";
8
8
  import "../../chunk-QN34TGD3.js";
9
9
  import "../../chunk-ZUJBM6HE.js";
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  processInsights
3
- } from "../../chunk-4YAP7RT7.js";
3
+ } from "../../chunk-OK3I555A.js";
4
4
  import "../../chunk-XZXS2W24.js";
5
- import "../../chunk-DGHWRQXL.js";
6
5
  import "../../chunk-FZKLLNDS.js";
6
+ import "../../chunk-DGHWRQXL.js";
7
7
  import "../../chunk-KRBQLMOP.js";
8
8
  import "../../chunk-ZUJBM6HE.js";
9
9
  import "../../chunk-W2YERO7E.js";
@@ -7,6 +7,8 @@ import {
7
7
  generateClaudeCodeHooksConfig,
8
8
  generateCopilotHooksConfig,
9
9
  generateCursorHooksConfig,
10
+ getCursorUserSettingsPath,
11
+ getVSCodeUserSettingsPath,
10
12
  hasRulemetricClaudeHooks,
11
13
  installMacOSProxyEnv,
12
14
  mergeClaudeCodeHooks,
@@ -34,7 +36,7 @@ import {
34
36
  writeCursorUserProxyConfig,
35
37
  writeVSCodeProxyConfig,
36
38
  writeVSCodeUserProxyConfig
37
- } from "../chunk-4KRMZLQV.js";
39
+ } from "../chunk-LXKPNATC.js";
38
40
  import "../chunk-OQBBVTDG.js";
39
41
  import "../chunk-NSBPE2FW.js";
40
42
  export {
@@ -46,6 +48,8 @@ export {
46
48
  generateClaudeCodeHooksConfig,
47
49
  generateCopilotHooksConfig,
48
50
  generateCursorHooksConfig,
51
+ getCursorUserSettingsPath,
52
+ getVSCodeUserSettingsPath,
49
53
  hasRulemetricClaudeHooks,
50
54
  installMacOSProxyEnv,
51
55
  mergeClaudeCodeHooks,
@@ -1,8 +1,6 @@
1
1
  import {
2
2
  processChangelog
3
- } from "../chunk-HZK43UCF.js";
4
- import "../chunk-QSN77T7C.js";
5
- import "../chunk-EI5BHWXA.js";
3
+ } from "../chunk-DUJSRBLP.js";
6
4
  import {
7
5
  cronRefreshSkills
8
6
  } from "../chunk-RQ2TMLKG.js";
@@ -10,6 +8,8 @@ import {
10
8
  cronSuggestInstructions
11
9
  } from "../chunk-PMI56ED5.js";
12
10
  import "../chunk-E3BIT53W.js";
11
+ import "../chunk-QSN77T7C.js";
12
+ import "../chunk-EI5BHWXA.js";
13
13
  import "../chunk-FZKLLNDS.js";
14
14
  import "../chunk-OQSQC7VB.js";
15
15
  import "../chunk-EKP32DLN.js";
@@ -9,8 +9,8 @@ import {
9
9
  isStepId,
10
10
  maskToken,
11
11
  renderDiff
12
- } from "../chunk-POFTPB33.js";
13
- import "../chunk-4KRMZLQV.js";
12
+ } from "../chunk-OKWCKCPE.js";
13
+ import "../chunk-LXKPNATC.js";
14
14
  import "../chunk-OQBBVTDG.js";
15
15
  import "../chunk-4LYCDS2N.js";
16
16
  import "../chunk-7LVVKQMZ.js";
@@ -4099,5 +4099,5 @@
4099
4099
  ]
4100
4100
  }
4101
4101
  },
4102
- "version": "0.7.0"
4102
+ "version": "0.7.2"
4103
4103
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rulemetric/cli",
3
- "version": "0.7.0",
3
+ "version": "0.7.2",
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/hooks": "0.7.0",
77
- "@rulemetric/skills-registry": "0.7.0",
78
- "@rulemetric/proxy": "0.7.0"
76
+ "@rulemetric/proxy": "0.7.2",
77
+ "@rulemetric/hooks": "0.7.2",
78
+ "@rulemetric/skills-registry": "0.7.2"
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": {
@@ -96,10 +96,10 @@
96
96
  "@rulemetric/api": "0.0.1",
97
97
  "@rulemetric/core": "0.0.1",
98
98
  "@rulemetric/db": "0.0.1",
99
- "@rulemetric/session": "0.2.1",
99
+ "@rulemetric/email": "0.0.1",
100
100
  "@rulemetric/telemetry": "0.0.1",
101
- "@rulemetric/typescript-config": "0.0.0",
102
- "@rulemetric/email": "0.0.1"
101
+ "@rulemetric/session": "0.2.1",
102
+ "@rulemetric/typescript-config": "0.0.0"
103
103
  },
104
104
  "scripts": {
105
105
  "build": "node scripts/build.mjs",