@os-eco/overstory-cli 0.6.8 → 0.6.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -2
- package/package.json +1 -1
- package/src/agents/hooks-deployer.test.ts +180 -0
- package/src/agents/hooks-deployer.ts +32 -1
- package/src/commands/agents.ts +9 -6
- package/src/commands/clean.ts +2 -1
- package/src/commands/completions.ts +3 -4
- package/src/commands/coordinator.test.ts +8 -0
- package/src/commands/coordinator.ts +11 -8
- package/src/commands/costs.test.ts +48 -38
- package/src/commands/costs.ts +48 -38
- package/src/commands/dashboard.ts +7 -7
- package/src/commands/doctor.test.ts +8 -0
- package/src/commands/doctor.ts +2 -6
- package/src/commands/errors.test.ts +47 -40
- package/src/commands/errors.ts +5 -4
- package/src/commands/feed.test.ts +40 -33
- package/src/commands/feed.ts +3 -2
- package/src/commands/group.ts +23 -14
- package/src/commands/hooks.ts +2 -1
- package/src/commands/init.test.ts +104 -0
- package/src/commands/init.ts +11 -7
- package/src/commands/inspect.test.ts +2 -0
- package/src/commands/inspect.ts +9 -8
- package/src/commands/logs.test.ts +5 -6
- package/src/commands/logs.ts +2 -1
- package/src/commands/mail.test.ts +11 -10
- package/src/commands/mail.ts +11 -12
- package/src/commands/merge.ts +11 -12
- package/src/commands/metrics.test.ts +15 -2
- package/src/commands/metrics.ts +3 -2
- package/src/commands/monitor.ts +5 -4
- package/src/commands/nudge.ts +2 -3
- package/src/commands/prime.test.ts +1 -6
- package/src/commands/prime.ts +2 -3
- package/src/commands/replay.test.ts +62 -55
- package/src/commands/replay.ts +3 -2
- package/src/commands/run.ts +17 -20
- package/src/commands/sling.ts +2 -1
- package/src/commands/status.test.ts +2 -1
- package/src/commands/status.ts +7 -6
- package/src/commands/stop.test.ts +2 -0
- package/src/commands/stop.ts +10 -11
- package/src/commands/supervisor.ts +7 -6
- package/src/commands/trace.test.ts +52 -44
- package/src/commands/trace.ts +5 -4
- package/src/commands/watch.ts +8 -10
- package/src/commands/worktree.test.ts +21 -15
- package/src/commands/worktree.ts +10 -4
- package/src/index.ts +3 -1
package/src/commands/costs.ts
CHANGED
|
@@ -11,6 +11,7 @@ import { join } from "node:path";
|
|
|
11
11
|
import { Command } from "commander";
|
|
12
12
|
import { loadConfig } from "../config.ts";
|
|
13
13
|
import { ValidationError } from "../errors.ts";
|
|
14
|
+
import { jsonError, jsonOutput } from "../json.ts";
|
|
14
15
|
import { color } from "../logging/color.ts";
|
|
15
16
|
import { createMetricsStore } from "../metrics/store.ts";
|
|
16
17
|
import { estimateCost, parseTranscriptUsage } from "../metrics/transcript.ts";
|
|
@@ -272,10 +273,7 @@ async function executeCosts(opts: CostsOpts): Promise<void> {
|
|
|
272
273
|
const transcriptPath = await discoverOrchestratorTranscript(config.project.root);
|
|
273
274
|
if (!transcriptPath) {
|
|
274
275
|
if (json) {
|
|
275
|
-
|
|
276
|
-
JSON.stringify({ error: "no_transcript", message: "No orchestrator transcript found" }) +
|
|
277
|
-
"\n",
|
|
278
|
-
);
|
|
276
|
+
jsonError("costs", "No orchestrator transcript found");
|
|
279
277
|
} else {
|
|
280
278
|
process.stdout.write(
|
|
281
279
|
"No orchestrator transcript found.\nExpected at: ~/.claude/projects/{project-key}/*.jsonl\n",
|
|
@@ -289,18 +287,16 @@ async function executeCosts(opts: CostsOpts): Promise<void> {
|
|
|
289
287
|
const cacheTotal = usage.cacheReadTokens + usage.cacheCreationTokens;
|
|
290
288
|
|
|
291
289
|
if (json) {
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
})}\n`,
|
|
303
|
-
);
|
|
290
|
+
jsonOutput("costs", {
|
|
291
|
+
source: "self",
|
|
292
|
+
transcriptPath,
|
|
293
|
+
model: usage.modelUsed,
|
|
294
|
+
inputTokens: usage.inputTokens,
|
|
295
|
+
outputTokens: usage.outputTokens,
|
|
296
|
+
cacheReadTokens: usage.cacheReadTokens,
|
|
297
|
+
cacheCreationTokens: usage.cacheCreationTokens,
|
|
298
|
+
estimatedCostUsd: cost,
|
|
299
|
+
});
|
|
304
300
|
} else {
|
|
305
301
|
const w = process.stdout.write.bind(process.stdout);
|
|
306
302
|
const separator = "\u2500".repeat(70);
|
|
@@ -327,9 +323,17 @@ async function executeCosts(opts: CostsOpts): Promise<void> {
|
|
|
327
323
|
const metricsFile = Bun.file(metricsDbPath);
|
|
328
324
|
if (!(await metricsFile.exists())) {
|
|
329
325
|
if (json) {
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
326
|
+
jsonOutput("costs", {
|
|
327
|
+
agents: [],
|
|
328
|
+
totals: {
|
|
329
|
+
inputTokens: 0,
|
|
330
|
+
outputTokens: 0,
|
|
331
|
+
cacheTokens: 0,
|
|
332
|
+
costUsd: 0,
|
|
333
|
+
burnRatePerMin: 0,
|
|
334
|
+
tokensPerMin: 0,
|
|
335
|
+
},
|
|
336
|
+
});
|
|
333
337
|
} else {
|
|
334
338
|
process.stdout.write(
|
|
335
339
|
"No live data available. Token snapshots begin after first tool call.\n",
|
|
@@ -345,9 +349,17 @@ async function executeCosts(opts: CostsOpts): Promise<void> {
|
|
|
345
349
|
const snapshots = metricsStore.getLatestSnapshots();
|
|
346
350
|
if (snapshots.length === 0) {
|
|
347
351
|
if (json) {
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
352
|
+
jsonOutput("costs", {
|
|
353
|
+
agents: [],
|
|
354
|
+
totals: {
|
|
355
|
+
inputTokens: 0,
|
|
356
|
+
outputTokens: 0,
|
|
357
|
+
cacheTokens: 0,
|
|
358
|
+
costUsd: 0,
|
|
359
|
+
burnRatePerMin: 0,
|
|
360
|
+
tokensPerMin: 0,
|
|
361
|
+
},
|
|
362
|
+
});
|
|
351
363
|
} else {
|
|
352
364
|
process.stdout.write(
|
|
353
365
|
"No live data available. Token snapshots begin after first tool call.\n",
|
|
@@ -428,19 +440,17 @@ async function executeCosts(opts: CostsOpts): Promise<void> {
|
|
|
428
440
|
const tokensPerMin = avgElapsedMs > 0 ? totalTokens / (avgElapsedMs / 60_000) : 0;
|
|
429
441
|
|
|
430
442
|
if (json) {
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
})}\n`,
|
|
443
|
-
);
|
|
443
|
+
jsonOutput("costs", {
|
|
444
|
+
agents: agentData,
|
|
445
|
+
totals: {
|
|
446
|
+
inputTokens: totalInput,
|
|
447
|
+
outputTokens: totalOutput,
|
|
448
|
+
cacheTokens: totalCacheTokens,
|
|
449
|
+
costUsd: totalCost,
|
|
450
|
+
burnRatePerMin,
|
|
451
|
+
tokensPerMin,
|
|
452
|
+
},
|
|
453
|
+
});
|
|
444
454
|
} else {
|
|
445
455
|
const w = process.stdout.write.bind(process.stdout);
|
|
446
456
|
const separator = "\u2500".repeat(70);
|
|
@@ -502,7 +512,7 @@ async function executeCosts(opts: CostsOpts): Promise<void> {
|
|
|
502
512
|
const metricsFile = Bun.file(metricsDbPath);
|
|
503
513
|
if (!(await metricsFile.exists())) {
|
|
504
514
|
if (json) {
|
|
505
|
-
|
|
515
|
+
jsonOutput("costs", { sessions: [] });
|
|
506
516
|
} else {
|
|
507
517
|
process.stdout.write("No metrics data yet.\n");
|
|
508
518
|
}
|
|
@@ -532,9 +542,9 @@ async function executeCosts(opts: CostsOpts): Promise<void> {
|
|
|
532
542
|
totals: group.totals,
|
|
533
543
|
};
|
|
534
544
|
}
|
|
535
|
-
|
|
545
|
+
jsonOutput("costs", { grouped });
|
|
536
546
|
} else {
|
|
537
|
-
|
|
547
|
+
jsonOutput("costs", { sessions });
|
|
538
548
|
}
|
|
539
549
|
return;
|
|
540
550
|
}
|
|
@@ -15,7 +15,7 @@ import { Command } from "commander";
|
|
|
15
15
|
import { loadConfig } from "../config.ts";
|
|
16
16
|
import { ValidationError } from "../errors.ts";
|
|
17
17
|
import type { ColorFn } from "../logging/color.ts";
|
|
18
|
-
import { color, noColor, visibleLength } from "../logging/color.ts";
|
|
18
|
+
import { accent, color, noColor, visibleLength } from "../logging/color.ts";
|
|
19
19
|
import { createMailStore, type MailStore } from "../mail/store.ts";
|
|
20
20
|
import { createMergeQueue, type MergeQueue } from "../merge/queue.ts";
|
|
21
21
|
import { createMetricsStore, type MetricsStore } from "../metrics/store.ts";
|
|
@@ -404,7 +404,7 @@ async function loadDashboardData(
|
|
|
404
404
|
function renderHeader(width: number, interval: number, currentRunId?: string | null): string {
|
|
405
405
|
const left = color.bold(`ov dashboard v${PKG_VERSION}`);
|
|
406
406
|
const now = new Date().toLocaleTimeString();
|
|
407
|
-
const scope = currentRunId ? ` [run: ${currentRunId.slice(0, 8)}]` : " [all runs]";
|
|
407
|
+
const scope = currentRunId ? ` [run: ${accent(currentRunId.slice(0, 8))}]` : " [all runs]";
|
|
408
408
|
const right = `${now}${scope} | refresh: ${interval}ms`;
|
|
409
409
|
const padding = width - visibleLength(left) - right.length;
|
|
410
410
|
const line = left + " ".repeat(Math.max(0, padding)) + right;
|
|
@@ -497,10 +497,10 @@ function renderAgentPanel(
|
|
|
497
497
|
|
|
498
498
|
const icon = getStateIcon(agent.state);
|
|
499
499
|
const stateColor = getStateColor(agent.state);
|
|
500
|
-
const name = pad(truncate(agent.agentName, 15), 15);
|
|
500
|
+
const name = accent(pad(truncate(agent.agentName, 15), 15));
|
|
501
501
|
const capability = pad(truncate(agent.capability, 12), 12);
|
|
502
502
|
const state = pad(agent.state, 10);
|
|
503
|
-
const taskId = pad(truncate(agent.taskId, 16), 16);
|
|
503
|
+
const taskId = accent(pad(truncate(agent.taskId, 16), 16));
|
|
504
504
|
const endTime =
|
|
505
505
|
agent.state === "completed" || agent.state === "zombie"
|
|
506
506
|
? new Date(agent.lastActivity).getTime()
|
|
@@ -575,8 +575,8 @@ function renderMailPanel(
|
|
|
575
575
|
|
|
576
576
|
const priorityColorFn = getPriorityColor(msg.priority);
|
|
577
577
|
const priority = msg.priority === "normal" ? "" : `[${msg.priority}] `;
|
|
578
|
-
const from = truncate(msg.from, 12);
|
|
579
|
-
const to = truncate(msg.to, 12);
|
|
578
|
+
const from = accent(truncate(msg.from, 12));
|
|
579
|
+
const to = accent(truncate(msg.to, 12));
|
|
580
580
|
const subject = truncate(msg.subject, panelWidth - 40);
|
|
581
581
|
const time = timeAgo(msg.createdAt);
|
|
582
582
|
|
|
@@ -643,7 +643,7 @@ function renderMergeQueuePanel(
|
|
|
643
643
|
|
|
644
644
|
const statusColorFn = getMergeStatusColor(entry.status);
|
|
645
645
|
const status = pad(entry.status, 10);
|
|
646
|
-
const agent = truncate(entry.agentName, 15);
|
|
646
|
+
const agent = accent(truncate(entry.agentName, 15));
|
|
647
647
|
const branch = truncate(entry.branchName, panelWidth - 30);
|
|
648
648
|
|
|
649
649
|
const line = `${BOX.vertical} ${statusColorFn(status)} ${agent} ${branch}`;
|
|
@@ -85,10 +85,14 @@ describe("doctorCommand", () => {
|
|
|
85
85
|
const out = output();
|
|
86
86
|
|
|
87
87
|
const parsed = JSON.parse(out.trim()) as {
|
|
88
|
+
success: boolean;
|
|
89
|
+
command: string;
|
|
88
90
|
checks: unknown[];
|
|
89
91
|
summary: { pass: number; warn: number; fail: number };
|
|
90
92
|
};
|
|
91
93
|
expect(parsed).toBeDefined();
|
|
94
|
+
expect(parsed.success).toBe(true);
|
|
95
|
+
expect(parsed.command).toBe("doctor");
|
|
92
96
|
expect(Array.isArray(parsed.checks)).toBe(true);
|
|
93
97
|
expect(parsed.summary).toBeDefined();
|
|
94
98
|
expect(typeof parsed.summary.pass).toBe("number");
|
|
@@ -101,9 +105,13 @@ describe("doctorCommand", () => {
|
|
|
101
105
|
const out = output();
|
|
102
106
|
|
|
103
107
|
const parsed = JSON.parse(out.trim()) as {
|
|
108
|
+
success: boolean;
|
|
109
|
+
command: string;
|
|
104
110
|
checks: unknown[];
|
|
105
111
|
summary: { pass: number; warn: number; fail: number };
|
|
106
112
|
};
|
|
113
|
+
expect(parsed.success).toBe(true);
|
|
114
|
+
expect(parsed.command).toBe("doctor");
|
|
107
115
|
expect(parsed.checks).toEqual([]);
|
|
108
116
|
expect(parsed.summary.pass).toBe(0);
|
|
109
117
|
expect(parsed.summary.warn).toBe(0);
|
package/src/commands/doctor.ts
CHANGED
|
@@ -18,6 +18,7 @@ import { checkStructure } from "../doctor/structure.ts";
|
|
|
18
18
|
import type { DoctorCategory, DoctorCheck, DoctorCheckFn } from "../doctor/types.ts";
|
|
19
19
|
import { checkVersion } from "../doctor/version.ts";
|
|
20
20
|
import { ValidationError } from "../errors.ts";
|
|
21
|
+
import { jsonOutput } from "../json.ts";
|
|
21
22
|
import { color } from "../logging/color.ts";
|
|
22
23
|
|
|
23
24
|
/** Registry of all check modules in execution order. */
|
|
@@ -114,12 +115,7 @@ function printJSON(checks: DoctorCheck[]): void {
|
|
|
114
115
|
const warn = checks.filter((c) => c.status === "warn").length;
|
|
115
116
|
const fail = checks.filter((c) => c.status === "fail").length;
|
|
116
117
|
|
|
117
|
-
|
|
118
|
-
checks,
|
|
119
|
-
summary: { pass, warn, fail },
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
process.stdout.write(`${JSON.stringify(output, null, 2)}\n`);
|
|
118
|
+
jsonOutput("doctor", { checks, summary: { pass, warn, fail } });
|
|
123
119
|
}
|
|
124
120
|
|
|
125
121
|
/** Options for dependency injection in doctorCommand. */
|
|
@@ -133,7 +133,14 @@ describe("errorsCommand", () => {
|
|
|
133
133
|
await errorsCommand(["--json"]);
|
|
134
134
|
const out = output();
|
|
135
135
|
|
|
136
|
-
|
|
136
|
+
const parsed = JSON.parse(out.trim()) as {
|
|
137
|
+
success: boolean;
|
|
138
|
+
command: string;
|
|
139
|
+
events: unknown[];
|
|
140
|
+
};
|
|
141
|
+
expect(parsed.success).toBe(true);
|
|
142
|
+
expect(parsed.command).toBe("errors");
|
|
143
|
+
expect(parsed.events).toEqual([]);
|
|
137
144
|
});
|
|
138
145
|
});
|
|
139
146
|
|
|
@@ -157,9 +164,9 @@ describe("errorsCommand", () => {
|
|
|
157
164
|
await errorsCommand(["--json"]);
|
|
158
165
|
const out = output();
|
|
159
166
|
|
|
160
|
-
const parsed = JSON.parse(out.trim()) as unknown[];
|
|
161
|
-
expect(parsed).toHaveLength(2);
|
|
162
|
-
expect(Array.isArray(parsed)).toBe(true);
|
|
167
|
+
const parsed = JSON.parse(out.trim()) as { events: unknown[] };
|
|
168
|
+
expect(parsed.events).toHaveLength(2);
|
|
169
|
+
expect(Array.isArray(parsed.events)).toBe(true);
|
|
163
170
|
});
|
|
164
171
|
|
|
165
172
|
test("JSON output includes expected fields", async () => {
|
|
@@ -176,9 +183,9 @@ describe("errorsCommand", () => {
|
|
|
176
183
|
await errorsCommand(["--json"]);
|
|
177
184
|
const out = output();
|
|
178
185
|
|
|
179
|
-
const parsed = JSON.parse(out.trim()) as Record<string, unknown>[];
|
|
180
|
-
expect(parsed).toHaveLength(1);
|
|
181
|
-
const event = parsed[0];
|
|
186
|
+
const parsed = JSON.parse(out.trim()) as { events: Record<string, unknown>[] };
|
|
187
|
+
expect(parsed.events).toHaveLength(1);
|
|
188
|
+
const event = parsed.events[0];
|
|
182
189
|
expect(event).toBeDefined();
|
|
183
190
|
expect(event?.agentName).toBe("builder-1");
|
|
184
191
|
expect(event?.eventType).toBe("error");
|
|
@@ -201,8 +208,8 @@ describe("errorsCommand", () => {
|
|
|
201
208
|
await errorsCommand(["--json"]);
|
|
202
209
|
const out = output();
|
|
203
210
|
|
|
204
|
-
const parsed = JSON.parse(out.trim()) as unknown[];
|
|
205
|
-
expect(parsed).toEqual([]);
|
|
211
|
+
const parsed = JSON.parse(out.trim()) as { events: unknown[] };
|
|
212
|
+
expect(parsed.events).toEqual([]);
|
|
206
213
|
});
|
|
207
214
|
});
|
|
208
215
|
|
|
@@ -388,9 +395,9 @@ describe("errorsCommand", () => {
|
|
|
388
395
|
await errorsCommand(["--agent", "builder-1", "--json"]);
|
|
389
396
|
const out = output();
|
|
390
397
|
|
|
391
|
-
const parsed = JSON.parse(out.trim()) as Record<string, unknown>[];
|
|
392
|
-
expect(parsed).toHaveLength(2);
|
|
393
|
-
for (const event of parsed) {
|
|
398
|
+
const parsed = JSON.parse(out.trim()) as { events: Record<string, unknown>[] };
|
|
399
|
+
expect(parsed.events).toHaveLength(2);
|
|
400
|
+
for (const event of parsed.events) {
|
|
394
401
|
expect(event.agentName).toBe("builder-1");
|
|
395
402
|
}
|
|
396
403
|
});
|
|
@@ -404,8 +411,8 @@ describe("errorsCommand", () => {
|
|
|
404
411
|
await errorsCommand(["--agent", "nonexistent", "--json"]);
|
|
405
412
|
const out = output();
|
|
406
413
|
|
|
407
|
-
const parsed = JSON.parse(out.trim()) as unknown[];
|
|
408
|
-
expect(parsed).toEqual([]);
|
|
414
|
+
const parsed = JSON.parse(out.trim()) as { events: unknown[] };
|
|
415
|
+
expect(parsed.events).toEqual([]);
|
|
409
416
|
});
|
|
410
417
|
|
|
411
418
|
test("only returns error-level events for the agent", async () => {
|
|
@@ -431,9 +438,9 @@ describe("errorsCommand", () => {
|
|
|
431
438
|
await errorsCommand(["--agent", "builder-1", "--json"]);
|
|
432
439
|
const out = output();
|
|
433
440
|
|
|
434
|
-
const parsed = JSON.parse(out.trim()) as Record<string, unknown>[];
|
|
435
|
-
expect(parsed).toHaveLength(1);
|
|
436
|
-
expect(parsed[0]?.level).toBe("error");
|
|
441
|
+
const parsed = JSON.parse(out.trim()) as { events: Record<string, unknown>[] };
|
|
442
|
+
expect(parsed.events).toHaveLength(1);
|
|
443
|
+
expect(parsed.events[0]?.level).toBe("error");
|
|
437
444
|
});
|
|
438
445
|
});
|
|
439
446
|
|
|
@@ -451,9 +458,9 @@ describe("errorsCommand", () => {
|
|
|
451
458
|
await errorsCommand(["--run", "run-001", "--json"]);
|
|
452
459
|
const out = output();
|
|
453
460
|
|
|
454
|
-
const parsed = JSON.parse(out.trim()) as Record<string, unknown>[];
|
|
455
|
-
expect(parsed).toHaveLength(2);
|
|
456
|
-
for (const event of parsed) {
|
|
461
|
+
const parsed = JSON.parse(out.trim()) as { events: Record<string, unknown>[] };
|
|
462
|
+
expect(parsed.events).toHaveLength(2);
|
|
463
|
+
for (const event of parsed.events) {
|
|
457
464
|
expect(event.runId).toBe("run-001");
|
|
458
465
|
}
|
|
459
466
|
});
|
|
@@ -467,8 +474,8 @@ describe("errorsCommand", () => {
|
|
|
467
474
|
await errorsCommand(["--run", "run-999", "--json"]);
|
|
468
475
|
const out = output();
|
|
469
476
|
|
|
470
|
-
const parsed = JSON.parse(out.trim()) as unknown[];
|
|
471
|
-
expect(parsed).toEqual([]);
|
|
477
|
+
const parsed = JSON.parse(out.trim()) as { events: unknown[] };
|
|
478
|
+
expect(parsed.events).toEqual([]);
|
|
472
479
|
});
|
|
473
480
|
|
|
474
481
|
test("only returns error-level events for the run", async () => {
|
|
@@ -487,9 +494,9 @@ describe("errorsCommand", () => {
|
|
|
487
494
|
await errorsCommand(["--run", "run-001", "--json"]);
|
|
488
495
|
const out = output();
|
|
489
496
|
|
|
490
|
-
const parsed = JSON.parse(out.trim()) as Record<string, unknown>[];
|
|
491
|
-
expect(parsed).toHaveLength(1);
|
|
492
|
-
expect(parsed[0]?.level).toBe("error");
|
|
497
|
+
const parsed = JSON.parse(out.trim()) as { events: Record<string, unknown>[] };
|
|
498
|
+
expect(parsed.events).toHaveLength(1);
|
|
499
|
+
expect(parsed.events[0]?.level).toBe("error");
|
|
493
500
|
});
|
|
494
501
|
});
|
|
495
502
|
|
|
@@ -507,8 +514,8 @@ describe("errorsCommand", () => {
|
|
|
507
514
|
await errorsCommand(["--json", "--limit", "3"]);
|
|
508
515
|
const out = output();
|
|
509
516
|
|
|
510
|
-
const parsed = JSON.parse(out.trim()) as unknown[];
|
|
511
|
-
expect(parsed).toHaveLength(3);
|
|
517
|
+
const parsed = JSON.parse(out.trim()) as { events: unknown[] };
|
|
518
|
+
expect(parsed.events).toHaveLength(3);
|
|
512
519
|
});
|
|
513
520
|
|
|
514
521
|
test("default limit is 100", async () => {
|
|
@@ -522,8 +529,8 @@ describe("errorsCommand", () => {
|
|
|
522
529
|
await errorsCommand(["--json"]);
|
|
523
530
|
const out = output();
|
|
524
531
|
|
|
525
|
-
const parsed = JSON.parse(out.trim()) as unknown[];
|
|
526
|
-
expect(parsed).toHaveLength(100);
|
|
532
|
+
const parsed = JSON.parse(out.trim()) as { events: unknown[] };
|
|
533
|
+
expect(parsed.events).toHaveLength(100);
|
|
527
534
|
});
|
|
528
535
|
});
|
|
529
536
|
|
|
@@ -539,8 +546,8 @@ describe("errorsCommand", () => {
|
|
|
539
546
|
await errorsCommand(["--json", "--since", "2099-01-01T00:00:00Z"]);
|
|
540
547
|
const out = output();
|
|
541
548
|
|
|
542
|
-
const parsed = JSON.parse(out.trim()) as unknown[];
|
|
543
|
-
expect(parsed).toEqual([]);
|
|
549
|
+
const parsed = JSON.parse(out.trim()) as { events: unknown[] };
|
|
550
|
+
expect(parsed.events).toEqual([]);
|
|
544
551
|
});
|
|
545
552
|
|
|
546
553
|
test("--since with past timestamp returns all errors", async () => {
|
|
@@ -553,8 +560,8 @@ describe("errorsCommand", () => {
|
|
|
553
560
|
await errorsCommand(["--json", "--since", "2020-01-01T00:00:00Z"]);
|
|
554
561
|
const out = output();
|
|
555
562
|
|
|
556
|
-
const parsed = JSON.parse(out.trim()) as unknown[];
|
|
557
|
-
expect(parsed).toHaveLength(2);
|
|
563
|
+
const parsed = JSON.parse(out.trim()) as { events: unknown[] };
|
|
564
|
+
expect(parsed.events).toHaveLength(2);
|
|
558
565
|
});
|
|
559
566
|
|
|
560
567
|
test("--until with past timestamp returns no errors", async () => {
|
|
@@ -566,8 +573,8 @@ describe("errorsCommand", () => {
|
|
|
566
573
|
await errorsCommand(["--json", "--until", "2000-01-01T00:00:00Z"]);
|
|
567
574
|
const out = output();
|
|
568
575
|
|
|
569
|
-
const parsed = JSON.parse(out.trim()) as unknown[];
|
|
570
|
-
expect(parsed).toEqual([]);
|
|
576
|
+
const parsed = JSON.parse(out.trim()) as { events: unknown[] };
|
|
577
|
+
expect(parsed.events).toEqual([]);
|
|
571
578
|
});
|
|
572
579
|
});
|
|
573
580
|
|
|
@@ -608,8 +615,8 @@ describe("errorsCommand", () => {
|
|
|
608
615
|
await errorsCommand(["--json"]);
|
|
609
616
|
const out = output();
|
|
610
617
|
|
|
611
|
-
const parsed = JSON.parse(out.trim()) as unknown[];
|
|
612
|
-
expect(parsed).toHaveLength(3);
|
|
618
|
+
const parsed = JSON.parse(out.trim()) as { events: unknown[] };
|
|
619
|
+
expect(parsed.events).toHaveLength(3);
|
|
613
620
|
});
|
|
614
621
|
|
|
615
622
|
test("excludes non-error events from global view", async () => {
|
|
@@ -639,9 +646,9 @@ describe("errorsCommand", () => {
|
|
|
639
646
|
await errorsCommand(["--json"]);
|
|
640
647
|
const out = output();
|
|
641
648
|
|
|
642
|
-
const parsed = JSON.parse(out.trim()) as Record<string, unknown>[];
|
|
643
|
-
expect(parsed).toHaveLength(1);
|
|
644
|
-
expect(parsed[0]?.level).toBe("error");
|
|
649
|
+
const parsed = JSON.parse(out.trim()) as { events: Record<string, unknown>[] };
|
|
650
|
+
expect(parsed.events).toHaveLength(1);
|
|
651
|
+
expect(parsed.events[0]?.level).toBe("error");
|
|
645
652
|
});
|
|
646
653
|
});
|
|
647
654
|
});
|
package/src/commands/errors.ts
CHANGED
|
@@ -11,7 +11,8 @@ import { Command } from "commander";
|
|
|
11
11
|
import { loadConfig } from "../config.ts";
|
|
12
12
|
import { ValidationError } from "../errors.ts";
|
|
13
13
|
import { createEventStore } from "../events/store.ts";
|
|
14
|
-
import {
|
|
14
|
+
import { jsonOutput } from "../json.ts";
|
|
15
|
+
import { accent, color } from "../logging/color.ts";
|
|
15
16
|
import type { StoredEvent } from "../types.ts";
|
|
16
17
|
|
|
17
18
|
/**
|
|
@@ -115,7 +116,7 @@ function printErrors(events: StoredEvent[]): void {
|
|
|
115
116
|
firstGroup = false;
|
|
116
117
|
|
|
117
118
|
w(
|
|
118
|
-
`${
|
|
119
|
+
`${accent(agentName)} ${color.dim(`(${agentEvents.length} error${agentEvents.length === 1 ? "" : "s"})`)}\n`,
|
|
119
120
|
);
|
|
120
121
|
|
|
121
122
|
for (const event of agentEvents) {
|
|
@@ -179,7 +180,7 @@ async function executeErrors(opts: ErrorsOpts): Promise<void> {
|
|
|
179
180
|
const eventsFile = Bun.file(eventsDbPath);
|
|
180
181
|
if (!(await eventsFile.exists())) {
|
|
181
182
|
if (json) {
|
|
182
|
-
|
|
183
|
+
jsonOutput("errors", { events: [] });
|
|
183
184
|
} else {
|
|
184
185
|
process.stdout.write("No events data yet.\n");
|
|
185
186
|
}
|
|
@@ -209,7 +210,7 @@ async function executeErrors(opts: ErrorsOpts): Promise<void> {
|
|
|
209
210
|
}
|
|
210
211
|
|
|
211
212
|
if (json) {
|
|
212
|
-
|
|
213
|
+
jsonOutput("errors", { events });
|
|
213
214
|
return;
|
|
214
215
|
}
|
|
215
216
|
|
|
@@ -138,7 +138,14 @@ describe("feedCommand", () => {
|
|
|
138
138
|
await feedCommand(["--json"]);
|
|
139
139
|
const out = output();
|
|
140
140
|
|
|
141
|
-
|
|
141
|
+
const parsed = JSON.parse(out.trim()) as {
|
|
142
|
+
success: boolean;
|
|
143
|
+
command: string;
|
|
144
|
+
events: unknown[];
|
|
145
|
+
};
|
|
146
|
+
expect(parsed.success).toBe(true);
|
|
147
|
+
expect(parsed.command).toBe("feed");
|
|
148
|
+
expect(parsed.events).toEqual([]);
|
|
142
149
|
});
|
|
143
150
|
});
|
|
144
151
|
|
|
@@ -156,9 +163,9 @@ describe("feedCommand", () => {
|
|
|
156
163
|
await feedCommand(["--json"]);
|
|
157
164
|
const out = output();
|
|
158
165
|
|
|
159
|
-
const parsed = JSON.parse(out.trim()) as unknown[];
|
|
160
|
-
expect(parsed).toHaveLength(3);
|
|
161
|
-
expect(Array.isArray(parsed)).toBe(true);
|
|
166
|
+
const parsed = JSON.parse(out.trim()) as { events: unknown[] };
|
|
167
|
+
expect(parsed.events).toHaveLength(3);
|
|
168
|
+
expect(Array.isArray(parsed.events)).toBe(true);
|
|
162
169
|
});
|
|
163
170
|
|
|
164
171
|
test("JSON output includes expected fields", async () => {
|
|
@@ -177,9 +184,9 @@ describe("feedCommand", () => {
|
|
|
177
184
|
await feedCommand(["--json"]);
|
|
178
185
|
const out = output();
|
|
179
186
|
|
|
180
|
-
const parsed = JSON.parse(out.trim()) as Record<string, unknown>[];
|
|
181
|
-
expect(parsed).toHaveLength(1);
|
|
182
|
-
const event = parsed[0];
|
|
187
|
+
const parsed = JSON.parse(out.trim()) as { events: Record<string, unknown>[] };
|
|
188
|
+
expect(parsed.events).toHaveLength(1);
|
|
189
|
+
const event = parsed.events[0];
|
|
183
190
|
expect(event).toBeDefined();
|
|
184
191
|
expect(event?.agentName).toBe("builder-1");
|
|
185
192
|
expect(event?.eventType).toBe("tool_start");
|
|
@@ -198,8 +205,8 @@ describe("feedCommand", () => {
|
|
|
198
205
|
await feedCommand(["--json", "--since", "2099-01-01T00:00:00Z"]);
|
|
199
206
|
const out = output();
|
|
200
207
|
|
|
201
|
-
const parsed = JSON.parse(out.trim()) as unknown[];
|
|
202
|
-
expect(parsed).toEqual([]);
|
|
208
|
+
const parsed = JSON.parse(out.trim()) as { events: unknown[] };
|
|
209
|
+
expect(parsed.events).toEqual([]);
|
|
203
210
|
});
|
|
204
211
|
});
|
|
205
212
|
|
|
@@ -317,9 +324,9 @@ describe("feedCommand", () => {
|
|
|
317
324
|
await feedCommand(["--agent", "builder-1", "--json"]);
|
|
318
325
|
const out = output();
|
|
319
326
|
|
|
320
|
-
const parsed = JSON.parse(out.trim()) as Record<string, unknown>[];
|
|
321
|
-
expect(parsed).toHaveLength(1);
|
|
322
|
-
expect(parsed[0]?.agentName).toBe("builder-1");
|
|
327
|
+
const parsed = JSON.parse(out.trim()) as { events: Record<string, unknown>[] };
|
|
328
|
+
expect(parsed.events).toHaveLength(1);
|
|
329
|
+
expect(parsed.events[0]?.agentName).toBe("builder-1");
|
|
323
330
|
});
|
|
324
331
|
|
|
325
332
|
test("filters to multiple agents", async () => {
|
|
@@ -333,9 +340,9 @@ describe("feedCommand", () => {
|
|
|
333
340
|
await feedCommand(["--agent", "builder-1", "--agent", "scout-1", "--json"]);
|
|
334
341
|
const out = output();
|
|
335
342
|
|
|
336
|
-
const parsed = JSON.parse(out.trim()) as Record<string, unknown>[];
|
|
337
|
-
expect(parsed).toHaveLength(2);
|
|
338
|
-
const agents = parsed.map((e) => e.agentName);
|
|
343
|
+
const parsed = JSON.parse(out.trim()) as { events: Record<string, unknown>[] };
|
|
344
|
+
expect(parsed.events).toHaveLength(2);
|
|
345
|
+
const agents = parsed.events.map((e) => e.agentName);
|
|
339
346
|
expect(agents).toContain("builder-1");
|
|
340
347
|
expect(agents).toContain("scout-1");
|
|
341
348
|
expect(agents).not.toContain("builder-2");
|
|
@@ -356,9 +363,9 @@ describe("feedCommand", () => {
|
|
|
356
363
|
await feedCommand(["--run", "run-001", "--json"]);
|
|
357
364
|
const out = output();
|
|
358
365
|
|
|
359
|
-
const parsed = JSON.parse(out.trim()) as Record<string, unknown>[];
|
|
360
|
-
expect(parsed).toHaveLength(2);
|
|
361
|
-
for (const event of parsed) {
|
|
366
|
+
const parsed = JSON.parse(out.trim()) as { events: Record<string, unknown>[] };
|
|
367
|
+
expect(parsed.events).toHaveLength(2);
|
|
368
|
+
for (const event of parsed.events) {
|
|
362
369
|
expect(event.runId).toBe("run-001");
|
|
363
370
|
}
|
|
364
371
|
});
|
|
@@ -378,8 +385,8 @@ describe("feedCommand", () => {
|
|
|
378
385
|
await feedCommand(["--json", "--limit", "10"]);
|
|
379
386
|
const out = output();
|
|
380
387
|
|
|
381
|
-
const parsed = JSON.parse(out.trim()) as unknown[];
|
|
382
|
-
expect(parsed).toHaveLength(10);
|
|
388
|
+
const parsed = JSON.parse(out.trim()) as { events: unknown[] };
|
|
389
|
+
expect(parsed.events).toHaveLength(10);
|
|
383
390
|
});
|
|
384
391
|
|
|
385
392
|
test("default limit is 50", async () => {
|
|
@@ -393,8 +400,8 @@ describe("feedCommand", () => {
|
|
|
393
400
|
await feedCommand(["--json"]);
|
|
394
401
|
const out = output();
|
|
395
402
|
|
|
396
|
-
const parsed = JSON.parse(out.trim()) as unknown[];
|
|
397
|
-
expect(parsed).toHaveLength(50);
|
|
403
|
+
const parsed = JSON.parse(out.trim()) as { events: unknown[] };
|
|
404
|
+
expect(parsed.events).toHaveLength(50);
|
|
398
405
|
});
|
|
399
406
|
});
|
|
400
407
|
|
|
@@ -411,8 +418,8 @@ describe("feedCommand", () => {
|
|
|
411
418
|
await feedCommand(["--json", "--since", "2099-01-01T00:00:00Z"]);
|
|
412
419
|
const out = output();
|
|
413
420
|
|
|
414
|
-
const parsed = JSON.parse(out.trim()) as unknown[];
|
|
415
|
-
expect(parsed).toEqual([]);
|
|
421
|
+
const parsed = JSON.parse(out.trim()) as { events: unknown[] };
|
|
422
|
+
expect(parsed.events).toEqual([]);
|
|
416
423
|
});
|
|
417
424
|
|
|
418
425
|
test("--since with past timestamp returns all events", async () => {
|
|
@@ -425,8 +432,8 @@ describe("feedCommand", () => {
|
|
|
425
432
|
await feedCommand(["--json", "--since", "2020-01-01T00:00:00Z"]);
|
|
426
433
|
const out = output();
|
|
427
434
|
|
|
428
|
-
const parsed = JSON.parse(out.trim()) as unknown[];
|
|
429
|
-
expect(parsed).toHaveLength(2);
|
|
435
|
+
const parsed = JSON.parse(out.trim()) as { events: unknown[] };
|
|
436
|
+
expect(parsed.events).toHaveLength(2);
|
|
430
437
|
});
|
|
431
438
|
|
|
432
439
|
test("default since is 5 minutes ago", async () => {
|
|
@@ -440,8 +447,8 @@ describe("feedCommand", () => {
|
|
|
440
447
|
await feedCommand(["--json"]);
|
|
441
448
|
const out = output();
|
|
442
449
|
|
|
443
|
-
const parsed = JSON.parse(out.trim()) as unknown[];
|
|
444
|
-
expect(parsed).toHaveLength(1);
|
|
450
|
+
const parsed = JSON.parse(out.trim()) as { events: unknown[] };
|
|
451
|
+
expect(parsed.events).toHaveLength(1);
|
|
445
452
|
});
|
|
446
453
|
});
|
|
447
454
|
|
|
@@ -503,11 +510,11 @@ describe("feedCommand", () => {
|
|
|
503
510
|
await feedCommand(["--json"]);
|
|
504
511
|
const out = output();
|
|
505
512
|
|
|
506
|
-
const parsed = JSON.parse(out.trim()) as Record<string, unknown>[];
|
|
507
|
-
expect(parsed).toHaveLength(3);
|
|
508
|
-
expect(parsed[0]?.eventType).toBe("session_start");
|
|
509
|
-
expect(parsed[1]?.eventType).toBe("tool_start");
|
|
510
|
-
expect(parsed[2]?.eventType).toBe("session_end");
|
|
513
|
+
const parsed = JSON.parse(out.trim()) as { events: Record<string, unknown>[] };
|
|
514
|
+
expect(parsed.events).toHaveLength(3);
|
|
515
|
+
expect(parsed.events[0]?.eventType).toBe("session_start");
|
|
516
|
+
expect(parsed.events[1]?.eventType).toBe("tool_start");
|
|
517
|
+
expect(parsed.events[2]?.eventType).toBe("session_end");
|
|
511
518
|
});
|
|
512
519
|
|
|
513
520
|
test("handles event with all null optional fields", async () => {
|
package/src/commands/feed.ts
CHANGED
|
@@ -11,6 +11,7 @@ import { Command } from "commander";
|
|
|
11
11
|
import { loadConfig } from "../config.ts";
|
|
12
12
|
import { ValidationError } from "../errors.ts";
|
|
13
13
|
import { createEventStore } from "../events/store.ts";
|
|
14
|
+
import { jsonOutput } from "../json.ts";
|
|
14
15
|
import type { ColorFn } from "../logging/color.ts";
|
|
15
16
|
import { color } from "../logging/color.ts";
|
|
16
17
|
import type { EventType, StoredEvent } from "../types.ts";
|
|
@@ -189,7 +190,7 @@ async function executeFeed(opts: FeedOpts): Promise<void> {
|
|
|
189
190
|
const eventsFile = Bun.file(eventsDbPath);
|
|
190
191
|
if (!(await eventsFile.exists())) {
|
|
191
192
|
if (json) {
|
|
192
|
-
|
|
193
|
+
jsonOutput("feed", { events: [] });
|
|
193
194
|
} else {
|
|
194
195
|
process.stdout.write("No events data yet.\n");
|
|
195
196
|
}
|
|
@@ -228,7 +229,7 @@ async function executeFeed(opts: FeedOpts): Promise<void> {
|
|
|
228
229
|
const events = queryEvents({ since, limit });
|
|
229
230
|
|
|
230
231
|
if (json) {
|
|
231
|
-
|
|
232
|
+
jsonOutput("feed", { events });
|
|
232
233
|
return;
|
|
233
234
|
}
|
|
234
235
|
|