@os-eco/overstory-cli 0.6.1 → 0.6.4
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 +7 -6
- package/package.json +12 -4
- package/src/agents/hooks-deployer.test.ts +94 -16
- package/src/agents/hooks-deployer.ts +18 -0
- package/src/agents/manifest.test.ts +86 -0
- package/src/commands/agents.test.ts +3 -3
- package/src/commands/agents.ts +59 -88
- package/src/commands/clean.test.ts +31 -46
- package/src/commands/clean.ts +28 -49
- package/src/commands/completions.ts +14 -0
- package/src/commands/coordinator.test.ts +131 -24
- package/src/commands/coordinator.ts +100 -63
- package/src/commands/costs.test.ts +2 -2
- package/src/commands/costs.ts +96 -75
- package/src/commands/dashboard.test.ts +2 -2
- package/src/commands/dashboard.ts +73 -93
- 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 +2 -2
- package/src/commands/inspect.ts +54 -57
- package/src/commands/log.test.ts +5 -10
- package/src/commands/log.ts +90 -84
- package/src/commands/logs.test.ts +1 -1
- package/src/commands/logs.ts +101 -104
- package/src/commands/mail.ts +157 -169
- package/src/commands/merge.test.ts +20 -58
- package/src/commands/merge.ts +13 -43
- package/src/commands/metrics.test.ts +2 -2
- package/src/commands/metrics.ts +33 -34
- package/src/commands/monitor.test.ts +3 -3
- package/src/commands/monitor.ts +56 -61
- package/src/commands/nudge.ts +41 -89
- package/src/commands/prime.test.ts +15 -47
- package/src/commands/prime.ts +7 -44
- package/src/commands/replay.test.ts +2 -2
- package/src/commands/replay.ts +79 -86
- package/src/commands/run.ts +97 -77
- package/src/commands/sling.test.ts +196 -0
- package/src/commands/sling.ts +24 -54
- package/src/commands/spec.test.ts +13 -39
- package/src/commands/spec.ts +30 -99
- package/src/commands/status.ts +46 -42
- package/src/commands/stop.test.ts +21 -39
- package/src/commands/stop.ts +18 -33
- package/src/commands/supervisor.test.ts +3 -5
- package/src/commands/supervisor.ts +136 -157
- package/src/commands/trace.test.ts +9 -9
- package/src/commands/trace.ts +54 -77
- package/src/commands/watch.test.ts +2 -2
- package/src/commands/watch.ts +38 -45
- package/src/commands/worktree.test.ts +8 -8
- package/src/commands/worktree.ts +63 -46
- package/src/config.test.ts +96 -0
- 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/e2e/init-sling-lifecycle.test.ts +6 -6
- 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/merge/queue.test.ts +66 -0
- package/src/merge/queue.ts +15 -0
- package/src/schema-consistency.test.ts +239 -0
- package/src/sessions/compat.ts +1 -1
- package/src/sessions/store.test.ts +37 -0
- package/src/sessions/store.ts +11 -0
- package/src/worktree/tmux.test.ts +98 -9
- package/src/worktree/tmux.ts +18 -0
|
@@ -66,41 +66,9 @@ describe("primeCommand", () => {
|
|
|
66
66
|
return stderrChunks.join("");
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
describe("Help", () => {
|
|
70
|
-
test("--help shows help text", async () => {
|
|
71
|
-
await primeCommand(["--help"]);
|
|
72
|
-
const out = output();
|
|
73
|
-
|
|
74
|
-
expect(out).toContain("overstory prime");
|
|
75
|
-
expect(out).toContain("--agent");
|
|
76
|
-
expect(out).toContain("--compact");
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
test("-h shows help text", async () => {
|
|
80
|
-
await primeCommand(["-h"]);
|
|
81
|
-
const out = output();
|
|
82
|
-
|
|
83
|
-
expect(out).toContain("overstory prime");
|
|
84
|
-
expect(out).toContain("--agent");
|
|
85
|
-
expect(out).toContain("--compact");
|
|
86
|
-
});
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
describe("parseArgs validation", () => {
|
|
90
|
-
test("--agent without a name throws AgentError", async () => {
|
|
91
|
-
await expect(primeCommand(["--agent"])).rejects.toThrow("--agent requires a name argument");
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
test("--agent followed by another flag throws AgentError", async () => {
|
|
95
|
-
await expect(primeCommand(["--agent", "--compact"])).rejects.toThrow(
|
|
96
|
-
"--agent requires a name argument",
|
|
97
|
-
);
|
|
98
|
-
});
|
|
99
|
-
});
|
|
100
|
-
|
|
101
69
|
describe("Orchestrator priming (no --agent flag)", () => {
|
|
102
70
|
test("default prime outputs project context", async () => {
|
|
103
|
-
await primeCommand(
|
|
71
|
+
await primeCommand({});
|
|
104
72
|
const out = output();
|
|
105
73
|
|
|
106
74
|
expect(out).toContain("# Overstory Context");
|
|
@@ -111,7 +79,7 @@ describe("primeCommand", () => {
|
|
|
111
79
|
});
|
|
112
80
|
|
|
113
81
|
test("includes agent manifest section", async () => {
|
|
114
|
-
await primeCommand(
|
|
82
|
+
await primeCommand({});
|
|
115
83
|
const out = output();
|
|
116
84
|
|
|
117
85
|
expect(out).toContain("## Agent Manifest");
|
|
@@ -120,7 +88,7 @@ describe("primeCommand", () => {
|
|
|
120
88
|
});
|
|
121
89
|
|
|
122
90
|
test("without metrics.db shows no recent sessions message", async () => {
|
|
123
|
-
await primeCommand(
|
|
91
|
+
await primeCommand({});
|
|
124
92
|
const out = output();
|
|
125
93
|
|
|
126
94
|
expect(out).toContain("## Recent Activity");
|
|
@@ -128,7 +96,7 @@ describe("primeCommand", () => {
|
|
|
128
96
|
});
|
|
129
97
|
|
|
130
98
|
test("--compact skips Recent Activity and Expertise sections", async () => {
|
|
131
|
-
await primeCommand(
|
|
99
|
+
await primeCommand({ compact: true });
|
|
132
100
|
const out = output();
|
|
133
101
|
|
|
134
102
|
// Should still have project basics
|
|
@@ -143,7 +111,7 @@ describe("primeCommand", () => {
|
|
|
143
111
|
|
|
144
112
|
describe("Agent priming (--agent <name>)", () => {
|
|
145
113
|
test("unknown agent outputs basic context and warns", async () => {
|
|
146
|
-
await primeCommand(
|
|
114
|
+
await primeCommand({ agent: "unknown-agent" });
|
|
147
115
|
const out = output();
|
|
148
116
|
const err = stderr();
|
|
149
117
|
|
|
@@ -172,7 +140,7 @@ recentTasks:
|
|
|
172
140
|
`,
|
|
173
141
|
);
|
|
174
142
|
|
|
175
|
-
await primeCommand(
|
|
143
|
+
await primeCommand({ agent: "my-builder" });
|
|
176
144
|
const out = output();
|
|
177
145
|
|
|
178
146
|
expect(out).toContain("# Agent Context: my-builder");
|
|
@@ -212,7 +180,7 @@ recentTasks:
|
|
|
212
180
|
`${JSON.stringify(sessions, null, 2)}\n`,
|
|
213
181
|
);
|
|
214
182
|
|
|
215
|
-
await primeCommand(
|
|
183
|
+
await primeCommand({ agent: "active-builder" });
|
|
216
184
|
const out = output();
|
|
217
185
|
|
|
218
186
|
expect(out).toContain("# Agent Context: active-builder");
|
|
@@ -249,7 +217,7 @@ recentTasks:
|
|
|
249
217
|
`${JSON.stringify(sessions, null, 2)}\n`,
|
|
250
218
|
);
|
|
251
219
|
|
|
252
|
-
await primeCommand(
|
|
220
|
+
await primeCommand({ agent: "completed-builder" });
|
|
253
221
|
const out = output();
|
|
254
222
|
|
|
255
223
|
expect(out).toContain("# Agent Context: completed-builder");
|
|
@@ -291,7 +259,7 @@ recentTasks: []
|
|
|
291
259
|
`,
|
|
292
260
|
);
|
|
293
261
|
|
|
294
|
-
await primeCommand(
|
|
262
|
+
await primeCommand({ agent: "recovery-agent", compact: true });
|
|
295
263
|
const out = output();
|
|
296
264
|
|
|
297
265
|
expect(out).toContain("# Agent Context: recovery-agent");
|
|
@@ -317,7 +285,7 @@ recentTasks: []
|
|
|
317
285
|
`,
|
|
318
286
|
);
|
|
319
287
|
|
|
320
|
-
await primeCommand(
|
|
288
|
+
await primeCommand({ agent: "compact-agent", compact: true });
|
|
321
289
|
const out = output();
|
|
322
290
|
|
|
323
291
|
expect(out).toContain("# Agent Context: compact-agent");
|
|
@@ -340,7 +308,7 @@ recentTasks: []
|
|
|
340
308
|
// Save and change cwd to the git repo
|
|
341
309
|
process.chdir(gitRepoDir);
|
|
342
310
|
|
|
343
|
-
await primeCommand(
|
|
311
|
+
await primeCommand({});
|
|
344
312
|
const out = output();
|
|
345
313
|
|
|
346
314
|
expect(out).toContain("# Overstory Context");
|
|
@@ -375,7 +343,7 @@ recentTasks: []
|
|
|
375
343
|
|
|
376
344
|
process.chdir(gitRepoDir);
|
|
377
345
|
|
|
378
|
-
await primeCommand(
|
|
346
|
+
await primeCommand({});
|
|
379
347
|
const out = output();
|
|
380
348
|
|
|
381
349
|
expect(out).toContain("Session branch: feature/my-work (merge target)");
|
|
@@ -412,7 +380,7 @@ recentTasks: []
|
|
|
412
380
|
expect(existsBefore).toBe(false);
|
|
413
381
|
|
|
414
382
|
// Run primeCommand
|
|
415
|
-
await primeCommand(
|
|
383
|
+
await primeCommand({});
|
|
416
384
|
|
|
417
385
|
// Verify .gitignore was created with correct content
|
|
418
386
|
const content = await Bun.file(gitignorePath).text();
|
|
@@ -435,7 +403,7 @@ sessions.db
|
|
|
435
403
|
expect(contentBefore).toBe(staleContent);
|
|
436
404
|
|
|
437
405
|
// Run primeCommand
|
|
438
|
-
await primeCommand(
|
|
406
|
+
await primeCommand({});
|
|
439
407
|
|
|
440
408
|
// Verify .gitignore now has the wildcard+whitelist content
|
|
441
409
|
const contentAfter = await Bun.file(gitignorePath).text();
|
|
@@ -455,7 +423,7 @@ sessions.db
|
|
|
455
423
|
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
456
424
|
|
|
457
425
|
// Run primeCommand
|
|
458
|
-
await primeCommand(
|
|
426
|
+
await primeCommand({});
|
|
459
427
|
|
|
460
428
|
// Verify content is still correct
|
|
461
429
|
const contentAfter = await Bun.file(gitignorePath).text();
|
package/src/commands/prime.ts
CHANGED
|
@@ -12,7 +12,6 @@ import { loadCheckpoint } from "../agents/checkpoint.ts";
|
|
|
12
12
|
import { loadIdentity } from "../agents/identity.ts";
|
|
13
13
|
import { createManifestLoader } from "../agents/manifest.ts";
|
|
14
14
|
import { loadConfig } from "../config.ts";
|
|
15
|
-
import { AgentError } from "../errors.ts";
|
|
16
15
|
import { createMetricsStore } from "../metrics/store.ts";
|
|
17
16
|
import { createMulchClient } from "../mulch/client.ts";
|
|
18
17
|
import { openSessionStore } from "../sessions/compat.ts";
|
|
@@ -35,32 +34,9 @@ const OVERSTORY_GITIGNORE = `# Wildcard+whitelist: ignore everything, whitelist
|
|
|
35
34
|
!agent-defs/
|
|
36
35
|
`;
|
|
37
36
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
* Supports:
|
|
42
|
-
* - `--agent <name>` — Prime for a specific agent
|
|
43
|
-
* - `--compact` — Output reduced context
|
|
44
|
-
*/
|
|
45
|
-
function parseArgs(args: string[]): { agentName: string | null; compact: boolean } {
|
|
46
|
-
let agentName: string | null = null;
|
|
47
|
-
let compact = false;
|
|
48
|
-
|
|
49
|
-
for (let i = 0; i < args.length; i++) {
|
|
50
|
-
const arg = args[i];
|
|
51
|
-
if (arg === "--agent") {
|
|
52
|
-
const next = args[i + 1];
|
|
53
|
-
if (next === undefined || next.startsWith("--")) {
|
|
54
|
-
throw new AgentError("--agent requires a name argument");
|
|
55
|
-
}
|
|
56
|
-
agentName = next;
|
|
57
|
-
i++; // Skip the value
|
|
58
|
-
} else if (arg === "--compact") {
|
|
59
|
-
compact = true;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
return { agentName, compact };
|
|
37
|
+
export interface PrimeOptions {
|
|
38
|
+
agent?: string;
|
|
39
|
+
compact?: boolean;
|
|
64
40
|
}
|
|
65
41
|
|
|
66
42
|
/**
|
|
@@ -156,24 +132,11 @@ async function healGitignore(overstoryDir: string): Promise<void> {
|
|
|
156
132
|
* Gathers project state and outputs context to stdout for injection
|
|
157
133
|
* into Claude Code's context.
|
|
158
134
|
*
|
|
159
|
-
* @param
|
|
135
|
+
* @param opts - Command options
|
|
160
136
|
*/
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
Options:
|
|
166
|
-
--agent <name> Prime for a specific agent (default: orchestrator)
|
|
167
|
-
--compact Output reduced context (for PreCompact hook)
|
|
168
|
-
--help, -h Show this help`;
|
|
169
|
-
|
|
170
|
-
export async function primeCommand(args: string[]): Promise<void> {
|
|
171
|
-
if (args.includes("--help") || args.includes("-h")) {
|
|
172
|
-
process.stdout.write(`${PRIME_HELP}\n`);
|
|
173
|
-
return;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
const { agentName, compact } = parseArgs(args);
|
|
137
|
+
export async function primeCommand(opts: PrimeOptions): Promise<void> {
|
|
138
|
+
const agentName = opts.agent ?? null;
|
|
139
|
+
const compact = opts.compact ?? false;
|
|
177
140
|
|
|
178
141
|
// 1. Load config
|
|
179
142
|
const config = await loadConfig(process.cwd());
|
|
@@ -78,7 +78,7 @@ describe("replayCommand", () => {
|
|
|
78
78
|
await replayCommand(["--help"]);
|
|
79
79
|
const out = output();
|
|
80
80
|
|
|
81
|
-
expect(out).toContain("
|
|
81
|
+
expect(out).toContain("replay");
|
|
82
82
|
expect(out).toContain("--run");
|
|
83
83
|
expect(out).toContain("--agent");
|
|
84
84
|
expect(out).toContain("--json");
|
|
@@ -91,7 +91,7 @@ describe("replayCommand", () => {
|
|
|
91
91
|
await replayCommand(["-h"]);
|
|
92
92
|
const out = output();
|
|
93
93
|
|
|
94
|
-
expect(out).toContain("
|
|
94
|
+
expect(out).toContain("replay");
|
|
95
95
|
});
|
|
96
96
|
});
|
|
97
97
|
|
package/src/commands/replay.ts
CHANGED
|
@@ -7,14 +7,16 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { join } from "node:path";
|
|
10
|
+
import { Command } from "commander";
|
|
10
11
|
import { loadConfig } from "../config.ts";
|
|
11
12
|
import { ValidationError } from "../errors.ts";
|
|
12
13
|
import { createEventStore } from "../events/store.ts";
|
|
14
|
+
import type { ColorFn } from "../logging/color.ts";
|
|
13
15
|
import { color } from "../logging/color.ts";
|
|
14
16
|
import type { EventType, StoredEvent } from "../types.ts";
|
|
15
17
|
|
|
16
18
|
/** Labels and colors for each event type. */
|
|
17
|
-
const EVENT_LABELS: Record<EventType, { label: string; color:
|
|
19
|
+
const EVENT_LABELS: Record<EventType, { label: string; color: ColorFn }> = {
|
|
18
20
|
tool_start: { label: "TOOL START", color: color.blue },
|
|
19
21
|
tool_end: { label: "TOOL END ", color: color.blue },
|
|
20
22
|
session_start: { label: "SESSION +", color: color.green },
|
|
@@ -26,41 +28,14 @@ const EVENT_LABELS: Record<EventType, { label: string; color: string }> = {
|
|
|
26
28
|
custom: { label: "CUSTOM ", color: color.gray },
|
|
27
29
|
};
|
|
28
30
|
|
|
29
|
-
/**
|
|
30
|
-
const AGENT_COLORS
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
if (idx === -1 || idx + 1 >= args.length) {
|
|
38
|
-
return undefined;
|
|
39
|
-
}
|
|
40
|
-
return args[idx + 1];
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Parse all occurrences of a named flag from args.
|
|
45
|
-
* Returns an array of values (e.g., --agent a --agent b => ["a", "b"]).
|
|
46
|
-
*/
|
|
47
|
-
function getAllFlags(args: string[], flag: string): string[] {
|
|
48
|
-
const values: string[] = [];
|
|
49
|
-
for (let i = 0; i < args.length; i++) {
|
|
50
|
-
if (args[i] === flag && i + 1 < args.length) {
|
|
51
|
-
const value = args[i + 1];
|
|
52
|
-
if (value !== undefined) {
|
|
53
|
-
values.push(value);
|
|
54
|
-
}
|
|
55
|
-
i++; // skip the value
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
return values;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function hasFlag(args: string[], flag: string): boolean {
|
|
62
|
-
return args.includes(flag);
|
|
63
|
-
}
|
|
31
|
+
/** Color functions assigned to agents in order of first appearance. */
|
|
32
|
+
const AGENT_COLORS: readonly ColorFn[] = [
|
|
33
|
+
color.blue,
|
|
34
|
+
color.green,
|
|
35
|
+
color.yellow,
|
|
36
|
+
color.cyan,
|
|
37
|
+
color.magenta,
|
|
38
|
+
];
|
|
64
39
|
|
|
65
40
|
/**
|
|
66
41
|
* Format a relative time string from a timestamp.
|
|
@@ -150,16 +125,16 @@ function buildEventDetail(event: StoredEvent): string {
|
|
|
150
125
|
}
|
|
151
126
|
|
|
152
127
|
/**
|
|
153
|
-
* Assign a stable color to each agent based on order of first appearance.
|
|
128
|
+
* Assign a stable color function to each agent based on order of first appearance.
|
|
154
129
|
*/
|
|
155
|
-
function buildAgentColorMap(events: StoredEvent[]): Map<string,
|
|
156
|
-
const colorMap = new Map<string,
|
|
130
|
+
function buildAgentColorMap(events: StoredEvent[]): Map<string, ColorFn> {
|
|
131
|
+
const colorMap = new Map<string, ColorFn>();
|
|
157
132
|
for (const event of events) {
|
|
158
133
|
if (!colorMap.has(event.agentName)) {
|
|
159
134
|
const colorIndex = colorMap.size % AGENT_COLORS.length;
|
|
160
|
-
const
|
|
161
|
-
if (
|
|
162
|
-
colorMap.set(event.agentName,
|
|
135
|
+
const agentColorFn = AGENT_COLORS[colorIndex];
|
|
136
|
+
if (agentColorFn !== undefined) {
|
|
137
|
+
colorMap.set(event.agentName, agentColorFn);
|
|
163
138
|
}
|
|
164
139
|
}
|
|
165
140
|
}
|
|
@@ -172,15 +147,15 @@ function buildAgentColorMap(events: StoredEvent[]): Map<string, string> {
|
|
|
172
147
|
function printReplay(events: StoredEvent[], useAbsoluteTime: boolean): void {
|
|
173
148
|
const w = process.stdout.write.bind(process.stdout);
|
|
174
149
|
|
|
175
|
-
w(`${color.bold
|
|
150
|
+
w(`${color.bold("Replay")}\n`);
|
|
176
151
|
w(`${"=".repeat(70)}\n`);
|
|
177
152
|
|
|
178
153
|
if (events.length === 0) {
|
|
179
|
-
w(`${color.dim
|
|
154
|
+
w(`${color.dim("No events found.")}\n`);
|
|
180
155
|
return;
|
|
181
156
|
}
|
|
182
157
|
|
|
183
|
-
w(`${color.dim
|
|
158
|
+
w(`${color.dim(`${events.length} event${events.length === 1 ? "" : "s"}`)}\n\n`);
|
|
184
159
|
|
|
185
160
|
const colorMap = buildAgentColorMap(events);
|
|
186
161
|
let lastDate = "";
|
|
@@ -192,7 +167,7 @@ function printReplay(events: StoredEvent[], useAbsoluteTime: boolean): void {
|
|
|
192
167
|
if (lastDate !== "") {
|
|
193
168
|
w("\n");
|
|
194
169
|
}
|
|
195
|
-
w(`${color.dim
|
|
170
|
+
w(`${color.dim(`--- ${date} ---`)}\n`);
|
|
196
171
|
lastDate = date;
|
|
197
172
|
}
|
|
198
173
|
|
|
@@ -205,57 +180,40 @@ function printReplay(events: StoredEvent[], useAbsoluteTime: boolean): void {
|
|
|
205
180
|
color: color.gray,
|
|
206
181
|
};
|
|
207
182
|
|
|
208
|
-
const
|
|
209
|
-
event.level === "error" ? color.red : event.level === "warn" ? color.yellow :
|
|
210
|
-
const
|
|
183
|
+
const levelColorFn =
|
|
184
|
+
event.level === "error" ? color.red : event.level === "warn" ? color.yellow : null;
|
|
185
|
+
const applyLevel = (text: string) => (levelColorFn ? levelColorFn(text) : text);
|
|
211
186
|
|
|
212
187
|
const detail = buildEventDetail(event);
|
|
213
|
-
const detailSuffix = detail ? ` ${color.dim
|
|
188
|
+
const detailSuffix = detail ? ` ${color.dim(detail)}` : "";
|
|
214
189
|
|
|
215
|
-
const
|
|
216
|
-
const agentLabel = ` ${
|
|
190
|
+
const agentColorFn = colorMap.get(event.agentName) ?? color.gray;
|
|
191
|
+
const agentLabel = ` ${agentColorFn(`[${event.agentName}]`)}`;
|
|
217
192
|
|
|
218
193
|
w(
|
|
219
|
-
`${color.dim
|
|
220
|
-
`${
|
|
194
|
+
`${color.dim(timeStr.padStart(10))} ` +
|
|
195
|
+
`${applyLevel(eventInfo.color(color.bold(eventInfo.label)))}` +
|
|
221
196
|
`${agentLabel}${detailSuffix}\n`,
|
|
222
197
|
);
|
|
223
198
|
}
|
|
224
199
|
}
|
|
225
200
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
--until <timestamp> End time filter (ISO 8601)
|
|
235
|
-
--limit <n> Max events to show (default: 200)
|
|
236
|
-
--json Output as JSON array of StoredEvent objects
|
|
237
|
-
--help, -h Show this help
|
|
238
|
-
|
|
239
|
-
If --run is specified, shows all events from that run.
|
|
240
|
-
If --agent is specified, shows events from those agents merged chronologically.
|
|
241
|
-
If neither is specified, tries to read the current run from .overstory/current-run.txt.
|
|
242
|
-
Falls back to a 24-hour timeline of all events.`;
|
|
243
|
-
|
|
244
|
-
/**
|
|
245
|
-
* Entry point for `overstory replay [--run <id>] [--agent <name>...] [--json]`.
|
|
246
|
-
*/
|
|
247
|
-
export async function replayCommand(args: string[]): Promise<void> {
|
|
248
|
-
if (args.includes("--help") || args.includes("-h")) {
|
|
249
|
-
process.stdout.write(`${REPLAY_HELP}\n`);
|
|
250
|
-
return;
|
|
251
|
-
}
|
|
201
|
+
interface ReplayOpts {
|
|
202
|
+
run?: string;
|
|
203
|
+
agent: string[]; // repeatable
|
|
204
|
+
since?: string;
|
|
205
|
+
until?: string;
|
|
206
|
+
limit?: string;
|
|
207
|
+
json?: boolean;
|
|
208
|
+
}
|
|
252
209
|
|
|
253
|
-
|
|
254
|
-
const
|
|
255
|
-
const
|
|
256
|
-
const
|
|
257
|
-
const
|
|
258
|
-
const
|
|
210
|
+
async function executeReplay(opts: ReplayOpts): Promise<void> {
|
|
211
|
+
const json = opts.json ?? false;
|
|
212
|
+
const runId = opts.run;
|
|
213
|
+
const agentNames = opts.agent;
|
|
214
|
+
const sinceStr = opts.since;
|
|
215
|
+
const untilStr = opts.until;
|
|
216
|
+
const limitStr = opts.limit;
|
|
259
217
|
const limit = limitStr ? Number.parseInt(limitStr, 10) : 200;
|
|
260
218
|
|
|
261
219
|
if (Number.isNaN(limit) || limit < 1) {
|
|
@@ -358,3 +316,38 @@ export async function replayCommand(args: string[]): Promise<void> {
|
|
|
358
316
|
eventStore.close();
|
|
359
317
|
}
|
|
360
318
|
}
|
|
319
|
+
|
|
320
|
+
export function createReplayCommand(): Command {
|
|
321
|
+
return new Command("replay")
|
|
322
|
+
.description("Interleaved chronological replay across agents")
|
|
323
|
+
.option("--run <id>", "Filter events by run ID")
|
|
324
|
+
.option(
|
|
325
|
+
"--agent <name>",
|
|
326
|
+
"Filter by agent name (can appear multiple times)",
|
|
327
|
+
(val: string, prev: string[]) => [...prev, val],
|
|
328
|
+
[] as string[],
|
|
329
|
+
)
|
|
330
|
+
.option("--since <timestamp>", "Start time filter (ISO 8601)")
|
|
331
|
+
.option("--until <timestamp>", "End time filter (ISO 8601)")
|
|
332
|
+
.option("--limit <n>", "Max events to show (default: 200)")
|
|
333
|
+
.option("--json", "Output as JSON array of StoredEvent objects")
|
|
334
|
+
.action(async (opts: ReplayOpts) => {
|
|
335
|
+
await executeReplay(opts);
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
export async function replayCommand(args: string[]): Promise<void> {
|
|
340
|
+
const cmd = createReplayCommand();
|
|
341
|
+
cmd.exitOverride();
|
|
342
|
+
try {
|
|
343
|
+
await cmd.parseAsync(args, { from: "user" });
|
|
344
|
+
} catch (err: unknown) {
|
|
345
|
+
if (err && typeof err === "object" && "code" in err) {
|
|
346
|
+
const code = (err as { code: string }).code;
|
|
347
|
+
if (code === "commander.helpDisplayed" || code === "commander.version") {
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
throw err;
|
|
352
|
+
}
|
|
353
|
+
}
|