@rulemetric/cli 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/dist/base-command.js +2 -2
  2. package/dist/{chunk-S56LOTGQ.js → chunk-7DIWBTB5.js} +2 -2
  3. package/dist/{chunk-L674RNKQ.js → chunk-ANOWI23I.js} +51 -1
  4. package/dist/chunk-ANOWI23I.js.map +7 -0
  5. package/dist/chunk-DUQGKWHJ.js +309 -0
  6. package/dist/chunk-DUQGKWHJ.js.map +7 -0
  7. package/dist/{chunk-LCT6MT2V.js → chunk-E32IADV3.js} +13 -12
  8. package/dist/chunk-E32IADV3.js.map +7 -0
  9. package/dist/{chunk-42A3HEEH.js → chunk-F5DPTXRC.js} +30 -2
  10. package/dist/{chunk-42A3HEEH.js.map → chunk-F5DPTXRC.js.map} +3 -3
  11. package/dist/{chunk-YEBBX4HQ.js → chunk-I4GHR6CY.js} +2 -2
  12. package/dist/{chunk-CROYS75W.js → chunk-LJ5OT5XN.js} +4 -4
  13. package/dist/{chunk-MTLAIJED.js → chunk-OWUS3HMA.js} +2 -2
  14. package/dist/{chunk-OGVORIFM.js → chunk-R56PH7YE.js} +11 -3
  15. package/dist/chunk-R56PH7YE.js.map +7 -0
  16. package/dist/{chunk-2BCPVUSF.js → chunk-RYMK7FQL.js} +5 -5
  17. package/dist/chunk-SO3T35U7.js +80 -0
  18. package/dist/chunk-SO3T35U7.js.map +7 -0
  19. package/dist/{chunk-Y2ARLMWZ.js → chunk-VBOTWXJW.js} +5 -5
  20. package/dist/commands/antigravity/backfill.js +2 -2
  21. package/dist/commands/auth/create-key.js +2 -2
  22. package/dist/commands/auth/login.js +2 -2
  23. package/dist/commands/auth/logout.js +2 -2
  24. package/dist/commands/auth/revoke-key.js +2 -2
  25. package/dist/commands/auth/status.js +2 -2
  26. package/dist/commands/catalog/index.js +2 -2
  27. package/dist/commands/catalog/recommend.js +2 -2
  28. package/dist/commands/convert.js +2 -2
  29. package/dist/commands/converters.js +2 -2
  30. package/dist/commands/crons/list.js +2 -2
  31. package/dist/commands/crons/run.js +2 -2
  32. package/dist/commands/dashboard.js +2 -2
  33. package/dist/commands/eval-targets/create.js +2 -2
  34. package/dist/commands/eval-targets/list.js +2 -2
  35. package/dist/commands/evals/agent.js +14 -13
  36. package/dist/commands/evals/agent.js.map +2 -2
  37. package/dist/commands/evals/benchmark.js +2 -2
  38. package/dist/commands/evals/compare.js +2 -2
  39. package/dist/commands/evals/create.js +2 -2
  40. package/dist/commands/evals/optimize-description.js +2 -2
  41. package/dist/commands/evals/optimize.js +2 -2
  42. package/dist/commands/evals/run.js +2 -2
  43. package/dist/commands/gateway/ensure.js +2 -2
  44. package/dist/commands/hooks/emit-instructions.js +25 -0
  45. package/dist/commands/hooks/emit-instructions.js.map +7 -0
  46. package/dist/commands/hooks/install.js +19 -25
  47. package/dist/commands/hooks/install.js.map +2 -2
  48. package/dist/commands/hooks/run.js +2 -2
  49. package/dist/commands/hooks/uninstall.js +6 -6
  50. package/dist/commands/hooks/uninstall.js.map +2 -2
  51. package/dist/commands/import.js +2 -2
  52. package/dist/commands/instructions/create.js +2 -2
  53. package/dist/commands/instructions/delete.js +2 -2
  54. package/dist/commands/instructions/fork.js +2 -2
  55. package/dist/commands/instructions/get.js +2 -2
  56. package/dist/commands/instructions/list.js +2 -2
  57. package/dist/commands/instructions/promote.js +2 -2
  58. package/dist/commands/instructions/pull.js +2 -2
  59. package/dist/commands/instructions/upstream.js +2 -2
  60. package/dist/commands/instructions/versions.js +2 -2
  61. package/dist/commands/launcher.js +2 -2
  62. package/dist/commands/org/current.js +2 -2
  63. package/dist/commands/org/list.js +2 -2
  64. package/dist/commands/org/switch.js +2 -2
  65. package/dist/commands/packs/add-instruction.js +2 -2
  66. package/dist/commands/packs/apply.js +2 -2
  67. package/dist/commands/packs/create.js +2 -2
  68. package/dist/commands/packs/fork.js +2 -2
  69. package/dist/commands/packs/get.js +2 -2
  70. package/dist/commands/packs/list.js +2 -2
  71. package/dist/commands/packs/remove-instruction.js +2 -2
  72. package/dist/commands/proxy/env.js +2 -2
  73. package/dist/commands/proxy/logs.js +2 -2
  74. package/dist/commands/proxy/restart.js +2 -2
  75. package/dist/commands/proxy/setup.js +2 -2
  76. package/dist/commands/proxy/start.js +2 -2
  77. package/dist/commands/proxy/status.js +2 -2
  78. package/dist/commands/proxy/stop.js +2 -2
  79. package/dist/commands/research/backfill.js +5 -5
  80. package/dist/commands/research/tail-transcript.js +5 -5
  81. package/dist/commands/service/install.js +9 -3
  82. package/dist/commands/service/install.js.map +2 -2
  83. package/dist/commands/service/status.js +2 -2
  84. package/dist/commands/service/uninstall.js +2 -2
  85. package/dist/commands/sessions/analyze.js +2 -2
  86. package/dist/commands/sessions/end.js +2 -2
  87. package/dist/commands/sessions/import.js +2 -2
  88. package/dist/commands/sessions/list.js +2 -2
  89. package/dist/commands/sessions/start.js +2 -2
  90. package/dist/commands/setup.js +146 -0
  91. package/dist/commands/setup.js.map +7 -0
  92. package/dist/commands/signals/report.js +2 -2
  93. package/dist/commands/skills/export-approved.js +2 -2
  94. package/dist/commands/skills/install.js +2 -2
  95. package/dist/commands/skills/list.js +2 -2
  96. package/dist/commands/skills/publish.js +2 -2
  97. package/dist/commands/skills/search.js +2 -2
  98. package/dist/commands/skills/update.js +2 -2
  99. package/dist/commands/suggestions/accept.js +2 -2
  100. package/dist/commands/suggestions/dismiss.js +2 -2
  101. package/dist/commands/suggestions/list.js +2 -2
  102. package/dist/commands/suggestions/refresh.js +2 -2
  103. package/dist/dashboard/Dashboard.js +17 -17
  104. package/dist/dashboard/launcher/LauncherOverlay.js +3 -3
  105. package/dist/lib/agent-loop.js +10 -10
  106. package/dist/lib/handlers/process-announcement.js +3 -3
  107. package/dist/lib/handlers/process-changelog.js +2 -2
  108. package/dist/lib/handlers/process-run-cleanup.js +2 -2
  109. package/dist/lib/handlers/process-session-goal.js +2 -2
  110. package/dist/lib/hooks-config.js +7 -1
  111. package/dist/lib/instruction-snapshot.js +8 -0
  112. package/dist/lib/instruction-snapshot.js.map +7 -0
  113. package/dist/lib/manual-tasks.js +2 -2
  114. package/dist/lib/setup-steps.js +26 -0
  115. package/dist/lib/setup-steps.js.map +7 -0
  116. package/dist/lib/version-check.js +3 -1
  117. package/oclif.manifest.json +207 -116
  118. package/package.json +7 -7
  119. package/dist/chunk-L674RNKQ.js.map +0 -7
  120. package/dist/chunk-LCT6MT2V.js.map +0 -7
  121. package/dist/chunk-OGVORIFM.js.map +0 -7
  122. /package/dist/{chunk-S56LOTGQ.js.map → chunk-7DIWBTB5.js.map} +0 -0
  123. /package/dist/{chunk-YEBBX4HQ.js.map → chunk-I4GHR6CY.js.map} +0 -0
  124. /package/dist/{chunk-CROYS75W.js.map → chunk-LJ5OT5XN.js.map} +0 -0
  125. /package/dist/{chunk-MTLAIJED.js.map → chunk-OWUS3HMA.js.map} +0 -0
  126. /package/dist/{chunk-2BCPVUSF.js.map → chunk-RYMK7FQL.js.map} +0 -0
  127. /package/dist/{chunk-Y2ARLMWZ.js.map → chunk-VBOTWXJW.js.map} +0 -0
@@ -311,6 +311,67 @@
311
311
  "launcher.js"
312
312
  ]
313
313
  },
