@rulemetric/cli 0.4.0 → 0.5.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.
Files changed (157) hide show
  1. package/dist/base-command.js +5 -5
  2. package/dist/chunk-346TGPVR.js +309 -0
  3. package/dist/chunk-346TGPVR.js.map +7 -0
  4. package/dist/{chunk-2AM54TUK.js → chunk-4UJATBTG.js} +2 -2
  5. package/dist/{chunk-2WWGG2A5.js → chunk-54IODRPZ.js} +16 -15
  6. package/dist/chunk-54IODRPZ.js.map +7 -0
  7. package/dist/{chunk-BD7CY42U.js → chunk-7DRLX3DN.js} +3 -3
  8. package/dist/{chunk-JX5UI6RW.js → chunk-AUUHXCJU.js} +2 -2
  9. package/dist/{chunk-S56LOTGQ.js → chunk-CB43W53J.js} +5 -5
  10. package/dist/{chunk-EHABPBGH.js → chunk-CCSR73HF.js} +2 -2
  11. package/dist/{chunk-EHZJ6OXD.js → chunk-CGUV6TUA.js} +2 -2
  12. package/dist/{chunk-EJOI2ZGU.js → chunk-CWFVBGX2.js} +8 -12
  13. package/dist/chunk-CWFVBGX2.js.map +7 -0
  14. package/dist/{chunk-42A3HEEH.js → chunk-F5DPTXRC.js} +30 -2
  15. package/dist/{chunk-42A3HEEH.js.map → chunk-F5DPTXRC.js.map} +3 -3
  16. package/dist/{chunk-YEBBX4HQ.js → chunk-I4GHR6CY.js} +2 -2
  17. package/dist/{chunk-XMWHTOCC.js → chunk-IKEI4KNV.js} +2 -2
  18. package/dist/{chunk-CROYS75W.js → chunk-LJ5OT5XN.js} +4 -4
  19. package/dist/{chunk-UF73WX2J.js → chunk-OAQG5WBV.js} +2 -2
  20. package/dist/{chunk-QJXAULRC.js → chunk-OKCFYPIQ.js} +2 -2
  21. package/dist/{chunk-MTLAIJED.js → chunk-OWUS3HMA.js} +2 -2
  22. package/dist/{chunk-YBNW7ARH.js → chunk-PHIAFGQ2.js} +2 -2
  23. package/dist/{chunk-OGVORIFM.js → chunk-R56PH7YE.js} +11 -3
  24. package/dist/chunk-R56PH7YE.js.map +7 -0
  25. package/dist/{chunk-CLC7ZFH2.js → chunk-RYMK7FQL.js} +2 -2
  26. package/dist/{chunk-IWYCRLEK.js → chunk-TDTHAOXO.js} +2 -2
  27. package/dist/{chunk-PQU7GZ7A.js → chunk-TJ2M5QVL.js} +2 -2
  28. package/dist/{chunk-C7SRRSPA.js → chunk-VBOTWXJW.js} +2 -2
  29. package/dist/{chunk-KZRFUXGF.js → chunk-W2YERO7E.js} +27 -2
  30. package/dist/chunk-W2YERO7E.js.map +7 -0
  31. package/dist/commands/antigravity/backfill.js +6 -6
  32. package/dist/commands/auth/create-key.js +5 -5
  33. package/dist/commands/auth/login.js +5 -5
  34. package/dist/commands/auth/logout.js +5 -5
  35. package/dist/commands/auth/revoke-key.js +5 -5
  36. package/dist/commands/auth/status.js +5 -5
  37. package/dist/commands/catalog/index.js +5 -5
  38. package/dist/commands/catalog/recommend.js +5 -5
  39. package/dist/commands/convert.js +5 -5
  40. package/dist/commands/converters.js +5 -5
  41. package/dist/commands/crons/list.js +5 -5
  42. package/dist/commands/crons/run.js +5 -5
  43. package/dist/commands/dashboard.js +5 -5
  44. package/dist/commands/eval-targets/create.js +5 -5
  45. package/dist/commands/eval-targets/list.js +5 -5
  46. package/dist/commands/evals/agent.js +20 -19
  47. package/dist/commands/evals/agent.js.map +2 -2
  48. package/dist/commands/evals/benchmark.js +6 -6
  49. package/dist/commands/evals/compare.js +5 -5
  50. package/dist/commands/evals/create.js +5 -5
  51. package/dist/commands/evals/optimize-description.js +5 -5
  52. package/dist/commands/evals/optimize.js +5 -5
  53. package/dist/commands/evals/run.js +5 -5
  54. package/dist/commands/gateway/ensure.js +5 -5
  55. package/dist/commands/hooks/install.js +5 -5
  56. package/dist/commands/hooks/run.js +5 -5
  57. package/dist/commands/hooks/uninstall.js +5 -5
  58. package/dist/commands/import.js +5 -5
  59. package/dist/commands/instructions/create.js +5 -5
  60. package/dist/commands/instructions/delete.js +5 -5
  61. package/dist/commands/instructions/fork.js +5 -5
  62. package/dist/commands/instructions/get.js +5 -5
  63. package/dist/commands/instructions/list.js +5 -5
  64. package/dist/commands/instructions/promote.js +5 -5
  65. package/dist/commands/instructions/pull.js +5 -5
  66. package/dist/commands/instructions/upstream.js +5 -5
  67. package/dist/commands/instructions/versions.js +5 -5
  68. package/dist/commands/launcher.js +5 -5
  69. package/dist/commands/org/current.js +5 -5
  70. package/dist/commands/org/list.js +5 -5
  71. package/dist/commands/org/switch.js +5 -5
  72. package/dist/commands/packs/add-instruction.js +5 -5
  73. package/dist/commands/packs/apply.js +5 -5
  74. package/dist/commands/packs/create.js +5 -5
  75. package/dist/commands/packs/fork.js +5 -5
  76. package/dist/commands/packs/get.js +5 -5
  77. package/dist/commands/packs/list.js +5 -5
  78. package/dist/commands/packs/remove-instruction.js +5 -5
  79. package/dist/commands/proxy/env.js +5 -5
  80. package/dist/commands/proxy/logs.js +5 -5
  81. package/dist/commands/proxy/restart.js +5 -5
  82. package/dist/commands/proxy/setup.js +5 -5
  83. package/dist/commands/proxy/start.js +5 -5
  84. package/dist/commands/proxy/status.js +5 -5
  85. package/dist/commands/proxy/stop.js +5 -5
  86. package/dist/commands/research/backfill.js +6 -6
  87. package/dist/commands/research/tail-transcript.js +6 -6
  88. package/dist/commands/service/install.js +5 -5
  89. package/dist/commands/service/status.js +5 -5
  90. package/dist/commands/service/uninstall.js +5 -5
  91. package/dist/commands/sessions/analyze.js +5 -5
  92. package/dist/commands/sessions/end.js +5 -5
  93. package/dist/commands/sessions/import.js +5 -5
  94. package/dist/commands/sessions/list.js +5 -5
  95. package/dist/commands/sessions/start.js +5 -5
  96. package/dist/commands/setup.js +146 -0
  97. package/dist/commands/setup.js.map +7 -0
  98. package/dist/commands/signals/report.js +5 -5
  99. package/dist/commands/skills/export-approved.js +5 -5
  100. package/dist/commands/skills/install.js +5 -5
  101. package/dist/commands/skills/list.js +5 -5
  102. package/dist/commands/skills/publish.js +5 -5
  103. package/dist/commands/skills/search.js +5 -5
  104. package/dist/commands/skills/update.js +5 -5
  105. package/dist/commands/suggestions/accept.js +5 -5
  106. package/dist/commands/suggestions/dismiss.js +5 -5
  107. package/dist/commands/suggestions/list.js +5 -5
  108. package/dist/commands/suggestions/refresh.js +5 -5
  109. package/dist/dashboard/Dashboard.js +11 -11
  110. package/dist/dashboard/launcher/LauncherOverlay.js +3 -3
  111. package/dist/lib/active-org-refresh.js +3 -3
  112. package/dist/lib/agent-loop.js +15 -15
  113. package/dist/lib/antigravity-transcript.js +3 -3
  114. package/dist/lib/api-client.js +2 -2
  115. package/dist/lib/auth.js +5 -1
  116. package/dist/lib/eval-benchmark.js +3 -3
  117. package/dist/lib/eval-conversation-handler.js +3 -3
  118. package/dist/lib/handlers/cron-close-stale-sessions.js +2 -2
  119. package/dist/lib/handlers/cron-data-integrity-audit.js +2 -2
  120. package/dist/lib/handlers/cron-suggest-instructions.js +3 -3
  121. package/dist/lib/handlers/process-announcement.js +2 -2
  122. package/dist/lib/handlers/process-changelog.js +2 -2
  123. package/dist/lib/handlers/process-conversation.js +4 -4
  124. package/dist/lib/handlers/process-eval.js +3 -3
  125. package/dist/lib/handlers/process-insights.js +3 -3
  126. package/dist/lib/handlers/process-launch.js +3 -3
  127. package/dist/lib/handlers/process-run-cleanup.js +2 -2
  128. package/dist/lib/handlers/process-session-goal.js +2 -2
  129. package/dist/lib/manual-tasks.js +5 -5
  130. package/dist/lib/research-client.js +3 -3
  131. package/dist/lib/setup-steps.js +26 -0
  132. package/dist/lib/setup-steps.js.map +7 -0
  133. package/dist/lib/terminal-target-heartbeat.js +3 -3
  134. package/dist/lib/version-check.js +3 -1
  135. package/oclif.manifest.json +1033 -972
  136. package/package.json +4 -4
  137. package/dist/chunk-2WWGG2A5.js.map +0 -7
  138. package/dist/chunk-EJOI2ZGU.js.map +0 -7
  139. package/dist/chunk-KZRFUXGF.js.map +0 -7
  140. package/dist/chunk-OGVORIFM.js.map +0 -7
  141. /package/dist/{chunk-2AM54TUK.js.map → chunk-4UJATBTG.js.map} +0 -0
  142. /package/dist/{chunk-BD7CY42U.js.map → chunk-7DRLX3DN.js.map} +0 -0
  143. /package/dist/{chunk-JX5UI6RW.js.map → chunk-AUUHXCJU.js.map} +0 -0
  144. /package/dist/{chunk-S56LOTGQ.js.map → chunk-CB43W53J.js.map} +0 -0
  145. /package/dist/{chunk-EHABPBGH.js.map → chunk-CCSR73HF.js.map} +0 -0
  146. /package/dist/{chunk-EHZJ6OXD.js.map → chunk-CGUV6TUA.js.map} +0 -0
  147. /package/dist/{chunk-YEBBX4HQ.js.map → chunk-I4GHR6CY.js.map} +0 -0
  148. /package/dist/{chunk-XMWHTOCC.js.map → chunk-IKEI4KNV.js.map} +0 -0
  149. /package/dist/{chunk-CROYS75W.js.map → chunk-LJ5OT5XN.js.map} +0 -0
  150. /package/dist/{chunk-UF73WX2J.js.map → chunk-OAQG5WBV.js.map} +0 -0
  151. /package/dist/{chunk-QJXAULRC.js.map → chunk-OKCFYPIQ.js.map} +0 -0
  152. /package/dist/{chunk-MTLAIJED.js.map → chunk-OWUS3HMA.js.map} +0 -0
  153. /package/dist/{chunk-YBNW7ARH.js.map → chunk-PHIAFGQ2.js.map} +0 -0
  154. /package/dist/{chunk-CLC7ZFH2.js.map → chunk-RYMK7FQL.js.map} +0 -0
  155. /package/dist/{chunk-IWYCRLEK.js.map → chunk-TDTHAOXO.js.map} +0 -0
  156. /package/dist/{chunk-PQU7GZ7A.js.map → chunk-TJ2M5QVL.js.map} +0 -0
  157. /package/dist/{chunk-C7SRRSPA.js.map → chunk-VBOTWXJW.js.map} +0 -0
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  BaseCommand
3
- } from "./chunk-S56LOTGQ.js";
4
- import "./chunk-OGVORIFM.js";
5
- import "./chunk-2AM54TUK.js";
6
- import "./chunk-EJOI2ZGU.js";
7
- import "./chunk-KZRFUXGF.js";
3
+ } from "./chunk-CB43W53J.js";
4
+ import "./chunk-R56PH7YE.js";
5
+ import "./chunk-4UJATBTG.js";
6
+ import "./chunk-CWFVBGX2.js";
7
+ import "./chunk-W2YERO7E.js";
8
8
  import "./chunk-EKJ2CABV.js";
