@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.
Files changed (110) hide show
  1. package/README.md +8 -7
  2. package/package.json +12 -4
  3. package/src/agents/checkpoint.test.ts +2 -2
  4. package/src/agents/hooks-deployer.test.ts +131 -16
  5. package/src/agents/hooks-deployer.ts +33 -1
  6. package/src/agents/identity.test.ts +27 -27
  7. package/src/agents/identity.ts +10 -10
  8. package/src/agents/lifecycle.test.ts +6 -6
  9. package/src/agents/lifecycle.ts +2 -2
  10. package/src/agents/manifest.test.ts +86 -0
  11. package/src/agents/overlay.test.ts +9 -9
  12. package/src/agents/overlay.ts +4 -4
  13. package/src/commands/agents.test.ts +8 -8
  14. package/src/commands/agents.ts +62 -91
  15. package/src/commands/clean.test.ts +36 -51
  16. package/src/commands/clean.ts +28 -49
  17. package/src/commands/completions.ts +14 -0
  18. package/src/commands/coordinator.test.ts +133 -26
  19. package/src/commands/coordinator.ts +101 -64
  20. package/src/commands/costs.test.ts +47 -47
  21. package/src/commands/costs.ts +96 -75
  22. package/src/commands/dashboard.test.ts +2 -2
  23. package/src/commands/dashboard.ts +75 -95
  24. package/src/commands/doctor.test.ts +2 -2
  25. package/src/commands/doctor.ts +92 -79
  26. package/src/commands/errors.test.ts +2 -2
  27. package/src/commands/errors.ts +56 -50
  28. package/src/commands/feed.test.ts +2 -2
  29. package/src/commands/feed.ts +86 -83
  30. package/src/commands/group.ts +167 -177
  31. package/src/commands/hooks.test.ts +2 -2
  32. package/src/commands/hooks.ts +52 -42
  33. package/src/commands/init.test.ts +19 -19
  34. package/src/commands/init.ts +7 -16
  35. package/src/commands/inspect.test.ts +18 -18
  36. package/src/commands/inspect.ts +55 -58
  37. package/src/commands/log.test.ts +26 -31
  38. package/src/commands/log.ts +97 -91
  39. package/src/commands/logs.test.ts +1 -1
  40. package/src/commands/logs.ts +101 -104
  41. package/src/commands/mail.test.ts +5 -5
  42. package/src/commands/mail.ts +157 -169
  43. package/src/commands/merge.test.ts +28 -66
  44. package/src/commands/merge.ts +21 -51
  45. package/src/commands/metrics.test.ts +8 -8
  46. package/src/commands/metrics.ts +34 -35
  47. package/src/commands/monitor.test.ts +3 -3
  48. package/src/commands/monitor.ts +57 -62
  49. package/src/commands/nudge.test.ts +1 -1
  50. package/src/commands/nudge.ts +41 -89
  51. package/src/commands/prime.test.ts +19 -51
  52. package/src/commands/prime.ts +13 -50
  53. package/src/commands/replay.test.ts +2 -2
  54. package/src/commands/replay.ts +79 -86
  55. package/src/commands/run.test.ts +1 -1
  56. package/src/commands/run.ts +97 -77
  57. package/src/commands/sling.test.ts +201 -5
  58. package/src/commands/sling.ts +37 -64
  59. package/src/commands/spec.test.ts +14 -40
  60. package/src/commands/spec.ts +32 -101
  61. package/src/commands/status.test.ts +97 -1
  62. package/src/commands/status.ts +63 -58
  63. package/src/commands/stop.test.ts +22 -40
  64. package/src/commands/stop.ts +18 -33
  65. package/src/commands/supervisor.test.ts +12 -14
  66. package/src/commands/supervisor.ts +144 -165
  67. package/src/commands/trace.test.ts +15 -15
  68. package/src/commands/trace.ts +59 -82
  69. package/src/commands/watch.test.ts +2 -2
  70. package/src/commands/watch.ts +38 -45
  71. package/src/commands/worktree.test.ts +213 -37
  72. package/src/commands/worktree.ts +110 -55
  73. package/src/config.test.ts +96 -0
  74. package/src/doctor/consistency.test.ts +14 -14
  75. package/src/doctor/databases.test.ts +22 -2
  76. package/src/doctor/databases.ts +16 -0
  77. package/src/doctor/dependencies.test.ts +55 -1
  78. package/src/doctor/dependencies.ts +113 -18
  79. package/src/doctor/merge-queue.test.ts +4 -4
  80. package/src/e2e/init-sling-lifecycle.test.ts +8 -8
  81. package/src/errors.ts +1 -1
  82. package/src/index.ts +223 -213
  83. package/src/logging/color.test.ts +74 -91
  84. package/src/logging/color.ts +52 -46
  85. package/src/logging/reporter.test.ts +10 -10
  86. package/src/logging/reporter.ts +6 -5
  87. package/src/mail/broadcast.test.ts +1 -1
  88. package/src/mail/client.test.ts +6 -6
  89. package/src/mail/store.test.ts +3 -3
  90. package/src/merge/queue.test.ts +73 -7
  91. package/src/merge/queue.ts +17 -2
  92. package/src/merge/resolver.test.ts +159 -7
  93. package/src/merge/resolver.ts +46 -2
  94. package/src/metrics/store.test.ts +44 -44
  95. package/src/metrics/store.ts +2 -2
  96. package/src/metrics/summary.test.ts +35 -35
  97. package/src/mulch/client.test.ts +1 -1
  98. package/src/schema-consistency.test.ts +239 -0
  99. package/src/sessions/compat.test.ts +3 -3
  100. package/src/sessions/compat.ts +2 -2
  101. package/src/sessions/store.test.ts +41 -4
  102. package/src/sessions/store.ts +13 -2
  103. package/src/types.ts +14 -14
  104. package/src/watchdog/daemon.test.ts +10 -10
  105. package/src/watchdog/daemon.ts +1 -1
  106. package/src/watchdog/health.test.ts +1 -1
  107. package/src/worktree/manager.test.ts +20 -20
  108. package/src/worktree/manager.ts +120 -4
  109. package/src/worktree/tmux.test.ts +98 -9
  110. package/src/worktree/tmux.ts +18 -0
