@os-eco/overstory-cli 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +381 -0
- package/agents/builder.md +137 -0
- package/agents/coordinator.md +263 -0
- package/agents/lead.md +301 -0
- package/agents/merger.md +160 -0
- package/agents/monitor.md +214 -0
- package/agents/reviewer.md +140 -0
- package/agents/scout.md +119 -0
- package/agents/supervisor.md +423 -0
- package/package.json +47 -0
- package/src/agents/checkpoint.test.ts +88 -0
- package/src/agents/checkpoint.ts +101 -0
- package/src/agents/hooks-deployer.test.ts +2040 -0
- package/src/agents/hooks-deployer.ts +607 -0
- package/src/agents/identity.test.ts +603 -0
- package/src/agents/identity.ts +384 -0
- package/src/agents/lifecycle.test.ts +196 -0
- package/src/agents/lifecycle.ts +183 -0
- package/src/agents/manifest.test.ts +746 -0
- package/src/agents/manifest.ts +354 -0
- package/src/agents/overlay.test.ts +676 -0
- package/src/agents/overlay.ts +308 -0
- package/src/beads/client.test.ts +217 -0
- package/src/beads/client.ts +202 -0
- package/src/beads/molecules.test.ts +338 -0
- package/src/beads/molecules.ts +198 -0
- package/src/commands/agents.test.ts +322 -0
- package/src/commands/agents.ts +287 -0
- package/src/commands/clean.test.ts +670 -0
- package/src/commands/clean.ts +618 -0
- package/src/commands/completions.test.ts +342 -0
- package/src/commands/completions.ts +887 -0
- package/src/commands/coordinator.test.ts +1530 -0
- package/src/commands/coordinator.ts +733 -0
- package/src/commands/costs.test.ts +1119 -0
- package/src/commands/costs.ts +564 -0
- package/src/commands/dashboard.test.ts +308 -0
- package/src/commands/dashboard.ts +838 -0
- package/src/commands/doctor.test.ts +294 -0
- package/src/commands/doctor.ts +213 -0
- package/src/commands/errors.test.ts +647 -0
- package/src/commands/errors.ts +248 -0
- package/src/commands/feed.test.ts +578 -0
- package/src/commands/feed.ts +361 -0
- package/src/commands/group.test.ts +262 -0
- package/src/commands/group.ts +511 -0
- package/src/commands/hooks.test.ts +458 -0
- package/src/commands/hooks.ts +253 -0
- package/src/commands/init.test.ts +347 -0
- package/src/commands/init.ts +650 -0
- package/src/commands/inspect.test.ts +670 -0
- package/src/commands/inspect.ts +431 -0
- package/src/commands/log.test.ts +1454 -0
- package/src/commands/log.ts +724 -0
- package/src/commands/logs.test.ts +379 -0
- package/src/commands/logs.ts +546 -0
- package/src/commands/mail.test.ts +1270 -0
- package/src/commands/mail.ts +771 -0
- package/src/commands/merge.test.ts +670 -0
- package/src/commands/merge.ts +355 -0
- package/src/commands/metrics.test.ts +444 -0
- package/src/commands/metrics.ts +143 -0
- package/src/commands/monitor.test.ts +191 -0
- package/src/commands/monitor.ts +390 -0
- package/src/commands/nudge.test.ts +230 -0
- package/src/commands/nudge.ts +372 -0
- package/src/commands/prime.test.ts +470 -0
- package/src/commands/prime.ts +381 -0
- package/src/commands/replay.test.ts +741 -0
- package/src/commands/replay.ts +360 -0
- package/src/commands/run.test.ts +431 -0
- package/src/commands/run.ts +351 -0
- package/src/commands/sling.test.ts +657 -0
- package/src/commands/sling.ts +661 -0
- package/src/commands/spec.test.ts +203 -0
- package/src/commands/spec.ts +168 -0
- package/src/commands/status.test.ts +430 -0
- package/src/commands/status.ts +398 -0
- package/src/commands/stop.test.ts +420 -0
- package/src/commands/stop.ts +151 -0
- package/src/commands/supervisor.test.ts +187 -0
- package/src/commands/supervisor.ts +535 -0
- package/src/commands/trace.test.ts +745 -0
- package/src/commands/trace.ts +325 -0
- package/src/commands/watch.test.ts +145 -0
- package/src/commands/watch.ts +247 -0
- package/src/commands/worktree.test.ts +786 -0
- package/src/commands/worktree.ts +311 -0
- package/src/config.test.ts +822 -0
- package/src/config.ts +829 -0
- package/src/doctor/agents.test.ts +454 -0
- package/src/doctor/agents.ts +396 -0
- package/src/doctor/config-check.test.ts +190 -0
- package/src/doctor/config-check.ts +183 -0
- package/src/doctor/consistency.test.ts +651 -0
- package/src/doctor/consistency.ts +294 -0
- package/src/doctor/databases.test.ts +290 -0
- package/src/doctor/databases.ts +218 -0
- package/src/doctor/dependencies.test.ts +184 -0
- package/src/doctor/dependencies.ts +175 -0
- package/src/doctor/logs.test.ts +251 -0
- package/src/doctor/logs.ts +295 -0
- package/src/doctor/merge-queue.test.ts +216 -0
- package/src/doctor/merge-queue.ts +144 -0
- package/src/doctor/structure.test.ts +291 -0
- package/src/doctor/structure.ts +198 -0
- package/src/doctor/types.ts +37 -0
- package/src/doctor/version.test.ts +136 -0
- package/src/doctor/version.ts +129 -0
- package/src/e2e/init-sling-lifecycle.test.ts +277 -0
- package/src/errors.ts +217 -0
- package/src/events/store.test.ts +660 -0
- package/src/events/store.ts +369 -0
- package/src/events/tool-filter.test.ts +330 -0
- package/src/events/tool-filter.ts +126 -0
- package/src/index.ts +316 -0
- package/src/insights/analyzer.test.ts +466 -0
- package/src/insights/analyzer.ts +203 -0
- package/src/logging/color.test.ts +142 -0
- package/src/logging/color.ts +71 -0
- package/src/logging/logger.test.ts +813 -0
- package/src/logging/logger.ts +266 -0
- package/src/logging/reporter.test.ts +259 -0
- package/src/logging/reporter.ts +109 -0
- package/src/logging/sanitizer.test.ts +190 -0
- package/src/logging/sanitizer.ts +57 -0
- package/src/mail/broadcast.test.ts +203 -0
- package/src/mail/broadcast.ts +92 -0
- package/src/mail/client.test.ts +773 -0
- package/src/mail/client.ts +223 -0
- package/src/mail/store.test.ts +705 -0
- package/src/mail/store.ts +387 -0
- package/src/merge/queue.test.ts +359 -0
- package/src/merge/queue.ts +231 -0
- package/src/merge/resolver.test.ts +1345 -0
- package/src/merge/resolver.ts +645 -0
- package/src/metrics/store.test.ts +667 -0
- package/src/metrics/store.ts +445 -0
- package/src/metrics/summary.test.ts +398 -0
- package/src/metrics/summary.ts +178 -0
- package/src/metrics/transcript.test.ts +356 -0
- package/src/metrics/transcript.ts +175 -0
- package/src/mulch/client.test.ts +671 -0
- package/src/mulch/client.ts +332 -0
- package/src/sessions/compat.test.ts +280 -0
- package/src/sessions/compat.ts +104 -0
- package/src/sessions/store.test.ts +873 -0
- package/src/sessions/store.ts +494 -0
- package/src/test-helpers.test.ts +124 -0
- package/src/test-helpers.ts +126 -0
- package/src/tracker/beads.ts +56 -0
- package/src/tracker/factory.test.ts +80 -0
- package/src/tracker/factory.ts +64 -0
- package/src/tracker/seeds.ts +182 -0
- package/src/tracker/types.ts +52 -0
- package/src/types.ts +724 -0
- package/src/watchdog/daemon.test.ts +1975 -0
- package/src/watchdog/daemon.ts +671 -0
- package/src/watchdog/health.test.ts +431 -0
- package/src/watchdog/health.ts +264 -0
- package/src/watchdog/triage.test.ts +164 -0
- package/src/watchdog/triage.ts +179 -0
- package/src/worktree/manager.test.ts +439 -0
- package/src/worktree/manager.ts +198 -0
- package/src/worktree/tmux.test.ts +1009 -0
- package/src/worktree/tmux.ts +509 -0
- package/templates/CLAUDE.md.tmpl +89 -0
- package/templates/hooks.json.tmpl +105 -0
- package/templates/overlay.md.tmpl +81 -0
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI command: overstory replay [--run <id>] [--agent <name>...] [--json]
|
|
3
|
+
* [--since <ts>] [--until <ts>] [--limit <n>]
|
|
4
|
+
*
|
|
5
|
+
* Shows an interleaved chronological replay of events across multiple agents.
|
|
6
|
+
* Like reading a combined log — all agents' events merged by timestamp.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import { loadConfig } from "../config.ts";
|
|
11
|
+
import { ValidationError } from "../errors.ts";
|
|
12
|
+
import { createEventStore } from "../events/store.ts";
|
|
13
|
+
import { color } from "../logging/color.ts";
|
|
14
|
+
import type { EventType, StoredEvent } from "../types.ts";
|
|
15
|
+
|
|
16
|
+
/** Labels and colors for each event type. */
|
|
17
|
+
const EVENT_LABELS: Record<EventType, { label: string; color: string }> = {
|
|
18
|
+
tool_start: { label: "TOOL START", color: color.blue },
|
|
19
|
+
tool_end: { label: "TOOL END ", color: color.blue },
|
|
20
|
+
session_start: { label: "SESSION +", color: color.green },
|
|
21
|
+
session_end: { label: "SESSION -", color: color.yellow },
|
|
22
|
+
mail_sent: { label: "MAIL SENT ", color: color.cyan },
|
|
23
|
+
mail_received: { label: "MAIL RECV ", color: color.cyan },
|
|
24
|
+
spawn: { label: "SPAWN ", color: color.magenta },
|
|
25
|
+
error: { label: "ERROR ", color: color.red },
|
|
26
|
+
custom: { label: "CUSTOM ", color: color.gray },
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/** Colors assigned to agents in order of first appearance. */
|
|
30
|
+
const AGENT_COLORS = [color.blue, color.green, color.yellow, color.cyan, color.magenta] as const;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Parse a named flag value from args.
|
|
34
|
+
*/
|
|
35
|
+
function getFlag(args: string[], flag: string): string | undefined {
|
|
36
|
+
const idx = args.indexOf(flag);
|
|
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
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Format a relative time string from a timestamp.
|
|
67
|
+
* Returns strings like "2m ago", "1h ago", "3d ago".
|
|
68
|
+
*/
|
|
69
|
+
function formatRelativeTime(timestamp: string): string {
|
|
70
|
+
const eventTime = new Date(timestamp).getTime();
|
|
71
|
+
const now = Date.now();
|
|
72
|
+
const diffMs = now - eventTime;
|
|
73
|
+
|
|
74
|
+
if (diffMs < 0) return "just now";
|
|
75
|
+
|
|
76
|
+
const seconds = Math.floor(diffMs / 1000);
|
|
77
|
+
if (seconds < 60) return `${seconds}s ago`;
|
|
78
|
+
|
|
79
|
+
const minutes = Math.floor(seconds / 60);
|
|
80
|
+
if (minutes < 60) return `${minutes}m ago`;
|
|
81
|
+
|
|
82
|
+
const hours = Math.floor(minutes / 60);
|
|
83
|
+
if (hours < 24) return `${hours}h ago`;
|
|
84
|
+
|
|
85
|
+
const days = Math.floor(hours / 24);
|
|
86
|
+
return `${days}d ago`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Format an absolute time from an ISO timestamp.
|
|
91
|
+
* Returns "HH:MM:SS" portion.
|
|
92
|
+
*/
|
|
93
|
+
function formatAbsoluteTime(timestamp: string): string {
|
|
94
|
+
const match = /T(\d{2}:\d{2}:\d{2})/.exec(timestamp);
|
|
95
|
+
if (match?.[1]) {
|
|
96
|
+
return match[1];
|
|
97
|
+
}
|
|
98
|
+
return timestamp;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Format the date portion of an ISO timestamp.
|
|
103
|
+
* Returns "YYYY-MM-DD".
|
|
104
|
+
*/
|
|
105
|
+
function formatDate(timestamp: string): string {
|
|
106
|
+
const match = /^(\d{4}-\d{2}-\d{2})/.exec(timestamp);
|
|
107
|
+
if (match?.[1]) {
|
|
108
|
+
return match[1];
|
|
109
|
+
}
|
|
110
|
+
return "";
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Build a detail string for a timeline event based on its type and fields.
|
|
115
|
+
*/
|
|
116
|
+
function buildEventDetail(event: StoredEvent): string {
|
|
117
|
+
const parts: string[] = [];
|
|
118
|
+
|
|
119
|
+
if (event.toolName) {
|
|
120
|
+
parts.push(`tool=${event.toolName}`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (event.toolDurationMs !== null) {
|
|
124
|
+
parts.push(`duration=${event.toolDurationMs}ms`);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (event.data) {
|
|
128
|
+
try {
|
|
129
|
+
const parsed: unknown = JSON.parse(event.data);
|
|
130
|
+
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
131
|
+
const data = parsed as Record<string, unknown>;
|
|
132
|
+
for (const [key, value] of Object.entries(data)) {
|
|
133
|
+
if (value !== null && value !== undefined) {
|
|
134
|
+
const strValue = typeof value === "string" ? value : JSON.stringify(value);
|
|
135
|
+
// Truncate long values
|
|
136
|
+
const truncated = strValue.length > 80 ? `${strValue.slice(0, 77)}...` : strValue;
|
|
137
|
+
parts.push(`${key}=${truncated}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
} catch {
|
|
142
|
+
// data is not valid JSON; show it raw if short enough
|
|
143
|
+
if (event.data.length <= 80) {
|
|
144
|
+
parts.push(event.data);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return parts.join(" ");
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Assign a stable color to each agent based on order of first appearance.
|
|
154
|
+
*/
|
|
155
|
+
function buildAgentColorMap(events: StoredEvent[]): Map<string, string> {
|
|
156
|
+
const colorMap = new Map<string, string>();
|
|
157
|
+
for (const event of events) {
|
|
158
|
+
if (!colorMap.has(event.agentName)) {
|
|
159
|
+
const colorIndex = colorMap.size % AGENT_COLORS.length;
|
|
160
|
+
const color = AGENT_COLORS[colorIndex];
|
|
161
|
+
if (color !== undefined) {
|
|
162
|
+
colorMap.set(event.agentName, color);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return colorMap;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Print events as an interleaved timeline with ANSI colors and agent labels.
|
|
171
|
+
*/
|
|
172
|
+
function printReplay(events: StoredEvent[], useAbsoluteTime: boolean): void {
|
|
173
|
+
const w = process.stdout.write.bind(process.stdout);
|
|
174
|
+
|
|
175
|
+
w(`${color.bold}Replay${color.reset}\n`);
|
|
176
|
+
w(`${"=".repeat(70)}\n`);
|
|
177
|
+
|
|
178
|
+
if (events.length === 0) {
|
|
179
|
+
w(`${color.dim}No events found.${color.reset}\n`);
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
w(`${color.dim}${events.length} event${events.length === 1 ? "" : "s"}${color.reset}\n\n`);
|
|
184
|
+
|
|
185
|
+
const colorMap = buildAgentColorMap(events);
|
|
186
|
+
let lastDate = "";
|
|
187
|
+
|
|
188
|
+
for (const event of events) {
|
|
189
|
+
// Print date separator when the date changes
|
|
190
|
+
const date = formatDate(event.createdAt);
|
|
191
|
+
if (date && date !== lastDate) {
|
|
192
|
+
if (lastDate !== "") {
|
|
193
|
+
w("\n");
|
|
194
|
+
}
|
|
195
|
+
w(`${color.dim}--- ${date} ---${color.reset}\n`);
|
|
196
|
+
lastDate = date;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const timeStr = useAbsoluteTime
|
|
200
|
+
? formatAbsoluteTime(event.createdAt)
|
|
201
|
+
: formatRelativeTime(event.createdAt);
|
|
202
|
+
|
|
203
|
+
const eventInfo = EVENT_LABELS[event.eventType] ?? {
|
|
204
|
+
label: event.eventType.padEnd(10),
|
|
205
|
+
color: color.gray,
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
const levelColor =
|
|
209
|
+
event.level === "error" ? color.red : event.level === "warn" ? color.yellow : "";
|
|
210
|
+
const levelReset = levelColor ? color.reset : "";
|
|
211
|
+
|
|
212
|
+
const detail = buildEventDetail(event);
|
|
213
|
+
const detailSuffix = detail ? ` ${color.dim}${detail}${color.reset}` : "";
|
|
214
|
+
|
|
215
|
+
const agentColor = colorMap.get(event.agentName) ?? color.gray;
|
|
216
|
+
const agentLabel = ` ${agentColor}[${event.agentName}]${color.reset}`;
|
|
217
|
+
|
|
218
|
+
w(
|
|
219
|
+
`${color.dim}${timeStr.padStart(10)}${color.reset} ` +
|
|
220
|
+
`${levelColor}${eventInfo.color}${color.bold}${eventInfo.label}${color.reset}${levelReset}` +
|
|
221
|
+
`${agentLabel}${detailSuffix}\n`,
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const REPLAY_HELP = `overstory replay -- Interleaved chronological replay across agents
|
|
227
|
+
|
|
228
|
+
Usage: overstory replay [options]
|
|
229
|
+
|
|
230
|
+
Options:
|
|
231
|
+
--run <id> Filter events by run ID
|
|
232
|
+
--agent <name> Filter by agent name (can appear multiple times)
|
|
233
|
+
--since <timestamp> Start time filter (ISO 8601)
|
|
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
|
+
}
|
|
252
|
+
|
|
253
|
+
const json = hasFlag(args, "--json");
|
|
254
|
+
const runId = getFlag(args, "--run");
|
|
255
|
+
const agentNames = getAllFlags(args, "--agent");
|
|
256
|
+
const sinceStr = getFlag(args, "--since");
|
|
257
|
+
const untilStr = getFlag(args, "--until");
|
|
258
|
+
const limitStr = getFlag(args, "--limit");
|
|
259
|
+
const limit = limitStr ? Number.parseInt(limitStr, 10) : 200;
|
|
260
|
+
|
|
261
|
+
if (Number.isNaN(limit) || limit < 1) {
|
|
262
|
+
throw new ValidationError("--limit must be a positive integer", {
|
|
263
|
+
field: "limit",
|
|
264
|
+
value: limitStr,
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Validate timestamps if provided
|
|
269
|
+
if (sinceStr !== undefined && Number.isNaN(new Date(sinceStr).getTime())) {
|
|
270
|
+
throw new ValidationError("--since must be a valid ISO 8601 timestamp", {
|
|
271
|
+
field: "since",
|
|
272
|
+
value: sinceStr,
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
if (untilStr !== undefined && Number.isNaN(new Date(untilStr).getTime())) {
|
|
276
|
+
throw new ValidationError("--until must be a valid ISO 8601 timestamp", {
|
|
277
|
+
field: "until",
|
|
278
|
+
value: untilStr,
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const cwd = process.cwd();
|
|
283
|
+
const config = await loadConfig(cwd);
|
|
284
|
+
const overstoryDir = join(config.project.root, ".overstory");
|
|
285
|
+
|
|
286
|
+
// Open event store
|
|
287
|
+
const eventsDbPath = join(overstoryDir, "events.db");
|
|
288
|
+
const eventsFile = Bun.file(eventsDbPath);
|
|
289
|
+
if (!(await eventsFile.exists())) {
|
|
290
|
+
if (json) {
|
|
291
|
+
process.stdout.write("[]\n");
|
|
292
|
+
} else {
|
|
293
|
+
process.stdout.write("No events data yet.\n");
|
|
294
|
+
}
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const eventStore = createEventStore(eventsDbPath);
|
|
299
|
+
|
|
300
|
+
try {
|
|
301
|
+
let events: StoredEvent[];
|
|
302
|
+
const queryOpts = { since: sinceStr, until: untilStr, limit };
|
|
303
|
+
|
|
304
|
+
if (runId) {
|
|
305
|
+
// Query by run ID
|
|
306
|
+
events = eventStore.getByRun(runId, queryOpts);
|
|
307
|
+
} else if (agentNames.length > 0) {
|
|
308
|
+
// Query each agent and merge
|
|
309
|
+
const allEvents: StoredEvent[] = [];
|
|
310
|
+
for (const name of agentNames) {
|
|
311
|
+
const agentEvents = eventStore.getByAgent(name, {
|
|
312
|
+
since: sinceStr,
|
|
313
|
+
until: untilStr,
|
|
314
|
+
});
|
|
315
|
+
allEvents.push(...agentEvents);
|
|
316
|
+
}
|
|
317
|
+
// Sort by createdAt chronologically
|
|
318
|
+
allEvents.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
319
|
+
// Apply limit after merge
|
|
320
|
+
events = allEvents.slice(0, limit);
|
|
321
|
+
} else {
|
|
322
|
+
// Default: try current-run.txt, then fall back to 24h timeline
|
|
323
|
+
const currentRunPath = join(overstoryDir, "current-run.txt");
|
|
324
|
+
const currentRunFile = Bun.file(currentRunPath);
|
|
325
|
+
if (await currentRunFile.exists()) {
|
|
326
|
+
const currentRunId = (await currentRunFile.text()).trim();
|
|
327
|
+
if (currentRunId) {
|
|
328
|
+
events = eventStore.getByRun(currentRunId, queryOpts);
|
|
329
|
+
} else {
|
|
330
|
+
// Empty file, fall back to timeline
|
|
331
|
+
const since24h = sinceStr ?? new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString();
|
|
332
|
+
events = eventStore.getTimeline({
|
|
333
|
+
since: since24h,
|
|
334
|
+
until: untilStr,
|
|
335
|
+
limit,
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
} else {
|
|
339
|
+
// No current run file, fall back to 24h timeline
|
|
340
|
+
const since24h = sinceStr ?? new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString();
|
|
341
|
+
events = eventStore.getTimeline({
|
|
342
|
+
since: since24h,
|
|
343
|
+
until: untilStr,
|
|
344
|
+
limit,
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (json) {
|
|
350
|
+
process.stdout.write(`${JSON.stringify(events)}\n`);
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Use absolute time if --since is specified, relative otherwise
|
|
355
|
+
const useAbsoluteTime = sinceStr !== undefined;
|
|
356
|
+
printReplay(events, useAbsoluteTime);
|
|
357
|
+
} finally {
|
|
358
|
+
eventStore.close();
|
|
359
|
+
}
|
|
360
|
+
}
|