9
9
  import "./chunk-NSBPE2FW.js";
10
10
  export {
@@ -0,0 +1,309 @@
1
+ import {
2
+ detectInstalledTools
3
+ } from "./chunk-ANOWI23I.js";
4
+ import {
5
+ GATEWAY_PORT
6
+ } from "./chunk-FTJRMUID.js";
7
+ import {
8
+ apiGet
9
+ } from "./chunk-CWFVBGX2.js";
10
+ import {
11
+ getDefaultConfigDir,
12
+ isAuthenticated
13
+ } from "./chunk-W2YERO7E.js";
14
+
15
+ // src/lib/setup-steps.ts
16
+ import { execSync } from "node:child_process";
17
+ import { existsSync, readFileSync } from "node:fs";
18
+ import { homedir } from "node:os";
19
+ import { join } from "node:path";
20
+ var CORE_STEP_IDS = ["install", "auth", "hooks", "worker"];
21
+ function defaultContext() {
22
+ return {
23
+ projectPath: process.cwd(),
24
+ configDir: getDefaultConfigDir()
25
+ };
26
+ }
27
+ var CERT_PATH = join(homedir(), ".mitmproxy", "mitmproxy-ca-cert.pem");
28
+ function maskToken(s) {
29
+ if (!s) return "";
30
+ if (s.length <= 8) return "*".repeat(s.length);
31
+ const head = s.slice(0, 4);
32
+ const tail = s.slice(-4);
33
+ return `${head}${"*".repeat(Math.max(4, s.length - 8))}${tail}`;
34
+ }
35
+ function renderDiff(before, after) {
36
+ const beforeLines = before === null ? [] : before.replace(/\n$/, "").split("\n");
37
+ const afterLines = after === null ? [] : after.replace(/\n$/, "").split("\n");
38
+ const out = [];
39
+ const max = Math.max(beforeLines.length, afterLines.length);
40
+ for (let i = 0; i < max; i++) {
41
+ const b = beforeLines[i];
42
+ const a = afterLines[i];
43
+ if (b === a) {
44
+ out.push(` ${b}`);
45
+ } else {
46
+ if (b !== void 0) out.push(`-${b}`);
47
+ if (a !== void 0) out.push(`+${a}`);
48
+ }
49
+ }
50
+ return out.join("\n");
51
+ }
52
+ function readEnvValue(configDir, key) {
53
+ const envPath = join(configDir, "env");
54
+ if (!existsSync(envPath)) return null;
55
+ try {
56
+ const content = readFileSync(envPath, "utf-8");
57
+ const match = content.match(new RegExp(`^${key}=(.+)$`, "m"));
58
+ return match ? match[1] : null;
59
+ } catch {
60
+ return null;
61
+ }
62
+ }
63
+ async function workerActive() {
64
+ if (!isAuthenticated()) return false;
65
+ try {
66
+ const status = await Promise.race([
67
+ apiGet("/api/setup/status"),
68
+ new Promise(
69
+ (_, reject) => setTimeout(() => reject(new Error("setup/status timeout")), 2500)
70
+ )
71
+ ]);
72
+ return status?.workerActive === true;
73
+ } catch {
74
+ return false;
75
+ }
76
+ }
77
+ function projectHooksSettings(projectPath) {
78
+ const settingsPath = join(projectPath, ".claude", "settings.json");
79
+ let settings = {};
80
+ let before = null;
81
+ if (existsSync(settingsPath)) {
82
+ try {
83
+ before = readFileSync(settingsPath, "utf-8").replace(/\n$/, "");
84
+ settings = JSON.parse(before);
85
+ } catch {
86
+ settings = {};
87
+ }
88
+ }
89
+ const env = { ...settings.env ?? {} };
90
+ env.HTTPS_PROXY = `http://localhost:${GATEWAY_PORT}`;
91
+ env.NO_PROXY = "localhost,127.0.0.1,*.supabase.co,*.supabase.in";
92
+ if (existsSync(CERT_PATH)) {
93
+ env.NODE_EXTRA_CA_CERTS = CERT_PATH;
94
+ }
95
+ const after = JSON.stringify({ ...settings, env }, null, 2);
96
+ return { before, after };
97
+ }
98
+ var WORKER_PLIST_PATH = join(homedir(), "Library", "LaunchAgents", "com.rulemetric.worker.plist");
99
+ var WORKER_SYSTEMD_PATH = join(homedir(), ".config", "systemd", "user", "rulemetric-worker.service");
100
+ function runCommand(cmd) {
101
+ execSync(cmd, { stdio: "inherit" });
102
+ }
103
+ var STEPS = [
104
+ {
105
+ id: "install",
106
+ title: "Install the CLI",
107
+ command: "npm install -g @rulemetric/cli",
108
+ // Monotonic: the CLI must exist to produce any downstream signal, so it
109
+ // greens off auth or worker presence (nothing local phones home before).
110
+ async detect() {
111
+ return isAuthenticated() || await workerActive();
112
+ },
113
+ async plan() {
114
+ return {
115
+ id: "install",
116
+ title: "Install the CLI",
117
+ command: "npm install -g @rulemetric/cli",
118
+ artifacts: [
119
+ {
120
+ path: "npm install -g @rulemetric/cli",
121
+ kind: "command",
122
+ before: null,
123
+ after: "rulemetric --version # verifies the binary is on PATH"
124
+ }
125
+ ]
126
+ };
127
+ },
128
+ async apply() {
129
+ runCommand("npm install -g @rulemetric/cli");
130
+ }
131
+ },
132
+ {
133
+ id: "auth",
134
+ title: "Authenticate",
135
+ command: "rulemetric auth login",
136
+ async detect() {
137
+ return isAuthenticated();
138
+ },
139
+ async plan(ctx) {
140
+ const envPath = join(ctx.configDir, "env");
141
+ let before = null;
142
+ if (existsSync(envPath)) {
143
+ try {
144
+ before = maskEnvFile(readFileSync(envPath, "utf-8"));
145
+ } catch {
146
+ before = "(unreadable)";
147
+ }
148
+ }
149
+ const rawToken = readEnvValue(ctx.configDir, "RULEMETRIC_ACCESS_TOKEN");
150
+ const tokenDisplay = rawToken ? maskToken(rawToken) : maskToken("<token-from-login>");
151
+ const afterLines = [`RULEMETRIC_ACCESS_TOKEN=${tokenDisplay}`];
152
+ const rawApiKey = readEnvValue(ctx.configDir, "RULEMETRIC_API_KEY");
153
+ if (rawApiKey) {
154
+ afterLines.push(`RULEMETRIC_API_KEY=${maskToken(rawApiKey)}`);
155
+ }
156
+ const loginUrl = process.env.RULEMETRIC_API_URL;
157
+ if (loginUrl) {
158
+ afterLines.push(`RULEMETRIC_API_URL=${loginUrl}`);
159
+ }
160
+ const after = afterLines.join("\n");
161
+ return {
162
+ id: "auth",
163
+ title: "Authenticate",
164
+ command: "rulemetric auth login",
165
+ artifacts: [
166
+ {
167
+ path: envPath,
168
+ kind: "file",
169
+ before,
170
+ after,
171
+ masked: true
172
+ }
173
+ ]
174
+ };
175
+ },
176
+ async apply() {
177
+ runCommand("rulemetric auth login");
178
+ }
179
+ },
180
+ {
181
+ id: "hooks",
182
+ title: "Install hooks",
183
+ command: "rulemetric hooks install",
184
+ async detect(ctx) {
185
+ const settingsPath = join(ctx.projectPath, ".claude", "settings.json");
186
+ if (!existsSync(settingsPath)) return false;
187
+ try {
188
+ const settings = JSON.parse(readFileSync(settingsPath, "utf-8"));
189
+ const env = settings.env ?? {};
190
+ return typeof env.HTTPS_PROXY === "string";
191
+ } catch {
192
+ return false;
193
+ }
194
+ },
195
+ async plan(ctx) {
196
+ const settingsPath = join(ctx.projectPath, ".claude", "settings.json");
197
+ const { before, after } = projectHooksSettings(ctx.projectPath);
198
+ const artifacts = [
199
+ { path: settingsPath, kind: "file", before, after }
200
+ ];
201
+ const tools = detectInstalledTools(ctx.projectPath).filter((t) => t !== "claude_code");
202
+ for (const tool of tools) {
203
+ artifacts.push({
204
+ path: `${ctx.projectPath} (${tool} proxy config)`,
205
+ kind: "file",
206
+ before: null,
207
+ after: `proxy \u2192 http://localhost:${GATEWAY_PORT}`
208
+ });
209
+ }
210
+ return {
211
+ id: "hooks",
212
+ title: "Install hooks",
213
+ command: "rulemetric hooks install",
214
+ artifacts
215
+ };
216
+ },
217
+ async apply() {
218
+ runCommand("rulemetric hooks install");
219
+ }
220
+ },
221
+ {
222
+ id: "worker",
223
+ title: "Run the worker",
224
+ command: "rulemetric service install --worker-only",
225
+ async detect() {
226
+ return workerActive();
227
+ },
228
+ async plan() {
229
+ const isMac = process.platform === "darwin";
230
+ const servicePath = isMac ? WORKER_PLIST_PATH : WORKER_SYSTEMD_PATH;
231
+ const before = existsSync(servicePath) ? `(service already installed at ${servicePath})` : null;
232
+ const after = isMac ? [
233
+ `Label: com.rulemetric.worker`,
234
+ `ProgramArguments: <node> <cli>/bin/run.js evals agent`,
235
+ `RunAtLoad: true, KeepAlive: true`
236
+ ].join("\n") : [
237
+ `[Service]`,
238
+ `ExecStart=<node> <cli>/bin/run.js evals agent`,
239
+ `Restart=on-failure`
240
+ ].join("\n");
241
+ return {
242
+ id: "worker",
243
+ title: "Run the worker",
244
+ command: "rulemetric service install --worker-only",
245
+ artifacts: [
246
+ { path: servicePath, kind: "service", before, after }
247
+ ]
248
+ };
249
+ },
250
+ async apply() {
251
+ runCommand("rulemetric service install --worker-only");
252
+ }
253
+ }
254
+ ];
255
+ var STEP_BY_ID = new Map(STEPS.map((s) => [s.id, s]));
256
+ function getStep(id) {
257
+ const step = STEP_BY_ID.get(id);
258
+ if (!step) throw new Error(`Unknown setup step: ${id}`);
259
+ return step;
260
+ }
261
+ function isStepId(value) {
262
+ return CORE_STEP_IDS.includes(value);
263
+ }
264
+ var SAFE_ENV_KEYS = /* @__PURE__ */ new Set([
265
+ "RULEMETRIC_API_URL",
266
+ "RULEMETRIC_USER_ID",
267
+ "RULEMETRIC_CLIENT_NAME",
268
+ "RESEND_FROM_EMAIL",
269
+ "SENTRY_DSN",
270
+ // a DSN is not a secret
271
+ "PG_POOL_MAX",
272
+ "PG_IDLE_TIMEOUT",
273
+ "PG_STATEMENT_TIMEOUT_MS",
274
+ "NODE_ENV"
275
+ ]);
276
+ function maskEnvFile(content) {
277
+ return content.replace(/\n$/, "").split("\n").map((line) => {
278
+ const match = line.match(/^([A-Z0-9_]+)=(.+)$/);
279
+ if (!match) return line;
280
+ const [, key, value] = match;
281
+ return SAFE_ENV_KEYS.has(key) ? line : `${key}=${maskToken(value)}`;
282
+ }).join("\n");
283
+ }
284
+ async function computeStatus(ctx = defaultContext()) {
285
+ const steps = [];
286
+ for (const step of STEPS) {
287
+ const done = await step.detect(ctx);
288
+ steps.push({ id: step.id, done });
289
+ }
290
+ const install = steps.find((s) => s.id === "install");
291
+ if (install && !install.done) {
292
+ install.done = steps.some((s) => s.id !== "install" && s.done);
293
+ }
294
+ const currentStepId = steps.find((s) => !s.done)?.id ?? null;
295
+ const coreComplete = steps.every((s) => s.done);
296
+ return { steps, coreComplete, currentStepId };
297
+ }
298
+
299
+ export {
300
+ CORE_STEP_IDS,
301
+ defaultContext,
302
+ maskToken,
303
+ renderDiff,
304
+ STEPS,
305
+ getStep,
306
+ isStepId,
307
+ computeStatus
308
+ };
309
+ //# sourceMappingURL=chunk-346TGPVR.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/lib/setup-steps.ts"],
4
+ "sourcesContent": ["import { execSync } from 'node:child_process';\nimport { existsSync, readFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { isAuthenticated, getDefaultConfigDir } from './auth.js';\nimport { apiGet } from './api-client.js';\nimport { GATEWAY_PORT } from './gateway-lifecycle.js';\nimport { detectInstalledTools } from './hooks-config.js';\n\n/**\n * Data model for the four core onboarding steps, shared (by step-ID list +\n * install-monotonicity) with the web reducer `deriveSetupProgress()`.\n *\n * The two surfaces are separate implementations by necessity, but they no\n * longer read *different sources* for the same fact:\n * - Server-observable signals (cli-worker heartbeat / captured session) are\n * unified through the shared `GET /api/setup/status` endpoint \u2014 the CLI's\n * `workerActive()` and the web's `use-setup-progress.ts` both bottom out on\n * that one server-side authority, so they cannot drift on it.\n * - Local signals (auth = `~/.config/rulemetric` creds via `isAuthenticated()`,\n * hooks = cwd `.claude/settings.json`) stay machine-local: the browser\n * cannot read the user's filesystem, so these legitimately differ from the\n * web's account-scoped proxies for the same step (see the design doc's\n * \"Shared step model, two implementations\").\n * Each step exposes:\n * - detect(): is it done? (reads a local/remote signal, never mutates)\n * - plan(): what would apply() write? (read-only, returns diffs/artifacts)\n * - apply(): perform the step (thin \u2014 shells out to existing commands)\n */\n\nexport const CORE_STEP_IDS = ['install', 'auth', 'hooks', 'worker'] as const;\nexport type StepId = (typeof CORE_STEP_IDS)[number];\n\nexport type ArtifactKind = 'file' | 'command' | 'service';\n\nexport interface Artifact {\n path: string;\n kind: ArtifactKind;\n before: string | null;\n after: string | null;\n /** True when `after` contains a masked secret (never the raw value). */\n masked?: boolean;\n}\n\nexport interface StepPlan {\n id: StepId;\n title: string;\n command?: string;\n artifacts: Artifact[];\n}\n\nexport interface SetupContext {\n projectPath: string;\n configDir: string;\n}\n\nexport interface SetupStep {\n id: StepId;\n title: string;\n command?: string;\n detect(ctx: SetupContext): Promise<boolean>;\n plan(ctx: SetupContext): Promise<StepPlan>;\n apply(ctx: SetupContext): Promise<void>;\n}\n\nexport interface StepStatus {\n id: StepId;\n done: boolean;\n}\n\nexport interface SetupStatus {\n steps: StepStatus[];\n coreComplete: boolean;\n currentStepId: StepId | null;\n}\n\n/** Build the default context from the current process (cwd + config dir). */\nexport function defaultContext(): SetupContext {\n return {\n projectPath: process.cwd(),\n configDir: getDefaultConfigDir(),\n };\n}\n\nconst CERT_PATH = join(homedir(), '.mitmproxy', 'mitmproxy-ca-cert.pem');\n\n/**\n * Mask a secret: keep the first 4 and last 4 characters, replace the middle\n * with asterisks. Strings too short to reveal safely are fully masked.\n */\nexport function maskToken(s: string): string {\n if (!s) return '';\n if (s.length <= 8) return '*'.repeat(s.length);\n const head = s.slice(0, 4);\n const tail = s.slice(-4);\n return `${head}${'*'.repeat(Math.max(4, s.length - 8))}${tail}`;\n}\n\n/**\n * Simple line-oriented +/- diff for terminal display. Not a real LCS \u2014 good\n * enough to show a human/agent exactly which lines an artifact would change.\n */\nexport function renderDiff(before: string | null, after: string | null): string {\n const beforeLines = before === null ? [] : before.replace(/\\n$/, '').split('\\n');\n const afterLines = after === null ? [] : after.replace(/\\n$/, '').split('\\n');\n const out: string[] = [];\n const max = Math.max(beforeLines.length, afterLines.length);\n for (let i = 0; i < max; i++) {\n const b = beforeLines[i];\n const a = afterLines[i];\n if (b === a) {\n out.push(` ${b}`);\n } else {\n if (b !== undefined) out.push(`-${b}`);\n if (a !== undefined) out.push(`+${a}`);\n }\n }\n return out.join('\\n');\n}\n\n/** Read a single KEY=value from the env file, or null if absent. */\nfunction readEnvValue(configDir: string, key: string): string | null {\n const envPath = join(configDir, 'env');\n if (!existsSync(envPath)) return null;\n try {\n const content = readFileSync(envPath, 'utf-8');\n const match = content.match(new RegExp(`^${key}=(.+)$`, 'm'));\n return match ? match[1] : null;\n } catch {\n return null;\n }\n}\n\n/**\n * Is a CLI worker heartbeat active for the caller? (best-effort, network)\n *\n * Reads the shared `GET /api/setup/status` endpoint's `workerActive` field\n * rather than re-deriving it from `/api/workers/status`. That endpoint computes\n * the cli-worker heartbeat signal server-side using the SAME window +\n * staleness thresholds + `workerType === 'cli-worker'` filter the web strip\n * uses, so the CLI and web can't drift on this fact by construction (they used\n * to each filter/threshold client-side, which is exactly how they could\n * disagree). Fast-fails after 2.5s so `setup --json` / `setup plan` degrade to\n * \"not running\" instead of blocking on a stalled connection \u2014 detection is a\n * UX signal, not a correctness gate; offline \u2192 false.\n */\nasync function workerActive(): Promise<boolean> {\n if (!isAuthenticated()) return false;\n try {\n const status = await Promise.race([\n apiGet<{\n cliEverConnected?: boolean;\n anyCapturedSession?: boolean;\n workerActive?: boolean;\n }>('/api/setup/status'),\n new Promise<never>((_, reject) =>\n setTimeout(() => reject(new Error('setup/status timeout')), 2500),\n ),\n ]);\n return status?.workerActive === true;\n } catch {\n return false;\n }\n}\n\n/**\n * Compute the in-memory `.claude/settings.json` after the hooks env block is\n * merged, WITHOUT touching disk. Replicates the read-only shape of\n * `hooks install` \u00A72 (env.HTTPS_PROXY / NO_PROXY / NODE_EXTRA_CA_CERTS).\n */\nfunction projectHooksSettings(projectPath: string): { before: string | null; after: string } {\n const settingsPath = join(projectPath, '.claude', 'settings.json');\n let settings: Record<string, unknown> = {};\n let before: string | null = null;\n if (existsSync(settingsPath)) {\n try {\n before = readFileSync(settingsPath, 'utf-8').replace(/\\n$/, '');\n settings = JSON.parse(before) as Record<string, unknown>;\n } catch {\n settings = {};\n }\n }\n const env = { ...((settings.env as Record<string, string>) ?? {}) };\n env.HTTPS_PROXY = `http://localhost:${GATEWAY_PORT}`;\n env.NO_PROXY = 'localhost,127.0.0.1,*.supabase.co,*.supabase.in';\n if (existsSync(CERT_PATH)) {\n env.NODE_EXTRA_CA_CERTS = CERT_PATH;\n }\n const after = JSON.stringify({ ...settings, env }, null, 2);\n return { before, after };\n}\n\nconst WORKER_PLIST_PATH = join(homedir(), 'Library', 'LaunchAgents', 'com.rulemetric.worker.plist');\nconst WORKER_SYSTEMD_PATH = join(homedir(), '.config', 'systemd', 'user', 'rulemetric-worker.service');\n\n/** Run a command with inherited stdio (interactive-friendly). Thin apply(). */\nfunction runCommand(cmd: string): void {\n execSync(cmd, { stdio: 'inherit' });\n}\n\nexport const STEPS: SetupStep[] = [\n {\n id: 'install',\n title: 'Install the CLI',\n command: 'npm install -g @rulemetric/cli',\n // Monotonic: the CLI must exist to produce any downstream signal, so it\n // greens off auth or worker presence (nothing local phones home before).\n async detect(): Promise<boolean> {\n return isAuthenticated() || (await workerActive());\n },\n async plan(): Promise<StepPlan> {\n return {\n id: 'install',\n title: 'Install the CLI',\n command: 'npm install -g @rulemetric/cli',\n artifacts: [\n {\n path: 'npm install -g @rulemetric/cli',\n kind: 'command',\n before: null,\n after: 'rulemetric --version # verifies the binary is on PATH',\n },\n ],\n };\n },\n async apply(): Promise<void> {\n runCommand('npm install -g @rulemetric/cli');\n },\n },\n {\n id: 'auth',\n title: 'Authenticate',\n command: 'rulemetric auth login',\n async detect(): Promise<boolean> {\n return isAuthenticated();\n },\n async plan(ctx): Promise<StepPlan> {\n const envPath = join(ctx.configDir, 'env');\n // Guard the read like the sibling helpers (readEnvValue /\n // projectHooksSettings) do: a TOCTOU race or permission error between\n // existsSync and readFileSync must NOT throw out of plan(). Fall back to\n // a secret-free marker rather than crashing `setup plan`/`--show`.\n let before: string | null = null;\n if (existsSync(envPath)) {\n try {\n before = maskEnvFile(readFileSync(envPath, 'utf-8'));\n } catch {\n before = '(unreadable)';\n }\n }\n const rawToken = readEnvValue(ctx.configDir, 'RULEMETRIC_ACCESS_TOKEN');\n const tokenDisplay = rawToken ? maskToken(rawToken) : maskToken('<token-from-login>');\n // Reconstruct EXACTLY what `auth login`'s saveEnvFile writes so the diff\n // doesn't show phantom changes: line order [TOKEN, KEY?, URL?], the key\n // preserved (masked) when present, and the URL sourced from\n // process.env.RULEMETRIC_API_URL \u2014 the same value login passes \u2014 and OMITTED\n // when unset (login writes no URL line then). All masking preserved; the raw\n // token/key are never emitted.\n const afterLines = [`RULEMETRIC_ACCESS_TOKEN=${tokenDisplay}`];\n const rawApiKey = readEnvValue(ctx.configDir, 'RULEMETRIC_API_KEY');\n if (rawApiKey) {\n afterLines.push(`RULEMETRIC_API_KEY=${maskToken(rawApiKey)}`);\n }\n const loginUrl = process.env.RULEMETRIC_API_URL;\n if (loginUrl) {\n afterLines.push(`RULEMETRIC_API_URL=${loginUrl}`);\n }\n const after = afterLines.join('\\n');\n return {\n id: 'auth',\n title: 'Authenticate',\n command: 'rulemetric auth login',\n artifacts: [\n {\n path: envPath,\n kind: 'file',\n before,\n after,\n masked: true,\n },\n ],\n };\n },\n async apply(): Promise<void> {\n runCommand('rulemetric auth login');\n },\n },\n {\n id: 'hooks',\n title: 'Install hooks',\n command: 'rulemetric hooks install',\n async detect(ctx): Promise<boolean> {\n const settingsPath = join(ctx.projectPath, '.claude', 'settings.json');\n if (!existsSync(settingsPath)) return false;\n try {\n const settings = JSON.parse(readFileSync(settingsPath, 'utf-8')) as Record<string, unknown>;\n const env = (settings.env ?? {}) as Record<string, unknown>;\n return typeof env.HTTPS_PROXY === 'string';\n } catch {\n return false;\n }\n },\n async plan(ctx): Promise<StepPlan> {\n const settingsPath = join(ctx.projectPath, '.claude', 'settings.json');\n const { before, after } = projectHooksSettings(ctx.projectPath);\n const artifacts: Artifact[] = [\n { path: settingsPath, kind: 'file', before, after },\n ];\n // Note additional tool surfaces the real install would also touch.\n const tools = detectInstalledTools(ctx.projectPath).filter((t) => t !== 'claude_code');\n for (const tool of tools) {\n artifacts.push({\n path: `${ctx.projectPath} (${tool} proxy config)`,\n kind: 'file',\n before: null,\n after: `proxy \u2192 http://localhost:${GATEWAY_PORT}`,\n });\n }\n return {\n id: 'hooks',\n title: 'Install hooks',\n command: 'rulemetric hooks install',\n artifacts,\n };\n },\n async apply(): Promise<void> {\n runCommand('rulemetric hooks install');\n },\n },\n {\n id: 'worker',\n title: 'Run the worker',\n command: 'rulemetric service install --worker-only',\n async detect(): Promise<boolean> {\n return workerActive();\n },\n async plan(): Promise<StepPlan> {\n const isMac = process.platform === 'darwin';\n const servicePath = isMac ? WORKER_PLIST_PATH : WORKER_SYSTEMD_PATH;\n // SECURITY: never read the installed plist/systemd unit into `before` \u2014\n // those files embed RULEMETRIC_ACCESS_TOKEN / RULEMETRIC_API_KEY verbatim\n // (service/install.ts writes them from ALLOWED_ENV_VARS), so echoing the\n // file contents would leak the raw secret through `plan`, `--show`, and\n // `--json`. Use a synthetic, secret-free marker for the current state.\n const before = existsSync(servicePath)\n ? `(service already installed at ${servicePath})`\n : null;\n // Concise representation (path + ExecStart node/cli invocation) rather\n // than reconstructing the full plist \u2014 keep read-only + legible.\n const after = isMac\n ? [\n `Label: com.rulemetric.worker`,\n `ProgramArguments: <node> <cli>/bin/run.js evals agent`,\n `RunAtLoad: true, KeepAlive: true`,\n ].join('\\n')\n : [\n `[Service]`,\n `ExecStart=<node> <cli>/bin/run.js evals agent`,\n `Restart=on-failure`,\n ].join('\\n');\n return {\n id: 'worker',\n title: 'Run the worker',\n command: 'rulemetric service install --worker-only',\n artifacts: [\n { path: servicePath, kind: 'service', before, after },\n ],\n };\n },\n async apply(): Promise<void> {\n runCommand('rulemetric service install --worker-only');\n },\n },\n];\n\nconst STEP_BY_ID = new Map<StepId, SetupStep>(STEPS.map((s) => [s.id, s]));\n\nexport function getStep(id: StepId): SetupStep {\n const step = STEP_BY_ID.get(id);\n if (!step) throw new Error(`Unknown setup step: ${id}`);\n return step;\n}\n\nexport function isStepId(value: string): value is StepId {\n return (CORE_STEP_IDS as readonly string[]).includes(value);\n}\n\n/**\n * Keys whose values are safe to show verbatim in the env-file diff. Everything\n * NOT in this set is masked. Deny-by-default is deliberate: `~/.config/rulemetric/env`\n * can legitimately hold DATABASE_URL, SUPABASE_SERVICE_ROLE_KEY, SUPABASE_JWT_SECRET,\n * GITHUB_TOKEN, RESEND_API_KEY, etc. (see ALLOWED_ENV_VARS in service/install.ts),\n * so an allowlist-to-hide would leak any secret we forgot to enumerate. Revealing\n * only known-safe keys means a future secret added to the file is masked automatically.\n */\nconst SAFE_ENV_KEYS = new Set([\n 'RULEMETRIC_API_URL',\n 'RULEMETRIC_USER_ID',\n 'RULEMETRIC_CLIENT_NAME',\n 'RESEND_FROM_EMAIL',\n 'SENTRY_DSN', // a DSN is not a secret\n 'PG_POOL_MAX',\n 'PG_IDLE_TIMEOUT',\n 'PG_STATEMENT_TIMEOUT_MS',\n 'NODE_ENV',\n]);\n\n/**\n * Mask secret-bearing lines in a raw env file for display. Deny-by-default:\n * every `KEY=value` line has its value masked unless KEY is in SAFE_ENV_KEYS.\n * Non-assignment lines (comments, blanks) pass through untouched.\n */\nfunction maskEnvFile(content: string): string {\n return content\n .replace(/\\n$/, '')\n .split('\\n')\n .map((line) => {\n const match = line.match(/^([A-Z0-9_]+)=(.+)$/);\n if (!match) return line; // comment / blank / non-assignment\n const [, key, value] = match;\n return SAFE_ENV_KEYS.has(key) ? line : `${key}=${maskToken(value)}`;\n })\n .join('\\n');\n}\n\n/**\n * Run every step's detect() and derive the overall status. `currentStepId` is\n * the first incomplete step in core order (mirrors the web reducer).\n */\nexport async function computeStatus(ctx: SetupContext = defaultContext()): Promise<SetupStatus> {\n const steps: StepStatus[] = [];\n for (const step of STEPS) {\n // Sequential: detect() may hit the network; order is stable + cheap.\n // eslint-disable-next-line no-await-in-loop\n const done = await step.detect(ctx);\n steps.push({ id: step.id, done });\n }\n // Monotonicity (mirrors the web reducer's `cliDetected`): the CLI binary must\n // exist to produce ANY downstream signal, so `install` greens whenever auth,\n // hooks, or worker is detected \u2014 even if `install.detect()` alone (which only\n // sees auth/worker, not hooks) came back false. Without this, a user with\n // hooks installed but no live worker sees \"Install the CLI: waiting\" while\n // \"Install hooks: done\" \u2014 a contradiction.\n const install = steps.find((s) => s.id === 'install');\n if (install && !install.done) {\n install.done = steps.some((s) => s.id !== 'install' && s.done);\n }\n\n const currentStepId = steps.find((s) => !s.done)?.id ?? null;\n const coreComplete = steps.every((s) => s.done);\n return { steps, coreComplete, currentStepId };\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  apiGet
3
- } from "./chunk-EJOI2ZGU.js";
3
+ } from "./chunk-CWFVBGX2.js";
4
4
  import {
5
5
  clearActiveOrg,
6
6
  isStale,
@@ -56,4 +56,4 @@ export {
56
56
  bootstrapActiveOrg,
57
57
  _resetRefreshAttempt
58
58
  };
59
- //# sourceMappingURL=chunk-2AM54TUK.js.map
59
+ //# sourceMappingURL=chunk-4UJATBTG.js.map
@@ -1,44 +1,44 @@
1
1
  import {
2
2
  startTerminalTargetHeartbeat
3
- } from "./chunk-JX5UI6RW.js";
3
+ } from "./chunk-AUUHXCJU.js";
4
+ import {
5
+ processConversation
6
+ } from "./chunk-7DRLX3DN.js";
4
7
  import {
5
8
  processEval
6
- } from "./chunk-QJXAULRC.js";
9
+ } from "./chunk-OKCFYPIQ.js";
7
10
  import {
8
11
  processInsights
9
- } from "./chunk-EHABPBGH.js";
12
+ } from "./chunk-CCSR73HF.js";
10
13
  import {
11
14
  processLaunch
12
- } from "./chunk-XMWHTOCC.js";
15
+ } from "./chunk-IKEI4KNV.js";
13
16
  import {
14
17
  processRunCleanup
15
- } from "./chunk-C7SRRSPA.js";
18
+ } from "./chunk-VBOTWXJW.js";
16
19
  import {
17
20
  processSendMessage
18
21
  } from "./chunk-W53GKIZQ.js";