@@ -10,26 +10,13 @@
10
10
 
11
11
  import { readdir, stat } from "node:fs/promises";
12
12
  import { join } from "node:path";
13
+ import { Command } from "commander";
13
14
  import { loadConfig } from "../config.ts";
14
15
  import { ValidationError } from "../errors.ts";
16
+ import type { ColorFn } from "../logging/color.ts";
15
17
  import { color } from "../logging/color.ts";
16
18
  import type { LogEvent } from "../types.ts";
17
19
 
18
- /**
19
- * Parse a named flag value from args.
20
- */
21
- function getFlag(args: string[], flag: string): string | undefined {
22
- const idx = args.indexOf(flag);
23
- if (idx === -1 || idx + 1 >= args.length) {
24
- return undefined;
25
- }
26
- return args[idx + 1];
27
- }
28
-
29
- function hasFlag(args: string[], flag: string): boolean {
30
- return args.includes(flag);
31
- }
32
-
33
20
  /**
34
21
  * Parse relative time formats like "1h", "30m", "2d", "10s" into a Date object.
35
22
  * Falls back to parsing as ISO 8601 if not in relative format.
@@ -247,21 +234,53 @@ function filterEvents(
247
234
  });
248
235
  }
249
236
 
237
+ /** Resolve a log level string to a color function. */
238
+ function getLevelColor(level: string): ColorFn {
239
+ switch (level) {
240
+ case "debug":
241
+ return color.gray;
242
+ case "info":
243
+ return color.blue;
244
+ case "warn":
245
+ return color.yellow;
246
+ case "error":
247
+ return color.red;
248
+ default:
249
+ return color.gray;
250
+ }
251
+ }
252
+
253
+ /** Resolve a log level to its label string. */
254
+ function getLevelLabel(level: string): string {
255
+ switch (level) {
256
+ case "debug":
257
+ return "DBG";
258
+ case "info":
259
+ return "INF";
260
+ case "warn":
261
+ return "WRN";
262
+ case "error":
263
+ return "ERR";
264
+ default:
265
+ return String(level).slice(0, 3).toUpperCase();
266
+ }
267
+ }
268
+
250
269
  /**
251
270
  * Print log events with ANSI colors and date separators.
252
271
  */
