squads-cli 0.1.2 → 0.3.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.
@@ -0,0 +1,915 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/lib/sessions.ts
4
+ import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, unlinkSync, appendFileSync, createReadStream } from "fs";
5
+ import { join, dirname } from "path";
6
+ import { randomBytes } from "crypto";
7
+ import { createInterface } from "readline";
8
+ import { execSync } from "child_process";
9
+ var AI_TOOL_PATTERNS = [
10
+ { pattern: /^claude$/, name: "claude" },
11
+ { pattern: /^cursor$/i, name: "cursor" },
12
+ { pattern: /^aider$/, name: "aider" },
13
+ { pattern: /^gemini$/i, name: "gemini" },
14
+ { pattern: /^copilot$/i, name: "copilot" },
15
+ { pattern: /^cody$/i, name: "cody" },
16
+ { pattern: /^continue$/i, name: "continue" }
17
+ ];
18
+ var STALE_THRESHOLD_MS = 5 * 60 * 1e3;
19
+ var HISTORY_FILE = "history.jsonl";
20
+ var ACTIVE_DIR = "active";
21
+ var SQUAD_DIR_MAP = {
22
+ "hq": "company",
23
+ "agents-squads-web": "website",
24
+ "company": "company",
25
+ "product": "product",
26
+ "engineering": "engineering",
27
+ "research": "research",
28
+ "intelligence": "intelligence",
29
+ "customer": "customer",
30
+ "finance": "finance",
31
+ "marketing": "marketing"
32
+ };
33
+ function findAgentsDir() {
34
+ let dir = process.cwd();
35
+ for (let i = 0; i < 5; i++) {
36
+ const agentsPath = join(dir, ".agents");
37
+ if (existsSync(agentsPath)) {
38
+ return agentsPath;
39
+ }
40
+ const parent = dirname(dir);
41
+ if (parent === dir) break;
42
+ dir = parent;
43
+ }
44
+ return null;
45
+ }
46
+ function getSessionsBaseDir() {
47
+ const agentsDir = findAgentsDir();
48
+ if (!agentsDir) return null;
49
+ const sessionsDir = join(agentsDir, "sessions");
50
+ if (!existsSync(sessionsDir)) {
51
+ mkdirSync(sessionsDir, { recursive: true });
52
+ }
53
+ return sessionsDir;
54
+ }
55
+ function getSessionsDir() {
56
+ const baseDir = getSessionsBaseDir();
57
+ if (!baseDir) return null;
58
+ const activeDir = join(baseDir, ACTIVE_DIR);
59
+ if (!existsSync(activeDir)) {
60
+ mkdirSync(activeDir, { recursive: true });
61
+ }
62
+ return activeDir;
63
+ }
64
+ function getHistoryFilePath() {
65
+ const baseDir = getSessionsBaseDir();
66
+ if (!baseDir) return null;
67
+ return join(baseDir, HISTORY_FILE);
68
+ }
69
+ function appendEvent(event) {
70
+ const historyPath = getHistoryFilePath();
71
+ if (!historyPath) return;
72
+ try {
73
+ const line = JSON.stringify(event) + "\n";
74
+ appendFileSync(historyPath, line);
75
+ } catch {
76
+ }
77
+ }
78
+ function detectSquad(cwd = process.cwd()) {
79
+ const match = cwd.match(/agents-squads\/([^/]+)/);
80
+ if (match) {
81
+ const dir = match[1];
82
+ return SQUAD_DIR_MAP[dir] || dir;
83
+ }
84
+ return null;
85
+ }
86
+ function detectAIProcesses() {
87
+ const processes = [];
88
+ try {
89
+ const psOutput = execSync("ps -eo pid,tty,comm 2>/dev/null", {
90
+ encoding: "utf-8",
91
+ timeout: 5e3
92
+ }).trim();
93
+ if (!psOutput) return [];
94
+ const lines = psOutput.split("\n").filter((line) => line.trim());
95
+ for (const line of lines) {
96
+ const parts = line.trim().split(/\s+/);
97
+ if (parts.length < 3) continue;
98
+ const pid = parseInt(parts[0], 10);
99
+ const tty = parts[1];
100
+ const comm = parts.slice(2).join(" ");
101
+ if (isNaN(pid)) continue;
102
+ let toolName = null;
103
+ for (const { pattern, name } of AI_TOOL_PATTERNS) {
104
+ if (pattern.test(comm)) {
105
+ toolName = name;
106
+ break;
107
+ }
108
+ }
109
+ if (!toolName) continue;
110
+ let cwd = "";
111
+ try {
112
+ const lsofOutput = execSync(`lsof -p ${pid} 2>/dev/null | grep cwd | awk '{print $NF}'`, {
113
+ encoding: "utf-8",
114
+ timeout: 3e3
115
+ }).trim();
116
+ cwd = lsofOutput || "";
117
+ } catch {
118
+ cwd = "";
119
+ }
120
+ const squad = detectSquad(cwd);
121
+ processes.push({
122
+ pid,
123
+ tty,
124
+ cwd,
125
+ squad,
126
+ tool: toolName
127
+ });
128
+ }
129
+ } catch {
130
+ }
131
+ return processes;
132
+ }
133
+ function getLiveSessionSummary() {
134
+ const processes = detectAIProcesses();
135
+ const bySquad = {};
136
+ const byTool = {};
137
+ for (const proc of processes) {
138
+ const squad = proc.squad || "unknown";
139
+ bySquad[squad] = (bySquad[squad] || 0) + 1;
140
+ byTool[proc.tool] = (byTool[proc.tool] || 0) + 1;
141
+ }
142
+ return {
143
+ totalSessions: processes.length,
144
+ bySquad,
145
+ squadCount: Object.keys(bySquad).length,
146
+ byTool
147
+ };
148
+ }
149
+ function generateSessionId() {
150
+ return randomBytes(8).toString("hex");
151
+ }
152
+ var currentSessionId = null;
153
+ function getSessionId() {
154
+ if (currentSessionId) return currentSessionId;
155
+ const sessionsDir = getSessionsDir();
156
+ if (sessionsDir) {
157
+ const pid = process.pid;
158
+ const files = readdirSync(sessionsDir).filter((f) => f.endsWith(".json"));
159
+ for (const file of files) {
160
+ try {
161
+ const content = readFileSync(join(sessionsDir, file), "utf-8");
162
+ const session = JSON.parse(content);
163
+ if (session.pid === pid) {
164
+ currentSessionId = session.sessionId;
165
+ return currentSessionId;
166
+ }
167
+ } catch {
168
+ }
169
+ }
170
+ }
171
+ currentSessionId = generateSessionId();
172
+ return currentSessionId;
173
+ }
174
+ function startSession(squad) {
175
+ const sessionsDir = getSessionsDir();
176
+ if (!sessionsDir) return null;
177
+ const sessionId = getSessionId();
178
+ const now = (/* @__PURE__ */ new Date()).toISOString();
179
+ const cwd = process.cwd();
180
+ const detectedSquad = squad || detectSquad(cwd);
181
+ const session = {
182
+ sessionId,
183
+ squad: detectedSquad,
184
+ startedAt: now,
185
+ lastHeartbeat: now,
186
+ cwd,
187
+ pid: process.pid
188
+ };
189
+ const sessionPath = join(sessionsDir, `${sessionId}.json`);
190
+ writeFileSync(sessionPath, JSON.stringify(session, null, 2));
191
+ appendEvent({
192
+ type: "start",
193
+ sessionId,
194
+ squad: detectedSquad,
195
+ ts: now,
196
+ cwd,
197
+ pid: process.pid
198
+ });
199
+ return session;
200
+ }
201
+ function updateHeartbeat() {
202
+ const sessionsDir = getSessionsDir();
203
+ if (!sessionsDir) return false;
204
+ const sessionId = getSessionId();
205
+ const sessionPath = join(sessionsDir, `${sessionId}.json`);
206
+ if (!existsSync(sessionPath)) {
207
+ startSession();
208
+ return true;
209
+ }
210
+ try {
211
+ const content = readFileSync(sessionPath, "utf-8");
212
+ const session = JSON.parse(content);
213
+ session.lastHeartbeat = (/* @__PURE__ */ new Date()).toISOString();
214
+ writeFileSync(sessionPath, JSON.stringify(session, null, 2));
215
+ return true;
216
+ } catch {
217
+ return false;
218
+ }
219
+ }
220
+ function stopSession() {
221
+ const sessionsDir = getSessionsDir();
222
+ if (!sessionsDir) return false;
223
+ const sessionId = getSessionId();
224
+ const sessionPath = join(sessionsDir, `${sessionId}.json`);
225
+ if (existsSync(sessionPath)) {
226
+ let squad = null;
227
+ let durationMs;
228
+ try {
229
+ const content = readFileSync(sessionPath, "utf-8");
230
+ const session = JSON.parse(content);
231
+ squad = session.squad;
232
+ durationMs = Date.now() - new Date(session.startedAt).getTime();
233
+ } catch {
234
+ }
235
+ unlinkSync(sessionPath);
236
+ currentSessionId = null;
237
+ appendEvent({
238
+ type: "stop",
239
+ sessionId,
240
+ squad,
241
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
242
+ durationMs
243
+ });
244
+ return true;
245
+ }
246
+ return false;
247
+ }
248
+ function getActiveSessions() {
249
+ const sessionsDir = getSessionsDir();
250
+ if (!sessionsDir) return [];
251
+ const now = Date.now();
252
+ const sessions = [];
253
+ try {
254
+ const files = readdirSync(sessionsDir).filter((f) => f.endsWith(".json"));
255
+ for (const file of files) {
256
+ try {
257
+ const filePath = join(sessionsDir, file);
258
+ const content = readFileSync(filePath, "utf-8");
259
+ const session = JSON.parse(content);
260
+ const lastHeartbeat = new Date(session.lastHeartbeat).getTime();
261
+ if (now - lastHeartbeat < STALE_THRESHOLD_MS) {
262
+ sessions.push(session);
263
+ }
264
+ } catch {
265
+ }
266
+ }
267
+ } catch {
268
+ }
269
+ return sessions;
270
+ }
271
+ function getSessionSummary() {
272
+ const sessions = getActiveSessions();
273
+ const bySquad = {};
274
+ for (const session of sessions) {
275
+ const squad = session.squad || "unknown";
276
+ bySquad[squad] = (bySquad[squad] || 0) + 1;
277
+ }
278
+ return {
279
+ totalSessions: sessions.length,
280
+ bySquad,
281
+ squadCount: Object.keys(bySquad).length
282
+ };
283
+ }
284
+ function cleanupStaleSessions() {
285
+ const sessionsDir = getSessionsDir();
286
+ if (!sessionsDir) return 0;
287
+ const now = Date.now();
288
+ const nowIso = (/* @__PURE__ */ new Date()).toISOString();
289
+ let cleaned = 0;
290
+ try {
291
+ const files = readdirSync(sessionsDir).filter((f) => f.endsWith(".json"));
292
+ for (const file of files) {
293
+ try {
294
+ const filePath = join(sessionsDir, file);
295
+ const content = readFileSync(filePath, "utf-8");
296
+ const session = JSON.parse(content);
297
+ const lastHeartbeat = new Date(session.lastHeartbeat).getTime();
298
+ if (now - lastHeartbeat >= STALE_THRESHOLD_MS) {
299
+ const durationMs = now - new Date(session.startedAt).getTime();
300
+ unlinkSync(filePath);
301
+ cleaned++;
302
+ appendEvent({
303
+ type: "stale_cleanup",
304
+ sessionId: session.sessionId,
305
+ squad: session.squad,
306
+ ts: nowIso,
307
+ durationMs
308
+ });
309
+ }
310
+ } catch {
311
+ try {
312
+ unlinkSync(join(sessionsDir, file));
313
+ cleaned++;
314
+ } catch {
315
+ }
316
+ }
317
+ }
318
+ } catch {
319
+ }
320
+ return cleaned;
321
+ }
322
+ async function readSessionHistory(options = {}) {
323
+ const historyPath = getHistoryFilePath();
324
+ if (!historyPath || !existsSync(historyPath)) return [];
325
+ const events = [];
326
+ const sinceMs = options.since?.getTime() || 0;
327
+ const untilMs = options.until?.getTime() || Date.now();
328
+ return new Promise((resolve) => {
329
+ const rl = createInterface({
330
+ input: createReadStream(historyPath),
331
+ crlfDelay: Infinity
332
+ });
333
+ rl.on("line", (line) => {
334
+ if (!line.trim()) return;
335
+ try {
336
+ const event = JSON.parse(line);
337
+ const eventMs = new Date(event.ts).getTime();
338
+ if (eventMs < sinceMs || eventMs > untilMs) return;
339
+ if (options.squad && event.squad !== options.squad) return;
340
+ if (options.type && event.type !== options.type) return;
341
+ events.push(event);
342
+ } catch {
343
+ }
344
+ });
345
+ rl.on("close", () => {
346
+ if (options.limit && events.length > options.limit) {
347
+ resolve(events.slice(-options.limit));
348
+ } else {
349
+ resolve(events);
350
+ }
351
+ });
352
+ rl.on("error", () => {
353
+ resolve([]);
354
+ });
355
+ });
356
+ }
357
+ async function getSessionHistoryStats(options = {}) {
358
+ const events = await readSessionHistory(options);
359
+ const stats = {
360
+ totalSessions: 0,
361
+ totalDurationMs: 0,
362
+ avgDurationMs: 0,
363
+ bySquad: {},
364
+ byDate: {},
365
+ peakConcurrent: 0
366
+ };
367
+ const activeSessions = /* @__PURE__ */ new Map();
368
+ let currentConcurrent = 0;
369
+ for (const event of events) {
370
+ const squad = event.squad || "unknown";
371
+ const date = event.ts.split("T")[0];
372
+ if (event.type === "start") {
373
+ stats.totalSessions++;
374
+ stats.byDate[date] = (stats.byDate[date] || 0) + 1;
375
+ if (!stats.bySquad[squad]) {
376
+ stats.bySquad[squad] = { count: 0, durationMs: 0 };
377
+ }
378
+ stats.bySquad[squad].count++;
379
+ activeSessions.set(event.sessionId, {
380
+ squad: event.squad,
381
+ startTs: new Date(event.ts).getTime()
382
+ });
383
+ currentConcurrent++;
384
+ stats.peakConcurrent = Math.max(stats.peakConcurrent, currentConcurrent);
385
+ }
386
+ if (event.type === "stop" || event.type === "stale_cleanup") {
387
+ const duration = event.durationMs || 0;
388
+ stats.totalDurationMs += duration;
389
+ if (stats.bySquad[squad]) {
390
+ stats.bySquad[squad].durationMs += duration;
391
+ }
392
+ if (activeSessions.has(event.sessionId)) {
393
+ activeSessions.delete(event.sessionId);
394
+ currentConcurrent = Math.max(0, currentConcurrent - 1);
395
+ }
396
+ }
397
+ }
398
+ const completedSessions = events.filter((e) => e.type === "stop" || e.type === "stale_cleanup").length;
399
+ if (completedSessions > 0) {
400
+ stats.avgDurationMs = Math.round(stats.totalDurationMs / completedSessions);
401
+ }
402
+ return stats;
403
+ }
404
+ async function getRecentSessions(limit = 20) {
405
+ const events = await readSessionHistory({ limit: limit * 3 });
406
+ const sessionEvents = /* @__PURE__ */ new Map();
407
+ for (const event of events) {
408
+ if (!sessionEvents.has(event.sessionId)) {
409
+ sessionEvents.set(event.sessionId, []);
410
+ }
411
+ sessionEvents.get(event.sessionId).push(event);
412
+ }
413
+ const startEvents = events.filter((e) => e.type === "start").slice(-limit);
414
+ return startEvents.reverse();
415
+ }
416
+
417
+ // src/lib/terminal.ts
418
+ var ESC = "\x1B[";
419
+ var RESET = `${ESC}0m`;
420
+ var rgb = (r, g, b) => `${ESC}38;2;${r};${g};${b}m`;
421
+ var colors = {
422
+ purple: rgb(168, 85, 247),
423
+ // #a855f7
424
+ pink: rgb(236, 72, 153),
425
+ // #ec4899
426
+ cyan: rgb(6, 182, 212),
427
+ // #06b6d4
428
+ green: rgb(16, 185, 129),
429
+ // #10b981
430
+ yellow: rgb(234, 179, 8),
431
+ // #eab308
432
+ red: rgb(239, 68, 68),
433
+ // #ef4444
434
+ gray: rgb(107, 114, 128),
435
+ // #6b7280
436
+ dim: rgb(75, 85, 99),
437
+ // #4b5563
438
+ white: rgb(255, 255, 255)
439
+ };
440
+ var bold = `${ESC}1m`;
441
+ var dim = `${ESC}2m`;
442
+ var cursor = {
443
+ hide: `${ESC}?25l`,
444
+ show: `${ESC}?25h`,
445
+ up: (n = 1) => `${ESC}${n}A`,
446
+ down: (n = 1) => `${ESC}${n}B`,
447
+ left: (n = 1) => `${ESC}${n}D`,
448
+ right: (n = 1) => `${ESC}${n}C`,
449
+ to: (x, y) => `${ESC}${y};${x}H`,
450
+ save: `${ESC}s`,
451
+ restore: `${ESC}u`
452
+ };
453
+ var clear = {
454
+ line: `${ESC}2K`,
455
+ toEnd: `${ESC}0K`,
456
+ screen: `${ESC}2J${ESC}0;0H`
457
+ };
458
+ function gradient(text) {
459
+ const stops = [
460
+ [168, 85, 247],
461
+ // purple
462
+ [192, 132, 252],
463
+ // purple-light
464
+ [232, 121, 249],
465
+ // pink
466
+ [244, 114, 182],
467
+ // pink-light
468
+ [251, 113, 133]
469
+ // rose
470
+ ];
471
+ let result = "";
472
+ for (let i = 0; i < text.length; i++) {
473
+ const t = i / Math.max(text.length - 1, 1);
474
+ const stopIndex = t * (stops.length - 1);
475
+ const lower = Math.floor(stopIndex);
476
+ const upper = Math.min(lower + 1, stops.length - 1);
477
+ const blend = stopIndex - lower;
478
+ const r = Math.round(stops[lower][0] + (stops[upper][0] - stops[lower][0]) * blend);
479
+ const g = Math.round(stops[lower][1] + (stops[upper][1] - stops[lower][1]) * blend);
480
+ const b = Math.round(stops[lower][2] + (stops[upper][2] - stops[lower][2]) * blend);
481
+ result += rgb(r, g, b) + text[i];
482
+ }
483
+ return result + RESET;
484
+ }
485
+ function progressBar(percent, width = 20) {
486
+ const filled = Math.round(percent / 100 * width);
487
+ const empty = width - filled;
488
+ let bar = "";
489
+ for (let i = 0; i < filled; i++) {
490
+ const t = i / Math.max(filled - 1, 1);
491
+ const r = Math.round(16 + (168 - 16) * t);
492
+ const g = Math.round(185 + (85 - 185) * t);
493
+ const b = Math.round(129 + (247 - 129) * t);
494
+ bar += rgb(r, g, b) + "\u2501";
495
+ }
496
+ bar += colors.dim + "\u2501".repeat(empty) + RESET;
497
+ return bar;
498
+ }
499
+ var box = {
500
+ topLeft: "\u250C",
501
+ topRight: "\u2510",
502
+ bottomLeft: "\u2514",
503
+ bottomRight: "\u2518",
504
+ horizontal: "\u2500",
505
+ vertical: "\u2502",
506
+ teeRight: "\u251C",
507
+ teeLeft: "\u2524"
508
+ };
509
+ function padEnd(str, len) {
510
+ const visible = str.replace(/\x1b\[[0-9;]*m/g, "");
511
+ const pad = Math.max(0, len - visible.length);
512
+ return str + " ".repeat(pad);
513
+ }
514
+ function truncate(str, len) {
515
+ const visible = str.replace(/\x1b\[[0-9;]*m/g, "");
516
+ if (visible.length <= len) return str;
517
+ let result = "";
518
+ let count = 0;
519
+ let i = 0;
520
+ while (i < str.length && count < len - 1) {
521
+ if (str[i] === "\x1B") {
522
+ const end = str.indexOf("m", i);
523
+ if (end !== -1) {
524
+ result += str.slice(i, end + 1);
525
+ i = end + 1;
526
+ continue;
527
+ }
528
+ }
529
+ result += str[i];
530
+ count++;
531
+ i++;
532
+ }
533
+ return result + colors.dim + "\u2026" + RESET;
534
+ }
535
+ var icons = {
536
+ success: `${colors.green}\u25CF${RESET}`,
537
+ warning: `${colors.yellow}\u25CB${RESET}`,
538
+ error: `${colors.red}\u25CF${RESET}`,
539
+ pending: `${colors.dim}\u25CB${RESET}`,
540
+ active: `${colors.green}\u25CF${RESET}`,
541
+ progress: `${colors.cyan}\u25C6${RESET}`,
542
+ empty: `${colors.dim}\u25C7${RESET}`
543
+ };
544
+ function writeLine(str = "") {
545
+ process.stdout.write(str + "\n");
546
+ }
547
+ function sparkline(values, width) {
548
+ if (values.length === 0) return "";
549
+ const blocks = ["\u2581", "\u2582", "\u2583", "\u2584", "\u2585", "\u2586", "\u2587", "\u2588"];
550
+ const max = Math.max(...values, 1);
551
+ let result = "";
552
+ for (const val of values) {
553
+ const normalized = val / max;
554
+ const blockIndex = Math.min(Math.floor(normalized * blocks.length), blocks.length - 1);
555
+ const intensity = normalized;
556
+ if (normalized === 0) {
557
+ result += colors.dim + blocks[0];
558
+ } else if (normalized < 0.5) {
559
+ result += colors.cyan + blocks[blockIndex];
560
+ } else {
561
+ result += colors.green + blocks[blockIndex];
562
+ }
563
+ }
564
+ return result + RESET;
565
+ }
566
+ function barChart(value, max, width = 20, label) {
567
+ const filled = Math.round(value / max * width);
568
+ const empty = width - filled;
569
+ let bar = "";
570
+ for (let i = 0; i < filled; i++) {
571
+ const t = i / Math.max(filled - 1, 1);
572
+ const r = Math.round(16 + (6 - 16) * t);
573
+ const g = Math.round(185 + (182 - 185) * t);
574
+ const b = Math.round(129 + (212 - 129) * t);
575
+ bar += rgb(r, g, b) + "\u2501";
576
+ }
577
+ bar += colors.dim + "\u2501".repeat(empty) + RESET;
578
+ if (label) {
579
+ return `${bar} ${label}`;
580
+ }
581
+ return bar;
582
+ }
583
+
584
+ // src/commands/sessions.ts
585
+ async function sessionsCommand(options = {}) {
586
+ cleanupStaleSessions();
587
+ const sessions = getActiveSessions();
588
+ const summary = getSessionSummary();
589
+ if (options.json) {
590
+ console.log(JSON.stringify({ sessions, summary }, null, 2));
591
+ return;
592
+ }
593
+ writeLine();
594
+ writeLine(` ${gradient("squads")} ${colors.dim}sessions${RESET}`);
595
+ writeLine();
596
+ if (sessions.length === 0) {
597
+ writeLine(` ${colors.dim}No active sessions${RESET}`);
598
+ writeLine();
599
+ writeLine(` ${colors.dim}Sessions are tracked automatically when Claude Code runs.${RESET}`);
600
+ writeLine(` ${colors.dim}Each session updates its heartbeat via squads CLI commands.${RESET}`);
601
+ writeLine();
602
+ return;
603
+ }
604
+ const squadText = summary.squadCount === 1 ? "squad" : "squads";
605
+ const sessionText = summary.totalSessions === 1 ? "session" : "sessions";
606
+ writeLine(` ${colors.green}${summary.totalSessions}${RESET} active ${sessionText} ${colors.dim}across${RESET} ${colors.cyan}${summary.squadCount}${RESET} ${squadText}`);
607
+ writeLine();
608
+ const bySquad = {};
609
+ for (const session of sessions) {
610
+ const squad = session.squad || "unknown";
611
+ if (!bySquad[squad]) bySquad[squad] = [];
612
+ bySquad[squad].push(session);
613
+ }
614
+ const w = { squad: 16, sessions: 10, activity: 14 };
615
+ const tableWidth = w.squad + w.sessions + w.activity + 4;
616
+ writeLine(` ${colors.purple}${box.topLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.topRight}${RESET}`);
617
+ const header = ` ${colors.purple}${box.vertical}${RESET} ${bold}${padEnd("SQUAD", w.squad)}${RESET}${bold}${padEnd("SESSIONS", w.sessions)}${RESET}${bold}LAST ACTIVITY${RESET} ${colors.purple}${box.vertical}${RESET}`;
618
+ writeLine(header);
619
+ writeLine(` ${colors.purple}${box.teeRight}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.teeLeft}${RESET}`);
620
+ for (const [squad, squadSessions] of Object.entries(bySquad).sort()) {
621
+ let mostRecent = 0;
622
+ for (const session of squadSessions) {
623
+ const ts = new Date(session.lastHeartbeat).getTime();
624
+ if (ts > mostRecent) mostRecent = ts;
625
+ }
626
+ const lastActivity = formatTimeAgo(mostRecent);
627
+ const activityColor = getActivityColor(mostRecent);
628
+ const row = ` ${colors.purple}${box.vertical}${RESET} ${colors.cyan}${padEnd(squad, w.squad)}${RESET}${padEnd(String(squadSessions.length), w.sessions)}${padEnd(`${activityColor}${lastActivity}${RESET}`, w.activity)}${colors.purple}${box.vertical}${RESET}`;
629
+ writeLine(row);
630
+ }
631
+ writeLine(` ${colors.purple}${box.bottomLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.bottomRight}${RESET}`);
632
+ if (options.verbose) {
633
+ writeLine();
634
+ writeLine(` ${bold}Session Details${RESET}`);
635
+ writeLine();
636
+ for (const session of sessions) {
637
+ const squad = session.squad || "unknown";
638
+ const ago = formatTimeAgo(new Date(session.lastHeartbeat).getTime());
639
+ writeLine(` ${icons.active} ${colors.white}${session.sessionId}${RESET}`);
640
+ writeLine(` ${colors.dim}squad: ${squad} | pid: ${session.pid} | heartbeat: ${ago}${RESET}`);
641
+ writeLine(` ${colors.dim}cwd: ${session.cwd}${RESET}`);
642
+ }
643
+ }
644
+ writeLine();
645
+ writeLine(` ${colors.dim}$${RESET} squads sessions -v ${colors.dim}Show session details${RESET}`);
646
+ writeLine();
647
+ }
648
+ function formatTimeAgo(timestamp) {
649
+ const now = Date.now();
650
+ const diff = now - timestamp;
651
+ const seconds = Math.floor(diff / 1e3);
652
+ const minutes = Math.floor(seconds / 60);
653
+ if (minutes >= 1) {
654
+ return `${minutes}m ago`;
655
+ }
656
+ return `${seconds}s ago`;
657
+ }
658
+ function getActivityColor(timestamp) {
659
+ const now = Date.now();
660
+ const diff = now - timestamp;
661
+ const minutes = Math.floor(diff / (1e3 * 60));
662
+ if (minutes < 1) return colors.green;
663
+ if (minutes < 3) return colors.yellow;
664
+ return colors.dim;
665
+ }
666
+ async function sessionsSummaryCommand(data, options = {}) {
667
+ if (options.json) {
668
+ console.log(JSON.stringify(data, null, 2));
669
+ return;
670
+ }
671
+ writeLine();
672
+ writeLine(` ${gradient("squads")} ${colors.dim}session summary${RESET}`);
673
+ writeLine();
674
+ if (data.squads.length > 0) {
675
+ const w = { squad: 14, actions: 26, outputs: 36 };
676
+ const tableWidth = w.squad + w.actions + w.outputs + 6;
677
+ const truncate2 = (text, max) => text.length > max ? text.substring(0, max - 1) + "\u2026" : text;
678
+ writeLine(` ${colors.green}${icons.active}${RESET} ${bold}${data.squads.length} Squads Active${RESET}`);
679
+ writeLine();
680
+ writeLine(` ${colors.purple}${box.topLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.topRight}${RESET}`);
681
+ const header = ` ${colors.purple}${box.vertical}${RESET} ${bold}${padEnd("SQUAD", w.squad)}${RESET}${bold}${padEnd("ACTIONS", w.actions)}${RESET}${bold}${padEnd("KEY OUTPUTS", w.outputs)}${RESET}${colors.purple}${box.vertical}${RESET}`;
682
+ writeLine(header);
683
+ writeLine(` ${colors.purple}${box.teeRight}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.teeLeft}${RESET}`);
684
+ for (const squad of data.squads) {
685
+ const row = ` ${colors.purple}${box.vertical}${RESET} ${colors.cyan}${padEnd(truncate2(squad.name, w.squad - 1), w.squad)}${RESET}${padEnd(truncate2(squad.actions, w.actions - 1), w.actions)}${padEnd(truncate2(squad.outputs, w.outputs - 1), w.outputs)}${colors.purple}${box.vertical}${RESET}`;
686
+ writeLine(row);
687
+ }
688
+ writeLine(` ${colors.purple}${box.bottomLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.bottomRight}${RESET}`);
689
+ }
690
+ if (data.decisions && data.decisions.length > 0) {
691
+ writeLine();
692
+ writeLine(` ${bold}Strategic Decisions${RESET}`);
693
+ writeLine();
694
+ const w = { question: 16, answer: 50 };
695
+ const tableWidth = w.question + w.answer + 4;
696
+ writeLine(` ${colors.purple}${box.topLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.topRight}${RESET}`);
697
+ for (const decision of data.decisions) {
698
+ const row = ` ${colors.purple}${box.vertical}${RESET} ${colors.yellow}${padEnd(decision.question, w.question)}${RESET}${padEnd(decision.answer, w.answer)}${colors.purple}${box.vertical}${RESET}`;
699
+ writeLine(row);
700
+ }
701
+ writeLine(` ${colors.purple}${box.bottomLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.bottomRight}${RESET}`);
702
+ }
703
+ if (data.customer) {
704
+ writeLine();
705
+ writeLine(` ${bold}Target Customer${RESET}`);
706
+ writeLine();
707
+ writeLine(` ${colors.dim}Vertical:${RESET} ${colors.cyan}${data.customer.vertical}${RESET}`);
708
+ writeLine(` ${colors.dim}Persona:${RESET} ${colors.white}${data.customer.persona}${RESET}`);
709
+ writeLine(` ${colors.dim}Pain:${RESET} ${data.customer.painPoints.join(", ")}`);
710
+ }
711
+ if (data.nextActions && data.nextActions.length > 0) {
712
+ writeLine();
713
+ writeLine(` ${bold}Next Actions${RESET}`);
714
+ writeLine();
715
+ for (const action of data.nextActions) {
716
+ writeLine(` ${colors.cyan}${padEnd(action.squad, 14)}${RESET}${colors.dim}\u2192${RESET} ${action.action}`);
717
+ }
718
+ }
719
+ if (data.targets && data.targets.length > 0) {
720
+ writeLine();
721
+ writeLine(` ${bold}Q1 Targets${RESET}`);
722
+ writeLine();
723
+ for (const target of data.targets) {
724
+ writeLine(` ${colors.dim}\u2022${RESET} ${target.metric}: ${colors.green}${target.value}${RESET}`);
725
+ }
726
+ }
727
+ if (data.filesUpdated && data.filesUpdated.length > 0) {
728
+ writeLine();
729
+ writeLine(` ${colors.dim}Files updated:${RESET}`);
730
+ for (const file of data.filesUpdated) {
731
+ writeLine(` ${colors.dim} \u2022${RESET} ${colors.cyan}${file}${RESET}`);
732
+ }
733
+ }
734
+ writeLine();
735
+ const modelText = data.model ? data.model : "Claude";
736
+ const durationText = data.duration ? ` ${colors.dim}(${data.duration})${RESET}` : "";
737
+ writeLine(` ${colors.dim}Generated by${RESET} ${colors.purple}${modelText}${RESET}${durationText}`);
738
+ writeLine();
739
+ }
740
+ async function buildCurrentSessionSummary() {
741
+ const { existsSync: existsSync2, readdirSync: readdirSync2, statSync, readFileSync: readFileSync2 } = await import("fs");
742
+ const { join: join2 } = await import("path");
743
+ const { findMemoryDir } = await import("./memory-4PVUKIDK.js");
744
+ const memoryDir = findMemoryDir();
745
+ const squads = [];
746
+ const filesUpdated = [];
747
+ const sessionWindow = 2 * 60 * 60 * 1e3;
748
+ const now = Date.now();
749
+ if (memoryDir && existsSync2(memoryDir)) {
750
+ const squadDirs = readdirSync2(memoryDir, { withFileTypes: true }).filter((d) => d.isDirectory());
751
+ for (const squadDir of squadDirs) {
752
+ const squadPath = join2(memoryDir, squadDir.name);
753
+ let squadModified = false;
754
+ let stateContent = "";
755
+ let executionContent = "";
756
+ try {
757
+ const agentDirs = readdirSync2(squadPath, { withFileTypes: true }).filter((d) => d.isDirectory());
758
+ for (const agentDir of agentDirs) {
759
+ const agentPath = join2(squadPath, agentDir.name);
760
+ const files = readdirSync2(agentPath).filter((f) => f.endsWith(".md"));
761
+ for (const file of files) {
762
+ const filePath = join2(agentPath, file);
763
+ const stats = statSync(filePath);
764
+ if (now - stats.mtimeMs < sessionWindow) {
765
+ squadModified = true;
766
+ const relativePath = `${squadDir.name}/${agentDir.name}/${file}`;
767
+ filesUpdated.push(relativePath);
768
+ if (file === "state.md") {
769
+ stateContent = readFileSync2(filePath, "utf-8");
770
+ } else if (file === "executions.md") {
771
+ executionContent = readFileSync2(filePath, "utf-8");
772
+ }
773
+ }
774
+ }
775
+ }
776
+ if (squadModified) {
777
+ let actions = "State updated";
778
+ let outputs = "Memory refreshed";
779
+ if (executionContent) {
780
+ const lines = executionContent.split("\n").filter((l) => l.trim());
781
+ const recentEntry = lines.slice(-10).join(" ");
782
+ if (recentEntry.includes("completed")) {
783
+ actions = "Execution completed";
784
+ }
785
+ const keyMatch = recentEntry.match(/Key (?:findings|decisions|outputs)?:?\s*([^.]+)/i);
786
+ if (keyMatch) {
787
+ outputs = keyMatch[1].substring(0, 50);
788
+ }
789
+ }
790
+ if (stateContent) {
791
+ const updatedMatch = stateContent.match(/Updated:\s*([^\n]+)/);
792
+ if (updatedMatch) {
793
+ actions = `Updated ${updatedMatch[1]}`;
794
+ }
795
+ }
796
+ squads.push({
797
+ name: squadDir.name.charAt(0).toUpperCase() + squadDir.name.slice(1),
798
+ actions,
799
+ outputs: outputs.length > 44 ? outputs.substring(0, 41) + "..." : outputs
800
+ });
801
+ }
802
+ } catch {
803
+ }
804
+ }
805
+ }
806
+ if (squads.length === 0) {
807
+ squads.push({
808
+ name: "No recent activity",
809
+ actions: "\u2014",
810
+ outputs: "Run squads to see activity here"
811
+ });
812
+ }
813
+ return {
814
+ squads,
815
+ filesUpdated: filesUpdated.length > 0 ? filesUpdated : void 0,
816
+ model: process.env.ANTHROPIC_MODEL || "Claude"
817
+ };
818
+ }
819
+ async function sessionsHistoryCommand(options = {}) {
820
+ const days = options.days || 7;
821
+ const since = new Date(Date.now() - days * 24 * 60 * 60 * 1e3);
822
+ const stats = await getSessionHistoryStats({
823
+ since,
824
+ squad: options.squad
825
+ });
826
+ const recentSessions = await getRecentSessions(10);
827
+ if (options.json) {
828
+ console.log(JSON.stringify({ stats, recentSessions }, null, 2));
829
+ return;
830
+ }
831
+ writeLine();
832
+ writeLine(` ${gradient("squads")} ${colors.dim}sessions history${RESET} ${colors.dim}(${days}d)${RESET}`);
833
+ writeLine();
834
+ if (stats.totalSessions === 0) {
835
+ writeLine(` ${colors.dim}No session history found${RESET}`);
836
+ writeLine();
837
+ writeLine(` ${colors.dim}Session events are logged to .agents/sessions/history.jsonl${RESET}`);
838
+ writeLine();
839
+ return;
840
+ }
841
+ const avgMinutes = Math.round(stats.avgDurationMs / 6e4);
842
+ const totalHours = Math.round(stats.totalDurationMs / 36e5 * 10) / 10;
843
+ writeLine(` ${bold}Summary${RESET}`);
844
+ writeLine(` ${colors.cyan}${stats.totalSessions}${RESET} sessions ${colors.dim}\u2502${RESET} ${colors.green}${totalHours}h${RESET} total ${colors.dim}\u2502${RESET} ${colors.yellow}${avgMinutes}m${RESET} avg ${colors.dim}\u2502${RESET} ${colors.purple}${stats.peakConcurrent}${RESET} peak`);
845
+ writeLine();
846
+ const squads = Object.entries(stats.bySquad).sort((a, b) => b[1].count - a[1].count);
847
+ if (squads.length > 0) {
848
+ const w = { squad: 16, sessions: 10, duration: 12 };
849
+ const tableWidth = w.squad + w.sessions + w.duration + 4;
850
+ writeLine(` ${bold}By Squad${RESET}`);
851
+ writeLine();
852
+ writeLine(` ${colors.purple}${box.topLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.topRight}${RESET}`);
853
+ const header = ` ${colors.purple}${box.vertical}${RESET} ${bold}${padEnd("SQUAD", w.squad)}${RESET}${bold}${padEnd("SESSIONS", w.sessions)}${RESET}${bold}DURATION${RESET} ${colors.purple}${box.vertical}${RESET}`;
854
+ writeLine(header);
855
+ writeLine(` ${colors.purple}${box.teeRight}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.teeLeft}${RESET}`);
856
+ for (const [squad, data] of squads) {
857
+ const hours = Math.round(data.durationMs / 36e5 * 10) / 10;
858
+ const row = ` ${colors.purple}${box.vertical}${RESET} ${colors.cyan}${padEnd(squad, w.squad)}${RESET}${padEnd(String(data.count), w.sessions)}${padEnd(`${hours}h`, w.duration)}${colors.purple}${box.vertical}${RESET}`;
859
+ writeLine(row);
860
+ }
861
+ writeLine(` ${colors.purple}${box.bottomLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.bottomRight}${RESET}`);
862
+ }
863
+ if (recentSessions.length > 0) {
864
+ writeLine();
865
+ writeLine(` ${bold}Recent Sessions${RESET}`);
866
+ writeLine();
867
+ for (const event of recentSessions.slice(0, 5)) {
868
+ const squad = event.squad || "unknown";
869
+ const date = new Date(event.ts);
870
+ const timeStr = date.toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit" });
871
+ const dateStr = date.toLocaleDateString("en-US", { month: "short", day: "numeric" });
872
+ writeLine(` ${colors.dim}${dateStr} ${timeStr}${RESET} ${colors.cyan}${squad}${RESET} ${colors.dim}${event.sessionId.slice(0, 8)}${RESET}`);
873
+ }
874
+ }
875
+ const dates = Object.entries(stats.byDate).sort((a, b) => b[0].localeCompare(a[0])).slice(0, 7);
876
+ if (dates.length > 1) {
877
+ writeLine();
878
+ writeLine(` ${bold}Daily Activity${RESET}`);
879
+ writeLine();
880
+ for (const [date, count] of dates) {
881
+ const bar = "\u2588".repeat(Math.min(count, 20));
882
+ writeLine(` ${colors.dim}${date}${RESET} ${colors.green}${bar}${RESET} ${count}`);
883
+ }
884
+ }
885
+ writeLine();
886
+ writeLine(` ${colors.dim}$${RESET} squads sessions history --days 30 ${colors.dim}Longer history${RESET}`);
887
+ writeLine(` ${colors.dim}$${RESET} squads sessions history -s website ${colors.dim}Filter by squad${RESET}`);
888
+ writeLine();
889
+ }
890
+
891
+ export {
892
+ RESET,
893
+ colors,
894
+ bold,
895
+ gradient,
896
+ progressBar,
897
+ box,
898
+ padEnd,
899
+ truncate,
900
+ icons,
901
+ writeLine,
902
+ sparkline,
903
+ barChart,
904
+ detectSquad,
905
+ getLiveSessionSummary,
906
+ startSession,
907
+ updateHeartbeat,
908
+ stopSession,
909
+ cleanupStaleSessions,
910
+ sessionsCommand,
911
+ sessionsSummaryCommand,
912
+ buildCurrentSessionSummary,
913
+ sessionsHistoryCommand
914
+ };
915
+ //# sourceMappingURL=chunk-266URT5W.js.map