squads-cli 0.3.0 → 0.4.4

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/dist/cli.js CHANGED
@@ -14,7 +14,7 @@ import {
14
14
  cleanupStaleSessions,
15
15
  colors,
16
16
  detectSquad,
17
- getLiveSessionSummary,
17
+ getLiveSessionSummaryAsync,
18
18
  gradient,
19
19
  icons,
20
20
  padEnd,
@@ -28,30 +28,39 @@ import {
28
28
  truncate,
29
29
  updateHeartbeat,
30
30
  writeLine
31
- } from "./chunk-266URT5W.js";
32
- import "./chunk-7OCVIDC7.js";
31
+ } from "./chunk-G63RBKDH.js";
32
+ import {
33
+ __require
34
+ } from "./chunk-7OCVIDC7.js";
33
35
 
34
36
  // src/cli.ts
35
37
  import { config } from "dotenv";
36
- import { existsSync as existsSync15 } from "fs";
37
- import { join as join15 } from "path";
38
- import { homedir as homedir6 } from "os";
38
+ import { existsSync as existsSync17 } from "fs";
39
+ import { join as join17 } from "path";
40
+ import { homedir as homedir5 } from "os";
39
41
  import { Command } from "commander";
40
42
  import chalk4 from "chalk";
41
43
 
42
44
  // src/version.ts
43
- var version = "0.1.0";
45
+ import { createRequire } from "module";
46
+ var require2 = createRequire(import.meta.url);
47
+ var pkg = require2("../package.json");
48
+ var version = pkg.version;
44
49
 
45
50
  // src/commands/init.ts
46
51
  import chalk from "chalk";
47
52
  import ora from "ora";
48
53
  import fs from "fs/promises";
49
54
  import path from "path";
55
+ import { execSync as execSync2, spawn } from "child_process";
56
+ import { createInterface } from "readline";
50
57
 
51
58
  // src/lib/git.ts
52
- import { execSync } from "child_process";
59
+ import { execSync, exec } from "child_process";
53
60
  import { existsSync } from "fs";
54
61
  import { join } from "path";