253
272
  function printLogs(events: LogEvent[]): void {
254
273
  const w = process.stdout.write.bind(process.stdout);
255
274
 
256
- w(`${color.bold}Logs${color.reset}\n`);
275
+ w(`${color.bold("Logs")}\n`);
257
276
  w(`${"=".repeat(70)}\n`);
258
277
 
259
278
  if (events.length === 0) {
260
- w(`${color.dim}No log files found.${color.reset}\n`);
279
+ w(`${color.dim("No log files found.")}\n`);
261
280
  return;
262
281
  }
263
282
 
264
- w(`${color.dim}${events.length} ${events.length === 1 ? "entry" : "entries"}${color.reset}\n\n`);
283
+ w(`${color.dim(`${events.length} ${events.length === 1 ? "entry" : "entries"}`)}\n\n`);
265
284
 
266
285
  let lastDate = "";
267
286
 
@@ -272,44 +291,21 @@ function printLogs(events: LogEvent[]): void {
272
291
  if (lastDate !== "") {
273
292
  w("\n");
274
293
  }
275
- w(`${color.dim}--- ${date} ---${color.reset}\n`);
294
+ w(`${color.dim(`--- ${date} ---`)}\n`);
276
295
  lastDate = date;
277
296
  }
278
297
 
279
298
  const time = formatAbsoluteTime(event.timestamp);
280
-
281
- // Format level display
282
- let levelStr: string;
283
- let levelColorCode: string;
284
- switch (event.level) {
285
- case "debug":
286
- levelStr = "DBG";
287
- levelColorCode = color.gray;
288
- break;
289
- case "info":
290
- levelStr = "INF";
291
- levelColorCode = color.blue;
292
- break;
293
- case "warn":
294
- levelStr = "WRN";
295
- levelColorCode = color.yellow;
296
- break;
297
- case "error":
298
- levelStr = "ERR";
299
- levelColorCode = color.red;
300
- break;
301
- default:
302
- levelStr = String(event.level).slice(0, 3).toUpperCase();
303
- levelColorCode = color.gray;
304
- }
299
+ const levelColorFn = getLevelColor(event.level);
300
+ const levelStr = getLevelLabel(event.level);
305
301
 
306
302
  const agentLabel = event.agentName ? `[${event.agentName}]` : "[unknown]";
307
303
  const detail = buildLogDetail(event);
308
- const detailSuffix = detail ? ` ${color.dim}${detail}${color.reset}` : "";
304
+ const detailSuffix = detail ? ` ${color.dim(detail)}` : "";
309
305
 
310
306
  w(
311
- `${time} ${levelColorCode}${levelStr}${color.reset} ` +
312
- `${event.event} ${color.dim}${agentLabel}${color.reset}${detailSuffix}\n`,
307
+ `${time} ${levelColorFn(levelStr)} ` +
308
+ `${event.event} ${color.dim(agentLabel)}${detailSuffix}\n`,
313
309
  );
314
310
  }
315
311
  }
