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