314
+ "setup": {
315
+ "aliases": [],
316
+ "args": {
317
+ "target": {
318
+ "description": "\"plan\" or a step id (install, auth, hooks, worker)",
319
+ "name": "target",
320
+ "required": false
321
+ }
322
+ },
323
+ "description": "Agentic, transparent setup: plan / show diffs / apply the four core onboarding steps",
324
+ "examples": [
325
+ "<%= config.bin %> setup # interactive walk (TTY)",
326
+ "<%= config.bin %> setup plan # read-only: every remaining artifact + diff",
327
+ "<%= config.bin %> setup hooks --show # print one step's diff",
328
+ "<%= config.bin %> setup hooks --apply # apply one step",
329
+ "<%= config.bin %> setup --json # machine-readable status (exit 0 iff complete)"
330
+ ],
331
+ "flags": {
332
+ "api-url": {
333
+ "description": "Override API base URL",
334
+ "env": "RULEMETRIC_API_URL",
335
+ "hidden": true,
336
+ "name": "api-url",
337
+ "hasDynamicHelp": false,
338
+ "multiple": false,
339
+ "type": "option"
340
+ },
341
+ "json": {
342
+ "description": "Machine-readable output",
343
+ "name": "json",
344
+ "allowNo": false,
345
+ "type": "boolean"
346
+ },
347
+ "show": {
348
+ "description": "Print the step diff (read-only)",
349
+ "name": "show",
350
+ "allowNo": false,
351
+ "type": "boolean"
352
+ },
353
+ "apply": {
354
+ "description": "Apply the step",
355
+ "name": "apply",
356
+ "allowNo": false,
357
+ "type": "boolean"
358
+ }
359
+ },
360
+ "hasDynamicHelp": false,
361
+ "hiddenAliases": [],
362
+ "id": "setup",
363
+ "pluginAlias": "@rulemetric/cli",
364
+ "pluginName": "@rulemetric/cli",
365
+ "pluginType": "core",
366
+ "strict": true,
367
+ "enableJsonFlag": false,
368
+ "isESM": true,
369
+ "relativePath": [
370
+ "dist",
371
+ "commands",
372
+ "setup.js"
373
+ ]
374
+ },
314
375
  "antigravity:backfill": {
315
376
  "aliases": [],
316
377
  "args": {
@@ -1458,6 +1519,36 @@
1458
1519
  "ensure.js"
1459
1520
  ]
1460
1521
  },
1522
+ "hooks:emit-instructions": {
1523
+ "aliases": [],
1524
+ "args": {},
1525
+ "description": "Print a synthetic instruction snapshot (on-disk CLAUDE.md) as JSON for zero-proxy instruction linking",
1526
+ "flags": {
1527
+ "project-dir": {
1528
+ "description": "Project directory to scan (defaults to cwd)",
1529
+ "name": "project-dir",
1530
+ "hasDynamicHelp": false,
1531
+ "multiple": false,
1532
+ "type": "option"
1533
+ }
1534
+ },
1535
+ "hasDynamicHelp": false,
1536
+ "hidden": true,
1537
+ "hiddenAliases": [],
1538
+ "id": "hooks:emit-instructions",
1539
+ "pluginAlias": "@rulemetric/cli",
1540
+ "pluginName": "@rulemetric/cli",
1541
+ "pluginType": "core",
1542
+ "strict": true,
1543
+ "enableJsonFlag": false,
1544
+ "isESM": true,
1545
+ "relativePath": [
1546
+ "dist",
1547
+ "commands",
1548
+ "hooks",
1549
+ "emit-instructions.js"
1550
+ ]
1551
+ },
1461
1552
  "hooks:install": {
1462
1553
  "aliases": [],
1463
1554
  "args": {},
@@ -2074,121 +2165,6 @@
2074
2165
  "versions.js"
2075
2166
  ]
2076
2167
  },
2077
- "org:current": {
2078
- "aliases": [],
2079
- "args": {},
2080
- "description": "Show the current active organization",
2081
- "examples": [
2082
- "<%= config.bin %> org current"
2083
- ],
2084
- "flags": {
2085
- "api-url": {
2086
- "description": "Override API base URL",
2087
- "env": "RULEMETRIC_API_URL",
2088
- "hidden": true,
2089
- "name": "api-url",
2090
- "hasDynamicHelp": false,
2091
- "multiple": false,
2092
- "type": "option"
2093
- }
2094
- },
2095
- "hasDynamicHelp": false,
2096
- "hiddenAliases": [],
2097
- "id": "org:current",
2098
- "pluginAlias": "@rulemetric/cli",
2099
- "pluginName": "@rulemetric/cli",
2100
- "pluginType": "core",
2101
- "strict": true,
2102
- "enableJsonFlag": false,
2103
- "isESM": true,
2104
- "relativePath": [
2105
- "dist",
2106
- "commands",
2107
- "org",
2108
- "current.js"
2109
- ]
2110
- },
2111
- "org:list": {
2112
- "aliases": [],
2113
- "args": {},
2114
- "description": "List organizations the current user is a member of",
2115
- "examples": [
2116
- "<%= config.bin %> org list"
2117
- ],
2118
- "flags": {
2119
- "api-url": {
2120
- "description": "Override API base URL",
2121
- "env": "RULEMETRIC_API_URL",
2122
- "hidden": true,
2123
- "name": "api-url",
2124
- "hasDynamicHelp": false,
2125
- "multiple": false,
2126
- "type": "option"
2127
- }
2128
- },
2129
- "hasDynamicHelp": false,
2130
- "hiddenAliases": [],
2131
- "id": "org:list",
2132
- "pluginAlias": "@rulemetric/cli",
2133
- "pluginName": "@rulemetric/cli",
2134
- "pluginType": "core",
2135
- "strict": true,
2136
- "enableJsonFlag": false,
2137
- "isESM": true,
2138
- "relativePath": [
2139
- "dist",
2140
- "commands",
2141
- "org",
2142
- "list.js"
2143
- ]
2144
- },
2145
- "org:switch": {
2146
- "aliases": [],
2147
- "args": {
2148
- "slug": {
2149
- "description": "Organization slug to switch to",
2150
- "name": "slug",
2151
- "required": false
2152
- }
2153
- },
2154
- "description": "Switch the active organization",
2155
- "examples": [
2156
- "<%= config.bin %> org switch acme",
2157
- "<%= config.bin %> org switch --clear"
2158
- ],
2159
- "flags": {
2160
- "api-url": {
2161
- "description": "Override API base URL",
2162
- "env": "RULEMETRIC_API_URL",
2163
- "hidden": true,
2164
- "name": "api-url",
2165
- "hasDynamicHelp": false,
2166
- "multiple": false,
2167
- "type": "option"
2168
- },
2169
- "clear": {
2170
- "description": "Clear the active organization (personal mode)",
2171
- "name": "clear",
2172
- "allowNo": false,
2173
- "type": "boolean"
2174
- }
2175
- },
2176
- "hasDynamicHelp": false,
2177
- "hiddenAliases": [],
2178
- "id": "org:switch",
2179
- "pluginAlias": "@rulemetric/cli",
2180
- "pluginName": "@rulemetric/cli",
2181
- "pluginType": "core",
2182
- "strict": true,
2183
- "enableJsonFlag": false,
2184
- "isESM": true,
2185
- "relativePath": [
2186
- "dist",
2187
- "commands",
2188
- "org",
2189
- "switch.js"
2190
- ]
2191
- },
2192
2168
  "packs:add-instruction": {
2193
2169
  "aliases": [],
2194
2170
  "args": {
@@ -3961,7 +3937,122 @@
3961
3937
  "suggestions",
3962
3938
  "refresh.js"
3963
3939
  ]
3940
+ },
3941
+ "org:current": {
3942
+ "aliases": [],
3943
+ "args": {},
3944
+ "description": "Show the current active organization",
3945
+ "examples": [
3946
+ "<%= config.bin %> org current"
3947
+ ],
3948
+ "flags": {
3949
+ "api-url": {
3950
+ "description": "Override API base URL",
3951
+ "env": "RULEMETRIC_API_URL",
3952
+ "hidden": true,
3953
+ "name": "api-url",
3954
+ "hasDynamicHelp": false,
3955
+ "multiple": false,
3956
+ "type": "option"
3957
+ }
3958
+ },
3959
+ "hasDynamicHelp": false,
3960
+ "hiddenAliases": [],
3961
+ "id": "org:current",
3962
+ "pluginAlias": "@rulemetric/cli",
3963
+ "pluginName": "@rulemetric/cli",
3964
+ "pluginType": "core",
3965
+ "strict": true,
3966
+ "enableJsonFlag": false,
3967
+ "isESM": true,
3968
+ "relativePath": [
3969
+ "dist",
3970
+ "commands",
3971
+ "org",
3972
+ "current.js"
3973
+ ]
3974
+ },
3975
+ "org:list": {
3976
+ "aliases": [],
3977
+ "args": {},
3978
+ "description": "List organizations the current user is a member of",
3979
+ "examples": [
3980
+ "<%= config.bin %> org list"
3981
+ ],
3982
+ "flags": {
3983
+ "api-url": {
3984
+ "description": "Override API base URL",
3985
+ "env": "RULEMETRIC_API_URL",
3986
+ "hidden": true,
3987
+ "name": "api-url",
3988
+ "hasDynamicHelp": false,
3989
+ "multiple": false,
3990
+ "type": "option"
3991
+ }
3992
+ },
3993
+ "hasDynamicHelp": false,
3994
+ "hiddenAliases": [],
3995
+ "id": "org:list",
3996
+ "pluginAlias": "@rulemetric/cli",
3997
+ "pluginName": "@rulemetric/cli",
3998
+ "pluginType": "core",
3999
+ "strict": true,
4000
+ "enableJsonFlag": false,
4001
+ "isESM": true,
4002
+ "relativePath": [
4003
+ "dist",
4004
+ "commands",
4005
+ "org",
4006
+ "list.js"
4007
+ ]
4008
+ },
4009
+ "org:switch": {
4010
+ "aliases": [],
4011
+ "args": {
4012
+ "slug": {
4013
+ "description": "Organization slug to switch to",
4014
+ "name": "slug",
4015
+ "required": false
4016
+ }
4017
+ },
4018
+ "description": "Switch the active organization",
4019
+ "examples": [
4020
+ "<%= config.bin %> org switch acme",
4021
+ "<%= config.bin %> org switch --clear"
4022
+ ],
4023
+ "flags": {
4024
+ "api-url": {
4025
+ "description": "Override API base URL",
4026
+ "env": "RULEMETRIC_API_URL",
4027
+ "hidden": true,
4028
+ "name": "api-url",
4029
+ "hasDynamicHelp": false,
4030
+ "multiple": false,
4031
+ "type": "option"
4032
+ },
4033
+ "clear": {
4034
+ "description": "Clear the active organization (personal mode)",
4035
+ "name": "clear",
4036
+ "allowNo": false,
4037
+ "type": "boolean"
4038
+ }
4039
+ },
4040
+ "hasDynamicHelp": false,
4041
+ "hiddenAliases": [],
4042
+ "id": "org:switch",
4043
+ "pluginAlias": "@rulemetric/cli",
4044
+ "pluginName": "@rulemetric/cli",
4045
+ "pluginType": "core",
4046
+ "strict": true,
4047
+ "enableJsonFlag": false,
4048
+ "isESM": true,
4049
+ "relativePath": [
4050
+ "dist",
4051
+ "commands",
4052
+ "org",
4053
+ "switch.js"
4054
+ ]
3964
4055
  }
