@rulemetric/cli 0.5.4 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-ANOWI23I.js → chunk-HWGRTIXN.js} +98 -105
- package/dist/chunk-HWGRTIXN.js.map +7 -0
- package/dist/{chunk-LTT2YMYY.js → chunk-JCYCAQ6U.js} +125 -32
- package/dist/chunk-JCYCAQ6U.js.map +7 -0
- package/dist/{chunk-JNFIYBFL.js → chunk-N2X35ITW.js} +7 -1
- package/dist/{chunk-JNFIYBFL.js.map → chunk-N2X35ITW.js.map} +2 -2
- package/dist/{chunk-FTJRMUID.js → chunk-OVGP4OGK.js} +25 -1
- package/dist/chunk-OVGP4OGK.js.map +7 -0
- package/dist/commands/auth/login.js +15 -1
- package/dist/commands/auth/login.js.map +2 -2
- package/dist/commands/gateway/ensure.js +1 -1
- package/dist/commands/hooks/install.js +84 -51
- package/dist/commands/hooks/install.js.map +2 -2
- package/dist/commands/hooks/run.js +5 -3
- package/dist/commands/hooks/run.js.map +2 -2
- package/dist/commands/hooks/uninstall.js +53 -7
- package/dist/commands/hooks/uninstall.js.map +2 -2
- package/dist/commands/proxy/env.js +1 -1
- package/dist/commands/proxy/status.js +1 -1
- package/dist/commands/service/install.js +8 -2
- package/dist/commands/service/install.js.map +2 -2
- package/dist/commands/setup.js +6 -6
- package/dist/commands/setup.js.map +2 -2
- package/dist/lib/gateway-lifecycle.js +7 -3
- package/dist/lib/hooks-config.js +7 -1
- package/dist/lib/setup-steps.js +7 -3
- package/dist/lib/statusline-shim.js +3 -1
- package/oclif.manifest.json +2 -2
- package/package.json +6 -6
- package/dist/chunk-ANOWI23I.js.map +0 -7
- package/dist/chunk-FTJRMUID.js.map +0 -7
- package/dist/chunk-LTT2YMYY.js.map +0 -7
|
@@ -1,18 +1,20 @@
|
|
|
1
|
+
import {
|
|
2
|
+
detectTmux
|
|
3
|
+
} from "../../chunk-BO76WKJR.js";
|
|
1
4
|
import {
|
|
2
5
|
STATUSLINE_SETTINGS_VALUE,
|
|
3
6
|
isStatuslineShimInstalled,
|
|
4
7
|
readCurrentStatusLine,
|
|
5
8
|
writeStatuslineShim
|
|
6
|
-
} from "../../chunk-
|
|
7
|
-
import {
|
|
8
|
-
detectTmux
|
|
9
|
-
} from "../../chunk-BO76WKJR.js";
|
|
9
|
+
} from "../../chunk-N2X35ITW.js";
|
|
10
10
|
import {
|
|
11
11
|
installMacOSProxyEnv,
|
|
12
12
|
mergeClaudeCodeHooks,
|
|
13
|
+
readClaudeSettings,
|
|
13
14
|
removeClaudeCodeHooks,
|
|
14
15
|
writeAntigravityHooks,
|
|
15
16
|
writeAntigravityUserHooks,
|
|
17
|
+
writeClaudeSettingsWithBackup,
|
|
16
18
|
writeCopilotHooks,
|
|
17
19
|
writeCursorHooks,
|
|
18
20
|
writeCursorProxyConfig,
|
|
@@ -20,13 +22,14 @@ import {
|
|
|
20
22
|
writeCursorUserProxyConfig,
|
|
21
23
|
writeVSCodeProxyConfig,
|
|
22
24
|
writeVSCodeUserProxyConfig
|
|
23
|
-
} from "../../chunk-
|
|
25
|
+
} from "../../chunk-HWGRTIXN.js";
|
|
24
26
|
import "../../chunk-KRBQLMOP.js";
|
|
25
27
|
import {
|
|
26
28
|
GATEWAY_PORT,
|
|
27
29
|
isGatewayRunning,
|
|
28
|
-
spawnGateway
|
|
29
|
-
|
|
30
|
+
spawnGateway,
|
|
31
|
+
waitForGatewayListening
|
|
32
|
+
} from "../../chunk-OVGP4OGK.js";
|
|
30
33
|
import {
|
|
31
34
|
BaseCommand
|
|
32
35
|
} from "../../chunk-SZ7VDCD6.js";
|
|
@@ -42,9 +45,19 @@ import "../../chunk-NSBPE2FW.js";
|
|
|
42
45
|
// src/commands/hooks/install.ts
|
|
43
46
|
import { Flags } from "@oclif/core";
|
|
44
47
|
import { execFileSync, execSync, spawnSync } from "node:child_process";
|
|
45
|
-
import { existsSync
|
|
48
|
+
import { existsSync } from "node:fs";
|
|
46
49
|
import { homedir } from "node:os";
|
|
47
|
-
import { join } from "node:path";
|
|
50
|
+
import { join, dirname } from "node:path";
|
|
51
|
+
function resolveHookBinDir() {
|
|
52
|
+
try {
|
|
53
|
+
const out = execFileSync("/bin/sh", ["-c", "command -v rulemetric"], {
|
|
54
|
+
encoding: "utf-8"
|
|
55
|
+
}).trim();
|
|
56
|
+
if (out && existsSync(out)) return dirname(out);
|
|
57
|
+
} catch {
|
|
58
|
+
}
|
|
59
|
+
return void 0;
|
|
60
|
+
}
|
|
48
61
|
function isGatewayLaunchdManaged() {
|
|
49
62
|
try {
|
|
50
63
|
execFileSync("launchctl", ["list", "com.rulemetric.gateway"], {
|
|
@@ -91,12 +104,13 @@ var HooksInstall = class _HooksInstall extends BaseCommand {
|
|
|
91
104
|
if (isGlobal) {
|
|
92
105
|
this.log(`Installing hooks globally \u2192 ${configPath}`);
|
|
93
106
|
}
|
|
94
|
-
|
|
95
|
-
if (
|
|
96
|
-
|
|
107
|
+
const { settings, recoveredBackup } = readClaudeSettings(configPath);
|
|
108
|
+
if (recoveredBackup) {
|
|
109
|
+
this.log(`[!!] ${configPath} was not valid JSON \u2014 backed it up to ${recoveredBackup} and continued with a fresh file.`);
|
|
97
110
|
}
|
|
98
111
|
const refreshedHooks = removeClaudeCodeHooks(settings);
|
|
99
|
-
|
|
112
|
+
const hookBinDir = resolveHookBinDir();
|
|
113
|
+
mergeClaudeCodeHooks(settings, hookBinDir);
|
|
100
114
|
this.log(
|
|
101
115
|
refreshedHooks ? "[OK] Refreshed RuleMetric Claude Code session hooks \u2192 .claude/settings.json" : "[OK] Installed RuleMetric Claude Code session hooks \u2192 .claude/settings.json"
|
|
102
116
|
);
|
|
@@ -129,10 +143,12 @@ var HooksInstall = class _HooksInstall extends BaseCommand {
|
|
|
129
143
|
} else {
|
|
130
144
|
this.log("[OK] CA certificate found");
|
|
131
145
|
}
|
|
132
|
-
|
|
146
|
+
const caGenerated = existsSync(CERT_PATH);
|
|
147
|
+
let caTrusted = false;
|
|
148
|
+
if (caGenerated && !this.isCACertTrusted()) {
|
|
133
149
|
this.log("[..] Installing CA certificate into system trust store (requires admin)...");
|
|
134
|
-
|
|
135
|
-
if (
|
|
150
|
+
caTrusted = this.trustCACert();
|
|
151
|
+
if (caTrusted) {
|
|
136
152
|
this.log("[OK] CA certificate trusted by system");
|
|
137
153
|
} else {
|
|
138
154
|
this.log("[!!] Could not install CA cert automatically.");
|
|
@@ -142,36 +158,9 @@ var HooksInstall = class _HooksInstall extends BaseCommand {
|
|
|
142
158
|
this.log(` Run manually: sudo cp ${CERT_PATH} /usr/local/share/ca-certificates/mitmproxy-ca.crt && sudo update-ca-certificates`);
|
|
143
159
|
}
|
|
144
160
|
}
|
|
145
|
-
} else if (
|
|
161
|
+
} else if (caGenerated) {
|
|
146
162
|
this.log("[OK] CA certificate already trusted");
|
|
147
|
-
|
|
148
|
-
const env = settings.env ?? {};
|
|
149
|
-
env.HTTPS_PROXY = `http://localhost:${GATEWAY_PORT}`;
|
|
150
|
-
env.NO_PROXY = "localhost,127.0.0.1,*.supabase.co,*.supabase.in";
|
|
151
|
-
if (existsSync(CERT_PATH)) {
|
|
152
|
-
env.NODE_EXTRA_CA_CERTS = CERT_PATH;
|
|
153
|
-
}
|
|
154
|
-
settings.env = env;
|
|
155
|
-
this.log(`[OK] HTTPS_PROXY set to gateway on :${GATEWAY_PORT} (permanent)`);
|
|
156
|
-
const cursorProxy = writeCursorProxyConfig(projectPath, GATEWAY_PORT, CERT_PATH);
|
|
157
|
-
if (cursorProxy) {
|
|
158
|
-
this.log("[OK] Cursor proxy configured \u2192 .cursor/settings.json");
|
|
159
|
-
}
|
|
160
|
-
const vscodeProxy = writeVSCodeProxyConfig(projectPath, GATEWAY_PORT, CERT_PATH);
|
|
161
|
-
if (vscodeProxy) {
|
|
162
|
-
this.log("[OK] VS Code workspace proxy configured \u2192 .vscode/settings.json");
|
|
163
|
-
}
|
|
164
|
-
const vscodeUserProxy = writeVSCodeUserProxyConfig(GATEWAY_PORT);
|
|
165
|
-
if (vscodeUserProxy) {
|
|
166
|
-
this.log("[OK] VS Code user proxy configured \u2192 ~/Library/Application Support/Code/User/settings.json");
|
|
167
|
-
}
|
|
168
|
-
const cursorUserProxy = writeCursorUserProxyConfig(GATEWAY_PORT);
|
|
169
|
-
if (cursorUserProxy) {
|
|
170
|
-
this.log("[OK] Cursor user proxy configured \u2192 ~/Library/Application Support/Cursor/User/settings.json");
|
|
171
|
-
}
|
|
172
|
-
const macProxy = installMacOSProxyEnv(GATEWAY_PORT, CERT_PATH);
|
|
173
|
-
if (macProxy) {
|
|
174
|
-
this.log("[OK] HTTPS_PROXY set for GUI apps (launchctl + LaunchAgent)");
|
|
163
|
+
caTrusted = true;
|
|
175
164
|
}
|
|
176
165
|
if (isGatewayLaunchdManaged()) {
|
|
177
166
|
this.log("[OK] Gateway managed by launchd (com.rulemetric.gateway)");
|
|
@@ -183,16 +172,58 @@ var HooksInstall = class _HooksInstall extends BaseCommand {
|
|
|
183
172
|
this.log(`[OK] Gateway started (PID ${pid})`);
|
|
184
173
|
} catch (err) {
|
|
185
174
|
this.log(`[!!] Could not start gateway: ${err.message}`);
|
|
186
|
-
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
const gatewayLive = await waitForGatewayListening(GATEWAY_PORT, { timeoutMs: 4e3 });
|
|
178
|
+
if (!caGenerated) {
|
|
179
|
+
this.log("[!!] No CA certificate \u2014 NOT setting HTTPS_PROXY (capture stays hooks-only).");
|
|
180
|
+
this.log(" Install mitmproxy, then re-run: rulemetric hooks install");
|
|
181
|
+
} else if (!gatewayLive) {
|
|
182
|
+
this.log(`[!!] Gateway is not answering on :${GATEWAY_PORT} \u2014 NOT setting HTTPS_PROXY.`);
|
|
183
|
+
this.log(" Capture still works via hooks. To enable deep (proxy) capture later:");
|
|
184
|
+
this.log(" rulemetric proxy start && rulemetric hooks install");
|
|
185
|
+
} else {
|
|
186
|
+
const env = settings.env ?? {};
|
|
187
|
+
env.HTTPS_PROXY = `http://localhost:${GATEWAY_PORT}`;
|
|
188
|
+
env.NO_PROXY = "localhost,127.0.0.1,*.supabase.co,*.supabase.in";
|
|
189
|
+
if (existsSync(CERT_PATH)) {
|
|
190
|
+
env.NODE_EXTRA_CA_CERTS = CERT_PATH;
|
|
191
|
+
}
|
|
192
|
+
settings.env = env;
|
|
193
|
+
this.log(`[OK] HTTPS_PROXY set to gateway on :${GATEWAY_PORT} (verified live)`);
|
|
194
|
+
const cursorProxy = writeCursorProxyConfig(projectPath, GATEWAY_PORT, CERT_PATH);
|
|
195
|
+
if (cursorProxy) {
|
|
196
|
+
this.log("[OK] Cursor proxy configured \u2192 .cursor/settings.json");
|
|
197
|
+
}
|
|
198
|
+
const vscodeProxy = writeVSCodeProxyConfig(projectPath, GATEWAY_PORT, CERT_PATH);
|
|
199
|
+
if (vscodeProxy) {
|
|
200
|
+
this.log("[OK] VS Code workspace proxy configured \u2192 .vscode/settings.json");
|
|
201
|
+
}
|
|
202
|
+
const vscodeUserProxy = writeVSCodeUserProxyConfig(GATEWAY_PORT);
|
|
203
|
+
if (vscodeUserProxy) {
|
|
204
|
+
this.log("[OK] VS Code user proxy configured \u2192 ~/Library/Application Support/Code/User/settings.json");
|
|
205
|
+
}
|
|
206
|
+
const cursorUserProxy = writeCursorUserProxyConfig(GATEWAY_PORT);
|
|
207
|
+
if (cursorUserProxy) {
|
|
208
|
+
this.log("[OK] Cursor user proxy configured \u2192 ~/Library/Application Support/Cursor/User/settings.json");
|
|
209
|
+
}
|
|
210
|
+
if (caTrusted) {
|
|
211
|
+
const macProxy = installMacOSProxyEnv(GATEWAY_PORT, CERT_PATH);
|
|
212
|
+
if (macProxy) {
|
|
213
|
+
this.log("[OK] HTTPS_PROXY set for GUI apps (launchctl + LaunchAgent)");
|
|
214
|
+
}
|
|
215
|
+
} else {
|
|
216
|
+
this.log("[!!] CA not trusted \u2014 skipping machine-wide GUI proxy (would break native-app TLS).");
|
|
217
|
+
this.log(" Claude Code capture is unaffected. Trust the CA + re-run to enable cross-app capture.");
|
|
187
218
|
}
|
|
188
219
|
}
|
|
189
220
|
}
|
|
190
221
|
if (!isGlobal) {
|
|
191
|
-
const cursorHooks = writeCursorHooks(projectPath);
|
|
222
|
+
const cursorHooks = writeCursorHooks(projectPath, hookBinDir);
|
|
192
223
|
if (cursorHooks) {
|
|
193
224
|
this.log(`[OK] Cursor hooks configured \u2192 ${cursorHooks}`);
|
|
194
225
|
}
|
|
195
|
-
const copilotHooks = writeCopilotHooks(projectPath);
|
|
226
|
+
const copilotHooks = writeCopilotHooks(projectPath, hookBinDir);
|
|
196
227
|
if (copilotHooks) {
|
|
197
228
|
this.log(`[OK] Copilot Agent hooks configured \u2192 ${copilotHooks}`);
|
|
198
229
|
}
|
|
@@ -205,7 +236,7 @@ var HooksInstall = class _HooksInstall extends BaseCommand {
|
|
|
205
236
|
if (antigravityUserHooks) {
|
|
206
237
|
this.log(`[OK] Antigravity user hooks configured \u2192 ${antigravityUserHooks}`);
|
|
207
238
|
}
|
|
208
|
-
const cursorUserHooks = writeCursorUserHooks();
|
|
239
|
+
const cursorUserHooks = writeCursorUserHooks(hookBinDir);
|
|
209
240
|
if (cursorUserHooks) {
|
|
210
241
|
this.log(`[OK] Cursor user hooks configured \u2192 ${cursorUserHooks}`);
|
|
211
242
|
}
|
|
@@ -224,8 +255,10 @@ var HooksInstall = class _HooksInstall extends BaseCommand {
|
|
|
224
255
|
this.log(" Rate-limit rings will now update on Claude Code's statusline refresh cadence");
|
|
225
256
|
}
|
|
226
257
|
}
|
|
227
|
-
|
|
228
|
-
|
|
258
|
+
const { backupPath } = writeClaudeSettingsWithBackup(configPath, settings);
|
|
259
|
+
if (backupPath) {
|
|
260
|
+
this.log(`[OK] Backed up previous settings \u2192 ${backupPath}`);
|
|
261
|
+
}
|
|
229
262
|
this.log("");
|
|
230
263
|
this.log("Setup complete! Next steps:");
|
|
231
264
|
if (isGlobal) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/commands/hooks/install.ts"],
|
|
4
|
-
"sourcesContent": ["import { Flags } from '@oclif/core';\nimport { execFileSync, execSync, spawnSync } from 'node:child_process';\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { BaseCommand } from '../../base-command.js';\nimport { isGatewayRunning, spawnGateway, GATEWAY_PORT } from '../../lib/gateway-lifecycle.js';\n\n// True iff `rulemetric service install` has wired the gateway up as a launchd\n// agent. When this returns true, hooks install must NOT spawn its own\n// detached gateway \u2014 two processes fighting for :8787 means one crash-loops\n// and Claude Code's HTTPS_PROXY breaks.\nfunction isGatewayLaunchdManaged(): boolean {\n try {\n execFileSync('launchctl', ['list', 'com.rulemetric.gateway'], {\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n return true;\n } catch {\n // Non-zero exit = label not loaded\n return false;\n }\n}\nimport {\n writeStatuslineShim,\n readCurrentStatusLine,\n isStatuslineShimInstalled,\n STATUSLINE_SETTINGS_VALUE,\n} from '../../lib/statusline-shim.js';\nimport {\n mergeClaudeCodeHooks,\n removeClaudeCodeHooks,\n writeCursorProxyConfig,\n writeCursorUserProxyConfig,\n writeVSCodeProxyConfig,\n installMacOSProxyEnv,\n writeVSCodeUserProxyConfig,\n writeCursorHooks,\n writeCursorUserHooks,\n writeCopilotHooks,\n writeAntigravityHooks,\n writeAntigravityUserHooks,\n} from '../../lib/hooks-config.js';\nimport { bootstrapActiveOrg } from '../../lib/active-org-refresh.js';\nimport { detectTmux } from '../../lib/detect-tmux.js';\n\nconst CERT_PATH = join(homedir(), '.mitmproxy', 'mitmproxy-ca-cert.pem');\n\nexport default class HooksInstall extends BaseCommand {\n static override description = 'Install session tracking hooks and context capture proxy for Claude Code';\n\n static override examples = [\n '<%= config.bin %> hooks install',\n '<%= config.bin %> hooks install --global',\n '<%= config.bin %> hooks install --no-proxy',\n ];\n\n static override flags = {\n 'no-proxy': Flags.boolean({\n default: false,\n description: 'Skip proxy/gateway configuration (hooks only)',\n }),\n 'project-dir': Flags.string({\n description: 'Project directory to install hooks in (defaults to cwd)',\n }),\n global: Flags.boolean({\n char: 'g',\n default: false,\n description: 'Install hooks globally (~/.claude/settings.json) so all projects are tracked',\n }),\n 'statusline-usage': Flags.boolean({\n default: false,\n description: 'Install the statusline shim so Claude Code pushes live rate_limits on every refresh (makes /usage rings update in real time without needing the proxy)',\n }),\n };\n\n async run(): Promise<void> {\n const { flags } = await this.parse(HooksInstall);\n const isGlobal = flags.global;\n const projectPath = isGlobal ? homedir() : (flags['project-dir'] ?? process.cwd());\n const configDir = join(projectPath, '.claude');\n const configPath = join(configDir, 'settings.json');\n const noProxy = flags['no-proxy'];\n\n if (isGlobal) {\n this.log(`Installing hooks globally \u2192 ${configPath}`);\n }\n\n // Read or create .claude/settings.json\n let settings: Record<string, unknown> = {};\n if (existsSync(configPath)) {\n settings = JSON.parse(readFileSync(configPath, 'utf-8'));\n }\n\n // \u2500\u2500 1. Install Claude Code session-tracking hooks (hooks-first, zero-proxy\n // capture). Strip any stale rulemetric entries first \u2014 handles old command\n // formats, preserves the user's own hooks \u2014 then merge the current canonical\n // set. Runs regardless of --no-proxy: this is the whole point of the hooks\n // path, which captures full sessions (lifecycle + SessionEnd transcript\n // reimport) without mitmproxy / CA trust / gateway. The proxy adds the\n // rendered system prompt (instruction-linking) + cross-tool capture on top.\n const refreshedHooks = removeClaudeCodeHooks(settings);\n mergeClaudeCodeHooks(settings);\n this.log(\n refreshedHooks\n ? '[OK] Refreshed RuleMetric Claude Code session hooks \u2192 .claude/settings.json'\n : '[OK] Installed RuleMetric Claude Code session hooks \u2192 .claude/settings.json',\n );\n\n // \u2500\u2500 2. Configure gateway + proxy \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n if (!noProxy) {\n // Ensure mitmproxy is available\n const hasMitmproxy = this.findBinary('mitmdump');\n if (!hasMitmproxy) {\n this.log('');\n this.log('[!!] mitmproxy not found \u2014 installing...');\n const installed = this.installMitmproxy();\n if (!installed) {\n this.log('[!!] Could not install mitmproxy. Skipping proxy config.');\n this.log(' Install manually: pipx install mitmproxy');\n this.log(' Then re-run: rulemetric hooks install');\n }\n }\n\n // Generate CA cert if missing\n if (!existsSync(CERT_PATH)) {\n this.log('[..] Generating CA certificate...');\n const mitmdump = this.findBinary('mitmdump');\n if (mitmdump) {\n spawnSync(mitmdump, ['--set', 'listen_port=0', '-q'], {\n timeout: 5000,\n stdio: 'ignore',\n });\n if (existsSync(CERT_PATH)) {\n this.log(`[OK] CA certificate generated at ${CERT_PATH}`);\n } else {\n this.log('[!!] Could not generate CA cert');\n }\n }\n } else {\n this.log('[OK] CA certificate found');\n }\n\n // Trust CA cert in system store (if not already trusted)\n if (existsSync(CERT_PATH) && !this.isCACertTrusted()) {\n this.log('[..] Installing CA certificate into system trust store (requires admin)...');\n const trusted = this.trustCACert();\n if (trusted) {\n this.log('[OK] CA certificate trusted by system');\n } else {\n this.log('[!!] Could not install CA cert automatically.');\n if (process.platform === 'darwin') {\n this.log(` Run manually: sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain ${CERT_PATH}`);\n } else if (process.platform === 'linux') {\n this.log(` Run manually: sudo cp ${CERT_PATH} /usr/local/share/ca-certificates/mitmproxy-ca.crt && sudo update-ca-certificates`);\n }\n }\n } else if (existsSync(CERT_PATH)) {\n this.log('[OK] CA certificate already trusted');\n }\n\n // Set permanent HTTPS_PROXY and NODE_EXTRA_CA_CERTS in settings.json\n // These point to the gateway (always running), not mitmproxy directly\n const env = (settings.env ?? {}) as Record<string, string>;\n env.HTTPS_PROXY = `http://localhost:${GATEWAY_PORT}`;\n // Bypass proxy for non-Anthropic traffic (Supabase auth, npm, etc.)\n env.NO_PROXY = 'localhost,127.0.0.1,*.supabase.co,*.supabase.in';\n if (existsSync(CERT_PATH)) {\n env.NODE_EXTRA_CA_CERTS = CERT_PATH;\n }\n settings.env = env;\n this.log(`[OK] HTTPS_PROXY set to gateway on :${GATEWAY_PORT} (permanent)`);\n\n // Configure Cursor proxy (if .cursor/ exists)\n const cursorProxy = writeCursorProxyConfig(projectPath, GATEWAY_PORT, CERT_PATH);\n if (cursorProxy) {\n this.log('[OK] Cursor proxy configured \u2192 .cursor/settings.json');\n }\n\n // Configure VS Code proxy for Copilot capture (workspace settings)\n const vscodeProxy = writeVSCodeProxyConfig(projectPath, GATEWAY_PORT, CERT_PATH);\n if (vscodeProxy) {\n this.log('[OK] VS Code workspace proxy configured \u2192 .vscode/settings.json');\n }\n\n // Set http.proxy in VS Code user settings (workspace-level is silently ignored\n // for http.proxy due to APPLICATION scope \u2014 microsoft/vscode#236932)\n const vscodeUserProxy = writeVSCodeUserProxyConfig(GATEWAY_PORT);\n if (vscodeUserProxy) {\n this.log('[OK] VS Code user proxy configured \u2192 ~/Library/Application Support/Code/User/settings.json');\n }\n\n // Same for Cursor (VS Code fork inherits the APPLICATION-scope restriction).\n const cursorUserProxy = writeCursorUserProxyConfig(GATEWAY_PORT);\n if (cursorUserProxy) {\n this.log('[OK] Cursor user proxy configured \u2192 ~/Library/Application Support/Cursor/User/settings.json');\n }\n\n // Set HTTPS_PROXY for all GUI apps via launchctl + LaunchAgent (macOS)\n // Copilot reads process.env.HTTPS_PROXY directly \u2014 VS Code settings alone\n // are insufficient because workspace http.proxy is ignored and extensions\n // may bypass vscode-proxy-agent (microsoft/vscode#12588)\n const macProxy = installMacOSProxyEnv(GATEWAY_PORT, CERT_PATH);\n if (macProxy) {\n this.log('[OK] HTTPS_PROXY set for GUI apps (launchctl + LaunchAgent)');\n }\n\n // Start gateway if not already running. Three possible owners (in order\n // of precedence):\n // 1. launchd (`rulemetric service install` plist) \u2014 authoritative;\n // hooks must NOT spawn a competing copy that would fight for :8787\n // 2. legacy PID-file (this same code path from a previous run)\n // 3. nothing \u2014 spawn a fresh detached process\n if (isGatewayLaunchdManaged()) {\n this.log('[OK] Gateway managed by launchd (com.rulemetric.gateway)');\n } else if (isGatewayRunning()) {\n this.log('[OK] Gateway already running');\n } else {\n try {\n const pid = spawnGateway(this.config.version);\n this.log(`[OK] Gateway started (PID ${pid})`);\n } catch (err) {\n this.log(`[!!] Could not start gateway: ${(err as Error).message}`);\n this.log(' The session-start hook will auto-start it on next Claude Code session.');\n }\n }\n }\n\n // \u2500\u2500 3. Write Cursor + Copilot hook configs \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // These are independent of the proxy (they capture editor lifecycle\n // events, not LLM traffic), so they run regardless of --no-proxy. Both\n // helpers no-op when the target directory (.cursor/ or .github/)\n // doesn't exist.\n if (!isGlobal) {\n const cursorHooks = writeCursorHooks(projectPath);\n if (cursorHooks) {\n this.log(`[OK] Cursor hooks configured \u2192 ${cursorHooks}`);\n }\n\n const copilotHooks = writeCopilotHooks(projectPath);\n if (copilotHooks) {\n this.log(`[OK] Copilot Agent hooks configured \u2192 ${copilotHooks}`);\n }\n\n const antigravityHooks = writeAntigravityHooks(projectPath);\n if (antigravityHooks) {\n this.log(`[OK] Antigravity workspace hooks configured \u2192 ${antigravityHooks}`);\n }\n }\n\n // User-level Antigravity hooks at ~/.gemini/config/hooks.json. Required\n // because the GUI Antigravity process is launched once per machine and\n // covers any workspace the user opens, not just the install-time cwd.\n const antigravityUserHooks = writeAntigravityUserHooks();\n if (antigravityUserHooks) {\n this.log(`[OK] Antigravity user hooks configured \u2192 ${antigravityUserHooks}`);\n }\n\n // User-level Cursor hooks (~/.cursor/hooks.json) are required for global\n // capture because Cursor's chat traffic bypasses HTTP proxies (HTTP/2\n // multiplexed persistent connection \u2014 see cursor.com/docs/hooks). Without\n // hooks, we get zero Cursor visibility for sessions outside this project.\n // Runs in both --global and per-project modes since the user file is\n // machine-wide either way.\n const cursorUserHooks = writeCursorUserHooks();\n if (cursorUserHooks) {\n this.log(`[OK] Cursor user hooks configured \u2192 ${cursorUserHooks}`);\n }\n\n // \u2500\u2500 4. Statusline shim (rate_limits freshness) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // When --statusline-usage is set, write ~/.claude/statusline-rulemetric.sh\n // and point settings.statusLine at it. If the user already has a statusLine\n // command configured, chain it so their prompt text is preserved.\n if (flags['statusline-usage']) {\n if (isStatuslineShimInstalled(settings)) {\n this.log('[OK] Statusline shim already installed');\n } else {\n const prevCmd = readCurrentStatusLine(settings);\n writeStatuslineShim(prevCmd);\n settings.statusLine = STATUSLINE_SETTINGS_VALUE;\n if (prevCmd) {\n this.log(`[OK] Statusline shim installed \u2192 ${STATUSLINE_SETTINGS_VALUE.command} (chains to: ${prevCmd})`);\n } else {\n this.log(`[OK] Statusline shim installed \u2192 ${STATUSLINE_SETTINGS_VALUE.command}`);\n }\n this.log(' Rate-limit rings will now update on Claude Code\\'s statusline refresh cadence');\n }\n }\n\n // \u2500\u2500 5. Write Claude Code settings \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n mkdirSync(configDir, { recursive: true });\n writeFileSync(configPath, JSON.stringify(settings, null, 2) + '\\n');\n\n this.log('');\n this.log('Setup complete! Next steps:');\n if (isGlobal) {\n this.log(' 1. Install always-on API: rulemetric service install');\n this.log(' 2. Start Claude Code in any project \u2014 all sessions tracked automatically');\n this.log('');\n this.log('The API service starts on login and restarts on crash.');\n } else if (!noProxy) {\n this.log(' 1. Start Claude Code \u2014 sessions are captured by hooks immediately');\n this.log(' 2. Start the capture proxy for full depth: rulemetric proxy start');\n this.log('');\n this.log('Hooks capture every session (lifecycle + transcript). The proxy adds the');\n this.log('rendered system prompt (instruction-effectiveness linking) + cross-tool capture.');\n this.log('');\n this.log('To capture traffic from other clients (Python, curl, etc.):');\n this.log(' eval \"$(rulemetric proxy env --all)\"');\n } else {\n // --no-proxy still installs the Claude Code session hooks above, so full\n // sessions ARE captured (lifecycle + SessionEnd transcript reimport) with\n // no mitmproxy / CA trust / gateway. Only the proxy-exclusive layer\n // (rendered system prompt \u2192 instruction-linking, cross-tool) is skipped.\n this.log(' 1. Start Claude Code \u2014 sessions are captured by the hooks just installed');\n this.log('');\n this.log('No proxy means no mitmproxy / CA cert needed. The hooks capture full');\n this.log('sessions on their own; re-run without --no-proxy to also capture system');\n this.log('prompts for instruction-effectiveness linking.');\n }\n\n // Ensure the active-org cache is populated before the user starts a\n // Claude Code session. BaseCommand fired the guarded refresh earlier\n // but may have raced with auth init or hit a transient error; this\n // explicit retry guarantees the cache reflects the user's current\n // active_org_id at the end of `hooks install`.\n await bootstrapActiveOrg();\n\n // Soft tmux capability check \u2014 live message send needs tmux on the\n // user's machine. We do not auto-install; just surface one line.\n const tmux = await detectTmux();\n this.log('');\n if (tmux.available) {\n this.log(`[OK] tmux found${tmux.version ? ` (v${tmux.version})` : ''} \u2014 live send enabled`);\n } else {\n this.log('[!!] tmux not found \u2014 live send will fall back to opening Terminal. Install with: brew install tmux');\n }\n }\n\n private findBinary(name: string): string | null {\n try {\n return execSync(`which ${name} 2>/dev/null`, { encoding: 'utf-8' }).trim() || null;\n } catch {\n return null;\n }\n }\n\n private installMitmproxy(): boolean {\n for (const [cmd, args] of [\n ['pipx', ['install', 'mitmproxy']],\n ['uv', ['tool', 'install', 'mitmproxy']],\n ['brew', ['install', 'mitmproxy']],\n ] as const) {\n if (this.findBinary(cmd)) {\n this.log(` Installing via ${cmd}...`);\n const result = spawnSync(cmd, [...args], { stdio: 'inherit' });\n if (result.status === 0) return true;\n }\n }\n return false;\n }\n\n private isCACertTrusted(): boolean {\n if (process.platform === 'darwin') {\n const result = spawnSync('security', ['verify-cert', '-c', CERT_PATH], {\n stdio: 'pipe',\n });\n return result.status === 0;\n }\n\n if (process.platform === 'linux') {\n return existsSync('/usr/local/share/ca-certificates/mitmproxy-ca.crt');\n }\n\n if (process.platform === 'win32') {\n // On Windows, check if cert is in the Root store\n const result = spawnSync('certutil', ['-verify', CERT_PATH], {\n stdio: 'pipe',\n });\n return result.status === 0;\n }\n\n return false;\n }\n\n private trustCACert(): boolean {\n if (process.platform === 'darwin') {\n const result = spawnSync('sudo', [\n 'security', 'add-trusted-cert', '-d', '-r', 'trustRoot',\n '-k', '/Library/Keychains/System.keychain', CERT_PATH,\n ], { stdio: 'inherit' });\n return result.status === 0;\n }\n\n if (process.platform === 'linux') {\n const cp = spawnSync('sudo', [\n 'cp', CERT_PATH, '/usr/local/share/ca-certificates/mitmproxy-ca.crt',\n ], { stdio: 'inherit' });\n if (cp.status !== 0) return false;\n const update = spawnSync('sudo', ['update-ca-certificates'], { stdio: 'inherit' });\n return update.status === 0;\n }\n\n if (process.platform === 'win32') {\n const result = spawnSync('certutil', [\n '-addstore', '-f', 'Root', CERT_PATH,\n ], { stdio: 'inherit' });\n return result.status === 0;\n }\n\n return false;\n }\n}\n"],
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["import { Flags } from '@oclif/core';\nimport { execFileSync, execSync, spawnSync } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join, dirname } from 'node:path';\nimport { BaseCommand } from '../../base-command.js';\nimport { isGatewayRunning, waitForGatewayListening, spawnGateway, GATEWAY_PORT } from '../../lib/gateway-lifecycle.js';\n\n// Resolve the directory holding the `rulemetric` executable, so hook commands\n// can prepend it to PATH. Claude Code runs hooks via `/bin/sh -c` with a\n// minimal PATH that omits nvm/pnpm global bin dirs \u2014 so a bare `rulemetric`\n// hits \"command not found\" and capture (plus the session-start gateway\n// auto-start) silently fails. We resolve it here, in the install process's\n// full PATH, and bake the dir into the hook command. Returns undefined on\n// failure \u2192 callers fall back to the bare `rulemetric` (dev/back-compat).\nfunction resolveHookBinDir(): string | undefined {\n try {\n const out = execFileSync('/bin/sh', ['-c', 'command -v rulemetric'], {\n encoding: 'utf-8',\n }).trim();\n if (out && existsSync(out)) return dirname(out);\n } catch {\n /* not resolvable \u2014 fall back to bare `rulemetric` */\n }\n return undefined;\n}\n\n// True iff `rulemetric service install` has wired the gateway up as a launchd\n// agent. When this returns true, hooks install must NOT spawn its own\n// detached gateway \u2014 two processes fighting for :8787 means one crash-loops\n// and Claude Code's HTTPS_PROXY breaks.\nfunction isGatewayLaunchdManaged(): boolean {\n try {\n execFileSync('launchctl', ['list', 'com.rulemetric.gateway'], {\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n return true;\n } catch {\n // Non-zero exit = label not loaded\n return false;\n }\n}\nimport {\n writeStatuslineShim,\n readCurrentStatusLine,\n isStatuslineShimInstalled,\n STATUSLINE_SETTINGS_VALUE,\n} from '../../lib/statusline-shim.js';\nimport {\n mergeClaudeCodeHooks,\n removeClaudeCodeHooks,\n readClaudeSettings,\n writeClaudeSettingsWithBackup,\n writeCursorProxyConfig,\n writeCursorUserProxyConfig,\n writeVSCodeProxyConfig,\n installMacOSProxyEnv,\n writeVSCodeUserProxyConfig,\n writeCursorHooks,\n writeCursorUserHooks,\n writeCopilotHooks,\n writeAntigravityHooks,\n writeAntigravityUserHooks,\n} from '../../lib/hooks-config.js';\nimport { bootstrapActiveOrg } from '../../lib/active-org-refresh.js';\nimport { detectTmux } from '../../lib/detect-tmux.js';\n\nconst CERT_PATH = join(homedir(), '.mitmproxy', 'mitmproxy-ca-cert.pem');\n\nexport default class HooksInstall extends BaseCommand {\n static override description = 'Install session tracking hooks and context capture proxy for Claude Code';\n\n static override examples = [\n '<%= config.bin %> hooks install',\n '<%= config.bin %> hooks install --global',\n '<%= config.bin %> hooks install --no-proxy',\n ];\n\n static override flags = {\n 'no-proxy': Flags.boolean({\n default: false,\n description: 'Skip proxy/gateway configuration (hooks only)',\n }),\n 'project-dir': Flags.string({\n description: 'Project directory to install hooks in (defaults to cwd)',\n }),\n global: Flags.boolean({\n char: 'g',\n default: false,\n description: 'Install hooks globally (~/.claude/settings.json) so all projects are tracked',\n }),\n 'statusline-usage': Flags.boolean({\n default: false,\n description: 'Install the statusline shim so Claude Code pushes live rate_limits on every refresh (makes /usage rings update in real time without needing the proxy)',\n }),\n };\n\n async run(): Promise<void> {\n const { flags } = await this.parse(HooksInstall);\n const isGlobal = flags.global;\n const projectPath = isGlobal ? homedir() : (flags['project-dir'] ?? process.cwd());\n const configDir = join(projectPath, '.claude');\n const configPath = join(configDir, 'settings.json');\n const noProxy = flags['no-proxy'];\n\n if (isGlobal) {\n this.log(`Installing hooks globally \u2192 ${configPath}`);\n }\n\n // Read or create .claude/settings.json. Never crash on a malformed file \u2014\n // a fresh user's hand-edited settings.json must not block capture install.\n const { settings, recoveredBackup } = readClaudeSettings(configPath);\n if (recoveredBackup) {\n this.log(`[!!] ${configPath} was not valid JSON \u2014 backed it up to ${recoveredBackup} and continued with a fresh file.`);\n }\n\n // \u2500\u2500 1. Install Claude Code session-tracking hooks (hooks-first, zero-proxy\n // capture). Strip any stale rulemetric entries first \u2014 handles old command\n // formats, preserves the user's own hooks \u2014 then merge the current canonical\n // set. Runs regardless of --no-proxy: this is the whole point of the hooks\n // path, which captures full sessions (lifecycle + SessionEnd transcript\n // reimport) without mitmproxy / CA trust / gateway. The proxy adds the\n // rendered system prompt (instruction-linking) + cross-tool capture on top.\n const refreshedHooks = removeClaudeCodeHooks(settings);\n // Resolve once; every hook writer (Claude Code, Cursor, Copilot) needs the\n // same PATH prefix so a GUI-launched tool with a minimal PATH can find the CLI.\n const hookBinDir = resolveHookBinDir();\n mergeClaudeCodeHooks(settings, hookBinDir);\n this.log(\n refreshedHooks\n ? '[OK] Refreshed RuleMetric Claude Code session hooks \u2192 .claude/settings.json'\n : '[OK] Installed RuleMetric Claude Code session hooks \u2192 .claude/settings.json',\n );\n\n // \u2500\u2500 2. Configure gateway + proxy \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n if (!noProxy) {\n // Ensure mitmproxy is available\n const hasMitmproxy = this.findBinary('mitmdump');\n if (!hasMitmproxy) {\n this.log('');\n this.log('[!!] mitmproxy not found \u2014 installing...');\n const installed = this.installMitmproxy();\n if (!installed) {\n this.log('[!!] Could not install mitmproxy. Skipping proxy config.');\n this.log(' Install manually: pipx install mitmproxy');\n this.log(' Then re-run: rulemetric hooks install');\n }\n }\n\n // Generate CA cert if missing\n if (!existsSync(CERT_PATH)) {\n this.log('[..] Generating CA certificate...');\n const mitmdump = this.findBinary('mitmdump');\n if (mitmdump) {\n spawnSync(mitmdump, ['--set', 'listen_port=0', '-q'], {\n timeout: 5000,\n stdio: 'ignore',\n });\n if (existsSync(CERT_PATH)) {\n this.log(`[OK] CA certificate generated at ${CERT_PATH}`);\n } else {\n this.log('[!!] Could not generate CA cert');\n }\n }\n } else {\n this.log('[OK] CA certificate found');\n }\n\n // Trust CA cert in system store (if not already trusted). Track the\n // result: whether the CA is trusted decides if we may route MACHINE-WIDE /\n // native-app traffic through the MITM proxy (native apps validate against\n // the system keychain, so an untrusted CA breaks their TLS).\n const caGenerated = existsSync(CERT_PATH);\n let caTrusted = false;\n if (caGenerated && !this.isCACertTrusted()) {\n this.log('[..] Installing CA certificate into system trust store (requires admin)...');\n caTrusted = this.trustCACert();\n if (caTrusted) {\n this.log('[OK] CA certificate trusted by system');\n } else {\n this.log('[!!] Could not install CA cert automatically.');\n if (process.platform === 'darwin') {\n this.log(` Run manually: sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain ${CERT_PATH}`);\n } else if (process.platform === 'linux') {\n this.log(` Run manually: sudo cp ${CERT_PATH} /usr/local/share/ca-certificates/mitmproxy-ca.crt && sudo update-ca-certificates`);\n }\n }\n } else if (caGenerated) {\n this.log('[OK] CA certificate already trusted');\n caTrusted = true;\n }\n\n // Start the gateway BEFORE pinning HTTPS_PROXY. Three possible owners (in\n // order of precedence):\n // 1. launchd (`rulemetric service install` plist) \u2014 authoritative;\n // hooks must NOT spawn a competing copy that would fight for :8787\n // 2. legacy PID-file (this same code path from a previous run)\n // 3. nothing \u2014 spawn a fresh detached process\n if (isGatewayLaunchdManaged()) {\n this.log('[OK] Gateway managed by launchd (com.rulemetric.gateway)');\n } else if (isGatewayRunning()) {\n this.log('[OK] Gateway already running');\n } else {\n try {\n const pid = spawnGateway(this.config.version);\n this.log(`[OK] Gateway started (PID ${pid})`);\n } catch (err) {\n this.log(`[!!] Could not start gateway: ${(err as Error).message}`);\n }\n }\n\n // Liveness guard: ONLY pin HTTPS_PROXY once :8787 actually accepts\n // connections. Writing it against a dead port is the fresh-laptop\n // ConnectionRefused footgun \u2014 it breaks all of Claude Code's HTTPS. If the\n // gateway isn't answering, we leave Claude Code talking to Anthropic\n // directly; capture still works via the hooks installed in \u00A71.\n // Poll (not a one-shot probe): a just-spawned gateway needs a moment to\n // bind :8787, and a single immediate probe would fail-close every fresh\n // install and silently disable deep capture.\n const gatewayLive = await waitForGatewayListening(GATEWAY_PORT, { timeoutMs: 4000 });\n if (!caGenerated) {\n // No CA \u2192 mitmproxy will regenerate one and intercept, but nothing\n // trusts it, so pinning HTTPS_PROXY would break even Claude Code's TLS.\n // Stay hooks-only rather than pin a proxy nothing can validate.\n this.log('[!!] No CA certificate \u2014 NOT setting HTTPS_PROXY (capture stays hooks-only).');\n this.log(' Install mitmproxy, then re-run: rulemetric hooks install');\n } else if (!gatewayLive) {\n this.log(`[!!] Gateway is not answering on :${GATEWAY_PORT} \u2014 NOT setting HTTPS_PROXY.`);\n this.log(' Capture still works via hooks. To enable deep (proxy) capture later:');\n this.log(' rulemetric proxy start && rulemetric hooks install');\n } else {\n // Set permanent HTTPS_PROXY and NODE_EXTRA_CA_CERTS in settings.json.\n // These point to the gateway (verified live above), not mitmproxy directly.\n const env = (settings.env ?? {}) as Record<string, string>;\n env.HTTPS_PROXY = `http://localhost:${GATEWAY_PORT}`;\n // Bypass proxy for non-Anthropic traffic (Supabase auth, npm, etc.)\n env.NO_PROXY = 'localhost,127.0.0.1,*.supabase.co,*.supabase.in';\n if (existsSync(CERT_PATH)) {\n env.NODE_EXTRA_CA_CERTS = CERT_PATH;\n }\n settings.env = env;\n this.log(`[OK] HTTPS_PROXY set to gateway on :${GATEWAY_PORT} (verified live)`);\n\n // Configure Cursor proxy (if .cursor/ exists)\n const cursorProxy = writeCursorProxyConfig(projectPath, GATEWAY_PORT, CERT_PATH);\n if (cursorProxy) {\n this.log('[OK] Cursor proxy configured \u2192 .cursor/settings.json');\n }\n\n // Configure VS Code proxy for Copilot capture (workspace settings)\n const vscodeProxy = writeVSCodeProxyConfig(projectPath, GATEWAY_PORT, CERT_PATH);\n if (vscodeProxy) {\n this.log('[OK] VS Code workspace proxy configured \u2192 .vscode/settings.json');\n }\n\n // Set http.proxy in VS Code user settings (workspace-level is silently ignored\n // for http.proxy due to APPLICATION scope \u2014 microsoft/vscode#236932)\n const vscodeUserProxy = writeVSCodeUserProxyConfig(GATEWAY_PORT);\n if (vscodeUserProxy) {\n this.log('[OK] VS Code user proxy configured \u2192 ~/Library/Application Support/Code/User/settings.json');\n }\n\n // Same for Cursor (VS Code fork inherits the APPLICATION-scope restriction).\n const cursorUserProxy = writeCursorUserProxyConfig(GATEWAY_PORT);\n if (cursorUserProxy) {\n this.log('[OK] Cursor user proxy configured \u2192 ~/Library/Application Support/Cursor/User/settings.json');\n }\n\n // Set HTTPS_PROXY for all GUI apps via launchctl + LaunchAgent (macOS)\n // Copilot reads process.env.HTTPS_PROXY directly \u2014 VS Code settings alone\n // are insufficient because workspace http.proxy is ignored and extensions\n // may bypass vscode-proxy-agent (microsoft/vscode#12588).\n //\n // GATED ON CA TRUST: this routes EVERY GUI/native app's HTTPS through the\n // MITM proxy machine-wide. Native apps validate against the system\n // keychain, so if the CA isn't trusted (sudo declined / non-admin) this\n // would break their TLS with an unknown-CA error attributed to nothing\n // obvious. Claude Code + Node apps are unaffected either way (they trust\n // NODE_EXTRA_CA_CERTS), so we keep that path and only skip the\n // machine-wide native layer.\n if (caTrusted) {\n const macProxy = installMacOSProxyEnv(GATEWAY_PORT, CERT_PATH);\n if (macProxy) {\n this.log('[OK] HTTPS_PROXY set for GUI apps (launchctl + LaunchAgent)');\n }\n } else {\n this.log('[!!] CA not trusted \u2014 skipping machine-wide GUI proxy (would break native-app TLS).');\n this.log(' Claude Code capture is unaffected. Trust the CA + re-run to enable cross-app capture.');\n }\n }\n }\n\n // \u2500\u2500 3. Write Cursor + Copilot hook configs \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // These are independent of the proxy (they capture editor lifecycle\n // events, not LLM traffic), so they run regardless of --no-proxy. Both\n // helpers no-op when the target directory (.cursor/ or .github/)\n // doesn't exist.\n if (!isGlobal) {\n const cursorHooks = writeCursorHooks(projectPath, hookBinDir);\n if (cursorHooks) {\n this.log(`[OK] Cursor hooks configured \u2192 ${cursorHooks}`);\n }\n\n const copilotHooks = writeCopilotHooks(projectPath, hookBinDir);\n if (copilotHooks) {\n this.log(`[OK] Copilot Agent hooks configured \u2192 ${copilotHooks}`);\n }\n\n const antigravityHooks = writeAntigravityHooks(projectPath);\n if (antigravityHooks) {\n this.log(`[OK] Antigravity workspace hooks configured \u2192 ${antigravityHooks}`);\n }\n }\n\n // User-level Antigravity hooks at ~/.gemini/config/hooks.json. Required\n // because the GUI Antigravity process is launched once per machine and\n // covers any workspace the user opens, not just the install-time cwd.\n const antigravityUserHooks = writeAntigravityUserHooks();\n if (antigravityUserHooks) {\n this.log(`[OK] Antigravity user hooks configured \u2192 ${antigravityUserHooks}`);\n }\n\n // User-level Cursor hooks (~/.cursor/hooks.json) are required for global\n // capture because Cursor's chat traffic bypasses HTTP proxies (HTTP/2\n // multiplexed persistent connection \u2014 see cursor.com/docs/hooks). Without\n // hooks, we get zero Cursor visibility for sessions outside this project.\n // Runs in both --global and per-project modes since the user file is\n // machine-wide either way.\n const cursorUserHooks = writeCursorUserHooks(hookBinDir);\n if (cursorUserHooks) {\n this.log(`[OK] Cursor user hooks configured \u2192 ${cursorUserHooks}`);\n }\n\n // \u2500\u2500 4. Statusline shim (rate_limits freshness) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // When --statusline-usage is set, write ~/.claude/statusline-rulemetric.sh\n // and point settings.statusLine at it. If the user already has a statusLine\n // command configured, chain it so their prompt text is preserved.\n if (flags['statusline-usage']) {\n if (isStatuslineShimInstalled(settings)) {\n this.log('[OK] Statusline shim already installed');\n } else {\n const prevCmd = readCurrentStatusLine(settings);\n writeStatuslineShim(prevCmd);\n settings.statusLine = STATUSLINE_SETTINGS_VALUE;\n if (prevCmd) {\n this.log(`[OK] Statusline shim installed \u2192 ${STATUSLINE_SETTINGS_VALUE.command} (chains to: ${prevCmd})`);\n } else {\n this.log(`[OK] Statusline shim installed \u2192 ${STATUSLINE_SETTINGS_VALUE.command}`);\n }\n this.log(' Rate-limit rings will now update on Claude Code\\'s statusline refresh cadence');\n }\n }\n\n // \u2500\u2500 5. Write Claude Code settings (backup prior file \u2192 reversible) \u2500\u2500\u2500\u2500\u2500\n const { backupPath } = writeClaudeSettingsWithBackup(configPath, settings);\n if (backupPath) {\n this.log(`[OK] Backed up previous settings \u2192 ${backupPath}`);\n }\n\n this.log('');\n this.log('Setup complete! Next steps:');\n if (isGlobal) {\n this.log(' 1. Install always-on API: rulemetric service install');\n this.log(' 2. Start Claude Code in any project \u2014 all sessions tracked automatically');\n this.log('');\n this.log('The API service starts on login and restarts on crash.');\n } else if (!noProxy) {\n this.log(' 1. Start Claude Code \u2014 sessions are captured by hooks immediately');\n this.log(' 2. Start the capture proxy for full depth: rulemetric proxy start');\n this.log('');\n this.log('Hooks capture every session (lifecycle + transcript). The proxy adds the');\n this.log('rendered system prompt (instruction-effectiveness linking) + cross-tool capture.');\n this.log('');\n this.log('To capture traffic from other clients (Python, curl, etc.):');\n this.log(' eval \"$(rulemetric proxy env --all)\"');\n } else {\n // --no-proxy still installs the Claude Code session hooks above, so full\n // sessions ARE captured (lifecycle + SessionEnd transcript reimport) with\n // no mitmproxy / CA trust / gateway. Only the proxy-exclusive layer\n // (rendered system prompt \u2192 instruction-linking, cross-tool) is skipped.\n this.log(' 1. Start Claude Code \u2014 sessions are captured by the hooks just installed');\n this.log('');\n this.log('No proxy means no mitmproxy / CA cert needed. The hooks capture full');\n this.log('sessions on their own; re-run without --no-proxy to also capture system');\n this.log('prompts for instruction-effectiveness linking.');\n }\n\n // Ensure the active-org cache is populated before the user starts a\n // Claude Code session. BaseCommand fired the guarded refresh earlier\n // but may have raced with auth init or hit a transient error; this\n // explicit retry guarantees the cache reflects the user's current\n // active_org_id at the end of `hooks install`.\n await bootstrapActiveOrg();\n\n // Soft tmux capability check \u2014 live message send needs tmux on the\n // user's machine. We do not auto-install; just surface one line.\n const tmux = await detectTmux();\n this.log('');\n if (tmux.available) {\n this.log(`[OK] tmux found${tmux.version ? ` (v${tmux.version})` : ''} \u2014 live send enabled`);\n } else {\n this.log('[!!] tmux not found \u2014 live send will fall back to opening Terminal. Install with: brew install tmux');\n }\n }\n\n private findBinary(name: string): string | null {\n try {\n return execSync(`which ${name} 2>/dev/null`, { encoding: 'utf-8' }).trim() || null;\n } catch {\n return null;\n }\n }\n\n private installMitmproxy(): boolean {\n for (const [cmd, args] of [\n ['pipx', ['install', 'mitmproxy']],\n ['uv', ['tool', 'install', 'mitmproxy']],\n ['brew', ['install', 'mitmproxy']],\n ] as const) {\n if (this.findBinary(cmd)) {\n this.log(` Installing via ${cmd}...`);\n const result = spawnSync(cmd, [...args], { stdio: 'inherit' });\n if (result.status === 0) return true;\n }\n }\n return false;\n }\n\n private isCACertTrusted(): boolean {\n if (process.platform === 'darwin') {\n const result = spawnSync('security', ['verify-cert', '-c', CERT_PATH], {\n stdio: 'pipe',\n });\n return result.status === 0;\n }\n\n if (process.platform === 'linux') {\n return existsSync('/usr/local/share/ca-certificates/mitmproxy-ca.crt');\n }\n\n if (process.platform === 'win32') {\n // On Windows, check if cert is in the Root store\n const result = spawnSync('certutil', ['-verify', CERT_PATH], {\n stdio: 'pipe',\n });\n return result.status === 0;\n }\n\n return false;\n }\n\n private trustCACert(): boolean {\n if (process.platform === 'darwin') {\n const result = spawnSync('sudo', [\n 'security', 'add-trusted-cert', '-d', '-r', 'trustRoot',\n '-k', '/Library/Keychains/System.keychain', CERT_PATH,\n ], { stdio: 'inherit' });\n return result.status === 0;\n }\n\n if (process.platform === 'linux') {\n const cp = spawnSync('sudo', [\n 'cp', CERT_PATH, '/usr/local/share/ca-certificates/mitmproxy-ca.crt',\n ], { stdio: 'inherit' });\n if (cp.status !== 0) return false;\n const update = spawnSync('sudo', ['update-ca-certificates'], { stdio: 'inherit' });\n return update.status === 0;\n }\n\n if (process.platform === 'win32') {\n const result = spawnSync('certutil', [\n '-addstore', '-f', 'Root', CERT_PATH,\n ], { stdio: 'inherit' });\n return result.status === 0;\n }\n\n return false;\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -12,7 +12,7 @@ import "../../chunk-NSBPE2FW.js";
|
|
|
12
12
|
import { Args } from "@oclif/core";
|
|
13
13
|
import { execFileSync } from "node:child_process";
|
|
14
14
|
import { existsSync } from "node:fs";
|
|
15
|
-
import { join } from "node:path";
|
|
15
|
+
import { join, delimiter } from "node:path";
|
|
16
16
|
var VALID_HOOKS = ["session-start", "session-end", "user-prompt", "post-tool-use", "assistant-response"];
|
|
17
17
|
var HooksRun = class _HooksRun extends BaseCommand {
|
|
18
18
|
static description = "Execute a hook script (called by AI tool hooks, not for direct use)";
|
|
@@ -34,11 +34,13 @@ var HooksRun = class _HooksRun extends BaseCommand {
|
|
|
34
34
|
}
|
|
35
35
|
try {
|
|
36
36
|
const stdin = await this.readStdin();
|
|
37
|
+
const augmentedPath = `${process.env.PATH ?? ""}${delimiter}${scriptsDir}`;
|
|
38
|
+
const timeout = hookName === "session-end" ? 3e4 : 1e4;
|
|
37
39
|
execFileSync("bash", [scriptPath], {
|
|
38
40
|
input: stdin,
|
|
39
41
|
stdio: ["pipe", "inherit", "inherit"],
|
|
40
|
-
timeout
|
|
41
|
-
env: process.env
|
|
42
|
+
timeout,
|
|
43
|
+
env: { ...process.env, PATH: augmentedPath }
|
|
42
44
|
});
|
|
43
45
|
if (process.env.RULEMETRIC_HOOK_TOOL === "antigravity" && hookName === "post-tool-use") {
|
|
44
46
|
try {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/commands/hooks/run.ts"],
|
|
4
|
-
"sourcesContent": ["import { Args } from '@oclif/core';\nimport { execFileSync } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { BaseCommand } from '../../base-command.js';\n\nconst VALID_HOOKS = ['session-start', 'session-end', 'user-prompt', 'post-tool-use', 'assistant-response'] as const;\n\nexport default class HooksRun extends BaseCommand {\n static override description = 'Execute a hook script (called by AI tool hooks, not for direct use)';\n static override hidden = true;\n\n static override args = {\n hook: Args.string({\n description: 'Hook name to run',\n required: true,\n options: [...VALID_HOOKS],\n }),\n };\n\n async run(): Promise<void> {\n const { args } = await this.parse(HooksRun);\n const hookName = args.hook;\n\n // Resolve bundled script path\n const { scriptsDir } = await import('@rulemetric/hooks/scripts-dir');\n const scriptPath = join(scriptsDir, `${hookName}.sh`);\n\n if (!existsSync(scriptPath)) {\n this.error(`Hook script not found: ${scriptPath}`);\n }\n\n try {\n // Read stdin and pass it to the bash script\n const stdin = await this.readStdin();\n execFileSync('bash', [scriptPath], {\n input: stdin,\n stdio: ['pipe', 'inherit', 'inherit'],\n timeout
|
|
5
|
-
"mappings": ";;;;;;;;;;;AAAA,SAAS,YAAY;AACrB,SAAS,oBAAoB;AAC7B,SAAS,kBAAkB;AAC3B,SAAS,
|
|
4
|
+
"sourcesContent": ["import { Args } from '@oclif/core';\nimport { execFileSync } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport { join, delimiter } from 'node:path';\nimport { BaseCommand } from '../../base-command.js';\n\nconst VALID_HOOKS = ['session-start', 'session-end', 'user-prompt', 'post-tool-use', 'assistant-response'] as const;\n\nexport default class HooksRun extends BaseCommand {\n static override description = 'Execute a hook script (called by AI tool hooks, not for direct use)';\n static override hidden = true;\n\n static override args = {\n hook: Args.string({\n description: 'Hook name to run',\n required: true,\n options: [...VALID_HOOKS],\n }),\n };\n\n async run(): Promise<void> {\n const { args } = await this.parse(HooksRun);\n const hookName = args.hook;\n\n // Resolve bundled script path\n const { scriptsDir } = await import('@rulemetric/hooks/scripts-dir');\n const scriptPath = join(scriptsDir, `${hookName}.sh`);\n\n if (!existsSync(scriptPath)) {\n this.error(`Hook script not found: ${scriptPath}`);\n }\n\n try {\n // Read stdin and pass it to the bash script\n const stdin = await this.readStdin();\n // APPEND the bundled scripts dir to PATH so the shipped `jq` shim resolves\n // when the host has no real jq (macOS ships none). Appended, not prepended,\n // so a real jq earlier on PATH still wins. Every hook `source`s\n // _normalize.sh, which calls bare `jq` under `set -e` \u2014 without this the\n // whole hooks-capture path silently dies on a jq-less machine.\n const augmentedPath = `${process.env.PATH ?? ''}${delimiter}${scriptsDir}`;\n // session-end reimports the full transcript + enrich/link/cleanup \u2014 it\n // budgets ~17s of network internally, so a 10s cap SIGKILLs it mid-reimport\n // and silently drops the session. Give it headroom; other hooks are quick.\n const timeout = hookName === 'session-end' ? 30_000 : 10_000;\n execFileSync('bash', [scriptPath], {\n input: stdin,\n stdio: ['pipe', 'inherit', 'inherit'],\n timeout,\n env: { ...process.env, PATH: augmentedPath },\n });\n\n // Antigravity-specific: tail the conversation transcript and emit\n // user_message / assistant_response / tool_result events that the\n // bash hook can't see. Antigravity does not fire UserPromptSubmit\n // or Stop hooks, and the Gemini streaming traffic bypasses the\n // proxy. First fire for a conversation primes state to the current\n // transcript end and returns immediately \u2014 capture is forward-only.\n if (\n process.env.RULEMETRIC_HOOK_TOOL === 'antigravity' &&\n hookName === 'post-tool-use'\n ) {\n try {\n const { tailAntigravityTranscript } = await import(\n '../../lib/antigravity-transcript.js'\n );\n await tailAntigravityTranscript(stdin);\n } catch (err) {\n process.stderr.write(\n `[antigravity-tailer] ${(err as Error).message}\\n`,\n );\n }\n }\n } catch (error: unknown) {\n // Hooks should not crash the AI tool \u2014 exit silently\n const code = (error as { status?: number }).status;\n if (code) {\n process.exitCode = code;\n }\n }\n }\n\n private readStdin(): Promise<string> {\n return new Promise((resolve) => {\n const chunks: Buffer[] = [];\n process.stdin.on('data', (chunk) => chunks.push(chunk));\n process.stdin.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')));\n // If stdin is already closed (no piped input), resolve immediately\n if (process.stdin.readableEnded) {\n resolve(Buffer.concat(chunks).toString('utf-8'));\n }\n });\n }\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;AAAA,SAAS,YAAY;AACrB,SAAS,oBAAoB;AAC7B,SAAS,kBAAkB;AAC3B,SAAS,MAAM,iBAAiB;AAGhC,IAAM,cAAc,CAAC,iBAAiB,eAAe,eAAe,iBAAiB,oBAAoB;AAEzG,IAAqB,WAArB,MAAqB,kBAAiB,YAAY;AAAA,EAChD,OAAgB,cAAc;AAAA,EAC9B,OAAgB,SAAS;AAAA,EAEzB,OAAgB,OAAO;AAAA,IACrB,MAAM,KAAK,OAAO;AAAA,MAChB,aAAa;AAAA,MACb,UAAU;AAAA,MACV,SAAS,CAAC,GAAG,WAAW;AAAA,IAC1B,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,MAAqB;AACzB,UAAM,EAAE,KAAK,IAAI,MAAM,KAAK,MAAM,SAAQ;AAC1C,UAAM,WAAW,KAAK;AAGtB,UAAM,EAAE,WAAW,IAAI,MAAM,OAAO,+BAA+B;AACnE,UAAM,aAAa,KAAK,YAAY,GAAG,QAAQ,KAAK;AAEpD,QAAI,CAAC,WAAW,UAAU,GAAG;AAC3B,WAAK,MAAM,0BAA0B,UAAU,EAAE;AAAA,IACnD;AAEA,QAAI;AAEF,YAAM,QAAQ,MAAM,KAAK,UAAU;AAMnC,YAAM,gBAAgB,GAAG,QAAQ,IAAI,QAAQ,EAAE,GAAG,SAAS,GAAG,UAAU;AAIxE,YAAM,UAAU,aAAa,gBAAgB,MAAS;AACtD,mBAAa,QAAQ,CAAC,UAAU,GAAG;AAAA,QACjC,OAAO;AAAA,QACP,OAAO,CAAC,QAAQ,WAAW,SAAS;AAAA,QACpC;AAAA,QACA,KAAK,EAAE,GAAG,QAAQ,KAAK,MAAM,cAAc;AAAA,MAC7C,CAAC;AAQD,UACE,QAAQ,IAAI,yBAAyB,iBACrC,aAAa,iBACb;AACA,YAAI;AACF,gBAAM,EAAE,0BAA0B,IAAI,MAAM,OAC1C,qCACF;AACA,gBAAM,0BAA0B,KAAK;AAAA,QACvC,SAAS,KAAK;AACZ,kBAAQ,OAAO;AAAA,YACb,wBAAyB,IAAc,OAAO;AAAA;AAAA,UAChD;AAAA,QACF;AAAA,MACF;AAAA,IACF,SAAS,OAAgB;AAEvB,YAAM,OAAQ,MAA8B;AAC5C,UAAI,MAAM;AACR,gBAAQ,WAAW;AAAA,MACrB;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,YAA6B;AACnC,WAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,YAAM,SAAmB,CAAC;AAC1B,cAAQ,MAAM,GAAG,QAAQ,CAAC,UAAU,OAAO,KAAK,KAAK,CAAC;AACtD,cAAQ,MAAM,GAAG,OAAO,MAAM,QAAQ,OAAO,OAAO,MAAM,EAAE,SAAS,OAAO,CAAC,CAAC;AAE9E,UAAI,QAAQ,MAAM,eAAe;AAC/B,gBAAQ,OAAO,OAAO,MAAM,EAAE,SAAS,OAAO,CAAC;AAAA,MACjD;AAAA,IACF,CAAC;AAAA,EACH;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
|
+
removeStatuslineShim
|
|
3
|
+
} from "../../chunk-N2X35ITW.js";
|
|
4
|
+
import {
|
|
5
|
+
readClaudeSettings,
|
|
2
6
|
removeAntigravityHooks,
|
|
3
7
|
removeAntigravityUserHooks,
|
|
4
8
|
removeClaudeCodeHooks,
|
|
@@ -8,11 +12,12 @@ import {
|
|
|
8
12
|
removeCursorUserProxyConfig,
|
|
9
13
|
removeVSCodeProxyConfig,
|
|
10
14
|
removeVSCodeUserProxyConfig,
|
|
11
|
-
uninstallMacOSProxyEnv
|
|
12
|
-
|
|
15
|
+
uninstallMacOSProxyEnv,
|
|
16
|
+
writeClaudeSettingsWithBackup
|
|
17
|
+
} from "../../chunk-HWGRTIXN.js";
|
|
13
18
|
import {
|
|
14
19
|
stopGateway
|
|
15
|
-
} from "../../chunk-
|
|
20
|
+
} from "../../chunk-OVGP4OGK.js";
|
|
16
21
|
import {
|
|
17
22
|
BaseCommand
|
|
18
23
|
} from "../../chunk-SZ7VDCD6.js";
|
|
@@ -25,7 +30,25 @@ import "../../chunk-NSBPE2FW.js";
|
|
|
25
30
|
|
|
26
31
|
// src/commands/hooks/uninstall.ts
|
|
27
32
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
33
|
+
import { spawnSync } from "node:child_process";
|
|
34
|
+
import { homedir } from "node:os";
|
|
28
35
|
import { join } from "node:path";
|
|
36
|
+
var CERT_PATH = join(homedir(), ".mitmproxy", "mitmproxy-ca-cert.pem");
|
|
37
|
+
function untrustCACert() {
|
|
38
|
+
if (!existsSync(CERT_PATH)) return false;
|
|
39
|
+
if (process.platform === "darwin") {
|
|
40
|
+
return spawnSync("sudo", ["security", "remove-trusted-cert", "-d", CERT_PATH], { stdio: "inherit" }).status === 0;
|
|
41
|
+
}
|
|
42
|
+
if (process.platform === "linux") {
|
|
43
|
+
const rm = spawnSync("sudo", ["rm", "-f", "/usr/local/share/ca-certificates/mitmproxy-ca.crt"], { stdio: "inherit" });
|
|
44
|
+
if (rm.status !== 0) return false;
|
|
45
|
+
return spawnSync("sudo", ["update-ca-certificates", "--fresh"], { stdio: "inherit" }).status === 0;
|
|
46
|
+
}
|
|
47
|
+
if (process.platform === "win32") {
|
|
48
|
+
return spawnSync("certutil", ["-delstore", "Root", "mitmproxy"], { stdio: "inherit" }).status === 0;
|
|
49
|
+
}
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
29
52
|
var HooksUninstall = class extends BaseCommand {
|
|
30
53
|
static description = "Remove session tracking hooks from the current project";
|
|
31
54
|
static examples = [
|
|
@@ -37,24 +60,34 @@ var HooksUninstall = class extends BaseCommand {
|
|
|
37
60
|
this.log("No .claude/settings.json found \u2014 nothing to uninstall.");
|
|
38
61
|
return;
|
|
39
62
|
}
|
|
40
|
-
const settings =
|
|
63
|
+
const { settings, recoveredBackup } = readClaudeSettings(configPath);
|
|
64
|
+
if (recoveredBackup) {
|
|
65
|
+
this.log(`[!!] ${configPath} was not valid JSON \u2014 backed up to ${recoveredBackup}; continuing.`);
|
|
66
|
+
}
|
|
41
67
|
let changed = false;
|
|
42
68
|
if (removeClaudeCodeHooks(settings)) {
|
|
43
69
|
this.log("Removed RuleMetric hooks from .claude/settings.json");
|
|
44
70
|
changed = true;
|
|
45
71
|
}
|
|
46
72
|
const env = settings.env;
|
|
47
|
-
|
|
73
|
+
let removedProxyEnv = false;
|
|
74
|
+
if (env && (env.HTTPS_PROXY || env.NO_PROXY || env.NODE_EXTRA_CA_CERTS)) {
|
|
48
75
|
delete env.HTTPS_PROXY;
|
|
76
|
+
delete env.NO_PROXY;
|
|
49
77
|
delete env.NODE_EXTRA_CA_CERTS;
|
|
50
78
|
if (Object.keys(env).length === 0) {
|
|
51
79
|
delete settings.env;
|
|
52
80
|
}
|
|
53
|
-
this.log("Removed HTTPS_PROXY and NODE_EXTRA_CA_CERTS from .claude/settings.json");
|
|
81
|
+
this.log("Removed HTTPS_PROXY, NO_PROXY and NODE_EXTRA_CA_CERTS from .claude/settings.json");
|
|
82
|
+
changed = true;
|
|
83
|
+
removedProxyEnv = true;
|
|
84
|
+
}
|
|
85
|
+
if (removeStatuslineShim(settings)) {
|
|
86
|
+
this.log("Removed RuleMetric statusline shim from .claude/settings.json");
|
|
54
87
|
changed = true;
|
|
55
88
|
}
|
|
56
89
|
if (changed) {
|
|
57
|
-
|
|
90
|
+
writeClaudeSettingsWithBackup(configPath, settings);
|
|
58
91
|
} else {
|
|
59
92
|
this.log("No hooks or proxy config found \u2014 nothing to uninstall.");
|
|
60
93
|
}
|
|
@@ -108,6 +141,19 @@ var HooksUninstall = class extends BaseCommand {
|
|
|
108
141
|
this.log("Removed HTTPS_PROXY LaunchAgent");
|
|
109
142
|
changed = true;
|
|
110
143
|
}
|
|
144
|
+
if (removedProxyEnv && existsSync(CERT_PATH)) {
|
|
145
|
+
this.log("Removing the mitmproxy CA from the system trust store (requires sudo)...");
|
|
146
|
+
if (untrustCACert()) {
|
|
147
|
+
this.log("[OK] mitmproxy CA untrusted.");
|
|
148
|
+
} else {
|
|
149
|
+
this.log(`[!!] Could not untrust the CA automatically. Remove it manually:`);
|
|
150
|
+
if (process.platform === "darwin") {
|
|
151
|
+
this.log(` sudo security remove-trusted-cert -d ${CERT_PATH}`);
|
|
152
|
+
} else if (process.platform === "linux") {
|
|
153
|
+
this.log(` sudo rm -f /usr/local/share/ca-certificates/mitmproxy-ca.crt && sudo update-ca-certificates --fresh`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
111
157
|
if (stopGateway()) {
|
|
112
158
|
this.log("Gateway stopped.");
|
|
113
159
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/commands/hooks/uninstall.ts"],
|
|
4
|
-
"sourcesContent": ["import { existsSync, readFileSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { BaseCommand } from '../../base-command.js';\nimport { stopGateway } from '../../lib/gateway-lifecycle.js';\nimport { removeClaudeCodeHooks, removeCursorHooks, removeCursorUserHooks, removeCopilotHooks, removeAntigravityHooks, removeAntigravityUserHooks, removeVSCodeProxyConfig, uninstallMacOSProxyEnv, removeVSCodeUserProxyConfig, removeCursorUserProxyConfig } from '../../lib/hooks-config.js';\n\nexport default class HooksUninstall extends BaseCommand {\n static override description = 'Remove session tracking hooks from the current project';\n\n static override examples = [\n '<%= config.bin %> hooks uninstall',\n ];\n\n async run(): Promise<void> {\n const configPath = join(process.cwd(), '.claude', 'settings.json');\n\n if (!existsSync(configPath)) {\n this.log('No .claude/settings.json found \u2014 nothing to uninstall.');\n return;\n }\n\n const settings
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["import { existsSync, readFileSync, writeFileSync } from 'node:fs';\nimport { spawnSync } from 'node:child_process';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { BaseCommand } from '../../base-command.js';\nimport { stopGateway } from '../../lib/gateway-lifecycle.js';\nimport { removeClaudeCodeHooks, readClaudeSettings, writeClaudeSettingsWithBackup, removeCursorHooks, removeCursorUserHooks, removeCopilotHooks, removeAntigravityHooks, removeAntigravityUserHooks, removeVSCodeProxyConfig, uninstallMacOSProxyEnv, removeVSCodeUserProxyConfig, removeCursorUserProxyConfig } from '../../lib/hooks-config.js';\nimport { removeStatuslineShim } from '../../lib/statusline-shim.js';\n\nconst CERT_PATH = join(homedir(), '.mitmproxy', 'mitmproxy-ca-cert.pem');\n\n/**\n * Best-effort reverse of install's trustCACert(): remove the mitmproxy root CA\n * from the system trust store. Without this, `hooks uninstall` leaves a MITM\n * root trusted system-wide forever \u2014 a real security residue of a supposedly\n * complete reversal. Runs sudo (symmetric with install, which sudo-trusts it).\n */\nfunction untrustCACert(): boolean {\n if (!existsSync(CERT_PATH)) return false;\n if (process.platform === 'darwin') {\n return spawnSync('sudo', ['security', 'remove-trusted-cert', '-d', CERT_PATH], { stdio: 'inherit' }).status === 0;\n }\n if (process.platform === 'linux') {\n const rm = spawnSync('sudo', ['rm', '-f', '/usr/local/share/ca-certificates/mitmproxy-ca.crt'], { stdio: 'inherit' });\n if (rm.status !== 0) return false;\n return spawnSync('sudo', ['update-ca-certificates', '--fresh'], { stdio: 'inherit' }).status === 0;\n }\n if (process.platform === 'win32') {\n return spawnSync('certutil', ['-delstore', 'Root', 'mitmproxy'], { stdio: 'inherit' }).status === 0;\n }\n return false;\n}\n\nexport default class HooksUninstall extends BaseCommand {\n static override description = 'Remove session tracking hooks from the current project';\n\n static override examples = [\n '<%= config.bin %> hooks uninstall',\n ];\n\n async run(): Promise<void> {\n const configPath = join(process.cwd(), '.claude', 'settings.json');\n\n if (!existsSync(configPath)) {\n this.log('No .claude/settings.json found \u2014 nothing to uninstall.');\n return;\n }\n\n // Guard the read: a malformed settings.json must not crash uninstall (the\n // one command a user runs precisely because things are broken).\n const { settings, recoveredBackup } = readClaudeSettings(configPath);\n if (recoveredBackup) {\n this.log(`[!!] ${configPath} was not valid JSON \u2014 backed up to ${recoveredBackup}; continuing.`);\n }\n\n let changed = false;\n\n // Remove only RuleMetric's hooks \u2014 preserve the user's own. The old wholesale\n // `delete settings.hooks` also nuked unrelated project hooks.\n if (removeClaudeCodeHooks(settings)) {\n this.log('Removed RuleMetric hooks from .claude/settings.json');\n changed = true;\n }\n\n // Remove gateway proxy env (HTTPS_PROXY + NO_PROXY + NODE_EXTRA_CA_CERTS \u2014\n // install writes all three; leaving NO_PROXY behind is a stray orphan).\n const env = settings.env as Record<string, string> | undefined;\n let removedProxyEnv = false;\n if (env && (env.HTTPS_PROXY || env.NO_PROXY || env.NODE_EXTRA_CA_CERTS)) {\n delete env.HTTPS_PROXY;\n delete env.NO_PROXY;\n delete env.NODE_EXTRA_CA_CERTS;\n if (Object.keys(env).length === 0) {\n delete settings.env;\n }\n this.log('Removed HTTPS_PROXY, NO_PROXY and NODE_EXTRA_CA_CERTS from .claude/settings.json');\n changed = true;\n removedProxyEnv = true;\n }\n\n // Remove our statusline shim (otherwise Claude Code keeps POSTing usage via it).\n if (removeStatuslineShim(settings)) {\n this.log('Removed RuleMetric statusline shim from .claude/settings.json');\n changed = true;\n }\n\n if (changed) {\n writeClaudeSettingsWithBackup(configPath, settings);\n } else {\n this.log('No hooks or proxy config found \u2014 nothing to uninstall.');\n }\n\n // Remove Cursor hooks\n // Remove Cursor and Copilot hooks\n if (removeCursorHooks(process.cwd())) {\n this.log('Removed RuleMetric hooks from .cursor/hooks.json');\n changed = true;\n }\n if (removeCursorUserHooks()) {\n this.log('Removed RuleMetric hooks from ~/.cursor/hooks.json');\n changed = true;\n }\n\n // Remove Cursor proxy config\n const cursorSettingsPath = join(process.cwd(), '.cursor', 'settings.json');\n if (existsSync(cursorSettingsPath)) {\n try {\n const cursorSettings = JSON.parse(readFileSync(cursorSettingsPath, 'utf-8'));\n if (cursorSettings['http.proxy']?.includes('localhost')) {\n delete cursorSettings['http.proxy'];\n delete cursorSettings['http.proxyStrictSSL'];\n writeFileSync(cursorSettingsPath, JSON.stringify(cursorSettings, null, 2) + '\\n');\n this.log('Removed proxy config from .cursor/settings.json');\n changed = true;\n }\n } catch { /* not valid JSON, skip */ }\n }\n\n // Remove Copilot hooks\n if (removeCopilotHooks(process.cwd())) {\n this.log('Removed RuleMetric hooks from .github/hooks/');\n changed = true;\n }\n\n // Remove Antigravity hooks (workspace + user)\n if (removeAntigravityHooks(process.cwd())) {\n this.log('Removed RuleMetric block from .agents/hooks.json');\n changed = true;\n }\n if (removeAntigravityUserHooks()) {\n this.log('Removed RuleMetric block from ~/.gemini/config/hooks.json');\n changed = true;\n }\n\n // Remove VS Code proxy config (workspace + user settings)\n if (removeVSCodeProxyConfig(process.cwd())) {\n this.log('Removed proxy config from .vscode/settings.json');\n changed = true;\n }\n if (removeVSCodeUserProxyConfig()) {\n this.log('Removed proxy config from VS Code user settings');\n changed = true;\n }\n if (removeCursorUserProxyConfig()) {\n this.log('Removed proxy config from Cursor user settings');\n changed = true;\n }\n\n // Remove macOS LaunchAgent + launchctl env vars\n if (uninstallMacOSProxyEnv()) {\n this.log('Removed HTTPS_PROXY LaunchAgent');\n changed = true;\n }\n\n // Untrust the mitmproxy CA (only if this was a proxy install \u2014 hooks-only\n // users never trusted one, so don't sudo-prompt them). This runs sudo,\n // symmetric with install's sudo-trust; without it a MITM root stays trusted.\n if (removedProxyEnv && existsSync(CERT_PATH)) {\n this.log('Removing the mitmproxy CA from the system trust store (requires sudo)...');\n if (untrustCACert()) {\n this.log('[OK] mitmproxy CA untrusted.');\n } else {\n this.log(`[!!] Could not untrust the CA automatically. Remove it manually:`);\n if (process.platform === 'darwin') {\n this.log(` sudo security remove-trusted-cert -d ${CERT_PATH}`);\n } else if (process.platform === 'linux') {\n this.log(` sudo rm -f /usr/local/share/ca-certificates/mitmproxy-ca.crt && sudo update-ca-certificates --fresh`);\n }\n }\n }\n\n // Stop gateway\n if (stopGateway()) {\n this.log('Gateway stopped.');\n }\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -18,7 +18,7 @@ import "../../chunk-NSBPE2FW.js";
|
|
|
18
18
|
import { Flags } from "@oclif/core";
|
|
19
19
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
20
20
|
import { execSync, execFileSync, spawnSync } from "node:child_process";
|
|
21
|
-
import { homedir } from "node:os";
|
|
21
|
+
import { homedir, userInfo } from "node:os";
|
|
22
22
|
import { dirname, join } from "node:path";
|
|
23
23
|
var ALLOWED_ENV_VARS = /* @__PURE__ */ new Set([
|
|
24
24
|
"SUPABASE_URL",
|
|
@@ -367,7 +367,13 @@ var ServiceInstall = class _ServiceInstall extends BaseCommand {
|
|
|
367
367
|
execFileSync("systemctl", ["--user", "daemon-reload"]);
|
|
368
368
|
execFileSync("systemctl", ["--user", "enable", "--now", WORKER_SYSTEMD_UNIT]);
|
|
369
369
|
this.log("[OK] Worker enabled + started (systemctl --user).");
|
|
370
|
-
|
|
370
|
+
try {
|
|
371
|
+
execFileSync("loginctl", ["enable-linger", userInfo().username]);
|
|
372
|
+
this.log("[OK] Enabled linger \u2014 worker survives logout/reboot.");
|
|
373
|
+
} catch {
|
|
374
|
+
this.log("[!!] Could not enable linger (worker will stop on logout).");
|
|
375
|
+
this.log(' Run: sudo loginctl enable-linger "$USER"');
|
|
376
|
+
}
|
|
371
377
|
this.log(` Logs: journalctl --user -u ${WORKER_SYSTEMD_UNIT} -f`);
|
|
372
378
|
} catch (err) {
|
|
373
379
|
this.warn(
|