squads-cli 0.4.6 → 0.4.7
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/README.md +38 -0
- package/dist/{chunk-G63RBKDH.js → chunk-EGOVJOZJ.js} +2 -1
- package/dist/chunk-EGOVJOZJ.js.map +1 -0
- package/dist/cli.js +1841 -83
- package/dist/cli.js.map +1 -1
- package/dist/{sessions-JCQ34BEU.js → sessions-SEITSWEV.js} +2 -2
- package/docker/init-db.sql +79 -0
- package/docker/squads-bridge/squads_bridge.py +179 -16
- package/package.json +4 -1
- package/dist/chunk-G63RBKDH.js.map +0 -1
- /package/dist/{sessions-JCQ34BEU.js.map → sessions-SEITSWEV.js.map} +0 -0
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
sessionsCommand,
|
|
5
5
|
sessionsHistoryCommand,
|
|
6
6
|
sessionsSummaryCommand
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-EGOVJOZJ.js";
|
|
8
8
|
import "./chunk-7OCVIDC7.js";
|
|
9
9
|
export {
|
|
10
10
|
buildCurrentSessionSummary,
|
|
@@ -12,4 +12,4 @@ export {
|
|
|
12
12
|
sessionsHistoryCommand,
|
|
13
13
|
sessionsSummaryCommand
|
|
14
14
|
};
|
|
15
|
-
//# sourceMappingURL=sessions-
|
|
15
|
+
//# sourceMappingURL=sessions-SEITSWEV.js.map
|
package/docker/init-db.sql
CHANGED
|
@@ -154,6 +154,11 @@ CREATE TABLE IF NOT EXISTS squads.llm_generations (
|
|
|
154
154
|
-- Cost
|
|
155
155
|
cost_usd NUMERIC(10,6) DEFAULT 0,
|
|
156
156
|
|
|
157
|
+
-- Execution context (for per-agent cost tracking)
|
|
158
|
+
task_type VARCHAR(50) DEFAULT 'execution', -- evaluation, execution, research, lead
|
|
159
|
+
trigger_source VARCHAR(50) DEFAULT 'manual', -- manual, scheduled, event, smart
|
|
160
|
+
execution_id VARCHAR(100), -- exec_<timestamp>_<random> for grouping
|
|
161
|
+
|
|
157
162
|
-- Timing
|
|
158
163
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
159
164
|
duration_ms INTEGER,
|
|
@@ -210,6 +215,8 @@ CREATE TABLE IF NOT EXISTS squads.sessions (
|
|
|
210
215
|
CREATE INDEX IF NOT EXISTS idx_llm_generations_session ON squads.llm_generations(session_id);
|
|
211
216
|
CREATE INDEX IF NOT EXISTS idx_llm_generations_squad ON squads.llm_generations(squad, agent);
|
|
212
217
|
CREATE INDEX IF NOT EXISTS idx_llm_generations_created ON squads.llm_generations(created_at DESC);
|
|
218
|
+
CREATE INDEX IF NOT EXISTS idx_llm_generations_task_type ON squads.llm_generations(task_type);
|
|
219
|
+
CREATE INDEX IF NOT EXISTS idx_llm_generations_execution ON squads.llm_generations(execution_id) WHERE execution_id IS NOT NULL;
|
|
213
220
|
CREATE INDEX IF NOT EXISTS idx_tool_executions_session ON squads.tool_executions(session_id);
|
|
214
221
|
CREATE INDEX IF NOT EXISTS idx_tool_executions_tool ON squads.tool_executions(tool_name);
|
|
215
222
|
CREATE INDEX IF NOT EXISTS idx_sessions_squad ON squads.sessions(squad, agent);
|
|
@@ -393,6 +400,78 @@ CREATE INDEX IF NOT EXISTS idx_tasks_created ON squads.tasks(started_at DESC);
|
|
|
393
400
|
CREATE INDEX IF NOT EXISTS idx_task_feedback_task ON squads.task_feedback(task_id);
|
|
394
401
|
CREATE INDEX IF NOT EXISTS idx_agent_insights_lookup ON squads.agent_insights(squad, period, period_start DESC);
|
|
395
402
|
|
|
403
|
+
-- =============================================================================
|
|
404
|
+
-- SQUAD GOALS: Automatic agent binding and progress tracking
|
|
405
|
+
-- =============================================================================
|
|
406
|
+
|
|
407
|
+
-- Goals table - centralized goal tracking with numeric targets
|
|
408
|
+
CREATE TABLE IF NOT EXISTS squads.squad_goals (
|
|
409
|
+
id SERIAL PRIMARY KEY,
|
|
410
|
+
squad VARCHAR(100) NOT NULL,
|
|
411
|
+
description TEXT NOT NULL,
|
|
412
|
+
|
|
413
|
+
-- Progress tracking
|
|
414
|
+
completed BOOLEAN DEFAULT FALSE,
|
|
415
|
+
progress_text TEXT,
|
|
416
|
+
progress_value NUMERIC(5,2) DEFAULT 0, -- 0.00 to 100.00
|
|
417
|
+
|
|
418
|
+
-- Numeric target (required for auto-agent binding)
|
|
419
|
+
target_value NUMERIC,
|
|
420
|
+
target_unit VARCHAR(50), -- leads, revenue, posts, features, etc.
|
|
421
|
+
|
|
422
|
+
-- Status
|
|
423
|
+
status VARCHAR(50) DEFAULT 'active', -- active, at_risk, on_hold, completed, blocked
|
|
424
|
+
|
|
425
|
+
-- Timing
|
|
426
|
+
deadline DATE,
|
|
427
|
+
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
428
|
+
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
429
|
+
|
|
430
|
+
-- Metadata
|
|
431
|
+
metadata JSONB DEFAULT '{}'::jsonb
|
|
432
|
+
);
|
|
433
|
+
|
|
434
|
+
-- Goal-agent bindings (automatic or manual)
|
|
435
|
+
CREATE TABLE IF NOT EXISTS squads.goal_agents (
|
|
436
|
+
id SERIAL PRIMARY KEY,
|
|
437
|
+
goal_id INTEGER NOT NULL REFERENCES squads.squad_goals(id) ON DELETE CASCADE,
|
|
438
|
+
squad VARCHAR(100) NOT NULL,
|
|
439
|
+
agent VARCHAR(100) NOT NULL,
|
|
440
|
+
|
|
441
|
+
-- Assignment metadata
|
|
442
|
+
assignment_type VARCHAR(20) DEFAULT 'auto', -- auto, manual
|
|
443
|
+
role VARCHAR(20), -- primary, supporting
|
|
444
|
+
confidence NUMERIC(3,2), -- 0.00-1.00 for auto-matching quality
|
|
445
|
+
|
|
446
|
+
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
447
|
+
|
|
448
|
+
UNIQUE(goal_id, agent)
|
|
449
|
+
);
|
|
450
|
+
|
|
451
|
+
-- Goal triggers (link goals to execution triggers)
|
|
452
|
+
CREATE TABLE IF NOT EXISTS squads.goal_triggers (
|
|
453
|
+
id SERIAL PRIMARY KEY,
|
|
454
|
+
goal_id INTEGER NOT NULL REFERENCES squads.squad_goals(id) ON DELETE CASCADE,
|
|
455
|
+
trigger_id INTEGER NOT NULL REFERENCES squads.triggers(id) ON DELETE CASCADE,
|
|
456
|
+
|
|
457
|
+
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
458
|
+
|
|
459
|
+
UNIQUE(goal_id, trigger_id)
|
|
460
|
+
);
|
|
461
|
+
|
|
462
|
+
-- Add goal_id to trigger_executions (link executions to goals)
|
|
463
|
+
ALTER TABLE squads.trigger_executions
|
|
464
|
+
ADD COLUMN IF NOT EXISTS goal_id INTEGER REFERENCES squads.squad_goals(id) ON DELETE SET NULL;
|
|
465
|
+
|
|
466
|
+
-- Indexes for goal queries
|
|
467
|
+
CREATE INDEX IF NOT EXISTS idx_squad_goals_squad ON squads.squad_goals(squad);
|
|
468
|
+
CREATE INDEX IF NOT EXISTS idx_squad_goals_status ON squads.squad_goals(status) WHERE status IN ('active', 'at_risk');
|
|
469
|
+
CREATE INDEX IF NOT EXISTS idx_squad_goals_deadline ON squads.squad_goals(deadline) WHERE deadline IS NOT NULL;
|
|
470
|
+
CREATE INDEX IF NOT EXISTS idx_goal_agents_goal ON squads.goal_agents(goal_id);
|
|
471
|
+
CREATE INDEX IF NOT EXISTS idx_goal_agents_squad ON squads.goal_agents(squad);
|
|
472
|
+
CREATE INDEX IF NOT EXISTS idx_goal_triggers_goal ON squads.goal_triggers(goal_id);
|
|
473
|
+
CREATE INDEX IF NOT EXISTS idx_trigger_executions_goal ON squads.trigger_executions(goal_id) WHERE goal_id IS NOT NULL;
|
|
474
|
+
|
|
396
475
|
-- Grant permissions
|
|
397
476
|
GRANT ALL PRIVILEGES ON SCHEMA squads TO squads;
|
|
398
477
|
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA squads TO squads;
|
|
@@ -10,7 +10,7 @@ import gzip
|
|
|
10
10
|
import threading
|
|
11
11
|
import time
|
|
12
12
|
import requests
|
|
13
|
-
from datetime import datetime, date
|
|
13
|
+
from datetime import datetime, date, timedelta
|
|
14
14
|
from collections import deque
|
|
15
15
|
from flask import Flask, request, jsonify
|
|
16
16
|
import psycopg2
|
|
@@ -398,20 +398,23 @@ def ensure_session(conn, session_id, squad, agent, user_id):
|
|
|
398
398
|
""", (session_id, squad, agent, user_id or None))
|
|
399
399
|
|
|
400
400
|
|
|
401
|
-
def save_generation(conn, session_id, squad, agent, user_id, model, token_data
|
|
401
|
+
def save_generation(conn, session_id, squad, agent, user_id, model, token_data,
|
|
402
|
+
task_type="execution", trigger_source="manual", execution_id=None):
|
|
402
403
|
"""Save LLM generation to postgres + Redis."""
|
|
403
404
|
with conn.cursor() as cur:
|
|
404
405
|
cur.execute("""
|
|
405
406
|
INSERT INTO squads.llm_generations
|
|
406
407
|
(session_id, squad, agent, user_id, model,
|
|
407
|
-
input_tokens, output_tokens, cache_read_tokens, cache_creation_tokens, cost_usd
|
|
408
|
-
|
|
408
|
+
input_tokens, output_tokens, cache_read_tokens, cache_creation_tokens, cost_usd,
|
|
409
|
+
task_type, trigger_source, execution_id)
|
|
410
|
+
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
|
409
411
|
RETURNING id
|
|
410
412
|
""", (
|
|
411
413
|
session_id, squad, agent, user_id or None, model,
|
|
412
414
|
token_data["input_tokens"], token_data["output_tokens"],
|
|
413
415
|
token_data["cache_read"], token_data["cache_creation"],
|
|
414
|
-
token_data["cost_usd"]
|
|
416
|
+
token_data["cost_usd"],
|
|
417
|
+
task_type, trigger_source, execution_id
|
|
415
418
|
))
|
|
416
419
|
gen_id = cur.fetchone()[0]
|
|
417
420
|
|
|
@@ -483,19 +486,36 @@ def receive_logs():
|
|
|
483
486
|
|
|
484
487
|
service_name = resource_attrs.get("service.name", "claude-code")
|
|
485
488
|
|
|
486
|
-
# Detect squad/agent context
|
|
489
|
+
# Detect squad/agent context - check OTel attrs first, then registered context
|
|
490
|
+
registered_ctx = get_context_for_session()
|
|
491
|
+
|
|
487
492
|
squad_name = (
|
|
488
493
|
resource_attrs.get("squad") or
|
|
489
494
|
resource_attrs.get("squads.squad") or
|
|
490
|
-
|
|
495
|
+
registered_ctx.get("squad") or
|
|
491
496
|
"hq"
|
|
492
497
|
)
|
|
493
498
|
agent_name = (
|
|
494
499
|
resource_attrs.get("agent") or
|
|
495
500
|
resource_attrs.get("squads.agent") or
|
|
496
|
-
|
|
501
|
+
registered_ctx.get("agent") or
|
|
497
502
|
"coo"
|
|
498
503
|
)
|
|
504
|
+
# Extract telemetry context (added for per-agent cost tracking)
|
|
505
|
+
task_type = (
|
|
506
|
+
resource_attrs.get("squads.task_type") or
|
|
507
|
+
registered_ctx.get("task_type") or
|
|
508
|
+
"execution"
|
|
509
|
+
)
|
|
510
|
+
trigger_source = (
|
|
511
|
+
resource_attrs.get("squads.trigger") or
|
|
512
|
+
registered_ctx.get("trigger") or
|
|
513
|
+
"manual"
|
|
514
|
+
)
|
|
515
|
+
execution_id = (
|
|
516
|
+
resource_attrs.get("squads.execution_id") or
|
|
517
|
+
None # Will be set by registered context if available
|
|
518
|
+
)
|
|
499
519
|
|
|
500
520
|
for scope_log in resource_log.get("scopeLogs", []):
|
|
501
521
|
for log_record in scope_log.get("logRecords", []):
|
|
@@ -527,22 +547,29 @@ def receive_logs():
|
|
|
527
547
|
# Save to postgres (primary)
|
|
528
548
|
gen_id = save_generation(
|
|
529
549
|
conn, session_id, squad_name, agent_name,
|
|
530
|
-
user_id, model, token_data
|
|
550
|
+
user_id, model, token_data,
|
|
551
|
+
task_type, trigger_source, execution_id
|
|
531
552
|
)
|
|
532
|
-
print(f"[PG] Generation #{gen_id}: {model} {token_data['input_tokens']}+{token_data['output_tokens']} tokens ${token_data['cost_usd']:.4f}")
|
|
553
|
+
print(f"[PG] Generation #{gen_id}: {model} {token_data['input_tokens']}+{token_data['output_tokens']} tokens ${token_data['cost_usd']:.4f} [{task_type}]")
|
|
533
554
|
|
|
534
555
|
# Forward to Langfuse (optional)
|
|
535
556
|
if langfuse:
|
|
536
557
|
try:
|
|
558
|
+
# Name format: squad/agent for easy filtering
|
|
559
|
+
trace_name = f"{squad_name}/{agent_name}" if agent_name != "coo" else f"llm:{model}"
|
|
537
560
|
trace = langfuse.trace(
|
|
538
|
-
name=
|
|
561
|
+
name=trace_name,
|
|
539
562
|
user_id=user_id or None,
|
|
540
563
|
session_id=session_id,
|
|
541
564
|
metadata={
|
|
542
565
|
"squad": squad_name,
|
|
543
566
|
"agent": agent_name,
|
|
567
|
+
"task_type": task_type,
|
|
568
|
+
"trigger": trigger_source,
|
|
569
|
+
"execution_id": execution_id,
|
|
544
570
|
"service": service_name,
|
|
545
571
|
},
|
|
572
|
+
tags=[squad_name, agent_name, task_type], # Enable filtering
|
|
546
573
|
)
|
|
547
574
|
trace.generation(
|
|
548
575
|
name=f"llm:{model}",
|
|
@@ -614,6 +641,80 @@ def receive_logs():
|
|
|
614
641
|
return jsonify({"error": str(e)}), 500
|
|
615
642
|
|
|
616
643
|
|
|
644
|
+
# Context registry for agent executions (maps session patterns to context)
|
|
645
|
+
# Used when OTel doesn't include resource attributes
|
|
646
|
+
execution_contexts = {} # execution_id -> {squad, agent, task_type, trigger, expires_at}
|
|
647
|
+
|
|
648
|
+
|
|
649
|
+
@app.route("/api/context/register", methods=["POST"])
|
|
650
|
+
def register_context():
|
|
651
|
+
"""Register execution context for upcoming Claude session.
|
|
652
|
+
|
|
653
|
+
CLI calls this before launching Claude, so the bridge knows
|
|
654
|
+
which squad/agent/task_type to associate with incoming telemetry.
|
|
655
|
+
"""
|
|
656
|
+
try:
|
|
657
|
+
data = request.get_json() or {}
|
|
658
|
+
execution_id = data.get("execution_id")
|
|
659
|
+
if not execution_id:
|
|
660
|
+
return jsonify({"error": "execution_id required"}), 400
|
|
661
|
+
|
|
662
|
+
# Store context with 2-hour expiry
|
|
663
|
+
expires_at = datetime.now() + timedelta(hours=2)
|
|
664
|
+
execution_contexts[execution_id] = {
|
|
665
|
+
"squad": data.get("squad", "hq"),
|
|
666
|
+
"agent": data.get("agent", "coo"),
|
|
667
|
+
"task_type": data.get("task_type", "execution"),
|
|
668
|
+
"trigger": data.get("trigger", "manual"),
|
|
669
|
+
"expires_at": expires_at,
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
# Also store in Redis for persistence across restarts
|
|
673
|
+
if redis_client:
|
|
674
|
+
redis_client.setex(
|
|
675
|
+
f"context:{execution_id}",
|
|
676
|
+
7200, # 2 hours TTL
|
|
677
|
+
json.dumps({
|
|
678
|
+
"squad": data.get("squad", "hq"),
|
|
679
|
+
"agent": data.get("agent", "coo"),
|
|
680
|
+
"task_type": data.get("task_type", "execution"),
|
|
681
|
+
"trigger": data.get("trigger", "manual"),
|
|
682
|
+
})
|
|
683
|
+
)
|
|
684
|
+
|
|
685
|
+
# Store as "latest" context for this squad/agent combination
|
|
686
|
+
key = f"{data.get('squad', 'hq')}:{data.get('agent', 'coo')}"
|
|
687
|
+
if redis_client:
|
|
688
|
+
redis_client.setex(f"context:latest:{key}", 3600, execution_id)
|
|
689
|
+
|
|
690
|
+
print(f"[CONTEXT] Registered {execution_id}: {data.get('squad')}/{data.get('agent')} [{data.get('task_type')}]")
|
|
691
|
+
return jsonify({"status": "ok", "execution_id": execution_id}), 200
|
|
692
|
+
|
|
693
|
+
except Exception as e:
|
|
694
|
+
return jsonify({"error": str(e)}), 500
|
|
695
|
+
|
|
696
|
+
|
|
697
|
+
def get_context_for_session() -> dict:
|
|
698
|
+
"""Get the most recent execution context.
|
|
699
|
+
|
|
700
|
+
Returns the latest registered context that hasn't expired.
|
|
701
|
+
This is used when OTel data doesn't include resource attributes.
|
|
702
|
+
"""
|
|
703
|
+
# Clean expired contexts
|
|
704
|
+
now = datetime.now()
|
|
705
|
+
expired = [k for k, v in execution_contexts.items() if v.get("expires_at", now) < now]
|
|
706
|
+
for k in expired:
|
|
707
|
+
del execution_contexts[k]
|
|
708
|
+
|
|
709
|
+
# Return most recent context
|
|
710
|
+
if execution_contexts:
|
|
711
|
+
# Get the most recently registered context
|
|
712
|
+
latest = max(execution_contexts.items(), key=lambda x: x[1].get("expires_at", now))
|
|
713
|
+
return latest[1]
|
|
714
|
+
|
|
715
|
+
return {}
|
|
716
|
+
|
|
717
|
+
|
|
617
718
|
@app.route("/health", methods=["GET"])
|
|
618
719
|
def health():
|
|
619
720
|
"""Health check endpoint."""
|
|
@@ -739,6 +840,8 @@ def cost_summary():
|
|
|
739
840
|
try:
|
|
740
841
|
period = request.args.get("period", "day")
|
|
741
842
|
squad = request.args.get("squad")
|
|
843
|
+
agent = request.args.get("agent")
|
|
844
|
+
task_type = request.args.get("task_type") # evaluation, execution, research, lead
|
|
742
845
|
|
|
743
846
|
conn = get_db()
|
|
744
847
|
with conn.cursor(cursor_factory=RealDictCursor) as cur:
|
|
@@ -750,8 +853,15 @@ def cost_summary():
|
|
|
750
853
|
else: # month
|
|
751
854
|
time_filter = "created_at >= CURRENT_DATE - INTERVAL '30 days'"
|
|
752
855
|
|
|
753
|
-
#
|
|
754
|
-
|
|
856
|
+
# Build filters
|
|
857
|
+
filters = [time_filter]
|
|
858
|
+
if squad:
|
|
859
|
+
filters.append(f"squad = '{squad}'")
|
|
860
|
+
if agent:
|
|
861
|
+
filters.append(f"agent = '{agent}'")
|
|
862
|
+
if task_type:
|
|
863
|
+
filters.append(f"task_type = '{task_type}'")
|
|
864
|
+
where_clause = " AND ".join(filters)
|
|
755
865
|
|
|
756
866
|
# Aggregated stats
|
|
757
867
|
cur.execute(f"""
|
|
@@ -761,7 +871,7 @@ def cost_summary():
|
|
|
761
871
|
COALESCE(SUM(output_tokens), 0) as output_tokens,
|
|
762
872
|
COALESCE(SUM(cost_usd), 0) as total_cost_usd
|
|
763
873
|
FROM squads.llm_generations
|
|
764
|
-
WHERE {
|
|
874
|
+
WHERE {where_clause}
|
|
765
875
|
""")
|
|
766
876
|
totals = cur.fetchone()
|
|
767
877
|
|
|
@@ -780,6 +890,39 @@ def cost_summary():
|
|
|
780
890
|
""")
|
|
781
891
|
by_squad = cur.fetchall()
|
|
782
892
|
|
|
893
|
+
# By agent (NEW - for per-agent cost tracking)
|
|
894
|
+
cur.execute(f"""
|
|
895
|
+
SELECT
|
|
896
|
+
squad,
|
|
897
|
+
agent,
|
|
898
|
+
task_type,
|
|
899
|
+
COUNT(*) as generations,
|
|
900
|
+
COALESCE(SUM(input_tokens), 0) as input_tokens,
|
|
901
|
+
COALESCE(SUM(output_tokens), 0) as output_tokens,
|
|
902
|
+
COALESCE(SUM(cost_usd), 0) as cost_usd
|
|
903
|
+
FROM squads.llm_generations
|
|
904
|
+
WHERE {time_filter}
|
|
905
|
+
GROUP BY squad, agent, task_type
|
|
906
|
+
ORDER BY cost_usd DESC
|
|
907
|
+
LIMIT 50
|
|
908
|
+
""")
|
|
909
|
+
by_agent = cur.fetchall()
|
|
910
|
+
|
|
911
|
+
# By task_type (NEW - evaluation vs execution breakdown)
|
|
912
|
+
cur.execute(f"""
|
|
913
|
+
SELECT
|
|
914
|
+
task_type,
|
|
915
|
+
COUNT(*) as generations,
|
|
916
|
+
COALESCE(SUM(input_tokens), 0) as input_tokens,
|
|
917
|
+
COALESCE(SUM(output_tokens), 0) as output_tokens,
|
|
918
|
+
COALESCE(SUM(cost_usd), 0) as cost_usd
|
|
919
|
+
FROM squads.llm_generations
|
|
920
|
+
WHERE {time_filter}
|
|
921
|
+
GROUP BY task_type
|
|
922
|
+
ORDER BY cost_usd DESC
|
|
923
|
+
""")
|
|
924
|
+
by_task_type = cur.fetchall()
|
|
925
|
+
|
|
783
926
|
# By model
|
|
784
927
|
cur.execute(f"""
|
|
785
928
|
SELECT
|
|
@@ -787,7 +930,7 @@ def cost_summary():
|
|
|
787
930
|
COUNT(*) as generations,
|
|
788
931
|
COALESCE(SUM(cost_usd), 0) as cost_usd
|
|
789
932
|
FROM squads.llm_generations
|
|
790
|
-
WHERE {
|
|
933
|
+
WHERE {where_clause}
|
|
791
934
|
GROUP BY model
|
|
792
935
|
ORDER BY cost_usd DESC
|
|
793
936
|
""")
|
|
@@ -797,7 +940,11 @@ def cost_summary():
|
|
|
797
940
|
|
|
798
941
|
return jsonify({
|
|
799
942
|
"period": period,
|
|
800
|
-
"
|
|
943
|
+
"filters": {
|
|
944
|
+
"squad": squad,
|
|
945
|
+
"agent": agent,
|
|
946
|
+
"task_type": task_type,
|
|
947
|
+
},
|
|
801
948
|
"totals": {
|
|
802
949
|
"generations": totals["generation_count"],
|
|
803
950
|
"input_tokens": totals["input_tokens"],
|
|
@@ -811,6 +958,22 @@ def cost_summary():
|
|
|
811
958
|
"output_tokens": r["output_tokens"],
|
|
812
959
|
"cost_usd": float(r["cost_usd"]),
|
|
813
960
|
} for r in by_squad],
|
|
961
|
+
"by_agent": [{
|
|
962
|
+
"squad": r["squad"],
|
|
963
|
+
"agent": r["agent"],
|
|
964
|
+
"task_type": r["task_type"],
|
|
965
|
+
"generations": r["generations"],
|
|
966
|
+
"input_tokens": r["input_tokens"],
|
|
967
|
+
"output_tokens": r["output_tokens"],
|
|
968
|
+
"cost_usd": float(r["cost_usd"]),
|
|
969
|
+
} for r in by_agent],
|
|
970
|
+
"by_task_type": [{
|
|
971
|
+
"task_type": r["task_type"],
|
|
972
|
+
"generations": r["generations"],
|
|
973
|
+
"input_tokens": r["input_tokens"],
|
|
974
|
+
"output_tokens": r["output_tokens"],
|
|
975
|
+
"cost_usd": float(r["cost_usd"]),
|
|
976
|
+
} for r in by_task_type],
|
|
814
977
|
"by_model": [{
|
|
815
978
|
"model": r["model"],
|
|
816
979
|
"generations": r["generations"],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "squads-cli",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.7",
|
|
4
4
|
"description": "A CLI for humans and agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -65,6 +65,7 @@
|
|
|
65
65
|
"registry": "https://registry.npmjs.org/"
|
|
66
66
|
},
|
|
67
67
|
"dependencies": {
|
|
68
|
+
"@anthropic-ai/sdk": "^0.71.2",
|
|
68
69
|
"@supabase/supabase-js": "^2.89.0",
|
|
69
70
|
"@types/blessed": "^0.1.27",
|
|
70
71
|
"blessed": "^0.1.81",
|
|
@@ -73,7 +74,9 @@
|
|
|
73
74
|
"chrono-node": "^2.9.0",
|
|
74
75
|
"commander": "^12.1.0",
|
|
75
76
|
"dotenv": "^17.2.3",
|
|
77
|
+
"gray-matter": "^4.0.3",
|
|
76
78
|
"inquirer": "^9.2.12",
|
|
79
|
+
"minimatch": "^10.1.1",
|
|
77
80
|
"open": "^11.0.0",
|
|
78
81
|
"ora": "^8.0.1",
|
|
79
82
|
"pg": "^8.16.3"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/lib/sessions.ts","../src/lib/terminal.ts","../src/commands/sessions.ts"],"sourcesContent":["/**\n * Session tracking for active Claude Code sessions\n * Provides heartbeat-based session state management\n *\n * Storage:\n * - Active sessions: .agents/sessions/active/{id}.json (quick lookup)\n * - Event history: .agents/sessions/history.jsonl (analytics)\n */\n\nimport { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, unlinkSync, appendFileSync, createReadStream } from 'fs';\nimport { join, dirname } from 'path';\nimport { randomBytes } from 'crypto';\nimport { createInterface } from 'readline';\nimport { execSync } from 'child_process';\n\nexport interface SessionState {\n sessionId: string;\n squad: string | null;\n startedAt: string;\n lastHeartbeat: string;\n cwd: string;\n pid: number;\n}\n\nexport interface SessionSummary {\n totalSessions: number;\n bySquad: Record<string, number>;\n squadCount: number;\n byTool?: Record<string, number>; // Optional: breakdown by AI tool\n}\n\n// Event types for JSONL history\nexport type SessionEventType = 'start' | 'heartbeat' | 'stop' | 'stale_cleanup';\n\nexport interface SessionEvent {\n type: SessionEventType;\n sessionId: string;\n squad: string | null;\n ts: string;\n cwd?: string;\n pid?: number;\n durationMs?: number; // For stop events\n}\n\nexport interface SessionHistoryStats {\n totalSessions: number;\n totalDurationMs: number;\n avgDurationMs: number;\n bySquad: Record<string, { count: number; durationMs: number }>;\n byDate: Record<string, number>; // YYYY-MM-DD -> count\n peakConcurrent: number;\n}\n\n// Live AI coding assistant process info\nexport interface AIProcess {\n pid: number;\n tty: string;\n cwd: string;\n squad: string | null;\n tool: string; // 'claude', 'cursor', 'aider', 'gemini', etc.\n}\n\n// Supported AI coding tools (process names to detect)\nconst AI_TOOL_PATTERNS: { pattern: RegExp; name: string }[] = [\n { pattern: /^claude$/, name: 'claude' },\n { pattern: /^cursor$/i, name: 'cursor' },\n { pattern: /^aider$/, name: 'aider' },\n { pattern: /^gemini$/i, name: 'gemini' },\n { pattern: /^copilot$/i, name: 'copilot' },\n { pattern: /^cody$/i, name: 'cody' },\n { pattern: /^continue$/i, name: 'continue' },\n];\n\n// Session is stale after 5 minutes without heartbeat\nconst STALE_THRESHOLD_MS = 5 * 60 * 1000;\n\n// History file name\nconst HISTORY_FILE = 'history.jsonl';\n\n// Active sessions subdirectory\nconst ACTIVE_DIR = 'active';\n\n// Directory mapping for squad detection\nconst SQUAD_DIR_MAP: Record<string, string> = {\n 'hq': 'company',\n 'agents-squads-web': 'website',\n 'company': 'company',\n 'product': 'product',\n 'engineering': 'engineering',\n 'research': 'research',\n 'intelligence': 'intelligence',\n 'customer': 'customer',\n 'finance': 'finance',\n 'marketing': 'marketing',\n};\n\n/**\n * Find the .agents directory (sessions live at .agents/sessions/)\n */\nexport function findAgentsDir(): string | null {\n let dir = process.cwd();\n\n for (let i = 0; i < 5; i++) {\n const agentsPath = join(dir, '.agents');\n if (existsSync(agentsPath)) {\n return agentsPath;\n }\n const parent = dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n\n return null;\n}\n\n/**\n * Get the sessions base directory\n */\nexport function getSessionsBaseDir(): string | null {\n const agentsDir = findAgentsDir();\n if (!agentsDir) return null;\n\n const sessionsDir = join(agentsDir, 'sessions');\n if (!existsSync(sessionsDir)) {\n mkdirSync(sessionsDir, { recursive: true });\n }\n return sessionsDir;\n}\n\n/**\n * Get the active sessions directory, creating it if needed\n */\nexport function getSessionsDir(): string | null {\n const baseDir = getSessionsBaseDir();\n if (!baseDir) return null;\n\n const activeDir = join(baseDir, ACTIVE_DIR);\n if (!existsSync(activeDir)) {\n mkdirSync(activeDir, { recursive: true });\n }\n return activeDir;\n}\n\n/**\n * Get the history file path\n */\nexport function getHistoryFilePath(): string | null {\n const baseDir = getSessionsBaseDir();\n if (!baseDir) return null;\n return join(baseDir, HISTORY_FILE);\n}\n\n/**\n * Append an event to the history JSONL file\n */\nfunction appendEvent(event: SessionEvent): void {\n const historyPath = getHistoryFilePath();\n if (!historyPath) return;\n\n try {\n const line = JSON.stringify(event) + '\\n';\n appendFileSync(historyPath, line);\n } catch {\n // Silently fail - history is optional\n }\n}\n\n/**\n * Detect which squad based on current working directory\n */\nexport function detectSquad(cwd: string = process.cwd()): string | null {\n // Pattern: .../agents-squads/{domain}/...\n const match = cwd.match(/agents-squads\\/([^/]+)/);\n if (match) {\n const dir = match[1];\n return SQUAD_DIR_MAP[dir] || dir;\n }\n return null;\n}\n\n/**\n * Detect running AI coding assistant processes (fast version - no lsof)\n * Returns processes immediately without cwd/squad info\n * Supports: Claude, Cursor, Aider, Gemini, Copilot, Cody, Continue\n */\nexport function detectAIProcessesFast(): AIProcess[] {\n const processes: AIProcess[] = [];\n\n try {\n // Get all processes - we'll filter for AI tools\n // Short timeout for responsiveness - this is called during dashboard render\n const psOutput = execSync('ps -eo pid,tty,comm 2>/dev/null', {\n encoding: 'utf-8',\n timeout: 2000, // Reduced from 5s for faster CLI response\n }).trim();\n\n if (!psOutput) return [];\n\n const lines = psOutput.split('\\n').filter(line => line.trim());\n\n for (const line of lines) {\n const parts = line.trim().split(/\\s+/);\n if (parts.length < 3) continue;\n\n const pid = parseInt(parts[0], 10);\n const tty = parts[1];\n const comm = parts.slice(2).join(' '); // Process name (may have spaces)\n\n if (isNaN(pid)) continue;\n\n // Check if this is an AI coding tool\n let toolName: string | null = null;\n for (const { pattern, name } of AI_TOOL_PATTERNS) {\n if (pattern.test(comm)) {\n toolName = name;\n break;\n }\n }\n\n if (!toolName) continue;\n\n processes.push({\n pid,\n tty,\n cwd: '', // No cwd in fast mode\n squad: null, // No squad detection in fast mode\n tool: toolName,\n });\n }\n } catch {\n // Process detection failed (command not found, timeout, etc.)\n // Return empty array - graceful degradation\n }\n\n return processes;\n}\n\n/**\n * Get cwd for a single process using lsof (async)\n */\nasync function getProcessCwd(pid: number): Promise<string> {\n return new Promise((resolve) => {\n try {\n const { exec } = require('child_process');\n exec(`lsof -p ${pid} 2>/dev/null | grep cwd | awk '{print $NF}'`, {\n encoding: 'utf-8',\n timeout: 3000,\n }, (error: Error | null, stdout: string) => {\n resolve(error ? '' : stdout.trim());\n });\n } catch {\n resolve('');\n }\n });\n}\n\n/**\n * Enrich processes with cwd and squad info (async, parallel lsof)\n * Call this after detectAIProcessesFast() when you need squad info\n */\nexport async function enrichProcessesWithSquad(processes: AIProcess[]): Promise<AIProcess[]> {\n if (processes.length === 0) return processes;\n\n // Run lsof for all processes in parallel\n const cwdPromises = processes.map(p => getProcessCwd(p.pid));\n const cwds = await Promise.all(cwdPromises);\n\n // Enrich each process with cwd and squad\n return processes.map((proc, i) => ({\n ...proc,\n cwd: cwds[i],\n squad: detectSquad(cwds[i]),\n }));\n}\n\n/**\n * Detect running AI coding assistant processes (full version with squad info)\n * Synchronous wrapper for backwards compatibility - calls lsof sequentially\n * For better performance, use detectAIProcessesFast() + enrichProcessesWithSquad()\n */\nexport function detectAIProcesses(): AIProcess[] {\n const processes = detectAIProcessesFast();\n\n // Synchronously enrich with cwd/squad (backwards compatible behavior)\n for (const proc of processes) {\n try {\n const lsofOutput = execSync(`lsof -p ${proc.pid} 2>/dev/null | grep cwd | awk '{print $NF}'`, {\n encoding: 'utf-8',\n timeout: 3000,\n }).trim();\n proc.cwd = lsofOutput || '';\n proc.squad = detectSquad(proc.cwd);\n } catch {\n // Keep empty cwd and null squad\n }\n }\n\n return processes;\n}\n\n// Backwards compatibility alias\nexport const detectClaudeProcesses = detectAIProcesses;\n\n/**\n * Get live session summary using real process detection (fast version)\n * Returns count immediately, squad breakdown shows 'unknown' until enriched\n */\nexport function getLiveSessionSummaryFast(): SessionSummary {\n const processes = detectAIProcessesFast();\n const bySquad: Record<string, number> = {};\n const byTool: Record<string, number> = {};\n\n for (const proc of processes) {\n const squad = proc.squad || 'unknown';\n bySquad[squad] = (bySquad[squad] || 0) + 1;\n byTool[proc.tool] = (byTool[proc.tool] || 0) + 1;\n }\n\n return {\n totalSessions: processes.length,\n bySquad,\n squadCount: Object.keys(bySquad).length,\n byTool,\n };\n}\n\n/**\n * Get live session summary with full squad detection (async, parallel lsof)\n * Use this when you need accurate squad breakdown\n */\nexport async function getLiveSessionSummaryAsync(): Promise<SessionSummary> {\n const processes = detectAIProcessesFast();\n const enrichedProcesses = await enrichProcessesWithSquad(processes);\n\n const bySquad: Record<string, number> = {};\n const byTool: Record<string, number> = {};\n\n for (const proc of enrichedProcesses) {\n const squad = proc.squad || 'unknown';\n bySquad[squad] = (bySquad[squad] || 0) + 1;\n byTool[proc.tool] = (byTool[proc.tool] || 0) + 1;\n }\n\n return {\n totalSessions: enrichedProcesses.length,\n bySquad,\n squadCount: Object.keys(bySquad).length,\n byTool,\n };\n}\n\n/**\n * Get live session summary using real process detection (synchronous, backwards compatible)\n * For better performance, use getLiveSessionSummaryFast() or getLiveSessionSummaryAsync()\n */\nexport function getLiveSessionSummary(): SessionSummary {\n const processes = detectAIProcesses();\n const bySquad: Record<string, number> = {};\n const byTool: Record<string, number> = {};\n\n for (const proc of processes) {\n const squad = proc.squad || 'unknown';\n bySquad[squad] = (bySquad[squad] || 0) + 1;\n byTool[proc.tool] = (byTool[proc.tool] || 0) + 1;\n }\n\n return {\n totalSessions: processes.length,\n bySquad,\n squadCount: Object.keys(bySquad).length,\n byTool,\n };\n}\n\n/**\n * Generate a unique session ID\n */\nfunction generateSessionId(): string {\n return randomBytes(8).toString('hex');\n}\n\n/**\n * Get current session ID from environment or generate new one\n */\nlet currentSessionId: string | null = null;\n\nexport function getSessionId(): string {\n if (currentSessionId) return currentSessionId;\n\n // Check if we have a session file for this PID\n const sessionsDir = getSessionsDir();\n if (sessionsDir) {\n const pid = process.pid;\n const files = readdirSync(sessionsDir).filter(f => f.endsWith('.json'));\n\n for (const file of files) {\n try {\n const content = readFileSync(join(sessionsDir, file), 'utf-8');\n const session = JSON.parse(content) as SessionState;\n if (session.pid === pid) {\n currentSessionId = session.sessionId;\n return currentSessionId;\n }\n } catch {\n // Ignore parse errors\n }\n }\n }\n\n // Generate new session ID\n currentSessionId = generateSessionId();\n return currentSessionId;\n}\n\n/**\n * Start a new session (write initial state file and log event)\n */\nexport function startSession(squad?: string): SessionState | null {\n const sessionsDir = getSessionsDir();\n if (!sessionsDir) return null;\n\n const sessionId = getSessionId();\n const now = new Date().toISOString();\n const cwd = process.cwd();\n const detectedSquad = squad || detectSquad(cwd);\n\n const session: SessionState = {\n sessionId,\n squad: detectedSquad,\n startedAt: now,\n lastHeartbeat: now,\n cwd,\n pid: process.pid,\n };\n\n const sessionPath = join(sessionsDir, `${sessionId}.json`);\n writeFileSync(sessionPath, JSON.stringify(session, null, 2));\n\n // Log start event to history\n appendEvent({\n type: 'start',\n sessionId,\n squad: detectedSquad,\n ts: now,\n cwd,\n pid: process.pid,\n });\n\n return session;\n}\n\n/**\n * Update heartbeat for current session\n */\nexport function updateHeartbeat(): boolean {\n const sessionsDir = getSessionsDir();\n if (!sessionsDir) return false;\n\n const sessionId = getSessionId();\n const sessionPath = join(sessionsDir, `${sessionId}.json`);\n\n if (!existsSync(sessionPath)) {\n // Session doesn't exist, start a new one\n startSession();\n return true;\n }\n\n try {\n const content = readFileSync(sessionPath, 'utf-8');\n const session = JSON.parse(content) as SessionState;\n session.lastHeartbeat = new Date().toISOString();\n writeFileSync(sessionPath, JSON.stringify(session, null, 2));\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Stop current session (remove state file and log event)\n */\nexport function stopSession(): boolean {\n const sessionsDir = getSessionsDir();\n if (!sessionsDir) return false;\n\n const sessionId = getSessionId();\n const sessionPath = join(sessionsDir, `${sessionId}.json`);\n\n if (existsSync(sessionPath)) {\n // Read session to get start time and squad for duration calculation\n let squad: string | null = null;\n let durationMs: number | undefined;\n\n try {\n const content = readFileSync(sessionPath, 'utf-8');\n const session = JSON.parse(content) as SessionState;\n squad = session.squad;\n durationMs = Date.now() - new Date(session.startedAt).getTime();\n } catch {\n // Ignore parse errors\n }\n\n unlinkSync(sessionPath);\n currentSessionId = null;\n\n // Log stop event to history\n appendEvent({\n type: 'stop',\n sessionId,\n squad,\n ts: new Date().toISOString(),\n durationMs,\n });\n\n return true;\n }\n\n return false;\n}\n\n/**\n * Get all active sessions (non-stale)\n */\nexport function getActiveSessions(): SessionState[] {\n const sessionsDir = getSessionsDir();\n if (!sessionsDir) return [];\n\n const now = Date.now();\n const sessions: SessionState[] = [];\n\n try {\n const files = readdirSync(sessionsDir).filter(f => f.endsWith('.json'));\n\n for (const file of files) {\n try {\n const filePath = join(sessionsDir, file);\n const content = readFileSync(filePath, 'utf-8');\n const session = JSON.parse(content) as SessionState;\n\n // Check if session is stale\n const lastHeartbeat = new Date(session.lastHeartbeat).getTime();\n if (now - lastHeartbeat < STALE_THRESHOLD_MS) {\n sessions.push(session);\n }\n } catch {\n // Ignore parse errors\n }\n }\n } catch {\n // Directory read error\n }\n\n return sessions;\n}\n\n/**\n * Get session summary for dashboard\n */\nexport function getSessionSummary(): SessionSummary {\n const sessions = getActiveSessions();\n const bySquad: Record<string, number> = {};\n\n for (const session of sessions) {\n const squad = session.squad || 'unknown';\n bySquad[squad] = (bySquad[squad] || 0) + 1;\n }\n\n return {\n totalSessions: sessions.length,\n bySquad,\n squadCount: Object.keys(bySquad).length,\n };\n}\n\n/**\n * Clean up stale sessions (older than threshold) and log events\n */\nexport function cleanupStaleSessions(): number {\n const sessionsDir = getSessionsDir();\n if (!sessionsDir) return 0;\n\n const now = Date.now();\n const nowIso = new Date().toISOString();\n let cleaned = 0;\n\n try {\n const files = readdirSync(sessionsDir).filter(f => f.endsWith('.json'));\n\n for (const file of files) {\n try {\n const filePath = join(sessionsDir, file);\n const content = readFileSync(filePath, 'utf-8');\n const session = JSON.parse(content) as SessionState;\n\n const lastHeartbeat = new Date(session.lastHeartbeat).getTime();\n if (now - lastHeartbeat >= STALE_THRESHOLD_MS) {\n const durationMs = now - new Date(session.startedAt).getTime();\n\n unlinkSync(filePath);\n cleaned++;\n\n // Log stale cleanup event\n appendEvent({\n type: 'stale_cleanup',\n sessionId: session.sessionId,\n squad: session.squad,\n ts: nowIso,\n durationMs,\n });\n }\n } catch {\n // If we can't parse, remove the file\n try {\n unlinkSync(join(sessionsDir, file));\n cleaned++;\n } catch {\n // Ignore\n }\n }\n }\n } catch {\n // Directory read error\n }\n\n return cleaned;\n}\n\n/**\n * Read all events from history file\n */\nexport async function readSessionHistory(options: {\n since?: Date;\n until?: Date;\n squad?: string;\n type?: SessionEventType;\n limit?: number;\n} = {}): Promise<SessionEvent[]> {\n const historyPath = getHistoryFilePath();\n if (!historyPath || !existsSync(historyPath)) return [];\n\n const events: SessionEvent[] = [];\n const sinceMs = options.since?.getTime() || 0;\n const untilMs = options.until?.getTime() || Date.now();\n\n return new Promise((resolve) => {\n const rl = createInterface({\n input: createReadStream(historyPath),\n crlfDelay: Infinity,\n });\n\n rl.on('line', (line) => {\n if (!line.trim()) return;\n\n try {\n const event = JSON.parse(line) as SessionEvent;\n const eventMs = new Date(event.ts).getTime();\n\n // Apply filters\n if (eventMs < sinceMs || eventMs > untilMs) return;\n if (options.squad && event.squad !== options.squad) return;\n if (options.type && event.type !== options.type) return;\n\n events.push(event);\n } catch {\n // Ignore parse errors\n }\n });\n\n rl.on('close', () => {\n // Apply limit (from end, most recent first)\n if (options.limit && events.length > options.limit) {\n resolve(events.slice(-options.limit));\n } else {\n resolve(events);\n }\n });\n\n rl.on('error', () => {\n resolve([]);\n });\n });\n}\n\n/**\n * Get session history statistics\n */\nexport async function getSessionHistoryStats(options: {\n since?: Date;\n until?: Date;\n squad?: string;\n} = {}): Promise<SessionHistoryStats> {\n const events = await readSessionHistory(options);\n\n const stats: SessionHistoryStats = {\n totalSessions: 0,\n totalDurationMs: 0,\n avgDurationMs: 0,\n bySquad: {},\n byDate: {},\n peakConcurrent: 0,\n };\n\n // Track active sessions for peak concurrent calculation\n const activeSessions = new Map<string, { squad: string | null; startTs: number }>();\n let currentConcurrent = 0;\n\n for (const event of events) {\n const squad = event.squad || 'unknown';\n const date = event.ts.split('T')[0]; // YYYY-MM-DD\n\n if (event.type === 'start') {\n stats.totalSessions++;\n stats.byDate[date] = (stats.byDate[date] || 0) + 1;\n\n if (!stats.bySquad[squad]) {\n stats.bySquad[squad] = { count: 0, durationMs: 0 };\n }\n stats.bySquad[squad].count++;\n\n // Track for concurrent calculation\n activeSessions.set(event.sessionId, {\n squad: event.squad,\n startTs: new Date(event.ts).getTime(),\n });\n currentConcurrent++;\n stats.peakConcurrent = Math.max(stats.peakConcurrent, currentConcurrent);\n }\n\n if (event.type === 'stop' || event.type === 'stale_cleanup') {\n const duration = event.durationMs || 0;\n stats.totalDurationMs += duration;\n\n if (stats.bySquad[squad]) {\n stats.bySquad[squad].durationMs += duration;\n }\n\n // Remove from concurrent tracking\n if (activeSessions.has(event.sessionId)) {\n activeSessions.delete(event.sessionId);\n currentConcurrent = Math.max(0, currentConcurrent - 1);\n }\n }\n }\n\n // Calculate average duration\n const completedSessions = events.filter(e => e.type === 'stop' || e.type === 'stale_cleanup').length;\n if (completedSessions > 0) {\n stats.avgDurationMs = Math.round(stats.totalDurationMs / completedSessions);\n }\n\n return stats;\n}\n\n/**\n * Get recent session events (for display)\n */\nexport async function getRecentSessions(limit: number = 20): Promise<SessionEvent[]> {\n const events = await readSessionHistory({ limit: limit * 3 }); // Get more to filter\n\n // Group by session and get start/stop pairs\n const sessionEvents = new Map<string, SessionEvent[]>();\n for (const event of events) {\n if (!sessionEvents.has(event.sessionId)) {\n sessionEvents.set(event.sessionId, []);\n }\n sessionEvents.get(event.sessionId)!.push(event);\n }\n\n // Get the most recent start events\n const startEvents = events\n .filter(e => e.type === 'start')\n .slice(-limit);\n\n return startEvents.reverse(); // Most recent first\n}\n","// Terminal utilities - Bun-style approach\n// Raw ANSI for performance, no heavy deps\n\n// ANSI escape codes\nexport const ESC = '\\x1b[';\nexport const RESET = `${ESC}0m`;\n\n// Detect true color support\nfunction supportsTrueColor(): boolean {\n const colorterm = process.env.COLORTERM;\n if (colorterm === 'truecolor' || colorterm === '24bit') return true;\n const term = process.env.TERM || '';\n if (term.includes('256color') || term.includes('truecolor')) return true;\n // iTerm2, VS Code, modern terminals\n if (process.env.TERM_PROGRAM === 'iTerm.app') return true;\n if (process.env.TERM_PROGRAM === 'vscode') return true;\n if (process.env.WT_SESSION) return true; // Windows Terminal\n return false;\n}\n\nconst USE_TRUE_COLOR = supportsTrueColor();\n\n// Colors - use 24-bit RGB if supported, fallback to basic ANSI\nexport const rgb = (r: number, g: number, b: number) => `${ESC}38;2;${r};${g};${b}m`;\nexport const bgRgb = (r: number, g: number, b: number) => `${ESC}48;2;${r};${g};${b}m`;\n\n// Basic ANSI color codes (work everywhere)\nconst ansi = {\n purple: `${ESC}35m`, // magenta\n pink: `${ESC}95m`, // bright magenta\n cyan: `${ESC}36m`, // cyan\n green: `${ESC}32m`, // green\n yellow: `${ESC}33m`, // yellow\n red: `${ESC}31m`, // red\n gray: `${ESC}90m`, // bright black (gray)\n dim: `${ESC}90m`, // bright black (gray)\n white: `${ESC}97m`, // bright white\n};\n\n// Named colors (our brand palette) - with fallback\nexport const colors = USE_TRUE_COLOR ? {\n purple: rgb(168, 85, 247), // #a855f7\n pink: rgb(236, 72, 153), // #ec4899\n cyan: rgb(6, 182, 212), // #06b6d4\n green: rgb(16, 185, 129), // #10b981\n yellow: rgb(234, 179, 8), // #eab308\n red: rgb(239, 68, 68), // #ef4444\n gray: rgb(107, 114, 128), // #6b7280\n dim: rgb(75, 85, 99), // #4b5563\n white: rgb(255, 255, 255),\n} : ansi;\n\n// Styles\nexport const bold = `${ESC}1m`;\nexport const dim = `${ESC}2m`;\n\n// Cursor control\nexport const cursor = {\n hide: `${ESC}?25l`,\n show: `${ESC}?25h`,\n up: (n = 1) => `${ESC}${n}A`,\n down: (n = 1) => `${ESC}${n}B`,\n left: (n = 1) => `${ESC}${n}D`,\n right: (n = 1) => `${ESC}${n}C`,\n to: (x: number, y: number) => `${ESC}${y};${x}H`,\n save: `${ESC}s`,\n restore: `${ESC}u`,\n};\n\n// Clear\nexport const clear = {\n line: `${ESC}2K`,\n toEnd: `${ESC}0K`,\n screen: `${ESC}2J${ESC}0;0H`,\n};\n\n// Gradient text (purple → pink → cyan)\nexport function gradient(text: string): string {\n const stops = [\n [168, 85, 247], // purple\n [192, 132, 252], // purple-light\n [232, 121, 249], // pink\n [244, 114, 182], // pink-light\n [251, 113, 133], // rose\n ];\n\n let result = '';\n for (let i = 0; i < text.length; i++) {\n const t = i / Math.max(text.length - 1, 1);\n const stopIndex = t * (stops.length - 1);\n const lower = Math.floor(stopIndex);\n const upper = Math.min(lower + 1, stops.length - 1);\n const blend = stopIndex - lower;\n\n const r = Math.round(stops[lower][0] + (stops[upper][0] - stops[lower][0]) * blend);\n const g = Math.round(stops[lower][1] + (stops[upper][1] - stops[lower][1]) * blend);\n const b = Math.round(stops[lower][2] + (stops[upper][2] - stops[lower][2]) * blend);\n\n result += rgb(r, g, b) + text[i];\n }\n return result + RESET;\n}\n\n// Progress bar with gradient fill\nexport function progressBar(percent: number, width = 20): string {\n // Clamp values to prevent negative repeat counts\n const clampedPercent = Math.max(0, Math.min(100, percent || 0));\n const filled = Math.round((clampedPercent / 100) * width);\n const empty = Math.max(0, width - filled);\n\n let bar = '';\n for (let i = 0; i < filled; i++) {\n const t = i / Math.max(filled - 1, 1);\n const r = Math.round(16 + (168 - 16) * t);\n const g = Math.round(185 + (85 - 185) * t);\n const b = Math.round(129 + (247 - 129) * t);\n bar += rgb(r, g, b) + '━';\n }\n\n bar += colors.dim + '━'.repeat(empty) + RESET;\n return bar;\n}\n\n// Box drawing\nexport const box = {\n topLeft: '┌',\n topRight: '┐',\n bottomLeft: '└',\n bottomRight: '┘',\n horizontal: '─',\n vertical: '│',\n teeRight: '├',\n teeLeft: '┤',\n};\n\n// Format helpers\nexport function padEnd(str: string, len: number): string {\n // Strip ANSI codes for length calculation\n const visible = str.replace(/\\x1b\\[[0-9;]*m/g, '');\n const pad = Math.max(0, len - visible.length);\n return str + ' '.repeat(pad);\n}\n\nexport function truncate(str: string, len: number): string {\n const visible = str.replace(/\\x1b\\[[0-9;]*m/g, '');\n if (visible.length <= len) return str;\n\n // Simple truncation (won't handle mid-ANSI truncation perfectly)\n let result = '';\n let count = 0;\n let i = 0;\n\n while (i < str.length && count < len - 1) {\n if (str[i] === '\\x1b') {\n const end = str.indexOf('m', i);\n if (end !== -1) {\n result += str.slice(i, end + 1);\n i = end + 1;\n continue;\n }\n }\n result += str[i];\n count++;\n i++;\n }\n\n return result + colors.dim + '…' + RESET;\n}\n\n// Spinner frames\nexport const spinnerFrames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];\n\n// Status icons\nexport const icons = {\n success: `${colors.green}●${RESET}`,\n warning: `${colors.yellow}○${RESET}`,\n error: `${colors.red}●${RESET}`,\n pending: `${colors.dim}○${RESET}`,\n active: `${colors.green}●${RESET}`,\n progress: `${colors.cyan}◆${RESET}`,\n empty: `${colors.dim}◇${RESET}`,\n};\n\n// Strip ANSI escape codes from a string\nexport function stripAnsi(str: string): string {\n return str.replace(/\\x1b\\[[0-9;]*m/g, '');\n}\n\n// Check if we should use colors (TTY detection)\nexport function isColorEnabled(): boolean {\n // NO_COLOR environment variable (standard: https://no-color.org/)\n if (process.env.NO_COLOR !== undefined) return false;\n // Force color via environment variable\n if (process.env.FORCE_COLOR !== undefined) return true;\n // Claude Code terminal - always enable colors (it supports them)\n if (process.env.CLAUDECODE !== undefined) return true;\n // Check if output is a TTY\n return process.stdout.isTTY ?? false;\n}\n\n// Write without newline (strips ANSI codes when piped)\nexport function write(str: string): void {\n const output = isColorEnabled() ? str : stripAnsi(str);\n process.stdout.write(output);\n}\n\n// Write line (strips ANSI codes when piped)\nexport function writeLine(str = ''): void {\n const output = isColorEnabled() ? str : stripAnsi(str);\n process.stdout.write(output + '\\n');\n}\n\n// Sparkline chart using block characters\nexport function sparkline(values: number[], _width?: number): string {\n if (values.length === 0) return '';\n\n const blocks = ['▁', '▂', '▃', '▄', '▅', '▆', '▇', '█'];\n const max = Math.max(...values, 1);\n\n let result = '';\n for (const val of values) {\n const normalized = val / max;\n const blockIndex = Math.min(Math.floor(normalized * blocks.length), blocks.length - 1);\n\n // Color gradient from dim to cyan to green based on value\n if (normalized === 0) {\n result += colors.dim + blocks[0];\n } else if (normalized < 0.5) {\n result += colors.cyan + blocks[blockIndex];\n } else {\n result += colors.green + blocks[blockIndex];\n }\n }\n\n return result + RESET;\n}\n\n// Bar chart (horizontal)\nexport function barChart(value: number, max: number, width: number = 20, label?: string): string {\n // Guard against invalid inputs to prevent crashes\n const safeValue = Math.max(0, value || 0);\n const safeMax = Math.max(1, max || 1); // Prevent division by zero\n const ratio = Math.min(1, safeValue / safeMax); // Clamp ratio to 0-1\n const filled = Math.round(ratio * width);\n const empty = width - filled;\n\n let bar = '';\n for (let i = 0; i < filled; i++) {\n const t = i / Math.max(filled - 1, 1);\n // Green to cyan gradient\n const r = Math.round(16 + (6 - 16) * t);\n const g = Math.round(185 + (182 - 185) * t);\n const b = Math.round(129 + (212 - 129) * t);\n bar += rgb(r, g, b) + '━';\n }\n\n bar += colors.dim + '━'.repeat(empty) + RESET;\n\n if (label) {\n return `${bar} ${label}`;\n }\n return bar;\n}\n","/**\n * List active Claude Code sessions across squads\n */\n\nimport {\n getActiveSessions,\n getSessionSummary,\n cleanupStaleSessions,\n getSessionHistoryStats,\n getRecentSessions,\n SessionState,\n} from '../lib/sessions.js';\nimport {\n colors,\n bold,\n RESET,\n gradient,\n box,\n padEnd,\n icons,\n writeLine,\n} from '../lib/terminal.js';\n\ninterface SessionsOptions {\n verbose?: boolean;\n json?: boolean;\n}\n\nexport async function sessionsCommand(\n options: SessionsOptions = {}\n): Promise<void> {\n // Clean up stale sessions first\n cleanupStaleSessions();\n\n const sessions = getActiveSessions();\n const summary = getSessionSummary();\n\n // JSON output for scripts\n if (options.json) {\n console.log(JSON.stringify({ sessions, summary }, null, 2));\n return;\n }\n\n writeLine();\n writeLine(` ${gradient('squads')} ${colors.dim}sessions${RESET}`);\n writeLine();\n\n if (sessions.length === 0) {\n writeLine(` ${colors.dim}No active sessions${RESET}`);\n writeLine();\n writeLine(` ${colors.dim}Sessions are tracked automatically when Claude Code runs.${RESET}`);\n writeLine(` ${colors.dim}Each session updates its heartbeat via squads CLI commands.${RESET}`);\n writeLine();\n return;\n }\n\n // Summary line\n const squadText = summary.squadCount === 1 ? 'squad' : 'squads';\n const sessionText = summary.totalSessions === 1 ? 'session' : 'sessions';\n writeLine(` ${colors.green}${summary.totalSessions}${RESET} active ${sessionText} ${colors.dim}across${RESET} ${colors.cyan}${summary.squadCount}${RESET} ${squadText}`);\n writeLine();\n\n // Group by squad\n const bySquad: Record<string, SessionState[]> = {};\n for (const session of sessions) {\n const squad = session.squad || 'unknown';\n if (!bySquad[squad]) bySquad[squad] = [];\n bySquad[squad].push(session);\n }\n\n // Table\n const w = { squad: 16, sessions: 10, activity: 14 };\n const tableWidth = w.squad + w.sessions + w.activity + 4;\n\n writeLine(` ${colors.purple}${box.topLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.topRight}${RESET}`);\n\n const header = ` ${colors.purple}${box.vertical}${RESET} ` +\n `${bold}${padEnd('SQUAD', w.squad)}${RESET}` +\n `${bold}${padEnd('SESSIONS', w.sessions)}${RESET}` +\n `${bold}LAST ACTIVITY${RESET}` +\n ` ${colors.purple}${box.vertical}${RESET}`;\n writeLine(header);\n\n writeLine(` ${colors.purple}${box.teeRight}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.teeLeft}${RESET}`);\n\n for (const [squad, squadSessions] of Object.entries(bySquad).sort()) {\n // Find most recent activity\n let mostRecent = 0;\n for (const session of squadSessions) {\n const ts = new Date(session.lastHeartbeat).getTime();\n if (ts > mostRecent) mostRecent = ts;\n }\n\n const lastActivity = formatTimeAgo(mostRecent);\n const activityColor = getActivityColor(mostRecent);\n\n const row = ` ${colors.purple}${box.vertical}${RESET} ` +\n `${colors.cyan}${padEnd(squad, w.squad)}${RESET}` +\n `${padEnd(String(squadSessions.length), w.sessions)}` +\n `${padEnd(`${activityColor}${lastActivity}${RESET}`, w.activity)}` +\n `${colors.purple}${box.vertical}${RESET}`;\n\n writeLine(row);\n }\n\n writeLine(` ${colors.purple}${box.bottomLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.bottomRight}${RESET}`);\n\n // Verbose: show individual sessions\n if (options.verbose) {\n writeLine();\n writeLine(` ${bold}Session Details${RESET}`);\n writeLine();\n\n for (const session of sessions) {\n const squad = session.squad || 'unknown';\n const ago = formatTimeAgo(new Date(session.lastHeartbeat).getTime());\n\n writeLine(` ${icons.active} ${colors.white}${session.sessionId}${RESET}`);\n writeLine(` ${colors.dim}squad: ${squad} | pid: ${session.pid} | heartbeat: ${ago}${RESET}`);\n writeLine(` ${colors.dim}cwd: ${session.cwd}${RESET}`);\n }\n }\n\n writeLine();\n writeLine(` ${colors.dim}$${RESET} squads sessions -v ${colors.dim}Show session details${RESET}`);\n writeLine();\n}\n\n/**\n * Format timestamp as \"Xm ago\" or \"Xs ago\"\n */\nfunction formatTimeAgo(timestamp: number): string {\n const now = Date.now();\n const diff = now - timestamp;\n\n const seconds = Math.floor(diff / 1000);\n const minutes = Math.floor(seconds / 60);\n\n if (minutes >= 1) {\n return `${minutes}m ago`;\n }\n return `${seconds}s ago`;\n}\n\n/**\n * Get color based on how recent the activity is\n */\nfunction getActivityColor(timestamp: number): string {\n const now = Date.now();\n const diff = now - timestamp;\n\n const minutes = Math.floor(diff / (1000 * 60));\n\n if (minutes < 1) return colors.green;\n if (minutes < 3) return colors.yellow;\n return colors.dim;\n}\n\ninterface HistoryOptions {\n days?: number;\n squad?: string;\n json?: boolean;\n}\n\ninterface SummaryOptions {\n json?: boolean;\n}\n\nexport interface SessionSummaryData {\n squads: Array<{\n name: string;\n actions: string;\n outputs: string;\n }>;\n decisions?: Array<{\n question: string;\n answer: string;\n }>;\n customer?: {\n vertical: string;\n persona: string;\n painPoints: string[];\n };\n nextActions?: Array<{\n squad: string;\n action: string;\n }>;\n filesUpdated?: string[];\n targets?: {\n metric: string;\n value: string;\n }[];\n model?: string; // e.g., \"Claude Opus 4.5\"\n duration?: string; // e.g., \"45m\"\n}\n\n/**\n * Show a pretty summary of session work\n */\nexport async function sessionsSummaryCommand(\n data: SessionSummaryData,\n options: SummaryOptions = {}\n): Promise<void> {\n if (options.json) {\n console.log(JSON.stringify(data, null, 2));\n return;\n }\n\n writeLine();\n writeLine(` ${gradient('squads')} ${colors.dim}session summary${RESET}`);\n writeLine();\n\n // Squads table\n if (data.squads.length > 0) {\n const w = { squad: 14, actions: 26, outputs: 36 };\n const tableWidth = w.squad + w.actions + w.outputs + 6;\n\n // Helper to truncate text\n const truncate = (text: string, max: number) =>\n text.length > max ? text.substring(0, max - 1) + '…' : text;\n\n writeLine(` ${colors.green}${icons.active}${RESET} ${bold}${data.squads.length} Squads Active${RESET}`);\n writeLine();\n\n writeLine(` ${colors.purple}${box.topLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.topRight}${RESET}`);\n\n const header = ` ${colors.purple}${box.vertical}${RESET} ` +\n `${bold}${padEnd('SQUAD', w.squad)}${RESET}` +\n `${bold}${padEnd('ACTIONS', w.actions)}${RESET}` +\n `${bold}${padEnd('KEY OUTPUTS', w.outputs)}${RESET}` +\n `${colors.purple}${box.vertical}${RESET}`;\n writeLine(header);\n\n writeLine(` ${colors.purple}${box.teeRight}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.teeLeft}${RESET}`);\n\n for (const squad of data.squads) {\n const row = ` ${colors.purple}${box.vertical}${RESET} ` +\n `${colors.cyan}${padEnd(truncate(squad.name, w.squad - 1), w.squad)}${RESET}` +\n `${padEnd(truncate(squad.actions, w.actions - 1), w.actions)}` +\n `${padEnd(truncate(squad.outputs, w.outputs - 1), w.outputs)}` +\n `${colors.purple}${box.vertical}${RESET}`;\n writeLine(row);\n }\n\n writeLine(` ${colors.purple}${box.bottomLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.bottomRight}${RESET}`);\n }\n\n // Decisions\n if (data.decisions && data.decisions.length > 0) {\n writeLine();\n writeLine(` ${bold}Strategic Decisions${RESET}`);\n writeLine();\n\n const w = { question: 16, answer: 50 };\n const tableWidth = w.question + w.answer + 4;\n\n writeLine(` ${colors.purple}${box.topLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.topRight}${RESET}`);\n\n for (const decision of data.decisions) {\n const row = ` ${colors.purple}${box.vertical}${RESET} ` +\n `${colors.yellow}${padEnd(decision.question, w.question)}${RESET}` +\n `${padEnd(decision.answer, w.answer)}` +\n `${colors.purple}${box.vertical}${RESET}`;\n writeLine(row);\n }\n\n writeLine(` ${colors.purple}${box.bottomLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.bottomRight}${RESET}`);\n }\n\n // Target customer\n if (data.customer) {\n writeLine();\n writeLine(` ${bold}Target Customer${RESET}`);\n writeLine();\n writeLine(` ${colors.dim}Vertical:${RESET} ${colors.cyan}${data.customer.vertical}${RESET}`);\n writeLine(` ${colors.dim}Persona:${RESET} ${colors.white}${data.customer.persona}${RESET}`);\n writeLine(` ${colors.dim}Pain:${RESET} ${data.customer.painPoints.join(', ')}`);\n }\n\n // Next actions\n if (data.nextActions && data.nextActions.length > 0) {\n writeLine();\n writeLine(` ${bold}Next Actions${RESET}`);\n writeLine();\n\n for (const action of data.nextActions) {\n writeLine(` ${colors.cyan}${padEnd(action.squad, 14)}${RESET}${colors.dim}→${RESET} ${action.action}`);\n }\n }\n\n // Q1 Targets\n if (data.targets && data.targets.length > 0) {\n writeLine();\n writeLine(` ${bold}Q1 Targets${RESET}`);\n writeLine();\n\n for (const target of data.targets) {\n writeLine(` ${colors.dim}•${RESET} ${target.metric}: ${colors.green}${target.value}${RESET}`);\n }\n }\n\n // Files updated\n if (data.filesUpdated && data.filesUpdated.length > 0) {\n writeLine();\n writeLine(` ${colors.dim}Files updated:${RESET}`);\n for (const file of data.filesUpdated) {\n writeLine(` ${colors.dim} •${RESET} ${colors.cyan}${file}${RESET}`);\n }\n }\n\n writeLine();\n\n // Footer with model attribution\n const modelText = data.model ? data.model : 'Claude';\n const durationText = data.duration ? ` ${colors.dim}(${data.duration})${RESET}` : '';\n writeLine(` ${colors.dim}Generated by${RESET} ${colors.purple}${modelText}${RESET}${durationText}`);\n writeLine();\n}\n\n/**\n * Build summary from current session by detecting recent activity\n */\nexport async function buildCurrentSessionSummary(): Promise<SessionSummaryData> {\n const { existsSync, readdirSync, statSync, readFileSync } = await import('fs');\n const { join } = await import('path');\n const { findMemoryDir } = await import('../lib/memory.js');\n\n const memoryDir = findMemoryDir();\n const squads: SessionSummaryData['squads'] = [];\n const filesUpdated: string[] = [];\n\n // Look for files modified in last 2 hours (current session window)\n const sessionWindow = 2 * 60 * 60 * 1000; // 2 hours\n const now = Date.now();\n\n if (memoryDir && existsSync(memoryDir)) {\n const squadDirs = readdirSync(memoryDir, { withFileTypes: true })\n .filter(d => d.isDirectory());\n\n for (const squadDir of squadDirs) {\n const squadPath = join(memoryDir, squadDir.name);\n let squadModified = false;\n let stateContent = '';\n let executionContent = '';\n\n try {\n const agentDirs = readdirSync(squadPath, { withFileTypes: true })\n .filter(d => d.isDirectory());\n\n for (const agentDir of agentDirs) {\n const agentPath = join(squadPath, agentDir.name);\n const files = readdirSync(agentPath).filter(f => f.endsWith('.md'));\n\n for (const file of files) {\n const filePath = join(agentPath, file);\n const stats = statSync(filePath);\n\n if (now - stats.mtimeMs < sessionWindow) {\n squadModified = true;\n const relativePath = `${squadDir.name}/${agentDir.name}/${file}`;\n filesUpdated.push(relativePath);\n\n // Read content for summary\n if (file === 'state.md') {\n stateContent = readFileSync(filePath, 'utf-8');\n } else if (file === 'executions.md') {\n executionContent = readFileSync(filePath, 'utf-8');\n }\n }\n }\n }\n\n if (squadModified) {\n // Extract summary from state/execution content\n let actions = 'State updated';\n let outputs = 'Memory refreshed';\n\n // Try to extract info from execution log\n if (executionContent) {\n const lines = executionContent.split('\\n').filter(l => l.trim());\n const recentEntry = lines.slice(-10).join(' ');\n if (recentEntry.includes('completed')) {\n actions = 'Execution completed';\n }\n // Extract key points\n const keyMatch = recentEntry.match(/Key (?:findings|decisions|outputs)?:?\\s*([^.]+)/i);\n if (keyMatch) {\n outputs = keyMatch[1].substring(0, 50);\n }\n }\n\n // Try to extract from state header\n if (stateContent) {\n const updatedMatch = stateContent.match(/Updated:\\s*([^\\n]+)/);\n if (updatedMatch) {\n actions = `Updated ${updatedMatch[1]}`;\n }\n }\n\n squads.push({\n name: squadDir.name.charAt(0).toUpperCase() + squadDir.name.slice(1),\n actions,\n outputs: outputs.length > 44 ? outputs.substring(0, 41) + '...' : outputs,\n });\n }\n } catch {\n // Skip if can't read\n }\n }\n }\n\n // If no recent activity found\n if (squads.length === 0) {\n squads.push({\n name: 'No recent activity',\n actions: '—',\n outputs: 'Run squads to see activity here',\n });\n }\n\n return {\n squads,\n filesUpdated: filesUpdated.length > 0 ? filesUpdated : undefined,\n model: process.env.ANTHROPIC_MODEL || 'Claude',\n };\n}\n\n/**\n * Show session history and statistics\n */\nexport async function sessionsHistoryCommand(\n options: HistoryOptions = {}\n): Promise<void> {\n const days = options.days || 7;\n const since = new Date(Date.now() - days * 24 * 60 * 60 * 1000);\n\n const stats = await getSessionHistoryStats({\n since,\n squad: options.squad,\n });\n\n const recentSessions = await getRecentSessions(10);\n\n // JSON output\n if (options.json) {\n console.log(JSON.stringify({ stats, recentSessions }, null, 2));\n return;\n }\n\n writeLine();\n writeLine(` ${gradient('squads')} ${colors.dim}sessions history${RESET} ${colors.dim}(${days}d)${RESET}`);\n writeLine();\n\n if (stats.totalSessions === 0) {\n writeLine(` ${colors.dim}No session history found${RESET}`);\n writeLine();\n writeLine(` ${colors.dim}Session events are logged to .agents/sessions/history.jsonl${RESET}`);\n writeLine();\n return;\n }\n\n // Summary stats\n const avgMinutes = Math.round(stats.avgDurationMs / 60000);\n const totalHours = Math.round(stats.totalDurationMs / 3600000 * 10) / 10;\n\n writeLine(` ${bold}Summary${RESET}`);\n writeLine(` ${colors.cyan}${stats.totalSessions}${RESET} sessions ${colors.dim}│${RESET} ${colors.green}${totalHours}h${RESET} total ${colors.dim}│${RESET} ${colors.yellow}${avgMinutes}m${RESET} avg ${colors.dim}│${RESET} ${colors.purple}${stats.peakConcurrent}${RESET} peak`);\n writeLine();\n\n // By squad table\n const squads = Object.entries(stats.bySquad).sort((a, b) => b[1].count - a[1].count);\n\n if (squads.length > 0) {\n const w = { squad: 16, sessions: 10, duration: 12 };\n const tableWidth = w.squad + w.sessions + w.duration + 4;\n\n writeLine(` ${bold}By Squad${RESET}`);\n writeLine();\n writeLine(` ${colors.purple}${box.topLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.topRight}${RESET}`);\n\n const header = ` ${colors.purple}${box.vertical}${RESET} ` +\n `${bold}${padEnd('SQUAD', w.squad)}${RESET}` +\n `${bold}${padEnd('SESSIONS', w.sessions)}${RESET}` +\n `${bold}DURATION${RESET}` +\n ` ${colors.purple}${box.vertical}${RESET}`;\n writeLine(header);\n\n writeLine(` ${colors.purple}${box.teeRight}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.teeLeft}${RESET}`);\n\n for (const [squad, data] of squads) {\n const hours = Math.round(data.durationMs / 3600000 * 10) / 10;\n\n const row = ` ${colors.purple}${box.vertical}${RESET} ` +\n `${colors.cyan}${padEnd(squad, w.squad)}${RESET}` +\n `${padEnd(String(data.count), w.sessions)}` +\n `${padEnd(`${hours}h`, w.duration)}` +\n `${colors.purple}${box.vertical}${RESET}`;\n\n writeLine(row);\n }\n\n writeLine(` ${colors.purple}${box.bottomLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.bottomRight}${RESET}`);\n }\n\n // Recent sessions\n if (recentSessions.length > 0) {\n writeLine();\n writeLine(` ${bold}Recent Sessions${RESET}`);\n writeLine();\n\n for (const event of recentSessions.slice(0, 5)) {\n const squad = event.squad || 'unknown';\n const date = new Date(event.ts);\n const timeStr = date.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' });\n const dateStr = date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });\n\n writeLine(` ${colors.dim}${dateStr} ${timeStr}${RESET} ${colors.cyan}${squad}${RESET} ${colors.dim}${event.sessionId.slice(0, 8)}${RESET}`);\n }\n }\n\n // By date (last 7 days)\n const dates = Object.entries(stats.byDate)\n .sort((a, b) => b[0].localeCompare(a[0]))\n .slice(0, 7);\n\n if (dates.length > 1) {\n writeLine();\n writeLine(` ${bold}Daily Activity${RESET}`);\n writeLine();\n\n for (const [date, count] of dates) {\n const bar = '█'.repeat(Math.min(count, 20));\n writeLine(` ${colors.dim}${date}${RESET} ${colors.green}${bar}${RESET} ${count}`);\n }\n }\n\n writeLine();\n writeLine(` ${colors.dim}$${RESET} squads sessions history --days 30 ${colors.dim}Longer history${RESET}`);\n writeLine(` ${colors.dim}$${RESET} squads sessions history -s website ${colors.dim}Filter by squad${RESET}`);\n writeLine();\n}\n"],"mappings":"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","names":["truncate","existsSync","readdirSync","readFileSync","join"]}
|
|
File without changes
|