@@ -326,7 +322,7 @@ async function followLogs(
326
322
  ): Promise<void> {
327
323
  const w = process.stdout.write.bind(process.stdout);
328
324
 
329
- w(`${color.bold}Following logs (Ctrl+C to stop)${color.reset}\n\n`);
325
+ w(`${color.bold("Following logs (Ctrl+C to stop)")}\n\n`);
330
326
 
331
327
  // Track file positions for tailing
332
328
  const filePositions = new Map<string, number>();
@@ -376,38 +372,16 @@ async function followLogs(
376
372
 
377
373
  // Print immediately
378
374
  const time = formatAbsoluteTime(event.timestamp);
379
-
380
- let levelStr: string;
381
- let levelColorCode: string;
382
- switch (event.level) {
383
- case "debug":
384
- levelStr = "DBG";
385
- levelColorCode = color.gray;
386
- break;
387
- case "info":
388
- levelStr = "INF";
389
- levelColorCode = color.blue;
390
- break;
391
- case "warn":
392
- levelStr = "WRN";
393
- levelColorCode = color.yellow;
394
- break;
395
- case "error":
396
- levelStr = "ERR";
397
- levelColorCode = color.red;
398
- break;
399
- default:
400
- levelStr = String(event.level).slice(0, 3).toUpperCase();
401
- levelColorCode = color.gray;
402
- }
375
+ const levelColorFn = getLevelColor(event.level);
376
+ const levelStr = getLevelLabel(event.level);
403
377
 
404
378
  const agentLabel = event.agentName ? `[${event.agentName}]` : "[unknown]";
405
379
  const detail = buildLogDetail(event);
406
- const detailSuffix = detail ? ` ${color.dim}${detail}${color.reset}` : "";
380
+ const detailSuffix = detail ? ` ${color.dim(detail)}` : "";
407
381
 
408
382
  w(
409
- `${time} ${levelColorCode}${levelStr}${color.reset} ` +
410
- `${event.event} ${color.dim}${agentLabel}${color.reset}${detailSuffix}\n`,
383
+ `${time} ${levelColorFn(levelStr)} ` +
384
+ `${event.event} ${color.dim(agentLabel)}${detailSuffix}\n`,
411
385
  );
412
386
  }
413
387
  } catch {
@@ -427,36 +401,24 @@ async function followLogs(
427
401
  }
428
402
  }
429
403
 