62
+ import { promisify } from "util";
63
+ var execAsync = promisify(exec);
55
64
  function checkGitStatus(cwd = process.cwd()) {
56
65
  const status = {
57
66
  isGitRepo: false,
@@ -64,34 +73,34 @@ function checkGitStatus(cwd = process.cwd()) {
64
73
  }
65
74
  status.isGitRepo = true;
66
75
  try {
67
- const branch = execSync("git rev-parse --abbrev-ref HEAD", {
68
- cwd,
69
- encoding: "utf-8",
70
- stdio: ["pipe", "pipe", "pipe"]
71
- }).trim();
72
- status.branch = branch;
73
- const remotes = execSync("git remote -v", {
74
- cwd,
75
- encoding: "utf-8",
76
- stdio: ["pipe", "pipe", "pipe"]
77
- }).trim();
78
- if (remotes) {
79
- status.hasRemote = true;
80
- const lines = remotes.split("\n");
81
- if (lines.length > 0) {
82
- const parts = lines[0].split(/\s+/);
83
- status.remoteName = parts[0];
84
- status.remoteUrl = parts[1];
76
+ const combined = execSync(
77
+ 'echo "BRANCH:" && git rev-parse --abbrev-ref HEAD && echo "REMOTES:" && git remote -v && echo "STATUS:" && git status --porcelain',
78
+ { cwd, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 5e3 }
79
+ );
80
+ const branchMatch = combined.match(/BRANCH:\n(.+)\n/);
81
+ if (branchMatch) {
82
+ status.branch = branchMatch[1].trim();
83
+ }
84
+ const remotesMatch = combined.match(/REMOTES:\n([\s\S]*?)STATUS:/);
85
+ if (remotesMatch) {
86
+ const remotes = remotesMatch[1].trim();
87
+ if (remotes) {
88
+ status.hasRemote = true;
89
+ const lines = remotes.split("\n");
90
+ if (lines.length > 0) {
91
+ const parts = lines[0].split(/\s+/);
92
+ status.remoteName = parts[0];
93
+ status.remoteUrl = parts[1];
94
+ }
85
95
  }
86
96
  }
87
- const statusOutput = execSync("git status --porcelain", {
88
- cwd,
89
- encoding: "utf-8",
90
- stdio: ["pipe", "pipe", "pipe"]
91
- }).trim();
92
- if (statusOutput) {
93
- status.isDirty = true;
94
- status.uncommittedCount = statusOutput.split("\n").filter((l) => l.trim()).length;
97
+ const statusMatch = combined.match(/STATUS:\n([\s\S]*?)$/);
98
+ if (statusMatch) {
99
+ const statusOutput = statusMatch[1].trim();
100
+ if (statusOutput) {
101
+ status.isDirty = true;
102
+ status.uncommittedCount = statusOutput.split("\n").filter((l) => l.trim()).length;
103
+ }
95
104
  }
96
105
  } catch {
97
106
  }
@@ -294,7 +303,7 @@ function getMultiRepoGitStats(basePath, days = 30) {
294
303
  const authors = /* @__PURE__ */ new Set();
295
304
  let lastCommit = "";
296
305
  for (const line of commits) {
297
- const [hash, author, date, message] = line.split("|");
306
+ const [hash, author, date] = line.split("|");
298
307
  if (!hash) continue;
299
308
  stats.totalCommits++;
300
309
  authors.add(author);
@@ -335,9 +344,6 @@ function getActivitySparkline(basePath, days = 7) {
335
344
  const activity = [];
336
345
  const now = /* @__PURE__ */ new Date();
337
346
  for (let i = days - 1; i >= 0; i--) {
338
- const date = new Date(now);
339
- date.setDate(date.getDate() - i);
340
- const dateStr = date.toISOString().split("T")[0];
341
347
  activity.push(0);
342
348
  }
343
349
  for (const repo of SQUAD_REPOS) {
@@ -480,6 +486,7 @@ var Events = {
480
486
  CLI_STATUS: "cli.status",
481
487
  CLI_DASHBOARD: "cli.dashboard",
482
488
  CLI_WORKERS: "cli.workers",
489
+ CLI_TONIGHT: "cli.tonight",
483
490
  // Goals
484
491
  CLI_GOAL_SET: "cli.goal.set",
485
492
  CLI_GOAL_LIST: "cli.goal.list",
@@ -508,7 +515,7 @@ function registerExitHandler() {
508
515
  await flushEvents();
509
516
  }
510
517
  });
511
- const signalHandler = async (signal) => {
518
+ const signalHandler = async (_signal) => {
512
519
  if (eventQueue.length > 0) {
513
520
  await flushEvents();
514
521
  }
@@ -519,32 +526,302 @@ function registerExitHandler() {
519
526
  }
520
527
 
521
528
  // src/commands/init.ts
529
+ async function confirm(question, defaultYes = true) {
530
+ const rl = createInterface({
531
+ input: process.stdin,
532
+ output: process.stdout
533
+ });
534
+ const suffix = defaultYes ? "[Y/n]" : "[y/N]";
535
+ return new Promise((resolve) => {
536
+ rl.question(` ${question} ${chalk.dim(suffix)} `, (answer) => {
537
+ rl.close();
538
+ const normalized = answer.toLowerCase().trim();
539
+ if (normalized === "") {
540
+ resolve(defaultYes);
541
+ } else {
542
+ resolve(normalized === "y" || normalized === "yes");
543
+ }
544
+ });
545
+ });
546
+ }
547
+ function commandExists(cmd) {
548
+ try {
549
+ execSync2(`which ${cmd}`, { stdio: "ignore" });
550
+ return true;
551
+ } catch {
552
+ return false;
553
+ }
554
+ }
555
+ function dockerRunning() {
556
+ try {
557
+ execSync2("docker info", { stdio: "ignore" });
558
+ return true;
559
+ } catch {
560
+ return false;
561
+ }
562
+ }
563
+ function checkClaudeAuth() {
564
+ try {
565
+ execSync2("which claude", { stdio: "ignore" });
566
+ } catch {
567
+ return { installed: false, loggedIn: false };
568
+ }
569
+ try {
570
+ const result = execSync2("claude --version", { stdio: "pipe" }).toString();
571
+ return { installed: true, loggedIn: result.includes("claude") };
572
+ } catch {
573
+ return { installed: true, loggedIn: false };
574
+ }
575
+ }
576
+ function checkGhAuth() {
577
+ try {
578
+ execSync2("gh auth status", { stdio: "ignore" });
579
+ return true;
580
+ } catch {
581
+ return false;
582
+ }
583
+ }
584
+ function checkRequirements() {
585
+ const checks = [];
586
+ const claudeStatus = checkClaudeAuth();
587
+ if (claudeStatus.installed && claudeStatus.loggedIn) {
588
+ checks.push({ name: "Claude CLI", status: "ok" });
589
+ } else if (claudeStatus.installed) {
590
+ checks.push({
591
+ name: "Claude CLI",
592
+ status: "warning",
593
+ message: "Installed but may need login",
594
+ hint: "Run: claude login"
595
+ });
596
+ } else {
597
+ checks.push({
598
+ name: "Claude CLI",
599
+ status: "missing",
600
+ message: "Required to run agents",
601
+ hint: "Install: npm install -g @anthropic-ai/claude-code"
602
+ });
603
+ }
604
+ if (checkGhAuth()) {
605
+ checks.push({ name: "GitHub CLI", status: "ok" });
606
+ } else if (commandExists("gh")) {
607
+ checks.push({
608
+ name: "GitHub CLI",
609
+ status: "warning",
610
+ message: "Installed but not logged in",
611
+ hint: "Run: gh auth login"
612
+ });
613
+ } else {
614
+ checks.push({
615
+ name: "GitHub CLI",
616
+ status: "missing",
617
+ message: "Required for GitHub integration (issues, PRs)",
618
+ hint: "Install: https://cli.github.com"
619
+ });
620
+ }
621
+ if (commandExists("docker")) {
622
+ if (dockerRunning()) {
623
+ checks.push({ name: "Docker", status: "ok" });
624
+ } else {
625
+ checks.push({
626
+ name: "Docker",
627
+ status: "warning",
628
+ message: "Installed but not running",
629
+ hint: "Start Docker Desktop or run: sudo systemctl start docker"
630
+ });
631
+ }
632
+ } else {
633
+ checks.push({
634
+ name: "Docker",
635
+ status: "warning",
636
+ message: "Optional - enables dashboard metrics",
637
+ hint: "Install: https://docker.com"
638
+ });
639
+ }
640
+ return checks;
641
+ }
642
+ async function setupInfrastructure(cwd) {
643
+ const spinner = ora("Setting up infrastructure...").start();
644
+ try {
645
+ let dockerDir = null;
646
+ const localDockerCompose = path.join(cwd, "docker", "docker-compose.yml");
647
+ const localDockerComposeRoot = path.join(cwd, "docker-compose.yml");
648
+ if (await fileExists(localDockerCompose)) {
649
+ dockerDir = path.join(cwd, "docker");
650
+ } else if (await fileExists(localDockerComposeRoot)) {
651
+ dockerDir = cwd;
652
+ } else {
653
+ const cliPath = new URL("../..", import.meta.url).pathname;
654
+ const bundledDockerCompose = path.join(cliPath, "docker", "docker-compose.yml");
655
+ if (await fileExists(bundledDockerCompose)) {
656
+ spinner.text = "Copying infrastructure files...";
657
+ const targetDockerDir = path.join(cwd, "docker");
658
+ await fs.mkdir(targetDockerDir, { recursive: true });
659
+ const filesToCopy = [
660
+ "docker-compose.yml",
661
+ ".env.example",
662
+ "init-db.sql",
663
+ "README.md"
664
+ ];
665
+ for (const file of filesToCopy) {
666
+ const src = path.join(cliPath, "docker", file);
667
+ const dest = path.join(targetDockerDir, file);
668
+ if (await fileExists(src)) {
669
+ await fs.copyFile(src, dest);
670
+ }
671
+ }
672
+ const bridgeSrc = path.join(cliPath, "docker", "squads-bridge");
673
+ const bridgeDest = path.join(targetDockerDir, "squads-bridge");
674
+ if (await fileExists(bridgeSrc)) {
675
+ await copyDir(bridgeSrc, bridgeDest);
676
+ }
677
+ const envExample = path.join(targetDockerDir, ".env.example");
678
+ const envFile = path.join(targetDockerDir, ".env");
679
+ if (await fileExists(envExample) && !await fileExists(envFile)) {
680
+ await fs.copyFile(envExample, envFile);
681
+ }
682
+ dockerDir = targetDockerDir;
683
+ }
684
+ }
685
+ if (!dockerDir) {
686
+ spinner.fail("Could not find docker-compose.yml");
687
+ console.log(chalk.dim(" Try cloning the full repo: git clone https://github.com/agents-squads/hq"));
688
+ return false;
689
+ }
690
+ spinner.text = "Starting containers...";
691
+ await new Promise((resolve, reject) => {
692
+ const proc = spawn("docker", ["compose", "up", "-d"], {
693
+ cwd: dockerDir,
694
+ stdio: "pipe"
695
+ });
696
+ let stderr = "";
697
+ proc.stderr?.on("data", (data) => {
698
+ stderr += data.toString();
699
+ });
700
+ proc.on("close", (code) => {
701
+ if (code === 0) {
702
+ resolve();
703
+ } else {
704
+ reject(new Error(stderr || `docker compose failed with code ${code}`));
705
+ }
706
+ });
707
+ proc.on("error", reject);
708
+ });
709
+ spinner.text = "Waiting for services to be ready...";
710
+ await sleep(3e3);
711
+ const services = ["squads-postgres", "squads-redis", "squads-bridge"];
712
+ let allRunning = true;
713
+ for (const service of services) {
714
+ try {
715
+ execSync2(`docker ps --filter "name=${service}" --filter "status=running" -q`, { stdio: "pipe" });
716
+ } catch {
717
+ allRunning = false;
718
+ }
719
+ }
720
+ if (allRunning) {
721
+ spinner.succeed("Infrastructure ready");
722
+ console.log();
723
+ console.log(chalk.dim(" Services running:"));
724
+ console.log(chalk.dim(" \u2022 postgres:5432 \u2022 redis:6379 \u2022 bridge:8088"));
725
+ return true;
726
+ } else {
727
+ spinner.warn("Some services may not be running");
728
+ console.log(chalk.dim(" Check with: docker ps"));
729
+ return true;
730
+ }
731
+ } catch (error) {
732
+ spinner.fail("Failed to start infrastructure");
733
+ console.log(chalk.dim(` ${error}`));
734
+ console.log(chalk.dim(" Try manually: cd docker && docker compose up -d"));
735
+ return false;
736
+ }
737
+ }
738
+ async function fileExists(filePath) {
739
+ try {
740
+ await fs.access(filePath);
741
+ return true;
742
+ } catch {
743
+ return false;
744
+ }
745
+ }
746
+ async function copyDir(src, dest) {
747
+ await fs.mkdir(dest, { recursive: true });
748
+ const entries = await fs.readdir(src, { withFileTypes: true });
749
+ for (const entry of entries) {
750
+ const srcPath = path.join(src, entry.name);
751
+ const destPath = path.join(dest, entry.name);
752
+ if (entry.isDirectory()) {
753
+ await copyDir(srcPath, destPath);
754
+ } else {
755
+ await fs.copyFile(srcPath, destPath);
756
+ }
757
+ }
758
+ }
759
+ function sleep(ms) {
760
+ return new Promise((resolve) => setTimeout(resolve, ms));
761
+ }
522
762
  async function initCommand(options) {
523
763
  const cwd = process.cwd();
524
- console.log(chalk.dim("Checking project setup...\n"));
764
+ console.log();
765
+ console.log(chalk.bold(" Checking requirements..."));
766
+ console.log();
767
+ const checks = checkRequirements();
768
+ let hasMissingRequired = false;
769
+ let hasDocker = false;
770
+ let dockerReady = false;
771
+ for (const check of checks) {
772
+ if (check.status === "ok") {
773
+ console.log(` ${chalk.green("\u2713")} ${check.name}`);
774
+ if (check.name === "Docker") {
775
+ hasDocker = true;
776
+ dockerReady = true;
777
+ }
778
+ } else if (check.status === "missing") {
779
+ console.log(` ${chalk.yellow("\u26A0")} ${chalk.yellow(check.name)}`);
780
+ if (check.message) {
781
+ console.log(chalk.dim(` ${check.message}`));
782
+ }
783
+ if (check.hint) {
784
+ console.log(chalk.dim(` \u2192 ${check.hint}`));
785
+ }
786
+ if (check.name !== "Docker") {
787
+ hasMissingRequired = true;
788
+ }
789
+ } else if (check.status === "warning") {
790
+ console.log(` ${chalk.dim("\u25CB")} ${check.name} ${chalk.dim(`(${check.message})`)}`);
791
+ if (check.hint) {
792
+ console.log(chalk.dim(` \u2192 ${check.hint}`));
793
+ }
794
+ if (check.name === "Docker") {
795
+ hasDocker = commandExists("docker");
796
+ }
797
+ }
798
+ }
799
+ console.log();
800
+ if (hasMissingRequired) {
801
+ console.log(chalk.yellow(" Install missing tools to continue, then run squads init again."));
802
+ console.log();
803
+ return;
804
+ }
805
+ console.log(chalk.dim(" Checking project setup..."));
806
+ console.log();
525
807
  const gitStatus = checkGitStatus(cwd);
526
808
  if (!gitStatus.isGitRepo) {
527
- console.log(chalk.yellow("\u26A0 No git repository found."));
528
- console.log(chalk.dim(" Squads work best with git for version control and GitHub integration."));
529
- console.log(chalk.dim(" Run: git init && git remote add origin <your-repo-url>\n"));
809
+ console.log(` ${chalk.yellow("\u26A0")} No git repository found`);
810
+ console.log(chalk.dim(" Run: git init && git remote add origin <repo-url>"));
811
+ console.log();
530
812
  } else {
531
- console.log(chalk.green("\u2713 Git repository detected"));
813
+ console.log(` ${chalk.green("\u2713")} Git repository`);
532
814
  if (gitStatus.hasRemote) {
533
815
  const repoName = getRepoName(gitStatus.remoteUrl);
534
- console.log(chalk.green(`\u2713 Remote: ${chalk.cyan(repoName || gitStatus.remoteUrl)}`));
535
- } else {
536
- console.log(chalk.yellow("\u26A0 No remote configured."));
537
- console.log(chalk.dim(" Add a remote: git remote add origin <your-repo-url>\n"));
538
- }
539
- if (gitStatus.isDirty) {
540
- console.log(chalk.yellow(`\u26A0 ${gitStatus.uncommittedCount} uncommitted changes`));
816
+ console.log(` ${chalk.green("\u2713")} Remote: ${chalk.cyan(repoName || gitStatus.remoteUrl)}`);
541
817
  }
542
818
  }
543
819
  console.log();
544
- const spinner = ora("Initializing squad project...").start();
820
+ const spinner = ora("Creating squad structure...").start();
545
821
  try {
546
822
  const dirs = [
547
823
  ".agents/squads",
824
+ ".agents/squads/demo",
548
825
  ".agents/memory",
549
826
  ".agents/outputs",
550
827
  ".claude"
@@ -552,56 +829,102 @@ async function initCommand(options) {
552
829
  for (const dir of dirs) {
553
830
  await fs.mkdir(path.join(cwd, dir), { recursive: true });
554
831
  }
555
- const commitTemplate = `
832
+ const demoSquadMd = `# Demo Squad
556
833
 
557
- # \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
558
- # Commit message format (delete this comment block):
559
- #
560
- # <type>(<scope>): <subject>
561
- #
562
- # <body>
563
- #
564
- # \u{1F916} Generated with [Agents Squads](https://agents-squads.com)
565
- #
566
- # Co-Authored-By: <model> <email>
567
- # \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
568
- # AI Models (add those that contributed to this commit):
569
- #
570
- # Model | Email | API Key
571
- # --------------------|----------------------------|------------------
572
- # Claude Opus 4.5 | <noreply@anthropic.com> | ANTHROPIC_API_KEY
573
- # Claude Sonnet 4 | <noreply@anthropic.com> | ANTHROPIC_API_KEY
574
- # Claude Haiku 3.5 | <noreply@anthropic.com> | ANTHROPIC_API_KEY
575
- # GPT-4o | <noreply@openai.com> | OPENAI_API_KEY
576
- # GPT-o1 | <noreply@openai.com> | OPENAI_API_KEY
577
- # Gemini 2.0 Flash | <noreply@google.com> | GEMINI_API_KEY
578
- # Grok 3 | <noreply@x.ai> | XAI_API_KEY
579
- # Perplexity | <noreply@perplexity.ai> | PERPLEXITY_API_KEY
580
- # Manus AI | <noreply@manus.im> | MANUS_API_KEY
581
- #
582
- # Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
583
- # Co-Authored-By: Claude Sonnet 4 <noreply@anthropic.com>
584
- # Co-Authored-By: Claude Haiku 3.5 <noreply@anthropic.com>
585
- # Co-Authored-By: GPT-4o <noreply@openai.com>
586
- # Co-Authored-By: GPT-o1 <noreply@openai.com>
587
- # Co-Authored-By: Gemini 2.0 Flash <noreply@google.com>
588
- # Co-Authored-By: Grok 3 <noreply@x.ai>
589
- # Co-Authored-By: Perplexity <noreply@perplexity.ai>
590
- # Co-Authored-By: Manus AI <noreply@manus.im>
591
- # \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
834
+ Demonstrates squads functionality with safe, educational examples.
835
+
836
+ ## Goals
837
+
838
+ - [ ] Run the demo agents and explore the dashboard
839
+
840
+ ## Agents
841
+
842
+ | Agent | Purpose |
843
+ |-------|---------|
844
+ | welcome | Creates a welcome GitHub issue |
845
+ | analyzer | Analyzes project structure |
846
+
847
+ ## Triggers
848
+
849
+ None (manual execution only)
850
+
851
+ ## Usage
852
+
853
+ \`\`\`bash
854
+ squads run demo
855
+ \`\`\`
856
+
857
+ This will:
858
+ 1. Create a GitHub issue explaining what happened
859
+ 2. Analyze your project structure
860
+ 3. Show results in the dashboard
861
+
862
+ All demo data is clearly labeled [demo] so you can distinguish it from real data.
592
863
  `;
593
864
  await fs.writeFile(
594
- path.join(cwd, ".agents/commit-template.txt"),
595
- commitTemplate
865
+ path.join(cwd, ".agents/squads/demo/SQUAD.md"),
866
+ demoSquadMd
867
+ );
868
+ const welcomeAgent = `# Welcome Agent
869
+
870
+ ## Purpose
871
+ Create a welcome GitHub issue to demonstrate squads functionality.
872
+
873
+ ## Model
874
+ claude-haiku-3-5
875
+
876
+ ## Tools
877
+ - Bash (gh cli)
878
+
879
+ ## Instructions
880
+ 1. Check if a welcome issue already exists
881
+ 2. If not, create a new issue titled "[Demo] Welcome to Squads!"
882
+ 3. The issue body should explain:
883
+ - What just happened (squads run demo executed this agent)
884
+ - What squads are and how they work
885
+ - Next steps to create their own agents
886
+
887
+ ## Output
888
+ Confirmation message with issue URL.
889
+
890
+ ## Labels
891
+ - demo
892
+ - automated
893
+ `;
894
+ await fs.writeFile(
895
+ path.join(cwd, ".agents/squads/demo/welcome.md"),
896
+ welcomeAgent
596
897
  );
597
- const mailmap = `# Git author name consolidation
598
- # Format: Proper Name <proper@email> Commit Name <commit@email>
599
- # Add entries to consolidate multiple git identities
898
+ const analyzerAgent = `# Project Analyzer Agent
899
+
900
+ ## Purpose
901
+ Analyze project structure and provide insights.
902
+
903
+ ## Model
904
+ claude-haiku-3-5
905
+
906
+ ## Tools
907
+ - Read
908
+ - Glob
909
+ - Bash (ls, find)
910
+
911
+ ## Instructions
912
+ 1. Scan the project directory structure
913
+ 2. Identify key files (package.json, Cargo.toml, pyproject.toml, etc.)
914
+ 3. Summarize the tech stack
915
+ 4. Create a brief report
916
+
917
+ ## Output
918
+ Markdown report saved to .agents/outputs/demo/project-analysis.md
600
919
 
601
- # AI Contributors
602
- Agents Squads <agents@agents-squads.com> Agents Squads <agents@agents-squads.com>
920
+ ## Labels
921
+ - demo
922
+ - analysis
603
923
  `;
604
- await fs.writeFile(path.join(cwd, ".mailmap"), mailmap);
924
+ await fs.writeFile(
925
+ path.join(cwd, ".agents/squads/demo/analyzer.md"),
926
+ analyzerAgent
927
+ );
605
928
  const claudeSettings = {
606
929
  hooks: {
607
930
  SessionStart: [
@@ -632,135 +955,154 @@ Agents Squads <agents@agents-squads.com> Agents Squads <agents@agents-squads.com
632
955
  path.join(cwd, ".claude/settings.json"),
633
956
  JSON.stringify(claudeSettings, null, 2)
634
957
  );
635
- const exampleAgent = `# Example Agent
636
-
637
- ## Purpose
638
- Demonstrate basic agent structure.
639
-
640
- ## Model
641
- claude-sonnet-4
642
-
643
- ## Tools
644
- - Read
645
- - Write
646
- - WebSearch
647
-
648
- ## Instructions
649
- 1. Greet the user
650
- 2. Ask how you can help
651
- 3. Execute the task
652
-
653
- ## Output
654
- Markdown summary of actions taken.
958
+ const commitTemplate = `
959
+ # Commit message format:
960
+ # <type>(<scope>): <subject>
961
+ #
962
+ # \u{1F916} Generated with [Agents Squads](https://agents-squads.com)
963
+ # Co-Authored-By: Claude <model> <noreply@anthropic.com>
655
964
  `;
656
965
  await fs.writeFile(
657
- path.join(cwd, ".agents/squads/example-agent.md"),
658
- exampleAgent
966
+ path.join(cwd, ".agents/commit-template.txt"),
967
+ commitTemplate
659
968
  );
660
969
  const claudeMdPath = path.join(cwd, "CLAUDE.md");
661
- try {
662
- await fs.access(claudeMdPath);
663
- } catch {
970
+ if (!await fileExists(claudeMdPath)) {
664
971
  await fs.writeFile(
665
972
  claudeMdPath,
666
973
  `# Project Instructions
667
974
 
668
- ## Squads CLI
975
+ ## What is Squads?
976
+
977
+ Squads is a framework for building AI agent teams that automate real work.
978
+ Each **squad** is a team of **agents** (markdown prompts) that execute via Claude.
979
+
980
+ ## For Claude (READ THIS)
669
981
 
670
- This project uses AI agent squads. The \`squads\` CLI provides persistent memory across sessions.
982
+ When helping users with squad-related tasks:
671
983
 
672
- ### Key Commands
984
+ ### Check Context First
985
+ \`\`\`bash
986
+ squads status # What squads exist?
987
+ squads memory query "X" # What do we know about X?
988
+ \`\`\`
989
+
990
+ ### Creating Agents
991
+ Agents live in \`.agents/squads/<squad-name>/<agent-name>.md\`:
673
992
 
674
- | Command | Purpose |
675
- |---------|---------|
676
- | \`squads status\` | Overview of all squads (runs on session start) |
677
- | \`squads dash\` | Full operational dashboard |
678
- | \`squads dash --ceo\` | Executive summary with P0/P1 priorities |
679
- | \`squads goal list\` | View all active goals |
680
- | \`squads memory query "<topic>"\` | Search squad memory before researching |
681
- | \`squads run <squad>\` | Execute a squad |
993
+ \`\`\`markdown
994
+ # Agent Name
682
995
 
683
- ### Workflow
996
+ ## Purpose
997
+ One sentence: what this agent does.
684
998
 
685
- 1. **Session Start**: \`squads status\` runs automatically via hook
686
- 2. **Before Research**: Query memory to avoid re-doing work
687
- 3. **Session End**: Memory syncs automatically from git commits
999
+ ## Instructions
1000
+ 1. Specific step
1001
+ 2. Another step
1002
+ 3. Output location
688
1003
 
689
- ### Git Commit Format
1004
+ ## Output
1005
+ What it produces and where it goes.
1006
+ \`\`\`
690
1007
 
691
- All commits should use the Agents Squads format:
1008
+ ### Running Agents
1009
+ \`\`\`bash
1010
+ squads run <squad> # Run all agents in squad
1011
+ squads run <squad>/<agent> # Run specific agent
1012
+ \`\`\`
692
1013
 
1014
+ ### Tracking Progress
1015
+ \`\`\`bash
1016
+ squads dash # Full dashboard with goals
1017
+ squads goal list # View all goals
1018
+ squads goal set <squad> "X" # Add a goal
693
1019
  \`\`\`
694
- <type>(<scope>): <subject>
695
1020
 
696
- <body>
1021
+ ### Common User Requests
1022
+
1023
+ | User says | You should |
1024
+ |-----------|------------|
1025
+ | "Create an agent to..." | Create \`.agents/squads/<squad>/<name>.md\` |
1026
+ | "Automate X" | Create agent, then \`squads run\` |
1027
+ | "What's the status?" | Run \`squads dash\` or \`squads status\` |
1028
+ | "Run the X agent" | \`squads run <squad>/x\` |
1029
+ | "Check memory" | \`squads memory query "<topic>"\` |
697
1030
 
698
- \u{1F916} Generated with [Agents Squads](https://agents-squads.com)
1031
+ ## Quick Reference
699
1032
 
700
- Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1033
+ \`\`\`bash
1034
+ squads status # Overview
1035
+ squads dash # Full dashboard
1036
+ squads run demo # Try demo squad
1037
+ squads list # All agents
1038
+ squads memory query X # Search memory
1039
+ squads goal list # View goals
701
1040
  \`\`\`
702
1041
 
703
- **AI Models** (include those that contributed):
704
-
705
- | Model | Co-Author Email | API Key Required |
706
- |-------|-----------------|------------------|
707
- | Claude Opus 4.5 | \`<noreply@anthropic.com>\` | \`ANTHROPIC_API_KEY\` |
708
- | Claude Sonnet 4 | \`<noreply@anthropic.com>\` | \`ANTHROPIC_API_KEY\` |
709
- | Claude Haiku 3.5 | \`<noreply@anthropic.com>\` | \`ANTHROPIC_API_KEY\` |
710
- | GPT-4o | \`<noreply@openai.com>\` | \`OPENAI_API_KEY\` |
711
- | GPT-o1 | \`<noreply@openai.com>\` | \`OPENAI_API_KEY\` |
712
- | Gemini 2.0 Flash | \`<noreply@google.com>\` | \`GEMINI_API_KEY\` |
713
- | Grok 3 | \`<noreply@x.ai>\` | \`XAI_API_KEY\` |
714
- | Perplexity | \`<noreply@perplexity.ai>\` | \`PERPLEXITY_API_KEY\` |
715
- | Manus AI | \`<noreply@manus.im>\` | \`MANUS_API_KEY\` |
716
-
717
- ### For Reports
718
-
719
- Always use CLI commands for status reports:
720
- - Executive summary: \`squads dash --ceo\`
721
- - Operational view: \`squads dash\`
722
- - Goals: \`squads goal list\`
1042
+ ## Project Structure
1043
+
1044
+ \`\`\`
1045
+ .agents/
1046
+ \u251C\u2500\u2500 squads/ # Agent teams
1047
+ \u2502 \u2514\u2500\u2500 <squad>/
1048
+ \u2502 \u251C\u2500\u2500 SQUAD.md # Squad definition
1049
+ \u2502 \u2514\u2500\u2500 *.md # Agent files
1050
+ \u251C\u2500\u2500 memory/ # Persistent context
1051
+ \u2514\u2500\u2500 outputs/ # Agent outputs
1052
+ \`\`\`
723
1053
  `
724
1054
  );
725
1055
  }
726
- spinner.succeed("Squad project initialized!");
1056
+ spinner.succeed("Squad structure created");
727
1057
  await track(Events.CLI_INIT, {
728
1058
  hasGit: gitStatus.isGitRepo,
729
1059
  hasRemote: gitStatus.hasRemote,
730
- template: options.template
1060
+ template: options.template,
1061
+ hasDocker
731
1062
  });
732
- console.log(`
733
- ${chalk.green("Success!")} Created squad project structure:
734
-
735
- ${chalk.cyan(".agents/")}
736
- ${chalk.dim("\u251C\u2500\u2500")} ${chalk.cyan("squads/")} Squad & agent definitions
737
- ${chalk.dim("\u251C\u2500\u2500")} ${chalk.cyan("memory/")} Persistent squad memory
738
- ${chalk.dim("\u251C\u2500\u2500")} ${chalk.cyan("outputs/")} Squad outputs
739
- ${chalk.dim("\u2514\u2500\u2500")} ${chalk.cyan("commit-template.txt")} Git commit format
740
-
741
- ${chalk.cyan(".mailmap")} Author name consolidation
742
-
743
- ${chalk.dim("Commit format:")}
744
- \u{1F916} Generated with [Agents Squads](https://agents-squads.com)
745
- Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
746
-
747
- ${chalk.dim("Next steps:")}
748
- ${chalk.cyan("1.")} Create a squad: ${chalk.yellow("mkdir .agents/squads/my-squad && touch .agents/squads/my-squad/SQUAD.md")}
749
- ${chalk.cyan("2.")} Set a goal: ${chalk.yellow('squads goal set my-squad "Solve a problem"')}
750
- ${chalk.cyan("3.")} Run it: ${chalk.yellow("squads run my-squad")}
751
-
752
- ${chalk.dim("Put your first squad to work solving a problem in minutes.")}
753
- `);
754
1063
  } catch (error) {
755
- spinner.fail("Failed to initialize project");
756
- console.error(chalk.red(error));
1064
+ spinner.fail("Failed to create structure");
1065
+ console.error(chalk.red(` ${error}`));
757
1066
  process.exit(1);
758
1067
  }
1068
+ if (!options.skipInfra && hasDocker) {
1069
+ console.log();
1070
+ if (!dockerReady) {
1071
+ console.log(chalk.dim(" Docker is installed but not running."));
1072
+ console.log(chalk.dim(" Start Docker to enable infrastructure setup."));
1073
+ } else {
1074
+ const setupInfra = await confirm("Set up local infrastructure (postgres, redis)?", true);
1075
+ if (setupInfra) {
1076
+ console.log();
1077
+ const success = await setupInfrastructure(cwd);
1078
+ if (!success) {
1079
+ console.log();
1080
+ console.log(chalk.dim(" You can set up infrastructure later with:"));
1081
+ console.log(chalk.dim(" cd docker && docker compose up -d"));
1082
+ }
1083
+ }
1084
+ }
1085
+ }
1086
+ console.log();
1087
+ console.log(chalk.green(" \u2713 Ready!"));
1088
+ console.log();
1089
+ console.log(` ${chalk.cyan(".agents/")}
1090
+ ${chalk.dim("\u251C\u2500\u2500")} ${chalk.cyan("squads/demo/")} Demo squad (try it!)
1091
+ ${chalk.dim("\u251C\u2500\u2500")} ${chalk.cyan("memory/")} Persistent context
1092
+ ${chalk.dim("\u2514\u2500\u2500")} ${chalk.cyan("outputs/")} Agent outputs`);
1093
+ console.log();
1094
+ console.log(chalk.dim(" Next steps:"));
1095
+ console.log(` ${chalk.cyan("1.")} Try the demo: ${chalk.yellow("squads run demo")}`);
1096
+ console.log(` ${chalk.cyan("2.")} Check status: ${chalk.yellow("squads dash")}`);
1097
+ console.log(` ${chalk.cyan("3.")} Create your own squad in ${chalk.cyan(".agents/squads/")}`);
1098
+ console.log();
1099
+ console.log(chalk.dim(" Need help? jorge@agents-squads.com"));
1100
+ console.log();
759
1101
  }
760
1102
 
761
1103
  // src/commands/run.ts
762
1104
  import ora2 from "ora";
763
- import { spawn } from "child_process";
1105
+ import { spawn as spawn2 } from "child_process";
764
1106
  import { join as join4, dirname } from "path";
765
1107
  import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync2 } from "fs";
766
1108
 
@@ -780,6 +1122,20 @@ function findSquadsDir() {
780
1122
  }
781
1123
  return null;
782
1124
  }
1125
+ function findProjectRoot() {
1126
+ const squadsDir = findSquadsDir();
1127
+ if (!squadsDir) return null;
1128
+ return join3(squadsDir, "..", "..");
1129
+ }
1130
+ function hasLocalInfraConfig() {
1131
+ const projectRoot = findProjectRoot();
1132
+ if (!projectRoot) return false;
1133
+ const envPath = join3(projectRoot, ".env");
1134
+ if (!existsSync3(envPath)) return false;
1135
+ const content = readFileSync2(envPath, "utf-8");
1136
+ const infraKeys = ["LANGFUSE_", "SQUADS_BRIDGE", "SQUADS_POSTGRES", "SQUADS_REDIS"];
1137
+ return infraKeys.some((key) => content.includes(key));
1138
+ }
783
1139
  function listSquads(squadsDir) {
784
1140
  const squads = [];
785
1141
  const entries = readdirSync(squadsDir, { withFileTypes: true });
@@ -1010,6 +1366,33 @@ function updateGoalInSquad(squadName, goalIndex, updates) {
1010
1366
  }
1011
1367
 
1012
1368
  // src/commands/run.ts
1369
+ function ensureProjectTrusted(projectPath) {
1370
+ const configPath = join4(process.env.HOME || "", ".claude.json");
1371
+ if (!existsSync4(configPath)) {
1372
+ return;
1373
+ }
1374
+ try {
1375
+ const config2 = JSON.parse(readFileSync3(configPath, "utf-8"));
1376
+ if (!config2.projects) {
1377
+ config2.projects = {};
1378
+ }
1379
+ if (!config2.projects[projectPath]) {
1380
+ config2.projects[projectPath] = {};
1381
+ }
1382
+ if (!config2.projects[projectPath].hasTrustDialogAccepted) {
1383
+ config2.projects[projectPath].hasTrustDialogAccepted = true;
1384
+ writeFileSync3(configPath, JSON.stringify(config2, null, 2));
1385
+ }
1386
+ } catch {
1387
+ }
1388
+ }
1389
+ function getProjectRoot() {
1390
+ const squadsDir = findSquadsDir();
1391
+ if (squadsDir) {
1392
+ return dirname(dirname(squadsDir));
1393
+ }
1394
+ return process.cwd();
1395
+ }
1013
1396
  function getExecutionLogPath(squadName, agentName) {
1014
1397
  const memoryDir = findMemoryDir();
1015
1398
  if (!memoryDir) return null;
@@ -1091,14 +1474,52 @@ async function runSquad(squad, squadsDir, options) {
1091
1474
  }
1092
1475
  writeLine(` ${colors.dim}Started: ${startTime}${RESET}`);
1093
1476
  writeLine();
1094
- if (squad.pipelines.length > 0) {
1095
- const pipeline = squad.pipelines[0];
1096
- writeLine(` ${bold}Pipeline${RESET} ${colors.dim}${pipeline.agents.join(" \u2192 ")}${RESET}`);
1477
+ if (options.lead) {
1478
+ await runLeadMode(squad, squadsDir, options);
1479
+ return;
1480
+ }
1481
+ if (options.parallel) {
1482
+ const agentFiles = squad.agents.map((a) => ({
1483
+ name: a.name,
1484
+ path: join4(squadsDir, squad.name, `${a.name}.md`)
1485
+ })).filter((a) => existsSync4(a.path));
1486
+ if (agentFiles.length === 0) {
1487
+ writeLine(` ${icons.error} ${colors.red}No agent files found${RESET}`);
1488
+ return;
1489
+ }
1490
+ writeLine(` ${bold}Parallel execution${RESET} ${colors.dim}${agentFiles.length} agents${RESET}`);
1097
1491
  writeLine();
1098
- for (let i = 0; i < pipeline.agents.length; i++) {
1099
- const agentName = pipeline.agents[i];
1100
- const agentPath = join4(squadsDir, squad.name, `${agentName}.md`);
1101
- if (existsSync4(agentPath)) {
1492
+ if (!options.execute) {
1493
+ for (const agent of agentFiles) {
1494
+ writeLine(` ${icons.empty} ${colors.cyan}${agent.name}${RESET}`);
1495
+ }
1496
+ writeLine();
1497
+ writeLine(` ${colors.dim}Launch all agents in parallel:${RESET}`);
1498
+ writeLine(` ${colors.dim}$${RESET} squads run ${colors.cyan}${squad.name}${RESET} --parallel --execute`);
1499
+ writeLine();
1500
+ return;
1501
+ }
1502
+ writeLine(` ${gradient("Launching")} ${agentFiles.length} agents in parallel...`);
1503
+ writeLine();
1504
+ const launches = agentFiles.map(
1505
+ (agent) => runAgent(agent.name, agent.path, squad.name, options)
1506
+ );
1507
+ await Promise.all(launches);
1508
+ writeLine();
1509
+ writeLine(` ${icons.success} All ${agentFiles.length} agents launched`);
1510
+ writeLine(` ${colors.dim}Monitor: tmux ls | grep squads-${squad.name}${RESET}`);
1511
+ writeLine(` ${colors.dim}Attach: tmux attach -t <session>${RESET}`);
1512
+ writeLine();
1513
+ return;
1514
+ }
1515
+ if (squad.pipelines.length > 0) {
1516
+ const pipeline = squad.pipelines[0];
1517
+ writeLine(` ${bold}Pipeline${RESET} ${colors.dim}${pipeline.agents.join(" \u2192 ")}${RESET}`);
1518
+ writeLine();
1519
+ for (let i = 0; i < pipeline.agents.length; i++) {
1520
+ const agentName = pipeline.agents[i];
1521
+ const agentPath = join4(squadsDir, squad.name, `${agentName}.md`);
1522
+ if (existsSync4(agentPath)) {
1102
1523
  writeLine(` ${colors.dim}[${i + 1}/${pipeline.agents.length}]${RESET}`);
1103
1524
  await runAgent(agentName, agentPath, squad.name, options);
1104
1525
  writeLine();
@@ -1132,6 +1553,9 @@ async function runSquad(squad, squadsDir, options) {
1132
1553
  writeLine();
1133
1554
  writeLine(` ${colors.dim}Run a specific agent:${RESET}`);
1134
1555
  writeLine(` ${colors.dim}$${RESET} squads run ${colors.cyan}${squad.name}${RESET} --agent ${colors.cyan}<name>${RESET}`);
1556
+ writeLine();
1557
+ writeLine(` ${colors.dim}Run all agents in parallel:${RESET}`);
1558
+ writeLine(` ${colors.dim}$${RESET} squads run ${colors.cyan}${squad.name}${RESET} --parallel --execute`);
1135
1559
  }
1136
1560
  }
1137
1561
  }
@@ -1140,6 +1564,114 @@ async function runSquad(squad, squadsDir, options) {
1140
1564
  writeLine(` ${colors.dim}$${RESET} squads feedback add ${colors.cyan}${squad.name}${RESET} ${colors.cyan}<1-5>${RESET} ${colors.cyan}"<feedback>"${RESET}`);
1141
1565
  writeLine();
1142
1566
  }
1567
+ async function runLeadMode(squad, squadsDir, options) {
1568
+ if (!squad) return;
1569
+ const agentFiles = squad.agents.map((a) => ({
1570
+ name: a.name,
1571
+ path: join4(squadsDir, squad.name, `${a.name}.md`),
1572
+ role: a.role || ""
1573
+ })).filter((a) => existsSync4(a.path));
1574
+ if (agentFiles.length === 0) {
1575
+ writeLine(` ${icons.error} ${colors.red}No agent files found${RESET}`);
1576
+ return;
1577
+ }
1578
+ writeLine(` ${bold}Lead mode${RESET} ${colors.dim}orchestrating ${agentFiles.length} agents${RESET}`);
1579
+ writeLine();
1580
+ for (const agent of agentFiles) {
1581
+ writeLine(` ${icons.empty} ${colors.cyan}${agent.name}${RESET} ${colors.dim}${agent.role}${RESET}`);
1582
+ }
1583
+ writeLine();
1584
+ if (!options.execute) {
1585
+ writeLine(` ${colors.dim}Launch lead session:${RESET}`);
1586
+ writeLine(` ${colors.dim}$${RESET} squads run ${colors.cyan}${squad.name}${RESET} --lead --execute`);
1587
+ writeLine();
1588
+ return;
1589
+ }
1590
+ const timeoutMins = options.timeout || 30;
1591
+ const agentList = agentFiles.map((a) => `- ${a.name}: ${a.role}`).join("\n");
1592
+ const agentPaths = agentFiles.map((a) => `- ${a.name}: ${a.path}`).join("\n");
1593
+ const prompt2 = `You are the Lead of the ${squad.name} squad.
1594
+
1595
+ ## Mission
1596
+ ${squad.mission || "Execute squad operations efficiently."}
1597
+
1598
+ ## Available Agents
1599
+ ${agentList}
1600
+
1601
+ ## Agent Definition Files
1602
+ ${agentPaths}
1603
+
1604
+ ## Your Role as Lead
1605
+
1606
+ 1. **Assess the situation**: Check for pending work:
1607
+ - Run \`gh issue list --repo agents-squads/hq --label squad:${squad.name}\` for assigned issues
1608
+ - Check .agents/memory/${squad.name}/ for squad state and pending tasks
1609
+ - Review recent activity with \`git log --oneline -10\`
1610
+
1611
+ 2. **Delegate work using Task tool**: For each piece of work:
1612
+ - Use the Task tool with subagent_type="general-purpose"
1613
+ - Include the agent definition file path in the prompt
1614
+ - Spawn multiple Task agents IN PARALLEL when work is independent
1615
+ - Example: "Read ${agentFiles[0]?.path || "agent.md"} and execute its instructions for [specific task]"
1616
+
1617
+ 3. **Coordinate parallel execution**:
1618
+ - Independent tasks \u2192 spawn Task agents in parallel (single message, multiple tool calls)
1619
+ - Dependent tasks \u2192 run sequentially
1620
+ - Monitor progress and handle failures
1621
+
1622
+ 4. **Report and update memory**:
1623
+ - Update .agents/memory/${squad.name}/state.md with completed work
1624
+ - Log learnings to learnings.md
1625
+ - Create issues for follow-up work if needed
1626
+
1627
+ ## Time Budget
1628
+ You have ${timeoutMins} minutes. Prioritize high-impact work.
1629
+
1630
+ ## Critical Instructions
1631
+ - Use Task tool for delegation, NOT direct execution of agent work
1632
+ - Spawn parallel Task agents when work is independent
1633
+ - When done, type /exit to end the session
1634
+ - Do NOT wait for user input - work autonomously
1635
+
1636
+ ## Async Mode (CRITICAL)
1637
+ This is ASYNC execution - Task agents must be fully autonomous:
1638
+ - **Findings** \u2192 Create GitHub issues (gh issue create)
1639
+ - **Code changes** \u2192 Create PRs (gh pr create)
1640
+ - **Analysis results** \u2192 Write to .agents/outputs/ or memory files
1641
+ - **NEVER wait for human review** - complete the work and move on
1642
+ - **NEVER ask clarifying questions** - make reasonable decisions
1643
+
1644
+ Instruct each Task agent: "Work autonomously. Output findings to GitHub issues. Output code changes as PRs. Do not wait for review."
1645
+
1646
+ Begin by assessing pending work, then delegate to agents via Task tool.`;
1647
+ const claudeAvailable = await checkClaudeCliAvailable();
1648
+ if (!claudeAvailable) {
1649
+ writeLine(` ${colors.yellow}Claude CLI not found${RESET}`);
1650
+ writeLine(` ${colors.dim}Install: npm install -g @anthropic-ai/claude-code${RESET}`);
1651
+ return;
1652
+ }
1653
+ writeLine(` ${gradient("Launching")} lead session${options.foreground ? " (foreground)" : ""}...`);
1654
+ writeLine();
1655
+ try {
1656
+ const result = await executeWithClaude(prompt2, options.verbose, timeoutMins, options.foreground, options.useApi);
1657
+ if (options.foreground) {
1658
+ writeLine();
1659
+ writeLine(` ${icons.success} Lead session completed`);
1660
+ } else {
1661
+ writeLine(` ${icons.success} Lead session launched`);
1662
+ writeLine(` ${colors.dim}${result}${RESET}`);
1663
+ writeLine();
1664
+ writeLine(` ${colors.dim}The lead will:${RESET}`);
1665
+ writeLine(` ${colors.dim} 1. Assess pending work (issues, memory)${RESET}`);
1666
+ writeLine(` ${colors.dim} 2. Spawn Task agents for parallel execution${RESET}`);
1667
+ writeLine(` ${colors.dim} 3. Coordinate and report results${RESET}`);
1668
+ writeLine();
1669
+ writeLine(` ${colors.dim}Monitor: squads workers${RESET}`);
1670
+ }
1671
+ } catch (error) {
1672
+ writeLine(` ${icons.error} ${colors.red}Failed to launch: ${error}${RESET}`);
1673
+ }
1674
+ }
1143
1675
  async function runAgent(agentName, agentPath, squadName, options) {
1144
1676
  const spinner = ora2(`Running agent: ${agentName}`).start();
1145
1677
  const startTime = (/* @__PURE__ */ new Date()).toISOString();
@@ -1158,6 +1690,7 @@ async function runAgent(agentName, agentPath, squadName, options) {
1158
1690
  startTime,
1159
1691
  status: "running"
1160
1692
  });
1693
+ const timeoutMins = options.timeout || 30;
1161
1694
  const prompt2 = `Execute the ${agentName} agent from squad ${squadName}.
1162
1695
 
1163
1696
  Read the agent definition at ${agentPath} and follow its instructions exactly.
@@ -1168,20 +1701,35 @@ The agent definition contains:
1168
1701
  - Step-by-step instructions
1169
1702
  - Expected output format
1170
1703
 
1704
+ TIME LIMIT: You have ${timeoutMins} minutes. Work efficiently:
1705
+ - Focus on the most important tasks first
1706
+ - If a task is taking too long, move on and note it for next run
1707
+ - Aim to complete within ${Math.floor(timeoutMins * 0.7)} minutes
1708
+
1171
1709
  After completion:
1172
1710
  1. Update the agent's memory in .agents/memory/${squadName}/${agentName}/state.md
1173
1711
  2. Log any learnings to learnings.md
1174
- 3. Report what was accomplished`;
1712
+ 3. Summarize what was accomplished
1713
+
1714
+ CRITICAL: When you have completed your tasks OR reached the time limit:
1715
+ - Type /exit immediately to end this session
1716
+ - Do NOT wait for user input
1717
+ - Do NOT ask follow-up questions
1718
+ - Just /exit when done`;
1175
1719
  const claudeAvailable = await checkClaudeCliAvailable();
1176
1720
  if (options.execute && claudeAvailable) {
1177
- spinner.text = `Launching ${agentName} as background task...`;
1721
+ spinner.text = options.foreground ? `Running ${agentName} in foreground...` : `Launching ${agentName} as background task...`;
1178
1722
  try {
1179
- const result = await executeWithClaude(prompt2, options.verbose, options.timeout || 30);
1180
- spinner.succeed(`Agent ${agentName} launched`);
1181
- writeLine(` ${colors.dim}${result}${RESET}`);
1182
- writeLine();
1183
- writeLine(` ${colors.dim}Monitor:${RESET} squads workers`);
1184
- writeLine(` ${colors.dim}Memory:${RESET} squads memory show ${squadName}`);
1723
+ const result = await executeWithClaude(prompt2, options.verbose, options.timeout || 30, options.foreground, options.useApi);
1724
+ if (options.foreground) {
1725
+ spinner.succeed(`Agent ${agentName} completed`);
1726
+ } else {
1727
+ spinner.succeed(`Agent ${agentName} launched`);
1728
+ writeLine(` ${colors.dim}${result}${RESET}`);
1729
+ writeLine();
1730
+ writeLine(` ${colors.dim}Monitor:${RESET} squads workers`);
1731
+ writeLine(` ${colors.dim}Memory:${RESET} squads memory show ${squadName}`);
1732
+ }
1185
1733
  } catch (error) {
1186
1734
  spinner.fail(`Agent ${agentName} failed to launch`);
1187
1735
  updateExecutionStatus(squadName, agentName, "failed", String(error));
@@ -1205,25 +1753,64 @@ After completion:
1205
1753
  }
1206
1754
  async function checkClaudeCliAvailable() {
1207
1755
  return new Promise((resolve) => {
1208
- const check = spawn("which", ["claude"], { stdio: "pipe" });
1756
+ const check = spawn2("which", ["claude"], { stdio: "pipe" });
1209
1757
  check.on("close", (code) => resolve(code === 0));
1210
1758
  check.on("error", () => resolve(false));
1211
1759
  });
1212
1760
  }
1213
- async function executeWithClaude(prompt2, verbose, timeoutMinutes = 30) {
1761
+ async function executeWithClaude(prompt2, verbose, _timeoutMinutes = 30, foreground, useApi) {
1214
1762
  const userConfigPath = join4(process.env.HOME || "", ".claude.json");
1763
+ const projectRoot = getProjectRoot();
1764
+ ensureProjectTrusted(projectRoot);
1215
1765
  const squadMatch = prompt2.match(/squad (\w+)/);
1216
1766
  const agentMatch = prompt2.match(/(\w+) agent/);
1217
- const squadName = squadMatch?.[1] || "unknown";
1218
- const agentName = agentMatch?.[1] || "unknown";
1219
- const timestamp = Date.now();
1220
- const sessionName = `squads-${squadName}-${agentName}-${timestamp}`;
1767
+ const squadName = process.env.SQUADS_SQUAD || squadMatch?.[1] || "unknown";
1768
+ const agentName = process.env.SQUADS_AGENT || agentMatch?.[1] || "unknown";
1769
+ const { ANTHROPIC_API_KEY: _apiKey, ...envWithoutApiKey } = process.env;
1770
+ const spawnEnv = useApi ? process.env : envWithoutApiKey;
1771
+ const escapedPrompt = prompt2.replace(/'/g, "'\\''");
1772
+ if (foreground) {
1773
+ if (verbose) {
1774
+ writeLine(` ${colors.dim}Project: ${projectRoot}${RESET}`);
1775
+ writeLine(` ${colors.dim}Mode: foreground${RESET}`);
1776
+ writeLine(` ${colors.dim}Auth: ${useApi ? "API credits" : "subscription"}${RESET}`);
1777
+ }
1778
+ return new Promise((resolve, reject) => {
1779
+ const claude = spawn2("claude", [
1780
+ "--dangerously-skip-permissions",
1781
+ "--mcp-config",
1782
+ userConfigPath,
1783
+ "--",
1784
+ prompt2
1785
+ ], {
1786
+ stdio: "inherit",
1787
+ cwd: projectRoot,
1788
+ env: {
1789
+ ...spawnEnv,
1790
+ SQUADS_SQUAD: squadName,
1791
+ SQUADS_AGENT: agentName
1792
+ }
1793
+ });
1794
+ claude.on("close", (code) => {
1795
+ if (code === 0) {
1796
+ resolve("Session completed");
1797
+ } else {
1798
+ reject(new Error(`Claude exited with code ${code}`));
1799
+ }
1800
+ });
1801
+ claude.on("error", (err) => {
1802
+ reject(err);
1803
+ });
1804
+ });
1805
+ }
1806
+ const sessionName = process.env.SQUADS_TMUX_SESSION || `squads-${squadName}-${agentName}-${Date.now()}`;
1221
1807
  if (verbose) {
1222
- writeLine(` ${colors.dim}Spawning tmux session: ${sessionName}${RESET}`);
1808
+ writeLine(` ${colors.dim}Project: ${projectRoot}${RESET}`);
1809
+ writeLine(` ${colors.dim}Session: ${sessionName}${RESET}`);
1810
+ writeLine(` ${colors.dim}Auth: ${useApi ? "API credits" : "subscription"}${RESET}`);
1223
1811
  }
1224
- const escapedPrompt = prompt2.replace(/'/g, "'\\''");
1225
- const claudeCmd = `claude --dangerously-skip-permissions --mcp-config '${userConfigPath}' -- '${escapedPrompt}'`;
1226
- const tmux = spawn("tmux", [
1812
+ const claudeCmd = `cd '${projectRoot}' && claude --dangerously-skip-permissions --mcp-config '${userConfigPath}' -- '${escapedPrompt}'; tmux kill-session -t ${sessionName} 2>/dev/null`;
1813
+ const tmux = spawn2("tmux", [
1227
1814
  "new-session",
1228
1815
  "-d",
1229
1816
  // Detached
@@ -1241,16 +1828,12 @@ async function executeWithClaude(prompt2, verbose, timeoutMinutes = 30) {
1241
1828
  stdio: "ignore",
1242
1829
  detached: true,
1243
1830
  env: {
1244
- ...process.env,
1831
+ ...spawnEnv,
1245
1832
  SQUADS_SQUAD: squadName,
1246
1833
  SQUADS_AGENT: agentName
1247
1834
  }
1248
1835
  });
1249
1836
  tmux.unref();
1250
- spawn("/bin/sh", ["-c", `sleep 2 && tmux send-keys -t '${sessionName}' Down Enter`], {
1251
- stdio: "ignore",
1252
- detached: true
1253
- }).unref();
1254
1837
  if (verbose) {
1255
1838
  writeLine(` ${colors.dim}Attach: tmux attach -t ${sessionName}${RESET}`);
1256
1839
  }
@@ -1259,6 +1842,7 @@ async function executeWithClaude(prompt2, verbose, timeoutMinutes = 30) {
1259
1842
 
1260
1843
  // src/commands/list.ts
1261
1844
  async function listCommand(options) {
1845
+ await track("cli.list", { squads: options.squads, agents: options.agents });
1262
1846
  const squadsDir = findSquadsDir();
1263
1847
  if (!squadsDir) {
1264
1848
  writeLine(` ${colors.red}No .agents/squads directory found${RESET}`);
@@ -1314,7 +1898,7 @@ import { join as join6 } from "path";
1314
1898
  import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync4, mkdirSync as mkdirSync3, unlinkSync } from "fs";
1315
1899
  import { join as join5, dirname as dirname2 } from "path";
1316
1900
  import { homedir as homedir2 } from "os";
1317
- import { execSync as execSync2 } from "child_process";
1901
+ import { execSync as execSync3 } from "child_process";
1318
1902
  import { fileURLToPath } from "url";
1319
1903
  function getPackageVersion() {
1320
1904
  try {
@@ -1330,8 +1914,8 @@ function getPackageVersion() {
1330
1914
  ];
1331
1915
  for (const pkgPath of possiblePaths) {
1332
1916
  if (existsSync5(pkgPath)) {
1333
- const pkg = JSON.parse(readFileSync4(pkgPath, "utf-8"));
1334
- return pkg.version || "0.0.0";
1917
+ const pkg2 = JSON.parse(readFileSync4(pkgPath, "utf-8"));
1918
+ return pkg2.version || "0.0.0";
1335
1919
  }
1336
1920
  }
1337
1921
  } catch {
@@ -1377,7 +1961,7 @@ function writeCache(latestVersion) {
1377
1961
  }
1378
1962
  function fetchLatestVersion() {
1379
1963
  try {
1380
- const result = execSync2("npm view squads-cli version 2>/dev/null", {
1964
+ const result = execSync3("npm view squads-cli version 2>/dev/null", {
1381
1965
  encoding: "utf-8",
1382
1966
  timeout: 5e3
1383
1967
  }).trim();
@@ -1394,25 +1978,42 @@ function checkForUpdate() {
1394
1978
  };
1395
1979
  const cache = readCache();
1396
1980
  const now = Date.now();
1397
- if (cache && now - cache.checkedAt < CACHE_TTL_MS) {
1981
+ if (cache) {
1398
1982
  result.latestVersion = cache.latestVersion;
1399
1983
  result.updateAvailable = isNewerVersion(CURRENT_VERSION, cache.latestVersion);
1984
+ if (now - cache.checkedAt >= CACHE_TTL_MS) {
1985
+ triggerBackgroundRefresh();
1986
+ }
1400
1987
  return result;
1401
1988
  }
1402
- const latestVersion = fetchLatestVersion();
1403
- if (latestVersion) {
1404
- writeCache(latestVersion);
1405
- result.latestVersion = latestVersion;
1406
- result.updateAvailable = isNewerVersion(CURRENT_VERSION, latestVersion);
1407
- } else if (cache) {
1408
- result.latestVersion = cache.latestVersion;
1409
- result.updateAvailable = isNewerVersion(CURRENT_VERSION, cache.latestVersion);
1410
- }
1989
+ triggerBackgroundRefresh();
1411
1990
  return result;
1412
1991
  }
1992
+ function triggerBackgroundRefresh() {
1993
+ try {
1994
+ const { spawn: spawn8 } = __require("child_process");
1995
+ const child = spawn8("npm", ["view", "squads-cli", "version"], {
1996
+ detached: true,
1997
+ stdio: ["ignore", "pipe", "ignore"],
1998
+ shell: true
1999
+ });
2000
+ let output = "";
2001
+ child.stdout?.on("data", (data) => {
2002
+ output += data.toString();
2003
+ });
2004
+ child.on("close", () => {
2005
+ const version2 = output.trim();
2006
+ if (version2 && /^\d+\.\d+\.\d+/.test(version2)) {
2007
+ writeCache(version2);
2008
+ }
2009
+ });
2010
+ child.unref();
2011
+ } catch {
2012
+ }
2013
+ }
1413
2014
  function performUpdate() {
1414
2015
  try {
1415
- execSync2("npm update -g squads-cli", {
2016
+ execSync3("npm update -g squads-cli", {
1416
2017
  encoding: "utf-8",
1417
2018
  stdio: "inherit",
1418
2019
  timeout: 12e4
@@ -1445,6 +2046,7 @@ function refreshVersionCache() {
1445
2046
 
1446
2047
  // src/commands/status.ts
1447
2048
  async function statusCommand(squadName, options = {}) {
2049
+ await track(Events.CLI_STATUS, { squad: squadName || "all", verbose: options.verbose });
1448
2050
  const squadsDir = findSquadsDir();
1449
2051
  if (!squadsDir) {
1450
2052
  writeLine(`${colors.red}No .agents/squads directory found${RESET}`);
@@ -1461,7 +2063,7 @@ async function showOverallStatus(squadsDir, _options) {
1461
2063
  const squads = listSquads(squadsDir);
1462
2064
  const memoryDir = findMemoryDir();
1463
2065
  cleanupStaleSessions();
1464
- const sessionSummary = getLiveSessionSummary();
2066
+ const sessionSummary = await getLiveSessionSummaryAsync();
1465
2067
  writeLine();
1466
2068
  writeLine(` ${gradient("squads")} ${colors.dim}status${RESET}`);
1467
2069
  const updateInfo = checkForUpdate();
@@ -1601,8 +2203,8 @@ async function showSquadStatus(squadName, squadsDir, options) {
1601
2203
  import { existsSync as existsSync7, readFileSync as readFileSync5, writeFileSync as writeFileSync5, mkdirSync as mkdirSync4, copyFileSync } from "fs";
1602
2204
  import { join as join7, dirname as dirname3 } from "path";
1603
2205
  import { homedir as homedir3 } from "os";
1604
- import { execSync as execSync3, spawn as spawn2 } from "child_process";
1605
- import { createInterface } from "readline";
2206
+ import { execSync as execSync4, spawn as spawn3 } from "child_process";
2207
+ import { createInterface as createInterface2 } from "readline";
1606
2208
  import { fileURLToPath as fileURLToPath2 } from "url";
1607
2209
  var __filename2 = fileURLToPath2(import.meta.url);
1608
2210
  var __dirname2 = dirname3(__filename2);
@@ -1728,7 +2330,7 @@ function showServiceSetupGuide(serviceName, issue) {
1728
2330
  writeLine();
1729
2331
  }
1730
2332
  async function prompt(question, defaultValue) {
1731
- const rl = createInterface({
2333
+ const rl = createInterface2({
1732
2334
  input: process.stdin,
1733
2335
  output: process.stdout
1734
2336
  });
@@ -1740,7 +2342,7 @@ async function prompt(question, defaultValue) {
1740
2342
  });
1741
2343
  });
1742
2344
  }
1743
- async function confirm(question, defaultYes = true) {
2345
+ async function confirm2(question, defaultYes = true) {
1744
2346
  const suffix = defaultYes ? "[Y/n]" : "[y/N]";
1745
2347
  const answer = await prompt(`${question} ${suffix}`);
1746
2348
  if (!answer) return defaultYes;
@@ -1821,7 +2423,7 @@ function applyStackConfig() {
1821
2423
  }
1822
2424
  function isDockerRunning() {
1823
2425
  try {
1824
- execSync3("docker info", { stdio: "ignore" });
2426
+ execSync4("docker info", { stdio: "ignore" });
1825
2427
  return true;
1826
2428
  } catch {
1827
2429
  return false;
@@ -1829,7 +2431,7 @@ function isDockerRunning() {
1829
2431
  }
1830
2432
  function getContainerStatus(name) {
1831
2433
  try {
1832
- const runningOutput = execSync3(
2434
+ const runningOutput = execSync4(
1833
2435
  `docker inspect ${name} --format '{{.State.Running}}'`,
1834
2436
  { encoding: "utf-8", stdio: ["pipe", "pipe", "ignore"] }
1835
2437
  ).trim();
@@ -1839,7 +2441,7 @@ function getContainerStatus(name) {
1839
2441
  }
1840
2442
  let port;
1841
2443
  try {
1842
- const portOutput = execSync3(
2444
+ const portOutput = execSync4(
1843
2445
  `docker inspect ${name} --format '{{range .NetworkSettings.Ports}}{{range .}}{{.HostPort}}{{end}}{{end}}'`,
1844
2446
  { encoding: "utf-8", stdio: ["pipe", "pipe", "ignore"] }
1845
2447
  ).trim();
@@ -1848,7 +2450,7 @@ function getContainerStatus(name) {
1848
2450
  }
1849
2451
  let healthy = true;
1850
2452
  try {
1851
- const healthOutput = execSync3(
2453
+ const healthOutput = execSync4(
1852
2454
  `docker inspect ${name} --format '{{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}'`,
1853
2455
  { encoding: "utf-8", stdio: ["pipe", "pipe", "ignore"] }
1854
2456
  ).trim();
@@ -1959,7 +2561,7 @@ async function stackInitCommand() {
1959
2561
  }
1960
2562
  composeDir = targetDir;
1961
2563
  writeLine(` ${colors.green}${icons.success}${RESET} Files copied`);
1962
- } catch (err) {
2564
+ } catch {
1963
2565
  writeLine(` ${colors.yellow}${icons.warning}${RESET} Could not copy files, using source location`);
1964
2566
  }
1965
2567
  } else if (existsSync7(targetDir)) {
@@ -2044,19 +2646,19 @@ BRIDGE_PORT=8088
2044
2646
  }
2045
2647
  writeLine();
2046
2648
  if (requiredNotRunning.length > 0) {
2047
- const shouldStart = await confirm("Start required services now?");
2649
+ const shouldStart = await confirm2("Start required services now?");
2048
2650
  if (shouldStart) {
2049
2651
  writeLine();
2050
2652
  writeLine(` ${colors.cyan}${icons.progress}${RESET} Starting containers...`);
2051
2653
  try {
2052
- execSync3("docker compose up -d", {
2654
+ execSync4("docker compose up -d", {
2053
2655
  cwd: composeDir,
2054
2656
  stdio: "inherit"
2055
2657
  });
2056
2658
  writeLine(` ${colors.cyan}${icons.progress}${RESET} Waiting for services to be ready...`);
2057
2659
  await new Promise((resolve) => setTimeout(resolve, 5e3));
2058
2660
  writeLine(` ${colors.green}${icons.success}${RESET} Services started`);
2059
- } catch (err) {
2661
+ } catch {
2060
2662
  writeLine(` ${colors.red}${icons.error}${RESET} Failed to start services`);
2061
2663
  writeLine(` ${colors.dim}Try manually: cd ${composeDir} && docker compose up -d${RESET}`);
2062
2664
  }
@@ -2182,7 +2784,7 @@ async function stackUpCommand() {
2182
2784
  writeLine(` ${colors.cyan}${icons.progress}${RESET} Starting containers from ${colors.dim}${composeDir}${RESET}`);
2183
2785
  writeLine();
2184
2786
  try {
2185
- const child = spawn2("docker-compose", ["up", "-d"], {
2787
+ const child = spawn3("docker-compose", ["up", "-d"], {
2186
2788
  cwd: composeDir,
2187
2789
  stdio: "inherit"
2188
2790
  });
@@ -2249,7 +2851,7 @@ async function stackHealthCommand(verbose = false) {
2249
2851
  let logs;
2250
2852
  if (!ok && verbose) {
2251
2853
  try {
2252
- logs = execSync3(`docker logs ${container.name} --tail 10 2>&1`, {
2854
+ logs = execSync4(`docker logs ${container.name} --tail 10 2>&1`, {
2253
2855
  encoding: "utf-8",
2254
2856
  stdio: ["pipe", "pipe", "pipe"]
2255
2857
  });
@@ -2300,7 +2902,7 @@ function stackLogsCommand(service, tail = 50) {
2300
2902
  };
2301
2903
  const container = containerMap[service] || `squads-${service}`;
2302
2904
  try {
2303
- execSync3(`docker logs ${container} --tail ${tail}`, { stdio: "inherit" });
2905
+ execSync4(`docker logs ${container} --tail ${tail}`, { stdio: "inherit" });
2304
2906
  } catch {
2305
2907
  writeLine(` ${colors.red}${icons.error}${RESET} Container ${container} not found`);
2306
2908
  }
@@ -2318,7 +2920,7 @@ async function stackDownCommand() {
2318
2920
  writeLine(` ${colors.cyan}${icons.progress}${RESET} Stopping containers...`);
2319
2921
  writeLine();
2320
2922
  try {
2321
- const child = spawn2("docker-compose", ["down"], {
2923
+ const child = spawn3("docker-compose", ["down"], {
2322
2924
  cwd: composeDir,
2323
2925
  stdio: "inherit"
2324
2926
  });
@@ -2343,6 +2945,7 @@ async function stackDownCommand() {
2343
2945
  var SQUADS_BRIDGE_URL = process.env.SQUADS_BRIDGE_URL || "http://localhost:8088";
2344
2946
  var MEM0_API_URL = process.env.MEM0_API_URL || "http://localhost:8000";
2345
2947
  async function memoryQueryCommand(query, options) {
2948
+ await track(Events.CLI_MEMORY_QUERY, { squad: options.squad, agent: options.agent });
2346
2949
  const memoryDir = findMemoryDir();
2347
2950
  if (!memoryDir) {
2348
2951
  writeLine(` ${colors.red}No .agents/memory directory found${RESET}`);
@@ -2403,6 +3006,7 @@ async function memoryQueryCommand(query, options) {
2403
3006
  writeLine();
2404
3007
  }
2405
3008
  async function memoryShowCommand(squadName, _options) {
3009
+ await track(Events.CLI_MEMORY_SHOW, { squad: squadName });
2406
3010
  const memoryDir = findMemoryDir();
2407
3011
  if (!memoryDir) {
2408
3012
  writeLine(` ${colors.red}No .agents/memory directory found${RESET}`);
@@ -2434,6 +3038,7 @@ async function memoryShowCommand(squadName, _options) {
2434
3038
  writeLine();
2435
3039
  }
2436
3040
  async function memoryUpdateCommand(squadName, content, options) {
3041
+ await track(Events.CLI_MEMORY_UPDATE, { squad: squadName, agent: options.agent, type: options.type });
2437
3042
  const agentName = options.agent || `${squadName}-lead`;
2438
3043
  const type = options.type || "learnings";
2439
3044
  writeLine();
@@ -2447,6 +3052,7 @@ async function memoryUpdateCommand(squadName, content, options) {
2447
3052
  writeLine();
2448
3053
  }
2449
3054
  async function memoryListCommand() {
3055
+ await track(Events.CLI_MEMORY_LIST);
2450
3056
  const memoryDir = findMemoryDir();
2451
3057
  if (!memoryDir) {
2452
3058
  writeLine(` ${colors.red}No .agents/memory directory found${RESET}`);
@@ -2683,7 +3289,7 @@ async function memoryExtractCommand(options = {}) {
2683
3289
  }
2684
3290
 
2685
3291
  // src/commands/sync.ts
2686
- import { execSync as execSync4 } from "child_process";
3292
+ import { execSync as execSync5 } from "child_process";
2687
3293
  import { existsSync as existsSync8, readFileSync as readFileSync6, writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, readdirSync as readdirSync2 } from "fs";
2688
3294
  import { join as join8 } from "path";
2689
3295
  var PATH_TO_SQUAD = {
@@ -2727,7 +3333,7 @@ function getRecentCommits(since) {
2727
3333
  const commits = [];
2728
3334
  try {
2729
3335
  const sinceArg = since ? `--since="${since}"` : "-n 20";
2730
- const logOutput = execSync4(
3336
+ const logOutput = execSync5(
2731
3337
  `git log ${sinceArg} --format="%H|%aI|%s" --name-only`,
2732
3338
  { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
2733
3339
  ).trim();
@@ -2748,7 +3354,7 @@ function getRecentCommits(since) {
2748
3354
  });
2749
3355
  }
2750
3356
  }
2751
- } catch (error) {
3357
+ } catch {
2752
3358
  }
2753
3359
  return commits;
2754
3360
  }
@@ -2825,8 +3431,8 @@ Updated: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}
2825
3431
  }
2826
3432
  function gitPullMemory() {
2827
3433
  try {
2828
- execSync4("git fetch origin", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
2829
- const status = execSync4("git status -sb", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
3434
+ execSync5("git fetch origin", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
3435
+ const status = execSync5("git status -sb", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
2830
3436
  const behindMatch = status.match(/behind (\d+)/);
2831
3437
  const aheadMatch = status.match(/ahead (\d+)/);
2832
3438
  const behind = behindMatch ? parseInt(behindMatch[1]) : 0;
@@ -2834,7 +3440,7 @@ function gitPullMemory() {
2834
3440
  if (behind === 0) {
2835
3441
  return { success: true, output: "Already up to date", behind: 0, ahead };
2836
3442
  }
2837
- const output = execSync4("git pull --rebase origin main", {
3443
+ const output = execSync5("git pull --rebase origin main", {
2838
3444
  encoding: "utf-8",
2839
3445
  stdio: ["pipe", "pipe", "pipe"]
2840
3446
  });
@@ -2846,18 +3452,18 @@ function gitPullMemory() {
2846
3452
  }
2847
3453
  function gitPushMemory() {
2848
3454
  try {
2849
- const status = execSync4("git status --porcelain .agents/memory/", {
3455
+ const status = execSync5("git status --porcelain .agents/memory/", {
2850
3456
  encoding: "utf-8",
2851
3457
  stdio: ["pipe", "pipe", "pipe"]
2852
3458
  }).trim();
2853
3459
  if (status) {
2854
- execSync4("git add .agents/memory/", { stdio: ["pipe", "pipe", "pipe"] });
2855
- execSync4('git commit -m "chore: sync squad memory"', {
3460
+ execSync5("git add .agents/memory/", { stdio: ["pipe", "pipe", "pipe"] });
3461
+ execSync5('git commit -m "chore: sync squad memory"', {
2856
3462
  encoding: "utf-8",
2857
3463
  stdio: ["pipe", "pipe", "pipe"]
2858
3464
  });
2859
3465
  }
2860
- const output = execSync4("git push origin main", {
3466
+ const output = execSync5("git push origin main", {
2861
3467
  encoding: "utf-8",
2862
3468
  stdio: ["pipe", "pipe", "pipe"]
2863
3469
  });
@@ -2868,8 +3474,9 @@ function gitPushMemory() {
2868
3474
  }
2869
3475
  }
2870
3476
  async function syncCommand(options = {}) {
3477
+ await track(Events.CLI_MEMORY_SYNC, { push: options.push, pull: options.pull });
2871
3478
  const memoryDir = findMemoryDir();
2872
- const squadsDir = findSquadsDir();
3479
+ const _squadsDir = findSquadsDir();
2873
3480
  if (!memoryDir) {
2874
3481
  writeLine(` ${colors.yellow}No .agents/memory directory found${RESET}`);
2875
3482
  writeLine(` ${colors.dim}Run \`squads init\` to create one.${RESET}`);
@@ -2958,6 +3565,7 @@ async function syncCommand(options = {}) {
2958
3565
 
2959
3566
  // src/commands/goal.ts
2960
3567
  async function goalSetCommand(squadName, description, options) {
3568
+ await track(Events.CLI_GOAL_SET, { squad: squadName });
2961
3569
  const squad = loadSquad(squadName);
2962
3570
  if (!squad) {
2963
3571
  writeLine(` ${colors.red}Squad "${squadName}" not found${RESET}`);
@@ -2981,9 +3589,11 @@ async function goalSetCommand(squadName, description, options) {
2981
3589
  writeLine();
2982
3590
  }
2983
3591
  async function goalListCommand(squadName, options = {}) {
3592
+ await track(Events.CLI_GOAL_LIST, { squad: squadName || "all" });
2984
3593
  const squadsDir = findSquadsDir();
2985
3594
  if (!squadsDir) {
2986
3595
  writeLine(` ${colors.red}No .agents/squads directory found${RESET}`);
3596
+ writeLine(` ${colors.dim}Run \`squads init\` to create one.${RESET}`);
2987
3597
  return;
2988
3598
  }
2989
3599
  const squadsToCheck = squadName ? [squadName] : listSquads(squadsDir);
@@ -3037,15 +3647,21 @@ async function goalListCommand(squadName, options = {}) {
3037
3647
  writeLine();
3038
3648
  }
3039
3649
  async function goalCompleteCommand(squadName, goalIndex) {
3650
+ await track(Events.CLI_GOAL_COMPLETE, { squad: squadName });
3040
3651
  const squad = loadSquad(squadName);
3041
3652
  if (!squad) {
3042
3653
  writeLine(` ${colors.red}Squad "${squadName}" not found${RESET}`);
3043
3654
  return;
3044
3655
  }
3045
3656
  const idx = parseInt(goalIndex) - 1;
3046
- if (idx < 0 || idx >= squad.goals.length) {
3657
+ if (isNaN(idx) || idx < 0 || idx >= squad.goals.length) {
3047
3658
  writeLine(` ${colors.red}Invalid goal index: ${goalIndex}${RESET}`);
3048
- writeLine(` ${colors.dim}Squad has ${squad.goals.length} goal(s)${RESET}`);
3659
+ if (squad.goals.length === 0) {
3660
+ writeLine(` ${colors.dim}Squad has no goals${RESET}`);
3661
+ } else {
3662
+ writeLine(` ${colors.dim}Valid indexes: 1-${squad.goals.length}${RESET}`);
3663
+ writeLine(` ${colors.dim}Tip: Run 'squads goal list ${squadName}' to see goals with indexes${RESET}`);
3664
+ }
3049
3665
  return;
3050
3666
  }
3051
3667
  const success = updateGoalInSquad(squadName, idx, { completed: true });
@@ -3058,14 +3674,21 @@ async function goalCompleteCommand(squadName, goalIndex) {
3058
3674
  writeLine();
3059
3675
  }
3060
3676
  async function goalProgressCommand(squadName, goalIndex, progress2) {
3677
+ await track(Events.CLI_GOAL_PROGRESS, { squad: squadName });
3061
3678
  const squad = loadSquad(squadName);
3062
3679
  if (!squad) {
3063
3680
  writeLine(` ${colors.red}Squad "${squadName}" not found${RESET}`);
3064
3681
  return;
3065
3682
  }
3066
3683
  const idx = parseInt(goalIndex) - 1;
3067
- if (idx < 0 || idx >= squad.goals.length) {
3684
+ if (isNaN(idx) || idx < 0 || idx >= squad.goals.length) {
3068
3685
  writeLine(` ${colors.red}Invalid goal index: ${goalIndex}${RESET}`);
3686
+ if (squad.goals.length === 0) {
3687
+ writeLine(` ${colors.dim}Squad has no goals${RESET}`);
3688
+ } else {
3689
+ writeLine(` ${colors.dim}Valid indexes: 1-${squad.goals.length}${RESET}`);
3690
+ writeLine(` ${colors.dim}Tip: Run 'squads goal list ${squadName}' to see goals with indexes${RESET}`);
3691
+ }
3069
3692
  return;
3070
3693
  }
3071
3694
  const success = updateGoalInSquad(squadName, idx, { progress: progress2 });
@@ -3081,7 +3704,7 @@ async function goalProgressCommand(squadName, goalIndex, progress2) {
3081
3704
 
3082
3705
  // src/commands/feedback.ts
3083
3706
  import { readFileSync as readFileSync7, writeFileSync as writeFileSync7, existsSync as existsSync9, mkdirSync as mkdirSync6, readdirSync as readdirSync3 } from "fs";
3084
- import { join as join9, dirname as dirname5 } from "path";
3707
+ import { join as join9, dirname as dirname4 } from "path";
3085
3708
  function getFeedbackPath(squadName) {
3086
3709
  const memoryDir = findMemoryDir();
3087
3710
  if (!memoryDir) return null;
@@ -3141,6 +3764,7 @@ function parseFeedbackHistory(content) {
3141
3764
  return entries;
3142
3765
  }
3143
3766
  async function feedbackAddCommand(squadName, rating, feedback2, options) {
3767
+ await track(Events.CLI_FEEDBACK_ADD, { squad: squadName, rating: parseInt(rating) });
3144
3768
  const feedbackPath = getFeedbackPath(squadName);
3145
3769
  if (!feedbackPath) {
3146
3770
  writeLine(` ${colors.red}Could not find memory directory${RESET}`);
@@ -3152,7 +3776,7 @@ async function feedbackAddCommand(squadName, rating, feedback2, options) {
3152
3776
  return;
3153
3777
  }
3154
3778
  const lastExec = getLastExecution(squadName);
3155
- const dir = dirname5(feedbackPath);
3779
+ const dir = dirname4(feedbackPath);
3156
3780
  if (!existsSync9(dir)) {
3157
3781
  mkdirSync6(dir, { recursive: true });
3158
3782
  }
@@ -3202,6 +3826,7 @@ _Date: ${date}_
3202
3826
  writeLine();
3203
3827
  }
3204
3828
  async function feedbackShowCommand(squadName, options) {
3829
+ await track(Events.CLI_FEEDBACK_SHOW, { squad: squadName });
3205
3830
  const feedbackPath = getFeedbackPath(squadName);
3206
3831
  if (!feedbackPath || !existsSync9(feedbackPath)) {
3207
3832
  writeLine(` ${colors.yellow}No feedback recorded for ${squadName}${RESET}`);
@@ -3234,6 +3859,7 @@ async function feedbackShowCommand(squadName, options) {
3234
3859
  }
3235
3860
  }
3236
3861
  async function feedbackStatsCommand() {
3862
+ await track(Events.CLI_FEEDBACK_STATS);
3237
3863
  const memoryDir = findMemoryDir();
3238
3864
  if (!memoryDir) {
3239
3865
  writeLine(` ${colors.red}Could not find memory directory${RESET}`);
@@ -3279,21 +3905,324 @@ async function feedbackStatsCommand() {
3279
3905
  // src/commands/dashboard.ts
3280
3906
  import { readdirSync as readdirSync4, existsSync as existsSync10, statSync as statSync2 } from "fs";
3281
3907
  import { join as join10 } from "path";
3282
- import { homedir as homedir4 } from "os";
3283
3908
 
3284
- // src/lib/costs.ts
3285
- var MODEL_PRICING = {
3286
- "claude-opus-4-5-20251101": { input: 15, output: 75 },
3287
- "claude-sonnet-4-20250514": { input: 3, output: 15 },
3288
- "claude-haiku-4-5-20251001": { input: 0.8, output: 4 },
3289
- "claude-3-5-sonnet-20241022": { input: 3, output: 15 },
3290
- "claude-3-5-haiku-20241022": { input: 0.8, output: 4 },
3291
- default: { input: 3, output: 15 }
3909
+ // src/lib/providers.ts
3910
+ var PROVIDERS = {
3911
+ anthropic: {
3912
+ name: "anthropic",
3913
+ displayName: "Anthropic",
3914
+ envVars: ["ANTHROPIC_API_KEY"],
3915
+ modelPatterns: [/^claude/i],
3916
+ defaultPricing: { input: 3, output: 15 },
3917
+ models: {
3918
+ "claude-opus-4-5": { input: 15, output: 75 },
3919
+ "claude-opus-4-5-20251101": { input: 15, output: 75 },
3920
+ "claude-sonnet-4": { input: 3, output: 15 },
3921
+ "claude-sonnet-4-20250514": { input: 3, output: 15 },
3922
+ "claude-3-5-sonnet": { input: 3, output: 15 },
3923
+ "claude-3-5-sonnet-20241022": { input: 3, output: 15 },
3924
+ "claude-3-5-sonnet-20240620": { input: 3, output: 15 },
3925
+ "claude-haiku-3-5": { input: 0.8, output: 4 },
3926
+ "claude-3-5-haiku": { input: 0.8, output: 4 },
3927
+ "claude-3-5-haiku-20241022": { input: 0.8, output: 4 },
3928
+ "claude-3-opus": { input: 15, output: 75 },
3929
+ "claude-3-sonnet": { input: 3, output: 15 },
3930
+ "claude-3-haiku": { input: 0.25, output: 1.25 }
3931
+ }
3932
+ },
3933
+ openai: {
3934
+ name: "openai",
3935
+ displayName: "OpenAI",
3936
+ envVars: ["OPENAI_API_KEY"],
3937
+ modelPatterns: [/^gpt-/i, /^o1/i, /^o3/i],
3938
+ defaultPricing: { input: 2.5, output: 10 },
3939
+ models: {
3940
+ "gpt-4o": { input: 2.5, output: 10 },
3941
+ "gpt-4o-2024-11-20": { input: 2.5, output: 10 },
3942
+ "gpt-4o-2024-08-06": { input: 2.5, output: 10 },
3943
+ "gpt-4o-mini": { input: 0.15, output: 0.6 },
3944
+ "gpt-4o-mini-2024-07-18": { input: 0.15, output: 0.6 },
3945
+ "gpt-4-turbo": { input: 10, output: 30 },
3946
+ "gpt-4-turbo-2024-04-09": { input: 10, output: 30 },
3947
+ "gpt-4": { input: 30, output: 60 },
3948
+ "gpt-4-32k": { input: 60, output: 120 },
3949
+ "gpt-3.5-turbo": { input: 0.5, output: 1.5 },
3950
+ "gpt-3.5-turbo-0125": { input: 0.5, output: 1.5 },
3951
+ o1: { input: 15, output: 60 },
3952
+ "o1-2024-12-17": { input: 15, output: 60 },
3953
+ "o1-preview": { input: 15, output: 60 },
3954
+ "o1-mini": { input: 3, output: 12 },
3955
+ "o1-mini-2024-09-12": { input: 3, output: 12 },
3956
+ "o3-mini": { input: 1.1, output: 4.4 }
3957
+ }
3958
+ },
3959
+ google: {
3960
+ name: "google",
3961
+ displayName: "Google",
3962
+ envVars: ["GEMINI_API_KEY", "GOOGLE_API_KEY"],
3963
+ modelPatterns: [/^gemini/i],
3964
+ defaultPricing: { input: 0.1, output: 0.4 },
3965
+ models: {
3966
+ "gemini-2.0-flash": { input: 0.1, output: 0.4 },
3967
+ "gemini-2.0-flash-exp": { input: 0.1, output: 0.4 },
3968
+ "gemini-1.5-pro": { input: 1.25, output: 5 },
3969
+ "gemini-1.5-pro-002": { input: 1.25, output: 5 },
3970
+ "gemini-1.5-flash": { input: 0.075, output: 0.3 },
3971
+ "gemini-1.5-flash-002": { input: 0.075, output: 0.3 },
3972
+ "gemini-1.0-pro": { input: 0.5, output: 1.5 }
3973
+ }
3974
+ },
3975
+ mistral: {
3976
+ name: "mistral",
3977
+ displayName: "Mistral",
3978
+ envVars: ["MISTRAL_API_KEY"],
3979
+ modelPatterns: [/^mistral/i, /^mixtral/i, /^codestral/i],
3980
+ defaultPricing: { input: 2, output: 6 },
3981
+ models: {
3982
+ "mistral-large": { input: 2, output: 6 },
3983
+ "mistral-large-2411": { input: 2, output: 6 },
3984
+ "mistral-medium": { input: 2.7, output: 8.1 },
3985
+ "mistral-small": { input: 0.2, output: 0.6 },
3986
+ "mistral-small-2409": { input: 0.2, output: 0.6 },
3987
+ "mixtral-8x7b": { input: 0.7, output: 0.7 },
3988
+ "mixtral-8x22b": { input: 2, output: 6 },
3989
+ codestral: { input: 0.2, output: 0.6 },
3990
+ "codestral-2405": { input: 0.2, output: 0.6 }
3991
+ }
3992
+ },
3993
+ groq: {
3994
+ name: "groq",
3995
+ displayName: "Groq",
3996
+ envVars: ["GROQ_API_KEY"],
3997
+ modelPatterns: [/^llama/i],
3998
+ // llama models on Groq
3999
+ defaultPricing: { input: 0.59, output: 0.79 },
4000
+ models: {
4001
+ "llama-3.3-70b-versatile": { input: 0.59, output: 0.79 },
4002
+ "llama-3.3-70b-specdec": { input: 0.59, output: 0.99 },
4003
+ "llama-3.1-70b-versatile": { input: 0.59, output: 0.79 },
4004
+ "llama-3.1-8b-instant": { input: 0.05, output: 0.08 },
4005
+ "llama-3-70b-8192": { input: 0.59, output: 0.79 },
4006
+ "llama-3-8b-8192": { input: 0.05, output: 0.08 },
4007
+ "mixtral-8x7b-32768": { input: 0.24, output: 0.24 },
4008
+ "gemma2-9b-it": { input: 0.2, output: 0.2 }
4009
+ }
4010
+ },
4011
+ aws_bedrock: {
4012
+ name: "aws_bedrock",
4013
+ displayName: "AWS Bedrock",
4014
+ envVars: ["AWS_ACCESS_KEY_ID"],
4015
+ modelPatterns: [/^anthropic\./i, /^amazon\./i, /^meta\./i, /^mistral\./i],
4016
+ defaultPricing: { input: 3, output: 15 },
4017
+ models: {
4018
+ "anthropic.claude-3-5-sonnet-20241022-v2:0": { input: 3, output: 15 },
4019
+ "anthropic.claude-3-5-sonnet-20240620-v1:0": { input: 3, output: 15 },
4020
+ "anthropic.claude-3-5-haiku-20241022-v1:0": { input: 0.8, output: 4 },
4021
+ "anthropic.claude-3-opus-20240229-v1:0": { input: 15, output: 75 },
4022
+ "anthropic.claude-3-sonnet-20240229-v1:0": { input: 3, output: 15 },
4023
+ "anthropic.claude-3-haiku-20240307-v1:0": { input: 0.25, output: 1.25 },
4024
+ "amazon.titan-text-premier-v1:0": { input: 0.5, output: 1.5 },
4025
+ "amazon.titan-text-express-v1": { input: 0.2, output: 0.6 },
4026
+ "amazon.titan-text-lite-v1": { input: 0.15, output: 0.2 },
4027
+ "meta.llama3-70b-instruct-v1:0": { input: 2.65, output: 3.5 },
4028
+ "meta.llama3-8b-instruct-v1:0": { input: 0.3, output: 0.6 },
4029
+ "meta.llama3-1-405b-instruct-v1:0": { input: 5.32, output: 16 },
4030
+ "mistral.mistral-large-2407-v1:0": { input: 4, output: 12 }
4031
+ }
4032
+ },
4033
+ azure: {
4034
+ name: "azure",
4035
+ displayName: "Azure OpenAI",
4036
+ envVars: ["AZURE_OPENAI_API_KEY"],
4037
+ modelPatterns: [],
4038
+ // Detected via endpoint, not model name
4039
+ defaultPricing: { input: 2.5, output: 10 },
4040
+ models: {
4041
+ // Same pricing as OpenAI (regional variations possible)
4042
+ "gpt-4o": { input: 2.5, output: 10 },
4043
+ "gpt-4o-mini": { input: 0.15, output: 0.6 },
4044
+ "gpt-4-turbo": { input: 10, output: 30 },
4045
+ "gpt-4": { input: 30, output: 60 },
4046
+ "gpt-35-turbo": { input: 0.5, output: 1.5 }
4047
+ // Azure uses different name
4048
+ }
4049
+ }
3292
4050
  };
4051
+ function detectProviderFromModel(model) {
4052
+ const modelLower = model.toLowerCase();
4053
+ if (modelLower.includes("anthropic.") || modelLower.includes("amazon.") || modelLower.includes("meta.") || modelLower.includes("mistral.")) {
4054
+ return "aws_bedrock";
4055
+ }
4056
+ if (modelLower.startsWith("claude")) {
4057
+ return "anthropic";
4058
+ }
4059
+ if (modelLower.startsWith("gpt-") || modelLower.startsWith("o1") || modelLower.startsWith("o3")) {
4060
+ if (process.env.AZURE_OPENAI_ENDPOINT || process.env.AZURE_OPENAI_API_KEY) {
4061
+ return "azure";
4062
+ }
4063
+ return "openai";
4064
+ }
4065
+ if (modelLower.startsWith("gemini")) {
4066
+ return "google";
4067
+ }
4068
+ if (modelLower.startsWith("mistral") || modelLower.startsWith("mixtral") || modelLower.startsWith("codestral")) {
4069
+ if (process.env.GROQ_API_KEY && !process.env.MISTRAL_API_KEY) {
4070
+ return "groq";
4071
+ }
4072
+ return "mistral";
4073
+ }
4074
+ if (modelLower.startsWith("llama") || modelLower.startsWith("gemma")) {
4075
+ return "groq";
4076
+ }
4077
+ return "unknown";
4078
+ }
4079
+ function detectProvidersFromEnv() {
4080
+ const detected = [];
4081
+ if (process.env.ANTHROPIC_API_KEY) {
4082
+ const tier = parseInt(process.env.ANTHROPIC_TIER || "0", 10);
4083
+ const budgetSet = process.env.ANTHROPIC_BUDGET_DAILY || process.env.SQUADS_DAILY_BUDGET;
4084
+ const explicitPlan = process.env.SQUADS_PLAN_TYPE?.toLowerCase();
4085
+ let plan = "Max";
4086
+ let confidence = "inferred";
4087
+ let reason = "API key detected";
4088
+ if (explicitPlan === "usage") {
4089
+ plan = "Usage";
4090
+ confidence = "explicit";
4091
+ reason = "SQUADS_PLAN_TYPE=usage";
4092
+ } else if (explicitPlan === "max") {
4093
+ plan = "Max";
4094
+ confidence = "explicit";
4095
+ reason = "SQUADS_PLAN_TYPE=max";
4096
+ } else if (budgetSet) {
4097
+ plan = "Usage";
4098
+ reason = `Budget set ($${budgetSet}/day)`;
4099
+ } else if (tier >= 4) {
4100
+ plan = "Max";
4101
+ reason = `Tier ${tier} (high usage)`;
4102
+ } else if (tier >= 1 && tier <= 2) {
4103
+ plan = "Usage";
4104
+ reason = `Tier ${tier} (new user)`;
4105
+ }
4106
+ detected.push({ provider: "anthropic", plan, confidence, reason });
4107
+ }
4108
+ if (process.env.OPENAI_API_KEY) {
4109
+ const orgId = process.env.OPENAI_ORG_ID;
4110
+ detected.push({
4111
+ provider: "openai",
4112
+ plan: orgId ? "Team/Enterprise" : "Personal",
4113
+ confidence: "inferred",
4114
+ reason: orgId ? "Org ID set" : "API key only"
4115
+ });
4116
+ }
4117
+ if (process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY) {
4118
+ detected.push({
4119
+ provider: "google",
4120
+ plan: "Pay-as-you-go",
4121
+ confidence: "inferred",
4122
+ reason: "API key detected"
4123
+ });
4124
+ }
4125
+ if (process.env.AWS_ACCESS_KEY_ID) {
4126
+ detected.push({
4127
+ provider: "aws_bedrock",
4128
+ plan: "On-demand",
4129
+ confidence: "inferred",
4130
+ reason: "AWS credentials detected"
4131
+ });
4132
+ }
4133
+ if (process.env.AZURE_OPENAI_API_KEY) {
4134
+ detected.push({
4135
+ provider: "azure",
4136
+ plan: process.env.AZURE_OPENAI_DEPLOYMENT ? "PTU" : "Pay-as-you-go",
4137
+ confidence: "inferred",
4138
+ reason: process.env.AZURE_OPENAI_DEPLOYMENT ? "Deployment detected" : "API key only"
4139
+ });
4140
+ }
4141
+ if (process.env.MISTRAL_API_KEY) {
4142
+ detected.push({
4143
+ provider: "mistral",
4144
+ plan: "Pay-per-token",
4145
+ confidence: "inferred",
4146
+ reason: "API key detected"
4147
+ });
4148
+ }
4149
+ if (process.env.GROQ_API_KEY) {
4150
+ detected.push({
4151
+ provider: "groq",
4152
+ plan: "Developer",
4153
+ confidence: "inferred",
4154
+ reason: "API key detected"
4155
+ });
4156
+ }
4157
+ return detected;
4158
+ }
4159
+ function getModelPricing(provider, model) {
4160
+ if (provider === "unknown") {
4161
+ return { input: 3, output: 15 };
4162
+ }
4163
+ const config2 = PROVIDERS[provider];
4164
+ const modelLower = model.toLowerCase();
4165
+ if (config2.models[model]) {
4166
+ return config2.models[model];
4167
+ }
4168
+ const matchedModel = Object.keys(config2.models).find(
4169
+ (m) => m.toLowerCase() === modelLower
4170
+ );
4171
+ if (matchedModel) {
4172
+ return config2.models[matchedModel];
4173
+ }
4174
+ const partialMatch = Object.keys(config2.models).find(
4175
+ (m) => modelLower.includes(m.toLowerCase()) || m.toLowerCase().includes(modelLower)
4176
+ );
4177
+ if (partialMatch) {
4178
+ return config2.models[partialMatch];
4179
+ }
4180
+ return config2.defaultPricing;
4181
+ }
4182
+ function calcCost(provider, model, inputTokens, outputTokens, cachedTokens = 0) {
4183
+ const pricing = getModelPricing(provider, model);
4184
+ const inputCost = inputTokens / 1e6 * pricing.input;
4185
+ const outputCost = outputTokens / 1e6 * pricing.output;
4186
+ const cachedCost = pricing.cached ? cachedTokens / 1e6 * pricing.cached : 0;
4187
+ return inputCost + outputCost + cachedCost;
4188
+ }
4189
+ function getProviderDisplayName(provider) {
4190
+ if (provider === "unknown") return "Unknown";
4191
+ return PROVIDERS[provider].displayName;
4192
+ }
4193
+
4194
+ // src/lib/costs.ts
3293
4195
  var DEFAULT_DAILY_BUDGET = 200;
3294
4196
  var DEFAULT_DAILY_CALL_LIMIT = 1e3;
3295
4197
  var BRIDGE_URL = process.env.SQUADS_BRIDGE_URL || "http://localhost:8088";
3296
4198
  var FETCH_TIMEOUT_MS = 2e3;
4199
+ function detectPlan() {
4200
+ const explicitPlan = process.env.SQUADS_PLAN_TYPE?.toLowerCase();
4201
+ if (explicitPlan === "usage") {
4202
+ return { plan: "usage", confidence: "explicit", reason: "SQUADS_PLAN_TYPE=usage" };
4203
+ }
4204
+ if (explicitPlan === "max") {
4205
+ return { plan: "max", confidence: "explicit", reason: "SQUADS_PLAN_TYPE=max" };
4206
+ }
4207
+ const budgetSet = process.env.ANTHROPIC_BUDGET_DAILY || process.env.SQUADS_DAILY_BUDGET;
4208
+ if (budgetSet) {
4209
+ return { plan: "usage", confidence: "inferred", reason: `Budget set ($${budgetSet}/day)` };
4210
+ }
4211
+ const tier = parseInt(process.env.ANTHROPIC_TIER || "0", 10);
4212
+ if (tier >= 4) {
4213
+ return { plan: "max", confidence: "inferred", reason: `Tier ${tier} (high usage)` };
4214
+ }
4215
+ if (tier >= 1 && tier <= 2) {
4216
+ return { plan: "usage", confidence: "inferred", reason: `Tier ${tier} (new user)` };
4217
+ }
4218
+ return { plan: "unknown", confidence: "inferred", reason: "Not configured" };
4219
+ }
4220
+ function getPlanType() {
4221
+ return detectPlan().plan;
4222
+ }
4223
+ function isMaxPlan() {
4224
+ return getPlanType() === "max";
4225
+ }
3297
4226
  async function fetchWithTimeout(url, options = {}, timeoutMs = FETCH_TIMEOUT_MS) {
3298
4227
  const controller = new AbortController();
3299
4228
  const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
@@ -3306,9 +4235,9 @@ async function fetchWithTimeout(url, options = {}, timeoutMs = FETCH_TIMEOUT_MS)
3306
4235
  throw error;
3307
4236
  }
3308
4237
  }
3309
- function calcCost(model, inputTokens, outputTokens) {
3310
- const pricing = MODEL_PRICING[model] || MODEL_PRICING.default;
3311
- return inputTokens / 1e6 * pricing.input + outputTokens / 1e6 * pricing.output;
4238
+ function calcCost2(model, inputTokens, outputTokens) {
4239
+ const provider = detectProviderFromModel(model);
4240
+ return calcCost(provider, model, inputTokens, outputTokens);
3312
4241
  }
3313
4242
  async function fetchFromBridge(period = "day") {
3314
4243
  try {
@@ -3336,6 +4265,20 @@ async function fetchFromBridge(period = "day") {
3336
4265
  const totalInputTokens = bySquad.reduce((sum, s) => sum + s.inputTokens, 0);
3337
4266
  const totalAllInput = totalInputTokens + totalCachedTokens;
3338
4267
  const cacheHitRate = totalAllInput > 0 ? totalCachedTokens / totalAllInput * 100 : 0;
4268
+ const detectedProviders = detectProvidersFromEnv();
4269
+ const byProvider = detectedProviders.map((p) => ({
4270
+ provider: p.provider,
4271
+ displayName: getProviderDisplayName(p.provider),
4272
+ calls: 0,
4273
+ // Bridge doesn't track by provider yet
4274
+ inputTokens: 0,
4275
+ outputTokens: 0,
4276
+ cost: p.provider === "anthropic" ? totalCost : 0,
4277
+ // Assume all cost is Anthropic for now
4278
+ plan: p.plan,
4279
+ confidence: p.confidence,
4280
+ reason: p.reason
4281
+ }));
3339
4282
  return {
3340
4283
  totalCost,
3341
4284
  dailyBudget,
@@ -3348,6 +4291,7 @@ async function fetchFromBridge(period = "day") {
3348
4291
  totalInputTokens,
3349
4292
  cacheHitRate,
3350
4293
  bySquad,
4294
+ byProvider,
3351
4295
  source: "postgres"
3352
4296
  };
3353
4297
  } catch {
@@ -3384,7 +4328,7 @@ async function fetchFromLangfuse(limit = 100) {
3384
4328
  const usage = obs.usage || {};
3385
4329
  const inputTokens = usage.input || 0;
3386
4330
  const outputTokens = usage.output || 0;
3387
- const cost = calcCost(model, inputTokens, outputTokens);
4331
+ const cost = calcCost2(model, inputTokens, outputTokens);
3388
4332
  if (!bySquad[squad]) {
3389
4333
  bySquad[squad] = {
3390
4334
  squad,
@@ -3411,6 +4355,35 @@ async function fetchFromLangfuse(limit = 100) {
3411
4355
  const totalInputTokens = squadList.reduce((sum, s) => sum + s.inputTokens, 0);
3412
4356
  const totalAllInput = totalInputTokens + totalCachedTokens;
3413
4357
  const cacheHitRate = totalAllInput > 0 ? totalCachedTokens / totalAllInput * 100 : 0;
4358
+ const providerMap = {};
4359
+ for (const obs of observations) {
4360
+ if (obs.type !== "GENERATION") continue;
4361
+ const model = obs.model || "unknown";
4362
+ const provider = detectProviderFromModel(model);
4363
+ const usage = obs.usage || {};
4364
+ const inputTokens = usage.input || 0;
4365
+ const outputTokens = usage.output || 0;
4366
+ const cost = calcCost2(model, inputTokens, outputTokens);
4367
+ if (!providerMap[provider]) {
4368
+ const detection = detectProvidersFromEnv().find((p) => p.provider === provider);
4369
+ providerMap[provider] = {
4370
+ provider,
4371
+ displayName: getProviderDisplayName(provider),
4372
+ calls: 0,
4373
+ inputTokens: 0,
4374
+ outputTokens: 0,
4375
+ cost: 0,
4376
+ plan: detection?.plan,
4377
+ confidence: detection?.confidence,
4378
+ reason: detection?.reason
4379
+ };
4380
+ }
4381
+ providerMap[provider].calls += 1;
4382
+ providerMap[provider].inputTokens += inputTokens;
4383
+ providerMap[provider].outputTokens += outputTokens;
4384
+ providerMap[provider].cost += cost;
4385
+ }
4386
+ const byProvider = Object.values(providerMap).sort((a, b) => b.cost - a.cost);
3414
4387
  return {
3415
4388
  totalCost,
3416
4389
  dailyBudget,
@@ -3423,6 +4396,7 @@ async function fetchFromLangfuse(limit = 100) {
3423
4396
  totalInputTokens,
3424
4397
  cacheHitRate,
3425
4398
  bySquad: squadList,
4399
+ byProvider,
3426
4400
  source: "langfuse"
3427
4401
  };
3428
4402
  } catch {
@@ -3439,6 +4413,18 @@ async function fetchCostSummary(limit = 100, period = "day") {
3439
4413
  return langfuseResult;
3440
4414
  }
3441
4415
  const defaultBudget = parseFloat(process.env.SQUADS_DAILY_BUDGET || "") || DEFAULT_DAILY_BUDGET;
4416
+ const detectedProviders = detectProvidersFromEnv();
4417
+ const byProvider = detectedProviders.map((p) => ({
4418
+ provider: p.provider,
4419
+ displayName: getProviderDisplayName(p.provider),
4420
+ calls: 0,
4421
+ inputTokens: 0,
4422
+ outputTokens: 0,
4423
+ cost: 0,
4424
+ plan: p.plan,
4425
+ confidence: p.confidence,
4426
+ reason: p.reason
4427
+ }));
3442
4428
  return {
3443
4429
  totalCost: 0,
3444
4430
  dailyBudget: defaultBudget,
@@ -3451,28 +4437,19 @@ async function fetchCostSummary(limit = 100, period = "day") {
3451
4437
  totalInputTokens: 0,
3452
4438
  cacheHitRate: 0,
3453
4439
  bySquad: [],
4440
+ byProvider,
3454
4441
  source: "none"
3455
4442
  };
3456
4443
  }
3457
- function formatCostBar(usedPercent, width = 20) {
3458
- const filled = Math.min(Math.round(usedPercent / 100 * width), width);
3459
- const empty = width - filled;
3460
- return "\u2588".repeat(filled) + "\u2591".repeat(empty);
3461
- }
3462
4444
  async function fetchBridgeStats() {
3463
4445
  try {
3464
- const statsResponse = await fetchWithTimeout(`${BRIDGE_URL}/stats`, {
3465
- headers: { "Content-Type": "application/json" }
3466
- });
3467
- if (!statsResponse.ok) {
3468
- return null;
3469
- }
3470
- const stats = await statsResponse.json();
3471
- const healthResponse = await fetchWithTimeout(`${BRIDGE_URL}/health`, {
3472
- headers: { "Content-Type": "application/json" }
3473
- });
3474
- const health = healthResponse.ok ? await healthResponse.json() : {};
3475
- const [costResponse, weekResponse] = await Promise.all([
4446
+ const [statsResponse, healthResponse, costResponse, weekResponse] = await Promise.all([
4447
+ fetchWithTimeout(`${BRIDGE_URL}/stats`, {
4448
+ headers: { "Content-Type": "application/json" }
4449
+ }),
4450
+ fetchWithTimeout(`${BRIDGE_URL}/health`, {
4451
+ headers: { "Content-Type": "application/json" }
4452
+ }),
3476
4453
  fetchWithTimeout(`${BRIDGE_URL}/api/cost/summary?period=day`, {
3477
4454
  headers: { "Content-Type": "application/json" }
3478
4455
  }),
@@ -3480,8 +4457,15 @@ async function fetchBridgeStats() {
3480
4457
  headers: { "Content-Type": "application/json" }
3481
4458
  })
3482
4459
  ]);
3483
- const costData = costResponse.ok ? await costResponse.json() : {};
3484
- const weekData = weekResponse.ok ? await weekResponse.json() : {};
4460
+ if (!statsResponse.ok) {
4461
+ return null;
4462
+ }
4463
+ const [stats, health, costData, weekData] = await Promise.all([
4464
+ statsResponse.json(),
4465
+ healthResponse.ok ? healthResponse.json() : Promise.resolve({}),
4466
+ costResponse.ok ? costResponse.json() : Promise.resolve({}),
4467
+ weekResponse.ok ? weekResponse.json() : Promise.resolve({})
4468
+ ]);
3485
4469
  return {
3486
4470
  status: stats.status || "unknown",
3487
4471
  source: stats.source || "none",
@@ -3528,6 +4512,38 @@ async function fetchBridgeStats() {
3528
4512
  return null;
3529
4513
  }
3530
4514
  }
4515
+ async function fetchRateLimits() {
4516
+ try {
4517
+ const response = await fetchWithTimeout(`${BRIDGE_URL}/api/rate-limits`, {
4518
+ headers: { "Content-Type": "application/json" }
4519
+ });
4520
+ if (!response.ok) {
4521
+ return { limits: {}, source: "none" };
4522
+ }
4523
+ const data = await response.json();
4524
+ const rateLimits = data.rate_limits || {};
4525
+ const limits = {};
4526
+ for (const [key, value] of Object.entries(rateLimits)) {
4527
+ limits[key] = {
4528
+ model: value.model || key,
4529
+ requestsLimit: value.requests_limit || 0,
4530
+ requestsRemaining: value.requests_remaining || 0,
4531
+ requestsReset: value.requests_reset,
4532
+ tokensLimit: value.tokens_limit || 0,
4533
+ tokensRemaining: value.tokens_remaining || 0,
4534
+ tokensReset: value.tokens_reset,
4535
+ inputTokensLimit: value.input_tokens_limit,
4536
+ inputTokensRemaining: value.input_tokens_remaining,
4537
+ outputTokensLimit: value.output_tokens_limit,
4538
+ outputTokensRemaining: value.output_tokens_remaining,
4539
+ capturedAt: value.captured_at || (/* @__PURE__ */ new Date()).toISOString()
4540
+ };
4541
+ }
4542
+ return { limits, source: "proxy" };
4543
+ } catch {
4544
+ return { limits: {}, source: "none" };
4545
+ }
4546
+ }
3531
4547
  async function fetchInsights(period = "week") {
3532
4548
  try {
3533
4549
  const response = await fetchWithTimeout(`${BRIDGE_URL}/api/insights?period=${period}`, {
@@ -3591,11 +4607,39 @@ async function fetchInsights(period = "week") {
3591
4607
  };
3592
4608
  }
3593
4609
  }
4610
+ async function fetchNpmStats(packageName = process.env.SQUADS_NPM_PACKAGE || "squads-cli") {
4611
+ try {
4612
+ const [dayRes, weekRes, monthRes] = await Promise.all([
4613
+ fetch(`https://api.npmjs.org/downloads/point/last-day/${packageName}`),
4614
+ fetch(`https://api.npmjs.org/downloads/point/last-week/${packageName}`),
4615
+ fetch(`https://api.npmjs.org/downloads/point/last-month/${packageName}`)
4616
+ ]);
4617
+ if (!dayRes.ok || !weekRes.ok || !monthRes.ok) return null;
4618
+ const [dayData, weekData, monthData] = await Promise.all([
4619
+ dayRes.json(),
4620
+ weekRes.json(),
4621
+ monthRes.json()
4622
+ ]);
4623
+ const avgWeeklyFromMonth = monthData.downloads / 4;
4624
+ const weekOverWeek = avgWeeklyFromMonth > 0 ? Math.round((weekData.downloads - avgWeeklyFromMonth) / avgWeeklyFromMonth * 100) : 0;
4625
+ return {
4626
+ package: packageName,
4627
+ downloads: {
4628
+ lastDay: dayData.downloads,
4629
+ lastWeek: weekData.downloads,
4630
+ lastMonth: monthData.downloads
4631
+ },
4632
+ weekOverWeek
4633
+ };
4634
+ } catch {
4635
+ return null;
4636
+ }
4637
+ }
3594
4638
 
3595
4639
  // src/lib/db.ts
3596
- import { createRequire } from "module";
3597
- var require2 = createRequire(import.meta.url);
3598
- var pg = require2("pg");
4640
+ import { createRequire as createRequire2 } from "module";
4641
+ var require3 = createRequire2(import.meta.url);
4642
+ var pg = require3("pg");
3599
4643
  var { Pool } = pg;
3600
4644
  var DATABASE_URL = process.env.SQUADS_DATABASE_URL || "postgresql://squads:squads_local_dev@localhost:5433/squads";
3601
4645
  var pool = null;
@@ -3747,9 +4791,11 @@ function getLastActivityDate(squadName) {
3747
4791
  return `${Math.floor(ageDays / 7)}w`;
3748
4792
  }
3749
4793
  async function dashboardCommand(options = {}) {
4794
+ await track(Events.CLI_DASHBOARD, { verbose: options.verbose, ceo: options.ceo, fast: options.fast });
3750
4795
  const squadsDir = findSquadsDir();
3751
4796
  if (!squadsDir) {
3752
4797
  writeLine(`${colors.red}No .agents/squads directory found${RESET}`);
4798
+ writeLine(`${colors.dim}Run \`squads init\` to create one.${RESET}`);
3753
4799
  return;
3754
4800
  }
3755
4801
  if (options.ceo) {
@@ -3760,7 +4806,8 @@ async function dashboardCommand(options = {}) {
3760
4806
  const squadNames = listSquads(squadsDir);
3761
4807
  const skipGitHub = options.fast !== false;
3762
4808
  const timeout = (promise, ms, fallback) => Promise.race([promise, new Promise((resolve) => setTimeout(() => resolve(fallback), ms))]);
3763
- const [gitStats, ghStats, costs, bridgeStats, activity, dbAvailable, history, insights] = await Promise.all([
4809
+ cleanupStaleSessions();
4810
+ const [gitStats, ghStats, costs, bridgeStats, activity, dbAvailable, history, insights, sessionSummary, npmStats] = await Promise.all([
3764
4811
  // Git stats (local, ~1s)
3765
4812
  Promise.resolve(baseDir ? getMultiRepoGitStats(baseDir, 30) : null),
3766
4813
  // GitHub stats (network, ~20-30s) - skip by default for fast mode
@@ -3776,9 +4823,13 @@ async function dashboardCommand(options = {}) {
3776
4823
  // Dashboard history (1.5s timeout)
3777
4824
  timeout(getDashboardHistory(14).catch(() => []), 1500, []),
3778
4825
  // Insights (2s timeout)
3779
- timeout(fetchInsights("week").catch(() => null), 2e3, null)
4826
+ timeout(fetchInsights("week").catch(() => null), 2e3, null),
4827
+ // Session summary (parallel lsof, ~1s)
4828
+ getLiveSessionSummaryAsync(),
4829
+ // NPM download stats (network, 2s timeout)
4830
+ timeout(fetchNpmStats("squads-cli"), 2e3, null)
3780
4831
  ]);
3781
- const cache = { gitStats, ghStats, costs, bridgeStats, activity, dbAvailable, history, insights };
4832
+ const cache = { gitStats, ghStats, costs, bridgeStats, activity, dbAvailable, history, insights, sessionSummary, npmStats };
3782
4833
  const squadData = [];
3783
4834
  for (const name of squadNames) {
3784
4835
  const squad = loadSquad(name);
@@ -3840,8 +4891,6 @@ async function dashboardCommand(options = {}) {
3840
4891
  const totalPRs = ghStats ? ghStats.prsMerged : 0;
3841
4892
  const totalIssuesClosed = ghStats ? ghStats.issuesClosed : 0;
3842
4893
  const totalIssuesOpen = ghStats ? ghStats.issuesOpen : 0;
3843
- cleanupStaleSessions();
3844
- const sessionSummary = getLiveSessionSummary();
3845
4894
  writeLine();
3846
4895
  writeLine(` ${gradient("squads")} ${colors.dim}dashboard${RESET}`);
3847
4896
  const updateInfo = checkForUpdate();
@@ -3873,8 +4922,8 @@ async function dashboardCommand(options = {}) {
3873
4922
  const overallProgress = squadData.length > 0 ? Math.round(squadData.reduce((sum, s) => sum + s.goalProgress, 0) / squadData.length) : 0;
3874
4923
  writeLine(` ${progressBar(overallProgress, 32)} ${colors.dim}${overallProgress}% goal progress${RESET}`);
3875
4924
  writeLine();
3876
- const w = { name: 13, commits: 7, prs: 4, issues: 6, goals: 6, bar: 10 };
3877
- const tableWidth = w.name + w.commits + w.prs + w.issues + w.goals + w.bar + 12;
4925
+ const w = { name: 13, commits: 9, prs: 5, issues: 8, goals: 7, bar: 10 };
4926
+ const tableWidth = w.name + w.commits + w.prs + w.issues + w.goals + w.bar + 6;
3878
4927
  writeLine(` ${colors.purple}${box.topLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.topRight}${RESET}`);
3879
4928
  writeLine(` ${colors.purple}${box.vertical}${RESET} ${bold}${padEnd("SQUAD", w.name)}${RESET}${bold}${padEnd("COMMITS", w.commits)}${RESET}${bold}${padEnd("PRs", w.prs)}${RESET}${bold}${padEnd("ISSUES", w.issues)}${RESET}${bold}${padEnd("GOALS", w.goals)}${RESET}${bold}PROGRESS${RESET} ${colors.purple}${box.vertical}${RESET}`);
3880
4929
  writeLine(` ${colors.purple}${box.teeRight}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.teeLeft}${RESET}`);
@@ -3889,18 +4938,23 @@ async function dashboardCommand(options = {}) {
3889
4938
  const prs = gh?.prsMerged || 0;
3890
4939
  const issuesClosed = gh?.issuesClosed || 0;
3891
4940
  const issuesOpen = gh?.issuesOpen || 0;
3892
- const activeCount = squad.goals.filter((g) => !g.completed).length;
4941
+ const completedCount = squad.goals.filter((g) => g.completed).length;
3893
4942
  const totalCount = squad.goals.length;
3894
4943
  const commitColor = commits > 10 ? colors.green : commits > 0 ? colors.cyan : colors.dim;
3895
4944
  const prColor = prs > 0 ? colors.green : colors.dim;
3896
4945
  const issueColor = issuesClosed > 0 ? colors.green : colors.dim;
3897
- writeLine(` ${colors.purple}${box.vertical}${RESET} ${colors.cyan}${padEnd(squad.name, w.name)}${RESET}${commitColor}${padEnd(String(commits), w.commits)}${RESET}${prColor}${padEnd(String(prs), w.prs)}${RESET}${issueColor}${padEnd(`${issuesClosed}/${issuesOpen}`, w.issues)}${RESET}${padEnd(`${activeCount}/${totalCount}`, w.goals)}${progressBar(squad.goalProgress, 8)} ${colors.purple}${box.vertical}${RESET}`);
4946
+ writeLine(` ${colors.purple}${box.vertical}${RESET} ${colors.cyan}${padEnd(squad.name, w.name)}${RESET}${commitColor}${padEnd(String(commits), w.commits)}${RESET}${prColor}${padEnd(String(prs), w.prs)}${RESET}${issueColor}${padEnd(`${issuesClosed}/${issuesOpen}`, w.issues)}${RESET}${padEnd(`${completedCount}/${totalCount}`, w.goals)}${progressBar(squad.goalProgress, 8)} ${colors.purple}${box.vertical}${RESET}`);
3898
4947
  }
3899
4948
  writeLine(` ${colors.purple}${box.bottomLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.bottomRight}${RESET}`);
3900
4949
  writeLine();
4950
+ const goalCount = {
4951
+ active: squadData.reduce((sum, s) => sum + s.goals.filter((g) => !g.completed).length, 0),
4952
+ completed: squadData.reduce((sum, s) => sum + s.goals.filter((g) => g.completed).length, 0)
4953
+ };
3901
4954
  renderGitPerformanceCached(cache);
3902
- renderTokenEconomicsCached(cache);
4955
+ renderTokenEconomicsCached(cache, goalCount);
3903
4956
  renderInfrastructureCached(cache);
4957
+ renderAcquisitionCached(cache);
3904
4958
  renderHistoricalTrendsCached(cache);
3905
4959
  renderInsightsCached(cache);
3906
4960
  const allActiveGoals = squadData.flatMap(
@@ -3931,14 +4985,12 @@ async function dashboardCommand(options = {}) {
3931
4985
  await closeDatabase();
3932
4986
  }
3933
4987
  function findAgentsSquadsDir() {
3934
- const candidates = [
3935
- join10(process.cwd(), ".."),
3936
- join10(homedir4(), "agents-squads")
3937
- ];
3938
- for (const dir of candidates) {
3939
- if (existsSync10(join10(dir, "hq"))) {
3940
- return dir;
3941
- }
4988
+ const parentDir = join10(process.cwd(), "..");
4989
+ if (existsSync10(join10(parentDir, "hq"))) {
4990
+ return parentDir;
4991
+ }
4992
+ if (existsSync10(join10(process.cwd(), ".git"))) {
4993
+ return process.cwd();
3942
4994
  }
3943
4995
  return null;
3944
4996
  }
@@ -3987,45 +5039,91 @@ function renderGitPerformanceCached(cache) {
3987
5039
  writeLine();
3988
5040
  }
3989
5041
  }
3990
- function renderTokenEconomicsCached(cache) {
5042
+ function renderTokenEconomicsCached(cache, goalCount) {
3991
5043
  const costs = cache.costs;
3992
- if (!costs) {
3993
- writeLine(` ${bold}Token Economics${RESET} ${colors.dim}(no data)${RESET}`);
3994
- writeLine(` ${colors.dim}Set LANGFUSE_PUBLIC_KEY & LANGFUSE_SECRET_KEY for cost tracking${RESET}`);
5044
+ const stats = cache.bridgeStats;
5045
+ const hasInfra = hasLocalInfraConfig();
5046
+ const hasData = costs || stats;
5047
+ writeLine(` ${bold}Token Economics${RESET}`);
5048
+ writeLine();
5049
+ const planType = getPlanType();
5050
+ const tier = parseInt(process.env.ANTHROPIC_TIER || "0", 10);
5051
+ if (planType === "unknown") {
5052
+ writeLine(` ${colors.dim}\u25CB${RESET} ${bold}Plan${RESET} ${colors.yellow}not configured${RESET}`);
5053
+ writeLine();
5054
+ writeLine(` ${colors.dim}Set your Claude plan:${RESET}`);
5055
+ writeLine(` ${colors.dim}$${RESET} export SQUADS_PLAN_TYPE=max ${colors.dim}# $200/mo flat${RESET}`);
5056
+ writeLine(` ${colors.dim}$${RESET} export SQUADS_PLAN_TYPE=usage ${colors.dim}# pay-per-token${RESET}`);
5057
+ writeLine();
5058
+ } else {
5059
+ const maxPlan = planType === "max";
5060
+ const planIcon = maxPlan ? `${colors.purple}\u25C6${RESET}` : `${colors.dim}\u25CB${RESET}`;
5061
+ const planLabel = maxPlan ? "Claude Max" : "Claude Pro";
5062
+ const planCost = maxPlan ? "$200/mo flat" : "pay-per-token";
5063
+ const tierDisplay = tier > 0 ? ` ${colors.dim}Tier ${tier}${RESET}` : "";
5064
+ writeLine(` ${planIcon} ${bold}${planLabel}${RESET} ${colors.dim}${planCost}${RESET}${tierDisplay}`);
5065
+ writeLine();
5066
+ }
5067
+ if (!hasInfra || !hasData) {
5068
+ writeLine(` ${colors.dim}\u25CB${RESET} Track costs, tokens, and API usage`);
5069
+ writeLine(` ${colors.dim}\u25CB${RESET} Monitor rate limits and budgets`);
5070
+ writeLine();
5071
+ writeLine(` ${colors.dim}Setup:${RESET} github.com/agents-squads/squads-cli#analytics`);
3995
5072
  writeLine();
3996
5073
  return;
3997
5074
  }
3998
- writeLine(` ${bold}Token Economics${RESET} ${colors.dim}(last 100 calls)${RESET}`);
3999
- writeLine();
4000
- const barWidth = 32;
4001
- const costBar = formatCostBar(costs.usedPercent, barWidth);
4002
- writeLine(` ${colors.dim}Budget $${costs.dailyBudget}${RESET} [${costBar}] ${costs.usedPercent.toFixed(1)}%`);
4003
- writeLine(` ${colors.green}$${costs.totalCost.toFixed(2)}${RESET} used ${colors.dim}\u2502${RESET} ${colors.cyan}$${costs.idleBudget.toFixed(2)}${RESET} idle`);
4004
- writeLine();
4005
- const tier = parseInt(process.env.ANTHROPIC_TIER || "4", 10);
4006
- writeLine(` ${colors.dim}Rate Limits (Tier ${tier})${RESET}`);
5075
+ const todayTokens = stats ? stats.today.inputTokens + stats.today.outputTokens : 0;
5076
+ const todayCalls = stats?.today.generations || costs?.totalCalls || 0;
5077
+ const todayCost = stats?.today.costUsd || costs?.totalCost || 0;
5078
+ writeLine(` ${colors.dim}Today${RESET}`);
5079
+ writeLine(` ${colors.cyan}${formatK(todayTokens)}${RESET} tokens ${colors.dim}\u2502${RESET} ${colors.cyan}${todayCalls}${RESET} calls ${colors.dim}\u2502${RESET} ${colors.green}$${todayCost.toFixed(2)}${RESET}`);
5080
+ if (stats?.week && stats.week.generations > 0) {
5081
+ const weekTokens = (stats.week.inputTokens || 0) + (stats.week.outputTokens || 0);
5082
+ writeLine(` ${colors.dim}Week${RESET} ${colors.purple}${formatK(weekTokens)}${RESET} tokens ${colors.dim}\u2502${RESET} ${colors.purple}${stats.week.generations}${RESET} calls ${colors.dim}\u2502${RESET} ${colors.purple}$${stats.week.costUsd.toFixed(2)}${RESET}`);
5083
+ }
4007
5084
  writeLine();
4008
- const now = /* @__PURE__ */ new Date();
4009
- const hoursElapsed = Math.max(now.getHours() + now.getMinutes() / 60, 1);
4010
- const hourlyRate = costs.totalCost / hoursElapsed;
4011
- const dailyProjection = hourlyRate * 24;
4012
- const monthlyProjection = dailyProjection * 30;
4013
- writeLine(` ${colors.dim}Projections${RESET}`);
4014
- const projColor = dailyProjection > costs.dailyBudget ? colors.red : colors.green;
4015
- writeLine(` ${colors.dim}Daily:${RESET} ${projColor}~$${dailyProjection.toFixed(2)}${RESET}${colors.dim}/${costs.dailyBudget}${RESET} ${colors.dim}Monthly:${RESET} ${colors.cyan}~$${monthlyProjection.toFixed(0)}${RESET}`);
4016
- if (dailyProjection > costs.dailyBudget * 0.8) {
4017
- writeLine(` ${colors.yellow}\u26A0${RESET} ${colors.yellow}Projected to exceed daily budget${RESET}`);
5085
+ if (goalCount && goalCount.completed > 0 && todayTokens > 0) {
5086
+ const tokensPerGoal = Math.round(todayTokens / goalCount.completed);
5087
+ writeLine(` ${colors.dim}Efficiency${RESET}`);
5088
+ writeLine(` ${colors.cyan}${formatK(tokensPerGoal)}${RESET} tokens/goal ${colors.dim}\u2502${RESET} ${colors.green}${goalCount.completed}${RESET} goals done`);
5089
+ writeLine();
4018
5090
  }
4019
- if (costs.usedPercent > 80) {
4020
- writeLine(` ${colors.red}\u26A0${RESET} ${colors.red}${costs.usedPercent.toFixed(0)}% of daily budget used${RESET}`);
5091
+ writeLine(` ${colors.dim}Rate Limits${RESET} ${colors.dim}(Tier ${tier})${RESET}`);
5092
+ const tier4Limits = {
5093
+ rpm: 4e3,
5094
+ inputTpm: 4e5,
5095
+ outputTpm: 8e4
5096
+ };
5097
+ const now = /* @__PURE__ */ new Date();
5098
+ const minutesElapsed = Math.max(now.getHours() * 60 + now.getMinutes(), 1);
5099
+ const callsPerMinute = todayCalls / minutesElapsed;
5100
+ const tokensPerMinute = todayTokens / minutesElapsed;
5101
+ const rpmPct = callsPerMinute / tier4Limits.rpm * 100;
5102
+ const tpmPct = tokensPerMinute / (tier4Limits.inputTpm + tier4Limits.outputTpm) * 100;
5103
+ const rpmBar = progressBar(Math.min(rpmPct, 100), 10);
5104
+ const tpmBar = progressBar(Math.min(tpmPct, 100), 10);
5105
+ const rpmColor = rpmPct > 75 ? colors.red : rpmPct > 50 ? colors.yellow : colors.green;
5106
+ const tpmColor = tpmPct > 75 ? colors.red : tpmPct > 50 ? colors.yellow : colors.green;
5107
+ writeLine(` RPM ${rpmBar} ${rpmColor}${callsPerMinute.toFixed(1)}${RESET}${colors.dim}/${tier4Limits.rpm}${RESET}`);
5108
+ writeLine(` TPM ${tpmBar} ${tpmColor}${formatK(Math.round(tokensPerMinute))}${RESET}${colors.dim}/${formatK(tier4Limits.inputTpm + tier4Limits.outputTpm)}${RESET}`);
5109
+ const rpmAvailable = Math.max(0, tier4Limits.rpm - callsPerMinute);
5110
+ const tpmAvailable = Math.max(0, tier4Limits.inputTpm + tier4Limits.outputTpm - tokensPerMinute);
5111
+ if (rpmAvailable > 100 && tpmAvailable > 1e4) {
5112
+ writeLine(` ${colors.green}\u25CF${RESET} ${colors.dim}Capacity for autonomous triggers${RESET}`);
5113
+ } else if (rpmPct > 75 || tpmPct > 75) {
5114
+ writeLine(` ${colors.yellow}\u26A0${RESET} ${colors.yellow}Rate limits constrained${RESET}`);
4021
5115
  }
4022
5116
  writeLine();
4023
5117
  }
4024
5118
  function renderInfrastructureCached(cache) {
4025
5119
  const stats = cache.bridgeStats;
4026
- if (!stats) {
4027
- writeLine(` ${bold}Infrastructure${RESET} ${colors.dim}(bridge offline)${RESET}`);
4028
- writeLine(` ${colors.dim}Start with: cd docker && docker-compose up -d${RESET}`);
5120
+ const hasInfra = hasLocalInfraConfig();
5121
+ if (!hasInfra || !stats) {
5122
+ writeLine(` ${bold}Infrastructure${RESET} ${colors.dim}(not connected)${RESET}`);
5123
+ writeLine();
5124
+ writeLine(` ${colors.dim}\u25CB${RESET} postgres ${colors.dim}\u25CB${RESET} redis ${colors.dim}\u25CB${RESET} otel`);
5125
+ writeLine();
5126
+ writeLine(` ${colors.dim}Setup:${RESET} github.com/agents-squads/squads-cli#infrastructure`);
4029
5127
  writeLine();
4030
5128
  return;
4031
5129
  }
@@ -4038,8 +5136,10 @@ function renderInfrastructureCached(cache) {
4038
5136
  writeLine(` ${pgStatus} postgres ${redisStatus} redis ${otelStatus} otel`);
4039
5137
  writeLine();
4040
5138
  if (stats.today.generations > 0 || stats.today.costUsd > 0) {
4041
- const costColor = stats.budget.usedPct > 80 ? colors.red : stats.budget.usedPct > 50 ? colors.yellow : colors.green;
4042
- writeLine(` ${colors.dim}Today:${RESET} ${colors.cyan}${stats.today.generations}${RESET}${colors.dim} calls${RESET} ${costColor}$${stats.today.costUsd.toFixed(2)}${RESET}${colors.dim}/$${stats.budget.daily}${RESET} ${colors.dim}${formatK(stats.today.inputTokens)}+${formatK(stats.today.outputTokens)} tokens${RESET}`);
5139
+ const maxPlan = isMaxPlan();
5140
+ const costColor = maxPlan ? colors.green : stats.budget.usedPct > 80 ? colors.red : stats.budget.usedPct > 50 ? colors.yellow : colors.green;
5141
+ const costDisplay = maxPlan ? `${costColor}$${stats.today.costUsd.toFixed(2)}${RESET}` : `${costColor}$${stats.today.costUsd.toFixed(2)}${RESET}${colors.dim}/$${stats.budget.daily}${RESET}`;
5142
+ writeLine(` ${colors.dim}Today:${RESET} ${colors.cyan}${stats.today.generations}${RESET}${colors.dim} calls${RESET} ${costDisplay} ${colors.dim}${formatK(stats.today.inputTokens)}+${formatK(stats.today.outputTokens)} tokens${RESET}`);
4043
5143
  if (stats.byModel && stats.byModel.length > 0) {
4044
5144
  const modelLine = stats.byModel.map((m) => {
4045
5145
  const shortName = m.model.includes("opus") ? "opus" : m.model.includes("sonnet") ? "sonnet" : m.model.includes("haiku") ? "haiku" : m.model.slice(0, 10);
@@ -4057,7 +5157,24 @@ function renderInfrastructureCached(cache) {
4057
5157
  }
4058
5158
  writeLine();
4059
5159
  }
4060
- async function saveSnapshotCached(squadData, cache, baseDir) {
5160
+ function renderAcquisitionCached(cache) {
5161
+ const npmPackage = process.env.SQUADS_NPM_PACKAGE;
5162
+ if (!npmPackage) {
5163
+ return;
5164
+ }
5165
+ const npm = cache.npmStats;
5166
+ if (!npm) {
5167
+ return;
5168
+ }
5169
+ writeLine(` ${bold}Acquisition${RESET} ${colors.dim}(npm)${RESET}`);
5170
+ writeLine();
5171
+ const trendIcon = npm.weekOverWeek >= 0 ? `${colors.green}\u2191${RESET}` : `${colors.red}\u2193${RESET}`;
5172
+ const trendColor = npm.weekOverWeek >= 0 ? colors.green : colors.red;
5173
+ writeLine(` ${colors.cyan}${npm.downloads.lastWeek}${RESET} installs/week ${trendIcon} ${trendColor}${Math.abs(npm.weekOverWeek)}%${RESET} ${colors.dim}wow${RESET}`);
5174
+ writeLine(` ${colors.dim}Today${RESET} ${npm.downloads.lastDay} ${colors.dim}\u2502${RESET} ${colors.dim}Month${RESET} ${npm.downloads.lastMonth}`);
5175
+ writeLine();
5176
+ }
5177
+ async function saveSnapshotCached(squadData, cache, _baseDir) {
4061
5178
  if (!cache.dbAvailable) return;
4062
5179
  const { gitStats, ghStats, costs } = cache;
4063
5180
  const squadsData = squadData.map((s) => ({
@@ -4261,7 +5378,10 @@ function renderInsightsCached(cache) {
4261
5378
  }
4262
5379
 
4263
5380
  // src/commands/issues.ts
4264
- import { execSync as execSync5 } from "child_process";
5381
+ import { execSync as execSync6 } from "child_process";
5382
+ function getLabelName(label) {
5383
+ return typeof label === "string" ? label : label.name;
5384
+ }
4265
5385
  var DEFAULT_ORG = "agents-squads";
4266
5386
  var DEFAULT_REPOS = ["hq", "agents-squads-web", "squads-cli"];
4267
5387
  async function issuesCommand(options = {}) {
@@ -4271,7 +5391,7 @@ async function issuesCommand(options = {}) {
4271
5391
  writeLine(` ${gradient("squads")} ${colors.dim}issues${RESET}`);
4272
5392
  writeLine();
4273
5393
  try {
4274
- execSync5("gh --version", { stdio: "pipe" });
5394
+ execSync6("gh --version", { stdio: "pipe" });
4275
5395
  } catch {
4276
5396
  writeLine(` ${colors.red}GitHub CLI (gh) not found${RESET}`);
4277
5397
  writeLine(` ${colors.dim}Install: brew install gh${RESET}`);
@@ -4282,14 +5402,14 @@ async function issuesCommand(options = {}) {
4282
5402
  let totalOpen = 0;
4283
5403
  for (const repo of repos) {
4284
5404
  try {
4285
- const result = execSync5(
5405
+ const result = execSync6(
4286
5406
  `gh issue list -R ${org}/${repo} --state open --json number,title,state,labels,createdAt --limit 50`,
4287
5407
  { stdio: "pipe", encoding: "utf-8" }
4288
5408
  );
4289
5409
  const issues = JSON.parse(result);
4290
5410
  repoData.push({ repo, issues });
4291
5411
  totalOpen += issues.length;
4292
- } catch (e) {
5412
+ } catch {
4293
5413
  repoData.push({ repo, issues: [], error: "not found or no access" });
4294
5414
  }
4295
5415
  }
@@ -4321,7 +5441,7 @@ async function issuesCommand(options = {}) {
4321
5441
  writeLine(` ${bold}Recent${RESET}`);
4322
5442
  writeLine();
4323
5443
  for (const issue of allIssues) {
4324
- const labelStr = issue.labels.length > 0 ? `${colors.dim}[${issue.labels.map((l) => l.name || l).join(", ")}]${RESET}` : "";
5444
+ const labelStr = issue.labels.length > 0 ? `${colors.dim}[${issue.labels.map(getLabelName).join(", ")}]${RESET}` : "";
4325
5445
  writeLine(` ${icons.empty} ${colors.dim}#${issue.number}${RESET} ${truncate(issue.title, 50)} ${labelStr}`);
4326
5446
  writeLine(` ${colors.dim}\u2514 ${issue.repo}${RESET}`);
4327
5447
  }
@@ -4333,7 +5453,7 @@ async function issuesCommand(options = {}) {
4333
5453
  }
4334
5454
 
4335
5455
  // src/commands/solve-issues.ts
4336
- import { execSync as execSync6, spawn as spawn3 } from "child_process";
5456
+ import { execSync as execSync7, spawn as spawn4 } from "child_process";
4337
5457
  import ora3 from "ora";
4338
5458
  var DEFAULT_ORG2 = "agents-squads";
4339
5459
  var DEFAULT_REPOS2 = ["hq", "agents-squads-web", "squads-cli", "agents-squads"];
@@ -4343,7 +5463,7 @@ async function solveIssuesCommand(options = {}) {
4343
5463
  writeLine(` ${gradient("squads")} ${colors.dim}solve-issues${RESET}`);
4344
5464
  writeLine();
4345
5465
  try {
4346
- execSync6("gh --version", { stdio: "pipe" });
5466
+ execSync7("gh --version", { stdio: "pipe" });
4347
5467
  } catch {
4348
5468
  writeLine(` ${colors.red}GitHub CLI (gh) not found${RESET}`);
4349
5469
  writeLine(` ${colors.dim}Install: brew install gh${RESET}`);
@@ -4353,7 +5473,7 @@ async function solveIssuesCommand(options = {}) {
4353
5473
  if (options.issue) {
4354
5474
  const repo = options.repo || "hq";
4355
5475
  try {
4356
- const result = execSync6(
5476
+ const result = execSync7(
4357
5477
  `gh issue view ${options.issue} -R ${DEFAULT_ORG2}/${repo} --json number,title,labels,body`,
4358
5478
  { stdio: "pipe", encoding: "utf-8" }
4359
5479
  );
@@ -4366,7 +5486,7 @@ async function solveIssuesCommand(options = {}) {
4366
5486
  } else {
4367
5487
  for (const repo of repos) {
4368
5488
  try {
4369
- const result = execSync6(
5489
+ const result = execSync7(
4370
5490
  `gh issue list -R ${DEFAULT_ORG2}/${repo} --label "ready-to-fix" --state open --json number,title,labels --limit 20`,
4371
5491
  { stdio: "pipe", encoding: "utf-8" }
4372
5492
  );
@@ -4420,7 +5540,7 @@ function showSolveInstructions(issues) {
4420
5540
  async function solveWithClaude(issues) {
4421
5541
  const spinner = ora3("Starting issue solver...").start();
4422
5542
  try {
4423
- execSync6("which claude", { stdio: "pipe" });
5543
+ execSync7("which claude", { stdio: "pipe" });
4424
5544
  } catch {
4425
5545
  spinner.fail("Claude CLI not found");
4426
5546
  writeLine(` ${colors.dim}Install: npm install -g @anthropic-ai/claude-code${RESET}`);
@@ -4479,7 +5599,7 @@ Return the PR URL when done.`;
4479
5599
  }
4480
5600
  function executeClaudePrompt(prompt2) {
4481
5601
  return new Promise((resolve, reject) => {
4482
- const claude = spawn3("claude", ["--print", prompt2], {
5602
+ const claude = spawn4("claude", ["--print", prompt2], {
4483
5603
  stdio: ["pipe", "pipe", "pipe"]
4484
5604
  });
4485
5605
  let output = "";
@@ -4506,7 +5626,7 @@ function executeClaudePrompt(prompt2) {
4506
5626
  }
4507
5627
 
4508
5628
  // src/commands/open-issues.ts
4509
- import { execSync as execSync7, spawn as spawn4 } from "child_process";
5629
+ import { execSync as execSync8, spawn as spawn5 } from "child_process";
4510
5630
  import { readdirSync as readdirSync5 } from "fs";
4511
5631
  import { join as join11 } from "path";
4512
5632
  import ora4 from "ora";
@@ -4523,6 +5643,7 @@ async function openIssuesCommand(options = {}) {
4523
5643
  const squadsDir = findSquadsDir();
4524
5644
  if (!squadsDir) {
4525
5645
  writeLine(` ${colors.red}No .agents/squads directory found${RESET}`);
5646
+ writeLine(` ${colors.dim}Run \`squads init\` to create one.${RESET}`);
4526
5647
  return;
4527
5648
  }
4528
5649
  const evalAgents = findEvalAgents(squadsDir, options.squad);
@@ -4602,7 +5723,7 @@ function showRunInstructions(agents) {
4602
5723
  async function runEvaluators(agents) {
4603
5724
  const spinner = ora4("Starting evaluators...").start();
4604
5725
  try {
4605
- execSync7("which claude", { stdio: "pipe" });
5726
+ execSync8("which claude", { stdio: "pipe" });
4606
5727
  } catch {
4607
5728
  spinner.fail("Claude CLI not found");
4608
5729
  writeLine(` ${colors.dim}Install: npm install -g @anthropic-ai/claude-code${RESET}`);
@@ -4653,7 +5774,7 @@ Do NOT get stuck re-reading files. Evaluate, report findings, create issues, don
4653
5774
  }
4654
5775
  function executeClaudePrompt2(prompt2) {
4655
5776
  return new Promise((resolve, reject) => {
4656
- const claude = spawn4("claude", ["--print", prompt2], {
5777
+ const claude = spawn5("claude", ["--print", prompt2], {
4657
5778
  stdio: ["pipe", "pipe", "pipe"]
4658
5779
  });
4659
5780
  let output = "";
@@ -4686,9 +5807,10 @@ import open from "open";
4686
5807
 
4687
5808
  // src/lib/auth.ts
4688
5809
  import { createClient } from "@supabase/supabase-js";
4689
- import { existsSync as existsSync12, readFileSync as readFileSync9, writeFileSync as writeFileSync8, mkdirSync as mkdirSync7 } from "fs";
5810
+ import { existsSync as existsSync11, readFileSync as readFileSync8, writeFileSync as writeFileSync8, mkdirSync as mkdirSync7 } from "fs";
4690
5811
  import { join as join12 } from "path";
4691
- import { homedir as homedir5 } from "os";
5812
+ import { homedir as homedir4 } from "os";
5813
+ import "open";
4692
5814
  import http from "http";
4693
5815
  var PERSONAL_DOMAINS = [
4694
5816
  "gmail.com",
@@ -4716,7 +5838,7 @@ var PERSONAL_DOMAINS = [
4716
5838
  "tutanota.com",
4717
5839
  "hey.com"
4718
5840
  ];
4719
- var AUTH_DIR = join12(homedir5(), ".squads-cli");
5841
+ var AUTH_DIR = join12(homedir4(), ".squads-cli");
4720
5842
  var AUTH_PATH = join12(AUTH_DIR, "auth.json");
4721
5843
  function isPersonalEmail(email) {
4722
5844
  const domain = email.split("@")[1]?.toLowerCase();
@@ -4726,21 +5848,21 @@ function getEmailDomain(email) {
4726
5848
  return email.split("@")[1]?.toLowerCase() || "";
4727
5849
  }
4728
5850
  function saveSession(session2) {
4729
- if (!existsSync12(AUTH_DIR)) {
5851
+ if (!existsSync11(AUTH_DIR)) {
4730
5852
  mkdirSync7(AUTH_DIR, { recursive: true });
4731
5853
  }
4732
5854
  writeFileSync8(AUTH_PATH, JSON.stringify(session2, null, 2));
4733
5855
  }
4734
5856
  function loadSession() {
4735
- if (!existsSync12(AUTH_PATH)) return null;
5857
+ if (!existsSync11(AUTH_PATH)) return null;
4736
5858
  try {
4737
- return JSON.parse(readFileSync9(AUTH_PATH, "utf-8"));
5859
+ return JSON.parse(readFileSync8(AUTH_PATH, "utf-8"));
4738
5860
  } catch {
4739
5861
  return null;
4740
5862
  }
4741
5863
  }
4742
5864
  function clearSession() {
4743
- if (existsSync12(AUTH_PATH)) {
5865
+ if (existsSync11(AUTH_PATH)) {
4744
5866
  writeFileSync8(AUTH_PATH, "");
4745
5867
  }
4746
5868
  }
@@ -4897,9 +6019,9 @@ Since: ${new Date(session2.createdAt).toLocaleDateString()}
4897
6019
  }
4898
6020
 
4899
6021
  // src/commands/update.ts
4900
- import { createInterface as createInterface2 } from "readline";
4901
- async function confirm2(message) {
4902
- const rl = createInterface2({
6022
+ import { createInterface as createInterface3 } from "readline";
6023
+ async function confirm3(message) {
6024
+ const rl = createInterface3({
4903
6025
  input: process.stdin,
4904
6026
  output: process.stdout
4905
6027
  });
@@ -4939,7 +6061,7 @@ async function updateCommand(options = {}) {
4939
6061
  writeLine();
4940
6062
  writeLine(` ${colors.cyan}\u2B06${RESET} Update available: ${colors.dim}${info.currentVersion}${RESET} \u2192 ${colors.green}${info.latestVersion}${RESET}`);
4941
6063
  writeLine();
4942
- const shouldUpdate = options.yes || await confirm2("Update now?");
6064
+ const shouldUpdate = options.yes || await confirm3("Update now?");
4943
6065
  if (!shouldUpdate) {
4944
6066
  writeLine();
4945
6067
  writeLine(` ${colors.dim}Update skipped${RESET}`);
@@ -4963,15 +6085,15 @@ async function updateCommand(options = {}) {
4963
6085
  }
4964
6086
 
4965
6087
  // src/commands/progress.ts
4966
- import { execSync as execSync8 } from "child_process";
4967
- import { existsSync as existsSync13, readFileSync as readFileSync10, writeFileSync as writeFileSync9, mkdirSync as mkdirSync8 } from "fs";
6088
+ import { execSync as execSync9 } from "child_process";
6089
+ import { existsSync as existsSync12, readFileSync as readFileSync9, writeFileSync as writeFileSync9, mkdirSync as mkdirSync8 } from "fs";
4968
6090
  import { join as join13 } from "path";
4969
6091
  function getTasksFilePath() {
4970
6092
  const memoryDir = findMemoryDir();
4971
6093
  if (!memoryDir) {
4972
6094
  const cwd = process.cwd();
4973
6095
  const agentsDir = join13(cwd, ".agents");
4974
- if (!existsSync13(agentsDir)) {
6096
+ if (!existsSync12(agentsDir)) {
4975
6097
  mkdirSync8(agentsDir, { recursive: true });
4976
6098
  }
4977
6099
  return join13(agentsDir, "tasks.json");
@@ -4980,9 +6102,9 @@ function getTasksFilePath() {
4980
6102
  }
4981
6103
  function loadTasks() {
4982
6104
  const tasksPath = getTasksFilePath();
4983
- if (existsSync13(tasksPath)) {
6105
+ if (existsSync12(tasksPath)) {
4984
6106
  try {
4985
- return JSON.parse(readFileSync10(tasksPath, "utf-8"));
6107
+ return JSON.parse(readFileSync9(tasksPath, "utf-8"));
4986
6108
  } catch {
4987
6109
  return { tasks: [], lastUpdated: (/* @__PURE__ */ new Date()).toISOString() };
4988
6110
  }
@@ -5008,7 +6130,7 @@ function getRecentActivity() {
5008
6130
  marketing: ["marketing", "content", "social"]
5009
6131
  };
5010
6132
  try {
5011
- const logOutput = execSync8(
6133
+ const logOutput = execSync9(
5012
6134
  'git log --since="24 hours ago" --format="%h|%aI|%s" 2>/dev/null',
5013
6135
  { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
5014
6136
  ).trim();
@@ -5142,7 +6264,7 @@ function getElapsedTime(startTime) {
5142
6264
  }
5143
6265
 
5144
6266
  // src/commands/results.ts
5145
- import { execSync as execSync9 } from "child_process";
6267
+ import { execSync as execSync10 } from "child_process";
5146
6268
  function getGitStats(days = 7) {
5147
6269
  const stats = /* @__PURE__ */ new Map();
5148
6270
  const squadKeywords = {
@@ -5157,7 +6279,7 @@ function getGitStats(days = 7) {
5157
6279
  marketing: ["marketing"]
5158
6280
  };
5159
6281
  try {
5160
- const logOutput = execSync9(
6282
+ const logOutput = execSync10(
5161
6283
  `git log --since="${days} days ago" --format="%s" --name-only 2>/dev/null`,
5162
6284
  { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
5163
6285
  ).trim();
@@ -5191,12 +6313,12 @@ function getGitStats(days = 7) {
5191
6313
  }
5192
6314
  return stats;
5193
6315
  }
5194
- function getGitHubStats2(days = 7) {
6316
+ function getGitHubStats(days = 7) {
5195
6317
  const prsOpened = /* @__PURE__ */ new Map();
5196
6318
  const prsMerged = /* @__PURE__ */ new Map();
5197
6319
  const issuesClosed = /* @__PURE__ */ new Map();
5198
6320
  try {
5199
- const prsOutput = execSync9(
6321
+ const prsOutput = execSync10(
5200
6322
  `gh pr list --state all --json title,createdAt,mergedAt --limit 50 2>/dev/null`,
5201
6323
  { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
5202
6324
  );
@@ -5211,7 +6333,7 @@ function getGitHubStats2(days = 7) {
5211
6333
  prsMerged.set(squad, (prsMerged.get(squad) || 0) + 1);
5212
6334
  }
5213
6335
  }
5214
- const issuesOutput = execSync9(
6336
+ const issuesOutput = execSync10(
5215
6337
  `gh issue list --state closed --json title,closedAt --limit 50 2>/dev/null`,
5216
6338
  { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
5217
6339
  );
@@ -5264,6 +6386,7 @@ async function resultsCommand(options = {}) {
5264
6386
  const squadsDir = findSquadsDir();
5265
6387
  if (!squadsDir) {
5266
6388
  writeLine(`${colors.red}No .agents/squads directory found${RESET}`);
6389
+ writeLine(`${colors.dim}Run \`squads init\` to create one.${RESET}`);
5267
6390
  return;
5268
6391
  }
5269
6392
  const days = parseInt(options.days || "7", 10);
@@ -5272,7 +6395,7 @@ async function resultsCommand(options = {}) {
5272
6395
  writeLine();
5273
6396
  const squadNames = options.squad ? [options.squad] : listSquads(squadsDir);
5274
6397
  const gitStats = getGitStats(days);
5275
- const ghStats = getGitHubStats2(days);
6398
+ const ghStats = getGitHubStats(days);
5276
6399
  const results = [];
5277
6400
  for (const name of squadNames) {
5278
6401
  const squad = loadSquad(name);
@@ -5342,14 +6465,410 @@ async function resultsCommand(options = {}) {
5342
6465
  writeLine();
5343
6466
  }
5344
6467
 
6468
+ // src/commands/history.ts
6469
+ import { existsSync as existsSync13, readFileSync as readFileSync10 } from "fs";
6470
+ import { join as join14 } from "path";
6471
+ var BRIDGE_URL2 = process.env.SQUADS_BRIDGE_URL || "http://localhost:8088";
6472
+ var FETCH_TIMEOUT_MS2 = 3e3;
6473
+ async function fetchWithTimeout2(url, timeoutMs = FETCH_TIMEOUT_MS2) {
6474
+ const controller = new AbortController();
6475
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
6476
+ try {
6477
+ const response = await fetch(url, { signal: controller.signal });
6478
+ clearTimeout(timeoutId);
6479
+ return response;
6480
+ } catch {
6481
+ clearTimeout(timeoutId);
6482
+ throw new Error("Request timed out");
6483
+ }
6484
+ }
6485
+ async function fetchFromBridge2(days, squad) {
6486
+ try {
6487
+ const params = new URLSearchParams({
6488
+ days: String(days),
6489
+ ...squad && { squad }
6490
+ });
6491
+ const response = await fetchWithTimeout2(`${BRIDGE_URL2}/api/executions?${params}`);
6492
+ if (!response.ok) {
6493
+ return [];
6494
+ }
6495
+ const data = await response.json();
6496
+ return (data.executions || []).map((e) => ({
6497
+ id: e.id || "",
6498
+ squad: e.squad || "unknown",
6499
+ agent: e.agent || "unknown",
6500
+ startedAt: new Date(e.started_at || Date.now()),
6501
+ endedAt: e.ended_at ? new Date(e.ended_at) : void 0,
6502
+ durationMs: e.duration_ms,
6503
+ status: e.status || "success",
6504
+ cost: e.cost_usd,
6505
+ tokens: e.total_tokens,
6506
+ error: e.error
6507
+ }));
6508
+ } catch {
6509
+ return [];
6510
+ }
6511
+ }
6512
+ function fetchFromLocal(days, squad) {
6513
+ const executions = [];
6514
+ const historyPaths = [
6515
+ join14(process.cwd(), ".agents/sessions/history.jsonl"),
6516
+ join14(process.env.HOME || "", "agents-squads/hq/.agents/sessions/history.jsonl")
6517
+ ];
6518
+ let historyPath;
6519
+ for (const path3 of historyPaths) {
6520
+ if (existsSync13(path3)) {
6521
+ historyPath = path3;
6522
+ break;
6523
+ }
6524
+ }
6525
+ if (!historyPath) {
6526
+ return [];
6527
+ }
6528
+ try {
6529
+ const content = readFileSync10(historyPath, "utf-8");
6530
+ const lines = content.trim().split("\n").filter(Boolean);
6531
+ const cutoff = Date.now() - days * 24 * 60 * 60 * 1e3;
6532
+ for (const line of lines) {
6533
+ try {
6534
+ const event = JSON.parse(line);
6535
+ const timestamp = new Date(event.timestamp || 0);
6536
+ if (timestamp.getTime() < cutoff) continue;
6537
+ if (squad && event.squad !== squad) continue;
6538
+ if (event.type === "session_end" || event.type === "agent_complete") {
6539
+ executions.push({
6540
+ id: event.sessionId || `local-${Date.now()}`,
6541
+ squad: event.squad || "unknown",
6542
+ agent: event.agent || "unknown",
6543
+ startedAt: timestamp,
6544
+ durationMs: event.duration,
6545
+ status: event.status === "error" ? "error" : "success",
6546
+ cost: event.cost,
6547
+ tokens: event.tokens
6548
+ });
6549
+ }
6550
+ } catch {
6551
+ }
6552
+ }
6553
+ } catch {
6554
+ }
6555
+ return executions;
6556
+ }
6557
+ function formatDuration(ms) {
6558
+ if (!ms) return "\u2014";
6559
+ const seconds = Math.floor(ms / 1e3);
6560
+ if (seconds < 60) return `${seconds}s`;
6561
+ const minutes = Math.floor(seconds / 60);
6562
+ const remainingSeconds = seconds % 60;
6563
+ if (minutes < 60) return `${minutes}m ${remainingSeconds}s`;
6564
+ const hours = Math.floor(minutes / 60);
6565
+ const remainingMinutes = minutes % 60;
6566
+ return `${hours}h ${remainingMinutes}m`;
6567
+ }
6568
+ function groupByDate(executions) {
6569
+ const groups = /* @__PURE__ */ new Map();
6570
+ for (const exec2 of executions) {
6571
+ const dateKey = exec2.startedAt.toISOString().split("T")[0];
6572
+ if (!groups.has(dateKey)) {
6573
+ groups.set(dateKey, []);
6574
+ }
6575
+ groups.get(dateKey).push(exec2);
6576
+ }
6577
+ return groups;
6578
+ }
6579
+ function formatDateHeader(dateStr) {
6580
+ const date = new Date(dateStr);
6581
+ const today = /* @__PURE__ */ new Date();
6582
+ const yesterday = new Date(today);
6583
+ yesterday.setDate(yesterday.getDate() - 1);
6584
+ if (dateStr === today.toISOString().split("T")[0]) {
6585
+ return `Today (${date.toLocaleDateString("en-US", { month: "short", day: "numeric" })})`;
6586
+ }
6587
+ if (dateStr === yesterday.toISOString().split("T")[0]) {
6588
+ return `Yesterday (${date.toLocaleDateString("en-US", { month: "short", day: "numeric" })})`;
6589
+ }
6590
+ return date.toLocaleDateString("en-US", { weekday: "short", month: "short", day: "numeric" });
6591
+ }
6592
+ async function historyCommand(options = {}) {
6593
+ const days = options.days || 7;
6594
+ const squad = options.squad;
6595
+ const verbose = options.verbose || false;
6596
+ const jsonOutput = options.json || false;
6597
+ writeLine();
6598
+ writeLine(` ${gradient("squads")} ${colors.dim}history${RESET}`);
6599
+ writeLine();
6600
+ const [bridgeExecs, localExecs] = await Promise.all([
6601
+ fetchFromBridge2(days, squad),
6602
+ Promise.resolve(fetchFromLocal(days, squad))
6603
+ ]);
6604
+ const seenIds = /* @__PURE__ */ new Set();
6605
+ const allExecutions = [];
6606
+ for (const exec2 of bridgeExecs) {
6607
+ seenIds.add(exec2.id);
6608
+ allExecutions.push(exec2);
6609
+ }
6610
+ for (const exec2 of localExecs) {
6611
+ if (!seenIds.has(exec2.id)) {
6612
+ allExecutions.push(exec2);
6613
+ }
6614
+ }
6615
+ allExecutions.sort((a, b) => b.startedAt.getTime() - a.startedAt.getTime());
6616
+ if (jsonOutput) {
6617
+ console.log(JSON.stringify(allExecutions, null, 2));
6618
+ return;
6619
+ }
6620
+ if (allExecutions.length === 0) {
6621
+ writeLine(` ${colors.dim}No executions found in the last ${days} day(s)${RESET}`);
6622
+ writeLine();
6623
+ writeLine(` ${colors.dim}Tip: Run agents with 'squads run <squad>' to see history${RESET}`);
6624
+ writeLine();
6625
+ return;
6626
+ }
6627
+ const grouped = groupByDate(allExecutions);
6628
+ const source = bridgeExecs.length > 0 ? "postgres" : "local";
6629
+ writeLine(` ${colors.dim}${allExecutions.length} executions (last ${days}d, source: ${source})${RESET}`);
6630
+ writeLine();
6631
+ for (const [dateStr, execs] of grouped) {
6632
+ writeLine(` ${bold}${formatDateHeader(dateStr)}${RESET}`);
6633
+ writeLine(` ${colors.purple}\u250C${"\u2500".repeat(60)}\u2510${RESET}`);
6634
+ writeLine(` ${colors.purple}\u2502${RESET} ${padEnd("TIME", 7)}${padEnd("SQUAD", 13)}${padEnd("AGENT", 16)}${padEnd("DURATION", 10)}${padEnd("STATUS", 8)}${colors.purple}\u2502${RESET}`);
6635
+ writeLine(` ${colors.purple}\u251C${"\u2500".repeat(60)}\u2524${RESET}`);
6636
+ for (const exec2 of execs) {
6637
+ const time = exec2.startedAt.toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit", hour12: false });
6638
+ const squadName = truncate(exec2.squad, 11);
6639
+ const agentName = truncate(exec2.agent, 14);
6640
+ const duration = formatDuration(exec2.durationMs);
6641
+ let statusIcon;
6642
+ let statusColor;
6643
+ switch (exec2.status) {
6644
+ case "success":
6645
+ statusIcon = icons.success;
6646
+ statusColor = colors.green;
6647
+ break;
6648
+ case "error":
6649
+ statusIcon = icons.error;
6650
+ statusColor = colors.red;
6651
+ break;
6652
+ case "running":
6653
+ statusIcon = icons.progress;
6654
+ statusColor = colors.cyan;
6655
+ break;
6656
+ default:
6657
+ statusIcon = icons.empty;
6658
+ statusColor = colors.dim;
6659
+ }
6660
+ writeLine(` ${colors.purple}\u2502${RESET} ${colors.dim}${time}${RESET} ${colors.cyan}${padEnd(squadName, 12)}${RESET}${padEnd(agentName, 16)}${padEnd(duration, 10)}${statusColor}${statusIcon}${RESET} ${colors.purple}\u2502${RESET}`);
6661
+ if (verbose && (exec2.cost || exec2.tokens)) {
6662
+ const costStr = exec2.cost ? `$${exec2.cost.toFixed(2)}` : "";
6663
+ const tokenStr = exec2.tokens ? `${exec2.tokens.toLocaleString()} tokens` : "";
6664
+ const details = [costStr, tokenStr].filter(Boolean).join(" \u2502 ");
6665
+ writeLine(` ${colors.purple}\u2502${RESET} ${colors.dim}\u2514 ${details}${RESET}${" ".repeat(Math.max(0, 45 - details.length))}${colors.purple}\u2502${RESET}`);
6666
+ }
6667
+ if (exec2.error) {
6668
+ writeLine(` ${colors.purple}\u2502${RESET} ${colors.red}\u2514 ${truncate(exec2.error, 45)}${RESET}${" ".repeat(Math.max(0, 45 - exec2.error.length))}${colors.purple}\u2502${RESET}`);
6669
+ }
6670
+ }
6671
+ writeLine(` ${colors.purple}\u2514${"\u2500".repeat(60)}\u2518${RESET}`);
6672
+ writeLine();
6673
+ }
6674
+ const successCount = allExecutions.filter((e) => e.status === "success").length;
6675
+ const errorCount = allExecutions.filter((e) => e.status === "error").length;
6676
+ const totalCost = allExecutions.reduce((sum, e) => sum + (e.cost || 0), 0);
6677
+ writeLine(` ${colors.dim}Summary:${RESET} ${colors.green}${successCount} success${RESET} ${errorCount > 0 ? `${colors.red}${errorCount} errors${RESET} ` : ""}${totalCost > 0 ? `${colors.cyan}$${totalCost.toFixed(2)} total${RESET}` : ""}`);
6678
+ writeLine();
6679
+ }
6680
+
6681
+ // src/commands/health.ts
6682
+ var FETCH_TIMEOUT_MS3 = 2e3;
6683
+ var SERVICES2 = [
6684
+ {
6685
+ name: "PostgreSQL",
6686
+ url: `${process.env.SQUADS_BRIDGE_URL || "http://localhost:8088"}/stats`,
6687
+ fix: "squads stack up postgres"
6688
+ },
6689
+ {
6690
+ name: "Redis",
6691
+ url: `${process.env.SQUADS_BRIDGE_URL || "http://localhost:8088"}/stats`,
6692
+ fix: "squads stack up redis"
6693
+ },
6694
+ {
6695
+ name: "Bridge API",
6696
+ url: `${process.env.SQUADS_BRIDGE_URL || "http://localhost:8088"}/health`,
6697
+ fix: "squads stack up bridge"
6698
+ },
6699
+ {
6700
+ name: "Scheduler",
6701
+ url: `${process.env.SQUADS_SCHEDULER_URL || "http://localhost:8090"}/health`,
6702
+ fix: "squads stack up scheduler"
6703
+ },
6704
+ {
6705
+ name: "Langfuse",
6706
+ url: `${process.env.LANGFUSE_HOST || "http://localhost:3100"}/api/public/health`,
6707
+ optional: true,
6708
+ fix: "squads stack up langfuse"
6709
+ }
6710
+ ];
6711
+ async function fetchWithTimeout3(url, timeoutMs = FETCH_TIMEOUT_MS3) {
6712
+ const controller = new AbortController();
6713
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
6714
+ try {
6715
+ const response = await fetch(url, { signal: controller.signal });
6716
+ clearTimeout(timeoutId);
6717
+ return response;
6718
+ } catch {
6719
+ clearTimeout(timeoutId);
6720
+ throw new Error("timeout");
6721
+ }
6722
+ }
6723
+ async function checkService2(service) {
6724
+ const start = Date.now();
6725
+ try {
6726
+ const response = await fetchWithTimeout3(service.url);
6727
+ const latencyMs = Date.now() - start;
6728
+ if (response.ok) {
6729
+ return {
6730
+ name: service.name,
6731
+ status: "healthy",
6732
+ latencyMs,
6733
+ optional: service.optional
6734
+ };
6735
+ }
6736
+ return {
6737
+ name: service.name,
6738
+ status: "degraded",
6739
+ latencyMs,
6740
+ error: `HTTP ${response.status}`,
6741
+ optional: service.optional,
6742
+ fix: service.fix
6743
+ };
6744
+ } catch (error) {
6745
+ return {
6746
+ name: service.name,
6747
+ status: "down",
6748
+ error: error instanceof Error ? error.message : "unknown",
6749
+ optional: service.optional,
6750
+ fix: service.fix
6751
+ };
6752
+ }
6753
+ }
6754
+ async function getTriggerStats() {
6755
+ try {
6756
+ const schedulerUrl = process.env.SQUADS_SCHEDULER_URL || "http://localhost:8090";
6757
+ const response = await fetchWithTimeout3(`${schedulerUrl}/api/triggers/stats`);
6758
+ if (!response.ok) return null;
6759
+ const data = await response.json();
6760
+ return {
6761
+ active: data.active || 0,
6762
+ disabled: data.disabled || 0,
6763
+ lastFire: data.last_fire ? {
6764
+ name: data.last_fire.name || "unknown",
6765
+ ago: formatTimeAgo(new Date(data.last_fire.fired_at || Date.now()))
6766
+ } : void 0
6767
+ };
6768
+ } catch {
6769
+ return null;
6770
+ }
6771
+ }
6772
+ function formatTimeAgo(date) {
6773
+ const seconds = Math.floor((Date.now() - date.getTime()) / 1e3);
6774
+ if (seconds < 60) return `${seconds}s ago`;
6775
+ if (seconds < 3600) return `${Math.floor(seconds / 60)}m ago`;
6776
+ if (seconds < 86400) return `${Math.floor(seconds / 3600)}h ago`;
6777
+ return `${Math.floor(seconds / 86400)}d ago`;
6778
+ }
6779
+ function formatLatency(ms) {
6780
+ if (!ms) return "\u2014";
6781
+ return `${ms}ms`;
6782
+ }
6783
+ async function healthCommand(options = {}) {
6784
+ writeLine();
6785
+ writeLine(` ${gradient("squads")} ${colors.dim}health${RESET}`);
6786
+ writeLine();
6787
+ const results = await Promise.all(SERVICES2.map(checkService2));
6788
+ writeLine(` ${colors.purple}\u250C${"\u2500".repeat(48)}\u2510${RESET}`);
6789
+ writeLine(` ${colors.purple}\u2502${RESET} ${padEnd("SERVICE", 18)}${padEnd("STATUS", 14)}${padEnd("LATENCY", 12)}${colors.purple}\u2502${RESET}`);
6790
+ writeLine(` ${colors.purple}\u251C${"\u2500".repeat(48)}\u2524${RESET}`);
6791
+ const issues = [];
6792
+ for (const result of results) {
6793
+ let statusIcon;
6794
+ let statusColor;
6795
+ let statusText;
6796
+ switch (result.status) {
6797
+ case "healthy":
6798
+ statusIcon = icons.success;
6799
+ statusColor = colors.green;
6800
+ statusText = "healthy";
6801
+ break;
6802
+ case "degraded":
6803
+ statusIcon = icons.warning;
6804
+ statusColor = colors.yellow;
6805
+ statusText = "degraded";
6806
+ issues.push(result);
6807
+ break;
6808
+ case "down":
6809
+ statusIcon = icons.error;
6810
+ statusColor = colors.red;
6811
+ statusText = "down";
6812
+ if (!result.optional) {
6813
+ issues.push(result);
6814
+ }
6815
+ break;
6816
+ }
6817
+ const nameDisplay = result.optional ? `${result.name} ${colors.dim}(opt)${RESET}` : result.name;
6818
+ const latency = formatLatency(result.latencyMs);
6819
+ writeLine(` ${colors.purple}\u2502${RESET} ${padEnd(nameDisplay, 18)}${statusColor}${statusIcon} ${padEnd(statusText, 11)}${RESET}${padEnd(latency, 12)}${colors.purple}\u2502${RESET}`);
6820
+ }
6821
+ writeLine(` ${colors.purple}\u2514${"\u2500".repeat(48)}\u2518${RESET}`);
6822
+ writeLine();
6823
+ const schedulerUp = results.find((r) => r.name === "Scheduler")?.status === "healthy";
6824
+ if (schedulerUp) {
6825
+ const stats = await getTriggerStats();
6826
+ if (stats) {
6827
+ const lastFireText = stats.lastFire ? `${colors.dim}Last fire:${RESET} ${stats.lastFire.ago} (${stats.lastFire.name})` : `${colors.dim}No recent fires${RESET}`;
6828
+ writeLine(` ${colors.cyan}Triggers:${RESET} ${stats.active} active, ${stats.disabled} disabled`);
6829
+ writeLine(` ${lastFireText}`);
6830
+ writeLine();
6831
+ }
6832
+ }
6833
+ if (issues.length > 0) {
6834
+ const criticalIssues = issues.filter((i) => !i.optional);
6835
+ const optionalIssues = issues.filter((i) => i.optional);
6836
+ if (criticalIssues.length > 0) {
6837
+ writeLine(` ${colors.red}${icons.warning} ${criticalIssues.length} service(s) need attention${RESET}`);
6838
+ for (const issue of criticalIssues) {
6839
+ writeLine(` ${colors.dim}\u2022${RESET} ${issue.name}: ${issue.error || "not responding"}`);
6840
+ if (issue.fix) {
6841
+ writeLine(` ${colors.cyan}Fix:${RESET} ${issue.fix}`);
6842
+ }
6843
+ }
6844
+ writeLine();
6845
+ }
6846
+ if (options.verbose && optionalIssues.length > 0) {
6847
+ writeLine(` ${colors.yellow}Optional services down:${RESET}`);
6848
+ for (const issue of optionalIssues) {
6849
+ writeLine(` ${colors.dim}\u2022${RESET} ${issue.name}`);
6850
+ }
6851
+ writeLine();
6852
+ }
6853
+ } else {
6854
+ writeLine(` ${colors.green}${icons.success} All services healthy${RESET}`);
6855
+ writeLine();
6856
+ }
6857
+ if (!schedulerUp) {
6858
+ writeLine(` ${colors.yellow}${icons.warning} Scheduler not running - triggers won't auto-fire${RESET}`);
6859
+ writeLine(` ${colors.dim}Start with:${RESET} squads stack up scheduler`);
6860
+ writeLine();
6861
+ }
6862
+ }
6863
+
5345
6864
  // src/commands/workers.ts
5346
- import { execSync as execSync10 } from "child_process";
6865
+ import { execSync as execSync11 } from "child_process";
5347
6866
  import { existsSync as existsSync14, readFileSync as readFileSync11 } from "fs";
5348
- import { join as join14 } from "path";
6867
+ import { join as join15 } from "path";
5349
6868
  function getTasksFilePath2() {
5350
6869
  const memoryDir = findMemoryDir();
5351
6870
  if (!memoryDir) return null;
5352
- return join14(memoryDir, "..", "tasks.json");
6871
+ return join15(memoryDir, "..", "tasks.json");
5353
6872
  }
5354
6873
  function loadActiveTasks() {
5355
6874
  const tasksPath = getTasksFilePath2();
@@ -5364,7 +6883,7 @@ function loadActiveTasks() {
5364
6883
  function getRunningProcesses() {
5365
6884
  const processes = [];
5366
6885
  try {
5367
- const psOutput = execSync10(
6886
+ const psOutput = execSync11(
5368
6887
  'ps aux | grep -E "claude|squads|astro|node.*agent" | grep -v grep',
5369
6888
  { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
5370
6889
  ).trim();
@@ -5415,7 +6934,7 @@ async function workersCommand(options = {}) {
5415
6934
  writeLine();
5416
6935
  if (options.kill) {
5417
6936
  try {
5418
- execSync10(`kill ${options.kill}`, { stdio: "pipe" });
6937
+ execSync11(`kill ${options.kill}`, { stdio: "pipe" });
5419
6938
  writeLine(` ${icons.success} Killed process ${colors.cyan}${options.kill}${RESET}`);
5420
6939
  writeLine();
5421
6940
  return;
@@ -5501,6 +7020,725 @@ function getElapsedTime2(startTime) {
5501
7020
  return "<1m";
5502
7021
  }
5503
7022
 
7023
+ // src/commands/context-feed.ts
7024
+ import { existsSync as existsSync15, statSync as statSync3, readdirSync as readdirSync6, readFileSync as readFileSync12 } from "fs";
7025
+ import { join as join16 } from "path";
7026
+ var BRIDGE_URL3 = process.env.SQUADS_BRIDGE_URL || "http://localhost:8088";
7027
+ async function syncBriefToBridge(brief, sourcePath) {
7028
+ try {
7029
+ const response = await fetch(`${BRIDGE_URL3}/api/brief`, {
7030
+ method: "POST",
7031
+ headers: { "Content-Type": "application/json" },
7032
+ body: JSON.stringify({
7033
+ priority: brief.priority,
7034
+ runway: brief.runway,
7035
+ focus: brief.focus || [],
7036
+ blockers: brief.blockers || [],
7037
+ decision_framework: brief.decisionFramework || [],
7038
+ raw_content: brief.raw || "",
7039
+ source_path: sourcePath,
7040
+ synced_by: "cli"
7041
+ })
7042
+ });
7043
+ if (!response.ok) {
7044
+ return false;
7045
+ }
7046
+ const result = await response.json();
7047
+ return result.status === "synced" || result.status === "unchanged";
7048
+ } catch {
7049
+ return false;
7050
+ }
7051
+ }
7052
+ function readBusinessBrief(squadsDir) {
7053
+ if (!squadsDir) return void 0;
7054
+ const briefPath = join16(squadsDir, "..", "BUSINESS_BRIEF.md");
7055
+ if (!existsSync15(briefPath)) return void 0;
7056
+ try {
7057
+ const content = readFileSync12(briefPath, "utf-8");
7058
+ const brief = { raw: content };
7059
+ const priorityMatch = content.match(/##\s*#1 Priority\s*\n+\*\*([^*]+)\*\*/);
7060
+ if (priorityMatch) {
7061
+ brief.priority = priorityMatch[1].trim();
7062
+ }
7063
+ const runwayMatch = content.match(/##\s*Runway\s*\n+([\s\S]*?)(?=\n##|$)/);
7064
+ if (runwayMatch) {
7065
+ const pressureMatch = runwayMatch[1].match(/\*\*Pressure\*\*:\s*(\w+)/i);
7066
+ if (pressureMatch) {
7067
+ brief.runway = pressureMatch[1];
7068
+ }
7069
+ }
7070
+ const focusMatch = content.match(/##\s*Current Focus\s*\n+([\s\S]*?)(?=\n##|$)/);
7071
+ if (focusMatch) {
7072
+ const items = focusMatch[1].match(/^\d+\.\s*\*\*([^*]+)\*\*/gm);
7073
+ if (items) {
7074
+ brief.focus = items.map((item) => {
7075
+ const match = item.match(/\*\*([^*]+)\*\*/);
7076
+ return match ? match[1].trim() : item;
7077
+ });
7078
+ }
7079
+ }
7080
+ const blockersMatch = content.match(/##\s*Blockers\s*\n+([\s\S]*?)(?=\n##|$)/);
7081
+ if (blockersMatch) {
7082
+ const text = blockersMatch[1].trim();
7083
+ if (text.toLowerCase().includes("none")) {
7084
+ brief.blockers = [];
7085
+ } else {
7086
+ const items = text.match(/^-\s*(.+)$/gm);
7087
+ if (items) {
7088
+ brief.blockers = items.map((item) => item.replace(/^-\s*/, "").trim());
7089
+ }
7090
+ }
7091
+ }
7092
+ const decisionMatch = content.match(/##\s*Decision Framework\s*\n+([\s\S]*?)(?=\n##|$)/);
7093
+ if (decisionMatch) {
7094
+ const items = decisionMatch[1].match(/^\d+\.\s*(.+)$/gm);
7095
+ if (items) {
7096
+ brief.decisionFramework = items.map((item) => item.replace(/^\d+\.\s*/, "").trim());
7097
+ }
7098
+ }
7099
+ return brief;
7100
+ } catch {
7101
+ return void 0;
7102
+ }
7103
+ }
7104
+ async function collectBriefingData(options) {
7105
+ const squadsDir = findSquadsDir();
7106
+ const memoryDir = findMemoryDir();
7107
+ const baseDir = squadsDir ? join16(squadsDir, "..", "..", "..") : null;
7108
+ const allSquads = squadsDir ? listSquads(squadsDir) : [];
7109
+ if (options.squad && !allSquads.includes(options.squad)) {
7110
+ return {
7111
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
7112
+ error: `Squad "${options.squad}" not found. Available: ${allSquads.join(", ")}`,
7113
+ squads: [],
7114
+ goals: { active: 0, completed: 0, bySquad: [] },
7115
+ sessions: { active: 0, bySquad: 0 }
7116
+ };
7117
+ }
7118
+ const squadNames = options.squad ? [options.squad] : allSquads;
7119
+ const [bridgeStats, rateLimits, sessions2, gitStats] = await Promise.all([
7120
+ fetchBridgeStats(),
7121
+ fetchRateLimits(),
7122
+ getLiveSessionSummaryAsync(),
7123
+ baseDir ? Promise.resolve(getMultiRepoGitStats(baseDir, 7)) : Promise.resolve(null)
7124
+ ]);
7125
+ const squadBriefings = [];
7126
+ const goalsBySquad = [];
7127
+ let totalActive = 0;
7128
+ let totalCompleted = 0;
7129
+ for (const squadName of squadNames) {
7130
+ const squad = loadSquad(squadName);
7131
+ if (!squad) continue;
7132
+ const agents = squadsDir ? listAgents(squadsDir, squadName) : [];
7133
+ const activeGoals = squad.goals.filter((g) => !g.completed);
7134
+ const completedGoals = squad.goals.filter((g) => g.completed);
7135
+ totalActive += activeGoals.length;
7136
+ totalCompleted += completedGoals.length;
7137
+ const states = getSquadState(squadName);
7138
+ const recentMemory = [];
7139
+ for (const state of states.slice(0, 3)) {
7140
+ const lines = state.content.split("\n").filter((l) => l.trim() && !l.startsWith("#"));
7141
+ if (lines.length > 0) {
7142
+ recentMemory.push(lines[0].substring(0, 100));
7143
+ }
7144
+ }
7145
+ let lastActivity;
7146
+ if (memoryDir) {
7147
+ const squadMemoryPath = join16(memoryDir, squadName);
7148
+ if (existsSync15(squadMemoryPath)) {
7149
+ let mostRecent = 0;
7150
+ try {
7151
+ const walkDir = (dir) => {
7152
+ const entries = readdirSync6(dir, { withFileTypes: true });
7153
+ for (const entry of entries) {
7154
+ const fullPath = join16(dir, entry.name);
7155
+ if (entry.isDirectory()) {
7156
+ walkDir(fullPath);
7157
+ } else if (entry.name.endsWith(".md")) {
7158
+ const stat = statSync3(fullPath);
7159
+ if (stat.mtimeMs > mostRecent) {
7160
+ mostRecent = stat.mtimeMs;
7161
+ }
7162
+ }
7163
+ }
7164
+ };
7165
+ walkDir(squadMemoryPath);
7166
+ } catch {
7167
+ }
7168
+ if (mostRecent > 0) {
7169
+ const daysAgo = Math.floor((Date.now() - mostRecent) / (1e3 * 60 * 60 * 24));
7170
+ if (daysAgo === 0) lastActivity = "today";
7171
+ else if (daysAgo === 1) lastActivity = "yesterday";
7172
+ else lastActivity = `${daysAgo}d ago`;
7173
+ }
7174
+ }
7175
+ }
7176
+ squadBriefings.push({
7177
+ name: squadName,
7178
+ mission: squad.mission,
7179
+ agentCount: agents.length,
7180
+ activeGoals: activeGoals.map((g) => ({
7181
+ description: g.description,
7182
+ progress: g.progress
7183
+ })),
7184
+ memoryEntries: states.length,
7185
+ recentMemory,
7186
+ lastActivity
7187
+ });
7188
+ if (activeGoals.length > 0) {
7189
+ goalsBySquad.push({
7190
+ squad: squadName,
7191
+ goals: activeGoals.map((g) => g.description)
7192
+ });
7193
+ }
7194
+ }
7195
+ const costs = bridgeStats ? {
7196
+ today: {
7197
+ generations: bridgeStats.today.generations,
7198
+ cost: bridgeStats.today.costUsd
7199
+ },
7200
+ budget: bridgeStats.budget,
7201
+ bySquad: bridgeStats.bySquad.map((s) => ({
7202
+ squad: s.squad,
7203
+ cost: s.costUsd,
7204
+ generations: s.generations
7205
+ }))
7206
+ } : void 0;
7207
+ const rateLimitsData = rateLimits.source !== "none" ? {
7208
+ models: Object.values(rateLimits.limits).map((l) => ({
7209
+ model: l.model,
7210
+ requestsRemaining: l.requestsRemaining,
7211
+ tokensRemaining: l.tokensRemaining
7212
+ }))
7213
+ } : void 0;
7214
+ const git = gitStats ? {
7215
+ commits: gitStats.totalCommits,
7216
+ activeDays: gitStats.activeDays,
7217
+ avgPerDay: gitStats.avgCommitsPerDay,
7218
+ byRepo: Array.from(gitStats.commitsByRepo.entries()).map(([repo, commits]) => ({
7219
+ repo,
7220
+ commits
7221
+ }))
7222
+ } : void 0;
7223
+ let relevantMemory;
7224
+ if (options.topic) {
7225
+ const results = searchMemory(options.topic);
7226
+ relevantMemory = results.slice(0, 5).map((r) => ({
7227
+ squad: r.entry.squad,
7228
+ agent: r.entry.agent,
7229
+ snippet: r.matches[0]?.substring(0, 150) || ""
7230
+ }));
7231
+ }
7232
+ const brief = readBusinessBrief(squadsDir);
7233
+ if (brief && squadsDir) {
7234
+ const briefPath = join16(squadsDir, "..", "BUSINESS_BRIEF.md");
7235
+ syncBriefToBridge(brief, briefPath).catch(() => {
7236
+ });
7237
+ }
7238
+ return {
7239
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
7240
+ brief,
7241
+ squads: squadBriefings,
7242
+ goals: {
7243
+ active: totalActive,
7244
+ completed: totalCompleted,
7245
+ bySquad: goalsBySquad
7246
+ },
7247
+ costs,
7248
+ rateLimits: rateLimitsData,
7249
+ git,
7250
+ sessions: {
7251
+ active: sessions2.totalSessions,
7252
+ bySquad: sessions2.squadCount
7253
+ },
7254
+ relevantMemory
7255
+ };
7256
+ }
7257
+ function renderHumanBriefing(data, options) {
7258
+ writeLine();
7259
+ writeLine(` ${gradient("squads")} ${colors.dim}context-feed${RESET}`);
7260
+ writeLine();
7261
+ if (data.error) {
7262
+ writeLine(` ${colors.yellow}${icons.warning || "\u26A0"}${RESET} ${data.error}`);
7263
+ writeLine();
7264
+ return;
7265
+ }
7266
+ if (data.brief) {
7267
+ if (data.brief.priority) {
7268
+ const runwayColor = data.brief.runway === "HIGH" ? colors.red : data.brief.runway === "MEDIUM" ? colors.yellow : colors.green;
7269
+ writeLine(` ${bold}#1 Priority${RESET} ${runwayColor}[${data.brief.runway || "\u2014"}]${RESET}`);
7270
+ writeLine(` ${colors.white}${data.brief.priority}${RESET}`);
7271
+ writeLine();
7272
+ }
7273
+ if (data.brief.focus && data.brief.focus.length > 0) {
7274
+ writeLine(` ${bold}Focus${RESET}`);
7275
+ for (const item of data.brief.focus.slice(0, 3)) {
7276
+ writeLine(` ${colors.cyan}\u2192${RESET} ${item}`);
7277
+ }
7278
+ writeLine();
7279
+ }
7280
+ if (data.brief.blockers && data.brief.blockers.length > 0) {
7281
+ writeLine(` ${colors.red}${bold}Blockers${RESET}`);
7282
+ for (const blocker of data.brief.blockers) {
7283
+ writeLine(` ${colors.red}\u2717${RESET} ${blocker}`);
7284
+ }
7285
+ writeLine();
7286
+ }
7287
+ }
7288
+ if (data.sessions.active > 0) {
7289
+ writeLine(` ${colors.green}${icons.active}${RESET} ${data.sessions.active} active sessions across ${data.sessions.bySquad} squads`);
7290
+ writeLine();
7291
+ }
7292
+ if (data.goals.active > 0) {
7293
+ writeLine(` ${bold}Active Goals${RESET} ${colors.dim}(${data.goals.active})${RESET}`);
7294
+ writeLine();
7295
+ for (const sq of data.goals.bySquad) {
7296
+ writeLine(` ${colors.cyan}${sq.squad}${RESET}`);
7297
+ for (const goal2 of sq.goals.slice(0, 2)) {
7298
+ writeLine(` ${icons.active} ${goal2}`);
7299
+ }
7300
+ if (sq.goals.length > 2) {
7301
+ writeLine(` ${colors.dim}+${sq.goals.length - 2} more${RESET}`);
7302
+ }
7303
+ }
7304
+ writeLine();
7305
+ }
7306
+ if (data.costs) {
7307
+ const { budget } = data.costs;
7308
+ const usedBar = "\u2588".repeat(Math.min(Math.round(budget.usedPct / 5), 20));
7309
+ const emptyBar = "\u2591".repeat(20 - usedBar.length);
7310
+ writeLine(` ${bold}Budget${RESET}`);
7311
+ writeLine(` ${colors.dim}$${budget.used.toFixed(2)}/${budget.daily} today${RESET} ${usedBar}${emptyBar} ${budget.usedPct.toFixed(0)}%`);
7312
+ writeLine();
7313
+ }
7314
+ if (data.rateLimits && options.verbose) {
7315
+ writeLine(` ${bold}Rate Limits${RESET}`);
7316
+ for (const model of data.rateLimits.models.slice(0, 3)) {
7317
+ const shortName = model.model.replace("claude-", "").replace(/-\d+$/, "");
7318
+ writeLine(` ${colors.dim}${shortName}:${RESET} ${model.requestsRemaining} req, ${Math.round(model.tokensRemaining / 1e3)}k tok`);
7319
+ }
7320
+ writeLine();
7321
+ }
7322
+ if (data.git && data.git.commits > 0) {
7323
+ writeLine(` ${bold}Git Activity${RESET} ${colors.dim}(7d)${RESET}`);
7324
+ writeLine(` ${data.git.commits} commits, ${data.git.avgPerDay}/day avg`);
7325
+ writeLine();
7326
+ }
7327
+ const activeSquads = data.squads.filter((s) => s.activeGoals.length > 0 || s.memoryEntries > 0);
7328
+ if (activeSquads.length > 0 && options.verbose) {
7329
+ const w = { name: 14, agents: 8, memory: 10, activity: 10 };
7330
+ const tableWidth = w.name + w.agents + w.memory + w.activity + 4;
7331
+ writeLine(` ${colors.purple}${box.topLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.topRight}${RESET}`);
7332
+ writeLine(` ${colors.purple}${box.vertical}${RESET} ${bold}${padEnd("SQUAD", w.name)}${RESET}${bold}${padEnd("AGENTS", w.agents)}${RESET}${bold}${padEnd("MEMORY", w.memory)}${RESET}${bold}ACTIVITY${RESET} ${colors.purple}${box.vertical}${RESET}`);
7333
+ writeLine(` ${colors.purple}${box.teeRight}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.teeLeft}${RESET}`);
7334
+ for (const sq of activeSquads.slice(0, 8)) {
7335
+ const row = ` ${colors.purple}${box.vertical}${RESET} ${colors.cyan}${padEnd(sq.name, w.name)}${RESET}${padEnd(String(sq.agentCount), w.agents)}${padEnd(String(sq.memoryEntries), w.memory)}${padEnd(sq.lastActivity || "\u2014", w.activity - 2)}${colors.purple}${box.vertical}${RESET}`;
7336
+ writeLine(row);
7337
+ }
7338
+ writeLine(` ${colors.purple}${box.bottomLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.bottomRight}${RESET}`);
7339
+ writeLine();
7340
+ }
7341
+ if (data.relevantMemory && data.relevantMemory.length > 0) {
7342
+ writeLine(` ${bold}Relevant Memory${RESET} ${colors.dim}("${options.topic}")${RESET}`);
7343
+ for (const mem of data.relevantMemory) {
7344
+ writeLine(` ${colors.cyan}${mem.squad}/${mem.agent}${RESET}`);
7345
+ writeLine(` ${colors.dim}${mem.snippet}${RESET}`);
7346
+ }
7347
+ writeLine();
7348
+ }
7349
+ writeLine(` ${colors.dim}$${RESET} squads feed ${colors.cyan}--topic "pricing"${RESET} ${colors.dim}Topic-focused${RESET}`);
7350
+ writeLine(` ${colors.dim}$${RESET} squads feed ${colors.cyan}--squad website${RESET} ${colors.dim}Single squad${RESET}`);
7351
+ writeLine(` ${colors.dim}$${RESET} squads feed ${colors.cyan}--agent${RESET} ${colors.dim}JSON for agents${RESET}`);
7352
+ writeLine();
7353
+ }
7354
+ function renderAgentBriefing(data) {
7355
+ console.log(JSON.stringify(data, null, 2));
7356
+ }
7357
+ async function contextFeedCommand(options = {}) {
7358
+ const squadsDir = findSquadsDir();
7359
+ if (!squadsDir) {
7360
+ if (options.json || options.agent) {
7361
+ console.log(JSON.stringify({ error: "No .agents/squads directory found" }));
7362
+ } else {
7363
+ writeLine(`${colors.red}No .agents/squads directory found${RESET}`);
7364
+ writeLine(`${colors.dim}Run \`squads init\` to create one.${RESET}`);
7365
+ }
7366
+ process.exit(1);
7367
+ }
7368
+ const data = await collectBriefingData(options);
7369
+ if (options.json || options.agent) {
7370
+ renderAgentBriefing(data);
7371
+ } else {
7372
+ renderHumanBriefing(data, options);
7373
+ }
7374
+ }
7375
+
7376
+ // src/commands/watch.ts
7377
+ import { spawn as spawn6 } from "child_process";
7378
+ async function watchCommand(command, args, options) {
7379
+ const interval = (options.interval || 2) * 1e3;
7380
+ const shouldClear = options.clear !== false;
7381
+ const validCommands = [
7382
+ "status",
7383
+ "workers",
7384
+ "dash",
7385
+ "dashboard",
7386
+ "sessions",
7387
+ "memory",
7388
+ "history",
7389
+ "results",
7390
+ "progress",
7391
+ "goal"
7392
+ ];
7393
+ if (!validCommands.includes(command)) {
7394
+ writeLine(` ${colors.red}Unknown command: ${command}${RESET}`);
7395
+ writeLine(` ${colors.dim}Valid commands: ${validCommands.join(", ")}${RESET}`);
7396
+ process.exit(1);
7397
+ }
7398
+ writeLine();
7399
+ writeLine(` ${gradient("squads")} ${colors.dim}watch${RESET} ${colors.cyan}${command}${RESET}`);
7400
+ writeLine(` ${colors.dim}Refreshing every ${options.interval || 2}s (Ctrl+C to stop)${RESET}`);
7401
+ writeLine();
7402
+ await runCommand2(command, args, shouldClear);
7403
+ const timer = setInterval(async () => {
7404
+ await runCommand2(command, args, shouldClear);
7405
+ }, interval);
7406
+ process.on("SIGINT", () => {
7407
+ clearInterval(timer);
7408
+ writeLine();
7409
+ writeLine(` ${colors.dim}Watch stopped${RESET}`);
7410
+ writeLine();
7411
+ process.exit(0);
7412
+ });
7413
+ await new Promise(() => {
7414
+ });
7415
+ }
7416
+ async function runCommand2(command, args, clear) {
7417
+ return new Promise((resolve) => {
7418
+ if (clear) {
7419
+ process.stdout.write("\x1B[2J\x1B[H");
7420
+ }
7421
+ const now = (/* @__PURE__ */ new Date()).toLocaleTimeString();
7422
+ process.stdout.write(`\x1B[90m${now}\x1B[0m
7423
+
7424
+ `);
7425
+ const child = spawn6("squads", [command, ...args], {
7426
+ stdio: "inherit",
7427
+ env: process.env
7428
+ });
7429
+ child.on("close", () => {
7430
+ resolve();
7431
+ });
7432
+ child.on("error", (err) => {
7433
+ writeLine(` ${colors.red}Error: ${err.message}${RESET}`);
7434
+ resolve();
7435
+ });
7436
+ });
7437
+ }
7438
+
7439
+ // src/commands/live.ts
7440
+ import blessed from "blessed";
7441
+ import contrib from "blessed-contrib";
7442
+ import { execSync as execSync12 } from "child_process";
7443
+ async function liveCommand(_options) {
7444
+ const screen = blessed.screen({
7445
+ smartCSR: true,
7446
+ title: "Squads Live Dashboard",
7447
+ fullUnicode: true
7448
+ });
7449
+ const grid = new contrib.grid({
7450
+ rows: 12,
7451
+ cols: 12,
7452
+ screen
7453
+ });
7454
+ const agentsTable = grid.set(0, 0, 6, 8, contrib.table, {
7455
+ keys: true,
7456
+ fg: "white",
7457
+ label: " Running Agents ",
7458
+ columnSpacing: 2,
7459
+ columnWidth: [20, 12, 10, 8, 8],
7460
+ border: { type: "line", fg: "cyan" }
7461
+ });
7462
+ const costGauge = grid.set(0, 8, 3, 4, contrib.gauge, {
7463
+ label: " Budget Used ",
7464
+ stroke: "green",
7465
+ fill: "white",
7466
+ border: { type: "line", fg: "cyan" }
7467
+ });
7468
+ const costLog = grid.set(3, 8, 3, 4, contrib.log, {
7469
+ fg: "green",
7470
+ label: " Cost Tracker ",
7471
+ border: { type: "line", fg: "cyan" }
7472
+ });
7473
+ const activityLog = grid.set(6, 0, 6, 6, contrib.log, {
7474
+ fg: "cyan",
7475
+ label: " Recent Activity ",
7476
+ border: { type: "line", fg: "magenta" }
7477
+ });
7478
+ const memoryLog = grid.set(6, 6, 6, 6, contrib.log, {
7479
+ fg: "yellow",
7480
+ label: " Memory Updates ",
7481
+ border: { type: "line", fg: "magenta" }
7482
+ });
7483
+ function getAgents() {
7484
+ try {
7485
+ const output = execSync12('ps aux | grep -E "claude|node.*squads" | grep -v grep', {
7486
+ encoding: "utf-8",
7487
+ timeout: 5e3
7488
+ });
7489
+ const agents = [];
7490
+ const lines = output.trim().split("\n").slice(0, 10);
7491
+ for (const line of lines) {
7492
+ const parts = line.trim().split(/\s+/);
7493
+ if (parts.length >= 10) {
7494
+ const cpu = parts[2];
7495
+ const mem = parts[3];
7496
+ const time = parts[9];
7497
+ const cmd = parts.slice(10).join(" ");
7498
+ let name = "claude";
7499
+ let squad = "unknown";
7500
+ if (cmd.includes("squads-")) {
7501
+ const match = cmd.match(/squads-(\w+)-(\w+)/);
7502
+ if (match) {
7503
+ squad = match[1];
7504
+ name = match[2];
7505
+ }
7506
+ }
7507
+ agents.push({
7508
+ name: name.substring(0, 18),
7509
+ squad: squad.substring(0, 10),
7510
+ status: parseFloat(cpu) > 5 ? "active" : "idle",
7511
+ duration: time,
7512
+ cpu: cpu + "%",
7513
+ mem: mem + "%"
7514
+ });
7515
+ }
7516
+ }
7517
+ return agents.slice(0, 8);
7518
+ } catch {
7519
+ return [];
7520
+ }
7521
+ }
7522
+ function getCosts() {
7523
+ const baseToday = 12.5;
7524
+ const baseWeek = 89.2;
7525
+ const variance = Math.random() * 0.5;
7526
+ return {
7527
+ today: baseToday + variance,
7528
+ week: baseWeek + variance * 10,
7529
+ budget: 200
7530
+ };
7531
+ }
7532
+ function getRecentActivity2() {
7533
+ try {
7534
+ const output = execSync12(
7535
+ "gh issue list --repo agents-squads/squads-cli --state open --limit 5 --json number,title,createdAt 2>/dev/null",
7536
+ { encoding: "utf-8", timeout: 1e4 }
7537
+ );
7538
+ const issues = JSON.parse(output);
7539
+ return issues.map(
7540
+ (i) => `#${i.number} ${i.title.substring(0, 40)}`
7541
+ );
7542
+ } catch {
7543
+ return ["(loading...)"];
7544
+ }
7545
+ }
7546
+ function getMemoryUpdates() {
7547
+ try {
7548
+ const output = execSync12(
7549
+ 'find .agents/memory -name "state.md" -mmin -60 2>/dev/null | head -5',
7550
+ { encoding: "utf-8", timeout: 5e3 }
7551
+ );
7552
+ return output.trim().split("\n").filter(Boolean).map((path3) => {
7553
+ const parts = path3.split("/");
7554
+ const squad = parts[2] || "";
7555
+ const agent = parts[3] || "";
7556
+ return `${squad}/${agent}`;
7557
+ });
7558
+ } catch {
7559
+ return ["(no recent updates)"];
7560
+ }
7561
+ }
7562
+ function updateAgents() {
7563
+ const agents = getAgents();
7564
+ const data = agents.length > 0 ? agents.map((a) => [a.name, a.squad, a.status === "active" ? "\u25CF active" : "\u25CB idle", a.cpu, a.mem]) : [["(no agents running)", "", "", "", ""]];
7565
+ agentsTable.setData({
7566
+ headers: ["Agent", "Squad", "Status", "CPU", "MEM"],
7567
+ data
7568
+ });
7569
+ }
7570
+ function updateCosts() {
7571
+ const costs = getCosts();
7572
+ const percent = Math.round(costs.week / costs.budget * 100);
7573
+ costGauge.setPercent(percent);
7574
+ costLog.log(`Today: $${costs.today.toFixed(2)}`);
7575
+ costLog.log(`Week: $${costs.week.toFixed(2)} / $${costs.budget}`);
7576
+ }
7577
+ function updateActivity() {
7578
+ const activities = getRecentActivity2();
7579
+ activities.forEach((a) => activityLog.log(a));
7580
+ }
7581
+ function updateMemory() {
7582
+ const updates = getMemoryUpdates();
7583
+ updates.forEach((u) => memoryLog.log(u));
7584
+ }
7585
+ updateAgents();
7586
+ updateCosts();
7587
+ updateActivity();
7588
+ updateMemory();
7589
+ screen.render();
7590
+ const agentInterval = setInterval(() => {
7591
+ updateAgents();
7592
+ screen.render();
7593
+ }, 2e3);
7594
+ const costInterval = setInterval(() => {
7595
+ updateCosts();
7596
+ screen.render();
7597
+ }, 5e3);
7598
+ const activityInterval = setInterval(() => {
7599
+ updateActivity();
7600
+ screen.render();
7601
+ }, 1e4);
7602
+ const memoryInterval = setInterval(() => {
7603
+ updateMemory();
7604
+ screen.render();
7605
+ }, 15e3);
7606
+ screen.key(["escape", "q", "C-c"], () => {
7607
+ clearInterval(agentInterval);
7608
+ clearInterval(costInterval);
7609
+ clearInterval(activityInterval);
7610
+ clearInterval(memoryInterval);
7611
+ return process.exit(0);
7612
+ });
7613
+ screen.key(["r"], () => {
7614
+ updateAgents();
7615
+ updateCosts();
7616
+ updateActivity();
7617
+ updateMemory();
7618
+ screen.render();
7619
+ });
7620
+ agentsTable.focus();
7621
+ }
7622
+
7623
+ // src/commands/top.ts
7624
+ import { execSync as execSync13 } from "child_process";
7625
+ var ESC = "\x1B";
7626
+ var CLEAR_SCREEN = `${ESC}[2J`;
7627
+ var CURSOR_HOME = `${ESC}[H`;
7628
+ var CURSOR_HIDE = `${ESC}[?25l`;
7629
+ var CURSOR_SHOW = `${ESC}[?25h`;
7630
+ var BOLD = `${ESC}[1m`;
7631
+ var DIM = `${ESC}[2m`;
7632
+ var RESET2 = `${ESC}[0m`;
7633
+ var CYAN = `${ESC}[36m`;
7634
+ var GREEN = `${ESC}[32m`;
7635
+ var YELLOW = `${ESC}[33m`;
7636
+ var MAGENTA = `${ESC}[35m`;
7637
+ async function topCommand() {
7638
+ process.stdout.write(CURSOR_HIDE + CLEAR_SCREEN + CURSOR_HOME);
7639
+ let running = true;
7640
+ const cleanup = () => {
7641
+ running = false;
7642
+ process.stdout.write(CURSOR_SHOW + "\n");
7643
+ process.exit(0);
7644
+ };
7645
+ process.on("SIGINT", cleanup);
7646
+ process.on("SIGTERM", cleanup);
7647
+ while (running) {
7648
+ const data = getProcessData();
7649
+ render(data);
7650
+ await sleep2(1e3);
7651
+ }
7652
+ }
7653
+ function getProcessData() {
7654
+ const processes = [];
7655
+ let claudeCount = 0;
7656
+ let agentCount = 0;
7657
+ try {
7658
+ const psOutput = execSync13(
7659
+ 'ps aux | grep -E "[c]laude" | head -15',
7660
+ { encoding: "utf-8", timeout: 5e3 }
7661
+ );
7662
+ const lines = psOutput.trim().split("\n").filter(Boolean);
7663
+ for (const line of lines) {
7664
+ const parts = line.trim().split(/\s+/);
7665
+ if (parts.length >= 11) {
7666
+ const pid = parts[1];
7667
+ const cpu = parts[2];
7668
+ const mem = parts[3];
7669
+ const time = parts[9];
7670
+ const cmd = parts.slice(10).join(" ").substring(0, 30);
7671
+ const cpuNum = parseFloat(cpu);
7672
+ const status = cpuNum > 5 ? "active" : "idle";
7673
+ if (cmd.includes("squads-")) {
7674
+ agentCount++;
7675
+ }
7676
+ claudeCount++;
7677
+ processes.push({ pid, cpu, mem, time, status, cmd });
7678
+ }
7679
+ }
7680
+ } catch {
7681
+ }
7682
+ try {
7683
+ const tmuxOutput = execSync13("tmux ls 2>/dev/null | grep squads- | wc -l", { encoding: "utf-8" });
7684
+ agentCount = parseInt(tmuxOutput.trim()) || 0;
7685
+ } catch {
7686
+ }
7687
+ return {
7688
+ processes: processes.slice(0, 12),
7689
+ summary: { claude: claudeCount, tasks: 0, agents: agentCount }
7690
+ };
7691
+ }
7692
+ function render(data) {
7693
+ const now = (/* @__PURE__ */ new Date()).toLocaleTimeString();
7694
+ const lines = [];
7695
+ lines.push("");
7696
+ lines.push(` ${MAGENTA}squads${RESET2} ${DIM}top${RESET2} ${DIM}${now}${RESET2} ${DIM}(q to quit)${RESET2}`);
7697
+ lines.push("");
7698
+ const activeCount = data.processes.filter((p) => p.status === "active").length;
7699
+ lines.push(` ${CYAN}${data.summary.claude}${RESET2} claude ${DIM}\u2502${RESET2} ${GREEN}${activeCount}${RESET2} active ${DIM}\u2502${RESET2} ${YELLOW}${data.summary.agents}${RESET2} agents`);
7700
+ lines.push("");
7701
+ lines.push(` ${BOLD}PID CPU% MEM% TIME STATUS${RESET2}`);
7702
+ lines.push(` ${DIM}${"\u2500".repeat(50)}${RESET2}`);
7703
+ if (data.processes.length === 0) {
7704
+ lines.push(` ${DIM}(no claude processes)${RESET2}`);
7705
+ } else {
7706
+ for (const p of data.processes) {
7707
+ const statusIcon = p.status === "active" ? `${GREEN}\u25CF${RESET2}` : `${DIM}\u25CB${RESET2}`;
7708
+ const statusText = p.status === "active" ? `${GREEN}active${RESET2}` : `${DIM}idle${RESET2}`;
7709
+ const cpuColor = parseFloat(p.cpu) > 10 ? YELLOW : "";
7710
+ lines.push(
7711
+ ` ${CYAN}${p.pid.padEnd(8)}${RESET2}${cpuColor}${p.cpu.padStart(5)}${RESET2} ${p.mem.padStart(5)} ${p.time.padEnd(10)}${statusIcon} ${statusText}`
7712
+ );
7713
+ }
7714
+ }
7715
+ lines.push("");
7716
+ lines.push(` ${DIM}Press q to quit, r to refresh${RESET2}`);
7717
+ lines.push("");
7718
+ process.stdout.write(CURSOR_HOME);
7719
+ for (const line of lines) {
7720
+ process.stdout.write(line + `${ESC}[K
7721
+ `);
7722
+ }
7723
+ for (let i = 0; i < 5; i++) {
7724
+ process.stdout.write(`${ESC}[K
7725
+ `);
7726
+ }
7727
+ }
7728
+ function sleep2(ms) {
7729
+ return new Promise((resolve) => setTimeout(resolve, ms));
7730
+ }
7731
+ if (process.stdin.isTTY) {
7732
+ process.stdin.setRawMode(true);
7733
+ process.stdin.resume();
7734
+ process.stdin.on("data", (key) => {
7735
+ if (key.toString() === "q" || key[0] === 3) {
7736
+ process.stdout.write(CURSOR_SHOW + "\n");
7737
+ process.exit(0);
7738
+ }
7739
+ });
7740
+ }
7741
+
5504
7742
  // src/commands/session.ts
5505
7743
  async function sessionStartCommand(options = {}) {
5506
7744
  cleanupStaleSessions();
@@ -5545,9 +7783,10 @@ async function detectSquadCommand() {
5545
7783
 
5546
7784
  // src/commands/trigger.ts
5547
7785
  import chalk3 from "chalk";
7786
+ import { existsSync as existsSync16 } from "fs";
5548
7787
  var SCHEDULER_URL = process.env.SCHEDULER_URL || "http://localhost:8090";
5549
- async function fetchScheduler(path2, options) {
5550
- const res = await fetch(`${SCHEDULER_URL}${path2}`, {
7788
+ async function fetchScheduler(path3, options) {
7789
+ const res = await fetch(`${SCHEDULER_URL}${path3}`, {
5551
7790
  ...options,
5552
7791
  headers: {
5553
7792
  "Content-Type": "application/json",
@@ -5590,11 +7829,13 @@ async function listTriggers(squad) {
5590
7829
  }
5591
7830
  async function syncTriggers() {
5592
7831
  console.log(chalk3.gray("Syncing triggers from SQUAD.md files...\n"));
5593
- const { execSync: execSync11 } = await import("child_process");
7832
+ const { execSync: execSync15 } = await import("child_process");
5594
7833
  const hqPath = process.env.HQ_PATH || `${process.env.HOME}/agents-squads/hq`;
5595
7834
  try {
5596
- const output = execSync11(
5597
- `python ${hqPath}/squads-scheduler/sync_triggers.py`,
7835
+ const venvPython = `${hqPath}/squads-scheduler/.venv/bin/python`;
7836
+ const pythonCmd = existsSync16(venvPython) ? venvPython : "python3";
7837
+ const output = execSync15(
7838
+ `${pythonCmd} ${hqPath}/squads-scheduler/sync_triggers.py`,
5598
7839
  { encoding: "utf-8", cwd: hqPath }
5599
7840
  );
5600
7841
  console.log(output);
@@ -5674,14 +7915,336 @@ function registerTriggerCommand(program2) {
5674
7915
  });
5675
7916
  }
5676
7917
 
7918
+ // src/commands/tonight.ts
7919
+ import ora6 from "ora";
7920
+ import fs2 from "fs/promises";
7921
+ import path2, { dirname as dirname5 } from "path";
7922
+ import { execSync as execSync14, spawn as spawn7 } from "child_process";
7923
+ function getProjectRoot2() {
7924
+ const squadsDir = findSquadsDir();
7925
+ if (squadsDir) {
7926
+ return dirname5(dirname5(squadsDir));
7927
+ }
7928
+ return process.cwd();
7929
+ }
7930
+ var TONIGHT_STATE_FILE = ".agents/tonight-state.json";
7931
+ var TONIGHT_LOG_DIR = ".agents/outputs/tonight";
7932
+ async function getCurrentCost() {
7933
+ try {
7934
+ const response = await fetch("http://localhost:8088/api/stats/today");
7935
+ if (response.ok) {
7936
+ const data = await response.json();
7937
+ return data.cost_usd || 0;
7938
+ }
7939
+ } catch {
7940
+ }
7941
+ return 0;
7942
+ }
7943
+ function killAllSessions() {
7944
+ try {
7945
+ const sessions2 = execSync14('tmux ls 2>/dev/null | grep "squads-tonight-" | cut -d: -f1', { encoding: "utf-8" }).trim().split("\n").filter(Boolean);
7946
+ for (const session2 of sessions2) {
7947
+ try {
7948
+ execSync14(`tmux kill-session -t "${session2}"`, { stdio: "ignore" });
7949
+ } catch {
7950
+ }
7951
+ }
7952
+ return sessions2.length;
7953
+ } catch {
7954
+ return 0;
7955
+ }
7956
+ }
7957
+ function getRunningSessionCount() {
7958
+ try {
7959
+ const output = execSync14('tmux ls 2>/dev/null | grep "squads-tonight-" | wc -l', { encoding: "utf-8" });
7960
+ return parseInt(output.trim()) || 0;
7961
+ } catch {
7962
+ return 0;
7963
+ }
7964
+ }
7965
+ function launchAgent(target, projectRoot, sessionId, logFile) {
7966
+ const sessionName = `squads-tonight-${sessionId}`;
7967
+ const claudeCmd = [
7968
+ `cd '${projectRoot}'`,
7969
+ `echo "=== Tonight Session: ${target} ===" >> '${logFile}'`,
7970
+ `echo "Started: $(date)" >> '${logFile}'`,
7971
+ `claude --dangerously-skip-permissions -p 'You are running as part of an overnight autonomous session. Execute your tasks efficiently. If you encounter errors, document them clearly and move on. Do not ask for user input.' -- "Run squad: ${target}" 2>&1 | tee -a '${logFile}'`,
7972
+ `echo "Ended: $(date)" >> '${logFile}'`
7973
+ ].join(" && ");
7974
+ spawn7("tmux", [
7975
+ "new-session",
7976
+ "-d",
7977
+ "-s",
7978
+ sessionName,
7979
+ "-x",
7980
+ "200",
7981
+ "-y",
7982
+ "50",
7983
+ "/bin/sh",
7984
+ "-c",
7985
+ claudeCmd
7986
+ ], {
7987
+ stdio: "ignore",
7988
+ detached: true
7989
+ }).unref();
7990
+ return sessionName;
7991
+ }
7992
+ async function generateReport(state, projectRoot) {
7993
+ const duration = Math.round((Date.now() - new Date(state.startedAt).getTime()) / 1e3 / 60);
7994
+ const completed = state.sessions.filter((s) => s.status === "completed").length;
7995
+ const failed = state.sessions.filter((s) => s.status === "failed").length;
7996
+ const report = `# Tonight Report - ${(/* @__PURE__ */ new Date()).toLocaleDateString()}
7997
+
7998
+ ## Summary
7999
+ - **Duration**: ${duration} minutes
8000
+ - **Total Cost**: $${state.totalCost.toFixed(2)} / $${state.costCap} cap
8001
+ - **Sessions**: ${completed} completed, ${failed} failed, ${state.sessions.length} total
8002
+ - **Stopped**: ${state.stoppedReason || "All tasks completed"}
8003
+
8004
+ ## Sessions
8005
+
8006
+ | Target | Status | Restarts | Duration |
8007
+ |--------|--------|----------|----------|
8008
+ ${state.sessions.map((s) => {
8009
+ const dur = s.startedAt ? Math.round((Date.now() - new Date(s.startedAt).getTime()) / 1e3 / 60) : 0;
8010
+ return `| ${s.target} | ${s.status} | ${s.restarts} | ${dur}min |`;
8011
+ }).join("\n")}
8012
+
8013
+ ## Logs
8014
+ See \`.agents/outputs/tonight/\` for detailed logs.
8015
+
8016
+ ---
8017
+ *Generated by squads tonight*
8018
+ `;
8019
+ const reportPath = path2.join(projectRoot, TONIGHT_LOG_DIR, `report-${Date.now()}.md`);
8020
+ await fs2.writeFile(reportPath, report);
8021
+ return reportPath;
8022
+ }
8023
+ async function tonightCommand(targets, options) {
8024
+ const costCap = options.costCap ?? 50;
8025
+ const stopAt = options.stopAt ?? "07:00";
8026
+ const maxRetries = options.maxRetries ?? 3;
8027
+ const dryRun = options.dryRun ?? false;
8028
+ const verbose = options.verbose ?? false;
8029
+ writeLine();
8030
+ writeLine(` ${bold}squads tonight${RESET}`);
8031
+ writeLine();
8032
+ const projectRoot = getProjectRoot2();
8033
+ if (targets.length === 0) {
8034
+ writeLine(` ${colors.red}\u2716${RESET} No targets specified`);
8035
+ writeLine();
8036
+ writeLine(` ${colors.dim}Usage: squads tonight <squad> [squad/agent...]${RESET}`);
8037
+ writeLine(` ${colors.dim}Example: squads tonight intelligence customer/outreach${RESET}`);
8038
+ writeLine();
8039
+ return;
8040
+ }
8041
+ writeLine(` ${colors.dim}Config:${RESET}`);
8042
+ writeLine(` Cost cap: ${colors.cyan}$${costCap}${RESET}`);
8043
+ writeLine(` Stop at: ${colors.cyan}${stopAt}${RESET}`);
8044
+ writeLine(` Max retries: ${colors.cyan}${maxRetries}${RESET}`);
8045
+ writeLine(` Targets: ${colors.cyan}${targets.join(", ")}${RESET}`);
8046
+ writeLine();
8047
+ if (dryRun) {
8048
+ writeLine(` ${colors.yellow}DRY RUN${RESET} - would launch:`);
8049
+ for (const target of targets) {
8050
+ writeLine(` \u2022 ${target}`);
8051
+ }
8052
+ writeLine();
8053
+ return;
8054
+ }
8055
+ const logDir = path2.join(projectRoot, TONIGHT_LOG_DIR);
8056
+ await fs2.mkdir(logDir, { recursive: true });
8057
+ const state = {
8058
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
8059
+ stopAt,
8060
+ costCap,
8061
+ maxRetries,
8062
+ sessions: [],
8063
+ totalCost: await getCurrentCost(),
8064
+ stopped: false
8065
+ };
8066
+ const spinner = ora6("Launching agents...").start();
8067
+ for (const target of targets) {
8068
+ const sessionId = `${target.replace("/", "-")}-${Date.now()}`;
8069
+ const logFile = path2.join(logDir, `${sessionId}.log`);
8070
+ const tmuxSession = launchAgent(target, projectRoot, sessionId, logFile);
8071
+ state.sessions.push({
8072
+ id: sessionId,
8073
+ target,
8074
+ tmuxSession,
8075
+ startedAt: /* @__PURE__ */ new Date(),
8076
+ status: "running",
8077
+ restarts: 0
8078
+ });
8079
+ }
8080
+ spinner.succeed(`Launched ${targets.length} agent(s)`);
8081
+ await fs2.writeFile(
8082
+ path2.join(projectRoot, TONIGHT_STATE_FILE),
8083
+ JSON.stringify(state, null, 2)
8084
+ );
8085
+ await track(Events.CLI_TONIGHT, {
8086
+ targets: targets.length,
8087
+ costCap,
8088
+ stopAt
8089
+ });
8090
+ writeLine();
8091
+ writeLine(` ${colors.green}\u2713${RESET} Tonight mode active`);
8092
+ writeLine();
8093
+ writeLine(` ${colors.dim}Monitor:${RESET}`);
8094
+ writeLine(` ${colors.cyan}squads tonight status${RESET} - Check progress`);
8095
+ writeLine(` ${colors.cyan}squads tonight stop${RESET} - Kill all agents`);
8096
+ writeLine(` ${colors.cyan}tmux ls | grep tonight${RESET} - List sessions`);
8097
+ writeLine();
8098
+ writeLine(` ${colors.dim}Logs: ${logDir}${RESET}`);
8099
+ writeLine();
8100
+ const watcherCmd = `
8101
+ while true; do
8102
+ sleep 60
8103
+ # Check cost
8104
+ COST=$(curl -s http://localhost:8088/api/stats/today 2>/dev/null | jq -r '.cost_usd // 0')
8105
+ if [ "$(echo "$COST >= ${costCap}" | bc)" -eq 1 ]; then
8106
+ echo "Cost cap reached: $COST" >> '${logDir}/watcher.log'
8107
+ tmux ls 2>/dev/null | grep squads-tonight | cut -d: -f1 | xargs -I{} tmux kill-session -t {}
8108
+ break
8109
+ fi
8110
+ # Check time
8111
+ HOUR=$(date +%H)
8112
+ MIN=$(date +%M)
8113
+ STOP_HOUR=${stopAt.split(":")[0]}
8114
+ STOP_MIN=${stopAt.split(":")[1]}
8115
+ if [ "$HOUR" -ge "$STOP_HOUR" ] && [ "$MIN" -ge "$STOP_MIN" ]; then
8116
+ echo "Stop time reached" >> '${logDir}/watcher.log'
8117
+ tmux ls 2>/dev/null | grep squads-tonight | cut -d: -f1 | xargs -I{} tmux kill-session -t {}
8118
+ break
8119
+ fi
8120
+ # Check if any sessions still running
8121
+ COUNT=$(tmux ls 2>/dev/null | grep squads-tonight | wc -l)
8122
+ if [ "$COUNT" -eq 0 ]; then
8123
+ echo "All sessions completed" >> '${logDir}/watcher.log'
8124
+ break
8125
+ fi
8126
+ done
8127
+ `;
8128
+ spawn7("tmux", [
8129
+ "new-session",
8130
+ "-d",
8131
+ "-s",
8132
+ "squads-tonight-watcher",
8133
+ "/bin/sh",
8134
+ "-c",
8135
+ watcherCmd
8136
+ ], {
8137
+ stdio: "ignore",
8138
+ detached: true
8139
+ }).unref();
8140
+ if (verbose) {
8141
+ writeLine(` ${colors.dim}Watcher session started${RESET}`);
8142
+ }
8143
+ }
8144
+ async function tonightStatusCommand() {
8145
+ writeLine();
8146
+ writeLine(` ${bold}Tonight Status${RESET}`);
8147
+ writeLine();
8148
+ const projectRoot = getProjectRoot2();
8149
+ const statePath = path2.join(projectRoot, TONIGHT_STATE_FILE);
8150
+ let state = null;
8151
+ try {
8152
+ const content = await fs2.readFile(statePath, "utf-8");
8153
+ state = JSON.parse(content);
8154
+ } catch {
8155
+ }
8156
+ const running = getRunningSessionCount();
8157
+ const cost = await getCurrentCost();
8158
+ if (running === 0 && !state) {
8159
+ writeLine(` ${colors.dim}No tonight session active${RESET}`);
8160
+ writeLine();
8161
+ writeLine(` ${colors.dim}Start with: squads tonight <squad> [squad...]${RESET}`);
8162
+ writeLine();
8163
+ return;
8164
+ }
8165
+ writeLine(` ${colors.cyan}${running}${RESET} agents running`);
8166
+ if (state) {
8167
+ const duration = Math.round((Date.now() - new Date(state.startedAt).getTime()) / 1e3 / 60);
8168
+ writeLine(` ${colors.dim}Duration:${RESET} ${duration} minutes`);
8169
+ writeLine(` ${colors.dim}Cost:${RESET} $${cost.toFixed(2)} / $${state.costCap} cap`);
8170
+ writeLine(` ${colors.dim}Stop at:${RESET} ${state.stopAt}`);
8171
+ }
8172
+ writeLine();
8173
+ try {
8174
+ const sessions2 = execSync14("tmux ls 2>/dev/null | grep squads-tonight", { encoding: "utf-8" }).trim().split("\n").filter(Boolean);
8175
+ if (sessions2.length > 0) {
8176
+ writeLine(` ${colors.dim}Sessions:${RESET}`);
8177
+ for (const session2 of sessions2) {
8178
+ const name = session2.split(":")[0];
8179
+ writeLine(` \u2022 ${name}`);
8180
+ }
8181
+ writeLine();
8182
+ writeLine(` ${colors.dim}Attach: tmux attach -t <session>${RESET}`);
8183
+ }
8184
+ } catch {
8185
+ }
8186
+ writeLine();
8187
+ }
8188
+ async function tonightStopCommand() {
8189
+ writeLine();
8190
+ writeLine(` ${bold}Stopping Tonight Mode${RESET}`);
8191
+ writeLine();
8192
+ const killed = killAllSessions();
8193
+ if (killed > 0) {
8194
+ writeLine(` ${colors.green}\u2713${RESET} Killed ${killed} session(s)`);
8195
+ } else {
8196
+ writeLine(` ${colors.dim}No sessions to kill${RESET}`);
8197
+ }
8198
+ const projectRoot = getProjectRoot2();
8199
+ const statePath = path2.join(projectRoot, TONIGHT_STATE_FILE);
8200
+ try {
8201
+ const content = await fs2.readFile(statePath, "utf-8");
8202
+ const state = JSON.parse(content);
8203
+ state.stopped = true;
8204
+ state.stoppedReason = "Manual stop";
8205
+ state.totalCost = await getCurrentCost();
8206
+ const reportPath = await generateReport(state, projectRoot);
8207
+ writeLine(` ${colors.green}\u2713${RESET} Report: ${reportPath}`);
8208
+ await fs2.unlink(statePath).catch(() => {
8209
+ });
8210
+ } catch {
8211
+ }
8212
+ writeLine();
8213
+ }
8214
+ async function tonightReportCommand() {
8215
+ writeLine();
8216
+ writeLine(` ${bold}Tonight Report${RESET}`);
8217
+ writeLine();
8218
+ const projectRoot = getProjectRoot2();
8219
+ const logDir = path2.join(projectRoot, TONIGHT_LOG_DIR);
8220
+ try {
8221
+ const files = await fs2.readdir(logDir);
8222
+ const reports = files.filter((f) => f.startsWith("report-")).sort().reverse();
8223
+ if (reports.length === 0) {
8224
+ writeLine(` ${colors.dim}No reports found${RESET}`);
8225
+ writeLine();
8226
+ return;
8227
+ }
8228
+ const latest = reports[0];
8229
+ const content = await fs2.readFile(path2.join(logDir, latest), "utf-8");
8230
+ writeLine(content);
8231
+ } catch {
8232
+ writeLine(` ${colors.dim}No reports found${RESET}`);
8233
+ }
8234
+ writeLine();
8235
+ }
8236
+
5677
8237
  // src/cli.ts
8238
+ if (!process.stdout.isTTY) {
8239
+ chalk4.level = 0;
8240
+ }
5678
8241
  var envPaths = [
5679
- join15(process.cwd(), ".env"),
5680
- join15(process.cwd(), "..", "hq", ".env"),
5681
- join15(homedir6(), "agents-squads", "hq", ".env")
8242
+ join17(process.cwd(), ".env"),
8243
+ join17(process.cwd(), "..", "hq", ".env"),
8244
+ join17(homedir5(), "agents-squads", "hq", ".env")
5682
8245
  ];
5683
8246
  for (const envPath of envPaths) {
5684
- if (existsSync15(envPath)) {
8247
+ if (existsSync17(envPath)) {
5685
8248
  config({ path: envPath, quiet: true });
5686
8249
  break;
5687
8250
  }
@@ -5689,9 +8252,19 @@ for (const envPath of envPaths) {
5689
8252
  applyStackConfig();
5690
8253
  registerExitHandler();
5691
8254
  var program = new Command();
5692
- program.name("squads").description("A CLI for humans and agents").version(version);
5693
- program.command("init").description("Initialize a new squad project").option("-t, --template <template>", "Project template", "default").action(initCommand);
5694
- program.command("run <target>").description("Run a squad or agent").option("-v, --verbose", "Verbose output").option("-d, --dry-run", "Show what would be run without executing").option("-e, --execute", "Execute agent via Claude CLI (requires claude installed)").option("-a, --agent <agent>", "Run specific agent within squad").option("-t, --timeout <minutes>", "Execution timeout in minutes (default: 30)", "30").action((target, options) => runCommand(target, { ...options, timeout: parseInt(options.timeout, 10) }));
8255
+ program.name("squads").description("A CLI for humans and agents").version(version).showSuggestionAfterError(true).configureOutput({
8256
+ outputError: (str, write) => write(str)
8257
+ }).exitOverride((err) => {
8258
+ if (err.code === "commander.helpDisplayed" || err.code === "commander.version") {
8259
+ process.exit(0);
8260
+ }
8261
+ if (err.exitCode !== void 0) {
8262
+ process.exit(err.exitCode);
8263
+ }
8264
+ throw err;
8265
+ });
8266
+ program.command("init").description("Initialize a new squad project").option("-t, --template <template>", "Project template", "default").option("--skip-infra", "Skip infrastructure setup prompt").action(initCommand);
8267
+ program.command("run <target>").description("Run a squad or agent").option("-v, --verbose", "Verbose output").option("-d, --dry-run", "Show what would be run without executing").option("-e, --execute", "Execute agent via Claude CLI (requires claude installed)").option("-a, --agent <agent>", "Run specific agent within squad").option("-t, --timeout <minutes>", "Execution timeout in minutes (default: 30)", "30").option("-p, --parallel", "Run all agents in parallel (N tmux sessions)").option("-l, --lead", "Lead mode: single orchestrator using Task tool for parallelization").option("-f, --foreground", "Run in foreground (no tmux, blocks terminal)").option("--use-api", "Use API credits instead of subscription").action((target, options) => runCommand(target, { ...options, timeout: parseInt(options.timeout, 10) }));
5695
8268
  program.command("list").description("List agents and squads").option("-s, --squads", "List squads only").option("-a, --agents", "List agents only").action(listCommand);
5696
8269
  program.command("status [squad]").description("Show squad status and state").option("-v, --verbose", "Show detailed status").action(statusCommand);
5697
8270
  program.command("dashboard").alias("dash").description("Show comprehensive goals and metrics dashboard").option("-v, --verbose", "Show additional details").option("-c, --ceo", "Executive summary with priorities and blockers").option("-f, --full", "Include GitHub PR/issue stats (slower, ~30s)").action((options) => dashboardCommand({ ...options, fast: !options.full }));
@@ -5702,8 +8275,19 @@ var progress = program.command("progress").description("Track active and complet
5702
8275
  progress.command("start <squad> <description>").description("Register a new active task").action(progressStartCommand);
5703
8276
  progress.command("complete <taskId>").description("Mark a task as completed").option("-f, --failed", "Mark as failed instead").action(progressCompleteCommand);
5704
8277
  program.command("results [squad]").description("Show squad results: git activity + KPI goals vs actuals").option("-d, --days <days>", "Days to look back", "7").option("-v, --verbose", "Show detailed KPIs per goal").action((squad, options) => resultsCommand({ ...options, squad }));
8278
+ program.command("history").description("Show recent agent execution history").option("-d, --days <days>", "Days to look back", "7").option("-s, --squad <squad>", "Filter by squad").option("-v, --verbose", "Show cost and token details").option("-j, --json", "Output as JSON").action((options) => historyCommand(options));
8279
+ program.command("context-feed").alias("feed").description("Context feed for agents: goals, memory, costs, activity").option("-s, --squad <squad>", "Focus on specific squad").option("-t, --topic <topic>", "Search memory for relevant context").option("-a, --agent", "Output JSON for agent consumption").option("-j, --json", "Output as JSON (alias for --agent)").option("-v, --verbose", "Show additional details").action((options) => contextFeedCommand(options));
5705
8280
  program.command("workers").description("Show active workers: Claude sessions, tasks, dev servers").option("-v, --verbose", "Show more details").option("-k, --kill <pid>", "Kill a process by PID").action(workersCommand);
5706
- var memory = program.command("memory").description("Query and manage squad memory");
8281
+ program.command("health").description("Quick health check for all infrastructure services").option("-v, --verbose", "Show optional services").action((options) => healthCommand(options));
8282
+ program.command("watch <command> [args...]").description("Live refresh any squads command (like Unix watch)").option("-n, --interval <seconds>", "Refresh interval in seconds", "2").option("--no-clear", "Don't clear screen between refreshes").action((command, args, options) => watchCommand(command, args, {
8283
+ interval: parseInt(options.interval, 10),
8284
+ clear: options.clear
8285
+ }));
8286
+ program.command("live").description("Live TUI dashboard with real-time metrics (like htop)").option("-m, --minimal", "Minimal view").option("-f, --focus <panel>", "Focus on specific panel (agents, cost, activity, memory)").action((options) => liveCommand(options));
8287
+ program.command("top").description("Live process table (like Unix top) - numbers update in place").action(() => topCommand());
8288
+ var memory = program.command("memory").description("Query and manage squad memory").action(() => {
8289
+ memory.outputHelp();
8290
+ });
5707
8291
  memory.command("query <query>").description("Search across all squad memory").option("-s, --squad <squad>", "Limit search to specific squad").option("-a, --agent <agent>", "Limit search to specific agent").action(memoryQueryCommand);
5708
8292
  memory.command("show <squad>").description("Show memory for a squad").action(memoryShowCommand);
5709
8293
  memory.command("update <squad> <content>").description("Add to squad memory").option("-a, --agent <agent>", "Specific agent (default: squad-lead)").option("-t, --type <type>", "Memory type: state, learnings, feedback", "learnings").action(memoryUpdateCommand);
@@ -5719,7 +8303,9 @@ memory.command("extract").description("Extract memories from recent conversation
5719
8303
  hours: parseInt(opts.hours, 10),
5720
8304
  dryRun: opts.dryRun
5721
8305
  }));
5722
- var goal = program.command("goal").description("Manage squad goals");
8306
+ var goal = program.command("goal").description("Manage squad goals").action(() => {
8307
+ goal.outputHelp();
8308
+ });
5723
8309
  goal.command("set <squad> <description>").description("Set a goal for a squad").option("-m, --metric <metrics...>", "Metrics to track").action(goalSetCommand);
5724
8310
  goal.command("list [squad]").description("List goals for squad(s)").option("-a, --all", "Show completed goals too").action(goalListCommand);
5725
8311
  goal.command("complete <squad> <index>").description("Mark a goal as completed").action(goalCompleteCommand);
@@ -5735,11 +8321,11 @@ sessions.command("history").description("Show session history and statistics").o
5735
8321
  json: options.json
5736
8322
  }));
5737
8323
  sessions.command("summary").description("Show pretty session summary (auto-detects current session or pass JSON)").option("-d, --data <json>", "JSON data for summary (overrides auto-detection)").option("-f, --file <path>", "Path to JSON file with summary data").option("-j, --json", "Output as JSON instead of pretty format").action(async (options) => {
5738
- const { buildCurrentSessionSummary } = await import("./sessions-UR3YGSLR.js");
8324
+ const { buildCurrentSessionSummary } = await import("./sessions-JCQ34BEU.js");
5739
8325
  let data;
5740
8326
  if (options.file) {
5741
- const { readFileSync: readFileSync12 } = await import("fs");
5742
- data = JSON.parse(readFileSync12(options.file, "utf-8"));
8327
+ const { readFileSync: readFileSync13 } = await import("fs");
8328
+ data = JSON.parse(readFileSync13(options.file, "utf-8"));
5743
8329
  } else if (options.data) {
5744
8330
  data = JSON.parse(options.data);
5745
8331
  } else if (!process.stdin.isTTY) {
@@ -5772,11 +8358,67 @@ stack.command("down").description("Stop Docker containers").action(stackDownComm
5772
8358
  stack.command("health").description("Comprehensive health check with diagnostics").option("-v, --verbose", "Show logs for unhealthy services").action((options) => stackHealthCommand(options.verbose));
5773
8359
  stack.command("logs <service>").description("Show logs for a service (postgres, redis, neo4j, bridge, langfuse, mem0, engram)").option("-n, --tail <lines>", "Number of lines to show", "50").action((service, options) => stackLogsCommand(service, parseInt(options.tail, 10)));
5774
8360
  registerTriggerCommand(program);
8361
+ var tonight = program.command("tonight").description("Run agents autonomously overnight with safety limits");
8362
+ tonight.command("run <targets...>").description("Start tonight mode with specified squads/agents").option("-c, --cost-cap <usd>", "Max USD to spend (default: 50)", "50").option("-s, --stop-at <time>", "Stop time HH:MM (default: 07:00)", "07:00").option("-r, --max-retries <n>", "Max restarts per agent (default: 3)", "3").option("-d, --dry-run", "Show what would run without executing").option("-v, --verbose", "Verbose output").action((targets, options) => tonightCommand(targets, {
8363
+ costCap: parseFloat(options.costCap),
8364
+ stopAt: options.stopAt,
8365
+ maxRetries: parseInt(options.maxRetries, 10),
8366
+ dryRun: options.dryRun,
8367
+ verbose: options.verbose
8368
+ }));
8369
+ tonight.command("status").description("Check tonight mode status").action(tonightStatusCommand);
8370
+ tonight.command("stop").description("Stop all tonight agents and generate report").action(tonightStopCommand);
8371
+ tonight.command("report").description("Show latest tonight report").action(tonightReportCommand);
5775
8372
  program.command("login").description("Log in to Squads (Pro & Enterprise)").action(loginCommand);
5776
8373
  program.command("logout").description("Log out from Squads").action(logoutCommand);
5777
8374
  program.command("whoami").description("Show current logged in user").action(whoamiCommand);
5778
8375
  program.command("update").description("Check for and install updates").option("-y, --yes", "Auto-confirm update without prompting").option("-c, --check", "Check for updates without installing").action((options) => updateCommand(options));
5779
- await program.parseAsync();
8376
+ program.command("version").description("Show version information").action(() => {
8377
+ console.log(`squads-cli ${version}`);
8378
+ });
8379
+ function handleError(error) {
8380
+ const err = error instanceof Error ? error : new Error(String(error));
8381
+ if (err.message.includes("ECONNREFUSED") || err.message.includes("fetch failed")) {
8382
+ console.error(chalk4.red("\nConnection error:"), err.message);
8383
+ console.error(chalk4.dim("\nPossible fixes:"));
8384
+ console.error(chalk4.dim(" 1. Check if Docker containers are running: squads stack status"));
8385
+ console.error(chalk4.dim(" 2. Start the stack: squads stack up"));
8386
+ console.error(chalk4.dim(" 3. Check your network connection"));
8387
+ } else if (err.message.includes("ENOENT")) {
8388
+ console.error(chalk4.red("\nFile not found:"), err.message);
8389
+ console.error(chalk4.dim("\nPossible fixes:"));
8390
+ console.error(chalk4.dim(" 1. Make sure you are in the correct directory"));
8391
+ console.error(chalk4.dim(" 2. Initialize the project: squads init"));
8392
+ } else if (err.message.includes("permission denied") || err.message.includes("EACCES")) {
8393
+ console.error(chalk4.red("\nPermission denied:"), err.message);
8394
+ console.error(chalk4.dim("\nPossible fixes:"));
8395
+ console.error(chalk4.dim(" 1. Check file permissions"));
8396
+ console.error(chalk4.dim(" 2. Avoid running with sudo if not needed"));
8397
+ } else if (err.message.includes("rate limit") || err.message.includes("429")) {
8398
+ console.error(chalk4.red("\nRate limit exceeded"));
8399
+ console.error(chalk4.dim("\nPossible fixes:"));
8400
+ console.error(chalk4.dim(" 1. Wait a few minutes and try again"));
8401
+ console.error(chalk4.dim(" 2. Check your API usage: squads dash"));
8402
+ } else {
8403
+ console.error(chalk4.red("\nError:"), err.message);
8404
+ if (process.env.DEBUG || process.env.VERBOSE) {
8405
+ console.error(chalk4.dim("\nStack trace:"));
8406
+ console.error(chalk4.dim(err.stack));
8407
+ } else {
8408
+ console.error(chalk4.dim("\nRun with DEBUG=1 for more details"));
8409
+ }
8410
+ }
8411
+ console.error(chalk4.dim("\nIf this persists, please report at:"));
8412
+ console.error(chalk4.cyan(" https://github.com/agents-squads/squads-cli/issues\n"));
8413
+ process.exit(1);
8414
+ }
8415
+ process.on("uncaughtException", handleError);
8416
+ process.on("unhandledRejection", handleError);
8417
+ try {
8418
+ await program.parseAsync();
8419
+ } catch (error) {
8420
+ handleError(error);
8421
+ }
5780
8422
  if (!process.argv.slice(2).length) {
5781
8423
  console.log(`
5782
8424
  ${chalk4.bold.magenta("squads")} - AI agent squad management