@os-eco/overstory-cli 0.6.1 → 0.6.5
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 +8 -7
- package/package.json +12 -4
- package/src/agents/checkpoint.test.ts +2 -2
- package/src/agents/hooks-deployer.test.ts +131 -16
- package/src/agents/hooks-deployer.ts +33 -1
- package/src/agents/identity.test.ts +27 -27
- package/src/agents/identity.ts +10 -10
- package/src/agents/lifecycle.test.ts +6 -6
- package/src/agents/lifecycle.ts +2 -2
- package/src/agents/manifest.test.ts +86 -0
- package/src/agents/overlay.test.ts +9 -9
- package/src/agents/overlay.ts +4 -4
- package/src/commands/agents.test.ts +8 -8
- package/src/commands/agents.ts +62 -91
- package/src/commands/clean.test.ts +36 -51
- package/src/commands/clean.ts +28 -49
- package/src/commands/completions.ts +14 -0
- package/src/commands/coordinator.test.ts +133 -26
- package/src/commands/coordinator.ts +101 -64
- package/src/commands/costs.test.ts +47 -47
- package/src/commands/costs.ts +96 -75
- package/src/commands/dashboard.test.ts +2 -2
- package/src/commands/dashboard.ts +75 -95
- package/src/commands/doctor.test.ts +2 -2
- package/src/commands/doctor.ts +92 -79
- package/src/commands/errors.test.ts +2 -2
- package/src/commands/errors.ts +56 -50
- package/src/commands/feed.test.ts +2 -2
- package/src/commands/feed.ts +86 -83
- package/src/commands/group.ts +167 -177
- package/src/commands/hooks.test.ts +2 -2
- package/src/commands/hooks.ts +52 -42
- package/src/commands/init.test.ts +19 -19
- package/src/commands/init.ts +7 -16
- package/src/commands/inspect.test.ts +18 -18
- package/src/commands/inspect.ts +55 -58
- package/src/commands/log.test.ts +26 -31
- package/src/commands/log.ts +97 -91
- package/src/commands/logs.test.ts +1 -1
- package/src/commands/logs.ts +101 -104
- package/src/commands/mail.test.ts +5 -5
- package/src/commands/mail.ts +157 -169
- package/src/commands/merge.test.ts +28 -66
- package/src/commands/merge.ts +21 -51
- package/src/commands/metrics.test.ts +8 -8
- package/src/commands/metrics.ts +34 -35
- package/src/commands/monitor.test.ts +3 -3
- package/src/commands/monitor.ts +57 -62
- package/src/commands/nudge.test.ts +1 -1
- package/src/commands/nudge.ts +41 -89
- package/src/commands/prime.test.ts +19 -51
- package/src/commands/prime.ts +13 -50
- package/src/commands/replay.test.ts +2 -2
- package/src/commands/replay.ts +79 -86
- package/src/commands/run.test.ts +1 -1
- package/src/commands/run.ts +97 -77
- package/src/commands/sling.test.ts +201 -5
- package/src/commands/sling.ts +37 -64
- package/src/commands/spec.test.ts +14 -40
- package/src/commands/spec.ts +32 -101
- package/src/commands/status.test.ts +97 -1
- package/src/commands/status.ts +63 -58
- package/src/commands/stop.test.ts +22 -40
- package/src/commands/stop.ts +18 -33
- package/src/commands/supervisor.test.ts +12 -14
- package/src/commands/supervisor.ts +144 -165
- package/src/commands/trace.test.ts +15 -15
- package/src/commands/trace.ts +59 -82
- package/src/commands/watch.test.ts +2 -2
- package/src/commands/watch.ts +38 -45
- package/src/commands/worktree.test.ts +213 -37
- package/src/commands/worktree.ts +110 -55
- package/src/config.test.ts +96 -0
- package/src/doctor/consistency.test.ts +14 -14
- package/src/doctor/databases.test.ts +22 -2
- package/src/doctor/databases.ts +16 -0
- package/src/doctor/dependencies.test.ts +55 -1
- package/src/doctor/dependencies.ts +113 -18
- package/src/doctor/merge-queue.test.ts +4 -4
- package/src/e2e/init-sling-lifecycle.test.ts +8 -8
- package/src/errors.ts +1 -1
- package/src/index.ts +223 -213
- package/src/logging/color.test.ts +74 -91
- package/src/logging/color.ts +52 -46
- package/src/logging/reporter.test.ts +10 -10
- package/src/logging/reporter.ts +6 -5
- package/src/mail/broadcast.test.ts +1 -1
- package/src/mail/client.test.ts +6 -6
- package/src/mail/store.test.ts +3 -3
- package/src/merge/queue.test.ts +73 -7
- package/src/merge/queue.ts +17 -2
- package/src/merge/resolver.test.ts +159 -7
- package/src/merge/resolver.ts +46 -2
- package/src/metrics/store.test.ts +44 -44
- package/src/metrics/store.ts +2 -2
- package/src/metrics/summary.test.ts +35 -35
- package/src/mulch/client.test.ts +1 -1
- package/src/schema-consistency.test.ts +239 -0
- package/src/sessions/compat.test.ts +3 -3
- package/src/sessions/compat.ts +2 -2
- package/src/sessions/store.test.ts +41 -4
- package/src/sessions/store.ts +13 -2
- package/src/types.ts +14 -14
- package/src/watchdog/daemon.test.ts +10 -10
- package/src/watchdog/daemon.ts +1 -1
- package/src/watchdog/health.test.ts +1 -1
- package/src/worktree/manager.test.ts +20 -20
- package/src/worktree/manager.ts +120 -4
- package/src/worktree/tmux.test.ts +98 -9
- package/src/worktree/tmux.ts +18 -0
|
@@ -22,7 +22,7 @@ import { costsCommand } from "./costs.ts";
|
|
|
22
22
|
function makeMetrics(overrides: Partial<SessionMetrics> = {}): SessionMetrics {
|
|
23
23
|
return {
|
|
24
24
|
agentName: "builder-1",
|
|
25
|
-
|
|
25
|
+
taskId: "task-001",
|
|
26
26
|
capability: "builder",
|
|
27
27
|
startedAt: new Date().toISOString(),
|
|
28
28
|
completedAt: new Date().toISOString(),
|
|
@@ -86,7 +86,7 @@ describe("costsCommand", () => {
|
|
|
86
86
|
await costsCommand(["--help"]);
|
|
87
87
|
const out = output();
|
|
88
88
|
|
|
89
|
-
expect(out).toContain("
|
|
89
|
+
expect(out).toContain("costs");
|
|
90
90
|
expect(out).toContain("--agent");
|
|
91
91
|
expect(out).toContain("--run");
|
|
92
92
|
expect(out).toContain("--by-capability");
|
|
@@ -98,7 +98,7 @@ describe("costsCommand", () => {
|
|
|
98
98
|
await costsCommand(["-h"]);
|
|
99
99
|
const out = output();
|
|
100
100
|
|
|
101
|
-
expect(out).toContain("
|
|
101
|
+
expect(out).toContain("costs");
|
|
102
102
|
});
|
|
103
103
|
});
|
|
104
104
|
|
|
@@ -142,8 +142,8 @@ describe("costsCommand", () => {
|
|
|
142
142
|
test("outputs valid JSON array with sessions", async () => {
|
|
143
143
|
const dbPath = join(tempDir, ".overstory", "metrics.db");
|
|
144
144
|
const store = createMetricsStore(dbPath);
|
|
145
|
-
store.recordSession(makeMetrics({ agentName: "builder-1",
|
|
146
|
-
store.recordSession(makeMetrics({ agentName: "scout-1",
|
|
145
|
+
store.recordSession(makeMetrics({ agentName: "builder-1", taskId: "t1" }));
|
|
146
|
+
store.recordSession(makeMetrics({ agentName: "scout-1", taskId: "t2", capability: "scout" }));
|
|
147
147
|
store.close();
|
|
148
148
|
|
|
149
149
|
await costsCommand(["--json"]);
|
|
@@ -160,7 +160,7 @@ describe("costsCommand", () => {
|
|
|
160
160
|
store.recordSession(
|
|
161
161
|
makeMetrics({
|
|
162
162
|
agentName: "builder-1",
|
|
163
|
-
|
|
163
|
+
taskId: "t1",
|
|
164
164
|
inputTokens: 100,
|
|
165
165
|
outputTokens: 50,
|
|
166
166
|
cacheReadTokens: 30,
|
|
@@ -187,7 +187,7 @@ describe("costsCommand", () => {
|
|
|
187
187
|
test("JSON output returns empty array when no sessions match", async () => {
|
|
188
188
|
const dbPath = join(tempDir, ".overstory", "metrics.db");
|
|
189
189
|
const store = createMetricsStore(dbPath);
|
|
190
|
-
store.recordSession(makeMetrics({ agentName: "builder-1",
|
|
190
|
+
store.recordSession(makeMetrics({ agentName: "builder-1", taskId: "t1" }));
|
|
191
191
|
store.close();
|
|
192
192
|
|
|
193
193
|
await costsCommand(["--json", "--agent", "nonexistent"]);
|
|
@@ -203,7 +203,7 @@ describe("costsCommand", () => {
|
|
|
203
203
|
store.recordSession(
|
|
204
204
|
makeMetrics({
|
|
205
205
|
agentName: "builder-1",
|
|
206
|
-
|
|
206
|
+
taskId: "t1",
|
|
207
207
|
capability: "builder",
|
|
208
208
|
inputTokens: 100,
|
|
209
209
|
}),
|
|
@@ -211,7 +211,7 @@ describe("costsCommand", () => {
|
|
|
211
211
|
store.recordSession(
|
|
212
212
|
makeMetrics({
|
|
213
213
|
agentName: "scout-1",
|
|
214
|
-
|
|
214
|
+
taskId: "t2",
|
|
215
215
|
capability: "scout",
|
|
216
216
|
inputTokens: 50,
|
|
217
217
|
}),
|
|
@@ -238,7 +238,7 @@ describe("costsCommand", () => {
|
|
|
238
238
|
test("shows Cost Summary header", async () => {
|
|
239
239
|
const dbPath = join(tempDir, ".overstory", "metrics.db");
|
|
240
240
|
const store = createMetricsStore(dbPath);
|
|
241
|
-
store.recordSession(makeMetrics({ agentName: "builder-1",
|
|
241
|
+
store.recordSession(makeMetrics({ agentName: "builder-1", taskId: "t1" }));
|
|
242
242
|
store.close();
|
|
243
243
|
|
|
244
244
|
await costsCommand([]);
|
|
@@ -250,7 +250,7 @@ describe("costsCommand", () => {
|
|
|
250
250
|
test("shows column headers", async () => {
|
|
251
251
|
const dbPath = join(tempDir, ".overstory", "metrics.db");
|
|
252
252
|
const store = createMetricsStore(dbPath);
|
|
253
|
-
store.recordSession(makeMetrics({ agentName: "builder-1",
|
|
253
|
+
store.recordSession(makeMetrics({ agentName: "builder-1", taskId: "t1" }));
|
|
254
254
|
store.close();
|
|
255
255
|
|
|
256
256
|
await costsCommand([]);
|
|
@@ -270,7 +270,7 @@ describe("costsCommand", () => {
|
|
|
270
270
|
store.recordSession(
|
|
271
271
|
makeMetrics({
|
|
272
272
|
agentName: "builder-1",
|
|
273
|
-
|
|
273
|
+
taskId: "t1",
|
|
274
274
|
capability: "builder",
|
|
275
275
|
}),
|
|
276
276
|
);
|
|
@@ -286,7 +286,7 @@ describe("costsCommand", () => {
|
|
|
286
286
|
test("shows separator line", async () => {
|
|
287
287
|
const dbPath = join(tempDir, ".overstory", "metrics.db");
|
|
288
288
|
const store = createMetricsStore(dbPath);
|
|
289
|
-
store.recordSession(makeMetrics({ agentName: "builder-1",
|
|
289
|
+
store.recordSession(makeMetrics({ agentName: "builder-1", taskId: "t1" }));
|
|
290
290
|
store.close();
|
|
291
291
|
|
|
292
292
|
await costsCommand([]);
|
|
@@ -298,7 +298,7 @@ describe("costsCommand", () => {
|
|
|
298
298
|
test("shows Total row", async () => {
|
|
299
299
|
const dbPath = join(tempDir, ".overstory", "metrics.db");
|
|
300
300
|
const store = createMetricsStore(dbPath);
|
|
301
|
-
store.recordSession(makeMetrics({ agentName: "builder-1",
|
|
301
|
+
store.recordSession(makeMetrics({ agentName: "builder-1", taskId: "t1" }));
|
|
302
302
|
store.close();
|
|
303
303
|
|
|
304
304
|
await costsCommand([]);
|
|
@@ -329,7 +329,7 @@ describe("costsCommand", () => {
|
|
|
329
329
|
store.recordSession(
|
|
330
330
|
makeMetrics({
|
|
331
331
|
agentName: "builder-1",
|
|
332
|
-
|
|
332
|
+
taskId: "t1",
|
|
333
333
|
inputTokens: 12345,
|
|
334
334
|
outputTokens: 5678,
|
|
335
335
|
}),
|
|
@@ -349,7 +349,7 @@ describe("costsCommand", () => {
|
|
|
349
349
|
store.recordSession(
|
|
350
350
|
makeMetrics({
|
|
351
351
|
agentName: "builder-1",
|
|
352
|
-
|
|
352
|
+
taskId: "t1",
|
|
353
353
|
estimatedCostUsd: 0.42,
|
|
354
354
|
}),
|
|
355
355
|
);
|
|
@@ -367,7 +367,7 @@ describe("costsCommand", () => {
|
|
|
367
367
|
store.recordSession(
|
|
368
368
|
makeMetrics({
|
|
369
369
|
agentName: "builder-1",
|
|
370
|
-
|
|
370
|
+
taskId: "t1",
|
|
371
371
|
inputTokens: 0,
|
|
372
372
|
outputTokens: 0,
|
|
373
373
|
cacheReadTokens: 0,
|
|
@@ -389,7 +389,7 @@ describe("costsCommand", () => {
|
|
|
389
389
|
store.recordSession(
|
|
390
390
|
makeMetrics({
|
|
391
391
|
agentName: "builder-1",
|
|
392
|
-
|
|
392
|
+
taskId: "t1",
|
|
393
393
|
estimatedCostUsd: null,
|
|
394
394
|
}),
|
|
395
395
|
);
|
|
@@ -408,8 +408,8 @@ describe("costsCommand", () => {
|
|
|
408
408
|
test("filters sessions by agent name", async () => {
|
|
409
409
|
const dbPath = join(tempDir, ".overstory", "metrics.db");
|
|
410
410
|
const store = createMetricsStore(dbPath);
|
|
411
|
-
store.recordSession(makeMetrics({ agentName: "builder-1",
|
|
412
|
-
store.recordSession(makeMetrics({ agentName: "scout-1",
|
|
411
|
+
store.recordSession(makeMetrics({ agentName: "builder-1", taskId: "t1", inputTokens: 100 }));
|
|
412
|
+
store.recordSession(makeMetrics({ agentName: "scout-1", taskId: "t2", inputTokens: 200 }));
|
|
413
413
|
store.close();
|
|
414
414
|
|
|
415
415
|
await costsCommand(["--json", "--agent", "builder-1"]);
|
|
@@ -423,7 +423,7 @@ describe("costsCommand", () => {
|
|
|
423
423
|
test("returns empty for non-existent agent", async () => {
|
|
424
424
|
const dbPath = join(tempDir, ".overstory", "metrics.db");
|
|
425
425
|
const store = createMetricsStore(dbPath);
|
|
426
|
-
store.recordSession(makeMetrics({ agentName: "builder-1",
|
|
426
|
+
store.recordSession(makeMetrics({ agentName: "builder-1", taskId: "t1" }));
|
|
427
427
|
store.close();
|
|
428
428
|
|
|
429
429
|
await costsCommand(["--json", "--agent", "nonexistent"]);
|
|
@@ -441,12 +441,12 @@ describe("costsCommand", () => {
|
|
|
441
441
|
const dbPath = join(tempDir, ".overstory", "metrics.db");
|
|
442
442
|
const store = createMetricsStore(dbPath);
|
|
443
443
|
store.recordSession(
|
|
444
|
-
makeMetrics({ agentName: "builder-1",
|
|
444
|
+
makeMetrics({ agentName: "builder-1", taskId: "task-001", runId: "run-2026-01-01" }),
|
|
445
445
|
);
|
|
446
446
|
store.recordSession(
|
|
447
447
|
makeMetrics({
|
|
448
448
|
agentName: "scout-1",
|
|
449
|
-
|
|
449
|
+
taskId: "task-002",
|
|
450
450
|
capability: "scout",
|
|
451
451
|
runId: "run-other",
|
|
452
452
|
}),
|
|
@@ -465,7 +465,7 @@ describe("costsCommand", () => {
|
|
|
465
465
|
const dbPath = join(tempDir, ".overstory", "metrics.db");
|
|
466
466
|
const store = createMetricsStore(dbPath);
|
|
467
467
|
store.recordSession(
|
|
468
|
-
makeMetrics({ agentName: "builder-1",
|
|
468
|
+
makeMetrics({ agentName: "builder-1", taskId: "t1", runId: "run-2026-01-01" }),
|
|
469
469
|
);
|
|
470
470
|
store.close();
|
|
471
471
|
|
|
@@ -484,7 +484,7 @@ describe("costsCommand", () => {
|
|
|
484
484
|
const dbPath = join(tempDir, ".overstory", "metrics.db");
|
|
485
485
|
const store = createMetricsStore(dbPath);
|
|
486
486
|
store.recordSession(
|
|
487
|
-
makeMetrics({ agentName: "builder-1",
|
|
487
|
+
makeMetrics({ agentName: "builder-1", taskId: "t1", capability: "builder" }),
|
|
488
488
|
);
|
|
489
489
|
store.close();
|
|
490
490
|
|
|
@@ -498,7 +498,7 @@ describe("costsCommand", () => {
|
|
|
498
498
|
const dbPath = join(tempDir, ".overstory", "metrics.db");
|
|
499
499
|
const store = createMetricsStore(dbPath);
|
|
500
500
|
store.recordSession(
|
|
501
|
-
makeMetrics({ agentName: "builder-1",
|
|
501
|
+
makeMetrics({ agentName: "builder-1", taskId: "t1", capability: "builder" }),
|
|
502
502
|
);
|
|
503
503
|
store.close();
|
|
504
504
|
|
|
@@ -514,7 +514,7 @@ describe("costsCommand", () => {
|
|
|
514
514
|
store.recordSession(
|
|
515
515
|
makeMetrics({
|
|
516
516
|
agentName: "builder-1",
|
|
517
|
-
|
|
517
|
+
taskId: "t1",
|
|
518
518
|
capability: "builder",
|
|
519
519
|
inputTokens: 1000,
|
|
520
520
|
}),
|
|
@@ -522,7 +522,7 @@ describe("costsCommand", () => {
|
|
|
522
522
|
store.recordSession(
|
|
523
523
|
makeMetrics({
|
|
524
524
|
agentName: "builder-2",
|
|
525
|
-
|
|
525
|
+
taskId: "t2",
|
|
526
526
|
capability: "builder",
|
|
527
527
|
inputTokens: 2000,
|
|
528
528
|
}),
|
|
@@ -530,7 +530,7 @@ describe("costsCommand", () => {
|
|
|
530
530
|
store.recordSession(
|
|
531
531
|
makeMetrics({
|
|
532
532
|
agentName: "scout-1",
|
|
533
|
-
|
|
533
|
+
taskId: "t3",
|
|
534
534
|
capability: "scout",
|
|
535
535
|
inputTokens: 500,
|
|
536
536
|
}),
|
|
@@ -548,10 +548,10 @@ describe("costsCommand", () => {
|
|
|
548
548
|
test("shows correct session count per capability", async () => {
|
|
549
549
|
const dbPath = join(tempDir, ".overstory", "metrics.db");
|
|
550
550
|
const store = createMetricsStore(dbPath);
|
|
551
|
-
store.recordSession(makeMetrics({ agentName: "b1",
|
|
552
|
-
store.recordSession(makeMetrics({ agentName: "b2",
|
|
553
|
-
store.recordSession(makeMetrics({ agentName: "b3",
|
|
554
|
-
store.recordSession(makeMetrics({ agentName: "s1",
|
|
551
|
+
store.recordSession(makeMetrics({ agentName: "b1", taskId: "t1", capability: "builder" }));
|
|
552
|
+
store.recordSession(makeMetrics({ agentName: "b2", taskId: "t2", capability: "builder" }));
|
|
553
|
+
store.recordSession(makeMetrics({ agentName: "b3", taskId: "t3", capability: "builder" }));
|
|
554
|
+
store.recordSession(makeMetrics({ agentName: "s1", taskId: "t4", capability: "scout" }));
|
|
555
555
|
store.close();
|
|
556
556
|
|
|
557
557
|
await costsCommand(["--json", "--by-capability"]);
|
|
@@ -584,7 +584,7 @@ describe("costsCommand", () => {
|
|
|
584
584
|
const dbPath = join(tempDir, ".overstory", "metrics.db");
|
|
585
585
|
const store = createMetricsStore(dbPath);
|
|
586
586
|
for (let i = 0; i < 10; i++) {
|
|
587
|
-
store.recordSession(makeMetrics({ agentName: `agent-${i}`,
|
|
587
|
+
store.recordSession(makeMetrics({ agentName: `agent-${i}`, taskId: `t-${i}` }));
|
|
588
588
|
}
|
|
589
589
|
store.close();
|
|
590
590
|
|
|
@@ -599,7 +599,7 @@ describe("costsCommand", () => {
|
|
|
599
599
|
const dbPath = join(tempDir, ".overstory", "metrics.db");
|
|
600
600
|
const store = createMetricsStore(dbPath);
|
|
601
601
|
for (let i = 0; i < 25; i++) {
|
|
602
|
-
store.recordSession(makeMetrics({ agentName: `agent-${i}`,
|
|
602
|
+
store.recordSession(makeMetrics({ agentName: `agent-${i}`, taskId: `t-${i}` }));
|
|
603
603
|
}
|
|
604
604
|
store.close();
|
|
605
605
|
|
|
@@ -620,7 +620,7 @@ describe("costsCommand", () => {
|
|
|
620
620
|
store.recordSession(
|
|
621
621
|
makeMetrics({
|
|
622
622
|
agentName: "builder-1",
|
|
623
|
-
|
|
623
|
+
taskId: "t1",
|
|
624
624
|
inputTokens: 0,
|
|
625
625
|
outputTokens: 0,
|
|
626
626
|
cacheReadTokens: 0,
|
|
@@ -644,7 +644,7 @@ describe("costsCommand", () => {
|
|
|
644
644
|
store.recordSession(
|
|
645
645
|
makeMetrics({
|
|
646
646
|
agentName: "builder-1",
|
|
647
|
-
|
|
647
|
+
taskId: "t1",
|
|
648
648
|
estimatedCostUsd: null,
|
|
649
649
|
}),
|
|
650
650
|
);
|
|
@@ -664,7 +664,7 @@ describe("costsCommand", () => {
|
|
|
664
664
|
store.recordSession(
|
|
665
665
|
makeMetrics({
|
|
666
666
|
agentName: "builder-1",
|
|
667
|
-
|
|
667
|
+
taskId: "t1",
|
|
668
668
|
cacheReadTokens: 8000,
|
|
669
669
|
cacheCreationTokens: 901,
|
|
670
670
|
}),
|
|
@@ -684,7 +684,7 @@ describe("costsCommand", () => {
|
|
|
684
684
|
store.recordSession(
|
|
685
685
|
makeMetrics({
|
|
686
686
|
agentName: "builder-1",
|
|
687
|
-
|
|
687
|
+
taskId: "t1",
|
|
688
688
|
inputTokens: 100,
|
|
689
689
|
outputTokens: 50,
|
|
690
690
|
estimatedCostUsd: 0.1,
|
|
@@ -693,7 +693,7 @@ describe("costsCommand", () => {
|
|
|
693
693
|
store.recordSession(
|
|
694
694
|
makeMetrics({
|
|
695
695
|
agentName: "scout-1",
|
|
696
|
-
|
|
696
|
+
taskId: "t2",
|
|
697
697
|
capability: "scout",
|
|
698
698
|
inputTokens: 200,
|
|
699
699
|
outputTokens: 100,
|
|
@@ -716,10 +716,10 @@ describe("costsCommand", () => {
|
|
|
716
716
|
const dbPath = join(tempDir, ".overstory", "metrics.db");
|
|
717
717
|
const store = createMetricsStore(dbPath);
|
|
718
718
|
store.recordSession(
|
|
719
|
-
makeMetrics({ agentName: "builder-1",
|
|
719
|
+
makeMetrics({ agentName: "builder-1", taskId: "t1", capability: "builder" }),
|
|
720
720
|
);
|
|
721
721
|
store.recordSession(
|
|
722
|
-
makeMetrics({ agentName: "builder-2",
|
|
722
|
+
makeMetrics({ agentName: "builder-2", taskId: "t2", capability: "builder" }),
|
|
723
723
|
);
|
|
724
724
|
store.close();
|
|
725
725
|
|
|
@@ -759,7 +759,7 @@ describe("costsCommand", () => {
|
|
|
759
759
|
capability: "builder",
|
|
760
760
|
worktreePath: "/tmp/wt1",
|
|
761
761
|
branchName: "feat/task1",
|
|
762
|
-
|
|
762
|
+
taskId: "task-001",
|
|
763
763
|
tmuxSession: "tmux-001",
|
|
764
764
|
state: "working",
|
|
765
765
|
pid: 12345,
|
|
@@ -815,7 +815,7 @@ describe("costsCommand", () => {
|
|
|
815
815
|
capability: "builder",
|
|
816
816
|
worktreePath: "/tmp/wt1",
|
|
817
817
|
branchName: "feat/task1",
|
|
818
|
-
|
|
818
|
+
taskId: "task-001",
|
|
819
819
|
tmuxSession: "tmux-001",
|
|
820
820
|
state: "working",
|
|
821
821
|
pid: 12345,
|
|
@@ -879,7 +879,7 @@ describe("costsCommand", () => {
|
|
|
879
879
|
capability: "builder",
|
|
880
880
|
worktreePath: "/tmp/wt1",
|
|
881
881
|
branchName: "feat/task1",
|
|
882
|
-
|
|
882
|
+
taskId: "task-001",
|
|
883
883
|
tmuxSession: "tmux-001",
|
|
884
884
|
state: "working",
|
|
885
885
|
pid: 12345,
|
|
@@ -897,7 +897,7 @@ describe("costsCommand", () => {
|
|
|
897
897
|
capability: "scout",
|
|
898
898
|
worktreePath: "/tmp/wt2",
|
|
899
899
|
branchName: "feat/task2",
|
|
900
|
-
|
|
900
|
+
taskId: "task-002",
|
|
901
901
|
tmuxSession: "tmux-002",
|
|
902
902
|
state: "working",
|
|
903
903
|
pid: 12346,
|
|
@@ -956,7 +956,7 @@ describe("costsCommand", () => {
|
|
|
956
956
|
capability: "builder",
|
|
957
957
|
worktreePath: "/tmp/wt1",
|
|
958
958
|
branchName: "feat/task1",
|
|
959
|
-
|
|
959
|
+
taskId: "task-001",
|
|
960
960
|
tmuxSession: "tmux-001",
|
|
961
961
|
state: "working",
|
|
962
962
|
pid: 12345,
|
package/src/commands/costs.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import { readdir, stat } from "node:fs/promises";
|
|
10
10
|
import { join } from "node:path";
|
|
11
|
+
import { Command } from "commander";
|
|
11
12
|
import { loadConfig } from "../config.ts";
|
|
12
13
|
import { ValidationError } from "../errors.ts";
|
|
13
14
|
import { color } from "../logging/color.ts";
|
|
@@ -16,21 +17,6 @@ import { estimateCost, parseTranscriptUsage } from "../metrics/transcript.ts";
|
|
|
16
17
|
import { openSessionStore } from "../sessions/compat.ts";
|
|
17
18
|
import type { SessionMetrics } from "../types.ts";
|
|
18
19
|
|
|
19
|
-
/**
|
|
20
|
-
* Parse a named flag value from args.
|
|
21
|
-
*/
|
|
22
|
-
function getFlag(args: string[], flag: string): string | undefined {
|
|
23
|
-
const idx = args.indexOf(flag);
|
|
24
|
-
if (idx === -1 || idx + 1 >= args.length) {
|
|
25
|
-
return undefined;
|
|
26
|
-
}
|
|
27
|
-
return args[idx + 1];
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function hasFlag(args: string[], flag: string): boolean {
|
|
31
|
-
return args.includes(flag);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
20
|
/** Format a number with thousands separators (e.g., 12345 -> "12,345"). */
|
|
35
21
|
function formatNumber(n: number): string {
|
|
36
22
|
return n.toLocaleString("en-US");
|
|
@@ -156,11 +142,11 @@ function printCostSummary(sessions: SessionMetrics[]): void {
|
|
|
156
142
|
const w = process.stdout.write.bind(process.stdout);
|
|
157
143
|
const separator = "\u2500".repeat(70);
|
|
158
144
|
|
|
159
|
-
w(`${color.bold
|
|
145
|
+
w(`${color.bold("Cost Summary")}\n`);
|
|
160
146
|
w(`${"=".repeat(70)}\n`);
|
|
161
147
|
|
|
162
148
|
if (sessions.length === 0) {
|
|
163
|
-
w(`${color.dim
|
|
149
|
+
w(`${color.dim("No session data found.")}\n`);
|
|
164
150
|
return;
|
|
165
151
|
}
|
|
166
152
|
|
|
@@ -169,7 +155,7 @@ function printCostSummary(sessions: SessionMetrics[]): void {
|
|
|
169
155
|
`${padLeft("Input", 10)}${padLeft("Output", 10)}` +
|
|
170
156
|
`${padLeft("Cache", 10)}${padLeft("Cost", 10)}\n`,
|
|
171
157
|
);
|
|
172
|
-
w(`${color.dim
|
|
158
|
+
w(`${color.dim(separator)}\n`);
|
|
173
159
|
|
|
174
160
|
for (const s of sessions) {
|
|
175
161
|
const cacheTotal = s.cacheReadTokens + s.cacheCreationTokens;
|
|
@@ -183,13 +169,17 @@ function printCostSummary(sessions: SessionMetrics[]): void {
|
|
|
183
169
|
}
|
|
184
170
|
|
|
185
171
|
const totals = computeTotals(sessions);
|
|
186
|
-
w(`${color.dim
|
|
172
|
+
w(`${color.dim(separator)}\n`);
|
|
187
173
|
w(
|
|
188
|
-
`${color.green
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
174
|
+
`${color.green(
|
|
175
|
+
color.bold(
|
|
176
|
+
padRight("Total", 31) +
|
|
177
|
+
padLeft(formatNumber(totals.inputTokens), 10) +
|
|
178
|
+
padLeft(formatNumber(totals.outputTokens), 10) +
|
|
179
|
+
padLeft(formatNumber(totals.cacheTokens), 10) +
|
|
180
|
+
padLeft(formatCost(totals.costUsd), 10),
|
|
181
|
+
),
|
|
182
|
+
)}\n`,
|
|
193
183
|
);
|
|
194
184
|
}
|
|
195
185
|
|
|
@@ -198,11 +188,11 @@ function printByCapability(sessions: SessionMetrics[]): void {
|
|
|
198
188
|
const w = process.stdout.write.bind(process.stdout);
|
|
199
189
|
const separator = "\u2500".repeat(70);
|
|
200
190
|
|
|
201
|
-
w(`${color.bold
|
|
191
|
+
w(`${color.bold("Cost by Capability")}\n`);
|
|
202
192
|
w(`${"=".repeat(70)}\n`);
|
|
203
193
|
|
|
204
194
|
if (sessions.length === 0) {
|
|
205
|
-
w(`${color.dim
|
|
195
|
+
w(`${color.dim("No session data found.")}\n`);
|
|
206
196
|
return;
|
|
207
197
|
}
|
|
208
198
|
|
|
@@ -211,7 +201,7 @@ function printByCapability(sessions: SessionMetrics[]): void {
|
|
|
211
201
|
`${padLeft("Input", 10)}${padLeft("Output", 10)}` +
|
|
212
202
|
`${padLeft("Cache", 10)}${padLeft("Cost", 10)}\n`,
|
|
213
203
|
);
|
|
214
|
-
w(`${color.dim
|
|
204
|
+
w(`${color.dim(separator)}\n`);
|
|
215
205
|
|
|
216
206
|
const groups = groupByCapability(sessions);
|
|
217
207
|
|
|
@@ -227,47 +217,39 @@ function printByCapability(sessions: SessionMetrics[]): void {
|
|
|
227
217
|
}
|
|
228
218
|
|
|
229
219
|
const totals = computeTotals(sessions);
|
|
230
|
-
w(`${color.dim
|
|
220
|
+
w(`${color.dim(separator)}\n`);
|
|
231
221
|
w(
|
|
232
|
-
`${color.green
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
222
|
+
`${color.green(
|
|
223
|
+
color.bold(
|
|
224
|
+
padRight("Total", 14) +
|
|
225
|
+
padLeft(formatNumber(sessions.length), 10) +
|
|
226
|
+
padLeft(formatNumber(totals.inputTokens), 10) +
|
|
227
|
+
padLeft(formatNumber(totals.outputTokens), 10) +
|
|
228
|
+
padLeft(formatNumber(totals.cacheTokens), 10) +
|
|
229
|
+
padLeft(formatCost(totals.costUsd), 10),
|
|
230
|
+
),
|
|
231
|
+
)}\n`,
|
|
238
232
|
);
|
|
239
233
|
}
|
|
240
234
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
--by-capability Group results by capability with subtotals
|
|
251
|
-
--last <n> Number of recent sessions (default: 20)
|
|
252
|
-
--json Output as JSON
|
|
253
|
-
--help, -h Show this help`;
|
|
254
|
-
|
|
255
|
-
/**
|
|
256
|
-
* Entry point for `overstory costs [--agent <name>] [--run <id>] [--by-capability] [--last <n>] [--self] [--json]`.
|
|
257
|
-
*/
|
|
258
|
-
export async function costsCommand(args: string[]): Promise<void> {
|
|
259
|
-
if (args.includes("--help") || args.includes("-h")) {
|
|
260
|
-
process.stdout.write(`${COSTS_HELP}\n`);
|
|
261
|
-
return;
|
|
262
|
-
}
|
|
235
|
+
interface CostsOpts {
|
|
236
|
+
live?: boolean;
|
|
237
|
+
self?: boolean;
|
|
238
|
+
byCapability?: boolean;
|
|
239
|
+
agent?: string;
|
|
240
|
+
run?: string;
|
|
241
|
+
last?: string;
|
|
242
|
+
json?: boolean;
|
|
243
|
+
}
|
|
263
244
|
|
|
264
|
-
|
|
265
|
-
const
|
|
266
|
-
const
|
|
267
|
-
const
|
|
268
|
-
const
|
|
269
|
-
const
|
|
270
|
-
const
|
|
245
|
+
async function executeCosts(opts: CostsOpts): Promise<void> {
|
|
246
|
+
const json = opts.json ?? false;
|
|
247
|
+
const live = opts.live ?? false;
|
|
248
|
+
const self = opts.self ?? false;
|
|
249
|
+
const byCapability = opts.byCapability ?? false;
|
|
250
|
+
const agentName = opts.agent;
|
|
251
|
+
const runId = opts.run;
|
|
252
|
+
const lastStr = opts.last;
|
|
271
253
|
|
|
272
254
|
if (lastStr !== undefined) {
|
|
273
255
|
const parsed = Number.parseInt(lastStr, 10);
|
|
@@ -323,17 +305,17 @@ export async function costsCommand(args: string[]): Promise<void> {
|
|
|
323
305
|
const w = process.stdout.write.bind(process.stdout);
|
|
324
306
|
const separator = "\u2500".repeat(70);
|
|
325
307
|
|
|
326
|
-
w(`${color.bold
|
|
308
|
+
w(`${color.bold("Orchestrator Session Cost")}\n`);
|
|
327
309
|
w(`${"=".repeat(70)}\n`);
|
|
328
310
|
w(`${padRight("Model:", 12)}${usage.modelUsed ?? "unknown"}\n`);
|
|
329
311
|
w(`${padRight("Transcript:", 12)}${transcriptPath}\n`);
|
|
330
|
-
w(`${color.dim
|
|
312
|
+
w(`${color.dim(separator)}\n`);
|
|
331
313
|
w(`${padRight("Input tokens:", 22)}${padLeft(formatNumber(usage.inputTokens), 12)}\n`);
|
|
332
314
|
w(`${padRight("Output tokens:", 22)}${padLeft(formatNumber(usage.outputTokens), 12)}\n`);
|
|
333
315
|
w(`${padRight("Cache tokens:", 22)}${padLeft(formatNumber(cacheTotal), 12)}\n`);
|
|
334
|
-
w(`${color.dim
|
|
316
|
+
w(`${color.dim(separator)}\n`);
|
|
335
317
|
w(
|
|
336
|
-
`${color.green
|
|
318
|
+
`${color.green(color.bold(padRight("Estimated cost:", 22) + padLeft(formatCost(cost), 12)))}\n`,
|
|
337
319
|
);
|
|
338
320
|
}
|
|
339
321
|
return;
|
|
@@ -463,14 +445,14 @@ export async function costsCommand(args: string[]): Promise<void> {
|
|
|
463
445
|
const w = process.stdout.write.bind(process.stdout);
|
|
464
446
|
const separator = "\u2500".repeat(70);
|
|
465
447
|
|
|
466
|
-
w(`${color.bold
|
|
448
|
+
w(`${color.bold(`Live Token Usage (${agentData.length} active agents)`)}\n`);
|
|
467
449
|
w(`${"=".repeat(70)}\n`);
|
|
468
450
|
w(
|
|
469
451
|
`${padRight("Agent", 19)}${padRight("Capability", 12)}` +
|
|
470
452
|
`${padLeft("Input", 10)}${padLeft("Output", 10)}` +
|
|
471
453
|
`${padLeft("Cache", 10)}${padLeft("Cost", 10)}\n`,
|
|
472
454
|
);
|
|
473
|
-
w(`${color.dim
|
|
455
|
+
w(`${color.dim(separator)}\n`);
|
|
474
456
|
|
|
475
457
|
for (const agent of agentData) {
|
|
476
458
|
const cacheTotal = agent.cacheReadTokens + agent.cacheCreationTokens;
|
|
@@ -483,13 +465,17 @@ export async function costsCommand(args: string[]): Promise<void> {
|
|
|
483
465
|
);
|
|
484
466
|
}
|
|
485
467
|
|
|
486
|
-
w(`${color.dim
|
|
468
|
+
w(`${color.dim(separator)}\n`);
|
|
487
469
|
w(
|
|
488
|
-
`${color.green
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
470
|
+
`${color.green(
|
|
471
|
+
color.bold(
|
|
472
|
+
padRight("Total", 31) +
|
|
473
|
+
padLeft(formatNumber(totalInput), 10) +
|
|
474
|
+
padLeft(formatNumber(totalOutput), 10) +
|
|
475
|
+
padLeft(formatNumber(totalCacheTokens), 10) +
|
|
476
|
+
padLeft(formatCost(totalCost), 10),
|
|
477
|
+
),
|
|
478
|
+
)}\n\n`,
|
|
493
479
|
);
|
|
494
480
|
|
|
495
481
|
// Format elapsed time
|
|
@@ -562,3 +548,38 @@ export async function costsCommand(args: string[]): Promise<void> {
|
|
|
562
548
|
metricsStore.close();
|
|
563
549
|
}
|
|
564
550
|
}
|
|
551
|
+
|
|
552
|
+
export function createCostsCommand(): Command {
|
|
553
|
+
return new Command("costs")
|
|
554
|
+
.description("Token/cost analysis and breakdown")
|
|
555
|
+
.option("--live", "Show real-time token usage for active agents")
|
|
556
|
+
.option("--self", "Show cost for the current orchestrator session")
|
|
557
|
+
.option("--agent <name>", "Filter by agent name")
|
|
558
|
+
.option("--run <id>", "Filter by run ID")
|
|
559
|
+
.option("--by-capability", "Group results by capability with subtotals")
|
|
560
|
+
.option("--last <n>", "Number of recent sessions (default: 20)")
|
|
561
|
+
.option("--json", "Output as JSON")
|
|
562
|
+
.action(async (opts: CostsOpts) => {
|
|
563
|
+
await executeCosts(opts);
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
export async function costsCommand(args: string[]): Promise<void> {
|
|
568
|
+
const cmd = createCostsCommand();
|
|
569
|
+
cmd.exitOverride();
|
|
570
|
+
try {
|
|
571
|
+
await cmd.parseAsync(args, { from: "user" });
|
|
572
|
+
} catch (err: unknown) {
|
|
573
|
+
if (err && typeof err === "object" && "code" in err) {
|
|
574
|
+
const code = (err as { code: string }).code;
|
|
575
|
+
if (code === "commander.helpDisplayed" || code === "commander.version") {
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
if (code.startsWith("commander.")) {
|
|
579
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
580
|
+
throw new ValidationError(message, { field: "args" });
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
throw err;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
@@ -51,7 +51,7 @@ describe("dashboardCommand", () => {
|
|
|
51
51
|
await dashboardCommand(["--help"]);
|
|
52
52
|
const out = output();
|
|
53
53
|
|
|
54
|
-
expect(out).toContain("
|
|
54
|
+
expect(out).toContain("dashboard");
|
|
55
55
|
expect(out).toContain("--interval");
|
|
56
56
|
expect(out).toContain("Ctrl+C");
|
|
57
57
|
});
|
|
@@ -60,7 +60,7 @@ describe("dashboardCommand", () => {
|
|
|
60
60
|
await dashboardCommand(["-h"]);
|
|
61
61
|
const out = output();
|
|
62
62
|
|
|
63
|
-
expect(out).toContain("
|
|
63
|
+
expect(out).toContain("dashboard");
|
|
64
64
|
expect(out).toContain("--interval");
|
|
65
65
|
expect(out).toContain("Ctrl+C");
|
|
66
66
|
});
|