430
- const LOGS_HELP = `overstory logs -- Query NDJSON log files from .overstory/logs
431
-
432
- Usage: overstory logs [options]
433
-
434
- Options:
435
- --agent <name> Filter logs by agent name
436
- --level <level> Filter by log level: debug, info, warn, error
437
- --since <time> Start time filter (ISO 8601 or relative: 1h, 30m, 2d, 10s)
438
- --until <time> End time filter (ISO 8601)
439
- --limit <n> Max entries to show (default: 100, returns most recent)
440
- --follow Tail logs in real time (poll every 1s, Ctrl+C to stop)
441
- --json Output as JSON array of LogEvent objects
442
- --help, -h Show this help`;
443
-
444
- /**
445
- * Entry point for `overstory logs` command.
446
- */
447
- export async function logsCommand(args: string[]): Promise<void> {
448
- if (args.includes("--help") || args.includes("-h")) {
449
- process.stdout.write(`${LOGS_HELP}\n`);
450
- return;
451
- }
404
+ interface LogsOpts {
405
+ agent?: string;
406
+ level?: string;
407
+ since?: string;
408
+ until?: string;
409
+ limit?: string;
410
+ follow?: boolean;
411
+ json?: boolean;
412
+ }
452
413
 
453
- const json = hasFlag(args, "--json");
454
- const follow = hasFlag(args, "--follow");
455
- const agentName = getFlag(args, "--agent");
456
- const level = getFlag(args, "--level");
457
- const sinceStr = getFlag(args, "--since");
458
- const untilStr = getFlag(args, "--until");
459
- const limitStr = getFlag(args, "--limit");
414
+ async function executeLogs(opts: LogsOpts): Promise<void> {
415
+ const json = opts.json ?? false;
416
+ const follow = opts.follow ?? false;
417
+ const agentName = opts.agent;
418
+ const level = opts.level;
419
+ const sinceStr = opts.since;
420
+ const untilStr = opts.until;
421
+ const limitStr = opts.limit;
460
422
  const limit = limitStr ? Number.parseInt(limitStr, 10) : 100;
461
423
 
462
424
  if (Number.isNaN(limit) || limit < 1) {
@@ -544,3 +506,38 @@ export async function logsCommand(args: string[]): Promise<void> {
544
506
 
545
507
  printLogs(limited);
546
508
  }
509
+
510
+ export function createLogsCommand(): Command {
511
+ return new Command("logs")
512
+ .description("Query NDJSON logs across agents")
513
+ .option("--agent <name>", "Filter logs by agent name")
514
+ .option("--level <level>", "Filter by log level: debug, info, warn, error")
515
+ .option("--since <time>", "Start time filter (ISO 8601 or relative: 1h, 30m, 2d, 10s)")
516
+ .option("--until <time>", "End time filter (ISO 8601)")
517
+ .option("--limit <n>", "Max entries to show (default: 100, returns most recent)")
518
+ .option("--follow", "Tail logs in real time (poll every 1s, Ctrl+C to stop)")
519
+ .option("--json", "Output as JSON array of LogEvent objects")
520
+ .action(async (opts: LogsOpts) => {
521
+ await executeLogs(opts);
522
+ });
523
+ }
524
+
525
+ export async function logsCommand(args: string[]): Promise<void> {
526
+ const cmd = createLogsCommand();
527
+ cmd.exitOverride();
528
+ try {
529
+ await cmd.parseAsync(args, { from: "user" });
530
+ } catch (err: unknown) {
531
+ if (err && typeof err === "object" && "code" in err) {
532
+ const code = (err as { code: string }).code;
533
+ if (code === "commander.helpDisplayed" || code === "commander.version") {
534
+ return;
535
+ }
536
+ if (code.startsWith("commander.")) {
537
+ const message = err instanceof Error ? err.message : String(err);
538
+ throw new ValidationError(message, { field: "args" });
539
+ }
540
+ }
541
+ throw err;
542
+ }
543
+ }
@@ -761,7 +761,7 @@ describe("mailCommand", () => {
761
761
  capability: "coordinator",
762
762
  worktreePath: "/worktrees/orchestrator",
763
763
  branchName: "main",
764
- beadId: "bead-001",
764
+ taskId: "bead-001",
765
765
  tmuxSession: "overstory-test-orchestrator",
766
766
  state: "working" as const,
767
767
  pid: 12345,
@@ -779,7 +779,7 @@ describe("mailCommand", () => {
779
779
  capability: "builder",
780
780
  worktreePath: "/worktrees/builder-1",
781
781
  branchName: "builder-1",
782
- beadId: "bead-002",
782
+ taskId: "bead-002",
783
783
  tmuxSession: "overstory-test-builder-1",
784
784
  state: "working" as const,
785
785
  pid: 12346,
@@ -797,7 +797,7 @@ describe("mailCommand", () => {
797
797
  capability: "builder",
798
798
  worktreePath: "/worktrees/builder-2",
799
799
  branchName: "builder-2",
800
- beadId: "bead-003",
800
+ taskId: "bead-003",
801
801
  tmuxSession: "overstory-test-builder-2",
802
802
  state: "working" as const,
803
803
  pid: 12347,
@@ -815,7 +815,7 @@ describe("mailCommand", () => {
815
815
  capability: "scout",
816
816
  worktreePath: "/worktrees/scout-1",
817
817
  branchName: "scout-1",
818
- beadId: "bead-004",
818
+ taskId: "bead-004",
819
819
  tmuxSession: "overstory-test-scout-1",
820
820
  state: "working" as const,
821
821
  pid: 12348,
@@ -1135,7 +1135,7 @@ describe("mailCommand", () => {
1135
1135
  | "monitor",
1136
1136
  worktreePath: `/worktrees/${session.agentName}`,
1137
1137
  branchName: session.agentName,
1138
- beadId: `bead-${idx}`,
1138
+ taskId: `bead-${idx}`,
1139
1139
  tmuxSession: `overstory-test-${session.agentName}`,
1140
1140
  state: "working" as const,
1141
1141
  pid: 10000 + idx,