@katyella/legio 0.1.0
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/CHANGELOG.md +422 -0
- package/LICENSE +21 -0
- package/README.md +555 -0
- package/agents/builder.md +141 -0
- package/agents/coordinator.md +351 -0
- package/agents/cto.md +196 -0
- package/agents/gateway.md +276 -0
- package/agents/lead.md +281 -0
- package/agents/merger.md +156 -0
- package/agents/monitor.md +212 -0
- package/agents/reviewer.md +142 -0
- package/agents/scout.md +131 -0
- package/agents/supervisor.md +416 -0
- package/bin/legio.mjs +38 -0
- package/package.json +77 -0
- package/src/agents/checkpoint.test.ts +88 -0
- package/src/agents/checkpoint.ts +102 -0
- package/src/agents/hooks-deployer.test.ts +1820 -0
- package/src/agents/hooks-deployer.ts +574 -0
- package/src/agents/identity.test.ts +614 -0
- package/src/agents/identity.ts +385 -0
- package/src/agents/lifecycle.test.ts +202 -0
- package/src/agents/lifecycle.ts +184 -0
- package/src/agents/manifest.test.ts +558 -0
- package/src/agents/manifest.ts +297 -0
- package/src/agents/overlay.test.ts +592 -0
- package/src/agents/overlay.ts +316 -0
- package/src/beads/client.test.ts +210 -0
- package/src/beads/client.ts +227 -0
- package/src/beads/molecules.test.ts +320 -0
- package/src/beads/molecules.ts +209 -0
- package/src/commands/agents.test.ts +325 -0
- package/src/commands/agents.ts +286 -0
- package/src/commands/clean.test.ts +730 -0
- package/src/commands/clean.ts +653 -0
- package/src/commands/completions.test.ts +346 -0
- package/src/commands/completions.ts +950 -0
- package/src/commands/coordinator.test.ts +1524 -0
- package/src/commands/coordinator.ts +880 -0
- package/src/commands/costs.test.ts +1015 -0
- package/src/commands/costs.ts +473 -0
- package/src/commands/dashboard.test.ts +94 -0
- package/src/commands/dashboard.ts +607 -0
- package/src/commands/doctor.test.ts +295 -0
- package/src/commands/doctor.ts +213 -0
- package/src/commands/down.test.ts +308 -0
- package/src/commands/down.ts +124 -0
- package/src/commands/errors.test.ts +648 -0
- package/src/commands/errors.ts +255 -0
- package/src/commands/feed.test.ts +579 -0
- package/src/commands/feed.ts +368 -0
- package/src/commands/gateway.test.ts +698 -0
- package/src/commands/gateway.ts +419 -0
- package/src/commands/group.test.ts +262 -0
- package/src/commands/group.ts +539 -0
- package/src/commands/hooks.test.ts +292 -0
- package/src/commands/hooks.ts +210 -0
- package/src/commands/init.test.ts +211 -0
- package/src/commands/init.ts +622 -0
- package/src/commands/inspect.test.ts +670 -0
- package/src/commands/inspect.ts +455 -0
- package/src/commands/log.test.ts +1556 -0
- package/src/commands/log.ts +752 -0
- package/src/commands/logs.test.ts +379 -0
- package/src/commands/logs.ts +544 -0
- package/src/commands/mail.test.ts +1726 -0
- package/src/commands/mail.ts +926 -0
- package/src/commands/merge.test.ts +676 -0
- package/src/commands/merge.ts +374 -0
- package/src/commands/metrics.test.ts +444 -0
- package/src/commands/metrics.ts +150 -0
- package/src/commands/monitor.test.ts +151 -0
- package/src/commands/monitor.ts +394 -0
- package/src/commands/nudge.test.ts +230 -0
- package/src/commands/nudge.ts +373 -0
- package/src/commands/prime.test.ts +467 -0
- package/src/commands/prime.ts +386 -0
- package/src/commands/replay.test.ts +742 -0
- package/src/commands/replay.ts +367 -0
- package/src/commands/run.test.ts +443 -0
- package/src/commands/run.ts +365 -0
- package/src/commands/server.test.ts +626 -0
- package/src/commands/server.ts +298 -0
- package/src/commands/sling.test.ts +810 -0
- package/src/commands/sling.ts +700 -0
- package/src/commands/spec.test.ts +206 -0
- package/src/commands/spec.ts +171 -0
- package/src/commands/status.test.ts +276 -0
- package/src/commands/status.ts +339 -0
- package/src/commands/stop.test.ts +357 -0
- package/src/commands/stop.ts +119 -0
- package/src/commands/supervisor.test.ts +186 -0
- package/src/commands/supervisor.ts +544 -0
- package/src/commands/trace.test.ts +746 -0
- package/src/commands/trace.ts +332 -0
- package/src/commands/up.test.ts +597 -0
- package/src/commands/up.ts +275 -0
- package/src/commands/watch.test.ts +152 -0
- package/src/commands/watch.ts +238 -0
- package/src/commands/worktree.test.ts +648 -0
- package/src/commands/worktree.ts +266 -0
- package/src/config.test.ts +496 -0
- package/src/config.ts +616 -0
- package/src/doctor/agents.test.ts +448 -0
- package/src/doctor/agents.ts +396 -0
- package/src/doctor/config-check.test.ts +184 -0
- package/src/doctor/config-check.ts +185 -0
- package/src/doctor/consistency.test.ts +645 -0
- package/src/doctor/consistency.ts +294 -0
- package/src/doctor/databases.test.ts +284 -0
- package/src/doctor/databases.ts +211 -0
- package/src/doctor/dependencies.test.ts +150 -0
- package/src/doctor/dependencies.ts +179 -0
- package/src/doctor/logs.test.ts +244 -0
- package/src/doctor/logs.ts +295 -0
- package/src/doctor/merge-queue.test.ts +210 -0
- package/src/doctor/merge-queue.ts +144 -0
- package/src/doctor/structure.test.ts +285 -0
- package/src/doctor/structure.ts +195 -0
- package/src/doctor/types.ts +37 -0
- package/src/doctor/version.test.ts +130 -0
- package/src/doctor/version.ts +131 -0
- package/src/e2e/chat-flow.test.ts +346 -0
- package/src/e2e/init-sling-lifecycle.test.ts +288 -0
- package/src/errors.test.ts +21 -0
- package/src/errors.ts +246 -0
- package/src/events/store.test.ts +660 -0
- package/src/events/store.ts +344 -0
- package/src/events/tool-filter.test.ts +330 -0
- package/src/events/tool-filter.ts +126 -0
- package/src/global-setup.ts +14 -0
- package/src/index.ts +339 -0
- package/src/insights/analyzer.test.ts +466 -0
- package/src/insights/analyzer.ts +203 -0
- package/src/logging/color.test.ts +118 -0
- package/src/logging/color.ts +71 -0
- package/src/logging/logger.test.ts +812 -0
- package/src/logging/logger.ts +266 -0
- package/src/logging/reporter.test.ts +258 -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 +873 -0
- package/src/mail/client.ts +236 -0
- package/src/mail/store.test.ts +815 -0
- package/src/mail/store.ts +402 -0
- package/src/merge/queue.test.ts +449 -0
- package/src/merge/queue.ts +262 -0
- package/src/merge/resolver.test.ts +1453 -0
- package/src/merge/resolver.ts +759 -0
- package/src/metrics/store.test.ts +1167 -0
- package/src/metrics/store.ts +511 -0
- package/src/metrics/summary.test.ts +397 -0
- package/src/metrics/summary.ts +178 -0
- package/src/metrics/transcript.test.ts +643 -0
- package/src/metrics/transcript.ts +351 -0
- package/src/mulch/client.test.ts +547 -0
- package/src/mulch/client.ts +416 -0
- package/src/server/audit-store.test.ts +384 -0
- package/src/server/audit-store.ts +257 -0
- package/src/server/headless.test.ts +180 -0
- package/src/server/headless.ts +151 -0
- package/src/server/index.test.ts +241 -0
- package/src/server/index.ts +317 -0
- package/src/server/public/app.js +187 -0
- package/src/server/public/apple-touch-icon.png +0 -0
- package/src/server/public/components/agent-badge.js +37 -0
- package/src/server/public/components/data-table.js +114 -0
- package/src/server/public/components/gateway-chat.js +256 -0
- package/src/server/public/components/issue-card.js +96 -0
- package/src/server/public/components/layout.js +88 -0
- package/src/server/public/components/message-bubble.js +120 -0
- package/src/server/public/components/stat-card.js +26 -0
- package/src/server/public/components/terminal-panel.js +140 -0
- package/src/server/public/favicon-16.png +0 -0
- package/src/server/public/favicon-32.png +0 -0
- package/src/server/public/favicon.ico +0 -0
- package/src/server/public/favicon.png +0 -0
- package/src/server/public/index.html +64 -0
- package/src/server/public/lib/api.js +35 -0
- package/src/server/public/lib/markdown.js +8 -0
- package/src/server/public/lib/preact-setup.js +8 -0
- package/src/server/public/lib/state.js +99 -0
- package/src/server/public/lib/utils.js +309 -0
- package/src/server/public/lib/ws.js +79 -0
- package/src/server/public/views/chat.js +983 -0
- package/src/server/public/views/costs.js +692 -0
- package/src/server/public/views/dashboard.js +781 -0
- package/src/server/public/views/gateway-chat.js +622 -0
- package/src/server/public/views/inspect.js +399 -0
- package/src/server/public/views/issues.js +470 -0
- package/src/server/public/views/setup.js +94 -0
- package/src/server/public/views/task-detail.js +422 -0
- package/src/server/routes.test.ts +3816 -0
- package/src/server/routes.ts +1964 -0
- package/src/server/websocket.test.ts +288 -0
- package/src/server/websocket.ts +196 -0
- package/src/sessions/compat.test.ts +109 -0
- package/src/sessions/compat.ts +17 -0
- package/src/sessions/store.test.ts +969 -0
- package/src/sessions/store.ts +480 -0
- package/src/test-helpers.test.ts +97 -0
- package/src/test-helpers.ts +143 -0
- package/src/types.ts +708 -0
- package/src/watchdog/daemon.test.ts +1233 -0
- package/src/watchdog/daemon.ts +533 -0
- package/src/watchdog/health.test.ts +371 -0
- package/src/watchdog/health.ts +248 -0
- package/src/watchdog/triage.test.ts +162 -0
- package/src/watchdog/triage.ts +193 -0
- package/src/worktree/manager.test.ts +444 -0
- package/src/worktree/manager.ts +224 -0
- package/src/worktree/tmux.test.ts +1238 -0
- package/src/worktree/tmux.ts +644 -0
- package/templates/CLAUDE.md.tmpl +89 -0
- package/templates/hooks.json.tmpl +132 -0
- package/templates/overlay.md.tmpl +79 -0
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI command: legio feed [--follow] [--agent <name>...] [--run <id>]
|
|
3
|
+
* [--since <ts>] [--limit <n>] [--interval <ms>] [--json]
|
|
4
|
+
*
|
|
5
|
+
* Unified real-time event stream across all agents — like `tail -f` for the fleet.
|
|
6
|
+
* Shows chronological events from all agents merged into a single feed.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { access } from "node:fs/promises";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
import { loadConfig } from "../config.ts";
|
|
12
|
+
import { ValidationError } from "../errors.ts";
|
|
13
|
+
import { createEventStore } from "../events/store.ts";
|
|
14
|
+
import { color } from "../logging/color.ts";
|
|
15
|
+
import type { EventType, StoredEvent } from "../types.ts";
|
|
16
|
+
|
|
17
|
+
/** Compact 5-char labels for feed output. */
|
|
18
|
+
const EVENT_LABELS: Record<EventType, { label: string; color: string }> = {
|
|
19
|
+
tool_start: { label: "TOOL+", color: color.blue },
|
|
20
|
+
tool_end: { label: "TOOL-", color: color.blue },
|
|
21
|
+
session_start: { label: "SESS+", color: color.green },
|
|
22
|
+
session_end: { label: "SESS-", color: color.yellow },
|
|
23
|
+
mail_sent: { label: "MAIL>", color: color.cyan },
|
|
24
|
+
mail_received: { label: "MAIL<", color: color.cyan },
|
|
25
|
+
spawn: { label: "SPAWN", color: color.magenta },
|
|
26
|
+
error: { label: "ERROR", color: color.red },
|
|
27
|
+
custom: { label: "CUSTM", color: color.gray },
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/** Colors assigned to agents in order of first appearance. */
|
|
31
|
+
const AGENT_COLORS = [color.blue, color.green, color.yellow, color.cyan, color.magenta] as const;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Parse a named flag value from args.
|
|
35
|
+
*/
|
|
36
|
+
function getFlag(args: string[], flag: string): string | undefined {
|
|
37
|
+
const idx = args.indexOf(flag);
|
|
38
|
+
if (idx === -1 || idx + 1 >= args.length) {
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
return args[idx + 1];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Parse all occurrences of a named flag from args.
|
|
46
|
+
* Returns an array of values (e.g., --agent a --agent b => ["a", "b"]).
|
|
47
|
+
*/
|
|
48
|
+
function getAllFlags(args: string[], flag: string): string[] {
|
|
49
|
+
const values: string[] = [];
|
|
50
|
+
for (let i = 0; i < args.length; i++) {
|
|
51
|
+
if (args[i] === flag && i + 1 < args.length) {
|
|
52
|
+
const value = args[i + 1];
|
|
53
|
+
if (value !== undefined) {
|
|
54
|
+
values.push(value);
|
|
55
|
+
}
|
|
56
|
+
i++; // skip the value
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return values;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function hasFlag(args: string[], flag: string): boolean {
|
|
63
|
+
return args.includes(flag);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Format an absolute time from an ISO timestamp.
|
|
68
|
+
* Returns "HH:MM:SS" portion.
|
|
69
|
+
*/
|
|
70
|
+
function formatAbsoluteTime(timestamp: string): string {
|
|
71
|
+
const match = /T(\d{2}:\d{2}:\d{2})/.exec(timestamp);
|
|
72
|
+
if (match?.[1]) {
|
|
73
|
+
return match[1];
|
|
74
|
+
}
|
|
75
|
+
return timestamp;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Build a detail string for a feed event based on its type and fields.
|
|
80
|
+
*/
|
|
81
|
+
function buildEventDetail(event: StoredEvent): string {
|
|
82
|
+
const parts: string[] = [];
|
|
83
|
+
|
|
84
|
+
if (event.toolName) {
|
|
85
|
+
parts.push(`tool=${event.toolName}`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (event.toolDurationMs !== null) {
|
|
89
|
+
parts.push(`${event.toolDurationMs}ms`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (event.data) {
|
|
93
|
+
try {
|
|
94
|
+
const parsed: unknown = JSON.parse(event.data);
|
|
95
|
+
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
96
|
+
const data = parsed as Record<string, unknown>;
|
|
97
|
+
for (const [key, value] of Object.entries(data)) {
|
|
98
|
+
if (value !== null && value !== undefined) {
|
|
99
|
+
const strValue = typeof value === "string" ? value : JSON.stringify(value);
|
|
100
|
+
// Truncate long values
|
|
101
|
+
const truncated = strValue.length > 60 ? `${strValue.slice(0, 57)}...` : strValue;
|
|
102
|
+
parts.push(`${key}=${truncated}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
} catch {
|
|
107
|
+
// data is not valid JSON; show it raw if short enough
|
|
108
|
+
if (event.data.length <= 60) {
|
|
109
|
+
parts.push(event.data);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return parts.join(" ");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Assign a stable color to each agent based on order of first appearance.
|
|
119
|
+
*/
|
|
120
|
+
function buildAgentColorMap(events: StoredEvent[]): Map<string, string> {
|
|
121
|
+
const colorMap = new Map<string, string>();
|
|
122
|
+
for (const event of events) {
|
|
123
|
+
if (!colorMap.has(event.agentName)) {
|
|
124
|
+
const colorIndex = colorMap.size % AGENT_COLORS.length;
|
|
125
|
+
const agentColor = AGENT_COLORS[colorIndex];
|
|
126
|
+
if (agentColor !== undefined) {
|
|
127
|
+
colorMap.set(event.agentName, agentColor);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return colorMap;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Print a single event in compact feed format:
|
|
136
|
+
* HH:MM:SS LABEL agentname detail
|
|
137
|
+
*/
|
|
138
|
+
function printEvent(event: StoredEvent, colorMap: Map<string, string>): void {
|
|
139
|
+
const w = process.stdout.write.bind(process.stdout);
|
|
140
|
+
|
|
141
|
+
const timeStr = formatAbsoluteTime(event.createdAt);
|
|
142
|
+
|
|
143
|
+
const eventInfo = EVENT_LABELS[event.eventType] ?? {
|
|
144
|
+
label: event.eventType.padEnd(5),
|
|
145
|
+
color: color.gray,
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const levelColor =
|
|
149
|
+
event.level === "error" ? color.red : event.level === "warn" ? color.yellow : "";
|
|
150
|
+
const levelReset = levelColor ? color.reset : "";
|
|
151
|
+
|
|
152
|
+
const detail = buildEventDetail(event);
|
|
153
|
+
const detailSuffix = detail ? ` ${color.dim}${detail}${color.reset}` : "";
|
|
154
|
+
|
|
155
|
+
const agentColor = colorMap.get(event.agentName) ?? color.gray;
|
|
156
|
+
const agentLabel = ` ${agentColor}${event.agentName.padEnd(15)}${color.reset}`;
|
|
157
|
+
|
|
158
|
+
w(
|
|
159
|
+
`${color.dim}${timeStr}${color.reset} ` +
|
|
160
|
+
`${levelColor}${eventInfo.color}${color.bold}${eventInfo.label}${color.reset}${levelReset}` +
|
|
161
|
+
`${agentLabel}${detailSuffix}\n`,
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const FEED_HELP = `legio feed -- Unified real-time event stream across all agents
|
|
166
|
+
|
|
167
|
+
Usage: legio feed [options]
|
|
168
|
+
|
|
169
|
+
Options:
|
|
170
|
+
--follow, -f Continuously poll for new events (like tail -f)
|
|
171
|
+
--interval <ms> Polling interval for --follow (default: 1000, min: 200)
|
|
172
|
+
--agent <name> Filter by agent name (can appear multiple times)
|
|
173
|
+
--run <id> Filter events by run ID
|
|
174
|
+
--since <timestamp> Start time (ISO 8601, default: 5 minutes ago)
|
|
175
|
+
--limit <n> Max initial events to show (default: 50)
|
|
176
|
+
--json Output events as JSON (one per line in follow mode)
|
|
177
|
+
--help, -h Show this help`;
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Entry point for `legio feed [--follow] [--agent <name>...] [--run <id>] [--json]`.
|
|
181
|
+
*/
|
|
182
|
+
export async function feedCommand(args: string[]): Promise<void> {
|
|
183
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
184
|
+
process.stdout.write(`${FEED_HELP}\n`);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const json = hasFlag(args, "--json");
|
|
189
|
+
const follow = hasFlag(args, "--follow") || hasFlag(args, "-f");
|
|
190
|
+
const runId = getFlag(args, "--run");
|
|
191
|
+
const agentNames = getAllFlags(args, "--agent");
|
|
192
|
+
const sinceStr = getFlag(args, "--since");
|
|
193
|
+
const limitStr = getFlag(args, "--limit");
|
|
194
|
+
const limit = limitStr ? Number.parseInt(limitStr, 10) : 50;
|
|
195
|
+
|
|
196
|
+
const intervalStr = getFlag(args, "--interval");
|
|
197
|
+
const interval = intervalStr ? Number.parseInt(intervalStr, 10) : 1000;
|
|
198
|
+
|
|
199
|
+
if (Number.isNaN(limit) || limit < 1) {
|
|
200
|
+
throw new ValidationError("--limit must be a positive integer", {
|
|
201
|
+
field: "limit",
|
|
202
|
+
value: limitStr,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (Number.isNaN(interval) || interval < 200) {
|
|
207
|
+
throw new ValidationError("--interval must be a number >= 200 (milliseconds)", {
|
|
208
|
+
field: "interval",
|
|
209
|
+
value: intervalStr,
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Validate timestamp if provided
|
|
214
|
+
if (sinceStr !== undefined && Number.isNaN(new Date(sinceStr).getTime())) {
|
|
215
|
+
throw new ValidationError("--since must be a valid ISO 8601 timestamp", {
|
|
216
|
+
field: "since",
|
|
217
|
+
value: sinceStr,
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const cwd = process.cwd();
|
|
222
|
+
const config = await loadConfig(cwd);
|
|
223
|
+
const legioDir = join(config.project.root, ".legio");
|
|
224
|
+
|
|
225
|
+
// Open event store
|
|
226
|
+
const eventsDbPath = join(legioDir, "events.db");
|
|
227
|
+
let eventsDbExists = false;
|
|
228
|
+
try {
|
|
229
|
+
await access(eventsDbPath);
|
|
230
|
+
eventsDbExists = true;
|
|
231
|
+
} catch {
|
|
232
|
+
/* not found */
|
|
233
|
+
}
|
|
234
|
+
if (!eventsDbExists) {
|
|
235
|
+
if (json) {
|
|
236
|
+
process.stdout.write("[]\n");
|
|
237
|
+
} else {
|
|
238
|
+
process.stdout.write("No events data yet.\n");
|
|
239
|
+
}
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const eventStore = createEventStore(eventsDbPath);
|
|
244
|
+
|
|
245
|
+
try {
|
|
246
|
+
// Default since: 5 minutes ago
|
|
247
|
+
const since = sinceStr ?? new Date(Date.now() - 5 * 60 * 1000).toISOString();
|
|
248
|
+
|
|
249
|
+
// Helper to query events based on filters
|
|
250
|
+
const queryEvents = (queryOpts: { since: string; limit: number }): StoredEvent[] => {
|
|
251
|
+
if (runId) {
|
|
252
|
+
return eventStore.getByRun(runId, queryOpts);
|
|
253
|
+
}
|
|
254
|
+
if (agentNames.length > 0) {
|
|
255
|
+
const allEvents: StoredEvent[] = [];
|
|
256
|
+
for (const name of agentNames) {
|
|
257
|
+
const agentEvents = eventStore.getByAgent(name, {
|
|
258
|
+
since: queryOpts.since,
|
|
259
|
+
});
|
|
260
|
+
allEvents.push(...agentEvents);
|
|
261
|
+
}
|
|
262
|
+
// Sort by createdAt chronologically
|
|
263
|
+
allEvents.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
264
|
+
// Apply limit after merge
|
|
265
|
+
return allEvents.slice(0, queryOpts.limit);
|
|
266
|
+
}
|
|
267
|
+
return eventStore.getTimeline(queryOpts);
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
if (!follow) {
|
|
271
|
+
// Non-follow mode: single snapshot
|
|
272
|
+
const events = queryEvents({ since, limit });
|
|
273
|
+
|
|
274
|
+
if (json) {
|
|
275
|
+
process.stdout.write(`${JSON.stringify(events)}\n`);
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (events.length === 0) {
|
|
280
|
+
process.stdout.write("No events found.\n");
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const colorMap = buildAgentColorMap(events);
|
|
285
|
+
for (const event of events) {
|
|
286
|
+
printEvent(event, colorMap);
|
|
287
|
+
}
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Follow mode: continuous polling
|
|
292
|
+
// Print initial events
|
|
293
|
+
let lastSeenId = 0;
|
|
294
|
+
const initialEvents = queryEvents({ since, limit });
|
|
295
|
+
|
|
296
|
+
if (!json) {
|
|
297
|
+
const colorMap = buildAgentColorMap(initialEvents);
|
|
298
|
+
for (const event of initialEvents) {
|
|
299
|
+
printEvent(event, colorMap);
|
|
300
|
+
}
|
|
301
|
+
if (initialEvents.length > 0) {
|
|
302
|
+
const lastEvent = initialEvents[initialEvents.length - 1];
|
|
303
|
+
if (lastEvent) {
|
|
304
|
+
lastSeenId = lastEvent.id;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
} else {
|
|
308
|
+
// JSON mode: print each event as a line
|
|
309
|
+
for (const event of initialEvents) {
|
|
310
|
+
process.stdout.write(`${JSON.stringify(event)}\n`);
|
|
311
|
+
}
|
|
312
|
+
if (initialEvents.length > 0) {
|
|
313
|
+
const lastEvent = initialEvents[initialEvents.length - 1];
|
|
314
|
+
if (lastEvent) {
|
|
315
|
+
lastSeenId = lastEvent.id;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Maintain a color map across polling iterations (for non-JSON mode)
|
|
321
|
+
const globalColorMap = buildAgentColorMap(initialEvents);
|
|
322
|
+
|
|
323
|
+
// Poll for new events
|
|
324
|
+
while (true) {
|
|
325
|
+
await new Promise((resolve) => setTimeout(resolve, interval));
|
|
326
|
+
|
|
327
|
+
// Query events from 60s ago, then filter client-side for id > lastSeenId
|
|
328
|
+
const pollSince = new Date(Date.now() - 60 * 1000).toISOString();
|
|
329
|
+
const recentEvents = queryEvents({ since: pollSince, limit: 1000 });
|
|
330
|
+
|
|
331
|
+
// Filter to new events only
|
|
332
|
+
const newEvents = recentEvents.filter((e) => e.id > lastSeenId);
|
|
333
|
+
|
|
334
|
+
if (newEvents.length > 0) {
|
|
335
|
+
if (!json) {
|
|
336
|
+
// Update color map for any new agents
|
|
337
|
+
for (const event of newEvents) {
|
|
338
|
+
if (!globalColorMap.has(event.agentName)) {
|
|
339
|
+
const colorIndex = globalColorMap.size % AGENT_COLORS.length;
|
|
340
|
+
const agentColor = AGENT_COLORS[colorIndex];
|
|
341
|
+
if (agentColor !== undefined) {
|
|
342
|
+
globalColorMap.set(event.agentName, agentColor);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Print new events
|
|
348
|
+
for (const event of newEvents) {
|
|
349
|
+
printEvent(event, globalColorMap);
|
|
350
|
+
}
|
|
351
|
+
} else {
|
|
352
|
+
// JSON mode: print each event as a line
|
|
353
|
+
for (const event of newEvents) {
|
|
354
|
+
process.stdout.write(`${JSON.stringify(event)}\n`);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Update lastSeenId
|
|
359
|
+
const lastNew = newEvents[newEvents.length - 1];
|
|
360
|
+
if (lastNew) {
|
|
361
|
+
lastSeenId = lastNew.id;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
} finally {
|
|
366
|
+
eventStore.close();
|
|
367
|
+
}
|
|
368
|
+
}
|