19
22
  import {
20
23
  processSessionGoal
21
- } from "./chunk-YEBBX4HQ.js";
24
+ } from "./chunk-I4GHR6CY.js";
22
25
  import {
23
26
  cronRefreshSkills
24
27
  } from "./chunk-RQ2TMLKG.js";
25
28
  import {
26
29
  cronSuggestInstructions
27
- } from "./chunk-YBNW7ARH.js";
30
+ } from "./chunk-PHIAFGQ2.js";
28
31
  import {
29
32
  processAnnouncement
30
- } from "./chunk-CLC7ZFH2.js";
33
+ } from "./chunk-RYMK7FQL.js";
31
34
  import {
32
35
  isPermanentJobError
33
36
  } from "./chunk-DGHWRQXL.js";
34
- import {
35
- processConversation
36
- } from "./chunk-BD7CY42U.js";
37
37
  import {
38
38
  ApiError,
39
39
  apiGet,
40
40
  apiPost
41
- } from "./chunk-EJOI2ZGU.js";
41
+ } from "./chunk-CWFVBGX2.js";
42
42
 
43
43
  // src/lib/agent-loop.ts
44
44
  var HANDLERS = {
@@ -71,11 +71,12 @@ var PRESENCE_HEARTBEAT_MS = 3e4;
71
71
  var BACKOFF_INITIAL_MS = 250;
72
72
  var BACKOFF_MAX_MS = 5e3;
73
73
  var PRESENCE_CLIENT_NAME = `thin-agent-${process.pid}`;
74
- function startPresenceHeartbeat(capabilities, signal, log) {
74
+ function startPresenceHeartbeat(capabilities, signal, log, version) {
75
75
  const send = (status) => {
76
76
  void apiPost("/api/workers/heartbeat", {
77
77
  workerType: "cli-worker",
78
78
  clientName: PRESENCE_CLIENT_NAME,
79
+ clientVersion: version,
79
80
  workerVersion: "0.1.0",
80
81
  currentJobId: null,
81
82
  inFlightCount: 0,
@@ -224,7 +225,7 @@ async function runAgentLoop(opts) {
224
225
  pollWaitMs,
225
226
  heartbeatIntervalMs
226
227
  });
227
- startPresenceHeartbeat(capabilities, opts.signal, log);
228
+ startPresenceHeartbeat(capabilities, opts.signal, log, opts.version);
228
229
  void startTerminalTargetHeartbeat({ signal: opts.signal, log }).catch((err) => {
229
230
  structuredLog(log, {
230
231
  event: "tt_heartbeat_fatal",
@@ -266,4 +267,4 @@ export {
266
267
  pollForWork,
267
268
  runAgentLoop
268
269
  };
269
- //# sourceMappingURL=chunk-2WWGG2A5.js.map
270
+ //# sourceMappingURL=chunk-54IODRPZ.js.map
@@ -0,0 +1,7 @@
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 /** Running CLI version, reported as `clientVersion` in the presence heartbeat\n * so the dashboard can detect an outdated CLI and nudge an upgrade. */\n version?: 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 version: string | undefined,\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 clientVersion: version,\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, opts.version);\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,9 +1,9 @@
1
1
  import {
2
2
  processConversationJob
3
- } from "./chunk-IWYCRLEK.js";
3
+ } from "./chunk-TDTHAOXO.js";
4
4
  import {
5
5
  apiPatch
6
- } from "./chunk-EJOI2ZGU.js";
6
+ } from "./chunk-CWFVBGX2.js";
7
7
 
8
8
  // src/lib/handlers/process-conversation.ts
9
9
  async function processConversation(payload, helpers) {
@@ -20,4 +20,4 @@ async function processConversation(payload, helpers) {
20
20
  export {
21
21
  processConversation
22
22
  };
23
- //# sourceMappingURL=chunk-BD7CY42U.js.map
23
+ //# sourceMappingURL=chunk-7DRLX3DN.js.map
@@ -5,7 +5,7 @@ import {
5
5
  import {
6
6
  apiGet,
7
7
  apiPatch
8
- } from "./chunk-EJOI2ZGU.js";
8
+ } from "./chunk-CWFVBGX2.js";
9
9
 
10
10
  // src/lib/terminal-target-heartbeat.ts
11
11
  var DEFAULT_INTERVAL_MS = 6e4;
@@ -96,4 +96,4 @@ export {
96
96
  runOneHeartbeatCycle,
97
97
  startTerminalTargetHeartbeat
98
98
  };
99
- //# sourceMappingURL=chunk-JX5UI6RW.js.map
99
+ //# sourceMappingURL=chunk-AUUHXCJU.js.map
@@ -1,16 +1,16 @@
1
1
  import {
2
2
  getUpgradeBanner,
3
3
  refreshVersionCacheInBackground
4
- } from "./chunk-OGVORIFM.js";
4
+ } from "./chunk-R56PH7YE.js";
5
5
  import {
6
6
  maybeRefreshActiveOrg
7
- } from "./chunk-2AM54TUK.js";
7
+ } from "./chunk-4UJATBTG.js";
8
8
  import {
9
9
  ApiError
10
- } from "./chunk-EJOI2ZGU.js";
10
+ } from "./chunk-CWFVBGX2.js";
11
11
  import {
12
12
  isAuthenticated
13
- } from "./chunk-KZRFUXGF.js";
13
+ } from "./chunk-W2YERO7E.js";
14
14
 
15
15
  // src/base-command.ts
16
16
  import { Command, Flags } from "@oclif/core";
@@ -60,4 +60,4 @@ ${banner}
60
60
  export {
61
61
  BaseCommand
62
62
  };
63
- //# sourceMappingURL=chunk-S56LOTGQ.js.map
63
+ //# sourceMappingURL=chunk-CB43W53J.js.map
@@ -15,7 +15,7 @@ import {
15
15
  apiGet,
16
16
  apiPatch,
17
17
  apiPost
18
- } from "./chunk-EJOI2ZGU.js";
18
+ } from "./chunk-CWFVBGX2.js";
19
19
 
20
20
  // src/lib/handlers/process-insights.ts
21
21
  async function processInsights(payload, helpers) {
@@ -240,4 +240,4 @@ async function buildDataSummaryFromApi(projectPath, projectId) {
240
240
  export {
241
241
  processInsights
242
242
  };
243
- //# sourceMappingURL=chunk-EHABPBGH.js.map
243
+ //# sourceMappingURL=chunk-CCSR73HF.js.map
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  ApiError,
3
3
  api
4
- } from "./chunk-EJOI2ZGU.js";
4
+ } from "./chunk-CWFVBGX2.js";
5
5
 
6
6
  // src/lib/antigravity-transcript.ts
7
7
  import { existsSync, mkdirSync, readFileSync, renameSync, rmdirSync, writeFileSync } from "node:fs";
@@ -304,4 +304,4 @@ export {
304
304
  tailAntigravityTranscript,
305
305
  stateApi
306
306
  };
307
- //# sourceMappingURL=chunk-EHZJ6OXD.js.map
307
+ //# sourceMappingURL=chunk-CGUV6TUA.js.map