3965
4056
  },
3966
- "version": "0.3.0"
4057
+ "version": "0.5.0"
3967
4058
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rulemetric/cli",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -70,9 +70,9 @@
70
70
  "postgres": "^3.4.0",
71
71
  "react": "^19.0.0",
72
72
  "zod": "^3.24.0",
73
- "@rulemetric/hooks": "0.3.0",
74
- "@rulemetric/proxy": "0.3.0",
75
- "@rulemetric/skills-registry": "0.3.0"
73
+ "@rulemetric/proxy": "0.5.0",
74
+ "@rulemetric/skills-registry": "0.5.0",
75
+ "@rulemetric/hooks": "0.5.0"
76
76
  },
77
77
  "//deps": "Worker (`evals agent`) runtime deps: the @opentelemetry/* set, @sentry/node, and postgres are STATIC top-level imports loaded at worker startup (via bundled @rulemetric/{telemetry,db}) — they MUST be runtime deps or a clean `npm i -g` crashes with ModuleLoadError. `better-sqlite3` is optionalDependencies (below): it's a native module, lazy `require()`'d inside a try/catch (packages/session providers cursor/opencode) so a missing binary degrades gracefully — making it a hard dep would break `npm i -g` on platforms without prebuilt binaries. `resend` stays a devDep: lazy `await import('resend')` guarded by RESEND_API_KEY, which end-user workers never set (provider falls back to noop). Classification verified via import-graph scan of dist/commands/evals/agent.js + per-importer static/lazy check.",
78
78
  "optionalDependencies": {
@@ -91,12 +91,12 @@
91
91
  "typescript": "^5.9.0",
92
92
  "vitest": "^3.2.0",
93
93
  "@rulemetric/api": "0.0.1",
94
- "@rulemetric/core": "0.0.1",
95
- "@rulemetric/email": "0.0.1",
94
+ "@rulemetric/db": "0.0.1",
96
95
  "@rulemetric/session": "0.2.1",
97
96
  "@rulemetric/telemetry": "0.0.1",
97
+ "@rulemetric/core": "0.0.1",
98
98
  "@rulemetric/typescript-config": "0.0.0",
99
- "@rulemetric/db": "0.0.1"
99
+ "@rulemetric/email": "0.0.1"
100
100
  },
