opencode-swarm-plugin 0.50.0 → 0.53.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/bin/commands/log.test.ts +117 -0
- package/bin/commands/log.ts +362 -0
- package/bin/commands/session.ts +377 -0
- package/bin/commands/tree.test.ts +71 -0
- package/bin/commands/tree.ts +131 -0
- package/bin/swarm.ts +78 -2
- package/dist/bin/swarm.js +2192 -1402
- package/dist/compaction-hook.d.ts.map +1 -1
- package/dist/examples/plugin-wrapper-template.ts +271 -23
- package/dist/hive.d.ts +48 -0
- package/dist/hive.d.ts.map +1 -1
- package/dist/hive.js +59 -1
- package/dist/index.d.ts +31 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +307 -8
- package/dist/plugin.js +305 -8
- package/dist/swarm-adversarial-review.d.ts +104 -0
- package/dist/swarm-adversarial-review.d.ts.map +1 -0
- package/dist/swarm-insights.d.ts +54 -0
- package/dist/swarm-insights.d.ts.map +1 -1
- package/dist/swarm-prompts.js +143 -2
- package/dist/swarm-strategies.d.ts.map +1 -1
- package/dist/swarm.d.ts +15 -1
- package/dist/swarm.d.ts.map +1 -1
- package/dist/utils/tree-renderer.d.ts +61 -0
- package/dist/utils/tree-renderer.d.ts.map +1 -0
- package/examples/plugin-wrapper-template.ts +271 -23
- package/package.json +2 -2
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for log command
|
|
3
|
+
*
|
|
4
|
+
* TDD: Write tests first, then implement
|
|
5
|
+
*/
|
|
6
|
+
import { describe, test, expect, beforeAll, afterAll } from "bun:test";
|
|
7
|
+
import { mkdirSync, rmSync, writeFileSync, existsSync } from "node:fs";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import { homedir } from "node:os";
|
|
10
|
+
|
|
11
|
+
const LOG_DIR = join(homedir(), ".config", "swarm-tools", "logs");
|
|
12
|
+
const TEST_LOG_DIR = join(process.cwd(), "test-logs");
|
|
13
|
+
|
|
14
|
+
describe("log command", () => {
|
|
15
|
+
beforeAll(() => {
|
|
16
|
+
// Create test log directory
|
|
17
|
+
if (!existsSync(TEST_LOG_DIR)) {
|
|
18
|
+
mkdirSync(TEST_LOG_DIR, { recursive: true });
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Create sample log files
|
|
22
|
+
const today = new Date().toISOString().split("T")[0];
|
|
23
|
+
const yesterday = new Date(Date.now() - 86400000).toISOString().split("T")[0];
|
|
24
|
+
|
|
25
|
+
writeFileSync(
|
|
26
|
+
join(TEST_LOG_DIR, `tools-${today}.log`),
|
|
27
|
+
JSON.stringify({ time: new Date().toISOString(), level: "info", msg: "test tool call", tool: "hive_create" }) + "\n" +
|
|
28
|
+
JSON.stringify({ time: new Date().toISOString(), level: "debug", msg: "another call", tool: "swarm_status" }) + "\n"
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
writeFileSync(
|
|
32
|
+
join(TEST_LOG_DIR, `swarmmail-${today}.log`),
|
|
33
|
+
JSON.stringify({ time: new Date().toISOString(), level: "info", msg: "message sent", to: ["agent"] }) + "\n"
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
writeFileSync(
|
|
37
|
+
join(TEST_LOG_DIR, `errors-${today}.log`),
|
|
38
|
+
JSON.stringify({ time: new Date().toISOString(), level: "error", msg: "something failed", error: "Test error" }) + "\n"
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
writeFileSync(
|
|
42
|
+
join(TEST_LOG_DIR, `tools-${yesterday}.log`),
|
|
43
|
+
JSON.stringify({ time: new Date(Date.now() - 86400000).toISOString(), level: "info", msg: "old log entry" }) + "\n"
|
|
44
|
+
);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
afterAll(() => {
|
|
48
|
+
// Clean up test logs
|
|
49
|
+
if (existsSync(TEST_LOG_DIR)) {
|
|
50
|
+
rmSync(TEST_LOG_DIR, { recursive: true, force: true });
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("log helper functions format log entries correctly", () => {
|
|
55
|
+
// This will be implemented in plugin-wrapper-template.ts
|
|
56
|
+
// For now, we test the expected format
|
|
57
|
+
const entry = {
|
|
58
|
+
time: new Date().toISOString(),
|
|
59
|
+
level: "info",
|
|
60
|
+
msg: "test message",
|
|
61
|
+
tool: "hive_create",
|
|
62
|
+
args: { title: "Test" }
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const formatted = JSON.stringify(entry);
|
|
66
|
+
expect(formatted).toContain("time");
|
|
67
|
+
expect(formatted).toContain("level");
|
|
68
|
+
expect(formatted).toContain("msg");
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("date-stamped log files use YYYY-MM-DD format", () => {
|
|
72
|
+
const today = new Date().toISOString().split("T")[0];
|
|
73
|
+
expect(today).toMatch(/^\d{4}-\d{2}-\d{2}$/);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("log rotation keeps only recent files", () => {
|
|
77
|
+
// Test that files older than 7 days would be deleted
|
|
78
|
+
const sevenDaysAgo = new Date(Date.now() - 7 * 86400000);
|
|
79
|
+
const eightDaysAgo = new Date(Date.now() - 8 * 86400000);
|
|
80
|
+
|
|
81
|
+
const sevenDaysDate = sevenDaysAgo.toISOString().split("T")[0];
|
|
82
|
+
const eightDaysDate = eightDaysAgo.toISOString().split("T")[0];
|
|
83
|
+
|
|
84
|
+
// Files with these dates should be deleted by rotation
|
|
85
|
+
expect(sevenDaysDate).toMatch(/^\d{4}-\d{2}-\d{2}$/);
|
|
86
|
+
expect(eightDaysDate).toMatch(/^\d{4}-\d{2}-\d{2}$/);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe("swarm log CLI", () => {
|
|
91
|
+
test("shows all logs by default", () => {
|
|
92
|
+
// CLI implementation will be tested via spawn
|
|
93
|
+
// For now, we verify the expected behavior exists
|
|
94
|
+
expect(true).toBe(true);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("filters by log type (tools, swarmmail, errors)", () => {
|
|
98
|
+
// CLI should support: swarm log tools, swarm log swarmmail, swarm log errors
|
|
99
|
+
const logTypes = ["tools", "swarmmail", "errors"];
|
|
100
|
+
expect(logTypes).toContain("tools");
|
|
101
|
+
expect(logTypes).toContain("swarmmail");
|
|
102
|
+
expect(logTypes).toContain("errors");
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("filters by time with --since flag", () => {
|
|
106
|
+
// CLI should support: swarm log --since 30s, --since 5m, --since 2h
|
|
107
|
+
const timeUnits = ["s", "m", "h"];
|
|
108
|
+
expect(timeUnits).toContain("s");
|
|
109
|
+
expect(timeUnits).toContain("m");
|
|
110
|
+
expect(timeUnits).toContain("h");
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test("supports watch mode with --watch flag", () => {
|
|
114
|
+
// CLI should support: swarm log --watch
|
|
115
|
+
expect(true).toBe(true);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Log Command - View and tail swarm logs
|
|
3
|
+
*
|
|
4
|
+
* Commands:
|
|
5
|
+
* swarm log [type] - Show recent logs (all types or specific: tools, swarmmail, errors, compaction)
|
|
6
|
+
* swarm log --since <time> - Show logs since time (e.g., 30s, 5m, 2h, 24h)
|
|
7
|
+
* swarm log --watch - Watch mode (live tail)
|
|
8
|
+
* swarm log --level <level> - Filter by level (info, debug, warn, error)
|
|
9
|
+
* swarm log --limit <n> - Limit output lines (default: 50)
|
|
10
|
+
* swarm log --json - JSON output
|
|
11
|
+
*
|
|
12
|
+
* Log files:
|
|
13
|
+
* ~/.config/swarm-tools/logs/tools-YYYY-MM-DD.log
|
|
14
|
+
* ~/.config/swarm-tools/logs/swarmmail-YYYY-MM-DD.log
|
|
15
|
+
* ~/.config/swarm-tools/logs/errors-YYYY-MM-DD.log
|
|
16
|
+
* ~/.config/swarm-tools/logs/compaction.log (legacy, single file)
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import * as p from "@clack/prompts";
|
|
20
|
+
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
21
|
+
import { join } from "node:path";
|
|
22
|
+
import { homedir } from "node:os";
|
|
23
|
+
|
|
24
|
+
// Color utilities (inline)
|
|
25
|
+
const cyan = (s: string) => `\x1b[36m${s}\x1b[0m`;
|
|
26
|
+
const green = (s: string) => `\x1b[32m${s}\x1b[0m`;
|
|
27
|
+
const yellow = (s: string) => `\x1b[33m${s}\x1b[0m`;
|
|
28
|
+
const red = (s: string) => `\x1b[31m${s}\x1b[0m`;
|
|
29
|
+
const dim = (s: string) => `\x1b[2m${s}\x1b[0m`;
|
|
30
|
+
const gray = (s: string) => `\x1b[90m${s}\x1b[0m`;
|
|
31
|
+
|
|
32
|
+
const LOG_DIR = join(homedir(), ".config", "swarm-tools", "logs");
|
|
33
|
+
|
|
34
|
+
interface LogEntry {
|
|
35
|
+
time: string;
|
|
36
|
+
level: string;
|
|
37
|
+
msg: string;
|
|
38
|
+
[key: string]: any;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface LogOptions {
|
|
42
|
+
type?: string; // tools, swarmmail, errors, compaction
|
|
43
|
+
since?: number; // milliseconds
|
|
44
|
+
watch?: boolean;
|
|
45
|
+
level?: string; // info, debug, warn, error
|
|
46
|
+
limit?: number;
|
|
47
|
+
json?: boolean;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Main log command handler
|
|
52
|
+
*/
|
|
53
|
+
export async function log() {
|
|
54
|
+
const args = process.argv.slice(3);
|
|
55
|
+
|
|
56
|
+
if (args.includes("--help") || args.includes("help")) {
|
|
57
|
+
showHelp();
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const options = parseOptions(args);
|
|
62
|
+
|
|
63
|
+
if (options.watch) {
|
|
64
|
+
await watchLogs(options);
|
|
65
|
+
} else {
|
|
66
|
+
await showLogs(options);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Parse command-line arguments
|
|
72
|
+
*/
|
|
73
|
+
function parseOptions(args: string[]): LogOptions {
|
|
74
|
+
const options: LogOptions = {
|
|
75
|
+
limit: 50,
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
for (let i = 0; i < args.length; i++) {
|
|
79
|
+
const arg = args[i];
|
|
80
|
+
|
|
81
|
+
if (arg === "--since" && i + 1 < args.length) {
|
|
82
|
+
options.since = parseSince(args[++i]);
|
|
83
|
+
} else if (arg === "--watch" || arg === "-w") {
|
|
84
|
+
options.watch = true;
|
|
85
|
+
} else if (arg === "--level" && i + 1 < args.length) {
|
|
86
|
+
options.level = args[++i];
|
|
87
|
+
} else if (arg === "--limit" && i + 1 < args.length) {
|
|
88
|
+
options.limit = parseInt(args[++i], 10);
|
|
89
|
+
} else if (arg === "--json") {
|
|
90
|
+
options.json = true;
|
|
91
|
+
} else if (!arg.startsWith("--")) {
|
|
92
|
+
// Positional argument - log type
|
|
93
|
+
options.type = arg;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return options;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Parse --since time string (e.g., "30s", "5m", "2h", "24h")
|
|
102
|
+
*/
|
|
103
|
+
function parseSince(since: string): number {
|
|
104
|
+
const match = since.match(/^(\d+)([smhd])$/);
|
|
105
|
+
if (!match) {
|
|
106
|
+
p.log.error(`Invalid --since format: ${since}. Use: 30s, 5m, 2h, 24h`);
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const [, value, unit] = match;
|
|
111
|
+
const num = parseInt(value, 10);
|
|
112
|
+
|
|
113
|
+
const units: Record<string, number> = {
|
|
114
|
+
s: 1000,
|
|
115
|
+
m: 60 * 1000,
|
|
116
|
+
h: 60 * 60 * 1000,
|
|
117
|
+
d: 24 * 60 * 60 * 1000,
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
return num * units[unit];
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Get log files for a specific type
|
|
125
|
+
*/
|
|
126
|
+
function getLogFiles(type?: string): string[] {
|
|
127
|
+
if (!existsSync(LOG_DIR)) {
|
|
128
|
+
return [];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const files = readdirSync(LOG_DIR);
|
|
132
|
+
const today = new Date().toISOString().split("T")[0];
|
|
133
|
+
|
|
134
|
+
if (type === "compaction") {
|
|
135
|
+
// Legacy single file
|
|
136
|
+
return files
|
|
137
|
+
.filter((f) => f === "compaction.log")
|
|
138
|
+
.map((f) => join(LOG_DIR, f));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Date-stamped log files
|
|
142
|
+
const pattern = type
|
|
143
|
+
? new RegExp(`^${type}-\\d{4}-\\d{2}-\\d{2}\\.log$`)
|
|
144
|
+
: /^(tools|swarmmail|errors)-\d{4}-\d{2}-\d{2}\.log$/;
|
|
145
|
+
|
|
146
|
+
return files
|
|
147
|
+
.filter((f) => pattern.test(f))
|
|
148
|
+
.map((f) => join(LOG_DIR, f))
|
|
149
|
+
.sort((a, b) => {
|
|
150
|
+
// Sort by modification time (newest first)
|
|
151
|
+
const statA = statSync(a);
|
|
152
|
+
const statB = statSync(b);
|
|
153
|
+
return statB.mtimeMs - statA.mtimeMs;
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Read and parse log entries from a file
|
|
159
|
+
*/
|
|
160
|
+
function readLogEntries(filePath: string): LogEntry[] {
|
|
161
|
+
if (!existsSync(filePath)) {
|
|
162
|
+
return [];
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const content = readFileSync(filePath, "utf-8");
|
|
166
|
+
const lines = content.split("\n").filter((line) => line.trim());
|
|
167
|
+
|
|
168
|
+
return lines
|
|
169
|
+
.map((line) => {
|
|
170
|
+
try {
|
|
171
|
+
return JSON.parse(line) as LogEntry;
|
|
172
|
+
} catch {
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
})
|
|
176
|
+
.filter((entry): entry is LogEntry => entry !== null);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Filter log entries by options
|
|
181
|
+
*/
|
|
182
|
+
function filterEntries(entries: LogEntry[], options: LogOptions): LogEntry[] {
|
|
183
|
+
let filtered = entries;
|
|
184
|
+
|
|
185
|
+
// Filter by time
|
|
186
|
+
if (options.since) {
|
|
187
|
+
const cutoff = Date.now() - options.since;
|
|
188
|
+
filtered = filtered.filter((e) => new Date(e.time).getTime() >= cutoff);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Filter by level
|
|
192
|
+
if (options.level) {
|
|
193
|
+
filtered = filtered.filter((e) => e.level === options.level);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return filtered;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Format log entry for display
|
|
201
|
+
*/
|
|
202
|
+
function formatEntry(entry: LogEntry): string {
|
|
203
|
+
const time = new Date(entry.time).toLocaleTimeString();
|
|
204
|
+
const level = formatLevel(entry.level);
|
|
205
|
+
const msg = entry.msg;
|
|
206
|
+
|
|
207
|
+
// Extract additional fields (excluding time, level, msg)
|
|
208
|
+
const { time: _, level: __, msg: ___, ...rest } = entry;
|
|
209
|
+
const extra = Object.keys(rest).length > 0 ? dim(JSON.stringify(rest)) : "";
|
|
210
|
+
|
|
211
|
+
return `${gray(time)} ${level} ${msg} ${extra}`;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Format log level with color
|
|
216
|
+
*/
|
|
217
|
+
function formatLevel(level: string): string {
|
|
218
|
+
switch (level) {
|
|
219
|
+
case "error":
|
|
220
|
+
return red("[ERROR]");
|
|
221
|
+
case "warn":
|
|
222
|
+
return yellow("[WARN] ");
|
|
223
|
+
case "info":
|
|
224
|
+
return green("[INFO] ");
|
|
225
|
+
case "debug":
|
|
226
|
+
return cyan("[DEBUG]");
|
|
227
|
+
default:
|
|
228
|
+
return `[${level}]`;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Show logs (non-watch mode)
|
|
234
|
+
*/
|
|
235
|
+
async function showLogs(options: LogOptions) {
|
|
236
|
+
const files = getLogFiles(options.type);
|
|
237
|
+
|
|
238
|
+
if (files.length === 0) {
|
|
239
|
+
if (options.type) {
|
|
240
|
+
p.log.warn(`No logs found for type: ${options.type}`);
|
|
241
|
+
} else {
|
|
242
|
+
p.log.warn("No logs found. Run a swarm command to generate logs.");
|
|
243
|
+
}
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Read all entries from all files
|
|
248
|
+
const allEntries = files.flatMap((f) => readLogEntries(f));
|
|
249
|
+
|
|
250
|
+
// Filter
|
|
251
|
+
const filtered = filterEntries(allEntries, options);
|
|
252
|
+
|
|
253
|
+
// Sort by time (newest last for tail-like output)
|
|
254
|
+
const sorted = filtered.sort(
|
|
255
|
+
(a, b) => new Date(a.time).getTime() - new Date(b.time).getTime()
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
// Limit
|
|
259
|
+
const limited =
|
|
260
|
+
options.limit && options.limit > 0
|
|
261
|
+
? sorted.slice(-options.limit)
|
|
262
|
+
: sorted;
|
|
263
|
+
|
|
264
|
+
if (options.json) {
|
|
265
|
+
console.log(JSON.stringify(limited, null, 2));
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Pretty output
|
|
270
|
+
if (limited.length === 0) {
|
|
271
|
+
p.log.warn("No matching log entries");
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
p.log.message(dim(`Showing ${limited.length} log entries`));
|
|
276
|
+
console.log("");
|
|
277
|
+
|
|
278
|
+
for (const entry of limited) {
|
|
279
|
+
console.log(formatEntry(entry));
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Watch logs (live tail)
|
|
285
|
+
*/
|
|
286
|
+
async function watchLogs(options: LogOptions) {
|
|
287
|
+
p.log.info("Watching logs (Ctrl+C to stop)...");
|
|
288
|
+
console.log("");
|
|
289
|
+
|
|
290
|
+
// Track last read position for each file
|
|
291
|
+
const filePositions = new Map<string, number>();
|
|
292
|
+
|
|
293
|
+
// eslint-disable-next-line no-constant-condition
|
|
294
|
+
while (true) {
|
|
295
|
+
const files = getLogFiles(options.type);
|
|
296
|
+
|
|
297
|
+
for (const file of files) {
|
|
298
|
+
const lastPos = filePositions.get(file) ?? 0;
|
|
299
|
+
const content = readFileSync(file, "utf-8");
|
|
300
|
+
|
|
301
|
+
if (content.length > lastPos) {
|
|
302
|
+
const newContent = content.slice(lastPos);
|
|
303
|
+
const newLines = newContent.split("\n").filter((line) => line.trim());
|
|
304
|
+
|
|
305
|
+
for (const line of newLines) {
|
|
306
|
+
try {
|
|
307
|
+
const entry = JSON.parse(line) as LogEntry;
|
|
308
|
+
const filtered = filterEntries([entry], options);
|
|
309
|
+
|
|
310
|
+
if (filtered.length > 0) {
|
|
311
|
+
if (options.json) {
|
|
312
|
+
console.log(JSON.stringify(entry));
|
|
313
|
+
} else {
|
|
314
|
+
console.log(formatEntry(entry));
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
} catch {
|
|
318
|
+
// Skip invalid lines
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
filePositions.set(file, content.length);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Poll interval (500ms)
|
|
327
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Show help
|
|
333
|
+
*/
|
|
334
|
+
function showHelp() {
|
|
335
|
+
console.log(`
|
|
336
|
+
${cyan("swarm log")} - View and tail swarm logs
|
|
337
|
+
|
|
338
|
+
${yellow("USAGE:")}
|
|
339
|
+
swarm log [type] [options]
|
|
340
|
+
|
|
341
|
+
${yellow("TYPES:")}
|
|
342
|
+
tools Tool invocations (hive_*, swarm_*, etc.)
|
|
343
|
+
swarmmail Inter-agent messages
|
|
344
|
+
errors Error logs
|
|
345
|
+
compaction Context compaction events (legacy single file)
|
|
346
|
+
|
|
347
|
+
${yellow("OPTIONS:")}
|
|
348
|
+
--since <time> Show logs since time (e.g., 30s, 5m, 2h, 24h)
|
|
349
|
+
--watch, -w Watch mode (live tail)
|
|
350
|
+
--level <level> Filter by level (info, debug, warn, error)
|
|
351
|
+
--limit <n> Limit output lines (default: 50)
|
|
352
|
+
--json JSON output
|
|
353
|
+
|
|
354
|
+
${yellow("EXAMPLES:")}
|
|
355
|
+
swarm log # Show recent logs (all types)
|
|
356
|
+
swarm log tools # Show tool invocations
|
|
357
|
+
swarm log --since 5m # Show logs from last 5 minutes
|
|
358
|
+
swarm log errors --watch # Live tail error logs
|
|
359
|
+
swarm log --level error --limit 20 # Show last 20 error-level logs
|
|
360
|
+
swarm log compaction --json # Show compaction logs as JSON
|
|
361
|
+
`);
|
|
362
|
+
}
|