@rulemetric/cli 0.6.9 → 0.7.1
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/base-command.js +4 -3
- package/dist/{chunk-SZ7VDCD6.js → chunk-2HK7RGZB.js} +22 -3
- package/dist/chunk-2HK7RGZB.js.map +7 -0
- package/dist/{chunk-RUCSRN7F.js → chunk-2ORJWTNZ.js} +2 -2
- package/dist/{chunk-SLL3OBKN.js → chunk-37LN5O7M.js} +2 -2
- package/dist/chunk-3S7223UR.js +81 -0
- package/dist/chunk-3S7223UR.js.map +7 -0
- package/dist/{chunk-DAJNO4OI.js → chunk-4LYCDS2N.js} +28 -16
- package/dist/chunk-4LYCDS2N.js.map +7 -0
- package/dist/{chunk-UPXIO7CG.js → chunk-4XAWK6S2.js} +8 -8
- package/dist/{chunk-QD6X4L4L.js → chunk-5BAOGNUH.js} +4 -4
- package/dist/chunk-5EDMGK3W.js +39 -0
- package/dist/chunk-5EDMGK3W.js.map +7 -0
- package/dist/{chunk-5ADHO5QF.js → chunk-6NBS5XFS.js} +6 -6
- package/dist/{chunk-BBLTN35F.js → chunk-7DULPSFU.js} +49 -20
- package/dist/chunk-7DULPSFU.js.map +7 -0
- package/dist/chunk-7LVVKQMZ.js +98 -0
- package/dist/chunk-7LVVKQMZ.js.map +7 -0
- package/dist/{chunk-PXVQLD7S.js → chunk-7NNFYWKK.js} +48 -1
- package/dist/chunk-7NNFYWKK.js.map +7 -0
- package/dist/{chunk-DTFXCHHI.js → chunk-DNKSVWMD.js} +4 -4
- package/dist/{chunk-CNMWYYSV.js → chunk-DUJSRBLP.js} +2 -2
- package/dist/chunk-EARWG4EV.js +22 -0
- package/dist/chunk-EARWG4EV.js.map +7 -0
- package/dist/{chunk-6EMZI4Y3.js → chunk-EI5BHWXA.js} +437 -401
- package/dist/chunk-EI5BHWXA.js.map +7 -0
- package/dist/{chunk-HNZLEEG2.js → chunk-EJ3YAFDP.js} +4 -4
- package/dist/{chunk-HNZLEEG2.js.map → chunk-EJ3YAFDP.js.map} +2 -2
- package/dist/{chunk-YSEQWCN4.js → chunk-EYPAAGN5.js} +2 -2
- package/dist/{chunk-JEJ3J5J6.js → chunk-GIUBARWS.js} +2 -2
- package/dist/chunk-GQNJO2P2.js +102 -0
- package/dist/chunk-GQNJO2P2.js.map +7 -0
- package/dist/chunk-ILYDELUE.js +46 -0
- package/dist/chunk-ILYDELUE.js.map +7 -0
- package/dist/chunk-J6QFW4LD.js +55 -0
- package/dist/chunk-J6QFW4LD.js.map +7 -0
- package/dist/{chunk-OTOWLUOJ.js → chunk-JULOLTZS.js} +2 -2
- package/dist/{chunk-DR5WLIOU.js → chunk-KJKBDQ4D.js} +2 -2
- package/dist/{chunk-XN23W5HL.js → chunk-LXKPNATC.js} +93 -7
- package/dist/chunk-LXKPNATC.js.map +7 -0
- package/dist/{chunk-N5A2IQAK.js → chunk-OK3I555A.js} +2 -2
- package/dist/{chunk-XNNOXGP6.js → chunk-OKWCKCPE.js} +38 -19
- package/dist/chunk-OKWCKCPE.js.map +7 -0
- package/dist/{chunk-AHDRN6NE.js → chunk-OO7JDFS4.js} +5 -5
- package/dist/{chunk-N2X35ITW.js → chunk-OQBBVTDG.js} +11 -1
- package/dist/{chunk-N2X35ITW.js.map → chunk-OQBBVTDG.js.map} +2 -2
- package/dist/{chunk-3QH73QOQ.js → chunk-PED7L5GX.js} +2 -2
- package/dist/{chunk-VBGUNFKY.js → chunk-PMI56ED5.js} +5 -5
- package/dist/{chunk-RCMXTLQF.js → chunk-RMTT4KDY.js} +2 -2
- package/dist/{chunk-W4MZXKZ2.js → chunk-UKMLICWS.js} +2 -2
- package/dist/{chunk-3POYC5NA.js → chunk-XK42ZBDE.js} +3 -3
- package/dist/{chunk-ZSGKRCKD.js → chunk-XUV6DDBF.js} +2 -2
- package/dist/{chunk-LAKZUE35.js → chunk-Y3WD5CSW.js} +2 -2
- package/dist/{chunk-3QVMIJUL.js → chunk-YOBA3NAM.js} +5 -5
- package/dist/{chunk-ZRLYHBPN.js → chunk-ZUJBM6HE.js} +20 -2
- package/dist/chunk-ZUJBM6HE.js.map +7 -0
- package/dist/commands/antigravity/backfill.js +5 -4
- package/dist/commands/antigravity/backfill.js.map +1 -1
- package/dist/commands/auth/create-key.js +4 -3
- package/dist/commands/auth/create-key.js.map +1 -1
- package/dist/commands/auth/login.js +13 -9
- package/dist/commands/auth/login.js.map +2 -2
- package/dist/commands/auth/logout.js +4 -3
- package/dist/commands/auth/logout.js.map +1 -1
- package/dist/commands/auth/revoke-key.js +4 -3
- package/dist/commands/auth/revoke-key.js.map +1 -1
- package/dist/commands/auth/signup.js +13 -9
- package/dist/commands/auth/signup.js.map +2 -2
- package/dist/commands/auth/status.js +4 -3
- package/dist/commands/auth/status.js.map +1 -1
- package/dist/commands/catalog/index.js +4 -3
- package/dist/commands/catalog/index.js.map +1 -1
- package/dist/commands/catalog/recommend.js +4 -3
- package/dist/commands/catalog/recommend.js.map +1 -1
- package/dist/commands/convert.js +4 -3
- package/dist/commands/convert.js.map +1 -1
- package/dist/commands/converters.js +4 -3
- package/dist/commands/converters.js.map +1 -1
- package/dist/commands/crons/list.js +4 -3
- package/dist/commands/crons/list.js.map +1 -1
- package/dist/commands/crons/run.js +4 -3
- package/dist/commands/crons/run.js.map +1 -1
- package/dist/commands/dashboard.js +4 -3
- package/dist/commands/dashboard.js.map +1 -1
- package/dist/commands/diagnostics.js +117 -0
- package/dist/commands/diagnostics.js.map +7 -0
- package/dist/commands/eval-targets/create.js +4 -3
- package/dist/commands/eval-targets/create.js.map +1 -1
- package/dist/commands/eval-targets/list.js +4 -3
- package/dist/commands/eval-targets/list.js.map +1 -1
- package/dist/commands/evals/agent.js +41 -20
- package/dist/commands/evals/agent.js.map +2 -2
- package/dist/commands/evals/benchmark.js +5 -4
- package/dist/commands/evals/benchmark.js.map +1 -1
- package/dist/commands/evals/compare.js +4 -3
- package/dist/commands/evals/compare.js.map +1 -1
- package/dist/commands/evals/create.js +4 -3
- package/dist/commands/evals/create.js.map +1 -1
- package/dist/commands/evals/optimize-description.js +4 -3
- package/dist/commands/evals/optimize-description.js.map +1 -1
- package/dist/commands/evals/optimize.js +4 -3
- package/dist/commands/evals/optimize.js.map +1 -1
- package/dist/commands/evals/run.js +4 -3
- package/dist/commands/evals/run.js.map +1 -1
- package/dist/commands/gateway/ensure.js +75 -5
- package/dist/commands/gateway/ensure.js.map +2 -2
- package/dist/commands/hooks/install.js +76 -23
- package/dist/commands/hooks/install.js.map +2 -2
- package/dist/commands/hooks/run.js +4 -3
- package/dist/commands/hooks/run.js.map +1 -1
- package/dist/commands/hooks/uninstall.js +31 -40
- package/dist/commands/hooks/uninstall.js.map +2 -2
- package/dist/commands/import.js +4 -3
- package/dist/commands/import.js.map +1 -1
- package/dist/commands/instructions/create.js +4 -3
- package/dist/commands/instructions/create.js.map +1 -1
- package/dist/commands/instructions/delete.js +4 -3
- package/dist/commands/instructions/delete.js.map +1 -1
- package/dist/commands/instructions/fork.js +4 -3
- package/dist/commands/instructions/fork.js.map +1 -1
- package/dist/commands/instructions/get.js +4 -3
- package/dist/commands/instructions/get.js.map +1 -1
- package/dist/commands/instructions/list.js +4 -3
- package/dist/commands/instructions/list.js.map +1 -1
- package/dist/commands/instructions/promote.js +4 -3
- package/dist/commands/instructions/promote.js.map +1 -1
- package/dist/commands/instructions/pull.js +4 -3
- package/dist/commands/instructions/pull.js.map +1 -1
- package/dist/commands/instructions/upstream.js +4 -3
- package/dist/commands/instructions/upstream.js.map +1 -1
- package/dist/commands/instructions/versions.js +4 -3
- package/dist/commands/instructions/versions.js.map +1 -1
- package/dist/commands/launcher.js +4 -3
- package/dist/commands/launcher.js.map +1 -1
- package/dist/commands/org/current.js +4 -3
- package/dist/commands/org/current.js.map +1 -1
- package/dist/commands/org/list.js +4 -3
- package/dist/commands/org/list.js.map +1 -1
- package/dist/commands/org/switch.js +4 -3
- package/dist/commands/org/switch.js.map +1 -1
- package/dist/commands/packs/add-instruction.js +4 -3
- package/dist/commands/packs/add-instruction.js.map +1 -1
- package/dist/commands/packs/apply.js +4 -3
- package/dist/commands/packs/apply.js.map +1 -1
- package/dist/commands/packs/create.js +4 -3
- package/dist/commands/packs/create.js.map +1 -1
- package/dist/commands/packs/fork.js +4 -3
- package/dist/commands/packs/fork.js.map +1 -1
- package/dist/commands/packs/get.js +4 -3
- package/dist/commands/packs/get.js.map +1 -1
- package/dist/commands/packs/list.js +4 -3
- package/dist/commands/packs/list.js.map +1 -1
- package/dist/commands/packs/remove-instruction.js +4 -3
- package/dist/commands/packs/remove-instruction.js.map +1 -1
- package/dist/commands/proxy/env.js +7 -4
- package/dist/commands/proxy/env.js.map +1 -1
- package/dist/commands/proxy/logs.js +4 -3
- package/dist/commands/proxy/logs.js.map +1 -1
- package/dist/commands/proxy/restart.js +4 -3
- package/dist/commands/proxy/restart.js.map +1 -1
- package/dist/commands/proxy/setup.js +20 -14
- package/dist/commands/proxy/setup.js.map +2 -2
- package/dist/commands/proxy/start.js +29 -15
- package/dist/commands/proxy/start.js.map +2 -2
- package/dist/commands/proxy/status.js +16 -13
- package/dist/commands/proxy/status.js.map +2 -2
- package/dist/commands/proxy/stop.js +21 -12
- package/dist/commands/proxy/stop.js.map +2 -2
- package/dist/commands/research/backfill.js +5 -4
- package/dist/commands/research/backfill.js.map +1 -1
- package/dist/commands/research/tail-transcript.js +5 -4
- package/dist/commands/research/tail-transcript.js.map +1 -1
- package/dist/commands/service/install.js +6 -6
- package/dist/commands/service/status.js +4 -3
- package/dist/commands/service/status.js.map +1 -1
- package/dist/commands/service/uninstall.js +4 -3
- package/dist/commands/service/uninstall.js.map +1 -1
- package/dist/commands/sessions/analyze.js +4 -3
- package/dist/commands/sessions/analyze.js.map +1 -1
- package/dist/commands/sessions/end.js +4 -3
- package/dist/commands/sessions/end.js.map +1 -1
- package/dist/commands/sessions/import.js +4 -3
- package/dist/commands/sessions/import.js.map +1 -1
- package/dist/commands/sessions/list.js +4 -3
- package/dist/commands/sessions/list.js.map +1 -1
- package/dist/commands/sessions/start.js +4 -3
- package/dist/commands/sessions/start.js.map +1 -1
- package/dist/commands/setup.js +10 -6
- package/dist/commands/setup.js.map +1 -1
- package/dist/commands/skills/export-approved.js +4 -3
- package/dist/commands/skills/export-approved.js.map +1 -1
- package/dist/commands/skills/install.js +4 -3
- package/dist/commands/skills/install.js.map +1 -1
- package/dist/commands/skills/list.js +4 -3
- package/dist/commands/skills/list.js.map +1 -1
- package/dist/commands/skills/publish.js +4 -3
- package/dist/commands/skills/publish.js.map +1 -1
- package/dist/commands/skills/search.js +4 -3
- package/dist/commands/skills/search.js.map +1 -1
- package/dist/commands/skills/update.js +4 -3
- package/dist/commands/skills/update.js.map +1 -1
- package/dist/commands/suggestions/accept.js +4 -3
- package/dist/commands/suggestions/accept.js.map +1 -1
- package/dist/commands/suggestions/dismiss.js +4 -3
- package/dist/commands/suggestions/dismiss.js.map +1 -1
- package/dist/commands/suggestions/list.js +4 -3
- package/dist/commands/suggestions/list.js.map +1 -1
- package/dist/commands/suggestions/refresh.js +4 -3
- package/dist/commands/suggestions/refresh.js.map +1 -1
- package/dist/dashboard/Dashboard.js +16 -16
- package/dist/dashboard/data.js +2 -2
- package/dist/dashboard/launcher/ActiveJobsList.js +2 -2
- package/dist/dashboard/launcher/LauncherOverlay.js +7 -7
- package/dist/dashboard/launcher/NewLaunchForm.js +2 -2
- package/dist/dashboard/launcher/api.js +2 -2
- package/dist/dashboard/launcher/useLaunchJobs.js +3 -3
- package/dist/lib/active-org-refresh.js +2 -2
- package/dist/lib/agent-loop.js +19 -17
- package/dist/lib/antigravity-transcript.js +2 -2
- package/dist/lib/api-client.js +1 -1
- package/dist/lib/capture-hosts.js +8 -0
- package/dist/lib/capture-hosts.js.map +7 -0
- package/dist/lib/diagnostics.js +12 -0
- package/dist/lib/diagnostics.js.map +7 -0
- package/dist/lib/editor-proxy-pins.js +14 -0
- package/dist/lib/editor-proxy-pins.js.map +7 -0
- package/dist/lib/ensure-api-key.js +3 -3
- package/dist/lib/eval-benchmark.js +2 -2
- package/dist/lib/eval-conversation-handler.js +2 -2
- package/dist/lib/gateway-entry.js +39 -1
- package/dist/lib/gateway-entry.js.map +2 -2
- package/dist/lib/gateway-identity.js +16 -0
- package/dist/lib/gateway-identity.js.map +7 -0
- package/dist/lib/gateway-lifecycle.js +9 -3
- package/dist/lib/gateway.js +3 -1
- package/dist/lib/handlers/cron-suggest-instructions.js +3 -3
- package/dist/lib/handlers/process-announcement.js +3 -3
- package/dist/lib/handlers/process-changelog.js +2 -2
- package/dist/lib/handlers/process-conversation.js +3 -3
- package/dist/lib/handlers/process-eval.js +2 -2
- package/dist/lib/handlers/process-insights.js +2 -2
- package/dist/lib/handlers/process-launch.js +2 -2
- package/dist/lib/handlers/process-run-cleanup.js +2 -2
- package/dist/lib/handlers/process-session-goal.js +2 -2
- package/dist/lib/hooks-config.js +18 -1
- package/dist/lib/manual-tasks.js +5 -5
- package/dist/lib/pid-identity.js +14 -0
- package/dist/lib/pid-identity.js.map +7 -0
- package/dist/lib/proxy-respawn.js +12 -0
- package/dist/lib/proxy-respawn.js.map +7 -0
- package/dist/lib/research-client.js +2 -2
- package/dist/lib/setup-steps.js +7 -4
- package/dist/lib/statusline-shim.js +3 -1
- package/dist/lib/telemetry.js +4 -2
- package/dist/lib/terminal-target-heartbeat.js +2 -2
- package/oclif.manifest.json +34 -1
- package/package.json +4 -4
- package/dist/chunk-6EMZI4Y3.js.map +0 -7
- package/dist/chunk-BBLTN35F.js.map +0 -7
- package/dist/chunk-DAJNO4OI.js.map +0 -7
- package/dist/chunk-PXVQLD7S.js.map +0 -7
- package/dist/chunk-SZ7VDCD6.js.map +0 -7
- package/dist/chunk-TX2HKJLA.js +0 -55
- package/dist/chunk-TX2HKJLA.js.map +0 -7
- package/dist/chunk-XN23W5HL.js.map +0 -7
- package/dist/chunk-XNNOXGP6.js.map +0 -7
- package/dist/chunk-ZRLYHBPN.js.map +0 -7
- /package/dist/{chunk-RUCSRN7F.js.map → chunk-2ORJWTNZ.js.map} +0 -0
- /package/dist/{chunk-SLL3OBKN.js.map → chunk-37LN5O7M.js.map} +0 -0
- /package/dist/{chunk-UPXIO7CG.js.map → chunk-4XAWK6S2.js.map} +0 -0
- /package/dist/{chunk-QD6X4L4L.js.map → chunk-5BAOGNUH.js.map} +0 -0
- /package/dist/{chunk-5ADHO5QF.js.map → chunk-6NBS5XFS.js.map} +0 -0
- /package/dist/{chunk-DTFXCHHI.js.map → chunk-DNKSVWMD.js.map} +0 -0
- /package/dist/{chunk-CNMWYYSV.js.map → chunk-DUJSRBLP.js.map} +0 -0
- /package/dist/{chunk-YSEQWCN4.js.map → chunk-EYPAAGN5.js.map} +0 -0
- /package/dist/{chunk-JEJ3J5J6.js.map → chunk-GIUBARWS.js.map} +0 -0
- /package/dist/{chunk-OTOWLUOJ.js.map → chunk-JULOLTZS.js.map} +0 -0
- /package/dist/{chunk-DR5WLIOU.js.map → chunk-KJKBDQ4D.js.map} +0 -0
- /package/dist/{chunk-N5A2IQAK.js.map → chunk-OK3I555A.js.map} +0 -0
- /package/dist/{chunk-AHDRN6NE.js.map → chunk-OO7JDFS4.js.map} +0 -0
- /package/dist/{chunk-3QH73QOQ.js.map → chunk-PED7L5GX.js.map} +0 -0
- /package/dist/{chunk-VBGUNFKY.js.map → chunk-PMI56ED5.js.map} +0 -0
- /package/dist/{chunk-RCMXTLQF.js.map → chunk-RMTT4KDY.js.map} +0 -0
- /package/dist/{chunk-W4MZXKZ2.js.map → chunk-UKMLICWS.js.map} +0 -0
- /package/dist/{chunk-3POYC5NA.js.map → chunk-XK42ZBDE.js.map} +0 -0
- /package/dist/{chunk-ZSGKRCKD.js.map → chunk-XUV6DDBF.js.map} +0 -0
- /package/dist/{chunk-LAKZUE35.js.map → chunk-Y3WD5CSW.js.map} +0 -0
- /package/dist/{chunk-3QVMIJUL.js.map → chunk-YOBA3NAM.js.map} +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/commands/proxy/setup.ts"],
|
|
4
|
-
"sourcesContent": ["import { Flags } from '@oclif/core';\nimport { execSync, spawnSync } from 'node:child_process';\nimport { existsSync, readFileSync, appendFileSync, writeFileSync, mkdirSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { dirname, join } from 'node:path';\nimport { BaseCommand } from '../../base-command.js';\nimport { isAuthenticated } from '../../lib/auth.js';\nimport { findBinary } from '../../lib/which.js';\n\nconst CERT_PATH = join(homedir(), '.mitmproxy', 'mitmproxy-ca-cert.pem');\nconst CONFIG_DIR = join(homedir(), '.config', 'rulemetric');\nconst DEFAULT_PORT = 8787;\n\ninterface CheckResult {\n name: string;\n ok: boolean;\n message: string;\n fix?: () => Promise<boolean>;\n}\n\nexport default class ProxySetup extends BaseCommand {\n static override description = 'Set up context capture proxy (installs mitmproxy, configures shell)';\n\n static override examples = [\n '<%= config.bin %> proxy setup',\n '<%= config.bin %> proxy setup --shell zsh',\n '<%= config.bin %> proxy setup --check',\n ];\n\n static override flags = {\n shell: Flags.string({\n description: 'Shell to configure (auto-detected if omitted)',\n options: ['zsh', 'bash', 'fish', 'powershell'],\n }),\n port: Flags.integer({\n char: 'p',\n default: DEFAULT_PORT,\n description: 'Proxy listen port',\n }),\n check: Flags.boolean({\n description: 'Only check prerequisites, do not install anything',\n default: false,\n }),\n };\n\n async run(): Promise<void> {\n const { flags } = await this.parse(ProxySetup);\n const checkOnly = flags.check;\n const port = flags.port;\n\n this.log('RuleMetric Proxy Setup');\n this.log('=====================\\n');\n\n const checks: CheckResult[] = [\n await this.checkAuth(),\n await this.checkMitmproxy(),\n await this.checkCACert(),\n await this.checkHooks(),\n this.checkShellEnv(port),\n ];\n\n // Display results\n let allPassed = true;\n for (const check of checks) {\n const icon = check.ok ? '[OK]' : '[!!]';\n this.log(`${icon} ${check.name}: ${check.message}`);\n if (!check.ok) allPassed = false;\n }\n\n if (allPassed) {\n this.log('\\nAll checks passed. Run `rulemetric proxy start` to begin capturing.');\n return;\n }\n\n if (checkOnly) {\n this.log('\\nSome checks failed. Run `rulemetric proxy setup` (without --check) to fix.');\n return;\n }\n\n // Fix failures\n this.log('\\nFixing issues...\\n');\n\n for (const check of checks) {\n if (check.ok || !check.fix) continue;\n this.log(`Fixing: ${check.name}...`);\n const fixed = await check.fix();\n if (fixed) {\n this.log(` Fixed.\\n`);\n } else {\n this.log(` Could not auto-fix. See instructions above.\\n`);\n }\n }\n\n // Re-check\n this.log('---\\nVerifying...\\n');\n const reChecks = [\n await this.checkAuth(),\n await this.checkMitmproxy(),\n await this.checkCACert(),\n await this.checkHooks(),\n this.checkShellEnv(port),\n ];\n\n let finalOk = true;\n for (const check of reChecks) {\n const icon = check.ok ? '[OK]' : '[!!]';\n this.log(`${icon} ${check.name}: ${check.message}`);\n if (!check.ok) finalOk = false;\n }\n\n if (finalOk) {\n this.log('\\nSetup complete! Next steps:');\n this.log(' 1. Restart your shell (or run `source ~/.zshrc`)');\n this.log(' 2. Run `rulemetric proxy start`');\n this.log(' 3. Start Claude Code \u2014 context will be captured automatically');\n } else {\n this.log('\\nSome issues remain. Fix manually and re-run `rulemetric proxy setup --check`.');\n }\n }\n\n private async checkAuth(): Promise<CheckResult> {\n // Check JWT auth (auth.json)\n if (isAuthenticated()) {\n return { name: 'Authentication', ok: true, message: 'Logged in (JWT)' };\n }\n\n // Check API key auth (env vars or env file)\n const apiKey = process.env.RULEMETRIC_API_KEY;\n if (apiKey) {\n return { name: 'Authentication', ok: true, message: `API key (RULEMETRIC_API_KEY=${apiKey.slice(0, 8)}...)` };\n }\n\n // Check env file\n const envFile = join(homedir(), '.config', 'rulemetric', 'env');\n if (existsSync(envFile)) {\n const content = readFileSync(envFile, 'utf-8');\n if (content.includes('RULEMETRIC_API_KEY') || content.includes('RULEMETRIC_ACCESS_TOKEN')) {\n return { name: 'Authentication', ok: true, message: 'Configured via ~/.config/rulemetric/env' };\n }\n }\n\n return {\n name: 'Authentication',\n ok: false,\n message: 'Not authenticated',\n fix: async () => {\n this.log(' Run `rulemetric auth login` to authenticate.');\n this.log(' Or set RULEMETRIC_API_KEY in ~/.config/rulemetric/env.');\n return false;\n },\n };\n }\n\n private async checkMitmproxy(): Promise<CheckResult> {\n const binary = this.findBinary('mitmdump');\n if (binary) {\n // Get version\n try {\n const output = execSync(`${binary} --version 2>&1`, { encoding: 'utf-8' });\n const version = output.match(/Mitmproxy:\\s*([\\d.]+)/)?.[1] ?? 'unknown';\n return { name: 'mitmproxy', ok: true, message: `Installed (v${version})` };\n } catch {\n return { name: 'mitmproxy', ok: true, message: `Found at ${binary}` };\n }\n }\n\n return {\n name: 'mitmproxy',\n ok: false,\n message: 'Not installed',\n fix: async () => this.installMitmproxy(),\n };\n }\n\n private async installMitmproxy(): Promise<boolean> {\n // Try pipx first (cleanest isolation)\n if (this.findBinary('pipx')) {\n this.log(' Installing via pipx...');\n const result = spawnSync('pipx', ['install', 'mitmproxy'], { stdio: 'inherit' });\n if (result.status === 0) return true;\n }\n\n // Try uv tool install\n if (this.findBinary('uv')) {\n this.log(' Installing via uv...');\n const result = spawnSync('uv', ['tool', 'install', 'mitmproxy'], { stdio: 'inherit' });\n if (result.status === 0) return true;\n }\n\n // Try pip with --user\n if (this.findBinary('pip3') || this.findBinary('pip')) {\n const pip = this.findBinary('pip3') || this.findBinary('pip')!;\n this.log(` Installing via ${pip} --user...`);\n const result = spawnSync(pip, ['install', '--user', 'mitmproxy'], { stdio: 'inherit' });\n if (result.status === 0) return true;\n }\n\n // Try brew\n if (this.findBinary('brew')) {\n this.log(' Installing via brew...');\n const result = spawnSync('brew', ['install', 'mitmproxy'], { stdio: 'inherit' });\n if (result.status === 0) return true;\n }\n\n this.log(' Could not auto-install mitmproxy. Install manually:');\n this.log(' pipx install mitmproxy # recommended');\n this.log(' brew install mitmproxy # macOS');\n this.log(' pip install --user mitmproxy');\n return false;\n }\n\n private async checkCACert(): Promise<CheckResult> {\n if (existsSync(CERT_PATH)) {\n return { name: 'CA Certificate', ok: true, message: `Found at ${CERT_PATH}` };\n }\n\n return {\n name: 'CA Certificate',\n ok: false,\n message: 'Not generated yet',\n fix: async () => this.generateCACert(),\n };\n }\n\n private async generateCACert(): Promise<boolean> {\n const mitmdump = this.findBinary('mitmdump');\n if (!mitmdump) {\n this.log(' Cannot generate cert \u2014 mitmproxy not installed.');\n return false;\n }\n\n // Run mitmdump briefly to generate certs\n this.log(' Generating CA certificate...');\n const result = spawnSync(mitmdump, ['--set', 'listen_port=0', '-q'], {\n timeout: 5000,\n stdio: 'ignore',\n });\n\n // mitmdump will timeout (expected) \u2014 certs should be generated\n if (existsSync(CERT_PATH)) {\n this.log(` Certificate generated at ${CERT_PATH}`);\n\n // On macOS, offer to trust the cert\n if (process.platform === 'darwin') {\n this.log(' Adding CA cert to macOS keychain (may require sudo)...');\n const trustResult = spawnSync('sudo', [\n 'security', 'add-trusted-cert', '-d',\n '-r', 'trustRoot',\n '-k', '/Library/Keychains/System.keychain',\n CERT_PATH,\n ], { stdio: 'inherit' });\n if (trustResult.status === 0) {\n this.log(' CA cert trusted system-wide.');\n } else {\n this.log(' Could not auto-trust cert. You may need NODE_EXTRA_CA_CERTS env var.');\n }\n }\n\n return true;\n }\n\n this.log(' Failed to generate cert. Try running: mitmdump --help');\n return false;\n }\n\n private async checkHooks(): Promise<CheckResult> {\n const settingsPath = join(process.cwd(), '.claude', 'settings.json');\n if (!existsSync(settingsPath)) {\n return {\n name: 'Session Hooks',\n ok: false,\n message: 'Not installed in current project',\n fix: async () => {\n this.log(' Run `rulemetric hooks install` in your project directory.');\n return false; // User needs to run in the right directory\n },\n };\n }\n\n try {\n const settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));\n const hasHooks = settings.hooks && Object.keys(settings.hooks).length > 0;\n if (hasHooks) {\n return { name: 'Session Hooks', ok: true, message: 'Installed in current project' };\n }\n } catch { /* fall through */ }\n\n return {\n name: 'Session Hooks',\n ok: false,\n message: 'settings.json exists but hooks not configured',\n fix: async () => {\n this.log(' Run `rulemetric hooks install` to configure hooks.');\n return false;\n },\n };\n }\n\n private checkShellEnv(port: number): CheckResult {\n const proxySet = process.env.HTTPS_PROXY?.includes(String(port));\n const certSet = !!process.env.NODE_EXTRA_CA_CERTS;\n\n if (proxySet && certSet) {\n return { name: 'Shell Environment', ok: true, message: 'HTTPS_PROXY and NODE_EXTRA_CA_CERTS set' };\n }\n\n const missing: string[] = [];\n if (!proxySet) missing.push('HTTPS_PROXY');\n if (!certSet) missing.push('NODE_EXTRA_CA_CERTS');\n\n return {\n name: 'Shell Environment',\n ok: false,\n message: `Missing: ${missing.join(', ')}`,\n fix: async () => this.writeShellEnv(port),\n };\n }\n\n private async writeShellEnv(port: number): Promise<boolean> {\n const shell = this.detectShell();\n const rcFile = this.getShellRcFile(shell);\n\n if (!rcFile) {\n this.log(` Could not detect shell RC file. Add these to your shell profile:`);\n this.log(` export HTTPS_PROXY=http://localhost:${port}`);\n this.log(` export NODE_EXTRA_CA_CERTS=${CERT_PATH}`);\n return false;\n }\n\n const oldMarker = '# RuleMetric proxy';\n const marker = '# RuleMetric proxy (auto-detect)';\n const envFile = join(CONFIG_DIR, 'proxy.env');\n const pidFile = join(CONFIG_DIR, 'proxy.pid');\n\n // The sentinel proves the block is already installed (idempotency). POSIX\n // shells share one function name; PowerShell uses its own.\n const sentinel = shell === 'powershell' ? '_RuleMetricProxyCheck' : '_rulemetric_proxy_check';\n\n // Dynamic proxy env: re-evaluates on every prompt so stopping the proxy\n // immediately unsets HTTPS_PROXY without needing a new shell.\n const block = shell === 'powershell'\n ? this.buildPowerShellBlock(marker, pidFile, port)\n : shell === 'fish'\n ? [\n '',\n marker,\n 'function _rulemetric_proxy_check',\n ` if test -f ${pidFile}; and kill -0 (cat ${pidFile}) 2>/dev/null`,\n ` source ${envFile} 2>/dev/null`,\n ' else',\n ' set -e HTTPS_PROXY 2>/dev/null',\n ' set -e NODE_EXTRA_CA_CERTS 2>/dev/null',\n ' end',\n 'end',\n '',\n ].join('\\n')\n : shell === 'zsh'\n ? [\n '',\n marker,\n '_rulemetric_proxy_check() {',\n ` if [ -f \"${pidFile}\" ] && kill -0 \"$(cat \"${pidFile}\" 2>/dev/null)\" 2>/dev/null; then`,\n ` [ -z \"\\${HTTPS_PROXY:-}\" ] && source \"${envFile}\" 2>/dev/null`,\n ' else',\n ` [ -n \"\\${HTTPS_PROXY:-}\" ] && unset HTTPS_PROXY NODE_EXTRA_CA_CERTS`,\n ' fi',\n '}',\n 'precmd_functions+=(_rulemetric_proxy_check)',\n '',\n ].join('\\n')\n : [\n '',\n marker,\n '_rulemetric_proxy_check() {',\n ` if [ -f \"${pidFile}\" ] && kill -0 \"$(cat \"${pidFile}\" 2>/dev/null)\" 2>/dev/null; then`,\n ` [ -z \"\\${HTTPS_PROXY:-}\" ] && source \"${envFile}\" 2>/dev/null`,\n ' else',\n ` [ -n \"\\${HTTPS_PROXY:-}\" ] && unset HTTPS_PROXY NODE_EXTRA_CA_CERTS`,\n ' fi',\n '}',\n 'PROMPT_COMMAND=\"_rulemetric_proxy_check${PROMPT_COMMAND:+;$PROMPT_COMMAND}\"',\n '',\n ].join('\\n');\n\n // The PowerShell `$PROFILE` directory frequently does not exist yet; create\n // it (and any other missing rc dir, e.g. fish) before appending.\n mkdirSync(dirname(rcFile), { recursive: true });\n\n if (existsSync(rcFile)) {\n let content = readFileSync(rcFile, 'utf-8');\n\n // Remove old static block if present\n if (content.includes(oldMarker) && !content.includes(marker)) {\n content = content.replace(\n /\\n# RuleMetric proxy\\nexport HTTPS_PROXY=[^\\n]*\\nexport NODE_EXTRA_CA_CERTS=[^\\n]*\\n/,\n '\\n',\n );\n writeFileSync(rcFile, content);\n this.log(` Removed old static env vars from ${rcFile}`);\n }\n\n // Remove old auto-detect block (the one without precmd that doesn't unset on stop)\n if (content.includes('# RuleMetric proxy (auto-detect') && !content.includes('_rulemetric_proxy_check')) {\n content = content.replace(\n /\\n# RuleMetric proxy \\(auto-detect[^\\n]*\\)\\nif \\[.*?\\nfi\\n/s,\n '\\n',\n );\n writeFileSync(rcFile, content);\n this.log(` Upgraded old auto-detect block in ${rcFile}`);\n }\n\n if (content.includes(sentinel)) {\n this.log(` Shell env already configured in ${rcFile}`);\n return true;\n }\n }\n\n this.log(` Appending proxy auto-detect hook to ${rcFile}`);\n this.log(` (HTTPS_PROXY is set/unset automatically when proxy starts/stops)`);\n appendFileSync(rcFile, block);\n return true;\n }\n\n private detectShell(): string {\n // On Windows there is no $SHELL and PowerShell is the de-facto shell.\n if (process.platform === 'win32') return 'powershell';\n const shell = process.env.SHELL || '';\n if (shell.includes('zsh')) return 'zsh';\n if (shell.includes('bash')) return 'bash';\n if (shell.includes('fish')) return 'fish';\n return 'zsh'; // default on macOS\n }\n\n private getShellRcFile(shell: string): string | null {\n const home = homedir();\n switch (shell) {\n case 'zsh': return join(home, '.zshrc');\n case 'bash': return join(home, '.bashrc');\n case 'fish': return join(home, '.config', 'fish', 'config.fish');\n case 'powershell': return this.getPowerShellProfilePath();\n default: return null;\n }\n }\n\n /**\n * Resolve the PowerShell profile that loads for every host (console, VS Code\n * integrated terminal, etc.). We ask PowerShell itself for the canonical path\n * \u2014 guessing `~/Documents/...` is wrong when Documents is OneDrive-redirected.\n * Falls back to the PowerShell 7 default only if no PS binary is reachable.\n */\n private getPowerShellProfilePath(): string {\n const psBin = findBinary('pwsh') || findBinary('powershell');\n if (psBin) {\n try {\n const out = execSync(`\"${psBin}\" -NoProfile -Command \"$PROFILE.CurrentUserAllHosts\"`, {\n encoding: 'utf-8',\n }).trim();\n if (out) return out;\n } catch {\n /* fall through to constructed default */\n }\n }\n return join(homedir(), 'Documents', 'PowerShell', 'profile.ps1');\n }\n\n /**\n * Self-contained PowerShell prompt hook. Unlike the POSIX blocks it does NOT\n * source `proxy.env` (that file is `export KEY=val` syntax PowerShell can't\n * read) \u2014 it embeds the values directly. Each prompt it sets HTTPS_PROXY while\n * the proxy PID is alive and removes the vars once it stops, so a dead proxy\n * never leaves a dangling HTTPS_PROXY that would break Claude Code.\n */\n private buildPowerShellBlock(marker: string, pidFile: string, port: number): string {\n const proxyUrl = `http://localhost:${port}`;\n const psPid = pidFile.replace(/'/g, \"''\");\n const psCert = CERT_PATH.replace(/'/g, \"''\");\n return [\n '',\n marker,\n 'function _RuleMetricProxyCheck {',\n ` $pidFile = '${psPid}'`,\n ' $alive = $false',\n ' if (Test-Path $pidFile) {',\n ' $procId = (Get-Content $pidFile -ErrorAction SilentlyContinue | Select-Object -First 1)',\n ' if ($procId -match \\'^\\\\d+$\\' -and (Get-Process -Id ([int]$procId) -ErrorAction SilentlyContinue)) {',\n ' $alive = $true',\n ' }',\n ' }',\n ' if ($alive) {',\n ` $env:HTTPS_PROXY = '${proxyUrl}'`,\n ` $env:HTTP_PROXY = '${proxyUrl}'`,\n ` $env:NODE_EXTRA_CA_CERTS = '${psCert}'`,\n ' } else {',\n ' Remove-Item Env:HTTPS_PROXY, Env:HTTP_PROXY, Env:NODE_EXTRA_CA_CERTS -ErrorAction SilentlyContinue',\n ' }',\n '}',\n '# Wrap the existing prompt once so the check runs every prompt.',\n 'if (-not (Test-Path Function:\\\\_RuleMetricOriginalPrompt)) {',\n ' $existing = Get-Command prompt -ErrorAction SilentlyContinue',\n ' if ($existing) { Set-Item Function:\\\\_RuleMetricOriginalPrompt $existing.ScriptBlock }',\n '}',\n 'function prompt {',\n ' _RuleMetricProxyCheck',\n ' if (Test-Path Function:\\\\_RuleMetricOriginalPrompt) { & _RuleMetricOriginalPrompt } else { \"PS $($PWD.Path)> \" }',\n '}',\n '',\n ].join('\\n');\n }\n\n private findBinary(name: string): string | null {\n // Portable PATH scan \u2014 `which` does not exist on Windows.\n return findBinary(name);\n }\n}\n"],
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["import { Flags } from '@oclif/core';\nimport { execSync, spawnSync } from 'node:child_process';\nimport { existsSync, readFileSync, appendFileSync, writeFileSync, mkdirSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { dirname, join } from 'node:path';\nimport { BaseCommand } from '../../base-command.js';\nimport { isAuthenticated } from '../../lib/auth.js';\nimport { findBinary } from '../../lib/which.js';\n\nconst CERT_PATH = join(homedir(), '.mitmproxy', 'mitmproxy-ca-cert.pem');\nconst CONFIG_DIR = join(homedir(), '.config', 'rulemetric');\nconst DEFAULT_PORT = 8787;\n\ninterface CheckResult {\n name: string;\n ok: boolean;\n message: string;\n fix?: () => Promise<boolean>;\n}\n\nexport default class ProxySetup extends BaseCommand {\n static override description = 'Set up context capture proxy (installs mitmproxy, configures shell)';\n\n static override examples = [\n '<%= config.bin %> proxy setup',\n '<%= config.bin %> proxy setup --shell zsh',\n '<%= config.bin %> proxy setup --check',\n ];\n\n static override flags = {\n shell: Flags.string({\n description: 'Shell to configure (auto-detected if omitted)',\n options: ['zsh', 'bash', 'fish', 'powershell'],\n }),\n port: Flags.integer({\n char: 'p',\n default: DEFAULT_PORT,\n description: 'Proxy listen port',\n }),\n check: Flags.boolean({\n description: 'Only check prerequisites, do not install anything',\n default: false,\n }),\n };\n\n async run(): Promise<void> {\n const { flags } = await this.parse(ProxySetup);\n const checkOnly = flags.check;\n const port = flags.port;\n\n this.log('RuleMetric Proxy Setup');\n this.log('=====================\\n');\n\n const checks: CheckResult[] = [\n await this.checkAuth(),\n await this.checkMitmproxy(),\n await this.checkCACert(),\n await this.checkHooks(),\n this.checkShellEnv(port),\n ];\n\n // Display results\n let allPassed = true;\n for (const check of checks) {\n const icon = check.ok ? '[OK]' : '[!!]';\n this.log(`${icon} ${check.name}: ${check.message}`);\n if (!check.ok) allPassed = false;\n }\n\n if (allPassed) {\n this.log('\\nAll checks passed. Run `rulemetric proxy start` to begin capturing.');\n return;\n }\n\n if (checkOnly) {\n this.log('\\nSome checks failed. Run `rulemetric proxy setup` (without --check) to fix.');\n return;\n }\n\n // Fix failures\n this.log('\\nFixing issues...\\n');\n\n for (const check of checks) {\n if (check.ok || !check.fix) continue;\n this.log(`Fixing: ${check.name}...`);\n const fixed = await check.fix();\n if (fixed) {\n this.log(` Fixed.\\n`);\n } else {\n this.log(` Could not auto-fix. See instructions above.\\n`);\n }\n }\n\n // Re-check\n this.log('---\\nVerifying...\\n');\n const reChecks = [\n await this.checkAuth(),\n await this.checkMitmproxy(),\n await this.checkCACert(),\n await this.checkHooks(),\n this.checkShellEnv(port),\n ];\n\n let finalOk = true;\n for (const check of reChecks) {\n const icon = check.ok ? '[OK]' : '[!!]';\n this.log(`${icon} ${check.name}: ${check.message}`);\n if (!check.ok) finalOk = false;\n }\n\n if (finalOk) {\n this.log('\\nSetup complete! Next steps:');\n this.log(' 1. Restart your shell (or run `source ~/.zshrc`)');\n this.log(' 2. Run `rulemetric proxy start`');\n this.log(' 3. Start Claude Code \u2014 context will be captured automatically');\n } else {\n this.log('\\nSome issues remain. Fix manually and re-run `rulemetric proxy setup --check`.');\n }\n }\n\n private async checkAuth(): Promise<CheckResult> {\n // Check JWT auth (auth.json)\n if (isAuthenticated()) {\n return { name: 'Authentication', ok: true, message: 'Logged in (JWT)' };\n }\n\n // Check API key auth (env vars or env file)\n const apiKey = process.env.RULEMETRIC_API_KEY;\n if (apiKey) {\n return { name: 'Authentication', ok: true, message: `API key (RULEMETRIC_API_KEY=${apiKey.slice(0, 8)}...)` };\n }\n\n // Check env file\n const envFile = join(homedir(), '.config', 'rulemetric', 'env');\n if (existsSync(envFile)) {\n const content = readFileSync(envFile, 'utf-8');\n if (content.includes('RULEMETRIC_API_KEY') || content.includes('RULEMETRIC_ACCESS_TOKEN')) {\n return { name: 'Authentication', ok: true, message: 'Configured via ~/.config/rulemetric/env' };\n }\n }\n\n return {\n name: 'Authentication',\n ok: false,\n message: 'Not authenticated',\n fix: async () => {\n this.log(' Run `rulemetric auth login` to authenticate.');\n this.log(' Or set RULEMETRIC_API_KEY in ~/.config/rulemetric/env.');\n return false;\n },\n };\n }\n\n private async checkMitmproxy(): Promise<CheckResult> {\n const binary = this.findBinary('mitmdump');\n if (binary) {\n // Get version\n try {\n const output = execSync(`${binary} --version 2>&1`, { encoding: 'utf-8' });\n const version = output.match(/Mitmproxy:\\s*([\\d.]+)/)?.[1] ?? 'unknown';\n return { name: 'mitmproxy', ok: true, message: `Installed (v${version})` };\n } catch {\n return { name: 'mitmproxy', ok: true, message: `Found at ${binary}` };\n }\n }\n\n return {\n name: 'mitmproxy',\n ok: false,\n message: 'Not installed',\n fix: async () => this.installMitmproxy(),\n };\n }\n\n private async installMitmproxy(): Promise<boolean> {\n // Try pipx first (cleanest isolation)\n if (this.findBinary('pipx')) {\n this.log(' Installing via pipx...');\n const result = spawnSync('pipx', ['install', 'mitmproxy'], { stdio: 'inherit' });\n if (result.status === 0) return true;\n }\n\n // Try uv tool install\n if (this.findBinary('uv')) {\n this.log(' Installing via uv...');\n const result = spawnSync('uv', ['tool', 'install', 'mitmproxy'], { stdio: 'inherit' });\n if (result.status === 0) return true;\n }\n\n // Try pip with --user\n if (this.findBinary('pip3') || this.findBinary('pip')) {\n const pip = this.findBinary('pip3') || this.findBinary('pip')!;\n this.log(` Installing via ${pip} --user...`);\n const result = spawnSync(pip, ['install', '--user', 'mitmproxy'], { stdio: 'inherit' });\n if (result.status === 0) return true;\n }\n\n // Try brew\n if (this.findBinary('brew')) {\n this.log(' Installing via brew...');\n const result = spawnSync('brew', ['install', 'mitmproxy'], { stdio: 'inherit' });\n if (result.status === 0) return true;\n }\n\n this.log(' Could not auto-install mitmproxy. Install manually:');\n this.log(' pipx install mitmproxy # recommended');\n this.log(' brew install mitmproxy # macOS');\n this.log(' pip install --user mitmproxy');\n return false;\n }\n\n private async checkCACert(): Promise<CheckResult> {\n if (existsSync(CERT_PATH)) {\n return { name: 'CA Certificate', ok: true, message: `Found at ${CERT_PATH}` };\n }\n\n return {\n name: 'CA Certificate',\n ok: false,\n message: 'Not generated yet',\n fix: async () => this.generateCACert(),\n };\n }\n\n private async generateCACert(): Promise<boolean> {\n const mitmdump = this.findBinary('mitmdump');\n if (!mitmdump) {\n this.log(' Cannot generate cert \u2014 mitmproxy not installed.');\n return false;\n }\n\n // Run mitmdump briefly to generate certs\n this.log(' Generating CA certificate...');\n const result = spawnSync(mitmdump, ['--set', 'listen_port=0', '-q'], {\n timeout: 5000,\n stdio: 'ignore',\n });\n\n // mitmdump will timeout (expected) \u2014 certs should be generated\n if (existsSync(CERT_PATH)) {\n this.log(` Certificate generated at ${CERT_PATH}`);\n\n // On macOS, offer to trust the cert\n if (process.platform === 'darwin') {\n this.log(' Adding CA cert to macOS keychain (may require sudo)...');\n const trustResult = spawnSync('sudo', [\n 'security', 'add-trusted-cert', '-d',\n '-r', 'trustRoot',\n '-k', '/Library/Keychains/System.keychain',\n CERT_PATH,\n ], { stdio: 'inherit' });\n if (trustResult.status === 0) {\n this.log(' CA cert trusted system-wide.');\n } else {\n this.log(' Could not auto-trust cert. You may need NODE_EXTRA_CA_CERTS env var.');\n }\n }\n\n return true;\n }\n\n this.log(' Failed to generate cert. Try running: mitmdump --help');\n return false;\n }\n\n private async checkHooks(): Promise<CheckResult> {\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.\n let sawSettingsFile = false;\n for (const fileName of ['settings.local.json', 'settings.json']) {\n const settingsPath = join(process.cwd(), '.claude', fileName);\n if (!existsSync(settingsPath)) continue;\n sawSettingsFile = true;\n try {\n const settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));\n const hasHooks = settings.hooks && Object.keys(settings.hooks).length > 0;\n if (hasHooks) {\n return { name: 'Session Hooks', ok: true, message: 'Installed in current project' };\n }\n } catch { /* fall through */ }\n }\n\n if (!sawSettingsFile) {\n return {\n name: 'Session Hooks',\n ok: false,\n message: 'Not installed in current project',\n fix: async () => {\n this.log(' Run `rulemetric hooks install` in your project directory.');\n return false; // User needs to run in the right directory\n },\n };\n }\n\n return {\n name: 'Session Hooks',\n ok: false,\n message: 'settings file exists but hooks not configured',\n fix: async () => {\n this.log(' Run `rulemetric hooks install` to configure hooks.');\n return false;\n },\n };\n }\n\n private checkShellEnv(port: number): CheckResult {\n const proxySet = process.env.HTTPS_PROXY?.includes(String(port));\n const certSet = !!process.env.NODE_EXTRA_CA_CERTS;\n\n if (proxySet && certSet) {\n return { name: 'Shell Environment', ok: true, message: 'HTTPS_PROXY and NODE_EXTRA_CA_CERTS set' };\n }\n\n const missing: string[] = [];\n if (!proxySet) missing.push('HTTPS_PROXY');\n if (!certSet) missing.push('NODE_EXTRA_CA_CERTS');\n\n return {\n name: 'Shell Environment',\n ok: false,\n message: `Missing: ${missing.join(', ')}`,\n fix: async () => this.writeShellEnv(port),\n };\n }\n\n private async writeShellEnv(port: number): Promise<boolean> {\n const shell = this.detectShell();\n const rcFile = this.getShellRcFile(shell);\n\n if (!rcFile) {\n this.log(` Could not detect shell RC file. Add these to your shell profile:`);\n this.log(` export HTTPS_PROXY=http://localhost:${port}`);\n this.log(` export NODE_EXTRA_CA_CERTS=${CERT_PATH}`);\n return false;\n }\n\n const oldMarker = '# RuleMetric proxy';\n const marker = '# RuleMetric proxy (auto-detect)';\n const envFile = join(CONFIG_DIR, 'proxy.env');\n const pidFile = join(CONFIG_DIR, 'proxy.pid');\n\n // The sentinel proves the block is already installed (idempotency). POSIX\n // shells share one function name; PowerShell uses its own.\n const sentinel = shell === 'powershell' ? '_RuleMetricProxyCheck' : '_rulemetric_proxy_check';\n\n // Dynamic proxy env: re-evaluates on every prompt so stopping the proxy\n // immediately unsets HTTPS_PROXY without needing a new shell.\n const block = shell === 'powershell'\n ? this.buildPowerShellBlock(marker, pidFile, port)\n : shell === 'fish'\n ? [\n '',\n marker,\n 'function _rulemetric_proxy_check',\n ` if test -f ${pidFile}; and kill -0 (cat ${pidFile}) 2>/dev/null`,\n ` source ${envFile} 2>/dev/null`,\n ' else',\n ' set -e HTTPS_PROXY 2>/dev/null',\n ' set -e NODE_EXTRA_CA_CERTS 2>/dev/null',\n ' end',\n 'end',\n '',\n ].join('\\n')\n : shell === 'zsh'\n ? [\n '',\n marker,\n '_rulemetric_proxy_check() {',\n ` if [ -f \"${pidFile}\" ] && kill -0 \"$(cat \"${pidFile}\" 2>/dev/null)\" 2>/dev/null; then`,\n ` [ -z \"\\${HTTPS_PROXY:-}\" ] && source \"${envFile}\" 2>/dev/null`,\n ' else',\n ` [ -n \"\\${HTTPS_PROXY:-}\" ] && unset HTTPS_PROXY NODE_EXTRA_CA_CERTS`,\n ' fi',\n '}',\n 'precmd_functions+=(_rulemetric_proxy_check)',\n '',\n ].join('\\n')\n : [\n '',\n marker,\n '_rulemetric_proxy_check() {',\n ` if [ -f \"${pidFile}\" ] && kill -0 \"$(cat \"${pidFile}\" 2>/dev/null)\" 2>/dev/null; then`,\n ` [ -z \"\\${HTTPS_PROXY:-}\" ] && source \"${envFile}\" 2>/dev/null`,\n ' else',\n ` [ -n \"\\${HTTPS_PROXY:-}\" ] && unset HTTPS_PROXY NODE_EXTRA_CA_CERTS`,\n ' fi',\n '}',\n 'PROMPT_COMMAND=\"_rulemetric_proxy_check${PROMPT_COMMAND:+;$PROMPT_COMMAND}\"',\n '',\n ].join('\\n');\n\n // The PowerShell `$PROFILE` directory frequently does not exist yet; create\n // it (and any other missing rc dir, e.g. fish) before appending.\n mkdirSync(dirname(rcFile), { recursive: true });\n\n if (existsSync(rcFile)) {\n let content = readFileSync(rcFile, 'utf-8');\n\n // Remove old static block if present\n if (content.includes(oldMarker) && !content.includes(marker)) {\n content = content.replace(\n /\\n# RuleMetric proxy\\nexport HTTPS_PROXY=[^\\n]*\\nexport NODE_EXTRA_CA_CERTS=[^\\n]*\\n/,\n '\\n',\n );\n writeFileSync(rcFile, content);\n this.log(` Removed old static env vars from ${rcFile}`);\n }\n\n // Remove old auto-detect block (the one without precmd that doesn't unset on stop)\n if (content.includes('# RuleMetric proxy (auto-detect') && !content.includes('_rulemetric_proxy_check')) {\n content = content.replace(\n /\\n# RuleMetric proxy \\(auto-detect[^\\n]*\\)\\nif \\[.*?\\nfi\\n/s,\n '\\n',\n );\n writeFileSync(rcFile, content);\n this.log(` Upgraded old auto-detect block in ${rcFile}`);\n }\n\n if (content.includes(sentinel)) {\n this.log(` Shell env already configured in ${rcFile}`);\n return true;\n }\n }\n\n this.log(` Appending proxy auto-detect hook to ${rcFile}`);\n this.log(` (HTTPS_PROXY is set/unset automatically when proxy starts/stops)`);\n appendFileSync(rcFile, block);\n return true;\n }\n\n private detectShell(): string {\n // On Windows there is no $SHELL and PowerShell is the de-facto shell.\n if (process.platform === 'win32') return 'powershell';\n const shell = process.env.SHELL || '';\n if (shell.includes('zsh')) return 'zsh';\n if (shell.includes('bash')) return 'bash';\n if (shell.includes('fish')) return 'fish';\n return 'zsh'; // default on macOS\n }\n\n private getShellRcFile(shell: string): string | null {\n const home = homedir();\n switch (shell) {\n case 'zsh': return join(home, '.zshrc');\n case 'bash': return join(home, '.bashrc');\n case 'fish': return join(home, '.config', 'fish', 'config.fish');\n case 'powershell': return this.getPowerShellProfilePath();\n default: return null;\n }\n }\n\n /**\n * Resolve the PowerShell profile that loads for every host (console, VS Code\n * integrated terminal, etc.). We ask PowerShell itself for the canonical path\n * \u2014 guessing `~/Documents/...` is wrong when Documents is OneDrive-redirected.\n * Falls back to the PowerShell 7 default only if no PS binary is reachable.\n */\n private getPowerShellProfilePath(): string {\n const psBin = findBinary('pwsh') || findBinary('powershell');\n if (psBin) {\n try {\n const out = execSync(`\"${psBin}\" -NoProfile -Command \"$PROFILE.CurrentUserAllHosts\"`, {\n encoding: 'utf-8',\n }).trim();\n if (out) return out;\n } catch {\n /* fall through to constructed default */\n }\n }\n return join(homedir(), 'Documents', 'PowerShell', 'profile.ps1');\n }\n\n /**\n * Self-contained PowerShell prompt hook. Unlike the POSIX blocks it does NOT\n * source `proxy.env` (that file is `export KEY=val` syntax PowerShell can't\n * read) \u2014 it embeds the values directly. Each prompt it sets HTTPS_PROXY while\n * the proxy PID is alive and removes the vars once it stops, so a dead proxy\n * never leaves a dangling HTTPS_PROXY that would break Claude Code.\n */\n private buildPowerShellBlock(marker: string, pidFile: string, port: number): string {\n const proxyUrl = `http://localhost:${port}`;\n const psPid = pidFile.replace(/'/g, \"''\");\n const psCert = CERT_PATH.replace(/'/g, \"''\");\n return [\n '',\n marker,\n 'function _RuleMetricProxyCheck {',\n ` $pidFile = '${psPid}'`,\n ' $alive = $false',\n ' if (Test-Path $pidFile) {',\n ' $procId = (Get-Content $pidFile -ErrorAction SilentlyContinue | Select-Object -First 1)',\n ' if ($procId -match \\'^\\\\d+$\\' -and (Get-Process -Id ([int]$procId) -ErrorAction SilentlyContinue)) {',\n ' $alive = $true',\n ' }',\n ' }',\n ' if ($alive) {',\n ` $env:HTTPS_PROXY = '${proxyUrl}'`,\n ` $env:HTTP_PROXY = '${proxyUrl}'`,\n ` $env:NODE_EXTRA_CA_CERTS = '${psCert}'`,\n ' } else {',\n ' Remove-Item Env:HTTPS_PROXY, Env:HTTP_PROXY, Env:NODE_EXTRA_CA_CERTS -ErrorAction SilentlyContinue',\n ' }',\n '}',\n '# Wrap the existing prompt once so the check runs every prompt.',\n 'if (-not (Test-Path Function:\\\\_RuleMetricOriginalPrompt)) {',\n ' $existing = Get-Command prompt -ErrorAction SilentlyContinue',\n ' if ($existing) { Set-Item Function:\\\\_RuleMetricOriginalPrompt $existing.ScriptBlock }',\n '}',\n 'function prompt {',\n ' _RuleMetricProxyCheck',\n ' if (Test-Path Function:\\\\_RuleMetricOriginalPrompt) { & _RuleMetricOriginalPrompt } else { \"PS $($PWD.Path)> \" }',\n '}',\n '',\n ].join('\\n');\n }\n\n private findBinary(name: string): string | null {\n // Portable PATH scan \u2014 `which` does not exist on Windows.\n return findBinary(name);\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,13 +1,21 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildAllowHostsArgs
|
|
3
|
+
} from "../../chunk-EARWG4EV.js";
|
|
1
4
|
import {
|
|
2
5
|
findBinary
|
|
3
6
|
} from "../../chunk-42GFSAJP.js";
|
|
7
|
+
import {
|
|
8
|
+
MITMPROXY_COMMAND_PATTERN,
|
|
9
|
+
isPidLiveAndMatching
|
|
10
|
+
} from "../../chunk-5EDMGK3W.js";
|
|
4
11
|
import {
|
|
5
12
|
BaseCommand
|
|
6
|
-
} from "../../chunk-
|
|
13
|
+
} from "../../chunk-2HK7RGZB.js";
|
|
7
14
|
import "../../chunk-R56PH7YE.js";
|
|
8
|
-
import "../../chunk-
|
|
15
|
+
import "../../chunk-GQNJO2P2.js";
|
|
16
|
+
import "../../chunk-37LN5O7M.js";
|
|
9
17
|
import "../../chunk-EKJ2CABV.js";
|
|
10
|
-
import "../../chunk-
|
|
18
|
+
import "../../chunk-ZUJBM6HE.js";
|
|
11
19
|
import {
|
|
12
20
|
isAuthenticated
|
|
13
21
|
} from "../../chunk-W2YERO7E.js";
|
|
@@ -194,18 +202,15 @@ var ProxyStart = class _ProxyStart extends BaseCommand {
|
|
|
194
202
|
this.error("Proxy is running in Docker. Stop it first: rulemetric proxy stop");
|
|
195
203
|
}
|
|
196
204
|
const pid = Number.parseInt(content, 10);
|
|
197
|
-
if (!Number.isNaN(pid)) {
|
|
198
|
-
|
|
199
|
-
process.kill(pid, 0);
|
|
200
|
-
this.error(`Proxy already running (PID ${pid}). Stop it first: rulemetric proxy stop`);
|
|
201
|
-
} catch {
|
|
202
|
-
}
|
|
205
|
+
if (!Number.isNaN(pid) && isPidLiveAndMatching(pid, MITMPROXY_COMMAND_PATTERN)) {
|
|
206
|
+
this.error(`Proxy already running (PID ${pid}). Stop it first: rulemetric proxy stop`);
|
|
203
207
|
}
|
|
204
208
|
}
|
|
205
|
-
const
|
|
206
|
-
|
|
209
|
+
const binaryName = useWeb ? "mitmweb" : "mitmdump";
|
|
210
|
+
const binary = findBinary(binaryName);
|
|
211
|
+
if (!binary) {
|
|
207
212
|
this.error(
|
|
208
|
-
`${
|
|
213
|
+
`${binaryName} not found. Use Docker mode instead:
|
|
209
214
|
rulemetric proxy start --docker
|
|
210
215
|
|
|
211
216
|
Or install mitmproxy locally:
|
|
@@ -230,8 +235,10 @@ Or install mitmproxy locally:
|
|
|
230
235
|
if (!existsSync(certPath)) {
|
|
231
236
|
warnings.push("CA cert not found \u2014 HTTPS interception may fail. Run `rulemetric proxy setup`.");
|
|
232
237
|
}
|
|
233
|
-
const
|
|
234
|
-
|
|
238
|
+
const hasClaudeSettings = ["settings.local.json", "settings.json"].some(
|
|
239
|
+
(fileName) => existsSync(join(process.cwd(), ".claude", fileName))
|
|
240
|
+
);
|
|
241
|
+
if (!hasClaudeSettings) {
|
|
235
242
|
warnings.push("Session hooks not installed \u2014 snapshots won't link to sessions. Run `rulemetric hooks install`.");
|
|
236
243
|
}
|
|
237
244
|
if (warnings.length > 0) {
|
|
@@ -267,13 +274,20 @@ Or install mitmproxy locally:
|
|
|
267
274
|
--- Proxy started at ${(/* @__PURE__ */ new Date()).toISOString()} (port ${port}) ---
|
|
268
275
|
`);
|
|
269
276
|
const logFd = openSync(LOG_FILE, "a");
|
|
277
|
+
const allowHostsArgs = buildAllowHostsArgs(dirname(addonPath));
|
|
278
|
+
if (allowHostsArgs.length > 0) {
|
|
279
|
+
this.log(`[OK] Interception scoped to ${allowHostsArgs.length / 2} LLM host patterns (RULEMETRIC_PROXY_ALL_HOSTS=1 to intercept everything)`);
|
|
280
|
+
} else {
|
|
281
|
+
this.log("[!!] Interception UNSCOPED \u2014 all HTTPS hosts pass through mitmproxy");
|
|
282
|
+
}
|
|
270
283
|
const args = [
|
|
271
284
|
"--listen-port",
|
|
272
285
|
String(port),
|
|
273
286
|
"-s",
|
|
274
287
|
addonPath,
|
|
275
288
|
"--set",
|
|
276
|
-
"console_eventlog_verbosity=info"
|
|
289
|
+
"console_eventlog_verbosity=info",
|
|
290
|
+
...allowHostsArgs
|
|
277
291
|
];
|
|
278
292
|
const child = spawn(binary, args, {
|
|
279
293
|
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 { 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 const pid = Number.parseInt(content, 10);\n if (!Number.isNaN(pid)) {\n try {\n process.kill(pid, 0);\n this.error(`Proxy already running (PID ${pid}). Stop it first: rulemetric proxy stop`);\n } catch { /* stale PID, continue */ }\n }\n }\n\n // Check mitmproxy is installed (portable PATH scan \u2014 Windows has no `which`)\n const binary = useWeb ? 'mitmweb' : 'mitmdump';\n if (!findBinary(binary)) {\n this.error(\n `${binary} 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 const settingsPath = join(process.cwd(), '.claude', 'settings.json');\n if (!existsSync(settingsPath)) {\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": "
|
|
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';\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\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 // 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
|
}
|
|
@@ -3,14 +3,21 @@ import {
|
|
|
3
3
|
MITMPROXY_PORT,
|
|
4
4
|
getGatewayPid,
|
|
5
5
|
isGatewayRunning
|
|
6
|
-
} from "../../chunk-
|
|
6
|
+
} from "../../chunk-4LYCDS2N.js";
|
|
7
|
+
import "../../chunk-7LVVKQMZ.js";
|
|
8
|
+
import {
|
|
9
|
+
MITMPROXY_COMMAND_PATTERN,
|
|
10
|
+
isPidLiveAndMatching,
|
|
11
|
+
pidAlive
|
|
12
|
+
} from "../../chunk-5EDMGK3W.js";
|
|
7
13
|
import {
|
|
8
14
|
BaseCommand
|
|
9
|
-
} from "../../chunk-
|
|
15
|
+
} from "../../chunk-2HK7RGZB.js";
|
|
10
16
|
import "../../chunk-R56PH7YE.js";
|
|
11
|
-
import "../../chunk-
|
|
17
|
+
import "../../chunk-GQNJO2P2.js";
|
|
18
|
+
import "../../chunk-37LN5O7M.js";
|
|
12
19
|
import "../../chunk-EKJ2CABV.js";
|
|
13
|
-
import "../../chunk-
|
|
20
|
+
import "../../chunk-ZUJBM6HE.js";
|
|
14
21
|
import "../../chunk-W2YERO7E.js";
|
|
15
22
|
import "../../chunk-NSBPE2FW.js";
|
|
16
23
|
|
|
@@ -56,16 +63,12 @@ var ProxyStatus = class _ProxyStatus extends BaseCommand {
|
|
|
56
63
|
this.log("Mitmproxy: not running (invalid PID file)");
|
|
57
64
|
return;
|
|
58
65
|
}
|
|
59
|
-
|
|
60
|
-
process.kill(pid, 0);
|
|
66
|
+
if (isPidLiveAndMatching(pid, MITMPROXY_COMMAND_PATTERN)) {
|
|
61
67
|
this.log(`Mitmproxy: running (PID ${pid}) on :${MITMPROXY_PORT} \u2014 capturing context`);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
} else {
|
|
67
|
-
this.log("Mitmproxy: not running (stale PID file)");
|
|
68
|
-
}
|
|
68
|
+
} else if (pidAlive(pid)) {
|
|
69
|
+
this.log(`Mitmproxy: not running (PID ${pid} was reused by another process \u2014 stale PID file)`);
|
|
70
|
+
} else {
|
|
71
|
+
this.log("Mitmproxy: not running (stale PID file)");
|
|
69
72
|
}
|
|
70
73
|
}
|
|
71
74
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/commands/proxy/status.ts"],
|
|
4
|
-
"sourcesContent": ["import { spawnSync } from 'node:child_process';\nimport { existsSync, readFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { BaseCommand } from '../../base-command.js';\nimport { isGatewayRunning, getGatewayPid, GATEWAY_PORT, MITMPROXY_PORT } from '../../lib/gateway-lifecycle.js';\n\nconst CONFIG_DIR = join(homedir(), '.config', 'rulemetric');\nconst PID_FILE = join(CONFIG_DIR, 'proxy.pid');\n\nexport default class ProxyStatus extends BaseCommand {\n static override description = 'Check gateway and context capture proxy status';\n\n static override examples = ['<%= config.bin %> proxy status'];\n\n async run(): Promise<void> {\n await this.parse(ProxyStatus);\n\n // Gateway status\n if (isGatewayRunning()) {\n const pid = getGatewayPid();\n this.log(`Gateway: running (PID ${pid}) on :${GATEWAY_PORT}`);\n } else {\n this.log(`Gateway: not running`);\n }\n\n // Mitmproxy status\n if (!existsSync(PID_FILE)) {\n this.log(`Mitmproxy: not running \u2014 routing direct (no capture)`);\n return;\n }\n\n const content = readFileSync(PID_FILE, 'utf-8').trim();\n\n // Docker mode\n if (content.startsWith('docker:')) {\n const containerName = content.slice('docker:'.length);\n const result = spawnSync('docker', ['container', 'inspect', '-f', '{{.State.Status}}', containerName], {\n encoding: 'utf-8',\n stdio: ['ignore', 'pipe', 'ignore'],\n });\n const status = result.stdout?.trim();\n if (result.status === 0 && status === 'running') {\n this.log(`Mitmproxy: running in Docker (container: ${containerName}) on :${MITMPROXY_PORT} \u2014 capturing context`);\n } else {\n this.log(`Mitmproxy: container ${status || 'not running'} (stale)`);\n }\n return;\n }\n\n // Local mode\n const pid = Number.parseInt(content, 10);\n\n if (Number.isNaN(pid)) {\n this.log('Mitmproxy: not running (invalid PID file)');\n return;\n }\n\n
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["import { spawnSync } from 'node:child_process';\nimport { existsSync, readFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { BaseCommand } from '../../base-command.js';\nimport { isGatewayRunning, getGatewayPid, GATEWAY_PORT, MITMPROXY_PORT } from '../../lib/gateway-lifecycle.js';\nimport { isPidLiveAndMatching, pidAlive, MITMPROXY_COMMAND_PATTERN } from '../../lib/pid-identity.js';\n\nconst CONFIG_DIR = join(homedir(), '.config', 'rulemetric');\nconst PID_FILE = join(CONFIG_DIR, 'proxy.pid');\n\nexport default class ProxyStatus extends BaseCommand {\n static override description = 'Check gateway and context capture proxy status';\n\n static override examples = ['<%= config.bin %> proxy status'];\n\n async run(): Promise<void> {\n await this.parse(ProxyStatus);\n\n // Gateway status\n if (isGatewayRunning()) {\n const pid = getGatewayPid();\n this.log(`Gateway: running (PID ${pid}) on :${GATEWAY_PORT}`);\n } else {\n this.log(`Gateway: not running`);\n }\n\n // Mitmproxy status\n if (!existsSync(PID_FILE)) {\n this.log(`Mitmproxy: not running \u2014 routing direct (no capture)`);\n return;\n }\n\n const content = readFileSync(PID_FILE, 'utf-8').trim();\n\n // Docker mode\n if (content.startsWith('docker:')) {\n const containerName = content.slice('docker:'.length);\n const result = spawnSync('docker', ['container', 'inspect', '-f', '{{.State.Status}}', containerName], {\n encoding: 'utf-8',\n stdio: ['ignore', 'pipe', 'ignore'],\n });\n const status = result.stdout?.trim();\n if (result.status === 0 && status === 'running') {\n this.log(`Mitmproxy: running in Docker (container: ${containerName}) on :${MITMPROXY_PORT} \u2014 capturing context`);\n } else {\n this.log(`Mitmproxy: container ${status || 'not running'} (stale)`);\n }\n return;\n }\n\n // Local mode\n const pid = Number.parseInt(content, 10);\n\n if (Number.isNaN(pid)) {\n this.log('Mitmproxy: not running (invalid PID file)');\n return;\n }\n\n // Identity-checked: a reused PID (alive but not mitmproxy) must read as\n // stale, or self-heal/restarts stay blocked on a phantom \"running\" proxy.\n if (isPidLiveAndMatching(pid, MITMPROXY_COMMAND_PATTERN)) {\n this.log(`Mitmproxy: running (PID ${pid}) on :${MITMPROXY_PORT} \u2014 capturing context`);\n } else if (pidAlive(pid)) {\n this.log(`Mitmproxy: not running (PID ${pid} was reused by another process \u2014 stale PID file)`);\n } else {\n this.log('Mitmproxy: not running (stale PID file)');\n }\n }\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;AAAA,SAAS,iBAAiB;AAC1B,SAAS,YAAY,oBAAoB;AACzC,SAAS,eAAe;AACxB,SAAS,YAAY;AAKrB,IAAM,aAAa,KAAK,QAAQ,GAAG,WAAW,YAAY;AAC1D,IAAM,WAAW,KAAK,YAAY,WAAW;AAE7C,IAAqB,cAArB,MAAqB,qBAAoB,YAAY;AAAA,EACnD,OAAgB,cAAc;AAAA,EAE9B,OAAgB,WAAW,CAAC,gCAAgC;AAAA,EAE5D,MAAM,MAAqB;AACzB,UAAM,KAAK,MAAM,YAAW;AAG5B,QAAI,iBAAiB,GAAG;AACtB,YAAMA,OAAM,cAAc;AAC1B,WAAK,IAAI,2BAA2BA,IAAG,SAAS,YAAY,EAAE;AAAA,IAChE,OAAO;AACL,WAAK,IAAI,wBAAwB;AAAA,IACnC;AAGA,QAAI,CAAC,WAAW,QAAQ,GAAG;AACzB,WAAK,IAAI,2DAAsD;AAC/D;AAAA,IACF;AAEA,UAAM,UAAU,aAAa,UAAU,OAAO,EAAE,KAAK;AAGrD,QAAI,QAAQ,WAAW,SAAS,GAAG;AACjC,YAAM,gBAAgB,QAAQ,MAAM,UAAU,MAAM;AACpD,YAAM,SAAS,UAAU,UAAU,CAAC,aAAa,WAAW,MAAM,qBAAqB,aAAa,GAAG;AAAA,QACrG,UAAU;AAAA,QACV,OAAO,CAAC,UAAU,QAAQ,QAAQ;AAAA,MACpC,CAAC;AACD,YAAM,SAAS,OAAO,QAAQ,KAAK;AACnC,UAAI,OAAO,WAAW,KAAK,WAAW,WAAW;AAC/C,aAAK,IAAI,4CAA4C,aAAa,SAAS,cAAc,2BAAsB;AAAA,MACjH,OAAO;AACL,aAAK,IAAI,wBAAwB,UAAU,aAAa,UAAU;AAAA,MACpE;AACA;AAAA,IACF;AAGA,UAAM,MAAM,OAAO,SAAS,SAAS,EAAE;AAEvC,QAAI,OAAO,MAAM,GAAG,GAAG;AACrB,WAAK,IAAI,2CAA2C;AACpD;AAAA,IACF;AAIA,QAAI,qBAAqB,KAAK,yBAAyB,GAAG;AACxD,WAAK,IAAI,2BAA2B,GAAG,SAAS,cAAc,2BAAsB;AAAA,IACtF,WAAW,SAAS,GAAG,GAAG;AACxB,WAAK,IAAI,+BAA+B,GAAG,uDAAkD;AAAA,IAC/F,OAAO;AACL,WAAK,IAAI,yCAAyC;AAAA,IACpD;AAAA,EACF;AACF;",
|
|
6
6
|
"names": ["pid"]
|
|
7
7
|
}
|
|
@@ -1,10 +1,15 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MITMPROXY_COMMAND_PATTERN,
|
|
3
|
+
isPidLiveAndMatching
|
|
4
|
+
} from "../../chunk-5EDMGK3W.js";
|
|
1
5
|
import {
|
|
2
6
|
BaseCommand
|
|
3
|
-
} from "../../chunk-
|
|
7
|
+
} from "../../chunk-2HK7RGZB.js";
|
|
4
8
|
import "../../chunk-R56PH7YE.js";
|
|
5
|
-
import "../../chunk-
|
|
9
|
+
import "../../chunk-GQNJO2P2.js";
|
|
10
|
+
import "../../chunk-37LN5O7M.js";
|
|
6
11
|
import "../../chunk-EKJ2CABV.js";
|
|
7
|
-
import "../../chunk-
|
|
12
|
+
import "../../chunk-ZUJBM6HE.js";
|
|
8
13
|
import "../../chunk-W2YERO7E.js";
|
|
9
14
|
import "../../chunk-NSBPE2FW.js";
|
|
10
15
|
|
|
@@ -50,16 +55,20 @@ var ProxyStop = class _ProxyStop extends BaseCommand {
|
|
|
50
55
|
this.log("Invalid PID file removed.");
|
|
51
56
|
return;
|
|
52
57
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
58
|
+
if (isPidLiveAndMatching(pid, MITMPROXY_COMMAND_PATTERN)) {
|
|
59
|
+
try {
|
|
60
|
+
process.kill(pid, "SIGTERM");
|
|
61
|
+
this.log(`Proxy stopped (PID ${pid}).`);
|
|
62
|
+
} catch (err) {
|
|
63
|
+
const code = err.code;
|
|
64
|
+
if (code === "ESRCH") {
|
|
65
|
+
this.log("Proxy process not running (stale PID file removed).");
|
|
66
|
+
} else {
|
|
67
|
+
this.error(`Failed to stop proxy: ${err.message}`);
|
|
68
|
+
}
|
|
62
69
|
}
|
|
70
|
+
} else {
|
|
71
|
+
this.log("Proxy process not running (stale PID file removed).");
|
|
63
72
|
}
|
|
64
73
|
try {
|
|
65
74
|
unlinkSync(PID_FILE);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/commands/proxy/stop.ts"],
|
|
4
|
-
"sourcesContent": ["import { spawnSync } from 'node:child_process';\nimport { existsSync, readFileSync, unlinkSync } 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';\n\n// MODULE_DIR 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 CONFIG_DIR = join(homedir(), '.config', 'rulemetric');\nconst PID_FILE = join(CONFIG_DIR, 'proxy.pid');\n\nexport default class ProxyStop extends BaseCommand {\n static override description = 'Stop the context capture proxy';\n\n static override examples = ['<%= config.bin %> proxy stop'];\n\n async run(): Promise<void> {\n await this.parse(ProxyStop);\n\n if (!existsSync(PID_FILE)) {\n this.log('No proxy process found (no PID file).');\n return;\n }\n\n const content = readFileSync(PID_FILE, 'utf-8').trim();\n\n // Docker mode: PID file contains \"docker:<container-name>\"\n if (content.startsWith('docker:')) {\n const containerName = content.slice('docker:'.length);\n // Try docker compose first, fall back to docker stop\n const composeFile = this.findComposeFile();\n if (composeFile) {\n spawnSync('docker', ['compose', '-f', composeFile, 'stop', 'proxy'], { stdio: 'inherit' });\n spawnSync('docker', ['compose', '-f', composeFile, 'rm', '-f', 'proxy'], { stdio: 'ignore' });\n } else {\n spawnSync('docker', ['stop', containerName], { stdio: 'ignore' });\n spawnSync('docker', ['rm', containerName], { stdio: 'ignore' });\n }\n this.log(`Proxy container stopped.`);\n try { unlinkSync(PID_FILE); } catch { /* already gone */ }\n return;\n }\n\n // Local mode: PID file contains a process ID\n const pid = Number.parseInt(content, 10);\n\n if (Number.isNaN(pid)) {\n unlinkSync(PID_FILE);\n this.log('Invalid PID file removed.');\n return;\n }\n\n try {\n
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["import { spawnSync } from 'node:child_process';\nimport { existsSync, readFileSync, unlinkSync } 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 { isPidLiveAndMatching, MITMPROXY_COMMAND_PATTERN } from '../../lib/pid-identity.js';\n\n// MODULE_DIR 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 CONFIG_DIR = join(homedir(), '.config', 'rulemetric');\nconst PID_FILE = join(CONFIG_DIR, 'proxy.pid');\n\nexport default class ProxyStop extends BaseCommand {\n static override description = 'Stop the context capture proxy';\n\n static override examples = ['<%= config.bin %> proxy stop'];\n\n async run(): Promise<void> {\n await this.parse(ProxyStop);\n\n if (!existsSync(PID_FILE)) {\n this.log('No proxy process found (no PID file).');\n return;\n }\n\n const content = readFileSync(PID_FILE, 'utf-8').trim();\n\n // Docker mode: PID file contains \"docker:<container-name>\"\n if (content.startsWith('docker:')) {\n const containerName = content.slice('docker:'.length);\n // Try docker compose first, fall back to docker stop\n const composeFile = this.findComposeFile();\n if (composeFile) {\n spawnSync('docker', ['compose', '-f', composeFile, 'stop', 'proxy'], { stdio: 'inherit' });\n spawnSync('docker', ['compose', '-f', composeFile, 'rm', '-f', 'proxy'], { stdio: 'ignore' });\n } else {\n spawnSync('docker', ['stop', containerName], { stdio: 'ignore' });\n spawnSync('docker', ['rm', containerName], { stdio: 'ignore' });\n }\n this.log(`Proxy container stopped.`);\n try { unlinkSync(PID_FILE); } catch { /* already gone */ }\n return;\n }\n\n // Local mode: PID file contains a process ID\n const pid = Number.parseInt(content, 10);\n\n if (Number.isNaN(pid)) {\n unlinkSync(PID_FILE);\n this.log('Invalid PID file removed.');\n return;\n }\n\n // Never SIGTERM a reused PID: the file may point at an unrelated process\n // that inherited the number after a reboot/crash. Identity-check first;\n // a mismatch is just a stale file to remove.\n if (isPidLiveAndMatching(pid, MITMPROXY_COMMAND_PATTERN)) {\n try {\n process.kill(pid, 'SIGTERM');\n this.log(`Proxy stopped (PID ${pid}).`);\n } catch (err: unknown) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code === 'ESRCH') {\n this.log('Proxy process not running (stale PID file removed).');\n } else {\n this.error(`Failed to stop proxy: ${(err as Error).message}`);\n }\n }\n } else {\n this.log('Proxy process not running (stale PID file removed).');\n }\n\n try { unlinkSync(PID_FILE); } catch { /* already gone */ }\n\n this.log('');\n this.log('Gateway is still running \u2014 Claude Code will route direct until proxy restarts.');\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"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;AAAA,SAAS,iBAAiB;AAC1B,SAAS,YAAY,cAAc,kBAAkB;AACrD,SAAS,eAAe;AACxB,SAAS,SAAS,MAAM,eAAe;AACvC,SAAS,qBAAqB;AAM9B,IAAM,aAAa,QAAQ,cAAc,YAAY,GAAG,CAAC;AAEzD,IAAM,aAAa,KAAK,QAAQ,GAAG,WAAW,YAAY;AAC1D,IAAM,WAAW,KAAK,YAAY,WAAW;AAE7C,IAAqB,YAArB,MAAqB,mBAAkB,YAAY;AAAA,EACjD,OAAgB,cAAc;AAAA,EAE9B,OAAgB,WAAW,CAAC,8BAA8B;AAAA,EAE1D,MAAM,MAAqB;AACzB,UAAM,KAAK,MAAM,UAAS;AAE1B,QAAI,CAAC,WAAW,QAAQ,GAAG;AACzB,WAAK,IAAI,uCAAuC;AAChD;AAAA,IACF;AAEA,UAAM,UAAU,aAAa,UAAU,OAAO,EAAE,KAAK;AAGrD,QAAI,QAAQ,WAAW,SAAS,GAAG;AACjC,YAAM,gBAAgB,QAAQ,MAAM,UAAU,MAAM;AAEpD,YAAM,cAAc,KAAK,gBAAgB;AACzC,UAAI,aAAa;AACf,kBAAU,UAAU,CAAC,WAAW,MAAM,aAAa,QAAQ,OAAO,GAAG,EAAE,OAAO,UAAU,CAAC;AACzF,kBAAU,UAAU,CAAC,WAAW,MAAM,aAAa,MAAM,MAAM,OAAO,GAAG,EAAE,OAAO,SAAS,CAAC;AAAA,MAC9F,OAAO;AACL,kBAAU,UAAU,CAAC,QAAQ,aAAa,GAAG,EAAE,OAAO,SAAS,CAAC;AAChE,kBAAU,UAAU,CAAC,MAAM,aAAa,GAAG,EAAE,OAAO,SAAS,CAAC;AAAA,MAChE;AACA,WAAK,IAAI,0BAA0B;AACnC,UAAI;AAAE,mBAAW,QAAQ;AAAA,MAAG,QAAQ;AAAA,MAAqB;AACzD;AAAA,IACF;AAGA,UAAM,MAAM,OAAO,SAAS,SAAS,EAAE;AAEvC,QAAI,OAAO,MAAM,GAAG,GAAG;AACrB,iBAAW,QAAQ;AACnB,WAAK,IAAI,2BAA2B;AACpC;AAAA,IACF;AAKA,QAAI,qBAAqB,KAAK,yBAAyB,GAAG;AACxD,UAAI;AACF,gBAAQ,KAAK,KAAK,SAAS;AAC3B,aAAK,IAAI,sBAAsB,GAAG,IAAI;AAAA,MACxC,SAAS,KAAc;AACrB,cAAM,OAAQ,IAA8B;AAC5C,YAAI,SAAS,SAAS;AACpB,eAAK,IAAI,qDAAqD;AAAA,QAChE,OAAO;AACL,eAAK,MAAM,yBAA0B,IAAc,OAAO,EAAE;AAAA,QAC9D;AAAA,MACF;AAAA,IACF,OAAO;AACL,WAAK,IAAI,qDAAqD;AAAA,IAChE;AAEA,QAAI;AAAE,iBAAW,QAAQ;AAAA,IAAG,QAAQ;AAAA,IAAqB;AAEzD,SAAK,IAAI,EAAE;AACX,SAAK,IAAI,qFAAgF;AAAA,EAC3F;AAAA,EAEQ,kBAAiC;AACvC,UAAM,aAAa;AAAA,MACjB,KAAK,QAAQ,IAAI,GAAG,oBAAoB;AAAA,MACxC,QAAQ,YAAY,mCAAmC;AAAA,IACzD;AACA,eAAW,KAAK,YAAY;AAC1B,UAAI,WAAW,CAAC,EAAG,QAAO;AAAA,IAC5B;AACA,WAAO;AAAA,EACT;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -3,14 +3,15 @@ import {
|
|
|
3
3
|
} from "../../chunk-WA4MWYRW.js";
|
|
4
4
|
import {
|
|
5
5
|
postObservations
|
|
6
|
-
} from "../../chunk-
|
|
6
|
+
} from "../../chunk-Y3WD5CSW.js";
|
|
7
7
|
import {
|
|
8
8
|
BaseCommand
|
|
9
|
-
} from "../../chunk-
|
|
9
|
+
} from "../../chunk-2HK7RGZB.js";
|
|
10
10
|
import "../../chunk-R56PH7YE.js";
|
|
11
|
-
import "../../chunk-
|
|
11
|
+
import "../../chunk-GQNJO2P2.js";
|
|
12
|
+
import "../../chunk-37LN5O7M.js";
|
|
12
13
|
import "../../chunk-EKJ2CABV.js";
|
|
13
|
-
import "../../chunk-
|
|
14
|
+
import "../../chunk-ZUJBM6HE.js";
|
|
14
15
|
import "../../chunk-W2YERO7E.js";
|
|
15
16
|
import "../../chunk-NSBPE2FW.js";
|
|
16
17
|
|
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/commands/research/backfill.ts"],
|
|
4
4
|
"sourcesContent": ["import { Flags } from '@oclif/core';\nimport path from 'node:path';\nimport os from 'node:os';\nimport { BaseCommand } from '../../base-command.js';\nimport { scanAllTranscripts } from '../../lib/transcript-rate-limit-watcher.js';\nimport { postObservations } from '../../lib/research-client.js';\n\nexport default class ResearchBackfill extends BaseCommand {\n static override description =\n 'Scan every ~/.claude/projects transcript for past rate-limit (429) events and POST them as observations. Opt-in seed for the usage-research dataset \u2014 forward-only tail is the default capture path.';\n\n static override examples = [\n '<%= config.bin %> research backfill',\n '<%= config.bin %> research backfill --claude-dir /custom/.claude --dry-run',\n ];\n\n static override flags = {\n 'claude-dir': Flags.string({\n description: 'Override the Claude Code projects directory',\n default: path.join(os.homedir(), '.claude'),\n }),\n 'dry-run': Flags.boolean({\n description: 'Print observation count without POSTing',\n default: false,\n }),\n };\n\n async run(): Promise<void> {\n const { flags } = await this.parse(ResearchBackfill);\n this.requireAuth();\n\n this.log(`Scanning ${path.join(flags['claude-dir'], 'projects')} for rate-limit events...`);\n const observations = await scanAllTranscripts(flags['claude-dir']);\n this.log(`Found ${observations.length} rate-limit observation(s).`);\n\n if (observations.length === 0) return;\n\n if (flags['dry-run']) {\n this.log('Dry run \u2014 nothing posted.');\n return;\n }\n\n const result = await postObservations(observations);\n this.log(`Posted ${result.posted} observation(s); ${result.failed} failed.`);\n if (result.failed > 0) {\n for (const e of result.errors) this.warn(e);\n }\n }\n}\n"],
|
|
5
|
-
"mappings": "
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;AAAA,SAAS,aAAa;AACtB,OAAO,UAAU;AACjB,OAAO,QAAQ;AAKf,IAAqB,mBAArB,MAAqB,0BAAyB,YAAY;AAAA,EACxD,OAAgB,cACd;AAAA,EAEF,OAAgB,WAAW;AAAA,IACzB;AAAA,IACA;AAAA,EACF;AAAA,EAEA,OAAgB,QAAQ;AAAA,IACtB,cAAc,MAAM,OAAO;AAAA,MACzB,aAAa;AAAA,MACb,SAAS,KAAK,KAAK,GAAG,QAAQ,GAAG,SAAS;AAAA,IAC5C,CAAC;AAAA,IACD,WAAW,MAAM,QAAQ;AAAA,MACvB,aAAa;AAAA,MACb,SAAS;AAAA,IACX,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,MAAqB;AACzB,UAAM,EAAE,MAAM,IAAI,MAAM,KAAK,MAAM,iBAAgB;AACnD,SAAK,YAAY;AAEjB,SAAK,IAAI,YAAY,KAAK,KAAK,MAAM,YAAY,GAAG,UAAU,CAAC,2BAA2B;AAC1F,UAAM,eAAe,MAAM,mBAAmB,MAAM,YAAY,CAAC;AACjE,SAAK,IAAI,SAAS,aAAa,MAAM,6BAA6B;AAElE,QAAI,aAAa,WAAW,EAAG;AAE/B,QAAI,MAAM,SAAS,GAAG;AACpB,WAAK,IAAI,gCAA2B;AACpC;AAAA,IACF;AAEA,UAAM,SAAS,MAAM,iBAAiB,YAAY;AAClD,SAAK,IAAI,UAAU,OAAO,MAAM,oBAAoB,OAAO,MAAM,UAAU;AAC3E,QAAI,OAAO,SAAS,GAAG;AACrB,iBAAW,KAAK,OAAO,OAAQ,MAAK,KAAK,CAAC;AAAA,IAC5C;AAAA,EACF;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -5,14 +5,15 @@ import {
|
|
|
5
5
|
} from "../../chunk-WA4MWYRW.js";
|
|
6
6
|
import {
|
|
7
7
|
postObservations
|
|
8
|
-
} from "../../chunk-
|
|
8
|
+
} from "../../chunk-Y3WD5CSW.js";
|
|
9
9
|
import {
|
|
10
10
|
BaseCommand
|
|
11
|
-
} from "../../chunk-
|
|
11
|
+
} from "../../chunk-2HK7RGZB.js";
|
|
12
12
|
import "../../chunk-R56PH7YE.js";
|
|
13
|
-
import "../../chunk-
|
|
13
|
+
import "../../chunk-GQNJO2P2.js";
|
|
14
|
+
import "../../chunk-37LN5O7M.js";
|
|
14
15
|
import "../../chunk-EKJ2CABV.js";
|
|
15
|
-
import "../../chunk-
|
|
16
|
+
import "../../chunk-ZUJBM6HE.js";
|
|
16
17
|
import "../../chunk-W2YERO7E.js";
|
|
17
18
|
import "../../chunk-NSBPE2FW.js";
|
|
18
19
|
|
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/commands/research/tail-transcript.ts"],
|
|
4
4
|
"sourcesContent": ["import { Args, Flags } from '@oclif/core';\nimport path from 'node:path';\nimport { BaseCommand } from '../../base-command.js';\nimport {\n loadOffsets,\n saveOffsets,\n tailJsonl,\n} from '../../lib/transcript-rate-limit-watcher.js';\nimport { postObservations } from '../../lib/research-client.js';\n\nexport default class ResearchTailTranscript extends BaseCommand {\n static override description =\n 'Read new lines from a Claude Code transcript since the last cached offset, POST any 429 events, and persist the new offset. Invoked by the session-end hook \u2014 safe to re-run.';\n static override hidden = true;\n\n static override args = {\n transcript: Args.string({\n description: 'Absolute path to a Claude Code .jsonl transcript',\n required: true,\n }),\n };\n\n static override flags = {\n 'no-post': Flags.boolean({\n description: 'Parse and advance the offset without POSTing (debug)',\n default: false,\n }),\n };\n\n async run(): Promise<void> {\n const { args, flags } = await this.parse(ResearchTailTranscript);\n const filepath = path.resolve(args.transcript);\n\n // Auth \u2014 but never crash the hook. Fall through to no-op on missing auth.\n try {\n this.requireAuth();\n } catch {\n // Hooks shouldn't break Claude Code. Silently exit.\n return;\n }\n\n const offsets = await loadOffsets();\n const fromOffset = offsets[filepath] ?? 0;\n const { observations, newOffset } = await tailJsonl(filepath, fromOffset);\n\n if (observations.length > 0 && !flags['no-post']) {\n try {\n const result = await postObservations(observations);\n if (process.env.RULEMETRIC_DEBUG) {\n this.log(`tail-transcript: posted ${result.posted}, failed ${result.failed}`);\n }\n } catch (err) {\n // Hook-safe: never throw.\n if (process.env.RULEMETRIC_DEBUG) {\n this.warn(`tail-transcript POST error: ${String(err)}`);\n }\n }\n }\n\n if (newOffset !== fromOffset) {\n offsets[filepath] = newOffset;\n await saveOffsets(offsets);\n }\n }\n}\n"],
|
|
5
|
-
"mappings": "
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;AAAA,SAAS,MAAM,aAAa;AAC5B,OAAO,UAAU;AASjB,IAAqB,yBAArB,MAAqB,gCAA+B,YAAY;AAAA,EAC9D,OAAgB,cACd;AAAA,EACF,OAAgB,SAAS;AAAA,EAEzB,OAAgB,OAAO;AAAA,IACrB,YAAY,KAAK,OAAO;AAAA,MACtB,aAAa;AAAA,MACb,UAAU;AAAA,IACZ,CAAC;AAAA,EACH;AAAA,EAEA,OAAgB,QAAQ;AAAA,IACtB,WAAW,MAAM,QAAQ;AAAA,MACvB,aAAa;AAAA,MACb,SAAS;AAAA,IACX,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,MAAqB;AACzB,UAAM,EAAE,MAAM,MAAM,IAAI,MAAM,KAAK,MAAM,uBAAsB;AAC/D,UAAM,WAAW,KAAK,QAAQ,KAAK,UAAU;AAG7C,QAAI;AACF,WAAK,YAAY;AAAA,IACnB,QAAQ;AAEN;AAAA,IACF;AAEA,UAAM,UAAU,MAAM,YAAY;AAClC,UAAM,aAAa,QAAQ,QAAQ,KAAK;AACxC,UAAM,EAAE,cAAc,UAAU,IAAI,MAAM,UAAU,UAAU,UAAU;AAExE,QAAI,aAAa,SAAS,KAAK,CAAC,MAAM,SAAS,GAAG;AAChD,UAAI;AACF,cAAM,SAAS,MAAM,iBAAiB,YAAY;AAClD,YAAI,QAAQ,IAAI,kBAAkB;AAChC,eAAK,IAAI,2BAA2B,OAAO,MAAM,YAAY,OAAO,MAAM,EAAE;AAAA,QAC9E;AAAA,MACF,SAAS,KAAK;AAEZ,YAAI,QAAQ,IAAI,kBAAkB;AAChC,eAAK,KAAK,+BAA+B,OAAO,GAAG,CAAC,EAAE;AAAA,QACxD;AAAA,MACF;AAAA,IACF;AAEA,QAAI,cAAc,YAAY;AAC5B,cAAQ,QAAQ,IAAI;AACpB,YAAM,YAAY,OAAO;AAAA,IAC3B;AAAA,EACF;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|