101
101
  "scripts": {
102
102
  "build": "node scripts/build.mjs",
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../src/lib/hooks-config.ts"],
4
- "sourcesContent": ["import { execSync } from 'node:child_process';\nimport { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\n\nexport type DetectedTool = 'claude_code' | 'cursor' | 'copilot_agent' | 'antigravity';\n\n/**\n * Detect which AI coding tools are installed in the project by checking\n * for their config directories.\n */\nexport function detectInstalledTools(projectPath: string): DetectedTool[] {\n const tools: DetectedTool[] = ['claude_code'];\n\n if (existsSync(join(projectPath, '.cursor'))) {\n tools.push('cursor');\n }\n\n if (existsSync(join(projectPath, '.github'))) {\n tools.push('copilot_agent');\n }\n\n // Antigravity's workspace config lives at `.agents/`. The user-level\n // hooks file at ~/.gemini/config/hooks.json is installed unconditionally\n // by writeAntigravityUserHooks() so we don't gate on that here.\n if (existsSync(join(projectPath, '.agents'))) {\n tools.push('antigravity');\n }\n\n return tools;\n}\n\n/**\n * Generate Cursor hooks config object.\n *\n * Cursor uses event names like beforeSubmitPrompt, afterFileEdit, etc.\n * Each event maps to an array of hook objects with a `command` field.\n */\nexport function generateCursorHooksConfig(): object {\n // Event coverage matches Cursor 1.7+ hook API (cursor.com/docs/hooks).\n // `sessionStart` is required so the registry file is written with\n // conversation_id before any tool runs; without it the proxy-based linker\n // path would have nothing to map and we'd drop captures. `sessionEnd`\n // emits the session-end signal once per chat conversation (vs `stop`\n // which fires per-turn \u2014 those go through post-tool-use).\n return {\n version: 1,\n hooks: {\n sessionStart: [\n { command: 'rulemetric hooks run session-start' },\n ],\n beforeSubmitPrompt: [\n { command: 'rulemetric hooks run user-prompt' },\n ],\n afterFileEdit: [\n { command: 'rulemetric hooks run post-tool-use' },\n ],\n beforeShellExecution: [\n { command: 'rulemetric hooks run post-tool-use' },\n ],\n afterShellExecution: [\n { command: 'rulemetric hooks run post-tool-use' },\n ],\n beforeMCPExecution: [\n { command: 'rulemetric hooks run post-tool-use' },\n ],\n afterMCPExecution: [\n { command: 'rulemetric hooks run post-tool-use' },\n ],\n // `afterAgentResponse` carries the full assistant response text +\n // token usage. Verified empirically via a probe hook on\n // 2026-05-12 \u2014 Cursor passes `text`, `input_tokens`,\n // `output_tokens`, `cache_read_tokens`, `cache_write_tokens`. This\n // is the cleanest path to per-turn assistant content for Cursor\n // (chat traffic itself bypasses the proxy via HTTP/2 \u2014 see\n // docs/audits/2026-05-11-cursor-capture-architecture.md).\n afterAgentResponse: [\n { command: 'rulemetric hooks run assistant-response' },\n ],\n // `stop` fires when the agent loop ends for a single turn (status:\n // completed|aborted|error). Per-turn, NOT per-session \u2014 route it to\n // post-tool-use, not session-end. The real session boundary is\n // `sessionEnd` below.\n stop: [\n { command: 'rulemetric hooks run post-tool-use' },\n ],\n sessionEnd: [\n { command: 'rulemetric hooks run session-end' },\n ],\n },\n };\n}\n\n/**\n * Generate Copilot Agent hooks config object.\n *\n * Copilot uses event names like sessionStart, userPromptSubmitted, etc.\n * Each event maps to an array of hook objects with `type` and `bash` fields.\n */\nexport function generateCopilotHooksConfig(): object {\n return {\n version: 1,\n hooks: {\n sessionStart: [\n { type: 'command', bash: 'rulemetric hooks run session-start' },\n ],\n userPromptSubmitted: [\n { type: 'command', bash: 'rulemetric hooks run user-prompt' },\n ],\n postToolUse: [\n { type: 'command', bash: 'rulemetric hooks run post-tool-use' },\n ],\n sessionEnd: [\n { type: 'command', bash: 'rulemetric hooks run session-end' },\n ],\n },\n };\n}\n\n/**\n * Merge rulemetric hooks into a Cursor `hooks.json` at the given directory.\n *\n * Used by both the project (`<repo>/.cursor/`) and user (`~/.cursor/`) writers.\n * Returns the config file path, or null if the cursor dir doesn't exist.\n */\nfunction writeCursorHooksToDir(cursorDir: string): string | null {\n if (!existsSync(cursorDir)) {\n return null;\n }\n\n const configPath = join(cursorDir, 'hooks.json');\n const generated = generateCursorHooksConfig() as {\n version: number;\n hooks: Record<string, Array<{ command: string }>>;\n };\n\n let existing: { version?: number; hooks?: Record<string, unknown[]> } = {};\n if (existsSync(configPath)) {\n try {\n existing = JSON.parse(readFileSync(configPath, 'utf-8'));\n } catch {\n existing = {};\n }\n }\n\n const merged: Record<string, unknown[]> = { ...(existing.hooks ?? {}) };\n\n for (const [event, hooks] of Object.entries(generated.hooks)) {\n const current = merged[event] ?? [];\n const hasRulemetric = current.some(\n (h: unknown) =>\n typeof h === 'object' &&\n h !== null &&\n 'command' in h &&\n typeof (h as { command: string }).command === 'string' &&\n (h as { command: string }).command.includes('rulemetric'),\n );\n if (!hasRulemetric) {\n merged[event] = [...current, ...hooks];\n }\n }\n\n const result = {\n version: existing.version ?? generated.version,\n hooks: merged,\n };\n\n writeFileSync(configPath, JSON.stringify(result, null, 2) + '\\n');\n return configPath;\n}\n\n/**\n * Write Cursor hooks config to `<projectPath>/.cursor/hooks.json`.\n * Merges with existing config \u2014 appends rulemetric hooks without duplicating.\n * Returns the config file path, or null if `.cursor/` doesn't exist.\n */\nexport function writeCursorHooks(projectPath: string): string | null {\n return writeCursorHooksToDir(join(projectPath, '.cursor'));\n}\n\n/**\n * Write Cursor hooks config to user-level `~/.cursor/hooks.json` so capture\n * works for every Cursor conversation without per-project setup.\n *\n * Returns the config file path, or null if `~/.cursor/` doesn't exist\n * (i.e., Cursor has never been launched on this machine).\n */\nexport function writeCursorUserHooks(): string | null {\n return writeCursorHooksToDir(join(homedir(), '.cursor'));\n}\n\n/**\n * Write Copilot Agent hooks config to `.github/hooks/rulemetric.json`.\n * Creates the hooks directory if needed.\n * Returns the config file path, or null if `.github/` doesn't exist.\n */\nexport function writeCopilotHooks(projectPath: string): string | null {\n const githubDir = join(projectPath, '.github');\n if (!existsSync(githubDir)) {\n return null;\n }\n\n const hooksDir = join(githubDir, 'hooks');\n mkdirSync(hooksDir, { recursive: true });\n\n const configPath = join(hooksDir, 'rulemetric.json');\n const config = generateCopilotHooksConfig();\n\n writeFileSync(configPath, JSON.stringify(config, null, 2) + '\\n');\n return configPath;\n}\n\n/**\n * Strip rulemetric hooks from a Cursor `hooks.json` at the given path.\n * Preserves other hooks and removes empty event arrays.\n * Returns true if changes were made.\n */\nfunction removeCursorHooksFromFile(configPath: string): boolean {\n if (!existsSync(configPath)) {\n return false;\n }\n\n let config: { version?: number; hooks?: Record<string, unknown[]> };\n try {\n config = JSON.parse(readFileSync(configPath, 'utf-8'));\n } catch {\n return false;\n }\n\n if (!config.hooks) {\n return false;\n }\n\n let changed = false;\n for (const [event, hooks] of Object.entries(config.hooks)) {\n const filtered = hooks.filter((h: unknown) => {\n if (typeof h !== 'object' || h === null || !('command' in h)) return true;\n const cmd = (h as { command: string }).command;\n if (typeof cmd === 'string' && cmd.includes('rulemetric')) {\n changed = true;\n return false;\n }\n return true;\n });\n\n if (filtered.length === 0) {\n delete config.hooks[event];\n } else {\n config.hooks[event] = filtered;\n }\n }\n\n if (changed) {\n writeFileSync(configPath, JSON.stringify(config, null, 2) + '\\n');\n }\n\n return changed;\n}\n\n/**\n * Remove rulemetric hooks from `<projectPath>/.cursor/hooks.json`.\n */\nexport function removeCursorHooks(projectPath: string): boolean {\n return removeCursorHooksFromFile(join(projectPath, '.cursor', 'hooks.json'));\n}\n\n/**\n * Remove rulemetric hooks from user-level `~/.cursor/hooks.json`.\n */\nexport function removeCursorUserHooks(): boolean {\n return removeCursorHooksFromFile(join(homedir(), '.cursor', 'hooks.json'));\n}\n\n/**\n * Write Cursor proxy config to `.cursor/settings.json`.\n * Sets `http.proxy` and `http.proxyStrictSSL` while preserving existing settings.\n * Returns true if `.cursor/` exists and config was written, false otherwise.\n */\nexport function writeCursorProxyConfig(projectPath: string, gatewayPort: number, certPath: string): boolean {\n const cursorDir = join(projectPath, '.cursor');\n if (!existsSync(cursorDir)) return false;\n\n const settingsPath = join(cursorDir, 'settings.json');\n let settings: Record<string, unknown> = {};\n if (existsSync(settingsPath)) {\n try { settings = JSON.parse(readFileSync(settingsPath, 'utf-8')); } catch { /* empty */ }\n }\n\n settings['http.proxy'] = `http://localhost:${gatewayPort}`;\n settings['http.proxyStrictSSL'] = existsSync(certPath);\n writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\\n');\n return true;\n}\n\n/**\n * Write VS Code proxy config to `.vscode/settings.json` for Copilot capture.\n * Sets `http.proxy`, `http.proxyStrictSSL`, and Copilot-specific override\n * while preserving existing settings.\n * Returns true if config was written, false if `.vscode/` doesn't exist.\n */\nexport function writeVSCodeProxyConfig(projectPath: string, gatewayPort: number, certPath: string): boolean {\n const vscodeDir = join(projectPath, '.vscode');\n mkdirSync(vscodeDir, { recursive: true });\n\n const settingsPath = join(vscodeDir, 'settings.json');\n let settings: Record<string, unknown> = {};\n if (existsSync(settingsPath)) {\n try { settings = JSON.parse(readFileSync(settingsPath, 'utf-8')); } catch { /* empty */ }\n }\n\n settings['http.proxy'] = `http://localhost:${gatewayPort}`;\n settings['http.proxyStrictSSL'] = existsSync(certPath);\n\n // Copilot-specific override for surgical proxy targeting\n const copilotAdvanced = (settings['github.copilot.advanced'] ?? {}) as Record<string, unknown>;\n copilotAdvanced['debug.overrideProxyUrl'] = `http://localhost:${gatewayPort}`;\n settings['github.copilot.advanced'] = copilotAdvanced;\n\n writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\\n');\n return true;\n}\n\n/**\n * Remove VS Code proxy config from `.vscode/settings.json`.\n * Removes `http.proxy`, `http.proxyStrictSSL`, and Copilot proxy override.\n * Returns true if changes were made.\n */\nexport function removeVSCodeProxyConfig(projectPath: string): boolean {\n const settingsPath = join(projectPath, '.vscode', 'settings.json');\n if (!existsSync(settingsPath)) return false;\n\n let settings: Record<string, unknown>;\n try { settings = JSON.parse(readFileSync(settingsPath, 'utf-8')); } catch { return false; }\n\n let changed = false;\n\n if (settings['http.proxy']) {\n delete settings['http.proxy'];\n delete settings['http.proxyStrictSSL'];\n changed = true;\n }\n\n const copilotAdvanced = settings['github.copilot.advanced'] as Record<string, unknown> | undefined;\n if (copilotAdvanced?.['debug.overrideProxyUrl']) {\n delete copilotAdvanced['debug.overrideProxyUrl'];\n if (Object.keys(copilotAdvanced).length === 0) {\n delete settings['github.copilot.advanced'];\n }\n changed = true;\n }\n\n if (changed) {\n writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\\n');\n }\n\n return changed;\n}\n\n/**\n * Remove Copilot Agent hooks by deleting `.github/hooks/rulemetric.json`.\n * Returns true if the file was removed.\n */\nexport function removeCopilotHooks(projectPath: string): boolean {\n const configPath = join(projectPath, '.github', 'hooks', 'rulemetric.json');\n if (!existsSync(configPath)) {\n return false;\n }\n\n unlinkSync(configPath);\n return true;\n}\n\n// ---------------------------------------------------------------------------\n// macOS LaunchAgent for GUI app env vars (HTTPS_PROXY, NODE_EXTRA_CA_CERTS)\n// ---------------------------------------------------------------------------\n\nconst LAUNCH_AGENT_LABEL = 'com.rulemetric.env';\n\nfunction getLaunchAgentPath(): string {\n return join(homedir(), 'Library', 'LaunchAgents', `${LAUNCH_AGENT_LABEL}.plist`);\n}\n\n/**\n * Set HTTPS_PROXY and NODE_EXTRA_CA_CERTS for all GUI apps on macOS.\n *\n * Two layers:\n * 1. `launchctl setenv` \u2014 immediate effect for newly launched apps\n * 2. LaunchAgent plist \u2014 persists across reboots\n *\n * This ensures VS Code / Copilot / Cursor route LLM traffic through the\n * gateway even when launched from Dock or Spotlight (not a terminal).\n *\n * References:\n * - anthropics/claude-code#30318 (same pattern for Claude Desktop)\n * - Snyk IDE docs recommend launchctl setenv for macOS\n * - Apple QA1067 (archived) \u2014 original env var mechanism removed in 10.10\n */\nexport function installMacOSProxyEnv(gatewayPort: number, certPath: string): boolean {\n if (process.platform !== 'darwin') return false;\n\n const proxyUrl = `http://localhost:${gatewayPort}`;\n\n // Layer 1: immediate effect via launchctl setenv\n try {\n execSync(`launchctl setenv HTTPS_PROXY ${proxyUrl}`, { stdio: 'pipe' });\n if (existsSync(certPath)) {\n execSync(`launchctl setenv NODE_EXTRA_CA_CERTS ${certPath}`, { stdio: 'pipe' });\n }\n } catch {\n return false;\n }\n\n // Layer 2: LaunchAgent plist for persistence across reboots\n const plistPath = getLaunchAgentPath();\n const agentsDir = join(homedir(), 'Library', 'LaunchAgents');\n mkdirSync(agentsDir, { recursive: true });\n\n const certLine = existsSync(certPath)\n ? `launchctl setenv NODE_EXTRA_CA_CERTS ${certPath};`\n : '';\n\n const plist = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\"\n \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n <key>Label</key>\n <string>${LAUNCH_AGENT_LABEL}</string>\n <key>ProgramArguments</key>\n <array>\n <string>/bin/sh</string>\n <string>-c</string>\n <string>launchctl setenv HTTPS_PROXY ${proxyUrl};${certLine}</string>\n </array>\n <key>RunAtLoad</key>\n <true/>\n</dict>\n</plist>\n`;\n\n writeFileSync(plistPath, plist);\n return true;\n}\n\n/**\n * Remove the RuleMetric LaunchAgent and unset env vars.\n */\nexport function uninstallMacOSProxyEnv(): boolean {\n if (process.platform !== 'darwin') return false;\n\n let changed = false;\n\n // Unset env vars for current session\n try {\n execSync('launchctl unsetenv HTTPS_PROXY', { stdio: 'pipe' });\n execSync('launchctl unsetenv NODE_EXTRA_CA_CERTS', { stdio: 'pipe' });\n } catch { /* may not be set */ }\n\n // Remove LaunchAgent plist\n const plistPath = getLaunchAgentPath();\n if (existsSync(plistPath)) {\n unlinkSync(plistPath);\n changed = true;\n }\n\n return changed;\n}\n\n// ---------------------------------------------------------------------------\n// VS Code user-level proxy settings\n// ---------------------------------------------------------------------------\n\nfunction getVSCodeUserSettingsPath(): string {\n if (process.platform === 'darwin') {\n return join(homedir(), 'Library', 'Application Support', 'Code', 'User', 'settings.json');\n }\n if (process.platform === 'win32') {\n return join(process.env.APPDATA ?? '', 'Code', 'User', 'settings.json');\n }\n return join(homedir(), '.config', 'Code', 'User', 'settings.json');\n}\n\n/**\n * Set http.proxy in VS Code user settings (not workspace).\n *\n * Workspace-level http.proxy is silently ignored because VS Code restricts\n * it to APPLICATION scope (microsoft/vscode#236932). User-level settings\n * are the only reliable way to configure http.proxy.\n */\nexport function writeVSCodeUserProxyConfig(gatewayPort: number): boolean {\n const settingsPath = getVSCodeUserSettingsPath();\n if (!existsSync(join(settingsPath, '..'))) return false;\n\n let settings: Record<string, unknown> = {};\n if (existsSync(settingsPath)) {\n try { settings = JSON.parse(readFileSync(settingsPath, 'utf-8')); } catch { /* empty */ }\n }\n\n // Only set if not already configured by the user\n if (settings['http.proxy'] && !(settings['http.proxy'] as string).includes('localhost')) {\n return false; // user has a custom proxy, don't overwrite\n }\n\n settings['http.proxy'] = `http://localhost:${gatewayPort}`;\n settings['http.proxyStrictSSL'] = false;\n\n writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\\n');\n return true;\n}\n\n/**\n * Remove RuleMetric proxy config from VS Code user settings.\n */\nexport function removeVSCodeUserProxyConfig(): boolean {\n const settingsPath = getVSCodeUserSettingsPath();\n if (!existsSync(settingsPath)) return false;\n\n let settings: Record<string, unknown>;\n try { settings = JSON.parse(readFileSync(settingsPath, 'utf-8')); } catch { return false; }\n\n const proxy = settings['http.proxy'] as string | undefined;\n if (!proxy?.includes('localhost')) return false;\n\n delete settings['http.proxy'];\n delete settings['http.proxyStrictSSL'];\n writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\\n');\n return true;\n}\n\n// ---------------------------------------------------------------------------\n// Cursor user-level proxy settings\n// ---------------------------------------------------------------------------\n//\n// Cursor is a VS Code fork and uses the same settings.json shape, but reads\n// from `Cursor/User/settings.json` instead of `Code/User/settings.json`. The\n// `.cursor/settings.json` (workspace) path can't reach Cursor's Electron main\n// process for the same reason VS Code's workspace path can't reach VS Code's\n// \u2014 the http.proxy setting is restricted to APPLICATION scope and only the\n// user-level file is honored.\n\nfunction getCursorUserSettingsPath(): string {\n if (process.platform === 'darwin') {\n return join(homedir(), 'Library', 'Application Support', 'Cursor', 'User', 'settings.json');\n }\n if (process.platform === 'win32') {\n return join(process.env.APPDATA ?? '', 'Cursor', 'User', 'settings.json');\n }\n return join(homedir(), '.config', 'Cursor', 'User', 'settings.json');\n}\n\n/**\n * Set http.proxy in Cursor user settings (not workspace). Mirrors\n * `writeVSCodeUserProxyConfig` \u2014 workspace-level http.proxy is silently\n * ignored due to APPLICATION-scope restriction (Cursor inherits this from\n * VS Code, see microsoft/vscode#236932).\n *\n * Returns true if config was written, false if Cursor isn't installed\n * (User dir absent) or the user already has a non-localhost proxy set.\n */\nexport function writeCursorUserProxyConfig(gatewayPort: number): boolean {\n const settingsPath = getCursorUserSettingsPath();\n if (!existsSync(join(settingsPath, '..'))) return false;\n\n let settings: Record<string, unknown> = {};\n if (existsSync(settingsPath)) {\n try { settings = JSON.parse(readFileSync(settingsPath, 'utf-8')); } catch { /* empty */ }\n }\n\n if (settings['http.proxy'] && !(settings['http.proxy'] as string).includes('localhost')) {\n return false;\n }\n\n settings['http.proxy'] = `http://localhost:${gatewayPort}`;\n settings['http.proxyStrictSSL'] = false;\n\n writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\\n');\n return true;\n}\n\n/**\n * Remove RuleMetric proxy config from Cursor user settings.\n */\nexport function removeCursorUserProxyConfig(): boolean {\n const settingsPath = getCursorUserSettingsPath();\n if (!existsSync(settingsPath)) return false;\n\n let settings: Record<string, unknown>;\n try { settings = JSON.parse(readFileSync(settingsPath, 'utf-8')); } catch { return false; }\n\n const proxy = settings['http.proxy'] as string | undefined;\n if (!proxy?.includes('localhost')) return false;\n\n delete settings['http.proxy'];\n delete settings['http.proxyStrictSSL'];\n writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\\n');\n return true;\n}\n\n// \u2500\u2500\u2500 Antigravity (Google's Gemini-based agentic IDE) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n//\n// Antigravity hooks live in `.agents/hooks.json` (workspace) and\n// `~/.gemini/config/hooks.json` (user-level). Events follow the\n// SessionStart / UserPromptSubmit / PreToolUse / PostToolUse / Stop /\n// SessionEnd vocabulary. Each hook command sets `RULEMETRIC_HOOK_TOOL=\n// antigravity` so `_normalize.sh` routes through the Antigravity branch.\n\ninterface AntigravityHookEntry {\n type: 'command';\n command: string;\n timeout?: number;\n}\n\ninterface AntigravityHookBlock {\n matcher?: string;\n hooks: AntigravityHookEntry[];\n}\n\ninterface AntigravityRuleMetricBlock {\n enabled: boolean;\n SessionStart: AntigravityHookBlock[];\n UserPromptSubmit: AntigravityHookBlock[];\n PreToolUse: AntigravityHookBlock[];\n PostToolUse: AntigravityHookBlock[];\n Stop: AntigravityHookBlock[];\n SessionEnd: AntigravityHookBlock[];\n}\n\nexport type AntigravityHooksConfig = { rulemetric: AntigravityRuleMetricBlock };\n\n/** Resolve the binary string to invoke for a hook command. */\nfunction getRulemetricCommand(): string {\n // Prefer the local Node binary + bin/run.js so the hook works even when\n // `rulemetric` isn't on PATH for the GUI Antigravity process.\n const nodeBin = process.execPath;\n // bin/run.js lives next to dist/ in the installed CLI; resolve from\n // import.meta isn't available in a portable way here, so we use the\n // process argv1 if it's pointing at the CLI entry, else fall back to\n // the bare command name. In practice this function is called inside\n // `rulemetric hooks install`, where process.argv[1] is bin/run.js.\n const cliEntry = process.argv[1];\n if (cliEntry && cliEntry.endsWith('run.js')) {\n return `${nodeBin} ${cliEntry}`;\n }\n return 'rulemetric';\n}\n\nexport function generateAntigravityHooksConfig(): AntigravityHooksConfig {\n const cmd = getRulemetricCommand();\n const mk = (name: string): AntigravityHookEntry => ({\n type: 'command',\n command: `RULEMETRIC_HOOK_TOOL=antigravity ${cmd} hooks run ${name}`,\n timeout: 30,\n });\n return {\n rulemetric: {\n enabled: true,\n SessionStart: [{ hooks: [mk('session-start')] }],\n UserPromptSubmit: [{ hooks: [mk('user-prompt')] }],\n PreToolUse: [{ matcher: '.*', hooks: [mk('post-tool-use')] }],\n PostToolUse: [{ matcher: '.*', hooks: [mk('post-tool-use')] }],\n Stop: [{ hooks: [mk('assistant-response')] }],\n SessionEnd: [{ hooks: [mk('session-end')] }],\n },\n };\n}\n\nfunction writeAntigravityHooksToDir(dir: string): string | null {\n const configPath = join(dir, 'hooks.json');\n let existing: Record<string, unknown> = {};\n if (existsSync(configPath)) {\n try { existing = JSON.parse(readFileSync(configPath, 'utf-8')); } catch { existing = {}; }\n }\n const generated = generateAntigravityHooksConfig();\n // Antigravity keys hook blocks by name; RuleMetric owns its own block\n // and never touches sibling blocks the user may have added.\n const merged = { ...existing, rulemetric: generated.rulemetric };\n mkdirSync(dir, { recursive: true });\n writeFileSync(configPath, JSON.stringify(merged, null, 2) + '\\n');\n return configPath;\n}\n\nexport function writeAntigravityHooks(projectPath: string): string | null {\n return writeAntigravityHooksToDir(join(projectPath, '.agents'));\n}\n\nexport function writeAntigravityUserHooks(): string | null {\n const configDir = join(homedir(), '.gemini', 'config');\n return writeAntigravityHooksToDir(configDir);\n}\n\nfunction removeAntigravityHooksFromFile(configPath: string): boolean {\n if (!existsSync(configPath)) return false;\n let existing: Record<string, unknown>;\n try { existing = JSON.parse(readFileSync(configPath, 'utf-8')); } catch { return false; }\n if (!('rulemetric' in existing)) return false;\n delete existing.rulemetric;\n if (Object.keys(existing).length === 0) {\n // No other blocks \u2014 remove the file entirely.\n try { unlinkSync(configPath); } catch { /* ignore */ }\n } else {\n writeFileSync(configPath, JSON.stringify(existing, null, 2) + '\\n');\n }\n return true;\n}\n\nexport function removeAntigravityHooks(projectPath: string): boolean {\n return removeAntigravityHooksFromFile(join(projectPath, '.agents', 'hooks.json'));\n}\n\nexport function removeAntigravityUserHooks(): boolean {\n const modern = removeAntigravityHooksFromFile(join(homedir(), '.gemini', 'config', 'hooks.json'));\n const legacy = removeAntigravityHooksFromFile(join(homedir(), '.gemini', 'hooks.json'));\n return modern || legacy;\n}\n"],
5
- "mappings": "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",
6
- "names": []
7
- }
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../src/lib/agent-loop.ts"],
4
- "sourcesContent": ["// Phase 4b thin-agent migration: the parallel-run worker loop that replaces\n// graphile-worker's LISTEN/NOTIFY pickup with HTTP long-polling against the\n// Phase 4a /api/work endpoints. See docs/plans/2026-05-25-thin-agent-migration.md.\n//\n// This file is invoked exclusively by `rulemetric evals agent` \u2014 the only\n// worker mode since Phase 4c deleted the graphile path.\n//\n// Lifecycle for one cycle:\n// 1. Long-poll GET /api/work/next?capabilities=...&waitMs=...\n// 2. If 204 (returned as \"\" by api-client because no JSON body), increment\n// a small backoff capped at 5s and re-poll.\n// 3. If 200, dispatch by `kind` to the matching handler from HANDLERS.\n// 4. Start a heartbeat timer that POSTs /api/work/:id/heartbeat at\n// heartbeatIntervalMs while the handler runs.\n// 5. On success: POST /api/work/:id/complete with { result, leaseToken }.\n// On PermanentJobError: POST /fail with terminal=true (server will not\n// requeue regardless of attempts). On any other error: POST /fail with\n// terminal=false (server requeues if attempts < max).\n//\n// Abort behavior: an AbortSignal short-circuits the next long-poll and any\n// backoff sleep. An in-flight handler can't be cancelled (it has already\n// spawned `claude` subprocesses), so we await it to natural completion and\n// then exit. This matches listen.ts's 10s shutdown grace pattern, except we\n// don't need an outer hard-kill timer here \u2014 the caller (the oclif command)\n// owns the process.exit.\n\nimport type { JobHelpers } from './job-helpers.js';\nimport { apiGet, apiPost, ApiError } from './api-client.js';\nimport { isPermanentJobError } from './permanent-job-error.js';\nimport { processEval } from './handlers/process-eval.js';\nimport { processInsights } from './handlers/process-insights.js';\nimport { processLaunch } from './handlers/process-launch.js';\nimport { processConversation } from './handlers/process-conversation.js';\nimport { processSendMessage } from './handlers/process-send-message.js';\nimport { processSessionGoal } from './handlers/process-session-goal.js';\nimport { processAnnouncement } from './handlers/process-announcement.js';\nimport { processRunCleanup } from './handlers/process-run-cleanup.js';\nimport { cronSuggestInstructions } from './handlers/cron-suggest-instructions.js';\nimport { cronRefreshSkills } from './handlers/cron-refresh-skills.js';\nimport { startTerminalTargetHeartbeat } from './terminal-target-heartbeat.js';\n\n// Handler dispatch table. Keys MUST match the API's CAPABILITY_TO_TABLE keys\n// in apps/api/src/routes/work.ts; mismatches would cause the API to silently\n// drop the capability and starve this kind of work.\ntype Handler = (\n payload: Record<string, unknown>,\n helpers: JobHelpers,\n) => Promise<Record<string, unknown> | undefined | void>;\n\nexport const HANDLERS: Record<string, Handler> = {\n process_eval: processEval as unknown as Handler,\n process_insights: processInsights as unknown as Handler,\n process_launch: processLaunch as unknown as Handler,\n process_send_message: processSendMessage as unknown as Handler,\n process_conversation: processConversation as unknown as Handler,\n // agent_jobs kinds (Phase 4c Stage 3b, migration 00083). processSessionGoal\n // and processAnnouncement take an optional deps object in the helpers\n // position \u2014 every key is optional, so JobHelpers passing through is\n // harmless and they fall back to their defaults.\n process_session_goal: processSessionGoal as unknown as Handler,\n process_announcement: processAnnouncement as unknown as Handler,\n // Multi-session orchestration (\"runs\") worktree cleanup (Task 5.2). agent_jobs\n // kind \u2014 no mirror table. Enqueued by POST /api/runs/:id/cleanup; prunes the\n // worktrees of a run's ended sessions, guarding against uncommitted work.\n process_run_cleanup: processRunCleanup as unknown as Handler,\n cron_suggest_instructions: cronSuggestInstructions as unknown as Handler,\n // Dropped by accident in the Phase 4c graphile\u2192agent_jobs migration: the\n // handler survived but lost its registration + schedule, so the skills\n // registry froze (last refresh 2026-05-21) and the suggest cron marked\n // every suggestion stale on each run. Re-scheduled by migration 00086.\n cron_refresh_skills: cronRefreshSkills as unknown as Handler,\n};\n\nexport const CAPABILITIES: readonly string[] = Object.keys(HANDLERS);\n\ninterface NextWorkResponse {\n id: string;\n kind: string;\n payload: Record<string, unknown>;\n attempts: number;\n leaseToken: string;\n leaseDurationSeconds: number;\n table: string;\n}\n\nexport interface RunAgentLoopOptions {\n signal: AbortSignal;\n log?: (msg: string) => void;\n /** Long-poll wait passed to GET /api/work/next?waitMs= */\n pollWaitMs?: number;\n /** How often to POST /api/work/:id/heartbeat while a handler runs */\n heartbeatIntervalMs?: number;\n /** Capability list to advertise. Defaults to all known handlers. */\n capabilities?: readonly string[];\n}\n\nconst DEFAULT_POLL_WAIT_MS = 30_000;\nconst DEFAULT_HEARTBEAT_MS = 30_000;\nconst PRESENCE_HEARTBEAT_MS = 30_000;\nconst BACKOFF_INITIAL_MS = 250;\nconst BACKOFF_MAX_MS = 5_000;\n\n// Stable per-process identifier so worker_connections groups the same agent\n// across heartbeats (the unique key is (userId, workerType, clientName)).\nconst PRESENCE_CLIENT_NAME = `thin-agent-${process.pid}`;\n\n/**\n * Background presence heartbeat. Posts to /api/workers/heartbeat every 30s so\n * worker_connections has a fresh row \u2014 that's what the dashboard's\n * useWorkerStatus reads to decide \"Worker online\" for live-send gating.\n * Distinct from the per-job heartbeat in runOne, which only extends a\n * specific job's lease. The graphile-worker runner had this since day one;\n * the thin agent was missing it, so the dashboard showed \"Worker offline\"\n * even when the agent was actively processing jobs.\n */\nfunction startPresenceHeartbeat(\n capabilities: readonly string[],\n signal: AbortSignal,\n log: (msg: string) => void,\n): void {\n const send = (status: 'alive' | 'shutting_down') => {\n void apiPost('/api/workers/heartbeat', {\n workerType: 'cli-worker',\n clientName: PRESENCE_CLIENT_NAME,\n workerVersion: '0.1.0',\n currentJobId: null,\n inFlightCount: 0,\n lastError: null,\n status,\n metadata: { capabilities, mode: 'thin-agent' },\n }).catch((err) => {\n structuredLog(log, {\n event: 'presence_heartbeat_error',\n error: err instanceof Error ? err.message : String(err),\n });\n });\n };\n send('alive');\n const timer = setInterval(() => send('alive'), PRESENCE_HEARTBEAT_MS);\n signal.addEventListener('abort', () => {\n clearInterval(timer);\n send('shutting_down');\n }, { once: true });\n}\n\nfunction structuredLog(\n log: (msg: string) => void,\n event: Record<string, unknown>,\n): void {\n try {\n log(JSON.stringify({ ts: new Date().toISOString(), ...event }));\n } catch {\n // JSON.stringify can throw on a circular structure; fall back to a\n // best-effort string so a log failure never takes down the loop.\n log(`[agent-loop] log_error event=${event['event']}`);\n }\n}\n\n/** Sleep that resolves early when the abort signal fires. Returns true if\n * the full duration elapsed, false if aborted. */\nfunction abortableSleep(ms: number, signal: AbortSignal): Promise<boolean> {\n if (signal.aborted) return Promise.resolve(false);\n if (ms <= 0) return Promise.resolve(true);\n return new Promise<boolean>((resolve) => {\n const timer = setTimeout(() => {\n signal.removeEventListener('abort', onAbort);\n resolve(true);\n }, ms);\n const onAbort = () => {\n clearTimeout(timer);\n signal.removeEventListener('abort', onAbort);\n resolve(false);\n };\n signal.addEventListener('abort', onAbort, { once: true });\n });\n}\n\n/** Poll once for work. Returns the parsed response or null on 204. api-client\n * returns \"\" for an empty response body (Content-Type is not JSON on 204), so\n * we use that as the 204 sentinel. */\nexport async function pollForWork(\n capabilities: readonly string[],\n waitMs: number,\n): Promise<NextWorkResponse | null> {\n const qs = `capabilities=${encodeURIComponent(capabilities.join(','))}&waitMs=${waitMs}`;\n const result = await apiGet<NextWorkResponse | string>(`/api/work/next?${qs}`);\n if (result == null || typeof result === 'string') return null;\n return result;\n}\n\n/** Construct a minimal JobHelpers shim. Handlers only touch\n * helpers.logger.{info,warn,error}(msg) and helpers.job.attempts \u2014 verified\n * by grep against apps/cli/src/lib/handlers/. We cast through `unknown` so\n * TS accepts the structurally smaller object as the full JobHelpers type;\n * pulling graphile-worker into this hot path would defeat the Phase 4c\n * cleanup that removes the dep entirely. */\nfunction makeHelpers(attempts: number, log: (msg: string) => void): JobHelpers {\n return {\n logger: {\n info: (msg: string) => log(msg),\n warn: (msg: string) => log(msg),\n error: (msg: string) => log(msg),\n },\n job: { attempts },\n };\n}\n\nasync function runOne(\n job: NextWorkResponse,\n heartbeatIntervalMs: number,\n log: (msg: string) => void,\n): Promise<void> {\n const handler = HANDLERS[job.kind];\n const startedAt = Date.now();\n structuredLog(log, {\n event: 'claim',\n jobId: job.id,\n kind: job.kind,\n attempts: job.attempts,\n });\n\n // Defensive: if the API returned a kind the agent doesn't know about\n // (capability filter mismatch, schema drift), fail terminally so the row\n // doesn't bounce forever. The /api/work/next filter should already prevent\n // this, but failing closed at the dispatcher means a single new kind on\n // the API side can't wedge an older agent.\n if (!handler) {\n const msg = `unknown kind: ${job.kind}`;\n structuredLog(log, {\n event: 'fail',\n jobId: job.id,\n kind: job.kind,\n terminal: true,\n error: msg,\n });\n await apiPost(`/api/work/${job.id}/fail`, {\n leaseToken: job.leaseToken,\n error: msg,\n terminal: true,\n }).catch(() => {\n // best-effort: the lease will expire and the reconciler reclaims it\n });\n return;\n }\n\n // Heartbeat timer. Starts after the first heartbeatIntervalMs elapses so\n // we don't spam an extra request for sub-second jobs. Errors are swallowed\n // \u2014 a single failed heartbeat doesn't abort the run; the server's\n // 5-minute lease gives us up to ten chances at 30s intervals before we\n // lose the claim.\n const heartbeatTimer = setInterval(() => {\n structuredLog(log, { event: 'heartbeat', jobId: job.id });\n apiPost(`/api/work/${job.id}/heartbeat`, {\n leaseToken: job.leaseToken,\n }).catch((err) => {\n structuredLog(log, {\n event: 'heartbeat_error',\n jobId: job.id,\n error: err instanceof Error ? err.message : String(err),\n });\n });\n }, heartbeatIntervalMs);\n\n try {\n const helpers = makeHelpers(job.attempts, log);\n const result = (await handler(job.payload, helpers)) ?? {};\n clearInterval(heartbeatTimer);\n structuredLog(log, {\n event: 'complete',\n jobId: job.id,\n kind: job.kind,\n durationMs: Date.now() - startedAt,\n });\n await apiPost(`/api/work/${job.id}/complete`, {\n leaseToken: job.leaseToken,\n result,\n });\n } catch (err) {\n clearInterval(heartbeatTimer);\n const terminal = isPermanentJobError(err);\n const message = err instanceof Error ? err.message : String(err);\n structuredLog(log, {\n event: 'fail',\n jobId: job.id,\n kind: job.kind,\n durationMs: Date.now() - startedAt,\n terminal,\n error: message,\n });\n await apiPost(`/api/work/${job.id}/fail`, {\n leaseToken: job.leaseToken,\n error: message,\n terminal,\n }).catch((postErr) => {\n structuredLog(log, {\n event: 'fail_post_error',\n jobId: job.id,\n error: postErr instanceof Error ? postErr.message : String(postErr),\n });\n });\n }\n}\n\nexport async function runAgentLoop(opts: RunAgentLoopOptions): Promise<void> {\n const log = opts.log ?? (() => undefined);\n const pollWaitMs = opts.pollWaitMs ?? DEFAULT_POLL_WAIT_MS;\n const heartbeatIntervalMs = opts.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_MS;\n const capabilities = opts.capabilities ?? CAPABILITIES;\n\n let backoffMs = BACKOFF_INITIAL_MS;\n structuredLog(log, {\n event: 'agent_start',\n capabilities,\n pollWaitMs,\n heartbeatIntervalMs,\n });\n\n // Background presence heartbeat \u2014 writes to worker_connections so the\n // dashboard's \"Worker online\" indicator turns on. Fire-and-forget; failures\n // are logged but don't block the work poll.\n startPresenceHeartbeat(capabilities, opts.signal, log);\n\n // Fire-and-forget the terminal-target liveness heartbeat. It runs on its\n // own interval (default 60s) and shuts down on the same abort signal.\n // Errors are caught internally \u2014 we don't await it so a slow tmux probe\n // can't delay the work poll.\n void startTerminalTargetHeartbeat({ signal: opts.signal, log }).catch((err) => {\n structuredLog(log, {\n event: 'tt_heartbeat_fatal',\n error: err instanceof Error ? err.message : String(err),\n });\n });\n\n while (!opts.signal.aborted) {\n let job: NextWorkResponse | null = null;\n try {\n job = await pollForWork(capabilities, pollWaitMs);\n } catch (err) {\n // Network/API errors during poll: log + backoff. The API may be\n // restarting, the laptop may have just woken, etc. Don't crash the\n // loop \u2014 that's the whole point of this migration.\n const status = err instanceof ApiError ? err.statusCode : undefined;\n structuredLog(log, {\n event: 'poll_error',\n status,\n error: err instanceof Error ? err.message : String(err),\n });\n const completed = await abortableSleep(backoffMs, opts.signal);\n if (!completed) break;\n backoffMs = Math.min(backoffMs * 2, BACKOFF_MAX_MS);\n continue;\n }\n\n if (!job) {\n structuredLog(log, { event: 'poll_204' });\n const completed = await abortableSleep(backoffMs, opts.signal);\n if (!completed) break;\n backoffMs = Math.min(backoffMs * 2, BACKOFF_MAX_MS);\n continue;\n }\n\n // Got work \u2014 reset the backoff and run it. An in-flight handler is\n // intentionally not abort-cancellable; we await its natural completion\n // even if the signal fires mid-run.\n backoffMs = BACKOFF_INITIAL_MS;\n await runOne(job, heartbeatIntervalMs, log);\n }\n\n structuredLog(log, { event: 'agent_stop' });\n}\n"],
5
- "mappings": "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",
6
- "names": []
7
- }
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../src/lib/version-check.ts"],
4
- "sourcesContent": ["// CLI version-check banner. Every command kicks off an async fetch to\n// /api/cli/latest-version (cached for an hour) and prints a one-line\n// notice on the next invocation if the cached version is newer than the\n// currently-running CLI.\n//\n// Design notes:\n// - Fire-and-forget: we never block command execution on the network.\n// - Cache: ~/.config/rulemetric/version-check.json, TTL 1h. Stored on disk\n// so the check survives between CLI invocations (each invocation is a\n// fresh process \u2014 in-memory cache wouldn't help).\n// - \"Newer\" uses semver-naive numeric comparison split on dots \u2014 fine for\n// our 0.x line; tightening this is a follow-up if/when we adopt semver\n// pre-releases.\n// - We DO NOT have an `upgrade` command yet; the banner just says \"version\n// X.Y.Z available\" and trusts the user to know how they installed.\nimport { mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\n\nconst CONFIG_DIR = join(homedir(), '.config', 'rulemetric');\nconst CACHE_FILE = join(CONFIG_DIR, 'version-check.json');\nconst TTL_MS = 60 * 60 * 1000; // 1h\n\ninterface CacheEntry {\n latest: string;\n fetchedAtMs: number;\n}\n\nfunction readCache(): CacheEntry | null {\n try {\n const data = JSON.parse(readFileSync(CACHE_FILE, 'utf-8')) as CacheEntry;\n if (typeof data.latest === 'string' && typeof data.fetchedAtMs === 'number') {\n return data;\n }\n return null;\n } catch {\n return null;\n }\n}\n\nfunction writeCache(entry: CacheEntry): void {\n try {\n mkdirSync(CONFIG_DIR, { recursive: true });\n writeFileSync(CACHE_FILE, JSON.stringify(entry));\n } catch {\n // Best-effort: a write failure just means we'll re-fetch next time.\n }\n}\n\nfunction compareVersions(a: string, b: string): number {\n const parse = (v: string) => v.split('.').map((n) => Number.parseInt(n, 10) || 0);\n const pa = parse(a);\n const pb = parse(b);\n const len = Math.max(pa.length, pb.length);\n for (let i = 0; i < len; i++) {\n const ai = pa[i] ?? 0;\n const bi = pb[i] ?? 0;\n if (ai < bi) return -1;\n if (ai > bi) return 1;\n }\n return 0;\n}\n\nfunction getApiUrl(): string {\n const env = process.env.RULEMETRIC_API_URL;\n if (env) return env.replace(/\\/$/, '');\n return 'https://rulemetric.com';\n}\n\n/**\n * Fetches latest version from the API in the background and refreshes the\n * cache if the entry is missing or older than TTL_MS. Never throws.\n */\nexport function refreshVersionCacheInBackground(): void {\n const cached = readCache();\n if (cached && Date.now() - cached.fetchedAtMs < TTL_MS) return;\n\n // Fire-and-forget \u2014 explicitly catch every failure mode so an unhandled\n // rejection can never reach the user. AbortController guards against\n // hanging connections (3s budget).\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), 3000);\n globalThis\n .fetch(`${getApiUrl()}/api/cli/latest-version`, { signal: controller.signal })\n .then(async (res) => {\n clearTimeout(timer);\n if (!res.ok) return;\n const data = (await res.json()) as { latest?: unknown };\n if (typeof data.latest !== 'string') return;\n writeCache({ latest: data.latest, fetchedAtMs: Date.now() });\n })\n .catch(() => {\n clearTimeout(timer);\n // Network error, timeout, malformed JSON \u2014 silently ignore. Banner\n // is purely a nicety; we never want it to surface failures.\n });\n}\n\n/**\n * Returns a one-line upgrade banner if a newer version is cached, else null.\n * Designed to be printed to stderr by the CLI's BaseCommand on launch.\n */\nexport function getUpgradeBanner(currentVersion: string): string | null {\n const cached = readCache();\n if (!cached) return null;\n if (compareVersions(cached.latest, currentVersion) <= 0) return null;\n return `RuleMetric ${cached.latest} available \u2014 you're on ${currentVersion}.`;\n}\n"],
5
- "mappings": ";AAeA,SAAS,WAAW,cAAc,qBAAqB;AACvD,SAAS,eAAe;AACxB,SAAS,YAAY;AAErB,IAAM,aAAa,KAAK,QAAQ,GAAG,WAAW,YAAY;AAC1D,IAAM,aAAa,KAAK,YAAY,oBAAoB;AACxD,IAAM,SAAS,KAAK,KAAK;AAOzB,SAAS,YAA+B;AACtC,MAAI;AACF,UAAM,OAAO,KAAK,MAAM,aAAa,YAAY,OAAO,CAAC;AACzD,QAAI,OAAO,KAAK,WAAW,YAAY,OAAO,KAAK,gBAAgB,UAAU;AAC3E,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,WAAW,OAAyB;AAC3C,MAAI;AACF,cAAU,YAAY,EAAE,WAAW,KAAK,CAAC;AACzC,kBAAc,YAAY,KAAK,UAAU,KAAK,CAAC;AAAA,EACjD,QAAQ;AAAA,EAER;AACF;AAEA,SAAS,gBAAgB,GAAW,GAAmB;AACrD,QAAM,QAAQ,CAAC,MAAc,EAAE,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,OAAO,SAAS,GAAG,EAAE,KAAK,CAAC;AAChF,QAAM,KAAK,MAAM,CAAC;AAClB,QAAM,KAAK,MAAM,CAAC;AAClB,QAAM,MAAM,KAAK,IAAI,GAAG,QAAQ,GAAG,MAAM;AACzC,WAAS,IAAI,GAAG,IAAI,KAAK,KAAK;AAC5B,UAAM,KAAK,GAAG,CAAC,KAAK;AACpB,UAAM,KAAK,GAAG,CAAC,KAAK;AACpB,QAAI,KAAK,GAAI,QAAO;AACpB,QAAI,KAAK,GAAI,QAAO;AAAA,EACtB;AACA,SAAO;AACT;AAEA,SAAS,YAAoB;AAC3B,QAAM,MAAM,QAAQ,IAAI;AACxB,MAAI,IAAK,QAAO,IAAI,QAAQ,OAAO,EAAE;AACrC,SAAO;AACT;AAMO,SAAS,kCAAwC;AACtD,QAAM,SAAS,UAAU;AACzB,MAAI,UAAU,KAAK,IAAI,IAAI,OAAO,cAAc,OAAQ;AAKxD,QAAM,aAAa,IAAI,gBAAgB;AACvC,QAAM,QAAQ,WAAW,MAAM,WAAW,MAAM,GAAG,GAAI;AACvD,aACG,MAAM,GAAG,UAAU,CAAC,2BAA2B,EAAE,QAAQ,WAAW,OAAO,CAAC,EAC5E,KAAK,OAAO,QAAQ;AACnB,iBAAa,KAAK;AAClB,QAAI,CAAC,IAAI,GAAI;AACb,UAAM,OAAQ,MAAM,IAAI,KAAK;AAC7B,QAAI,OAAO,KAAK,WAAW,SAAU;AACrC,eAAW,EAAE,QAAQ,KAAK,QAAQ,aAAa,KAAK,IAAI,EAAE,CAAC;AAAA,EAC7D,CAAC,EACA,MAAM,MAAM;AACX,iBAAa,KAAK;AAAA,EAGpB,CAAC;AACL;AAMO,SAAS,iBAAiB,gBAAuC;AACtE,QAAM,SAAS,UAAU;AACzB,MAAI,CAAC,OAAQ,QAAO;AACpB,MAAI,gBAAgB,OAAO,QAAQ,cAAc,KAAK,EAAG,QAAO;AAChE,SAAO,cAAc,OAAO,MAAM,+BAA0B,cAAc;AAC5E;",
6
- "names": []
7
- }