squads-cli 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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,36 @@ 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 { existsSync as existsSync17 } from "fs";
39
+ import { join as join17 } from "path";
38
40
  import { homedir as homedir6 } 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
+ var version = "0.3.0";
44
46
 
45
47
  // src/commands/init.ts
46
48
  import chalk from "chalk";
47
49
  import ora from "ora";
48
50
  import fs from "fs/promises";
49
51
  import path from "path";
52
+ import { execSync as execSync2, spawn } from "child_process";
53
+ import { createInterface } from "readline";
50
54
 
51
55
  // src/lib/git.ts
52
- import { execSync } from "child_process";
56
+ import { execSync, exec } from "child_process";
53
57
  import { existsSync } from "fs";
54
58
  import { join } from "path";
59
+ import { promisify } from "util";
60
+ var execAsync = promisify(exec);
55
61
  function checkGitStatus(cwd = process.cwd()) {
56
62
  const status = {
57
63
  isGitRepo: false,
@@ -64,34 +70,34 @@ function checkGitStatus(cwd = process.cwd()) {
64
70
  }
65
71
  status.isGitRepo = true;
66
72
  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];
73
+ const combined = execSync(
74
+ 'echo "BRANCH:" && git rev-parse --abbrev-ref HEAD && echo "REMOTES:" && git remote -v && echo "STATUS:" && git status --porcelain',
75
+ { cwd, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 5e3 }
76
+ );
77
+ const branchMatch = combined.match(/BRANCH:\n(.+)\n/);
78
+ if (branchMatch) {
79
+ status.branch = branchMatch[1].trim();
80
+ }
81
+ const remotesMatch = combined.match(/REMOTES:\n([\s\S]*?)STATUS:/);
82
+ if (remotesMatch) {
83
+ const remotes = remotesMatch[1].trim();
84
+ if (remotes) {
85
+ status.hasRemote = true;
86
+ const lines = remotes.split("\n");
87
+ if (lines.length > 0) {
88
+ const parts = lines[0].split(/\s+/);
89
+ status.remoteName = parts[0];
90
+ status.remoteUrl = parts[1];
91
+ }
85
92
  }
86
93
  }
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;
94
+ const statusMatch = combined.match(/STATUS:\n([\s\S]*?)$/);
95
+ if (statusMatch) {
96
+ const statusOutput = statusMatch[1].trim();
97
+ if (statusOutput) {
98
+ status.isDirty = true;
99
+ status.uncommittedCount = statusOutput.split("\n").filter((l) => l.trim()).length;
100
+ }
95
101
  }
96
102
  } catch {
97
103
  }
@@ -294,7 +300,7 @@ function getMultiRepoGitStats(basePath, days = 30) {
294
300
  const authors = /* @__PURE__ */ new Set();
295
301
  let lastCommit = "";
296
302
  for (const line of commits) {
297
- const [hash, author, date, message] = line.split("|");
303
+ const [hash, author, date] = line.split("|");
298
304
  if (!hash) continue;
299
305
  stats.totalCommits++;
300
306
  authors.add(author);
@@ -335,9 +341,6 @@ function getActivitySparkline(basePath, days = 7) {
335
341
  const activity = [];
336
342
  const now = /* @__PURE__ */ new Date();
337
343
  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
344
  activity.push(0);
342
345
  }
343
346
  for (const repo of SQUAD_REPOS) {
@@ -480,6 +483,7 @@ var Events = {
480
483
  CLI_STATUS: "cli.status",
481
484
  CLI_DASHBOARD: "cli.dashboard",
482
485
  CLI_WORKERS: "cli.workers",
486
+ CLI_TONIGHT: "cli.tonight",
483
487
  // Goals
484
488
  CLI_GOAL_SET: "cli.goal.set",
485
489
  CLI_GOAL_LIST: "cli.goal.list",
@@ -508,7 +512,7 @@ function registerExitHandler() {
508
512
  await flushEvents();
509
513
  }
510
514
  });
511
- const signalHandler = async (signal) => {
515
+ const signalHandler = async (_signal) => {
512
516
  if (eventQueue.length > 0) {
513
517
  await flushEvents();
514
518
  }
@@ -519,32 +523,302 @@ function registerExitHandler() {
519
523
  }
520
524
 
521
525
  // src/commands/init.ts
526
+ async function confirm(question, defaultYes = true) {
527
+ const rl = createInterface({
528
+ input: process.stdin,
529
+ output: process.stdout
530
+ });
531
+ const suffix = defaultYes ? "[Y/n]" : "[y/N]";
532
+ return new Promise((resolve) => {
533
+ rl.question(` ${question} ${chalk.dim(suffix)} `, (answer) => {
534
+ rl.close();
535
+ const normalized = answer.toLowerCase().trim();
536
+ if (normalized === "") {
537
+ resolve(defaultYes);
538
+ } else {
539
+ resolve(normalized === "y" || normalized === "yes");
540
+ }
541
+ });
542
+ });
543
+ }
544
+ function commandExists(cmd) {
545
+ try {
546
+ execSync2(`which ${cmd}`, { stdio: "ignore" });
547
+ return true;
548
+ } catch {
549
+ return false;
550
+ }
551
+ }
552
+ function dockerRunning() {
553
+ try {
554
+ execSync2("docker info", { stdio: "ignore" });
555
+ return true;
556
+ } catch {
557
+ return false;
558
+ }
559
+ }
560
+ function checkClaudeAuth() {
561
+ try {
562
+ execSync2("which claude", { stdio: "ignore" });
563
+ } catch {
564
+ return { installed: false, loggedIn: false };
565
+ }
566
+ try {
567
+ const result = execSync2("claude --version", { stdio: "pipe" }).toString();
568
+ return { installed: true, loggedIn: result.includes("claude") };
569
+ } catch {
570
+ return { installed: true, loggedIn: false };
571
+ }
572
+ }
573
+ function checkGhAuth() {
574
+ try {
575
+ execSync2("gh auth status", { stdio: "ignore" });
576
+ return true;
577
+ } catch {
578
+ return false;
579
+ }
580
+ }
581
+ function checkRequirements() {
582
+ const checks = [];
583
+ const claudeStatus = checkClaudeAuth();
584
+ if (claudeStatus.installed && claudeStatus.loggedIn) {
585
+ checks.push({ name: "Claude CLI", status: "ok" });
586
+ } else if (claudeStatus.installed) {
587
+ checks.push({
588
+ name: "Claude CLI",
589
+ status: "warning",
590
+ message: "Installed but may need login",
591
+ hint: "Run: claude login"
592
+ });
593
+ } else {
594
+ checks.push({
595
+ name: "Claude CLI",
596
+ status: "missing",
597
+ message: "Required to run agents",
598
+ hint: "Install: npm install -g @anthropic-ai/claude-code"
599
+ });
600
+ }
601
+ if (checkGhAuth()) {
602
+ checks.push({ name: "GitHub CLI", status: "ok" });
603
+ } else if (commandExists("gh")) {
604
+ checks.push({
605
+ name: "GitHub CLI",
606
+ status: "warning",
607
+ message: "Installed but not logged in",
608
+ hint: "Run: gh auth login"
609
+ });
610
+ } else {
611
+ checks.push({
612
+ name: "GitHub CLI",
613
+ status: "missing",
614
+ message: "Required for GitHub integration (issues, PRs)",
615
+ hint: "Install: https://cli.github.com"
616
+ });
617
+ }
618
+ if (commandExists("docker")) {
619
+ if (dockerRunning()) {
620
+ checks.push({ name: "Docker", status: "ok" });
621
+ } else {
622
+ checks.push({
623
+ name: "Docker",
624
+ status: "warning",
625
+ message: "Installed but not running",
626
+ hint: "Start Docker Desktop or run: sudo systemctl start docker"
627
+ });
628
+ }
629
+ } else {
630
+ checks.push({
631
+ name: "Docker",
632
+ status: "warning",
633
+ message: "Optional - enables dashboard metrics",
634
+ hint: "Install: https://docker.com"
635
+ });
636
+ }
637
+ return checks;
638
+ }
639
+ async function setupInfrastructure(cwd) {
640
+ const spinner = ora("Setting up infrastructure...").start();
641
+ try {
642
+ let dockerDir = null;
643
+ const localDockerCompose = path.join(cwd, "docker", "docker-compose.yml");
644
+ const localDockerComposeRoot = path.join(cwd, "docker-compose.yml");
645
+ if (await fileExists(localDockerCompose)) {
646
+ dockerDir = path.join(cwd, "docker");
647
+ } else if (await fileExists(localDockerComposeRoot)) {
648
+ dockerDir = cwd;
649
+ } else {
650
+ const cliPath = new URL("../..", import.meta.url).pathname;
651
+ const bundledDockerCompose = path.join(cliPath, "docker", "docker-compose.yml");
652
+ if (await fileExists(bundledDockerCompose)) {
653
+ spinner.text = "Copying infrastructure files...";
654
+ const targetDockerDir = path.join(cwd, "docker");
655
+ await fs.mkdir(targetDockerDir, { recursive: true });
656
+ const filesToCopy = [
657
+ "docker-compose.yml",
658
+ ".env.example",
659
+ "init-db.sql",
660
+ "README.md"
661
+ ];
662
+ for (const file of filesToCopy) {
663
+ const src = path.join(cliPath, "docker", file);
664
+ const dest = path.join(targetDockerDir, file);
665
+ if (await fileExists(src)) {
666
+ await fs.copyFile(src, dest);
667
+ }
668
+ }
669
+ const bridgeSrc = path.join(cliPath, "docker", "squads-bridge");
670
+ const bridgeDest = path.join(targetDockerDir, "squads-bridge");
671
+ if (await fileExists(bridgeSrc)) {
672
+ await copyDir(bridgeSrc, bridgeDest);
673
+ }
674
+ const envExample = path.join(targetDockerDir, ".env.example");
675
+ const envFile = path.join(targetDockerDir, ".env");
676
+ if (await fileExists(envExample) && !await fileExists(envFile)) {
677
+ await fs.copyFile(envExample, envFile);
678
+ }
679
+ dockerDir = targetDockerDir;
680
+ }
681
+ }
682
+ if (!dockerDir) {
683
+ spinner.fail("Could not find docker-compose.yml");
684
+ console.log(chalk.dim(" Try cloning the full repo: git clone https://github.com/agents-squads/hq"));
685
+ return false;
686
+ }
687
+ spinner.text = "Starting containers...";
688
+ await new Promise((resolve, reject) => {
689
+ const proc = spawn("docker", ["compose", "up", "-d"], {
690
+ cwd: dockerDir,
691
+ stdio: "pipe"
692
+ });
693
+ let stderr = "";
694
+ proc.stderr?.on("data", (data) => {
695
+ stderr += data.toString();
696
+ });
697
+ proc.on("close", (code) => {
698
+ if (code === 0) {
699
+ resolve();
700
+ } else {
701
+ reject(new Error(stderr || `docker compose failed with code ${code}`));
702
+ }
703
+ });
704
+ proc.on("error", reject);
705
+ });
706
+ spinner.text = "Waiting for services to be ready...";
707
+ await sleep(3e3);
708
+ const services = ["squads-postgres", "squads-redis", "squads-bridge"];
709
+ let allRunning = true;
710
+ for (const service of services) {
711
+ try {
712
+ execSync2(`docker ps --filter "name=${service}" --filter "status=running" -q`, { stdio: "pipe" });
713
+ } catch {
714
+ allRunning = false;
715
+ }
716
+ }
717
+ if (allRunning) {
718
+ spinner.succeed("Infrastructure ready");
719
+ console.log();
720
+ console.log(chalk.dim(" Services running:"));
721
+ console.log(chalk.dim(" \u2022 postgres:5432 \u2022 redis:6379 \u2022 bridge:8088"));
722
+ return true;
723
+ } else {
724
+ spinner.warn("Some services may not be running");
725
+ console.log(chalk.dim(" Check with: docker ps"));
726
+ return true;
727
+ }
728
+ } catch (error) {
729
+ spinner.fail("Failed to start infrastructure");
730
+ console.log(chalk.dim(` ${error}`));
731
+ console.log(chalk.dim(" Try manually: cd docker && docker compose up -d"));
732
+ return false;
733
+ }
734
+ }
735
+ async function fileExists(filePath) {
736
+ try {
737
+ await fs.access(filePath);
738
+ return true;
739
+ } catch {
740
+ return false;
741
+ }
742
+ }
743
+ async function copyDir(src, dest) {
744
+ await fs.mkdir(dest, { recursive: true });
745
+ const entries = await fs.readdir(src, { withFileTypes: true });
746
+ for (const entry of entries) {
747
+ const srcPath = path.join(src, entry.name);
748
+ const destPath = path.join(dest, entry.name);
749
+ if (entry.isDirectory()) {
750
+ await copyDir(srcPath, destPath);
751
+ } else {
752
+ await fs.copyFile(srcPath, destPath);
753
+ }
754
+ }
755
+ }
756
+ function sleep(ms) {
757
+ return new Promise((resolve) => setTimeout(resolve, ms));
758
+ }
522
759
  async function initCommand(options) {
523
760
  const cwd = process.cwd();
524
- console.log(chalk.dim("Checking project setup...\n"));
761
+ console.log();
762
+ console.log(chalk.bold(" Checking requirements..."));
763
+ console.log();
764
+ const checks = checkRequirements();
765
+ let hasMissingRequired = false;
766
+ let hasDocker = false;
767
+ let dockerReady = false;
768
+ for (const check of checks) {
769
+ if (check.status === "ok") {
770
+ console.log(` ${chalk.green("\u2713")} ${check.name}`);
771
+ if (check.name === "Docker") {
772
+ hasDocker = true;
773
+ dockerReady = true;
774
+ }
775
+ } else if (check.status === "missing") {
776
+ console.log(` ${chalk.yellow("\u26A0")} ${chalk.yellow(check.name)}`);
777
+ if (check.message) {
778
+ console.log(chalk.dim(` ${check.message}`));
779
+ }
780
+ if (check.hint) {
781
+ console.log(chalk.dim(` \u2192 ${check.hint}`));
782
+ }
783
+ if (check.name !== "Docker") {
784
+ hasMissingRequired = true;
785
+ }
786
+ } else if (check.status === "warning") {
787
+ console.log(` ${chalk.dim("\u25CB")} ${check.name} ${chalk.dim(`(${check.message})`)}`);
788
+ if (check.hint) {
789
+ console.log(chalk.dim(` \u2192 ${check.hint}`));
790
+ }
791
+ if (check.name === "Docker") {
792
+ hasDocker = commandExists("docker");
793
+ }
794
+ }
795
+ }
796
+ console.log();
797
+ if (hasMissingRequired) {
798
+ console.log(chalk.yellow(" Install missing tools to continue, then run squads init again."));
799
+ console.log();
800
+ return;
801
+ }
802
+ console.log(chalk.dim(" Checking project setup..."));
803
+ console.log();
525
804
  const gitStatus = checkGitStatus(cwd);
526
805
  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"));
806
+ console.log(` ${chalk.yellow("\u26A0")} No git repository found`);
807
+ console.log(chalk.dim(" Run: git init && git remote add origin <repo-url>"));
808
+ console.log();
530
809
  } else {
531
- console.log(chalk.green("\u2713 Git repository detected"));
810
+ console.log(` ${chalk.green("\u2713")} Git repository`);
532
811
  if (gitStatus.hasRemote) {
533
812
  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`));
813
+ console.log(` ${chalk.green("\u2713")} Remote: ${chalk.cyan(repoName || gitStatus.remoteUrl)}`);
541
814
  }
542
815
  }
543
816
  console.log();
544
- const spinner = ora("Initializing squad project...").start();
817
+ const spinner = ora("Creating squad structure...").start();
545
818
  try {
546
819
  const dirs = [
547
820
  ".agents/squads",
821
+ ".agents/squads/demo",
548
822
  ".agents/memory",
549
823
  ".agents/outputs",
550
824
  ".claude"
@@ -552,56 +826,98 @@ async function initCommand(options) {
552
826
  for (const dir of dirs) {
553
827
  await fs.mkdir(path.join(cwd, dir), { recursive: true });
554
828
  }
555
- const commitTemplate = `
829
+ const demoSquadMd = `# Demo Squad
556
830
 
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
831
+ Demonstrates squads functionality with safe, educational examples.
832
+
833
+ ## Agents
834
+
835
+ | Agent | Purpose |
836
+ |-------|---------|
837
+ | welcome | Creates a welcome GitHub issue |
838
+ | analyzer | Analyzes project structure |
839
+
840
+ ## Triggers
841
+
842
+ None (manual execution only)
843
+
844
+ ## Usage
845
+
846
+ \`\`\`bash
847
+ squads run demo
848
+ \`\`\`
849
+
850
+ This will:
851
+ 1. Create a GitHub issue explaining what happened
852
+ 2. Analyze your project structure
853
+ 3. Show results in the dashboard
854
+
855
+ All demo data is clearly labeled [demo] so you can distinguish it from real data.
592
856
  `;
593
857
  await fs.writeFile(
594
- path.join(cwd, ".agents/commit-template.txt"),
595
- commitTemplate
858
+ path.join(cwd, ".agents/squads/demo/SQUAD.md"),
859
+ demoSquadMd
860
+ );
861
+ const welcomeAgent = `# Welcome Agent
862
+
863
+ ## Purpose
864
+ Create a welcome GitHub issue to demonstrate squads functionality.
865
+
866
+ ## Model
867
+ claude-haiku-3-5
868
+
869
+ ## Tools
870
+ - Bash (gh cli)
871
+
872
+ ## Instructions
873
+ 1. Check if a welcome issue already exists
874
+ 2. If not, create a new issue titled "[Demo] Welcome to Squads!"
875
+ 3. The issue body should explain:
876
+ - What just happened (squads run demo executed this agent)
877
+ - What squads are and how they work
878
+ - Next steps to create their own agents
879
+
880
+ ## Output
881
+ Confirmation message with issue URL.
882
+
883
+ ## Labels
884
+ - demo
885
+ - automated
886
+ `;
887
+ await fs.writeFile(
888
+ path.join(cwd, ".agents/squads/demo/welcome.md"),
889
+ welcomeAgent
596
890
  );
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
891
+ const analyzerAgent = `# Project Analyzer Agent
600
892
 
601
- # AI Contributors
602
- Agents Squads <agents@agents-squads.com> Agents Squads <agents@agents-squads.com>
893
+ ## Purpose
894
+ Analyze project structure and provide insights.
895
+
896
+ ## Model
897
+ claude-haiku-3-5
898
+
899
+ ## Tools
900
+ - Read
901
+ - Glob
902
+ - Bash (ls, find)
903
+
904
+ ## Instructions
905
+ 1. Scan the project directory structure
906
+ 2. Identify key files (package.json, Cargo.toml, pyproject.toml, etc.)
907
+ 3. Summarize the tech stack
908
+ 4. Create a brief report
909
+
910
+ ## Output
911
+ Markdown report saved to .agents/outputs/demo/project-analysis.md
912
+
913
+ ## Labels
914
+ - demo
915
+ - analysis
603
916
  `;
604
- await fs.writeFile(path.join(cwd, ".mailmap"), mailmap);
917
+ await fs.writeFile(
918
+ path.join(cwd, ".agents/squads/demo/analyzer.md"),
919
+ analyzerAgent
920
+ );
605
921
  const claudeSettings = {
606
922
  hooks: {
607
923
  SessionStart: [
@@ -632,135 +948,115 @@ Agents Squads <agents@agents-squads.com> Agents Squads <agents@agents-squads.com
632
948
  path.join(cwd, ".claude/settings.json"),
633
949
  JSON.stringify(claudeSettings, null, 2)
634
950
  );
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.
951
+ const commitTemplate = `
952
+ # Commit message format:
953
+ # <type>(<scope>): <subject>
954
+ #
955
+ # \u{1F916} Generated with [Agents Squads](https://agents-squads.com)
956
+ # Co-Authored-By: Claude <model> <noreply@anthropic.com>
655
957
  `;
656
958
  await fs.writeFile(
657
- path.join(cwd, ".agents/squads/example-agent.md"),
658
- exampleAgent
959
+ path.join(cwd, ".agents/commit-template.txt"),
960
+ commitTemplate
659
961
  );
660
962
  const claudeMdPath = path.join(cwd, "CLAUDE.md");
661
- try {
662
- await fs.access(claudeMdPath);
663
- } catch {
963
+ if (!await fileExists(claudeMdPath)) {
664
964
  await fs.writeFile(
665
965
  claudeMdPath,
666
966
  `# Project Instructions
667
967
 
668
968
  ## Squads CLI
669
969
 
670
- This project uses AI agent squads. The \`squads\` CLI provides persistent memory across sessions.
970
+ This project uses AI agent squads for automation.
671
971
 
672
- ### Key Commands
972
+ ### Quick Start
673
973
 
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 |
974
+ \`\`\`bash
975
+ squads status # Overview
976
+ squads dash # Full dashboard
977
+ squads run demo # Try the demo squad
978
+ squads run <squad> # Execute a squad
979
+ \`\`\`
980
+
981
+ ### Memory
682
982
 
683
- ### Workflow
983
+ Squads have persistent memory across sessions:
684
984
 
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
985
+ \`\`\`bash
986
+ squads memory query "<topic>" # Search memory
987
+ squads memory show <squad> # View squad memory
988
+ \`\`\`
688
989
 
689
- ### Git Commit Format
990
+ ### Creating Agents
690
991
 
691
- All commits should use the Agents Squads format:
992
+ Agents are markdown files in \`.agents/squads/<squad>/\`:
692
993
 
693
- \`\`\`
694
- <type>(<scope>): <subject>
994
+ \`\`\`markdown
995
+ # My Agent
695
996
 
696
- <body>
997
+ ## Purpose
998
+ What this agent does.
697
999
 
698
- \u{1F916} Generated with [Agents Squads](https://agents-squads.com)
1000
+ ## Instructions
1001
+ 1. Step one
1002
+ 2. Step two
699
1003
 
700
- Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1004
+ ## Output
1005
+ What it produces.
701
1006
  \`\`\`
702
-
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\`
723
1007
  `
724
1008
  );
725
1009
  }
726
- spinner.succeed("Squad project initialized!");
1010
+ spinner.succeed("Squad structure created");
727
1011
  await track(Events.CLI_INIT, {
728
1012
  hasGit: gitStatus.isGitRepo,
729
1013
  hasRemote: gitStatus.hasRemote,
730
- template: options.template
1014
+ template: options.template,
1015
+ hasDocker
731
1016
  });
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
1017
  } catch (error) {
755
- spinner.fail("Failed to initialize project");
756
- console.error(chalk.red(error));
1018
+ spinner.fail("Failed to create structure");
1019
+ console.error(chalk.red(` ${error}`));
757
1020
  process.exit(1);
758
1021
  }
1022
+ if (!options.skipInfra && hasDocker) {
1023
+ console.log();
1024
+ if (!dockerReady) {
1025
+ console.log(chalk.dim(" Docker is installed but not running."));
1026
+ console.log(chalk.dim(" Start Docker to enable infrastructure setup."));
1027
+ } else {
1028
+ const setupInfra = await confirm("Set up local infrastructure (postgres, redis)?", true);
1029
+ if (setupInfra) {
1030
+ console.log();
1031
+ const success = await setupInfrastructure(cwd);
1032
+ if (!success) {
1033
+ console.log();
1034
+ console.log(chalk.dim(" You can set up infrastructure later with:"));
1035
+ console.log(chalk.dim(" cd docker && docker compose up -d"));
1036
+ }
1037
+ }
1038
+ }
1039
+ }
1040
+ console.log();
1041
+ console.log(chalk.green(" \u2713 Ready!"));
1042
+ console.log();
1043
+ console.log(` ${chalk.cyan(".agents/")}
1044
+ ${chalk.dim("\u251C\u2500\u2500")} ${chalk.cyan("squads/demo/")} Demo squad (try it!)
1045
+ ${chalk.dim("\u251C\u2500\u2500")} ${chalk.cyan("memory/")} Persistent context
1046
+ ${chalk.dim("\u2514\u2500\u2500")} ${chalk.cyan("outputs/")} Agent outputs`);
1047
+ console.log();
1048
+ console.log(chalk.dim(" Next steps:"));
1049
+ console.log(` ${chalk.cyan("1.")} Try the demo: ${chalk.yellow("squads run demo")}`);
1050
+ console.log(` ${chalk.cyan("2.")} Check status: ${chalk.yellow("squads dash")}`);
1051
+ console.log(` ${chalk.cyan("3.")} Create your own squad in ${chalk.cyan(".agents/squads/")}`);
1052
+ console.log();
1053
+ console.log(chalk.dim(" Need help? jorge@agents-squads.com"));
1054
+ console.log();
759
1055
  }
760
1056
 
761
1057
  // src/commands/run.ts
762
1058
  import ora2 from "ora";
763
- import { spawn } from "child_process";
1059
+ import { spawn as spawn2 } from "child_process";
764
1060
  import { join as join4, dirname } from "path";
765
1061
  import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync2 } from "fs";
766
1062
 
@@ -1010,6 +1306,33 @@ function updateGoalInSquad(squadName, goalIndex, updates) {
1010
1306
  }
1011
1307
 
1012
1308
  // src/commands/run.ts
1309
+ function ensureProjectTrusted(projectPath) {
1310
+ const configPath = join4(process.env.HOME || "", ".claude.json");
1311
+ if (!existsSync4(configPath)) {
1312
+ return;
1313
+ }
1314
+ try {
1315
+ const config2 = JSON.parse(readFileSync3(configPath, "utf-8"));
1316
+ if (!config2.projects) {
1317
+ config2.projects = {};
1318
+ }
1319
+ if (!config2.projects[projectPath]) {
1320
+ config2.projects[projectPath] = {};
1321
+ }
1322
+ if (!config2.projects[projectPath].hasTrustDialogAccepted) {
1323
+ config2.projects[projectPath].hasTrustDialogAccepted = true;
1324
+ writeFileSync3(configPath, JSON.stringify(config2, null, 2));
1325
+ }
1326
+ } catch {
1327
+ }
1328
+ }
1329
+ function getProjectRoot() {
1330
+ const squadsDir = findSquadsDir();
1331
+ if (squadsDir) {
1332
+ return dirname(dirname(squadsDir));
1333
+ }
1334
+ return process.cwd();
1335
+ }
1013
1336
  function getExecutionLogPath(squadName, agentName) {
1014
1337
  const memoryDir = findMemoryDir();
1015
1338
  if (!memoryDir) return null;
@@ -1091,6 +1414,44 @@ async function runSquad(squad, squadsDir, options) {
1091
1414
  }
1092
1415
  writeLine(` ${colors.dim}Started: ${startTime}${RESET}`);
1093
1416
  writeLine();
1417
+ if (options.lead) {
1418
+ await runLeadMode(squad, squadsDir, options);
1419
+ return;
1420
+ }
1421
+ if (options.parallel) {
1422
+ const agentFiles = squad.agents.map((a) => ({
1423
+ name: a.name,
1424
+ path: join4(squadsDir, squad.name, `${a.name}.md`)
1425
+ })).filter((a) => existsSync4(a.path));
1426
+ if (agentFiles.length === 0) {
1427
+ writeLine(` ${icons.error} ${colors.red}No agent files found${RESET}`);
1428
+ return;
1429
+ }
1430
+ writeLine(` ${bold}Parallel execution${RESET} ${colors.dim}${agentFiles.length} agents${RESET}`);
1431
+ writeLine();
1432
+ if (!options.execute) {
1433
+ for (const agent of agentFiles) {
1434
+ writeLine(` ${icons.empty} ${colors.cyan}${agent.name}${RESET}`);
1435
+ }
1436
+ writeLine();
1437
+ writeLine(` ${colors.dim}Launch all agents in parallel:${RESET}`);
1438
+ writeLine(` ${colors.dim}$${RESET} squads run ${colors.cyan}${squad.name}${RESET} --parallel --execute`);
1439
+ writeLine();
1440
+ return;
1441
+ }
1442
+ writeLine(` ${gradient("Launching")} ${agentFiles.length} agents in parallel...`);
1443
+ writeLine();
1444
+ const launches = agentFiles.map(
1445
+ (agent) => runAgent(agent.name, agent.path, squad.name, options)
1446
+ );
1447
+ await Promise.all(launches);
1448
+ writeLine();
1449
+ writeLine(` ${icons.success} All ${agentFiles.length} agents launched`);
1450
+ writeLine(` ${colors.dim}Monitor: tmux ls | grep squads-${squad.name}${RESET}`);
1451
+ writeLine(` ${colors.dim}Attach: tmux attach -t <session>${RESET}`);
1452
+ writeLine();
1453
+ return;
1454
+ }
1094
1455
  if (squad.pipelines.length > 0) {
1095
1456
  const pipeline = squad.pipelines[0];
1096
1457
  writeLine(` ${bold}Pipeline${RESET} ${colors.dim}${pipeline.agents.join(" \u2192 ")}${RESET}`);
@@ -1132,6 +1493,9 @@ async function runSquad(squad, squadsDir, options) {
1132
1493
  writeLine();
1133
1494
  writeLine(` ${colors.dim}Run a specific agent:${RESET}`);
1134
1495
  writeLine(` ${colors.dim}$${RESET} squads run ${colors.cyan}${squad.name}${RESET} --agent ${colors.cyan}<name>${RESET}`);
1496
+ writeLine();
1497
+ writeLine(` ${colors.dim}Run all agents in parallel:${RESET}`);
1498
+ writeLine(` ${colors.dim}$${RESET} squads run ${colors.cyan}${squad.name}${RESET} --parallel --execute`);
1135
1499
  }
1136
1500
  }
1137
1501
  }
@@ -1140,15 +1504,123 @@ async function runSquad(squad, squadsDir, options) {
1140
1504
  writeLine(` ${colors.dim}$${RESET} squads feedback add ${colors.cyan}${squad.name}${RESET} ${colors.cyan}<1-5>${RESET} ${colors.cyan}"<feedback>"${RESET}`);
1141
1505
  writeLine();
1142
1506
  }
1143
- async function runAgent(agentName, agentPath, squadName, options) {
1144
- const spinner = ora2(`Running agent: ${agentName}`).start();
1145
- const startTime = (/* @__PURE__ */ new Date()).toISOString();
1146
- const definition = loadAgentDefinition(agentPath);
1147
- if (options.dryRun) {
1148
- spinner.info(`[DRY RUN] Would run ${agentName}`);
1149
- if (options.verbose) {
1150
- writeLine(` ${colors.dim}Agent definition:${RESET}`);
1151
- writeLine(` ${colors.dim}${definition.slice(0, 500)}...${RESET}`);
1507
+ async function runLeadMode(squad, squadsDir, options) {
1508
+ if (!squad) return;
1509
+ const agentFiles = squad.agents.map((a) => ({
1510
+ name: a.name,
1511
+ path: join4(squadsDir, squad.name, `${a.name}.md`),
1512
+ role: a.role || ""
1513
+ })).filter((a) => existsSync4(a.path));
1514
+ if (agentFiles.length === 0) {
1515
+ writeLine(` ${icons.error} ${colors.red}No agent files found${RESET}`);
1516
+ return;
1517
+ }
1518
+ writeLine(` ${bold}Lead mode${RESET} ${colors.dim}orchestrating ${agentFiles.length} agents${RESET}`);
1519
+ writeLine();
1520
+ for (const agent of agentFiles) {
1521
+ writeLine(` ${icons.empty} ${colors.cyan}${agent.name}${RESET} ${colors.dim}${agent.role}${RESET}`);
1522
+ }
1523
+ writeLine();
1524
+ if (!options.execute) {
1525
+ writeLine(` ${colors.dim}Launch lead session:${RESET}`);
1526
+ writeLine(` ${colors.dim}$${RESET} squads run ${colors.cyan}${squad.name}${RESET} --lead --execute`);
1527
+ writeLine();
1528
+ return;
1529
+ }
1530
+ const timeoutMins = options.timeout || 30;
1531
+ const agentList = agentFiles.map((a) => `- ${a.name}: ${a.role}`).join("\n");
1532
+ const agentPaths = agentFiles.map((a) => `- ${a.name}: ${a.path}`).join("\n");
1533
+ const prompt2 = `You are the Lead of the ${squad.name} squad.
1534
+
1535
+ ## Mission
1536
+ ${squad.mission || "Execute squad operations efficiently."}
1537
+
1538
+ ## Available Agents
1539
+ ${agentList}
1540
+
1541
+ ## Agent Definition Files
1542
+ ${agentPaths}
1543
+
1544
+ ## Your Role as Lead
1545
+
1546
+ 1. **Assess the situation**: Check for pending work:
1547
+ - Run \`gh issue list --repo agents-squads/hq --label squad:${squad.name}\` for assigned issues
1548
+ - Check .agents/memory/${squad.name}/ for squad state and pending tasks
1549
+ - Review recent activity with \`git log --oneline -10\`
1550
+
1551
+ 2. **Delegate work using Task tool**: For each piece of work:
1552
+ - Use the Task tool with subagent_type="general-purpose"
1553
+ - Include the agent definition file path in the prompt
1554
+ - Spawn multiple Task agents IN PARALLEL when work is independent
1555
+ - Example: "Read ${agentFiles[0]?.path || "agent.md"} and execute its instructions for [specific task]"
1556
+
1557
+ 3. **Coordinate parallel execution**:
1558
+ - Independent tasks \u2192 spawn Task agents in parallel (single message, multiple tool calls)
1559
+ - Dependent tasks \u2192 run sequentially
1560
+ - Monitor progress and handle failures
1561
+
1562
+ 4. **Report and update memory**:
1563
+ - Update .agents/memory/${squad.name}/state.md with completed work
1564
+ - Log learnings to learnings.md
1565
+ - Create issues for follow-up work if needed
1566
+
1567
+ ## Time Budget
1568
+ You have ${timeoutMins} minutes. Prioritize high-impact work.
1569
+
1570
+ ## Critical Instructions
1571
+ - Use Task tool for delegation, NOT direct execution of agent work
1572
+ - Spawn parallel Task agents when work is independent
1573
+ - When done, type /exit to end the session
1574
+ - Do NOT wait for user input - work autonomously
1575
+
1576
+ ## Async Mode (CRITICAL)
1577
+ This is ASYNC execution - Task agents must be fully autonomous:
1578
+ - **Findings** \u2192 Create GitHub issues (gh issue create)
1579
+ - **Code changes** \u2192 Create PRs (gh pr create)
1580
+ - **Analysis results** \u2192 Write to .agents/outputs/ or memory files
1581
+ - **NEVER wait for human review** - complete the work and move on
1582
+ - **NEVER ask clarifying questions** - make reasonable decisions
1583
+
1584
+ Instruct each Task agent: "Work autonomously. Output findings to GitHub issues. Output code changes as PRs. Do not wait for review."
1585
+
1586
+ Begin by assessing pending work, then delegate to agents via Task tool.`;
1587
+ const claudeAvailable = await checkClaudeCliAvailable();
1588
+ if (!claudeAvailable) {
1589
+ writeLine(` ${colors.yellow}Claude CLI not found${RESET}`);
1590
+ writeLine(` ${colors.dim}Install: npm install -g @anthropic-ai/claude-code${RESET}`);
1591
+ return;
1592
+ }
1593
+ writeLine(` ${gradient("Launching")} lead session${options.foreground ? " (foreground)" : ""}...`);
1594
+ writeLine();
1595
+ try {
1596
+ const result = await executeWithClaude(prompt2, options.verbose, timeoutMins, options.foreground, options.useApi);
1597
+ if (options.foreground) {
1598
+ writeLine();
1599
+ writeLine(` ${icons.success} Lead session completed`);
1600
+ } else {
1601
+ writeLine(` ${icons.success} Lead session launched`);
1602
+ writeLine(` ${colors.dim}${result}${RESET}`);
1603
+ writeLine();
1604
+ writeLine(` ${colors.dim}The lead will:${RESET}`);
1605
+ writeLine(` ${colors.dim} 1. Assess pending work (issues, memory)${RESET}`);
1606
+ writeLine(` ${colors.dim} 2. Spawn Task agents for parallel execution${RESET}`);
1607
+ writeLine(` ${colors.dim} 3. Coordinate and report results${RESET}`);
1608
+ writeLine();
1609
+ writeLine(` ${colors.dim}Monitor: squads workers${RESET}`);
1610
+ }
1611
+ } catch (error) {
1612
+ writeLine(` ${icons.error} ${colors.red}Failed to launch: ${error}${RESET}`);
1613
+ }
1614
+ }
1615
+ async function runAgent(agentName, agentPath, squadName, options) {
1616
+ const spinner = ora2(`Running agent: ${agentName}`).start();
1617
+ const startTime = (/* @__PURE__ */ new Date()).toISOString();
1618
+ const definition = loadAgentDefinition(agentPath);
1619
+ if (options.dryRun) {
1620
+ spinner.info(`[DRY RUN] Would run ${agentName}`);
1621
+ if (options.verbose) {
1622
+ writeLine(` ${colors.dim}Agent definition:${RESET}`);
1623
+ writeLine(` ${colors.dim}${definition.slice(0, 500)}...${RESET}`);
1152
1624
  }
1153
1625
  return;
1154
1626
  }
@@ -1158,6 +1630,7 @@ async function runAgent(agentName, agentPath, squadName, options) {
1158
1630
  startTime,
1159
1631
  status: "running"
1160
1632
  });
1633
+ const timeoutMins = options.timeout || 30;
1161
1634
  const prompt2 = `Execute the ${agentName} agent from squad ${squadName}.
1162
1635
 
1163
1636
  Read the agent definition at ${agentPath} and follow its instructions exactly.
@@ -1168,20 +1641,35 @@ The agent definition contains:
1168
1641
  - Step-by-step instructions
1169
1642
  - Expected output format
1170
1643
 
1644
+ TIME LIMIT: You have ${timeoutMins} minutes. Work efficiently:
1645
+ - Focus on the most important tasks first
1646
+ - If a task is taking too long, move on and note it for next run
1647
+ - Aim to complete within ${Math.floor(timeoutMins * 0.7)} minutes
1648
+
1171
1649
  After completion:
1172
1650
  1. Update the agent's memory in .agents/memory/${squadName}/${agentName}/state.md
1173
1651
  2. Log any learnings to learnings.md
1174
- 3. Report what was accomplished`;
1652
+ 3. Summarize what was accomplished
1653
+
1654
+ CRITICAL: When you have completed your tasks OR reached the time limit:
1655
+ - Type /exit immediately to end this session
1656
+ - Do NOT wait for user input
1657
+ - Do NOT ask follow-up questions
1658
+ - Just /exit when done`;
1175
1659
  const claudeAvailable = await checkClaudeCliAvailable();
1176
1660
  if (options.execute && claudeAvailable) {
1177
- spinner.text = `Launching ${agentName} as background task...`;
1661
+ spinner.text = options.foreground ? `Running ${agentName} in foreground...` : `Launching ${agentName} as background task...`;
1178
1662
  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}`);
1663
+ const result = await executeWithClaude(prompt2, options.verbose, options.timeout || 30, options.foreground, options.useApi);
1664
+ if (options.foreground) {
1665
+ spinner.succeed(`Agent ${agentName} completed`);
1666
+ } else {
1667
+ spinner.succeed(`Agent ${agentName} launched`);
1668
+ writeLine(` ${colors.dim}${result}${RESET}`);
1669
+ writeLine();
1670
+ writeLine(` ${colors.dim}Monitor:${RESET} squads workers`);
1671
+ writeLine(` ${colors.dim}Memory:${RESET} squads memory show ${squadName}`);
1672
+ }
1185
1673
  } catch (error) {
1186
1674
  spinner.fail(`Agent ${agentName} failed to launch`);
1187
1675
  updateExecutionStatus(squadName, agentName, "failed", String(error));
@@ -1205,25 +1693,64 @@ After completion:
1205
1693
  }
1206
1694
  async function checkClaudeCliAvailable() {
1207
1695
  return new Promise((resolve) => {
1208
- const check = spawn("which", ["claude"], { stdio: "pipe" });
1696
+ const check = spawn2("which", ["claude"], { stdio: "pipe" });
1209
1697
  check.on("close", (code) => resolve(code === 0));
1210
1698
  check.on("error", () => resolve(false));
1211
1699
  });
1212
1700
  }
1213
- async function executeWithClaude(prompt2, verbose, timeoutMinutes = 30) {
1701
+ async function executeWithClaude(prompt2, verbose, _timeoutMinutes = 30, foreground, useApi) {
1214
1702
  const userConfigPath = join4(process.env.HOME || "", ".claude.json");
1703
+ const projectRoot = getProjectRoot();
1704
+ ensureProjectTrusted(projectRoot);
1215
1705
  const squadMatch = prompt2.match(/squad (\w+)/);
1216
1706
  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}`;
1707
+ const squadName = process.env.SQUADS_SQUAD || squadMatch?.[1] || "unknown";
1708
+ const agentName = process.env.SQUADS_AGENT || agentMatch?.[1] || "unknown";
1709
+ const { ANTHROPIC_API_KEY: _apiKey, ...envWithoutApiKey } = process.env;
1710
+ const spawnEnv = useApi ? process.env : envWithoutApiKey;
1711
+ const escapedPrompt = prompt2.replace(/'/g, "'\\''");
1712
+ if (foreground) {
1713
+ if (verbose) {
1714
+ writeLine(` ${colors.dim}Project: ${projectRoot}${RESET}`);
1715
+ writeLine(` ${colors.dim}Mode: foreground${RESET}`);
1716
+ writeLine(` ${colors.dim}Auth: ${useApi ? "API credits" : "subscription"}${RESET}`);
1717
+ }
1718
+ return new Promise((resolve, reject) => {
1719
+ const claude = spawn2("claude", [
1720
+ "--dangerously-skip-permissions",
1721
+ "--mcp-config",
1722
+ userConfigPath,
1723
+ "--",
1724
+ prompt2
1725
+ ], {
1726
+ stdio: "inherit",
1727
+ cwd: projectRoot,
1728
+ env: {
1729
+ ...spawnEnv,
1730
+ SQUADS_SQUAD: squadName,
1731
+ SQUADS_AGENT: agentName
1732
+ }
1733
+ });
1734
+ claude.on("close", (code) => {
1735
+ if (code === 0) {
1736
+ resolve("Session completed");
1737
+ } else {
1738
+ reject(new Error(`Claude exited with code ${code}`));
1739
+ }
1740
+ });
1741
+ claude.on("error", (err) => {
1742
+ reject(err);
1743
+ });
1744
+ });
1745
+ }
1746
+ const sessionName = process.env.SQUADS_TMUX_SESSION || `squads-${squadName}-${agentName}-${Date.now()}`;
1221
1747
  if (verbose) {
1222
- writeLine(` ${colors.dim}Spawning tmux session: ${sessionName}${RESET}`);
1748
+ writeLine(` ${colors.dim}Project: ${projectRoot}${RESET}`);
1749
+ writeLine(` ${colors.dim}Session: ${sessionName}${RESET}`);
1750
+ writeLine(` ${colors.dim}Auth: ${useApi ? "API credits" : "subscription"}${RESET}`);
1223
1751
  }
1224
- const escapedPrompt = prompt2.replace(/'/g, "'\\''");
1225
- const claudeCmd = `claude --dangerously-skip-permissions --mcp-config '${userConfigPath}' -- '${escapedPrompt}'`;
1226
- const tmux = spawn("tmux", [
1752
+ const claudeCmd = `cd '${projectRoot}' && claude --dangerously-skip-permissions --mcp-config '${userConfigPath}' -- '${escapedPrompt}'`;
1753
+ const tmux = spawn2("tmux", [
1227
1754
  "new-session",
1228
1755
  "-d",
1229
1756
  // Detached
@@ -1241,16 +1768,12 @@ async function executeWithClaude(prompt2, verbose, timeoutMinutes = 30) {
1241
1768
  stdio: "ignore",
1242
1769
  detached: true,
1243
1770
  env: {
1244
- ...process.env,
1771
+ ...spawnEnv,
1245
1772
  SQUADS_SQUAD: squadName,
1246
1773
  SQUADS_AGENT: agentName
1247
1774
  }
1248
1775
  });
1249
1776
  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
1777
  if (verbose) {
1255
1778
  writeLine(` ${colors.dim}Attach: tmux attach -t ${sessionName}${RESET}`);
1256
1779
  }
@@ -1259,6 +1782,7 @@ async function executeWithClaude(prompt2, verbose, timeoutMinutes = 30) {
1259
1782
 
1260
1783
  // src/commands/list.ts
1261
1784
  async function listCommand(options) {
1785
+ await track("cli.list", { squads: options.squads, agents: options.agents });
1262
1786
  const squadsDir = findSquadsDir();
1263
1787
  if (!squadsDir) {
1264
1788
  writeLine(` ${colors.red}No .agents/squads directory found${RESET}`);
@@ -1314,7 +1838,7 @@ import { join as join6 } from "path";
1314
1838
  import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync4, mkdirSync as mkdirSync3, unlinkSync } from "fs";
1315
1839
  import { join as join5, dirname as dirname2 } from "path";
1316
1840
  import { homedir as homedir2 } from "os";
1317
- import { execSync as execSync2 } from "child_process";
1841
+ import { execSync as execSync3 } from "child_process";
1318
1842
  import { fileURLToPath } from "url";
1319
1843
  function getPackageVersion() {
1320
1844
  try {
@@ -1377,7 +1901,7 @@ function writeCache(latestVersion) {
1377
1901
  }
1378
1902
  function fetchLatestVersion() {
1379
1903
  try {
1380
- const result = execSync2("npm view squads-cli version 2>/dev/null", {
1904
+ const result = execSync3("npm view squads-cli version 2>/dev/null", {
1381
1905
  encoding: "utf-8",
1382
1906
  timeout: 5e3
1383
1907
  }).trim();
@@ -1394,25 +1918,42 @@ function checkForUpdate() {
1394
1918
  };
1395
1919
  const cache = readCache();
1396
1920
  const now = Date.now();
1397
- if (cache && now - cache.checkedAt < CACHE_TTL_MS) {
1921
+ if (cache) {
1398
1922
  result.latestVersion = cache.latestVersion;
1399
1923
  result.updateAvailable = isNewerVersion(CURRENT_VERSION, cache.latestVersion);
1924
+ if (now - cache.checkedAt >= CACHE_TTL_MS) {
1925
+ triggerBackgroundRefresh();
1926
+ }
1400
1927
  return result;
1401
1928
  }
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
- }
1929
+ triggerBackgroundRefresh();
1411
1930
  return result;
1412
1931
  }
1932
+ function triggerBackgroundRefresh() {
1933
+ try {
1934
+ const { spawn: spawn8 } = __require("child_process");
1935
+ const child = spawn8("npm", ["view", "squads-cli", "version"], {
1936
+ detached: true,
1937
+ stdio: ["ignore", "pipe", "ignore"],
1938
+ shell: true
1939
+ });
1940
+ let output = "";
1941
+ child.stdout?.on("data", (data) => {
1942
+ output += data.toString();
1943
+ });
1944
+ child.on("close", () => {
1945
+ const version2 = output.trim();
1946
+ if (version2 && /^\d+\.\d+\.\d+/.test(version2)) {
1947
+ writeCache(version2);
1948
+ }
1949
+ });
1950
+ child.unref();
1951
+ } catch {
1952
+ }
1953
+ }
1413
1954
  function performUpdate() {
1414
1955
  try {
1415
- execSync2("npm update -g squads-cli", {
1956
+ execSync3("npm update -g squads-cli", {
1416
1957
  encoding: "utf-8",
1417
1958
  stdio: "inherit",
1418
1959
  timeout: 12e4
@@ -1445,6 +1986,7 @@ function refreshVersionCache() {
1445
1986
 
1446
1987
  // src/commands/status.ts
1447
1988
  async function statusCommand(squadName, options = {}) {
1989
+ await track(Events.CLI_STATUS, { squad: squadName || "all", verbose: options.verbose });
1448
1990
  const squadsDir = findSquadsDir();
1449
1991
  if (!squadsDir) {
1450
1992
  writeLine(`${colors.red}No .agents/squads directory found${RESET}`);
@@ -1461,7 +2003,7 @@ async function showOverallStatus(squadsDir, _options) {
1461
2003
  const squads = listSquads(squadsDir);
1462
2004
  const memoryDir = findMemoryDir();
1463
2005
  cleanupStaleSessions();
1464
- const sessionSummary = getLiveSessionSummary();
2006
+ const sessionSummary = await getLiveSessionSummaryAsync();
1465
2007
  writeLine();
1466
2008
  writeLine(` ${gradient("squads")} ${colors.dim}status${RESET}`);
1467
2009
  const updateInfo = checkForUpdate();
@@ -1601,8 +2143,8 @@ async function showSquadStatus(squadName, squadsDir, options) {
1601
2143
  import { existsSync as existsSync7, readFileSync as readFileSync5, writeFileSync as writeFileSync5, mkdirSync as mkdirSync4, copyFileSync } from "fs";
1602
2144
  import { join as join7, dirname as dirname3 } from "path";
1603
2145
  import { homedir as homedir3 } from "os";
1604
- import { execSync as execSync3, spawn as spawn2 } from "child_process";
1605
- import { createInterface } from "readline";
2146
+ import { execSync as execSync4, spawn as spawn3 } from "child_process";
2147
+ import { createInterface as createInterface2 } from "readline";
1606
2148
  import { fileURLToPath as fileURLToPath2 } from "url";
1607
2149
  var __filename2 = fileURLToPath2(import.meta.url);
1608
2150
  var __dirname2 = dirname3(__filename2);
@@ -1728,7 +2270,7 @@ function showServiceSetupGuide(serviceName, issue) {
1728
2270
  writeLine();
1729
2271
  }
1730
2272
  async function prompt(question, defaultValue) {
1731
- const rl = createInterface({
2273
+ const rl = createInterface2({
1732
2274
  input: process.stdin,
1733
2275
  output: process.stdout
1734
2276
  });
@@ -1740,7 +2282,7 @@ async function prompt(question, defaultValue) {
1740
2282
  });
1741
2283
  });
1742
2284
  }
1743
- async function confirm(question, defaultYes = true) {
2285
+ async function confirm2(question, defaultYes = true) {
1744
2286
  const suffix = defaultYes ? "[Y/n]" : "[y/N]";
1745
2287
  const answer = await prompt(`${question} ${suffix}`);
1746
2288
  if (!answer) return defaultYes;
@@ -1821,7 +2363,7 @@ function applyStackConfig() {
1821
2363
  }
1822
2364
  function isDockerRunning() {
1823
2365
  try {
1824
- execSync3("docker info", { stdio: "ignore" });
2366
+ execSync4("docker info", { stdio: "ignore" });
1825
2367
  return true;
1826
2368
  } catch {
1827
2369
  return false;
@@ -1829,7 +2371,7 @@ function isDockerRunning() {
1829
2371
  }
1830
2372
  function getContainerStatus(name) {
1831
2373
  try {
1832
- const runningOutput = execSync3(
2374
+ const runningOutput = execSync4(
1833
2375
  `docker inspect ${name} --format '{{.State.Running}}'`,
1834
2376
  { encoding: "utf-8", stdio: ["pipe", "pipe", "ignore"] }
1835
2377
  ).trim();
@@ -1839,7 +2381,7 @@ function getContainerStatus(name) {
1839
2381
  }
1840
2382
  let port;
1841
2383
  try {
1842
- const portOutput = execSync3(
2384
+ const portOutput = execSync4(
1843
2385
  `docker inspect ${name} --format '{{range .NetworkSettings.Ports}}{{range .}}{{.HostPort}}{{end}}{{end}}'`,
1844
2386
  { encoding: "utf-8", stdio: ["pipe", "pipe", "ignore"] }
1845
2387
  ).trim();
@@ -1848,7 +2390,7 @@ function getContainerStatus(name) {
1848
2390
  }
1849
2391
  let healthy = true;
1850
2392
  try {
1851
- const healthOutput = execSync3(
2393
+ const healthOutput = execSync4(
1852
2394
  `docker inspect ${name} --format '{{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}'`,
1853
2395
  { encoding: "utf-8", stdio: ["pipe", "pipe", "ignore"] }
1854
2396
  ).trim();
@@ -1959,7 +2501,7 @@ async function stackInitCommand() {
1959
2501
  }
1960
2502
  composeDir = targetDir;
1961
2503
  writeLine(` ${colors.green}${icons.success}${RESET} Files copied`);
1962
- } catch (err) {
2504
+ } catch {
1963
2505
  writeLine(` ${colors.yellow}${icons.warning}${RESET} Could not copy files, using source location`);
1964
2506
  }
1965
2507
  } else if (existsSync7(targetDir)) {
@@ -2044,19 +2586,19 @@ BRIDGE_PORT=8088
2044
2586
  }
2045
2587
  writeLine();
2046
2588
  if (requiredNotRunning.length > 0) {
2047
- const shouldStart = await confirm("Start required services now?");
2589
+ const shouldStart = await confirm2("Start required services now?");
2048
2590
  if (shouldStart) {
2049
2591
  writeLine();
2050
2592
  writeLine(` ${colors.cyan}${icons.progress}${RESET} Starting containers...`);
2051
2593
  try {
2052
- execSync3("docker compose up -d", {
2594
+ execSync4("docker compose up -d", {
2053
2595
  cwd: composeDir,
2054
2596
  stdio: "inherit"
2055
2597
  });
2056
2598
  writeLine(` ${colors.cyan}${icons.progress}${RESET} Waiting for services to be ready...`);
2057
2599
  await new Promise((resolve) => setTimeout(resolve, 5e3));
2058
2600
  writeLine(` ${colors.green}${icons.success}${RESET} Services started`);
2059
- } catch (err) {
2601
+ } catch {
2060
2602
  writeLine(` ${colors.red}${icons.error}${RESET} Failed to start services`);
2061
2603
  writeLine(` ${colors.dim}Try manually: cd ${composeDir} && docker compose up -d${RESET}`);
2062
2604
  }
@@ -2182,7 +2724,7 @@ async function stackUpCommand() {
2182
2724
  writeLine(` ${colors.cyan}${icons.progress}${RESET} Starting containers from ${colors.dim}${composeDir}${RESET}`);
2183
2725
  writeLine();
2184
2726
  try {
2185
- const child = spawn2("docker-compose", ["up", "-d"], {
2727
+ const child = spawn3("docker-compose", ["up", "-d"], {
2186
2728
  cwd: composeDir,
2187
2729
  stdio: "inherit"
2188
2730
  });
@@ -2249,7 +2791,7 @@ async function stackHealthCommand(verbose = false) {
2249
2791
  let logs;
2250
2792
  if (!ok && verbose) {
2251
2793
  try {
2252
- logs = execSync3(`docker logs ${container.name} --tail 10 2>&1`, {
2794
+ logs = execSync4(`docker logs ${container.name} --tail 10 2>&1`, {
2253
2795
  encoding: "utf-8",
2254
2796
  stdio: ["pipe", "pipe", "pipe"]
2255
2797
  });
@@ -2300,7 +2842,7 @@ function stackLogsCommand(service, tail = 50) {
2300
2842
  };
2301
2843
  const container = containerMap[service] || `squads-${service}`;
2302
2844
  try {
2303
- execSync3(`docker logs ${container} --tail ${tail}`, { stdio: "inherit" });
2845
+ execSync4(`docker logs ${container} --tail ${tail}`, { stdio: "inherit" });
2304
2846
  } catch {
2305
2847
  writeLine(` ${colors.red}${icons.error}${RESET} Container ${container} not found`);
2306
2848
  }
@@ -2318,7 +2860,7 @@ async function stackDownCommand() {
2318
2860
  writeLine(` ${colors.cyan}${icons.progress}${RESET} Stopping containers...`);
2319
2861
  writeLine();
2320
2862
  try {
2321
- const child = spawn2("docker-compose", ["down"], {
2863
+ const child = spawn3("docker-compose", ["down"], {
2322
2864
  cwd: composeDir,
2323
2865
  stdio: "inherit"
2324
2866
  });
@@ -2343,6 +2885,7 @@ async function stackDownCommand() {
2343
2885
  var SQUADS_BRIDGE_URL = process.env.SQUADS_BRIDGE_URL || "http://localhost:8088";
2344
2886
  var MEM0_API_URL = process.env.MEM0_API_URL || "http://localhost:8000";
2345
2887
  async function memoryQueryCommand(query, options) {
2888
+ await track(Events.CLI_MEMORY_QUERY, { squad: options.squad, agent: options.agent });
2346
2889
  const memoryDir = findMemoryDir();
2347
2890
  if (!memoryDir) {
2348
2891
  writeLine(` ${colors.red}No .agents/memory directory found${RESET}`);
@@ -2403,6 +2946,7 @@ async function memoryQueryCommand(query, options) {
2403
2946
  writeLine();
2404
2947
  }
2405
2948
  async function memoryShowCommand(squadName, _options) {
2949
+ await track(Events.CLI_MEMORY_SHOW, { squad: squadName });
2406
2950
  const memoryDir = findMemoryDir();
2407
2951
  if (!memoryDir) {
2408
2952
  writeLine(` ${colors.red}No .agents/memory directory found${RESET}`);
@@ -2434,6 +2978,7 @@ async function memoryShowCommand(squadName, _options) {
2434
2978
  writeLine();
2435
2979
  }
2436
2980
  async function memoryUpdateCommand(squadName, content, options) {
2981
+ await track(Events.CLI_MEMORY_UPDATE, { squad: squadName, agent: options.agent, type: options.type });
2437
2982
  const agentName = options.agent || `${squadName}-lead`;
2438
2983
  const type = options.type || "learnings";
2439
2984
  writeLine();
@@ -2447,6 +2992,7 @@ async function memoryUpdateCommand(squadName, content, options) {
2447
2992
  writeLine();
2448
2993
  }
2449
2994
  async function memoryListCommand() {
2995
+ await track(Events.CLI_MEMORY_LIST);
2450
2996
  const memoryDir = findMemoryDir();
2451
2997
  if (!memoryDir) {
2452
2998
  writeLine(` ${colors.red}No .agents/memory directory found${RESET}`);
@@ -2683,7 +3229,7 @@ async function memoryExtractCommand(options = {}) {
2683
3229
  }
2684
3230
 
2685
3231
  // src/commands/sync.ts
2686
- import { execSync as execSync4 } from "child_process";
3232
+ import { execSync as execSync5 } from "child_process";
2687
3233
  import { existsSync as existsSync8, readFileSync as readFileSync6, writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, readdirSync as readdirSync2 } from "fs";
2688
3234
  import { join as join8 } from "path";
2689
3235
  var PATH_TO_SQUAD = {
@@ -2727,7 +3273,7 @@ function getRecentCommits(since) {
2727
3273
  const commits = [];
2728
3274
  try {
2729
3275
  const sinceArg = since ? `--since="${since}"` : "-n 20";
2730
- const logOutput = execSync4(
3276
+ const logOutput = execSync5(
2731
3277
  `git log ${sinceArg} --format="%H|%aI|%s" --name-only`,
2732
3278
  { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
2733
3279
  ).trim();
@@ -2748,7 +3294,7 @@ function getRecentCommits(since) {
2748
3294
  });
2749
3295
  }
2750
3296
  }
2751
- } catch (error) {
3297
+ } catch {
2752
3298
  }
2753
3299
  return commits;
2754
3300
  }
@@ -2825,8 +3371,8 @@ Updated: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}
2825
3371
  }
2826
3372
  function gitPullMemory() {
2827
3373
  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"] });
3374
+ execSync5("git fetch origin", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
3375
+ const status = execSync5("git status -sb", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
2830
3376
  const behindMatch = status.match(/behind (\d+)/);
2831
3377
  const aheadMatch = status.match(/ahead (\d+)/);
2832
3378
  const behind = behindMatch ? parseInt(behindMatch[1]) : 0;
@@ -2834,7 +3380,7 @@ function gitPullMemory() {
2834
3380
  if (behind === 0) {
2835
3381
  return { success: true, output: "Already up to date", behind: 0, ahead };
2836
3382
  }
2837
- const output = execSync4("git pull --rebase origin main", {
3383
+ const output = execSync5("git pull --rebase origin main", {
2838
3384
  encoding: "utf-8",
2839
3385
  stdio: ["pipe", "pipe", "pipe"]
2840
3386
  });
@@ -2846,18 +3392,18 @@ function gitPullMemory() {
2846
3392
  }
2847
3393
  function gitPushMemory() {
2848
3394
  try {
2849
- const status = execSync4("git status --porcelain .agents/memory/", {
3395
+ const status = execSync5("git status --porcelain .agents/memory/", {
2850
3396
  encoding: "utf-8",
2851
3397
  stdio: ["pipe", "pipe", "pipe"]
2852
3398
  }).trim();
2853
3399
  if (status) {
2854
- execSync4("git add .agents/memory/", { stdio: ["pipe", "pipe", "pipe"] });
2855
- execSync4('git commit -m "chore: sync squad memory"', {
3400
+ execSync5("git add .agents/memory/", { stdio: ["pipe", "pipe", "pipe"] });
3401
+ execSync5('git commit -m "chore: sync squad memory"', {
2856
3402
  encoding: "utf-8",
2857
3403
  stdio: ["pipe", "pipe", "pipe"]
2858
3404
  });
2859
3405
  }
2860
- const output = execSync4("git push origin main", {
3406
+ const output = execSync5("git push origin main", {
2861
3407
  encoding: "utf-8",
2862
3408
  stdio: ["pipe", "pipe", "pipe"]
2863
3409
  });
@@ -2868,8 +3414,9 @@ function gitPushMemory() {
2868
3414
  }
2869
3415
  }
2870
3416
  async function syncCommand(options = {}) {
3417
+ await track(Events.CLI_MEMORY_SYNC, { push: options.push, pull: options.pull });
2871
3418
  const memoryDir = findMemoryDir();
2872
- const squadsDir = findSquadsDir();
3419
+ const _squadsDir = findSquadsDir();
2873
3420
  if (!memoryDir) {
2874
3421
  writeLine(` ${colors.yellow}No .agents/memory directory found${RESET}`);
2875
3422
  writeLine(` ${colors.dim}Run \`squads init\` to create one.${RESET}`);
@@ -2958,6 +3505,7 @@ async function syncCommand(options = {}) {
2958
3505
 
2959
3506
  // src/commands/goal.ts
2960
3507
  async function goalSetCommand(squadName, description, options) {
3508
+ await track(Events.CLI_GOAL_SET, { squad: squadName });
2961
3509
  const squad = loadSquad(squadName);
2962
3510
  if (!squad) {
2963
3511
  writeLine(` ${colors.red}Squad "${squadName}" not found${RESET}`);
@@ -2981,6 +3529,7 @@ async function goalSetCommand(squadName, description, options) {
2981
3529
  writeLine();
2982
3530
  }
2983
3531
  async function goalListCommand(squadName, options = {}) {
3532
+ await track(Events.CLI_GOAL_LIST, { squad: squadName || "all" });
2984
3533
  const squadsDir = findSquadsDir();
2985
3534
  if (!squadsDir) {
2986
3535
  writeLine(` ${colors.red}No .agents/squads directory found${RESET}`);
@@ -3037,15 +3586,21 @@ async function goalListCommand(squadName, options = {}) {
3037
3586
  writeLine();
3038
3587
  }
3039
3588
  async function goalCompleteCommand(squadName, goalIndex) {
3589
+ await track(Events.CLI_GOAL_COMPLETE, { squad: squadName });
3040
3590
  const squad = loadSquad(squadName);
3041
3591
  if (!squad) {
3042
3592
  writeLine(` ${colors.red}Squad "${squadName}" not found${RESET}`);
3043
3593
  return;
3044
3594
  }
3045
3595
  const idx = parseInt(goalIndex) - 1;
3046
- if (idx < 0 || idx >= squad.goals.length) {
3596
+ if (isNaN(idx) || idx < 0 || idx >= squad.goals.length) {
3047
3597
  writeLine(` ${colors.red}Invalid goal index: ${goalIndex}${RESET}`);
3048
- writeLine(` ${colors.dim}Squad has ${squad.goals.length} goal(s)${RESET}`);
3598
+ if (squad.goals.length === 0) {
3599
+ writeLine(` ${colors.dim}Squad has no goals${RESET}`);
3600
+ } else {
3601
+ writeLine(` ${colors.dim}Valid indexes: 1-${squad.goals.length}${RESET}`);
3602
+ writeLine(` ${colors.dim}Tip: Run 'squads goal list ${squadName}' to see goals with indexes${RESET}`);
3603
+ }
3049
3604
  return;
3050
3605
  }
3051
3606
  const success = updateGoalInSquad(squadName, idx, { completed: true });
@@ -3058,14 +3613,21 @@ async function goalCompleteCommand(squadName, goalIndex) {
3058
3613
  writeLine();
3059
3614
  }
3060
3615
  async function goalProgressCommand(squadName, goalIndex, progress2) {
3616
+ await track(Events.CLI_GOAL_PROGRESS, { squad: squadName });
3061
3617
  const squad = loadSquad(squadName);
3062
3618
  if (!squad) {
3063
3619
  writeLine(` ${colors.red}Squad "${squadName}" not found${RESET}`);
3064
3620
  return;
3065
3621
  }
3066
3622
  const idx = parseInt(goalIndex) - 1;
3067
- if (idx < 0 || idx >= squad.goals.length) {
3623
+ if (isNaN(idx) || idx < 0 || idx >= squad.goals.length) {
3068
3624
  writeLine(` ${colors.red}Invalid goal index: ${goalIndex}${RESET}`);
3625
+ if (squad.goals.length === 0) {
3626
+ writeLine(` ${colors.dim}Squad has no goals${RESET}`);
3627
+ } else {
3628
+ writeLine(` ${colors.dim}Valid indexes: 1-${squad.goals.length}${RESET}`);
3629
+ writeLine(` ${colors.dim}Tip: Run 'squads goal list ${squadName}' to see goals with indexes${RESET}`);
3630
+ }
3069
3631
  return;
3070
3632
  }
3071
3633
  const success = updateGoalInSquad(squadName, idx, { progress: progress2 });
@@ -3081,7 +3643,7 @@ async function goalProgressCommand(squadName, goalIndex, progress2) {
3081
3643
 
3082
3644
  // src/commands/feedback.ts
3083
3645
  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";
3646
+ import { join as join9, dirname as dirname4 } from "path";
3085
3647
  function getFeedbackPath(squadName) {
3086
3648
  const memoryDir = findMemoryDir();
3087
3649
  if (!memoryDir) return null;
@@ -3141,6 +3703,7 @@ function parseFeedbackHistory(content) {
3141
3703
  return entries;
3142
3704
  }
3143
3705
  async function feedbackAddCommand(squadName, rating, feedback2, options) {
3706
+ await track(Events.CLI_FEEDBACK_ADD, { squad: squadName, rating: parseInt(rating) });
3144
3707
  const feedbackPath = getFeedbackPath(squadName);
3145
3708
  if (!feedbackPath) {
3146
3709
  writeLine(` ${colors.red}Could not find memory directory${RESET}`);
@@ -3152,7 +3715,7 @@ async function feedbackAddCommand(squadName, rating, feedback2, options) {
3152
3715
  return;
3153
3716
  }
3154
3717
  const lastExec = getLastExecution(squadName);
3155
- const dir = dirname5(feedbackPath);
3718
+ const dir = dirname4(feedbackPath);
3156
3719
  if (!existsSync9(dir)) {
3157
3720
  mkdirSync6(dir, { recursive: true });
3158
3721
  }
@@ -3202,6 +3765,7 @@ _Date: ${date}_
3202
3765
  writeLine();
3203
3766
  }
3204
3767
  async function feedbackShowCommand(squadName, options) {
3768
+ await track(Events.CLI_FEEDBACK_SHOW, { squad: squadName });
3205
3769
  const feedbackPath = getFeedbackPath(squadName);
3206
3770
  if (!feedbackPath || !existsSync9(feedbackPath)) {
3207
3771
  writeLine(` ${colors.yellow}No feedback recorded for ${squadName}${RESET}`);
@@ -3234,6 +3798,7 @@ async function feedbackShowCommand(squadName, options) {
3234
3798
  }
3235
3799
  }
3236
3800
  async function feedbackStatsCommand() {
3801
+ await track(Events.CLI_FEEDBACK_STATS);
3237
3802
  const memoryDir = findMemoryDir();
3238
3803
  if (!memoryDir) {
3239
3804
  writeLine(` ${colors.red}Could not find memory directory${RESET}`);
@@ -3281,19 +3846,323 @@ import { readdirSync as readdirSync4, existsSync as existsSync10, statSync as st
3281
3846
  import { join as join10 } from "path";
3282
3847
  import { homedir as homedir4 } from "os";
3283
3848
 
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 }
3849
+ // src/lib/providers.ts
3850
+ var PROVIDERS = {
3851
+ anthropic: {
3852
+ name: "anthropic",
3853
+ displayName: "Anthropic",
3854
+ envVars: ["ANTHROPIC_API_KEY"],
3855
+ modelPatterns: [/^claude/i],
3856
+ defaultPricing: { input: 3, output: 15 },
3857
+ models: {
3858
+ "claude-opus-4-5": { input: 15, output: 75 },
3859
+ "claude-opus-4-5-20251101": { input: 15, output: 75 },
3860
+ "claude-sonnet-4": { input: 3, output: 15 },
3861
+ "claude-sonnet-4-20250514": { input: 3, output: 15 },
3862
+ "claude-3-5-sonnet": { input: 3, output: 15 },
3863
+ "claude-3-5-sonnet-20241022": { input: 3, output: 15 },
3864
+ "claude-3-5-sonnet-20240620": { input: 3, output: 15 },
3865
+ "claude-haiku-3-5": { input: 0.8, output: 4 },
3866
+ "claude-3-5-haiku": { input: 0.8, output: 4 },
3867
+ "claude-3-5-haiku-20241022": { input: 0.8, output: 4 },
3868
+ "claude-3-opus": { input: 15, output: 75 },
3869
+ "claude-3-sonnet": { input: 3, output: 15 },
3870
+ "claude-3-haiku": { input: 0.25, output: 1.25 }
3871
+ }
3872
+ },
3873
+ openai: {
3874
+ name: "openai",
3875
+ displayName: "OpenAI",
3876
+ envVars: ["OPENAI_API_KEY"],
3877
+ modelPatterns: [/^gpt-/i, /^o1/i, /^o3/i],
3878
+ defaultPricing: { input: 2.5, output: 10 },
3879
+ models: {
3880
+ "gpt-4o": { input: 2.5, output: 10 },
3881
+ "gpt-4o-2024-11-20": { input: 2.5, output: 10 },
3882
+ "gpt-4o-2024-08-06": { input: 2.5, output: 10 },
3883
+ "gpt-4o-mini": { input: 0.15, output: 0.6 },
3884
+ "gpt-4o-mini-2024-07-18": { input: 0.15, output: 0.6 },
3885
+ "gpt-4-turbo": { input: 10, output: 30 },
3886
+ "gpt-4-turbo-2024-04-09": { input: 10, output: 30 },
3887
+ "gpt-4": { input: 30, output: 60 },
3888
+ "gpt-4-32k": { input: 60, output: 120 },
3889
+ "gpt-3.5-turbo": { input: 0.5, output: 1.5 },
3890
+ "gpt-3.5-turbo-0125": { input: 0.5, output: 1.5 },
3891
+ o1: { input: 15, output: 60 },
3892
+ "o1-2024-12-17": { input: 15, output: 60 },
3893
+ "o1-preview": { input: 15, output: 60 },
3894
+ "o1-mini": { input: 3, output: 12 },
3895
+ "o1-mini-2024-09-12": { input: 3, output: 12 },
3896
+ "o3-mini": { input: 1.1, output: 4.4 }
3897
+ }
3898
+ },
3899
+ google: {
3900
+ name: "google",
3901
+ displayName: "Google",
3902
+ envVars: ["GEMINI_API_KEY", "GOOGLE_API_KEY"],
3903
+ modelPatterns: [/^gemini/i],
3904
+ defaultPricing: { input: 0.1, output: 0.4 },
3905
+ models: {
3906
+ "gemini-2.0-flash": { input: 0.1, output: 0.4 },
3907
+ "gemini-2.0-flash-exp": { input: 0.1, output: 0.4 },
3908
+ "gemini-1.5-pro": { input: 1.25, output: 5 },
3909
+ "gemini-1.5-pro-002": { input: 1.25, output: 5 },
3910
+ "gemini-1.5-flash": { input: 0.075, output: 0.3 },
3911
+ "gemini-1.5-flash-002": { input: 0.075, output: 0.3 },
3912
+ "gemini-1.0-pro": { input: 0.5, output: 1.5 }
3913
+ }
3914
+ },
3915
+ mistral: {
3916
+ name: "mistral",
3917
+ displayName: "Mistral",
3918
+ envVars: ["MISTRAL_API_KEY"],
3919
+ modelPatterns: [/^mistral/i, /^mixtral/i, /^codestral/i],
3920
+ defaultPricing: { input: 2, output: 6 },
3921
+ models: {
3922
+ "mistral-large": { input: 2, output: 6 },
3923
+ "mistral-large-2411": { input: 2, output: 6 },
3924
+ "mistral-medium": { input: 2.7, output: 8.1 },
3925
+ "mistral-small": { input: 0.2, output: 0.6 },
3926
+ "mistral-small-2409": { input: 0.2, output: 0.6 },
3927
+ "mixtral-8x7b": { input: 0.7, output: 0.7 },
3928
+ "mixtral-8x22b": { input: 2, output: 6 },
3929
+ codestral: { input: 0.2, output: 0.6 },
3930
+ "codestral-2405": { input: 0.2, output: 0.6 }
3931
+ }
3932
+ },
3933
+ groq: {
3934
+ name: "groq",
3935
+ displayName: "Groq",
3936
+ envVars: ["GROQ_API_KEY"],
3937
+ modelPatterns: [/^llama/i],
3938
+ // llama models on Groq
3939
+ defaultPricing: { input: 0.59, output: 0.79 },
3940
+ models: {
3941
+ "llama-3.3-70b-versatile": { input: 0.59, output: 0.79 },
3942
+ "llama-3.3-70b-specdec": { input: 0.59, output: 0.99 },
3943
+ "llama-3.1-70b-versatile": { input: 0.59, output: 0.79 },
3944
+ "llama-3.1-8b-instant": { input: 0.05, output: 0.08 },
3945
+ "llama-3-70b-8192": { input: 0.59, output: 0.79 },
3946
+ "llama-3-8b-8192": { input: 0.05, output: 0.08 },
3947
+ "mixtral-8x7b-32768": { input: 0.24, output: 0.24 },
3948
+ "gemma2-9b-it": { input: 0.2, output: 0.2 }
3949
+ }
3950
+ },
3951
+ aws_bedrock: {
3952
+ name: "aws_bedrock",
3953
+ displayName: "AWS Bedrock",
3954
+ envVars: ["AWS_ACCESS_KEY_ID"],
3955
+ modelPatterns: [/^anthropic\./i, /^amazon\./i, /^meta\./i, /^mistral\./i],
3956
+ defaultPricing: { input: 3, output: 15 },
3957
+ models: {
3958
+ "anthropic.claude-3-5-sonnet-20241022-v2:0": { input: 3, output: 15 },
3959
+ "anthropic.claude-3-5-sonnet-20240620-v1:0": { input: 3, output: 15 },
3960
+ "anthropic.claude-3-5-haiku-20241022-v1:0": { input: 0.8, output: 4 },
3961
+ "anthropic.claude-3-opus-20240229-v1:0": { input: 15, output: 75 },
3962
+ "anthropic.claude-3-sonnet-20240229-v1:0": { input: 3, output: 15 },
3963
+ "anthropic.claude-3-haiku-20240307-v1:0": { input: 0.25, output: 1.25 },
3964
+ "amazon.titan-text-premier-v1:0": { input: 0.5, output: 1.5 },
3965
+ "amazon.titan-text-express-v1": { input: 0.2, output: 0.6 },
3966
+ "amazon.titan-text-lite-v1": { input: 0.15, output: 0.2 },
3967
+ "meta.llama3-70b-instruct-v1:0": { input: 2.65, output: 3.5 },
3968
+ "meta.llama3-8b-instruct-v1:0": { input: 0.3, output: 0.6 },
3969
+ "meta.llama3-1-405b-instruct-v1:0": { input: 5.32, output: 16 },
3970
+ "mistral.mistral-large-2407-v1:0": { input: 4, output: 12 }
3971
+ }
3972
+ },
3973
+ azure: {
3974
+ name: "azure",
3975
+ displayName: "Azure OpenAI",
3976
+ envVars: ["AZURE_OPENAI_API_KEY"],
3977
+ modelPatterns: [],
3978
+ // Detected via endpoint, not model name
3979
+ defaultPricing: { input: 2.5, output: 10 },
3980
+ models: {
3981
+ // Same pricing as OpenAI (regional variations possible)
3982
+ "gpt-4o": { input: 2.5, output: 10 },
3983
+ "gpt-4o-mini": { input: 0.15, output: 0.6 },
3984
+ "gpt-4-turbo": { input: 10, output: 30 },
3985
+ "gpt-4": { input: 30, output: 60 },
3986
+ "gpt-35-turbo": { input: 0.5, output: 1.5 }
3987
+ // Azure uses different name
3988
+ }
3989
+ }
3292
3990
  };
3991
+ function detectProviderFromModel(model) {
3992
+ const modelLower = model.toLowerCase();
3993
+ if (modelLower.includes("anthropic.") || modelLower.includes("amazon.") || modelLower.includes("meta.") || modelLower.includes("mistral.")) {
3994
+ return "aws_bedrock";
3995
+ }
3996
+ if (modelLower.startsWith("claude")) {
3997
+ return "anthropic";
3998
+ }
3999
+ if (modelLower.startsWith("gpt-") || modelLower.startsWith("o1") || modelLower.startsWith("o3")) {
4000
+ if (process.env.AZURE_OPENAI_ENDPOINT || process.env.AZURE_OPENAI_API_KEY) {
4001
+ return "azure";
4002
+ }
4003
+ return "openai";
4004
+ }
4005
+ if (modelLower.startsWith("gemini")) {
4006
+ return "google";
4007
+ }
4008
+ if (modelLower.startsWith("mistral") || modelLower.startsWith("mixtral") || modelLower.startsWith("codestral")) {
4009
+ if (process.env.GROQ_API_KEY && !process.env.MISTRAL_API_KEY) {
4010
+ return "groq";
4011
+ }
4012
+ return "mistral";
4013
+ }
4014
+ if (modelLower.startsWith("llama") || modelLower.startsWith("gemma")) {
4015
+ return "groq";
4016
+ }
4017
+ return "unknown";
4018
+ }
4019
+ function detectProvidersFromEnv() {
4020
+ const detected = [];
4021
+ if (process.env.ANTHROPIC_API_KEY) {
4022
+ const tier = parseInt(process.env.ANTHROPIC_TIER || "0", 10);
4023
+ const budgetSet = process.env.ANTHROPIC_BUDGET_DAILY || process.env.SQUADS_DAILY_BUDGET;
4024
+ const explicitPlan = process.env.SQUADS_PLAN_TYPE?.toLowerCase();
4025
+ let plan = "Max";
4026
+ let confidence = "inferred";
4027
+ let reason = "API key detected";
4028
+ if (explicitPlan === "usage") {
4029
+ plan = "Usage";
4030
+ confidence = "explicit";
4031
+ reason = "SQUADS_PLAN_TYPE=usage";
4032
+ } else if (explicitPlan === "max") {
4033
+ plan = "Max";
4034
+ confidence = "explicit";
4035
+ reason = "SQUADS_PLAN_TYPE=max";
4036
+ } else if (budgetSet) {
4037
+ plan = "Usage";
4038
+ reason = `Budget set ($${budgetSet}/day)`;
4039
+ } else if (tier >= 4) {
4040
+ plan = "Max";
4041
+ reason = `Tier ${tier} (high usage)`;
4042
+ } else if (tier >= 1 && tier <= 2) {
4043
+ plan = "Usage";
4044
+ reason = `Tier ${tier} (new user)`;
4045
+ }
4046
+ detected.push({ provider: "anthropic", plan, confidence, reason });
4047
+ }
4048
+ if (process.env.OPENAI_API_KEY) {
4049
+ const orgId = process.env.OPENAI_ORG_ID;
4050
+ detected.push({
4051
+ provider: "openai",
4052
+ plan: orgId ? "Team/Enterprise" : "Personal",
4053
+ confidence: "inferred",
4054
+ reason: orgId ? "Org ID set" : "API key only"
4055
+ });
4056
+ }
4057
+ if (process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY) {
4058
+ detected.push({
4059
+ provider: "google",
4060
+ plan: "Pay-as-you-go",
4061
+ confidence: "inferred",
4062
+ reason: "API key detected"
4063
+ });
4064
+ }
4065
+ if (process.env.AWS_ACCESS_KEY_ID) {
4066
+ detected.push({
4067
+ provider: "aws_bedrock",
4068
+ plan: "On-demand",
4069
+ confidence: "inferred",
4070
+ reason: "AWS credentials detected"
4071
+ });
4072
+ }
4073
+ if (process.env.AZURE_OPENAI_API_KEY) {
4074
+ detected.push({
4075
+ provider: "azure",
4076
+ plan: process.env.AZURE_OPENAI_DEPLOYMENT ? "PTU" : "Pay-as-you-go",
4077
+ confidence: "inferred",
4078
+ reason: process.env.AZURE_OPENAI_DEPLOYMENT ? "Deployment detected" : "API key only"
4079
+ });
4080
+ }
4081
+ if (process.env.MISTRAL_API_KEY) {
4082
+ detected.push({
4083
+ provider: "mistral",
4084
+ plan: "Pay-per-token",
4085
+ confidence: "inferred",
4086
+ reason: "API key detected"
4087
+ });
4088
+ }
4089
+ if (process.env.GROQ_API_KEY) {
4090
+ detected.push({
4091
+ provider: "groq",
4092
+ plan: "Developer",
4093
+ confidence: "inferred",
4094
+ reason: "API key detected"
4095
+ });
4096
+ }
4097
+ return detected;
4098
+ }
4099
+ function getModelPricing(provider, model) {
4100
+ if (provider === "unknown") {
4101
+ return { input: 3, output: 15 };
4102
+ }
4103
+ const config2 = PROVIDERS[provider];
4104
+ const modelLower = model.toLowerCase();
4105
+ if (config2.models[model]) {
4106
+ return config2.models[model];
4107
+ }
4108
+ const matchedModel = Object.keys(config2.models).find(
4109
+ (m) => m.toLowerCase() === modelLower
4110
+ );
4111
+ if (matchedModel) {
4112
+ return config2.models[matchedModel];
4113
+ }
4114
+ const partialMatch = Object.keys(config2.models).find(
4115
+ (m) => modelLower.includes(m.toLowerCase()) || m.toLowerCase().includes(modelLower)
4116
+ );
4117
+ if (partialMatch) {
4118
+ return config2.models[partialMatch];
4119
+ }
4120
+ return config2.defaultPricing;
4121
+ }
4122
+ function calcCost(provider, model, inputTokens, outputTokens, cachedTokens = 0) {
4123
+ const pricing = getModelPricing(provider, model);
4124
+ const inputCost = inputTokens / 1e6 * pricing.input;
4125
+ const outputCost = outputTokens / 1e6 * pricing.output;
4126
+ const cachedCost = pricing.cached ? cachedTokens / 1e6 * pricing.cached : 0;
4127
+ return inputCost + outputCost + cachedCost;
4128
+ }
4129
+ function getProviderDisplayName(provider) {
4130
+ if (provider === "unknown") return "Unknown";
4131
+ return PROVIDERS[provider].displayName;
4132
+ }
4133
+
4134
+ // src/lib/costs.ts
3293
4135
  var DEFAULT_DAILY_BUDGET = 200;
3294
4136
  var DEFAULT_DAILY_CALL_LIMIT = 1e3;
3295
4137
  var BRIDGE_URL = process.env.SQUADS_BRIDGE_URL || "http://localhost:8088";
3296
4138
  var FETCH_TIMEOUT_MS = 2e3;
4139
+ function detectPlan() {
4140
+ const explicitPlan = process.env.SQUADS_PLAN_TYPE?.toLowerCase();
4141
+ if (explicitPlan === "usage") {
4142
+ return { plan: "usage", confidence: "explicit", reason: "SQUADS_PLAN_TYPE=usage" };
4143
+ }
4144
+ if (explicitPlan === "max") {
4145
+ return { plan: "max", confidence: "explicit", reason: "SQUADS_PLAN_TYPE=max" };
4146
+ }
4147
+ const budgetSet = process.env.ANTHROPIC_BUDGET_DAILY || process.env.SQUADS_DAILY_BUDGET;
4148
+ if (budgetSet) {
4149
+ return { plan: "usage", confidence: "inferred", reason: `Budget set ($${budgetSet}/day)` };
4150
+ }
4151
+ const tier = parseInt(process.env.ANTHROPIC_TIER || "0", 10);
4152
+ if (tier >= 4) {
4153
+ return { plan: "max", confidence: "inferred", reason: `Tier ${tier} (high usage)` };
4154
+ }
4155
+ if (tier >= 1 && tier <= 2) {
4156
+ return { plan: "usage", confidence: "inferred", reason: `Tier ${tier} (new user)` };
4157
+ }
4158
+ return { plan: "max", confidence: "inferred", reason: "Default (no config)" };
4159
+ }
4160
+ function getPlanType() {
4161
+ return detectPlan().plan;
4162
+ }
4163
+ function isMaxPlan() {
4164
+ return getPlanType() === "max";
4165
+ }
3297
4166
  async function fetchWithTimeout(url, options = {}, timeoutMs = FETCH_TIMEOUT_MS) {
3298
4167
  const controller = new AbortController();
3299
4168
  const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
@@ -3306,9 +4175,9 @@ async function fetchWithTimeout(url, options = {}, timeoutMs = FETCH_TIMEOUT_MS)
3306
4175
  throw error;
3307
4176
  }
3308
4177
  }
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;
4178
+ function calcCost2(model, inputTokens, outputTokens) {
4179
+ const provider = detectProviderFromModel(model);
4180
+ return calcCost(provider, model, inputTokens, outputTokens);
3312
4181
  }
3313
4182
  async function fetchFromBridge(period = "day") {
3314
4183
  try {
@@ -3336,6 +4205,20 @@ async function fetchFromBridge(period = "day") {
3336
4205
  const totalInputTokens = bySquad.reduce((sum, s) => sum + s.inputTokens, 0);
3337
4206
  const totalAllInput = totalInputTokens + totalCachedTokens;
3338
4207
  const cacheHitRate = totalAllInput > 0 ? totalCachedTokens / totalAllInput * 100 : 0;
4208
+ const detectedProviders = detectProvidersFromEnv();
4209
+ const byProvider = detectedProviders.map((p) => ({
4210
+ provider: p.provider,
4211
+ displayName: getProviderDisplayName(p.provider),
4212
+ calls: 0,
4213
+ // Bridge doesn't track by provider yet
4214
+ inputTokens: 0,
4215
+ outputTokens: 0,
4216
+ cost: p.provider === "anthropic" ? totalCost : 0,
4217
+ // Assume all cost is Anthropic for now
4218
+ plan: p.plan,
4219
+ confidence: p.confidence,
4220
+ reason: p.reason
4221
+ }));
3339
4222
  return {
3340
4223
  totalCost,
3341
4224
  dailyBudget,
@@ -3348,6 +4231,7 @@ async function fetchFromBridge(period = "day") {
3348
4231
  totalInputTokens,
3349
4232
  cacheHitRate,
3350
4233
  bySquad,
4234
+ byProvider,
3351
4235
  source: "postgres"
3352
4236
  };
3353
4237
  } catch {
@@ -3384,7 +4268,7 @@ async function fetchFromLangfuse(limit = 100) {
3384
4268
  const usage = obs.usage || {};
3385
4269
  const inputTokens = usage.input || 0;
3386
4270
  const outputTokens = usage.output || 0;
3387
- const cost = calcCost(model, inputTokens, outputTokens);
4271
+ const cost = calcCost2(model, inputTokens, outputTokens);
3388
4272
  if (!bySquad[squad]) {
3389
4273
  bySquad[squad] = {
3390
4274
  squad,
@@ -3411,6 +4295,35 @@ async function fetchFromLangfuse(limit = 100) {
3411
4295
  const totalInputTokens = squadList.reduce((sum, s) => sum + s.inputTokens, 0);
3412
4296
  const totalAllInput = totalInputTokens + totalCachedTokens;
3413
4297
  const cacheHitRate = totalAllInput > 0 ? totalCachedTokens / totalAllInput * 100 : 0;
4298
+ const providerMap = {};
4299
+ for (const obs of observations) {
4300
+ if (obs.type !== "GENERATION") continue;
4301
+ const model = obs.model || "unknown";
4302
+ const provider = detectProviderFromModel(model);
4303
+ const usage = obs.usage || {};
4304
+ const inputTokens = usage.input || 0;
4305
+ const outputTokens = usage.output || 0;
4306
+ const cost = calcCost2(model, inputTokens, outputTokens);
4307
+ if (!providerMap[provider]) {
4308
+ const detection = detectProvidersFromEnv().find((p) => p.provider === provider);
4309
+ providerMap[provider] = {
4310
+ provider,
4311
+ displayName: getProviderDisplayName(provider),
4312
+ calls: 0,
4313
+ inputTokens: 0,
4314
+ outputTokens: 0,
4315
+ cost: 0,
4316
+ plan: detection?.plan,
4317
+ confidence: detection?.confidence,
4318
+ reason: detection?.reason
4319
+ };
4320
+ }
4321
+ providerMap[provider].calls += 1;
4322
+ providerMap[provider].inputTokens += inputTokens;
4323
+ providerMap[provider].outputTokens += outputTokens;
4324
+ providerMap[provider].cost += cost;
4325
+ }
4326
+ const byProvider = Object.values(providerMap).sort((a, b) => b.cost - a.cost);
3414
4327
  return {
3415
4328
  totalCost,
3416
4329
  dailyBudget,
@@ -3423,6 +4336,7 @@ async function fetchFromLangfuse(limit = 100) {
3423
4336
  totalInputTokens,
3424
4337
  cacheHitRate,
3425
4338
  bySquad: squadList,
4339
+ byProvider,
3426
4340
  source: "langfuse"
3427
4341
  };
3428
4342
  } catch {
@@ -3439,6 +4353,18 @@ async function fetchCostSummary(limit = 100, period = "day") {
3439
4353
  return langfuseResult;
3440
4354
  }
3441
4355
  const defaultBudget = parseFloat(process.env.SQUADS_DAILY_BUDGET || "") || DEFAULT_DAILY_BUDGET;
4356
+ const detectedProviders = detectProvidersFromEnv();
4357
+ const byProvider = detectedProviders.map((p) => ({
4358
+ provider: p.provider,
4359
+ displayName: getProviderDisplayName(p.provider),
4360
+ calls: 0,
4361
+ inputTokens: 0,
4362
+ outputTokens: 0,
4363
+ cost: 0,
4364
+ plan: p.plan,
4365
+ confidence: p.confidence,
4366
+ reason: p.reason
4367
+ }));
3442
4368
  return {
3443
4369
  totalCost: 0,
3444
4370
  dailyBudget: defaultBudget,
@@ -3451,28 +4377,19 @@ async function fetchCostSummary(limit = 100, period = "day") {
3451
4377
  totalInputTokens: 0,
3452
4378
  cacheHitRate: 0,
3453
4379
  bySquad: [],
4380
+ byProvider,
3454
4381
  source: "none"
3455
4382
  };
3456
4383
  }
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
4384
  async function fetchBridgeStats() {
3463
4385
  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([
4386
+ const [statsResponse, healthResponse, costResponse, weekResponse] = await Promise.all([
4387
+ fetchWithTimeout(`${BRIDGE_URL}/stats`, {
4388
+ headers: { "Content-Type": "application/json" }
4389
+ }),
4390
+ fetchWithTimeout(`${BRIDGE_URL}/health`, {
4391
+ headers: { "Content-Type": "application/json" }
4392
+ }),
3476
4393
  fetchWithTimeout(`${BRIDGE_URL}/api/cost/summary?period=day`, {
3477
4394
  headers: { "Content-Type": "application/json" }
3478
4395
  }),
@@ -3480,8 +4397,15 @@ async function fetchBridgeStats() {
3480
4397
  headers: { "Content-Type": "application/json" }
3481
4398
  })
3482
4399
  ]);
3483
- const costData = costResponse.ok ? await costResponse.json() : {};
3484
- const weekData = weekResponse.ok ? await weekResponse.json() : {};
4400
+ if (!statsResponse.ok) {
4401
+ return null;
4402
+ }
4403
+ const [stats, health, costData, weekData] = await Promise.all([
4404
+ statsResponse.json(),
4405
+ healthResponse.ok ? healthResponse.json() : Promise.resolve({}),
4406
+ costResponse.ok ? costResponse.json() : Promise.resolve({}),
4407
+ weekResponse.ok ? weekResponse.json() : Promise.resolve({})
4408
+ ]);
3485
4409
  return {
3486
4410
  status: stats.status || "unknown",
3487
4411
  source: stats.source || "none",
@@ -3528,6 +4452,38 @@ async function fetchBridgeStats() {
3528
4452
  return null;
3529
4453
  }
3530
4454
  }
4455
+ async function fetchRateLimits() {
4456
+ try {
4457
+ const response = await fetchWithTimeout(`${BRIDGE_URL}/api/rate-limits`, {
4458
+ headers: { "Content-Type": "application/json" }
4459
+ });
4460
+ if (!response.ok) {
4461
+ return { limits: {}, source: "none" };
4462
+ }
4463
+ const data = await response.json();
4464
+ const rateLimits = data.rate_limits || {};
4465
+ const limits = {};
4466
+ for (const [key, value] of Object.entries(rateLimits)) {
4467
+ limits[key] = {
4468
+ model: value.model || key,
4469
+ requestsLimit: value.requests_limit || 0,
4470
+ requestsRemaining: value.requests_remaining || 0,
4471
+ requestsReset: value.requests_reset,
4472
+ tokensLimit: value.tokens_limit || 0,
4473
+ tokensRemaining: value.tokens_remaining || 0,
4474
+ tokensReset: value.tokens_reset,
4475
+ inputTokensLimit: value.input_tokens_limit,
4476
+ inputTokensRemaining: value.input_tokens_remaining,
4477
+ outputTokensLimit: value.output_tokens_limit,
4478
+ outputTokensRemaining: value.output_tokens_remaining,
4479
+ capturedAt: value.captured_at || (/* @__PURE__ */ new Date()).toISOString()
4480
+ };
4481
+ }
4482
+ return { limits, source: "proxy" };
4483
+ } catch {
4484
+ return { limits: {}, source: "none" };
4485
+ }
4486
+ }
3531
4487
  async function fetchInsights(period = "week") {
3532
4488
  try {
3533
4489
  const response = await fetchWithTimeout(`${BRIDGE_URL}/api/insights?period=${period}`, {
@@ -3591,6 +4547,34 @@ async function fetchInsights(period = "week") {
3591
4547
  };
3592
4548
  }
3593
4549
  }
4550
+ async function fetchNpmStats(packageName = "squads-cli") {
4551
+ try {
4552
+ const [dayRes, weekRes, monthRes] = await Promise.all([
4553
+ fetch(`https://api.npmjs.org/downloads/point/last-day/${packageName}`),
4554
+ fetch(`https://api.npmjs.org/downloads/point/last-week/${packageName}`),
4555
+ fetch(`https://api.npmjs.org/downloads/point/last-month/${packageName}`)
4556
+ ]);
4557
+ if (!dayRes.ok || !weekRes.ok || !monthRes.ok) return null;
4558
+ const [dayData, weekData, monthData] = await Promise.all([
4559
+ dayRes.json(),
4560
+ weekRes.json(),
4561
+ monthRes.json()
4562
+ ]);
4563
+ const avgWeeklyFromMonth = monthData.downloads / 4;
4564
+ const weekOverWeek = avgWeeklyFromMonth > 0 ? Math.round((weekData.downloads - avgWeeklyFromMonth) / avgWeeklyFromMonth * 100) : 0;
4565
+ return {
4566
+ package: packageName,
4567
+ downloads: {
4568
+ lastDay: dayData.downloads,
4569
+ lastWeek: weekData.downloads,
4570
+ lastMonth: monthData.downloads
4571
+ },
4572
+ weekOverWeek
4573
+ };
4574
+ } catch {
4575
+ return null;
4576
+ }
4577
+ }
3594
4578
 
3595
4579
  // src/lib/db.ts
3596
4580
  import { createRequire } from "module";
@@ -3747,6 +4731,7 @@ function getLastActivityDate(squadName) {
3747
4731
  return `${Math.floor(ageDays / 7)}w`;
3748
4732
  }
3749
4733
  async function dashboardCommand(options = {}) {
4734
+ await track(Events.CLI_DASHBOARD, { verbose: options.verbose, ceo: options.ceo, fast: options.fast });
3750
4735
  const squadsDir = findSquadsDir();
3751
4736
  if (!squadsDir) {
3752
4737
  writeLine(`${colors.red}No .agents/squads directory found${RESET}`);
@@ -3760,7 +4745,8 @@ async function dashboardCommand(options = {}) {
3760
4745
  const squadNames = listSquads(squadsDir);
3761
4746
  const skipGitHub = options.fast !== false;
3762
4747
  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([
4748
+ cleanupStaleSessions();
4749
+ const [gitStats, ghStats, costs, bridgeStats, activity, dbAvailable, history, insights, sessionSummary, npmStats] = await Promise.all([
3764
4750
  // Git stats (local, ~1s)
3765
4751
  Promise.resolve(baseDir ? getMultiRepoGitStats(baseDir, 30) : null),
3766
4752
  // GitHub stats (network, ~20-30s) - skip by default for fast mode
@@ -3776,9 +4762,13 @@ async function dashboardCommand(options = {}) {
3776
4762
  // Dashboard history (1.5s timeout)
3777
4763
  timeout(getDashboardHistory(14).catch(() => []), 1500, []),
3778
4764
  // Insights (2s timeout)
3779
- timeout(fetchInsights("week").catch(() => null), 2e3, null)
4765
+ timeout(fetchInsights("week").catch(() => null), 2e3, null),
4766
+ // Session summary (parallel lsof, ~1s)
4767
+ getLiveSessionSummaryAsync(),
4768
+ // NPM download stats (network, 2s timeout)
4769
+ timeout(fetchNpmStats("squads-cli"), 2e3, null)
3780
4770
  ]);
3781
- const cache = { gitStats, ghStats, costs, bridgeStats, activity, dbAvailable, history, insights };
4771
+ const cache = { gitStats, ghStats, costs, bridgeStats, activity, dbAvailable, history, insights, sessionSummary, npmStats };
3782
4772
  const squadData = [];
3783
4773
  for (const name of squadNames) {
3784
4774
  const squad = loadSquad(name);
@@ -3840,8 +4830,6 @@ async function dashboardCommand(options = {}) {
3840
4830
  const totalPRs = ghStats ? ghStats.prsMerged : 0;
3841
4831
  const totalIssuesClosed = ghStats ? ghStats.issuesClosed : 0;
3842
4832
  const totalIssuesOpen = ghStats ? ghStats.issuesOpen : 0;
3843
- cleanupStaleSessions();
3844
- const sessionSummary = getLiveSessionSummary();
3845
4833
  writeLine();
3846
4834
  writeLine(` ${gradient("squads")} ${colors.dim}dashboard${RESET}`);
3847
4835
  const updateInfo = checkForUpdate();
@@ -3873,8 +4861,8 @@ async function dashboardCommand(options = {}) {
3873
4861
  const overallProgress = squadData.length > 0 ? Math.round(squadData.reduce((sum, s) => sum + s.goalProgress, 0) / squadData.length) : 0;
3874
4862
  writeLine(` ${progressBar(overallProgress, 32)} ${colors.dim}${overallProgress}% goal progress${RESET}`);
3875
4863
  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;
4864
+ const w = { name: 13, commits: 9, prs: 5, issues: 8, goals: 7, bar: 10 };
4865
+ const tableWidth = w.name + w.commits + w.prs + w.issues + w.goals + w.bar + 6;
3878
4866
  writeLine(` ${colors.purple}${box.topLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.topRight}${RESET}`);
3879
4867
  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
4868
  writeLine(` ${colors.purple}${box.teeRight}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.teeLeft}${RESET}`);
@@ -3898,9 +4886,14 @@ async function dashboardCommand(options = {}) {
3898
4886
  }
3899
4887
  writeLine(` ${colors.purple}${box.bottomLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.bottomRight}${RESET}`);
3900
4888
  writeLine();
4889
+ const goalCount = {
4890
+ active: squadData.reduce((sum, s) => sum + s.goals.filter((g) => !g.completed).length, 0),
4891
+ completed: squadData.reduce((sum, s) => sum + s.goals.filter((g) => g.completed).length, 0)
4892
+ };
3901
4893
  renderGitPerformanceCached(cache);
3902
- renderTokenEconomicsCached(cache);
4894
+ renderTokenEconomicsCached(cache, goalCount);
3903
4895
  renderInfrastructureCached(cache);
4896
+ renderAcquisitionCached(cache);
3904
4897
  renderHistoricalTrendsCached(cache);
3905
4898
  renderInsightsCached(cache);
3906
4899
  const allActiveGoals = squadData.flatMap(
@@ -3987,37 +4980,62 @@ function renderGitPerformanceCached(cache) {
3987
4980
  writeLine();
3988
4981
  }
3989
4982
  }
3990
- function renderTokenEconomicsCached(cache) {
4983
+ function renderTokenEconomicsCached(cache, goalCount) {
3991
4984
  const costs = cache.costs;
3992
- if (!costs) {
4985
+ const stats = cache.bridgeStats;
4986
+ if (!costs && !stats) {
3993
4987
  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}`);
4988
+ writeLine(` ${colors.dim}Set LANGFUSE_PUBLIC_KEY & LANGFUSE_SECRET_KEY for tracking${RESET}`);
3995
4989
  writeLine();
3996
4990
  return;
3997
4991
  }
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`);
4992
+ writeLine(` ${bold}Token Economics${RESET}`);
4004
4993
  writeLine();
4994
+ const maxPlan = isMaxPlan();
4005
4995
  const tier = parseInt(process.env.ANTHROPIC_TIER || "4", 10);
4006
- writeLine(` ${colors.dim}Rate Limits (Tier ${tier})${RESET}`);
4007
- 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}`);
4996
+ const planIcon = maxPlan ? `${colors.purple}\u25C6${RESET}` : `${colors.dim}\u25CB${RESET}`;
4997
+ writeLine(` ${planIcon} ${bold}Claude Max${RESET} ${colors.dim}$200/mo flat${RESET} ${colors.dim}Tier ${tier}${RESET}`);
4998
+ writeLine();
4999
+ const todayTokens = stats ? stats.today.inputTokens + stats.today.outputTokens : 0;
5000
+ const todayCalls = stats?.today.generations || costs?.totalCalls || 0;
5001
+ const todayCost = stats?.today.costUsd || costs?.totalCost || 0;
5002
+ writeLine(` ${colors.dim}Today${RESET}`);
5003
+ 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}`);
5004
+ if (stats?.week && stats.week.generations > 0) {
5005
+ const weekTokens = (stats.week.inputTokens || 0) + (stats.week.outputTokens || 0);
5006
+ 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}`);
5007
+ }
5008
+ writeLine();
5009
+ if (goalCount && goalCount.completed > 0 && todayTokens > 0) {
5010
+ const tokensPerGoal = Math.round(todayTokens / goalCount.completed);
5011
+ writeLine(` ${colors.dim}Efficiency${RESET}`);
5012
+ writeLine(` ${colors.cyan}${formatK(tokensPerGoal)}${RESET} tokens/goal ${colors.dim}\u2502${RESET} ${colors.green}${goalCount.completed}${RESET} goals done`);
5013
+ writeLine();
4018
5014
  }
4019
- if (costs.usedPercent > 80) {
4020
- writeLine(` ${colors.red}\u26A0${RESET} ${colors.red}${costs.usedPercent.toFixed(0)}% of daily budget used${RESET}`);
5015
+ writeLine(` ${colors.dim}Rate Limits${RESET} ${colors.dim}(Tier ${tier})${RESET}`);
5016
+ const tier4Limits = {
5017
+ rpm: 4e3,
5018
+ inputTpm: 4e5,
5019
+ outputTpm: 8e4
5020
+ };
5021
+ const now = /* @__PURE__ */ new Date();
5022
+ const minutesElapsed = Math.max(now.getHours() * 60 + now.getMinutes(), 1);
5023
+ const callsPerMinute = todayCalls / minutesElapsed;
5024
+ const tokensPerMinute = todayTokens / minutesElapsed;
5025
+ const rpmPct = callsPerMinute / tier4Limits.rpm * 100;
5026
+ const tpmPct = tokensPerMinute / (tier4Limits.inputTpm + tier4Limits.outputTpm) * 100;
5027
+ const rpmBar = progressBar(Math.min(rpmPct, 100), 10);
5028
+ const tpmBar = progressBar(Math.min(tpmPct, 100), 10);
5029
+ const rpmColor = rpmPct > 75 ? colors.red : rpmPct > 50 ? colors.yellow : colors.green;
5030
+ const tpmColor = tpmPct > 75 ? colors.red : tpmPct > 50 ? colors.yellow : colors.green;
5031
+ writeLine(` RPM ${rpmBar} ${rpmColor}${callsPerMinute.toFixed(1)}${RESET}${colors.dim}/${tier4Limits.rpm}${RESET}`);
5032
+ writeLine(` TPM ${tpmBar} ${tpmColor}${formatK(Math.round(tokensPerMinute))}${RESET}${colors.dim}/${formatK(tier4Limits.inputTpm + tier4Limits.outputTpm)}${RESET}`);
5033
+ const rpmAvailable = Math.max(0, tier4Limits.rpm - callsPerMinute);
5034
+ const tpmAvailable = Math.max(0, tier4Limits.inputTpm + tier4Limits.outputTpm - tokensPerMinute);
5035
+ if (rpmAvailable > 100 && tpmAvailable > 1e4) {
5036
+ writeLine(` ${colors.green}\u25CF${RESET} ${colors.dim}Capacity for autonomous triggers${RESET}`);
5037
+ } else if (rpmPct > 75 || tpmPct > 75) {
5038
+ writeLine(` ${colors.yellow}\u26A0${RESET} ${colors.yellow}Rate limits constrained${RESET}`);
4021
5039
  }
4022
5040
  writeLine();
4023
5041
  }
@@ -4025,7 +5043,10 @@ function renderInfrastructureCached(cache) {
4025
5043
  const stats = cache.bridgeStats;
4026
5044
  if (!stats) {
4027
5045
  writeLine(` ${bold}Infrastructure${RESET} ${colors.dim}(bridge offline)${RESET}`);
4028
- writeLine(` ${colors.dim}Start with: cd docker && docker-compose up -d${RESET}`);
5046
+ writeLine();
5047
+ writeLine(` ${colors.dim}Start with:${RESET} cd docker && docker-compose up -d`);
5048
+ writeLine(` ${colors.dim}Docs:${RESET} https://agents-squads.com/docs/setup`);
5049
+ writeLine(` ${colors.yellow}Need help?${RESET} ${colors.dim}jorge@agents-squads.com${RESET}`);
4029
5050
  writeLine();
4030
5051
  return;
4031
5052
  }
@@ -4038,8 +5059,10 @@ function renderInfrastructureCached(cache) {
4038
5059
  writeLine(` ${pgStatus} postgres ${redisStatus} redis ${otelStatus} otel`);
4039
5060
  writeLine();
4040
5061
  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}`);
5062
+ const maxPlan = isMaxPlan();
5063
+ const costColor = maxPlan ? colors.green : stats.budget.usedPct > 80 ? colors.red : stats.budget.usedPct > 50 ? colors.yellow : colors.green;
5064
+ const costDisplay = maxPlan ? `${costColor}$${stats.today.costUsd.toFixed(2)}${RESET}` : `${costColor}$${stats.today.costUsd.toFixed(2)}${RESET}${colors.dim}/$${stats.budget.daily}${RESET}`;
5065
+ 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
5066
  if (stats.byModel && stats.byModel.length > 0) {
4044
5067
  const modelLine = stats.byModel.map((m) => {
4045
5068
  const shortName = m.model.includes("opus") ? "opus" : m.model.includes("sonnet") ? "sonnet" : m.model.includes("haiku") ? "haiku" : m.model.slice(0, 10);
@@ -4057,7 +5080,22 @@ function renderInfrastructureCached(cache) {
4057
5080
  }
4058
5081
  writeLine();
4059
5082
  }
4060
- async function saveSnapshotCached(squadData, cache, baseDir) {
5083
+ function renderAcquisitionCached(cache) {
5084
+ const npm = cache.npmStats;
5085
+ if (!npm) {
5086
+ return;
5087
+ }
5088
+ writeLine(` ${bold}Acquisition${RESET} ${colors.dim}(npm)${RESET}`);
5089
+ writeLine();
5090
+ const trendIcon = npm.weekOverWeek >= 0 ? `${colors.green}\u2191${RESET}` : `${colors.red}\u2193${RESET}`;
5091
+ const trendColor = npm.weekOverWeek >= 0 ? colors.green : colors.red;
5092
+ writeLine(` ${colors.cyan}${npm.downloads.lastWeek}${RESET} installs/week ${trendIcon} ${trendColor}${Math.abs(npm.weekOverWeek)}%${RESET} ${colors.dim}wow${RESET}`);
5093
+ writeLine(` ${colors.dim}Today${RESET} ${npm.downloads.lastDay} ${colors.dim}\u2502${RESET} ${colors.dim}Month${RESET} ${npm.downloads.lastMonth}`);
5094
+ writeLine();
5095
+ writeLine(` ${colors.dim}GitHub \u2192 npm install \u2192 squads dash \u2192 ?${RESET}`);
5096
+ writeLine();
5097
+ }
5098
+ async function saveSnapshotCached(squadData, cache, _baseDir) {
4061
5099
  if (!cache.dbAvailable) return;
4062
5100
  const { gitStats, ghStats, costs } = cache;
4063
5101
  const squadsData = squadData.map((s) => ({
@@ -4261,7 +5299,10 @@ function renderInsightsCached(cache) {
4261
5299
  }
4262
5300
 
4263
5301
  // src/commands/issues.ts
4264
- import { execSync as execSync5 } from "child_process";
5302
+ import { execSync as execSync6 } from "child_process";
5303
+ function getLabelName(label) {
5304
+ return typeof label === "string" ? label : label.name;
5305
+ }
4265
5306
  var DEFAULT_ORG = "agents-squads";
4266
5307
  var DEFAULT_REPOS = ["hq", "agents-squads-web", "squads-cli"];
4267
5308
  async function issuesCommand(options = {}) {
@@ -4271,7 +5312,7 @@ async function issuesCommand(options = {}) {
4271
5312
  writeLine(` ${gradient("squads")} ${colors.dim}issues${RESET}`);
4272
5313
  writeLine();
4273
5314
  try {
4274
- execSync5("gh --version", { stdio: "pipe" });
5315
+ execSync6("gh --version", { stdio: "pipe" });
4275
5316
  } catch {
4276
5317
  writeLine(` ${colors.red}GitHub CLI (gh) not found${RESET}`);
4277
5318
  writeLine(` ${colors.dim}Install: brew install gh${RESET}`);
@@ -4282,14 +5323,14 @@ async function issuesCommand(options = {}) {
4282
5323
  let totalOpen = 0;
4283
5324
  for (const repo of repos) {
4284
5325
  try {
4285
- const result = execSync5(
5326
+ const result = execSync6(
4286
5327
  `gh issue list -R ${org}/${repo} --state open --json number,title,state,labels,createdAt --limit 50`,
4287
5328
  { stdio: "pipe", encoding: "utf-8" }
4288
5329
  );
4289
5330
  const issues = JSON.parse(result);
4290
5331
  repoData.push({ repo, issues });
4291
5332
  totalOpen += issues.length;
4292
- } catch (e) {
5333
+ } catch {
4293
5334
  repoData.push({ repo, issues: [], error: "not found or no access" });
4294
5335
  }
4295
5336
  }
@@ -4321,7 +5362,7 @@ async function issuesCommand(options = {}) {
4321
5362
  writeLine(` ${bold}Recent${RESET}`);
4322
5363
  writeLine();
4323
5364
  for (const issue of allIssues) {
4324
- const labelStr = issue.labels.length > 0 ? `${colors.dim}[${issue.labels.map((l) => l.name || l).join(", ")}]${RESET}` : "";
5365
+ const labelStr = issue.labels.length > 0 ? `${colors.dim}[${issue.labels.map(getLabelName).join(", ")}]${RESET}` : "";
4325
5366
  writeLine(` ${icons.empty} ${colors.dim}#${issue.number}${RESET} ${truncate(issue.title, 50)} ${labelStr}`);
4326
5367
  writeLine(` ${colors.dim}\u2514 ${issue.repo}${RESET}`);
4327
5368
  }
@@ -4333,7 +5374,7 @@ async function issuesCommand(options = {}) {
4333
5374
  }
4334
5375
 
4335
5376
  // src/commands/solve-issues.ts
4336
- import { execSync as execSync6, spawn as spawn3 } from "child_process";
5377
+ import { execSync as execSync7, spawn as spawn4 } from "child_process";
4337
5378
  import ora3 from "ora";
4338
5379
  var DEFAULT_ORG2 = "agents-squads";
4339
5380
  var DEFAULT_REPOS2 = ["hq", "agents-squads-web", "squads-cli", "agents-squads"];
@@ -4343,7 +5384,7 @@ async function solveIssuesCommand(options = {}) {
4343
5384
  writeLine(` ${gradient("squads")} ${colors.dim}solve-issues${RESET}`);
4344
5385
  writeLine();
4345
5386
  try {
4346
- execSync6("gh --version", { stdio: "pipe" });
5387
+ execSync7("gh --version", { stdio: "pipe" });
4347
5388
  } catch {
4348
5389
  writeLine(` ${colors.red}GitHub CLI (gh) not found${RESET}`);
4349
5390
  writeLine(` ${colors.dim}Install: brew install gh${RESET}`);
@@ -4353,7 +5394,7 @@ async function solveIssuesCommand(options = {}) {
4353
5394
  if (options.issue) {
4354
5395
  const repo = options.repo || "hq";
4355
5396
  try {
4356
- const result = execSync6(
5397
+ const result = execSync7(
4357
5398
  `gh issue view ${options.issue} -R ${DEFAULT_ORG2}/${repo} --json number,title,labels,body`,
4358
5399
  { stdio: "pipe", encoding: "utf-8" }
4359
5400
  );
@@ -4366,7 +5407,7 @@ async function solveIssuesCommand(options = {}) {
4366
5407
  } else {
4367
5408
  for (const repo of repos) {
4368
5409
  try {
4369
- const result = execSync6(
5410
+ const result = execSync7(
4370
5411
  `gh issue list -R ${DEFAULT_ORG2}/${repo} --label "ready-to-fix" --state open --json number,title,labels --limit 20`,
4371
5412
  { stdio: "pipe", encoding: "utf-8" }
4372
5413
  );
@@ -4420,7 +5461,7 @@ function showSolveInstructions(issues) {
4420
5461
  async function solveWithClaude(issues) {
4421
5462
  const spinner = ora3("Starting issue solver...").start();
4422
5463
  try {
4423
- execSync6("which claude", { stdio: "pipe" });
5464
+ execSync7("which claude", { stdio: "pipe" });
4424
5465
  } catch {
4425
5466
  spinner.fail("Claude CLI not found");
4426
5467
  writeLine(` ${colors.dim}Install: npm install -g @anthropic-ai/claude-code${RESET}`);
@@ -4479,7 +5520,7 @@ Return the PR URL when done.`;
4479
5520
  }
4480
5521
  function executeClaudePrompt(prompt2) {
4481
5522
  return new Promise((resolve, reject) => {
4482
- const claude = spawn3("claude", ["--print", prompt2], {
5523
+ const claude = spawn4("claude", ["--print", prompt2], {
4483
5524
  stdio: ["pipe", "pipe", "pipe"]
4484
5525
  });
4485
5526
  let output = "";
@@ -4506,7 +5547,7 @@ function executeClaudePrompt(prompt2) {
4506
5547
  }
4507
5548
 
4508
5549
  // src/commands/open-issues.ts
4509
- import { execSync as execSync7, spawn as spawn4 } from "child_process";
5550
+ import { execSync as execSync8, spawn as spawn5 } from "child_process";
4510
5551
  import { readdirSync as readdirSync5 } from "fs";
4511
5552
  import { join as join11 } from "path";
4512
5553
  import ora4 from "ora";
@@ -4602,7 +5643,7 @@ function showRunInstructions(agents) {
4602
5643
  async function runEvaluators(agents) {
4603
5644
  const spinner = ora4("Starting evaluators...").start();
4604
5645
  try {
4605
- execSync7("which claude", { stdio: "pipe" });
5646
+ execSync8("which claude", { stdio: "pipe" });
4606
5647
  } catch {
4607
5648
  spinner.fail("Claude CLI not found");
4608
5649
  writeLine(` ${colors.dim}Install: npm install -g @anthropic-ai/claude-code${RESET}`);
@@ -4653,7 +5694,7 @@ Do NOT get stuck re-reading files. Evaluate, report findings, create issues, don
4653
5694
  }
4654
5695
  function executeClaudePrompt2(prompt2) {
4655
5696
  return new Promise((resolve, reject) => {
4656
- const claude = spawn4("claude", ["--print", prompt2], {
5697
+ const claude = spawn5("claude", ["--print", prompt2], {
4657
5698
  stdio: ["pipe", "pipe", "pipe"]
4658
5699
  });
4659
5700
  let output = "";
@@ -4686,9 +5727,10 @@ import open from "open";
4686
5727
 
4687
5728
  // src/lib/auth.ts
4688
5729
  import { createClient } from "@supabase/supabase-js";
4689
- import { existsSync as existsSync12, readFileSync as readFileSync9, writeFileSync as writeFileSync8, mkdirSync as mkdirSync7 } from "fs";
5730
+ import { existsSync as existsSync11, readFileSync as readFileSync8, writeFileSync as writeFileSync8, mkdirSync as mkdirSync7 } from "fs";
4690
5731
  import { join as join12 } from "path";
4691
5732
  import { homedir as homedir5 } from "os";
5733
+ import "open";
4692
5734
  import http from "http";
4693
5735
  var PERSONAL_DOMAINS = [
4694
5736
  "gmail.com",
@@ -4726,21 +5768,21 @@ function getEmailDomain(email) {
4726
5768
  return email.split("@")[1]?.toLowerCase() || "";
4727
5769
  }
4728
5770
  function saveSession(session2) {
4729
- if (!existsSync12(AUTH_DIR)) {
5771
+ if (!existsSync11(AUTH_DIR)) {
4730
5772
  mkdirSync7(AUTH_DIR, { recursive: true });
4731
5773
  }
4732
5774
  writeFileSync8(AUTH_PATH, JSON.stringify(session2, null, 2));
4733
5775
  }
4734
5776
  function loadSession() {
4735
- if (!existsSync12(AUTH_PATH)) return null;
5777
+ if (!existsSync11(AUTH_PATH)) return null;
4736
5778
  try {
4737
- return JSON.parse(readFileSync9(AUTH_PATH, "utf-8"));
5779
+ return JSON.parse(readFileSync8(AUTH_PATH, "utf-8"));
4738
5780
  } catch {
4739
5781
  return null;
4740
5782
  }
4741
5783
  }
4742
5784
  function clearSession() {
4743
- if (existsSync12(AUTH_PATH)) {
5785
+ if (existsSync11(AUTH_PATH)) {
4744
5786
  writeFileSync8(AUTH_PATH, "");
4745
5787
  }
4746
5788
  }
@@ -4897,9 +5939,9 @@ Since: ${new Date(session2.createdAt).toLocaleDateString()}
4897
5939
  }
4898
5940
 
4899
5941
  // src/commands/update.ts
4900
- import { createInterface as createInterface2 } from "readline";
4901
- async function confirm2(message) {
4902
- const rl = createInterface2({
5942
+ import { createInterface as createInterface3 } from "readline";
5943
+ async function confirm3(message) {
5944
+ const rl = createInterface3({
4903
5945
  input: process.stdin,
4904
5946
  output: process.stdout
4905
5947
  });
@@ -4939,7 +5981,7 @@ async function updateCommand(options = {}) {
4939
5981
  writeLine();
4940
5982
  writeLine(` ${colors.cyan}\u2B06${RESET} Update available: ${colors.dim}${info.currentVersion}${RESET} \u2192 ${colors.green}${info.latestVersion}${RESET}`);
4941
5983
  writeLine();
4942
- const shouldUpdate = options.yes || await confirm2("Update now?");
5984
+ const shouldUpdate = options.yes || await confirm3("Update now?");
4943
5985
  if (!shouldUpdate) {
4944
5986
  writeLine();
4945
5987
  writeLine(` ${colors.dim}Update skipped${RESET}`);
@@ -4963,15 +6005,15 @@ async function updateCommand(options = {}) {
4963
6005
  }
4964
6006
 
4965
6007
  // 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";
6008
+ import { execSync as execSync9 } from "child_process";
6009
+ import { existsSync as existsSync12, readFileSync as readFileSync9, writeFileSync as writeFileSync9, mkdirSync as mkdirSync8 } from "fs";
4968
6010
  import { join as join13 } from "path";
4969
6011
  function getTasksFilePath() {
4970
6012
  const memoryDir = findMemoryDir();
4971
6013
  if (!memoryDir) {
4972
6014
  const cwd = process.cwd();
4973
6015
  const agentsDir = join13(cwd, ".agents");
4974
- if (!existsSync13(agentsDir)) {
6016
+ if (!existsSync12(agentsDir)) {
4975
6017
  mkdirSync8(agentsDir, { recursive: true });
4976
6018
  }
4977
6019
  return join13(agentsDir, "tasks.json");
@@ -4980,9 +6022,9 @@ function getTasksFilePath() {
4980
6022
  }
4981
6023
  function loadTasks() {
4982
6024
  const tasksPath = getTasksFilePath();
4983
- if (existsSync13(tasksPath)) {
6025
+ if (existsSync12(tasksPath)) {
4984
6026
  try {
4985
- return JSON.parse(readFileSync10(tasksPath, "utf-8"));
6027
+ return JSON.parse(readFileSync9(tasksPath, "utf-8"));
4986
6028
  } catch {
4987
6029
  return { tasks: [], lastUpdated: (/* @__PURE__ */ new Date()).toISOString() };
4988
6030
  }
@@ -5008,7 +6050,7 @@ function getRecentActivity() {
5008
6050
  marketing: ["marketing", "content", "social"]
5009
6051
  };
5010
6052
  try {
5011
- const logOutput = execSync8(
6053
+ const logOutput = execSync9(
5012
6054
  'git log --since="24 hours ago" --format="%h|%aI|%s" 2>/dev/null',
5013
6055
  { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
5014
6056
  ).trim();
@@ -5142,7 +6184,7 @@ function getElapsedTime(startTime) {
5142
6184
  }
5143
6185
 
5144
6186
  // src/commands/results.ts
5145
- import { execSync as execSync9 } from "child_process";
6187
+ import { execSync as execSync10 } from "child_process";
5146
6188
  function getGitStats(days = 7) {
5147
6189
  const stats = /* @__PURE__ */ new Map();
5148
6190
  const squadKeywords = {
@@ -5157,7 +6199,7 @@ function getGitStats(days = 7) {
5157
6199
  marketing: ["marketing"]
5158
6200
  };
5159
6201
  try {
5160
- const logOutput = execSync9(
6202
+ const logOutput = execSync10(
5161
6203
  `git log --since="${days} days ago" --format="%s" --name-only 2>/dev/null`,
5162
6204
  { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
5163
6205
  ).trim();
@@ -5191,12 +6233,12 @@ function getGitStats(days = 7) {
5191
6233
  }
5192
6234
  return stats;
5193
6235
  }
5194
- function getGitHubStats2(days = 7) {
6236
+ function getGitHubStats(days = 7) {
5195
6237
  const prsOpened = /* @__PURE__ */ new Map();
5196
6238
  const prsMerged = /* @__PURE__ */ new Map();
5197
6239
  const issuesClosed = /* @__PURE__ */ new Map();
5198
6240
  try {
5199
- const prsOutput = execSync9(
6241
+ const prsOutput = execSync10(
5200
6242
  `gh pr list --state all --json title,createdAt,mergedAt --limit 50 2>/dev/null`,
5201
6243
  { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
5202
6244
  );
@@ -5211,7 +6253,7 @@ function getGitHubStats2(days = 7) {
5211
6253
  prsMerged.set(squad, (prsMerged.get(squad) || 0) + 1);
5212
6254
  }
5213
6255
  }
5214
- const issuesOutput = execSync9(
6256
+ const issuesOutput = execSync10(
5215
6257
  `gh issue list --state closed --json title,closedAt --limit 50 2>/dev/null`,
5216
6258
  { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
5217
6259
  );
@@ -5272,7 +6314,7 @@ async function resultsCommand(options = {}) {
5272
6314
  writeLine();
5273
6315
  const squadNames = options.squad ? [options.squad] : listSquads(squadsDir);
5274
6316
  const gitStats = getGitStats(days);
5275
- const ghStats = getGitHubStats2(days);
6317
+ const ghStats = getGitHubStats(days);
5276
6318
  const results = [];
5277
6319
  for (const name of squadNames) {
5278
6320
  const squad = loadSquad(name);
@@ -5342,29 +6384,425 @@ async function resultsCommand(options = {}) {
5342
6384
  writeLine();
5343
6385
  }
5344
6386
 
5345
- // src/commands/workers.ts
5346
- import { execSync as execSync10 } from "child_process";
5347
- import { existsSync as existsSync14, readFileSync as readFileSync11 } from "fs";
6387
+ // src/commands/history.ts
6388
+ import { existsSync as existsSync13, readFileSync as readFileSync10 } from "fs";
5348
6389
  import { join as join14 } from "path";
5349
- function getTasksFilePath2() {
5350
- const memoryDir = findMemoryDir();
5351
- if (!memoryDir) return null;
5352
- return join14(memoryDir, "..", "tasks.json");
5353
- }
5354
- function loadActiveTasks() {
5355
- const tasksPath = getTasksFilePath2();
5356
- if (!tasksPath || !existsSync14(tasksPath)) return [];
6390
+ var BRIDGE_URL2 = process.env.SQUADS_BRIDGE_URL || "http://localhost:8088";
6391
+ var FETCH_TIMEOUT_MS2 = 3e3;
6392
+ async function fetchWithTimeout2(url, timeoutMs = FETCH_TIMEOUT_MS2) {
6393
+ const controller = new AbortController();
6394
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
5357
6395
  try {
5358
- const data = JSON.parse(readFileSync11(tasksPath, "utf-8"));
5359
- return data.tasks?.filter((t) => t.status === "active") || [];
6396
+ const response = await fetch(url, { signal: controller.signal });
6397
+ clearTimeout(timeoutId);
6398
+ return response;
5360
6399
  } catch {
5361
- return [];
6400
+ clearTimeout(timeoutId);
6401
+ throw new Error("Request timed out");
5362
6402
  }
5363
6403
  }
5364
- function getRunningProcesses() {
5365
- const processes = [];
6404
+ async function fetchFromBridge2(days, squad) {
5366
6405
  try {
5367
- const psOutput = execSync10(
6406
+ const params = new URLSearchParams({
6407
+ days: String(days),
6408
+ ...squad && { squad }
6409
+ });
6410
+ const response = await fetchWithTimeout2(`${BRIDGE_URL2}/api/executions?${params}`);
6411
+ if (!response.ok) {
6412
+ return [];
6413
+ }
6414
+ const data = await response.json();
6415
+ return (data.executions || []).map((e) => ({
6416
+ id: e.id || "",
6417
+ squad: e.squad || "unknown",
6418
+ agent: e.agent || "unknown",
6419
+ startedAt: new Date(e.started_at || Date.now()),
6420
+ endedAt: e.ended_at ? new Date(e.ended_at) : void 0,
6421
+ durationMs: e.duration_ms,
6422
+ status: e.status || "success",
6423
+ cost: e.cost_usd,
6424
+ tokens: e.total_tokens,
6425
+ error: e.error
6426
+ }));
6427
+ } catch {
6428
+ return [];
6429
+ }
6430
+ }
6431
+ function fetchFromLocal(days, squad) {
6432
+ const executions = [];
6433
+ const historyPaths = [
6434
+ join14(process.cwd(), ".agents/sessions/history.jsonl"),
6435
+ join14(process.env.HOME || "", "agents-squads/hq/.agents/sessions/history.jsonl")
6436
+ ];
6437
+ let historyPath;
6438
+ for (const path3 of historyPaths) {
6439
+ if (existsSync13(path3)) {
6440
+ historyPath = path3;
6441
+ break;
6442
+ }
6443
+ }
6444
+ if (!historyPath) {
6445
+ return [];
6446
+ }
6447
+ try {
6448
+ const content = readFileSync10(historyPath, "utf-8");
6449
+ const lines = content.trim().split("\n").filter(Boolean);
6450
+ const cutoff = Date.now() - days * 24 * 60 * 60 * 1e3;
6451
+ for (const line of lines) {
6452
+ try {
6453
+ const event = JSON.parse(line);
6454
+ const timestamp = new Date(event.timestamp || 0);
6455
+ if (timestamp.getTime() < cutoff) continue;
6456
+ if (squad && event.squad !== squad) continue;
6457
+ if (event.type === "session_end" || event.type === "agent_complete") {
6458
+ executions.push({
6459
+ id: event.sessionId || `local-${Date.now()}`,
6460
+ squad: event.squad || "unknown",
6461
+ agent: event.agent || "unknown",
6462
+ startedAt: timestamp,
6463
+ durationMs: event.duration,
6464
+ status: event.status === "error" ? "error" : "success",
6465
+ cost: event.cost,
6466
+ tokens: event.tokens
6467
+ });
6468
+ }
6469
+ } catch {
6470
+ }
6471
+ }
6472
+ } catch {
6473
+ }
6474
+ return executions;
6475
+ }
6476
+ function formatDuration(ms) {
6477
+ if (!ms) return "\u2014";
6478
+ const seconds = Math.floor(ms / 1e3);
6479
+ if (seconds < 60) return `${seconds}s`;
6480
+ const minutes = Math.floor(seconds / 60);
6481
+ const remainingSeconds = seconds % 60;
6482
+ if (minutes < 60) return `${minutes}m ${remainingSeconds}s`;
6483
+ const hours = Math.floor(minutes / 60);
6484
+ const remainingMinutes = minutes % 60;
6485
+ return `${hours}h ${remainingMinutes}m`;
6486
+ }
6487
+ function groupByDate(executions) {
6488
+ const groups = /* @__PURE__ */ new Map();
6489
+ for (const exec2 of executions) {
6490
+ const dateKey = exec2.startedAt.toISOString().split("T")[0];
6491
+ if (!groups.has(dateKey)) {
6492
+ groups.set(dateKey, []);
6493
+ }
6494
+ groups.get(dateKey).push(exec2);
6495
+ }
6496
+ return groups;
6497
+ }
6498
+ function formatDateHeader(dateStr) {
6499
+ const date = new Date(dateStr);
6500
+ const today = /* @__PURE__ */ new Date();
6501
+ const yesterday = new Date(today);
6502
+ yesterday.setDate(yesterday.getDate() - 1);
6503
+ if (dateStr === today.toISOString().split("T")[0]) {
6504
+ return `Today (${date.toLocaleDateString("en-US", { month: "short", day: "numeric" })})`;
6505
+ }
6506
+ if (dateStr === yesterday.toISOString().split("T")[0]) {
6507
+ return `Yesterday (${date.toLocaleDateString("en-US", { month: "short", day: "numeric" })})`;
6508
+ }
6509
+ return date.toLocaleDateString("en-US", { weekday: "short", month: "short", day: "numeric" });
6510
+ }
6511
+ async function historyCommand(options = {}) {
6512
+ const days = options.days || 7;
6513
+ const squad = options.squad;
6514
+ const verbose = options.verbose || false;
6515
+ const jsonOutput = options.json || false;
6516
+ writeLine();
6517
+ writeLine(` ${gradient("squads")} ${colors.dim}history${RESET}`);
6518
+ writeLine();
6519
+ const [bridgeExecs, localExecs] = await Promise.all([
6520
+ fetchFromBridge2(days, squad),
6521
+ Promise.resolve(fetchFromLocal(days, squad))
6522
+ ]);
6523
+ const seenIds = /* @__PURE__ */ new Set();
6524
+ const allExecutions = [];
6525
+ for (const exec2 of bridgeExecs) {
6526
+ seenIds.add(exec2.id);
6527
+ allExecutions.push(exec2);
6528
+ }
6529
+ for (const exec2 of localExecs) {
6530
+ if (!seenIds.has(exec2.id)) {
6531
+ allExecutions.push(exec2);
6532
+ }
6533
+ }
6534
+ allExecutions.sort((a, b) => b.startedAt.getTime() - a.startedAt.getTime());
6535
+ if (jsonOutput) {
6536
+ console.log(JSON.stringify(allExecutions, null, 2));
6537
+ return;
6538
+ }
6539
+ if (allExecutions.length === 0) {
6540
+ writeLine(` ${colors.dim}No executions found in the last ${days} day(s)${RESET}`);
6541
+ writeLine();
6542
+ writeLine(` ${colors.dim}Tip: Run agents with 'squads run <squad>' to see history${RESET}`);
6543
+ writeLine();
6544
+ return;
6545
+ }
6546
+ const grouped = groupByDate(allExecutions);
6547
+ const source = bridgeExecs.length > 0 ? "postgres" : "local";
6548
+ writeLine(` ${colors.dim}${allExecutions.length} executions (last ${days}d, source: ${source})${RESET}`);
6549
+ writeLine();
6550
+ for (const [dateStr, execs] of grouped) {
6551
+ writeLine(` ${bold}${formatDateHeader(dateStr)}${RESET}`);
6552
+ writeLine(` ${colors.purple}\u250C${"\u2500".repeat(60)}\u2510${RESET}`);
6553
+ writeLine(` ${colors.purple}\u2502${RESET} ${padEnd("TIME", 7)}${padEnd("SQUAD", 13)}${padEnd("AGENT", 16)}${padEnd("DURATION", 10)}${padEnd("STATUS", 8)}${colors.purple}\u2502${RESET}`);
6554
+ writeLine(` ${colors.purple}\u251C${"\u2500".repeat(60)}\u2524${RESET}`);
6555
+ for (const exec2 of execs) {
6556
+ const time = exec2.startedAt.toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit", hour12: false });
6557
+ const squadName = truncate(exec2.squad, 11);
6558
+ const agentName = truncate(exec2.agent, 14);
6559
+ const duration = formatDuration(exec2.durationMs);
6560
+ let statusIcon;
6561
+ let statusColor;
6562
+ switch (exec2.status) {
6563
+ case "success":
6564
+ statusIcon = icons.success;
6565
+ statusColor = colors.green;
6566
+ break;
6567
+ case "error":
6568
+ statusIcon = icons.error;
6569
+ statusColor = colors.red;
6570
+ break;
6571
+ case "running":
6572
+ statusIcon = icons.progress;
6573
+ statusColor = colors.cyan;
6574
+ break;
6575
+ default:
6576
+ statusIcon = icons.empty;
6577
+ statusColor = colors.dim;
6578
+ }
6579
+ 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}`);
6580
+ if (verbose && (exec2.cost || exec2.tokens)) {
6581
+ const costStr = exec2.cost ? `$${exec2.cost.toFixed(2)}` : "";
6582
+ const tokenStr = exec2.tokens ? `${exec2.tokens.toLocaleString()} tokens` : "";
6583
+ const details = [costStr, tokenStr].filter(Boolean).join(" \u2502 ");
6584
+ writeLine(` ${colors.purple}\u2502${RESET} ${colors.dim}\u2514 ${details}${RESET}${" ".repeat(Math.max(0, 45 - details.length))}${colors.purple}\u2502${RESET}`);
6585
+ }
6586
+ if (exec2.error) {
6587
+ 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}`);
6588
+ }
6589
+ }
6590
+ writeLine(` ${colors.purple}\u2514${"\u2500".repeat(60)}\u2518${RESET}`);
6591
+ writeLine();
6592
+ }
6593
+ const successCount = allExecutions.filter((e) => e.status === "success").length;
6594
+ const errorCount = allExecutions.filter((e) => e.status === "error").length;
6595
+ const totalCost = allExecutions.reduce((sum, e) => sum + (e.cost || 0), 0);
6596
+ 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}` : ""}`);
6597
+ writeLine();
6598
+ }
6599
+
6600
+ // src/commands/health.ts
6601
+ var FETCH_TIMEOUT_MS3 = 2e3;
6602
+ var SERVICES2 = [
6603
+ {
6604
+ name: "PostgreSQL",
6605
+ url: `${process.env.SQUADS_BRIDGE_URL || "http://localhost:8088"}/stats`,
6606
+ fix: "squads stack up postgres"
6607
+ },
6608
+ {
6609
+ name: "Redis",
6610
+ url: `${process.env.SQUADS_BRIDGE_URL || "http://localhost:8088"}/stats`,
6611
+ fix: "squads stack up redis"
6612
+ },
6613
+ {
6614
+ name: "Bridge API",
6615
+ url: `${process.env.SQUADS_BRIDGE_URL || "http://localhost:8088"}/health`,
6616
+ fix: "squads stack up bridge"
6617
+ },
6618
+ {
6619
+ name: "Scheduler",
6620
+ url: `${process.env.SQUADS_SCHEDULER_URL || "http://localhost:8090"}/health`,
6621
+ fix: "squads stack up scheduler"
6622
+ },
6623
+ {
6624
+ name: "Langfuse",
6625
+ url: `${process.env.LANGFUSE_HOST || "http://localhost:3100"}/api/public/health`,
6626
+ optional: true,
6627
+ fix: "squads stack up langfuse"
6628
+ }
6629
+ ];
6630
+ async function fetchWithTimeout3(url, timeoutMs = FETCH_TIMEOUT_MS3) {
6631
+ const controller = new AbortController();
6632
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
6633
+ try {
6634
+ const response = await fetch(url, { signal: controller.signal });
6635
+ clearTimeout(timeoutId);
6636
+ return response;
6637
+ } catch {
6638
+ clearTimeout(timeoutId);
6639
+ throw new Error("timeout");
6640
+ }
6641
+ }
6642
+ async function checkService2(service) {
6643
+ const start = Date.now();
6644
+ try {
6645
+ const response = await fetchWithTimeout3(service.url);
6646
+ const latencyMs = Date.now() - start;
6647
+ if (response.ok) {
6648
+ return {
6649
+ name: service.name,
6650
+ status: "healthy",
6651
+ latencyMs,
6652
+ optional: service.optional
6653
+ };
6654
+ }
6655
+ return {
6656
+ name: service.name,
6657
+ status: "degraded",
6658
+ latencyMs,
6659
+ error: `HTTP ${response.status}`,
6660
+ optional: service.optional,
6661
+ fix: service.fix
6662
+ };
6663
+ } catch (error) {
6664
+ return {
6665
+ name: service.name,
6666
+ status: "down",
6667
+ error: error instanceof Error ? error.message : "unknown",
6668
+ optional: service.optional,
6669
+ fix: service.fix
6670
+ };
6671
+ }
6672
+ }
6673
+ async function getTriggerStats() {
6674
+ try {
6675
+ const schedulerUrl = process.env.SQUADS_SCHEDULER_URL || "http://localhost:8090";
6676
+ const response = await fetchWithTimeout3(`${schedulerUrl}/api/triggers/stats`);
6677
+ if (!response.ok) return null;
6678
+ const data = await response.json();
6679
+ return {
6680
+ active: data.active || 0,
6681
+ disabled: data.disabled || 0,
6682
+ lastFire: data.last_fire ? {
6683
+ name: data.last_fire.name || "unknown",
6684
+ ago: formatTimeAgo(new Date(data.last_fire.fired_at || Date.now()))
6685
+ } : void 0
6686
+ };
6687
+ } catch {
6688
+ return null;
6689
+ }
6690
+ }
6691
+ function formatTimeAgo(date) {
6692
+ const seconds = Math.floor((Date.now() - date.getTime()) / 1e3);
6693
+ if (seconds < 60) return `${seconds}s ago`;
6694
+ if (seconds < 3600) return `${Math.floor(seconds / 60)}m ago`;
6695
+ if (seconds < 86400) return `${Math.floor(seconds / 3600)}h ago`;
6696
+ return `${Math.floor(seconds / 86400)}d ago`;
6697
+ }
6698
+ function formatLatency(ms) {
6699
+ if (!ms) return "\u2014";
6700
+ return `${ms}ms`;
6701
+ }
6702
+ async function healthCommand(options = {}) {
6703
+ writeLine();
6704
+ writeLine(` ${gradient("squads")} ${colors.dim}health${RESET}`);
6705
+ writeLine();
6706
+ const results = await Promise.all(SERVICES2.map(checkService2));
6707
+ writeLine(` ${colors.purple}\u250C${"\u2500".repeat(48)}\u2510${RESET}`);
6708
+ writeLine(` ${colors.purple}\u2502${RESET} ${padEnd("SERVICE", 18)}${padEnd("STATUS", 14)}${padEnd("LATENCY", 12)}${colors.purple}\u2502${RESET}`);
6709
+ writeLine(` ${colors.purple}\u251C${"\u2500".repeat(48)}\u2524${RESET}`);
6710
+ const issues = [];
6711
+ for (const result of results) {
6712
+ let statusIcon;
6713
+ let statusColor;
6714
+ let statusText;
6715
+ switch (result.status) {
6716
+ case "healthy":
6717
+ statusIcon = icons.success;
6718
+ statusColor = colors.green;
6719
+ statusText = "healthy";
6720
+ break;
6721
+ case "degraded":
6722
+ statusIcon = icons.warning;
6723
+ statusColor = colors.yellow;
6724
+ statusText = "degraded";
6725
+ issues.push(result);
6726
+ break;
6727
+ case "down":
6728
+ statusIcon = icons.error;
6729
+ statusColor = colors.red;
6730
+ statusText = "down";
6731
+ if (!result.optional) {
6732
+ issues.push(result);
6733
+ }
6734
+ break;
6735
+ }
6736
+ const nameDisplay = result.optional ? `${result.name} ${colors.dim}(opt)${RESET}` : result.name;
6737
+ const latency = formatLatency(result.latencyMs);
6738
+ writeLine(` ${colors.purple}\u2502${RESET} ${padEnd(nameDisplay, 18)}${statusColor}${statusIcon} ${padEnd(statusText, 11)}${RESET}${padEnd(latency, 12)}${colors.purple}\u2502${RESET}`);
6739
+ }
6740
+ writeLine(` ${colors.purple}\u2514${"\u2500".repeat(48)}\u2518${RESET}`);
6741
+ writeLine();
6742
+ const schedulerUp = results.find((r) => r.name === "Scheduler")?.status === "healthy";
6743
+ if (schedulerUp) {
6744
+ const stats = await getTriggerStats();
6745
+ if (stats) {
6746
+ const lastFireText = stats.lastFire ? `${colors.dim}Last fire:${RESET} ${stats.lastFire.ago} (${stats.lastFire.name})` : `${colors.dim}No recent fires${RESET}`;
6747
+ writeLine(` ${colors.cyan}Triggers:${RESET} ${stats.active} active, ${stats.disabled} disabled`);
6748
+ writeLine(` ${lastFireText}`);
6749
+ writeLine();
6750
+ }
6751
+ }
6752
+ if (issues.length > 0) {
6753
+ const criticalIssues = issues.filter((i) => !i.optional);
6754
+ const optionalIssues = issues.filter((i) => i.optional);
6755
+ if (criticalIssues.length > 0) {
6756
+ writeLine(` ${colors.red}${icons.warning} ${criticalIssues.length} service(s) need attention${RESET}`);
6757
+ for (const issue of criticalIssues) {
6758
+ writeLine(` ${colors.dim}\u2022${RESET} ${issue.name}: ${issue.error || "not responding"}`);
6759
+ if (issue.fix) {
6760
+ writeLine(` ${colors.cyan}Fix:${RESET} ${issue.fix}`);
6761
+ }
6762
+ }
6763
+ writeLine();
6764
+ }
6765
+ if (options.verbose && optionalIssues.length > 0) {
6766
+ writeLine(` ${colors.yellow}Optional services down:${RESET}`);
6767
+ for (const issue of optionalIssues) {
6768
+ writeLine(` ${colors.dim}\u2022${RESET} ${issue.name}`);
6769
+ }
6770
+ writeLine();
6771
+ }
6772
+ } else {
6773
+ writeLine(` ${colors.green}${icons.success} All services healthy${RESET}`);
6774
+ writeLine();
6775
+ }
6776
+ if (!schedulerUp) {
6777
+ writeLine(` ${colors.yellow}${icons.warning} Scheduler not running - triggers won't auto-fire${RESET}`);
6778
+ writeLine(` ${colors.dim}Start with:${RESET} squads stack up scheduler`);
6779
+ writeLine();
6780
+ }
6781
+ }
6782
+
6783
+ // src/commands/workers.ts
6784
+ import { execSync as execSync11 } from "child_process";
6785
+ import { existsSync as existsSync14, readFileSync as readFileSync11 } from "fs";
6786
+ import { join as join15 } from "path";
6787
+ function getTasksFilePath2() {
6788
+ const memoryDir = findMemoryDir();
6789
+ if (!memoryDir) return null;
6790
+ return join15(memoryDir, "..", "tasks.json");
6791
+ }
6792
+ function loadActiveTasks() {
6793
+ const tasksPath = getTasksFilePath2();
6794
+ if (!tasksPath || !existsSync14(tasksPath)) return [];
6795
+ try {
6796
+ const data = JSON.parse(readFileSync11(tasksPath, "utf-8"));
6797
+ return data.tasks?.filter((t) => t.status === "active") || [];
6798
+ } catch {
6799
+ return [];
6800
+ }
6801
+ }
6802
+ function getRunningProcesses() {
6803
+ const processes = [];
6804
+ try {
6805
+ const psOutput = execSync11(
5368
6806
  'ps aux | grep -E "claude|squads|astro|node.*agent" | grep -v grep',
5369
6807
  { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
5370
6808
  ).trim();
@@ -5415,7 +6853,7 @@ async function workersCommand(options = {}) {
5415
6853
  writeLine();
5416
6854
  if (options.kill) {
5417
6855
  try {
5418
- execSync10(`kill ${options.kill}`, { stdio: "pipe" });
6856
+ execSync11(`kill ${options.kill}`, { stdio: "pipe" });
5419
6857
  writeLine(` ${icons.success} Killed process ${colors.cyan}${options.kill}${RESET}`);
5420
6858
  writeLine();
5421
6859
  return;
@@ -5501,6 +6939,725 @@ function getElapsedTime2(startTime) {
5501
6939
  return "<1m";
5502
6940
  }
5503
6941
 
6942
+ // src/commands/context-feed.ts
6943
+ import { existsSync as existsSync15, statSync as statSync3, readdirSync as readdirSync6, readFileSync as readFileSync12 } from "fs";
6944
+ import { join as join16 } from "path";
6945
+ var BRIDGE_URL3 = process.env.SQUADS_BRIDGE_URL || "http://localhost:8088";
6946
+ async function syncBriefToBridge(brief, sourcePath) {
6947
+ try {
6948
+ const response = await fetch(`${BRIDGE_URL3}/api/brief`, {
6949
+ method: "POST",
6950
+ headers: { "Content-Type": "application/json" },
6951
+ body: JSON.stringify({
6952
+ priority: brief.priority,
6953
+ runway: brief.runway,
6954
+ focus: brief.focus || [],
6955
+ blockers: brief.blockers || [],
6956
+ decision_framework: brief.decisionFramework || [],
6957
+ raw_content: brief.raw || "",
6958
+ source_path: sourcePath,
6959
+ synced_by: "cli"
6960
+ })
6961
+ });
6962
+ if (!response.ok) {
6963
+ return false;
6964
+ }
6965
+ const result = await response.json();
6966
+ return result.status === "synced" || result.status === "unchanged";
6967
+ } catch {
6968
+ return false;
6969
+ }
6970
+ }
6971
+ function readBusinessBrief(squadsDir) {
6972
+ if (!squadsDir) return void 0;
6973
+ const briefPath = join16(squadsDir, "..", "BUSINESS_BRIEF.md");
6974
+ if (!existsSync15(briefPath)) return void 0;
6975
+ try {
6976
+ const content = readFileSync12(briefPath, "utf-8");
6977
+ const brief = { raw: content };
6978
+ const priorityMatch = content.match(/##\s*#1 Priority\s*\n+\*\*([^*]+)\*\*/);
6979
+ if (priorityMatch) {
6980
+ brief.priority = priorityMatch[1].trim();
6981
+ }
6982
+ const runwayMatch = content.match(/##\s*Runway\s*\n+([\s\S]*?)(?=\n##|$)/);
6983
+ if (runwayMatch) {
6984
+ const pressureMatch = runwayMatch[1].match(/\*\*Pressure\*\*:\s*(\w+)/i);
6985
+ if (pressureMatch) {
6986
+ brief.runway = pressureMatch[1];
6987
+ }
6988
+ }
6989
+ const focusMatch = content.match(/##\s*Current Focus\s*\n+([\s\S]*?)(?=\n##|$)/);
6990
+ if (focusMatch) {
6991
+ const items = focusMatch[1].match(/^\d+\.\s*\*\*([^*]+)\*\*/gm);
6992
+ if (items) {
6993
+ brief.focus = items.map((item) => {
6994
+ const match = item.match(/\*\*([^*]+)\*\*/);
6995
+ return match ? match[1].trim() : item;
6996
+ });
6997
+ }
6998
+ }
6999
+ const blockersMatch = content.match(/##\s*Blockers\s*\n+([\s\S]*?)(?=\n##|$)/);
7000
+ if (blockersMatch) {
7001
+ const text = blockersMatch[1].trim();
7002
+ if (text.toLowerCase().includes("none")) {
7003
+ brief.blockers = [];
7004
+ } else {
7005
+ const items = text.match(/^-\s*(.+)$/gm);
7006
+ if (items) {
7007
+ brief.blockers = items.map((item) => item.replace(/^-\s*/, "").trim());
7008
+ }
7009
+ }
7010
+ }
7011
+ const decisionMatch = content.match(/##\s*Decision Framework\s*\n+([\s\S]*?)(?=\n##|$)/);
7012
+ if (decisionMatch) {
7013
+ const items = decisionMatch[1].match(/^\d+\.\s*(.+)$/gm);
7014
+ if (items) {
7015
+ brief.decisionFramework = items.map((item) => item.replace(/^\d+\.\s*/, "").trim());
7016
+ }
7017
+ }
7018
+ return brief;
7019
+ } catch {
7020
+ return void 0;
7021
+ }
7022
+ }
7023
+ async function collectBriefingData(options) {
7024
+ const squadsDir = findSquadsDir();
7025
+ const memoryDir = findMemoryDir();
7026
+ const baseDir = squadsDir ? join16(squadsDir, "..", "..", "..") : null;
7027
+ const allSquads = squadsDir ? listSquads(squadsDir) : [];
7028
+ if (options.squad && !allSquads.includes(options.squad)) {
7029
+ return {
7030
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
7031
+ error: `Squad "${options.squad}" not found. Available: ${allSquads.join(", ")}`,
7032
+ squads: [],
7033
+ goals: { active: 0, completed: 0, bySquad: [] },
7034
+ sessions: { active: 0, bySquad: 0 }
7035
+ };
7036
+ }
7037
+ const squadNames = options.squad ? [options.squad] : allSquads;
7038
+ const [bridgeStats, rateLimits, sessions2, gitStats] = await Promise.all([
7039
+ fetchBridgeStats(),
7040
+ fetchRateLimits(),
7041
+ getLiveSessionSummaryAsync(),
7042
+ baseDir ? Promise.resolve(getMultiRepoGitStats(baseDir, 7)) : Promise.resolve(null)
7043
+ ]);
7044
+ const squadBriefings = [];
7045
+ const goalsBySquad = [];
7046
+ let totalActive = 0;
7047
+ let totalCompleted = 0;
7048
+ for (const squadName of squadNames) {
7049
+ const squad = loadSquad(squadName);
7050
+ if (!squad) continue;
7051
+ const agents = squadsDir ? listAgents(squadsDir, squadName) : [];
7052
+ const activeGoals = squad.goals.filter((g) => !g.completed);
7053
+ const completedGoals = squad.goals.filter((g) => g.completed);
7054
+ totalActive += activeGoals.length;
7055
+ totalCompleted += completedGoals.length;
7056
+ const states = getSquadState(squadName);
7057
+ const recentMemory = [];
7058
+ for (const state of states.slice(0, 3)) {
7059
+ const lines = state.content.split("\n").filter((l) => l.trim() && !l.startsWith("#"));
7060
+ if (lines.length > 0) {
7061
+ recentMemory.push(lines[0].substring(0, 100));
7062
+ }
7063
+ }
7064
+ let lastActivity;
7065
+ if (memoryDir) {
7066
+ const squadMemoryPath = join16(memoryDir, squadName);
7067
+ if (existsSync15(squadMemoryPath)) {
7068
+ let mostRecent = 0;
7069
+ try {
7070
+ const walkDir = (dir) => {
7071
+ const entries = readdirSync6(dir, { withFileTypes: true });
7072
+ for (const entry of entries) {
7073
+ const fullPath = join16(dir, entry.name);
7074
+ if (entry.isDirectory()) {
7075
+ walkDir(fullPath);
7076
+ } else if (entry.name.endsWith(".md")) {
7077
+ const stat = statSync3(fullPath);
7078
+ if (stat.mtimeMs > mostRecent) {
7079
+ mostRecent = stat.mtimeMs;
7080
+ }
7081
+ }
7082
+ }
7083
+ };
7084
+ walkDir(squadMemoryPath);
7085
+ } catch {
7086
+ }
7087
+ if (mostRecent > 0) {
7088
+ const daysAgo = Math.floor((Date.now() - mostRecent) / (1e3 * 60 * 60 * 24));
7089
+ if (daysAgo === 0) lastActivity = "today";
7090
+ else if (daysAgo === 1) lastActivity = "yesterday";
7091
+ else lastActivity = `${daysAgo}d ago`;
7092
+ }
7093
+ }
7094
+ }
7095
+ squadBriefings.push({
7096
+ name: squadName,
7097
+ mission: squad.mission,
7098
+ agentCount: agents.length,
7099
+ activeGoals: activeGoals.map((g) => ({
7100
+ description: g.description,
7101
+ progress: g.progress
7102
+ })),
7103
+ memoryEntries: states.length,
7104
+ recentMemory,
7105
+ lastActivity
7106
+ });
7107
+ if (activeGoals.length > 0) {
7108
+ goalsBySquad.push({
7109
+ squad: squadName,
7110
+ goals: activeGoals.map((g) => g.description)
7111
+ });
7112
+ }
7113
+ }
7114
+ const costs = bridgeStats ? {
7115
+ today: {
7116
+ generations: bridgeStats.today.generations,
7117
+ cost: bridgeStats.today.costUsd
7118
+ },
7119
+ budget: bridgeStats.budget,
7120
+ bySquad: bridgeStats.bySquad.map((s) => ({
7121
+ squad: s.squad,
7122
+ cost: s.costUsd,
7123
+ generations: s.generations
7124
+ }))
7125
+ } : void 0;
7126
+ const rateLimitsData = rateLimits.source !== "none" ? {
7127
+ models: Object.values(rateLimits.limits).map((l) => ({
7128
+ model: l.model,
7129
+ requestsRemaining: l.requestsRemaining,
7130
+ tokensRemaining: l.tokensRemaining
7131
+ }))
7132
+ } : void 0;
7133
+ const git = gitStats ? {
7134
+ commits: gitStats.totalCommits,
7135
+ activeDays: gitStats.activeDays,
7136
+ avgPerDay: gitStats.avgCommitsPerDay,
7137
+ byRepo: Array.from(gitStats.commitsByRepo.entries()).map(([repo, commits]) => ({
7138
+ repo,
7139
+ commits
7140
+ }))
7141
+ } : void 0;
7142
+ let relevantMemory;
7143
+ if (options.topic) {
7144
+ const results = searchMemory(options.topic);
7145
+ relevantMemory = results.slice(0, 5).map((r) => ({
7146
+ squad: r.entry.squad,
7147
+ agent: r.entry.agent,
7148
+ snippet: r.matches[0]?.substring(0, 150) || ""
7149
+ }));
7150
+ }
7151
+ const brief = readBusinessBrief(squadsDir);
7152
+ if (brief && squadsDir) {
7153
+ const briefPath = join16(squadsDir, "..", "BUSINESS_BRIEF.md");
7154
+ syncBriefToBridge(brief, briefPath).catch(() => {
7155
+ });
7156
+ }
7157
+ return {
7158
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
7159
+ brief,
7160
+ squads: squadBriefings,
7161
+ goals: {
7162
+ active: totalActive,
7163
+ completed: totalCompleted,
7164
+ bySquad: goalsBySquad
7165
+ },
7166
+ costs,
7167
+ rateLimits: rateLimitsData,
7168
+ git,
7169
+ sessions: {
7170
+ active: sessions2.totalSessions,
7171
+ bySquad: sessions2.squadCount
7172
+ },
7173
+ relevantMemory
7174
+ };
7175
+ }
7176
+ function renderHumanBriefing(data, options) {
7177
+ writeLine();
7178
+ writeLine(` ${gradient("squads")} ${colors.dim}context-feed${RESET}`);
7179
+ writeLine();
7180
+ if (data.error) {
7181
+ writeLine(` ${colors.yellow}${icons.warning || "\u26A0"}${RESET} ${data.error}`);
7182
+ writeLine();
7183
+ return;
7184
+ }
7185
+ if (data.brief) {
7186
+ if (data.brief.priority) {
7187
+ const runwayColor = data.brief.runway === "HIGH" ? colors.red : data.brief.runway === "MEDIUM" ? colors.yellow : colors.green;
7188
+ writeLine(` ${bold}#1 Priority${RESET} ${runwayColor}[${data.brief.runway || "\u2014"}]${RESET}`);
7189
+ writeLine(` ${colors.white}${data.brief.priority}${RESET}`);
7190
+ writeLine();
7191
+ }
7192
+ if (data.brief.focus && data.brief.focus.length > 0) {
7193
+ writeLine(` ${bold}Focus${RESET}`);
7194
+ for (const item of data.brief.focus.slice(0, 3)) {
7195
+ writeLine(` ${colors.cyan}\u2192${RESET} ${item}`);
7196
+ }
7197
+ writeLine();
7198
+ }
7199
+ if (data.brief.blockers && data.brief.blockers.length > 0) {
7200
+ writeLine(` ${colors.red}${bold}Blockers${RESET}`);
7201
+ for (const blocker of data.brief.blockers) {
7202
+ writeLine(` ${colors.red}\u2717${RESET} ${blocker}`);
7203
+ }
7204
+ writeLine();
7205
+ }
7206
+ }
7207
+ if (data.sessions.active > 0) {
7208
+ writeLine(` ${colors.green}${icons.active}${RESET} ${data.sessions.active} active sessions across ${data.sessions.bySquad} squads`);
7209
+ writeLine();
7210
+ }
7211
+ if (data.goals.active > 0) {
7212
+ writeLine(` ${bold}Active Goals${RESET} ${colors.dim}(${data.goals.active})${RESET}`);
7213
+ writeLine();
7214
+ for (const sq of data.goals.bySquad) {
7215
+ writeLine(` ${colors.cyan}${sq.squad}${RESET}`);
7216
+ for (const goal2 of sq.goals.slice(0, 2)) {
7217
+ writeLine(` ${icons.active} ${goal2}`);
7218
+ }
7219
+ if (sq.goals.length > 2) {
7220
+ writeLine(` ${colors.dim}+${sq.goals.length - 2} more${RESET}`);
7221
+ }
7222
+ }
7223
+ writeLine();
7224
+ }
7225
+ if (data.costs) {
7226
+ const { budget } = data.costs;
7227
+ const usedBar = "\u2588".repeat(Math.min(Math.round(budget.usedPct / 5), 20));
7228
+ const emptyBar = "\u2591".repeat(20 - usedBar.length);
7229
+ writeLine(` ${bold}Budget${RESET}`);
7230
+ writeLine(` ${colors.dim}$${budget.used.toFixed(2)}/${budget.daily} today${RESET} ${usedBar}${emptyBar} ${budget.usedPct.toFixed(0)}%`);
7231
+ writeLine();
7232
+ }
7233
+ if (data.rateLimits && options.verbose) {
7234
+ writeLine(` ${bold}Rate Limits${RESET}`);
7235
+ for (const model of data.rateLimits.models.slice(0, 3)) {
7236
+ const shortName = model.model.replace("claude-", "").replace(/-\d+$/, "");
7237
+ writeLine(` ${colors.dim}${shortName}:${RESET} ${model.requestsRemaining} req, ${Math.round(model.tokensRemaining / 1e3)}k tok`);
7238
+ }
7239
+ writeLine();
7240
+ }
7241
+ if (data.git && data.git.commits > 0) {
7242
+ writeLine(` ${bold}Git Activity${RESET} ${colors.dim}(7d)${RESET}`);
7243
+ writeLine(` ${data.git.commits} commits, ${data.git.avgPerDay}/day avg`);
7244
+ writeLine();
7245
+ }
7246
+ const activeSquads = data.squads.filter((s) => s.activeGoals.length > 0 || s.memoryEntries > 0);
7247
+ if (activeSquads.length > 0 && options.verbose) {
7248
+ const w = { name: 14, agents: 8, memory: 10, activity: 10 };
7249
+ const tableWidth = w.name + w.agents + w.memory + w.activity + 4;
7250
+ writeLine(` ${colors.purple}${box.topLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.topRight}${RESET}`);
7251
+ 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}`);
7252
+ writeLine(` ${colors.purple}${box.teeRight}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.teeLeft}${RESET}`);
7253
+ for (const sq of activeSquads.slice(0, 8)) {
7254
+ 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}`;
7255
+ writeLine(row);
7256
+ }
7257
+ writeLine(` ${colors.purple}${box.bottomLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.bottomRight}${RESET}`);
7258
+ writeLine();
7259
+ }
7260
+ if (data.relevantMemory && data.relevantMemory.length > 0) {
7261
+ writeLine(` ${bold}Relevant Memory${RESET} ${colors.dim}("${options.topic}")${RESET}`);
7262
+ for (const mem of data.relevantMemory) {
7263
+ writeLine(` ${colors.cyan}${mem.squad}/${mem.agent}${RESET}`);
7264
+ writeLine(` ${colors.dim}${mem.snippet}${RESET}`);
7265
+ }
7266
+ writeLine();
7267
+ }
7268
+ writeLine(` ${colors.dim}$${RESET} squads feed ${colors.cyan}--topic "pricing"${RESET} ${colors.dim}Topic-focused${RESET}`);
7269
+ writeLine(` ${colors.dim}$${RESET} squads feed ${colors.cyan}--squad website${RESET} ${colors.dim}Single squad${RESET}`);
7270
+ writeLine(` ${colors.dim}$${RESET} squads feed ${colors.cyan}--agent${RESET} ${colors.dim}JSON for agents${RESET}`);
7271
+ writeLine();
7272
+ }
7273
+ function renderAgentBriefing(data) {
7274
+ console.log(JSON.stringify(data, null, 2));
7275
+ }
7276
+ async function contextFeedCommand(options = {}) {
7277
+ const squadsDir = findSquadsDir();
7278
+ if (!squadsDir) {
7279
+ if (options.json || options.agent) {
7280
+ console.log(JSON.stringify({ error: "No .agents/squads directory found" }));
7281
+ } else {
7282
+ writeLine(`${colors.red}No .agents/squads directory found${RESET}`);
7283
+ writeLine(`${colors.dim}Run \`squads init\` to create one.${RESET}`);
7284
+ }
7285
+ process.exit(1);
7286
+ }
7287
+ const data = await collectBriefingData(options);
7288
+ if (options.json || options.agent) {
7289
+ renderAgentBriefing(data);
7290
+ } else {
7291
+ renderHumanBriefing(data, options);
7292
+ }
7293
+ }
7294
+
7295
+ // src/commands/watch.ts
7296
+ import { spawn as spawn6 } from "child_process";
7297
+ async function watchCommand(command, args, options) {
7298
+ const interval = (options.interval || 2) * 1e3;
7299
+ const shouldClear = options.clear !== false;
7300
+ const validCommands = [
7301
+ "status",
7302
+ "workers",
7303
+ "dash",
7304
+ "dashboard",
7305
+ "sessions",
7306
+ "memory",
7307
+ "history",
7308
+ "results",
7309
+ "progress",
7310
+ "goal"
7311
+ ];
7312
+ if (!validCommands.includes(command)) {
7313
+ writeLine(` ${colors.red}Unknown command: ${command}${RESET}`);
7314
+ writeLine(` ${colors.dim}Valid commands: ${validCommands.join(", ")}${RESET}`);
7315
+ process.exit(1);
7316
+ }
7317
+ writeLine();
7318
+ writeLine(` ${gradient("squads")} ${colors.dim}watch${RESET} ${colors.cyan}${command}${RESET}`);
7319
+ writeLine(` ${colors.dim}Refreshing every ${options.interval || 2}s (Ctrl+C to stop)${RESET}`);
7320
+ writeLine();
7321
+ await runCommand2(command, args, shouldClear);
7322
+ const timer = setInterval(async () => {
7323
+ await runCommand2(command, args, shouldClear);
7324
+ }, interval);
7325
+ process.on("SIGINT", () => {
7326
+ clearInterval(timer);
7327
+ writeLine();
7328
+ writeLine(` ${colors.dim}Watch stopped${RESET}`);
7329
+ writeLine();
7330
+ process.exit(0);
7331
+ });
7332
+ await new Promise(() => {
7333
+ });
7334
+ }
7335
+ async function runCommand2(command, args, clear) {
7336
+ return new Promise((resolve) => {
7337
+ if (clear) {
7338
+ process.stdout.write("\x1B[2J\x1B[H");
7339
+ }
7340
+ const now = (/* @__PURE__ */ new Date()).toLocaleTimeString();
7341
+ process.stdout.write(`\x1B[90m${now}\x1B[0m
7342
+
7343
+ `);
7344
+ const child = spawn6("squads", [command, ...args], {
7345
+ stdio: "inherit",
7346
+ env: process.env
7347
+ });
7348
+ child.on("close", () => {
7349
+ resolve();
7350
+ });
7351
+ child.on("error", (err) => {
7352
+ writeLine(` ${colors.red}Error: ${err.message}${RESET}`);
7353
+ resolve();
7354
+ });
7355
+ });
7356
+ }
7357
+
7358
+ // src/commands/live.ts
7359
+ import blessed from "blessed";
7360
+ import contrib from "blessed-contrib";
7361
+ import { execSync as execSync12 } from "child_process";
7362
+ async function liveCommand(_options) {
7363
+ const screen = blessed.screen({
7364
+ smartCSR: true,
7365
+ title: "Squads Live Dashboard",
7366
+ fullUnicode: true
7367
+ });
7368
+ const grid = new contrib.grid({
7369
+ rows: 12,
7370
+ cols: 12,
7371
+ screen
7372
+ });
7373
+ const agentsTable = grid.set(0, 0, 6, 8, contrib.table, {
7374
+ keys: true,
7375
+ fg: "white",
7376
+ label: " Running Agents ",
7377
+ columnSpacing: 2,
7378
+ columnWidth: [20, 12, 10, 8, 8],
7379
+ border: { type: "line", fg: "cyan" }
7380
+ });
7381
+ const costGauge = grid.set(0, 8, 3, 4, contrib.gauge, {
7382
+ label: " Budget Used ",
7383
+ stroke: "green",
7384
+ fill: "white",
7385
+ border: { type: "line", fg: "cyan" }
7386
+ });
7387
+ const costLog = grid.set(3, 8, 3, 4, contrib.log, {
7388
+ fg: "green",
7389
+ label: " Cost Tracker ",
7390
+ border: { type: "line", fg: "cyan" }
7391
+ });
7392
+ const activityLog = grid.set(6, 0, 6, 6, contrib.log, {
7393
+ fg: "cyan",
7394
+ label: " Recent Activity ",
7395
+ border: { type: "line", fg: "magenta" }
7396
+ });
7397
+ const memoryLog = grid.set(6, 6, 6, 6, contrib.log, {
7398
+ fg: "yellow",
7399
+ label: " Memory Updates ",
7400
+ border: { type: "line", fg: "magenta" }
7401
+ });
7402
+ function getAgents() {
7403
+ try {
7404
+ const output = execSync12('ps aux | grep -E "claude|node.*squads" | grep -v grep', {
7405
+ encoding: "utf-8",
7406
+ timeout: 5e3
7407
+ });
7408
+ const agents = [];
7409
+ const lines = output.trim().split("\n").slice(0, 10);
7410
+ for (const line of lines) {
7411
+ const parts = line.trim().split(/\s+/);
7412
+ if (parts.length >= 10) {
7413
+ const cpu = parts[2];
7414
+ const mem = parts[3];
7415
+ const time = parts[9];
7416
+ const cmd = parts.slice(10).join(" ");
7417
+ let name = "claude";
7418
+ let squad = "unknown";
7419
+ if (cmd.includes("squads-")) {
7420
+ const match = cmd.match(/squads-(\w+)-(\w+)/);
7421
+ if (match) {
7422
+ squad = match[1];
7423
+ name = match[2];
7424
+ }
7425
+ }
7426
+ agents.push({
7427
+ name: name.substring(0, 18),
7428
+ squad: squad.substring(0, 10),
7429
+ status: parseFloat(cpu) > 5 ? "active" : "idle",
7430
+ duration: time,
7431
+ cpu: cpu + "%",
7432
+ mem: mem + "%"
7433
+ });
7434
+ }
7435
+ }
7436
+ return agents.slice(0, 8);
7437
+ } catch {
7438
+ return [];
7439
+ }
7440
+ }
7441
+ function getCosts() {
7442
+ const baseToday = 12.5;
7443
+ const baseWeek = 89.2;
7444
+ const variance = Math.random() * 0.5;
7445
+ return {
7446
+ today: baseToday + variance,
7447
+ week: baseWeek + variance * 10,
7448
+ budget: 200
7449
+ };
7450
+ }
7451
+ function getRecentActivity2() {
7452
+ try {
7453
+ const output = execSync12(
7454
+ "gh issue list --repo agents-squads/squads-cli --state open --limit 5 --json number,title,createdAt 2>/dev/null",
7455
+ { encoding: "utf-8", timeout: 1e4 }
7456
+ );
7457
+ const issues = JSON.parse(output);
7458
+ return issues.map(
7459
+ (i) => `#${i.number} ${i.title.substring(0, 40)}`
7460
+ );
7461
+ } catch {
7462
+ return ["(loading...)"];
7463
+ }
7464
+ }
7465
+ function getMemoryUpdates() {
7466
+ try {
7467
+ const output = execSync12(
7468
+ 'find .agents/memory -name "state.md" -mmin -60 2>/dev/null | head -5',
7469
+ { encoding: "utf-8", timeout: 5e3 }
7470
+ );
7471
+ return output.trim().split("\n").filter(Boolean).map((path3) => {
7472
+ const parts = path3.split("/");
7473
+ const squad = parts[2] || "";
7474
+ const agent = parts[3] || "";
7475
+ return `${squad}/${agent}`;
7476
+ });
7477
+ } catch {
7478
+ return ["(no recent updates)"];
7479
+ }
7480
+ }
7481
+ function updateAgents() {
7482
+ const agents = getAgents();
7483
+ 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)", "", "", "", ""]];
7484
+ agentsTable.setData({
7485
+ headers: ["Agent", "Squad", "Status", "CPU", "MEM"],
7486
+ data
7487
+ });
7488
+ }
7489
+ function updateCosts() {
7490
+ const costs = getCosts();
7491
+ const percent = Math.round(costs.week / costs.budget * 100);
7492
+ costGauge.setPercent(percent);
7493
+ costLog.log(`Today: $${costs.today.toFixed(2)}`);
7494
+ costLog.log(`Week: $${costs.week.toFixed(2)} / $${costs.budget}`);
7495
+ }
7496
+ function updateActivity() {
7497
+ const activities = getRecentActivity2();
7498
+ activities.forEach((a) => activityLog.log(a));
7499
+ }
7500
+ function updateMemory() {
7501
+ const updates = getMemoryUpdates();
7502
+ updates.forEach((u) => memoryLog.log(u));
7503
+ }
7504
+ updateAgents();
7505
+ updateCosts();
7506
+ updateActivity();
7507
+ updateMemory();
7508
+ screen.render();
7509
+ const agentInterval = setInterval(() => {
7510
+ updateAgents();
7511
+ screen.render();
7512
+ }, 2e3);
7513
+ const costInterval = setInterval(() => {
7514
+ updateCosts();
7515
+ screen.render();
7516
+ }, 5e3);
7517
+ const activityInterval = setInterval(() => {
7518
+ updateActivity();
7519
+ screen.render();
7520
+ }, 1e4);
7521
+ const memoryInterval = setInterval(() => {
7522
+ updateMemory();
7523
+ screen.render();
7524
+ }, 15e3);
7525
+ screen.key(["escape", "q", "C-c"], () => {
7526
+ clearInterval(agentInterval);
7527
+ clearInterval(costInterval);
7528
+ clearInterval(activityInterval);
7529
+ clearInterval(memoryInterval);
7530
+ return process.exit(0);
7531
+ });
7532
+ screen.key(["r"], () => {
7533
+ updateAgents();
7534
+ updateCosts();
7535
+ updateActivity();
7536
+ updateMemory();
7537
+ screen.render();
7538
+ });
7539
+ agentsTable.focus();
7540
+ }
7541
+
7542
+ // src/commands/top.ts
7543
+ import { execSync as execSync13 } from "child_process";
7544
+ var ESC = "\x1B";
7545
+ var CLEAR_SCREEN = `${ESC}[2J`;
7546
+ var CURSOR_HOME = `${ESC}[H`;
7547
+ var CURSOR_HIDE = `${ESC}[?25l`;
7548
+ var CURSOR_SHOW = `${ESC}[?25h`;
7549
+ var BOLD = `${ESC}[1m`;
7550
+ var DIM = `${ESC}[2m`;
7551
+ var RESET2 = `${ESC}[0m`;
7552
+ var CYAN = `${ESC}[36m`;
7553
+ var GREEN = `${ESC}[32m`;
7554
+ var YELLOW = `${ESC}[33m`;
7555
+ var MAGENTA = `${ESC}[35m`;
7556
+ async function topCommand() {
7557
+ process.stdout.write(CURSOR_HIDE + CLEAR_SCREEN + CURSOR_HOME);
7558
+ let running = true;
7559
+ const cleanup = () => {
7560
+ running = false;
7561
+ process.stdout.write(CURSOR_SHOW + "\n");
7562
+ process.exit(0);
7563
+ };
7564
+ process.on("SIGINT", cleanup);
7565
+ process.on("SIGTERM", cleanup);
7566
+ while (running) {
7567
+ const data = getProcessData();
7568
+ render(data);
7569
+ await sleep2(1e3);
7570
+ }
7571
+ }
7572
+ function getProcessData() {
7573
+ const processes = [];
7574
+ let claudeCount = 0;
7575
+ let agentCount = 0;
7576
+ try {
7577
+ const psOutput = execSync13(
7578
+ 'ps aux | grep -E "[c]laude" | head -15',
7579
+ { encoding: "utf-8", timeout: 5e3 }
7580
+ );
7581
+ const lines = psOutput.trim().split("\n").filter(Boolean);
7582
+ for (const line of lines) {
7583
+ const parts = line.trim().split(/\s+/);
7584
+ if (parts.length >= 11) {
7585
+ const pid = parts[1];
7586
+ const cpu = parts[2];
7587
+ const mem = parts[3];
7588
+ const time = parts[9];
7589
+ const cmd = parts.slice(10).join(" ").substring(0, 30);
7590
+ const cpuNum = parseFloat(cpu);
7591
+ const status = cpuNum > 5 ? "active" : "idle";
7592
+ if (cmd.includes("squads-")) {
7593
+ agentCount++;
7594
+ }
7595
+ claudeCount++;
7596
+ processes.push({ pid, cpu, mem, time, status, cmd });
7597
+ }
7598
+ }
7599
+ } catch {
7600
+ }
7601
+ try {
7602
+ const tmuxOutput = execSync13("tmux ls 2>/dev/null | grep squads- | wc -l", { encoding: "utf-8" });
7603
+ agentCount = parseInt(tmuxOutput.trim()) || 0;
7604
+ } catch {
7605
+ }
7606
+ return {
7607
+ processes: processes.slice(0, 12),
7608
+ summary: { claude: claudeCount, tasks: 0, agents: agentCount }
7609
+ };
7610
+ }
7611
+ function render(data) {
7612
+ const now = (/* @__PURE__ */ new Date()).toLocaleTimeString();
7613
+ const lines = [];
7614
+ lines.push("");
7615
+ lines.push(` ${MAGENTA}squads${RESET2} ${DIM}top${RESET2} ${DIM}${now}${RESET2} ${DIM}(q to quit)${RESET2}`);
7616
+ lines.push("");
7617
+ const activeCount = data.processes.filter((p) => p.status === "active").length;
7618
+ lines.push(` ${CYAN}${data.summary.claude}${RESET2} claude ${DIM}\u2502${RESET2} ${GREEN}${activeCount}${RESET2} active ${DIM}\u2502${RESET2} ${YELLOW}${data.summary.agents}${RESET2} agents`);
7619
+ lines.push("");
7620
+ lines.push(` ${BOLD}PID CPU% MEM% TIME STATUS${RESET2}`);
7621
+ lines.push(` ${DIM}${"\u2500".repeat(50)}${RESET2}`);
7622
+ if (data.processes.length === 0) {
7623
+ lines.push(` ${DIM}(no claude processes)${RESET2}`);
7624
+ } else {
7625
+ for (const p of data.processes) {
7626
+ const statusIcon = p.status === "active" ? `${GREEN}\u25CF${RESET2}` : `${DIM}\u25CB${RESET2}`;
7627
+ const statusText = p.status === "active" ? `${GREEN}active${RESET2}` : `${DIM}idle${RESET2}`;
7628
+ const cpuColor = parseFloat(p.cpu) > 10 ? YELLOW : "";
7629
+ lines.push(
7630
+ ` ${CYAN}${p.pid.padEnd(8)}${RESET2}${cpuColor}${p.cpu.padStart(5)}${RESET2} ${p.mem.padStart(5)} ${p.time.padEnd(10)}${statusIcon} ${statusText}`
7631
+ );
7632
+ }
7633
+ }
7634
+ lines.push("");
7635
+ lines.push(` ${DIM}Press q to quit, r to refresh${RESET2}`);
7636
+ lines.push("");
7637
+ process.stdout.write(CURSOR_HOME);
7638
+ for (const line of lines) {
7639
+ process.stdout.write(line + `${ESC}[K
7640
+ `);
7641
+ }
7642
+ for (let i = 0; i < 5; i++) {
7643
+ process.stdout.write(`${ESC}[K
7644
+ `);
7645
+ }
7646
+ }
7647
+ function sleep2(ms) {
7648
+ return new Promise((resolve) => setTimeout(resolve, ms));
7649
+ }
7650
+ if (process.stdin.isTTY) {
7651
+ process.stdin.setRawMode(true);
7652
+ process.stdin.resume();
7653
+ process.stdin.on("data", (key) => {
7654
+ if (key.toString() === "q" || key[0] === 3) {
7655
+ process.stdout.write(CURSOR_SHOW + "\n");
7656
+ process.exit(0);
7657
+ }
7658
+ });
7659
+ }
7660
+
5504
7661
  // src/commands/session.ts
5505
7662
  async function sessionStartCommand(options = {}) {
5506
7663
  cleanupStaleSessions();
@@ -5545,9 +7702,10 @@ async function detectSquadCommand() {
5545
7702
 
5546
7703
  // src/commands/trigger.ts
5547
7704
  import chalk3 from "chalk";
7705
+ import { existsSync as existsSync16 } from "fs";
5548
7706
  var SCHEDULER_URL = process.env.SCHEDULER_URL || "http://localhost:8090";
5549
- async function fetchScheduler(path2, options) {
5550
- const res = await fetch(`${SCHEDULER_URL}${path2}`, {
7707
+ async function fetchScheduler(path3, options) {
7708
+ const res = await fetch(`${SCHEDULER_URL}${path3}`, {
5551
7709
  ...options,
5552
7710
  headers: {
5553
7711
  "Content-Type": "application/json",
@@ -5590,11 +7748,13 @@ async function listTriggers(squad) {
5590
7748
  }
5591
7749
  async function syncTriggers() {
5592
7750
  console.log(chalk3.gray("Syncing triggers from SQUAD.md files...\n"));
5593
- const { execSync: execSync11 } = await import("child_process");
7751
+ const { execSync: execSync15 } = await import("child_process");
5594
7752
  const hqPath = process.env.HQ_PATH || `${process.env.HOME}/agents-squads/hq`;
5595
7753
  try {
5596
- const output = execSync11(
5597
- `python ${hqPath}/squads-scheduler/sync_triggers.py`,
7754
+ const venvPython = `${hqPath}/squads-scheduler/.venv/bin/python`;
7755
+ const pythonCmd = existsSync16(venvPython) ? venvPython : "python3";
7756
+ const output = execSync15(
7757
+ `${pythonCmd} ${hqPath}/squads-scheduler/sync_triggers.py`,
5598
7758
  { encoding: "utf-8", cwd: hqPath }
5599
7759
  );
5600
7760
  console.log(output);
@@ -5674,14 +7834,336 @@ function registerTriggerCommand(program2) {
5674
7834
  });
5675
7835
  }
5676
7836
 
7837
+ // src/commands/tonight.ts
7838
+ import ora6 from "ora";
7839
+ import fs2 from "fs/promises";
7840
+ import path2, { dirname as dirname5 } from "path";
7841
+ import { execSync as execSync14, spawn as spawn7 } from "child_process";
7842
+ function getProjectRoot2() {
7843
+ const squadsDir = findSquadsDir();
7844
+ if (squadsDir) {
7845
+ return dirname5(dirname5(squadsDir));
7846
+ }
7847
+ return process.cwd();
7848
+ }
7849
+ var TONIGHT_STATE_FILE = ".agents/tonight-state.json";
7850
+ var TONIGHT_LOG_DIR = ".agents/outputs/tonight";
7851
+ async function getCurrentCost() {
7852
+ try {
7853
+ const response = await fetch("http://localhost:8088/api/stats/today");
7854
+ if (response.ok) {
7855
+ const data = await response.json();
7856
+ return data.cost_usd || 0;
7857
+ }
7858
+ } catch {
7859
+ }
7860
+ return 0;
7861
+ }
7862
+ function killAllSessions() {
7863
+ try {
7864
+ const sessions2 = execSync14('tmux ls 2>/dev/null | grep "squads-tonight-" | cut -d: -f1', { encoding: "utf-8" }).trim().split("\n").filter(Boolean);
7865
+ for (const session2 of sessions2) {
7866
+ try {
7867
+ execSync14(`tmux kill-session -t "${session2}"`, { stdio: "ignore" });
7868
+ } catch {
7869
+ }
7870
+ }
7871
+ return sessions2.length;
7872
+ } catch {
7873
+ return 0;
7874
+ }
7875
+ }
7876
+ function getRunningSessionCount() {
7877
+ try {
7878
+ const output = execSync14('tmux ls 2>/dev/null | grep "squads-tonight-" | wc -l', { encoding: "utf-8" });
7879
+ return parseInt(output.trim()) || 0;
7880
+ } catch {
7881
+ return 0;
7882
+ }
7883
+ }
7884
+ function launchAgent(target, projectRoot, sessionId, logFile) {
7885
+ const sessionName = `squads-tonight-${sessionId}`;
7886
+ const claudeCmd = [
7887
+ `cd '${projectRoot}'`,
7888
+ `echo "=== Tonight Session: ${target} ===" >> '${logFile}'`,
7889
+ `echo "Started: $(date)" >> '${logFile}'`,
7890
+ `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}'`,
7891
+ `echo "Ended: $(date)" >> '${logFile}'`
7892
+ ].join(" && ");
7893
+ spawn7("tmux", [
7894
+ "new-session",
7895
+ "-d",
7896
+ "-s",
7897
+ sessionName,
7898
+ "-x",
7899
+ "200",
7900
+ "-y",
7901
+ "50",
7902
+ "/bin/sh",
7903
+ "-c",
7904
+ claudeCmd
7905
+ ], {
7906
+ stdio: "ignore",
7907
+ detached: true
7908
+ }).unref();
7909
+ return sessionName;
7910
+ }
7911
+ async function generateReport(state, projectRoot) {
7912
+ const duration = Math.round((Date.now() - new Date(state.startedAt).getTime()) / 1e3 / 60);
7913
+ const completed = state.sessions.filter((s) => s.status === "completed").length;
7914
+ const failed = state.sessions.filter((s) => s.status === "failed").length;
7915
+ const report = `# Tonight Report - ${(/* @__PURE__ */ new Date()).toLocaleDateString()}
7916
+
7917
+ ## Summary
7918
+ - **Duration**: ${duration} minutes
7919
+ - **Total Cost**: $${state.totalCost.toFixed(2)} / $${state.costCap} cap
7920
+ - **Sessions**: ${completed} completed, ${failed} failed, ${state.sessions.length} total
7921
+ - **Stopped**: ${state.stoppedReason || "All tasks completed"}
7922
+
7923
+ ## Sessions
7924
+
7925
+ | Target | Status | Restarts | Duration |
7926
+ |--------|--------|----------|----------|
7927
+ ${state.sessions.map((s) => {
7928
+ const dur = s.startedAt ? Math.round((Date.now() - new Date(s.startedAt).getTime()) / 1e3 / 60) : 0;
7929
+ return `| ${s.target} | ${s.status} | ${s.restarts} | ${dur}min |`;
7930
+ }).join("\n")}
7931
+
7932
+ ## Logs
7933
+ See \`.agents/outputs/tonight/\` for detailed logs.
7934
+
7935
+ ---
7936
+ *Generated by squads tonight*
7937
+ `;
7938
+ const reportPath = path2.join(projectRoot, TONIGHT_LOG_DIR, `report-${Date.now()}.md`);
7939
+ await fs2.writeFile(reportPath, report);
7940
+ return reportPath;
7941
+ }
7942
+ async function tonightCommand(targets, options) {
7943
+ const costCap = options.costCap ?? 50;
7944
+ const stopAt = options.stopAt ?? "07:00";
7945
+ const maxRetries = options.maxRetries ?? 3;
7946
+ const dryRun = options.dryRun ?? false;
7947
+ const verbose = options.verbose ?? false;
7948
+ writeLine();
7949
+ writeLine(` ${bold}squads tonight${RESET}`);
7950
+ writeLine();
7951
+ const projectRoot = getProjectRoot2();
7952
+ if (targets.length === 0) {
7953
+ writeLine(` ${colors.red}\u2716${RESET} No targets specified`);
7954
+ writeLine();
7955
+ writeLine(` ${colors.dim}Usage: squads tonight <squad> [squad/agent...]${RESET}`);
7956
+ writeLine(` ${colors.dim}Example: squads tonight intelligence customer/outreach${RESET}`);
7957
+ writeLine();
7958
+ return;
7959
+ }
7960
+ writeLine(` ${colors.dim}Config:${RESET}`);
7961
+ writeLine(` Cost cap: ${colors.cyan}$${costCap}${RESET}`);
7962
+ writeLine(` Stop at: ${colors.cyan}${stopAt}${RESET}`);
7963
+ writeLine(` Max retries: ${colors.cyan}${maxRetries}${RESET}`);
7964
+ writeLine(` Targets: ${colors.cyan}${targets.join(", ")}${RESET}`);
7965
+ writeLine();
7966
+ if (dryRun) {
7967
+ writeLine(` ${colors.yellow}DRY RUN${RESET} - would launch:`);
7968
+ for (const target of targets) {
7969
+ writeLine(` \u2022 ${target}`);
7970
+ }
7971
+ writeLine();
7972
+ return;
7973
+ }
7974
+ const logDir = path2.join(projectRoot, TONIGHT_LOG_DIR);
7975
+ await fs2.mkdir(logDir, { recursive: true });
7976
+ const state = {
7977
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
7978
+ stopAt,
7979
+ costCap,
7980
+ maxRetries,
7981
+ sessions: [],
7982
+ totalCost: await getCurrentCost(),
7983
+ stopped: false
7984
+ };
7985
+ const spinner = ora6("Launching agents...").start();
7986
+ for (const target of targets) {
7987
+ const sessionId = `${target.replace("/", "-")}-${Date.now()}`;
7988
+ const logFile = path2.join(logDir, `${sessionId}.log`);
7989
+ const tmuxSession = launchAgent(target, projectRoot, sessionId, logFile);
7990
+ state.sessions.push({
7991
+ id: sessionId,
7992
+ target,
7993
+ tmuxSession,
7994
+ startedAt: /* @__PURE__ */ new Date(),
7995
+ status: "running",
7996
+ restarts: 0
7997
+ });
7998
+ }
7999
+ spinner.succeed(`Launched ${targets.length} agent(s)`);
8000
+ await fs2.writeFile(
8001
+ path2.join(projectRoot, TONIGHT_STATE_FILE),
8002
+ JSON.stringify(state, null, 2)
8003
+ );
8004
+ await track(Events.CLI_TONIGHT, {
8005
+ targets: targets.length,
8006
+ costCap,
8007
+ stopAt
8008
+ });
8009
+ writeLine();
8010
+ writeLine(` ${colors.green}\u2713${RESET} Tonight mode active`);
8011
+ writeLine();
8012
+ writeLine(` ${colors.dim}Monitor:${RESET}`);
8013
+ writeLine(` ${colors.cyan}squads tonight status${RESET} - Check progress`);
8014
+ writeLine(` ${colors.cyan}squads tonight stop${RESET} - Kill all agents`);
8015
+ writeLine(` ${colors.cyan}tmux ls | grep tonight${RESET} - List sessions`);
8016
+ writeLine();
8017
+ writeLine(` ${colors.dim}Logs: ${logDir}${RESET}`);
8018
+ writeLine();
8019
+ const watcherCmd = `
8020
+ while true; do
8021
+ sleep 60
8022
+ # Check cost
8023
+ COST=$(curl -s http://localhost:8088/api/stats/today 2>/dev/null | jq -r '.cost_usd // 0')
8024
+ if [ "$(echo "$COST >= ${costCap}" | bc)" -eq 1 ]; then
8025
+ echo "Cost cap reached: $COST" >> '${logDir}/watcher.log'
8026
+ tmux ls 2>/dev/null | grep squads-tonight | cut -d: -f1 | xargs -I{} tmux kill-session -t {}
8027
+ break
8028
+ fi
8029
+ # Check time
8030
+ HOUR=$(date +%H)
8031
+ MIN=$(date +%M)
8032
+ STOP_HOUR=${stopAt.split(":")[0]}
8033
+ STOP_MIN=${stopAt.split(":")[1]}
8034
+ if [ "$HOUR" -ge "$STOP_HOUR" ] && [ "$MIN" -ge "$STOP_MIN" ]; then
8035
+ echo "Stop time reached" >> '${logDir}/watcher.log'
8036
+ tmux ls 2>/dev/null | grep squads-tonight | cut -d: -f1 | xargs -I{} tmux kill-session -t {}
8037
+ break
8038
+ fi
8039
+ # Check if any sessions still running
8040
+ COUNT=$(tmux ls 2>/dev/null | grep squads-tonight | wc -l)
8041
+ if [ "$COUNT" -eq 0 ]; then
8042
+ echo "All sessions completed" >> '${logDir}/watcher.log'
8043
+ break
8044
+ fi
8045
+ done
8046
+ `;
8047
+ spawn7("tmux", [
8048
+ "new-session",
8049
+ "-d",
8050
+ "-s",
8051
+ "squads-tonight-watcher",
8052
+ "/bin/sh",
8053
+ "-c",
8054
+ watcherCmd
8055
+ ], {
8056
+ stdio: "ignore",
8057
+ detached: true
8058
+ }).unref();
8059
+ if (verbose) {
8060
+ writeLine(` ${colors.dim}Watcher session started${RESET}`);
8061
+ }
8062
+ }
8063
+ async function tonightStatusCommand() {
8064
+ writeLine();
8065
+ writeLine(` ${bold}Tonight Status${RESET}`);
8066
+ writeLine();
8067
+ const projectRoot = getProjectRoot2();
8068
+ const statePath = path2.join(projectRoot, TONIGHT_STATE_FILE);
8069
+ let state = null;
8070
+ try {
8071
+ const content = await fs2.readFile(statePath, "utf-8");
8072
+ state = JSON.parse(content);
8073
+ } catch {
8074
+ }
8075
+ const running = getRunningSessionCount();
8076
+ const cost = await getCurrentCost();
8077
+ if (running === 0 && !state) {
8078
+ writeLine(` ${colors.dim}No tonight session active${RESET}`);
8079
+ writeLine();
8080
+ writeLine(` ${colors.dim}Start with: squads tonight <squad> [squad...]${RESET}`);
8081
+ writeLine();
8082
+ return;
8083
+ }
8084
+ writeLine(` ${colors.cyan}${running}${RESET} agents running`);
8085
+ if (state) {
8086
+ const duration = Math.round((Date.now() - new Date(state.startedAt).getTime()) / 1e3 / 60);
8087
+ writeLine(` ${colors.dim}Duration:${RESET} ${duration} minutes`);
8088
+ writeLine(` ${colors.dim}Cost:${RESET} $${cost.toFixed(2)} / $${state.costCap} cap`);
8089
+ writeLine(` ${colors.dim}Stop at:${RESET} ${state.stopAt}`);
8090
+ }
8091
+ writeLine();
8092
+ try {
8093
+ const sessions2 = execSync14("tmux ls 2>/dev/null | grep squads-tonight", { encoding: "utf-8" }).trim().split("\n").filter(Boolean);
8094
+ if (sessions2.length > 0) {
8095
+ writeLine(` ${colors.dim}Sessions:${RESET}`);
8096
+ for (const session2 of sessions2) {
8097
+ const name = session2.split(":")[0];
8098
+ writeLine(` \u2022 ${name}`);
8099
+ }
8100
+ writeLine();
8101
+ writeLine(` ${colors.dim}Attach: tmux attach -t <session>${RESET}`);
8102
+ }
8103
+ } catch {
8104
+ }
8105
+ writeLine();
8106
+ }
8107
+ async function tonightStopCommand() {
8108
+ writeLine();
8109
+ writeLine(` ${bold}Stopping Tonight Mode${RESET}`);
8110
+ writeLine();
8111
+ const killed = killAllSessions();
8112
+ if (killed > 0) {
8113
+ writeLine(` ${colors.green}\u2713${RESET} Killed ${killed} session(s)`);
8114
+ } else {
8115
+ writeLine(` ${colors.dim}No sessions to kill${RESET}`);
8116
+ }
8117
+ const projectRoot = getProjectRoot2();
8118
+ const statePath = path2.join(projectRoot, TONIGHT_STATE_FILE);
8119
+ try {
8120
+ const content = await fs2.readFile(statePath, "utf-8");
8121
+ const state = JSON.parse(content);
8122
+ state.stopped = true;
8123
+ state.stoppedReason = "Manual stop";
8124
+ state.totalCost = await getCurrentCost();
8125
+ const reportPath = await generateReport(state, projectRoot);
8126
+ writeLine(` ${colors.green}\u2713${RESET} Report: ${reportPath}`);
8127
+ await fs2.unlink(statePath).catch(() => {
8128
+ });
8129
+ } catch {
8130
+ }
8131
+ writeLine();
8132
+ }
8133
+ async function tonightReportCommand() {
8134
+ writeLine();
8135
+ writeLine(` ${bold}Tonight Report${RESET}`);
8136
+ writeLine();
8137
+ const projectRoot = getProjectRoot2();
8138
+ const logDir = path2.join(projectRoot, TONIGHT_LOG_DIR);
8139
+ try {
8140
+ const files = await fs2.readdir(logDir);
8141
+ const reports = files.filter((f) => f.startsWith("report-")).sort().reverse();
8142
+ if (reports.length === 0) {
8143
+ writeLine(` ${colors.dim}No reports found${RESET}`);
8144
+ writeLine();
8145
+ return;
8146
+ }
8147
+ const latest = reports[0];
8148
+ const content = await fs2.readFile(path2.join(logDir, latest), "utf-8");
8149
+ writeLine(content);
8150
+ } catch {
8151
+ writeLine(` ${colors.dim}No reports found${RESET}`);
8152
+ }
8153
+ writeLine();
8154
+ }
8155
+
5677
8156
  // src/cli.ts
8157
+ if (!process.stdout.isTTY) {
8158
+ chalk4.level = 0;
8159
+ }
5678
8160
  var envPaths = [
5679
- join15(process.cwd(), ".env"),
5680
- join15(process.cwd(), "..", "hq", ".env"),
5681
- join15(homedir6(), "agents-squads", "hq", ".env")
8161
+ join17(process.cwd(), ".env"),
8162
+ join17(process.cwd(), "..", "hq", ".env"),
8163
+ join17(homedir6(), "agents-squads", "hq", ".env")
5682
8164
  ];
5683
8165
  for (const envPath of envPaths) {
5684
- if (existsSync15(envPath)) {
8166
+ if (existsSync17(envPath)) {
5685
8167
  config({ path: envPath, quiet: true });
5686
8168
  break;
5687
8169
  }
@@ -5689,9 +8171,19 @@ for (const envPath of envPaths) {
5689
8171
  applyStackConfig();
5690
8172
  registerExitHandler();
5691
8173
  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) }));
8174
+ program.name("squads").description("A CLI for humans and agents").version(version).showSuggestionAfterError(true).configureOutput({
8175
+ outputError: (str, write) => write(str)
8176
+ }).exitOverride((err) => {
8177
+ if (err.code === "commander.helpDisplayed" || err.code === "commander.version") {
8178
+ process.exit(0);
8179
+ }
8180
+ if (err.exitCode !== void 0) {
8181
+ process.exit(err.exitCode);
8182
+ }
8183
+ throw err;
8184
+ });
8185
+ 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);
8186
+ 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
8187
  program.command("list").description("List agents and squads").option("-s, --squads", "List squads only").option("-a, --agents", "List agents only").action(listCommand);
5696
8188
  program.command("status [squad]").description("Show squad status and state").option("-v, --verbose", "Show detailed status").action(statusCommand);
5697
8189
  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 +8194,19 @@ var progress = program.command("progress").description("Track active and complet
5702
8194
  progress.command("start <squad> <description>").description("Register a new active task").action(progressStartCommand);
5703
8195
  progress.command("complete <taskId>").description("Mark a task as completed").option("-f, --failed", "Mark as failed instead").action(progressCompleteCommand);
5704
8196
  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 }));
8197
+ 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));
8198
+ 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
8199
  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");
8200
+ program.command("health").description("Quick health check for all infrastructure services").option("-v, --verbose", "Show optional services").action((options) => healthCommand(options));
8201
+ 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, {
8202
+ interval: parseInt(options.interval, 10),
8203
+ clear: options.clear
8204
+ }));
8205
+ 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));
8206
+ program.command("top").description("Live process table (like Unix top) - numbers update in place").action(() => topCommand());
8207
+ var memory = program.command("memory").description("Query and manage squad memory").action(() => {
8208
+ memory.outputHelp();
8209
+ });
5707
8210
  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
8211
  memory.command("show <squad>").description("Show memory for a squad").action(memoryShowCommand);
5709
8212
  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 +8222,9 @@ memory.command("extract").description("Extract memories from recent conversation
5719
8222
  hours: parseInt(opts.hours, 10),
5720
8223
  dryRun: opts.dryRun
5721
8224
  }));
5722
- var goal = program.command("goal").description("Manage squad goals");
8225
+ var goal = program.command("goal").description("Manage squad goals").action(() => {
8226
+ goal.outputHelp();
8227
+ });
5723
8228
  goal.command("set <squad> <description>").description("Set a goal for a squad").option("-m, --metric <metrics...>", "Metrics to track").action(goalSetCommand);
5724
8229
  goal.command("list [squad]").description("List goals for squad(s)").option("-a, --all", "Show completed goals too").action(goalListCommand);
5725
8230
  goal.command("complete <squad> <index>").description("Mark a goal as completed").action(goalCompleteCommand);
@@ -5735,11 +8240,11 @@ sessions.command("history").description("Show session history and statistics").o
5735
8240
  json: options.json
5736
8241
  }));
5737
8242
  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");
8243
+ const { buildCurrentSessionSummary } = await import("./sessions-JCQ34BEU.js");
5739
8244
  let data;
5740
8245
  if (options.file) {
5741
- const { readFileSync: readFileSync12 } = await import("fs");
5742
- data = JSON.parse(readFileSync12(options.file, "utf-8"));
8246
+ const { readFileSync: readFileSync13 } = await import("fs");
8247
+ data = JSON.parse(readFileSync13(options.file, "utf-8"));
5743
8248
  } else if (options.data) {
5744
8249
  data = JSON.parse(options.data);
5745
8250
  } else if (!process.stdin.isTTY) {
@@ -5772,11 +8277,67 @@ stack.command("down").description("Stop Docker containers").action(stackDownComm
5772
8277
  stack.command("health").description("Comprehensive health check with diagnostics").option("-v, --verbose", "Show logs for unhealthy services").action((options) => stackHealthCommand(options.verbose));
5773
8278
  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
8279
  registerTriggerCommand(program);
8280
+ var tonight = program.command("tonight").description("Run agents autonomously overnight with safety limits");
8281
+ 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, {
8282
+ costCap: parseFloat(options.costCap),
8283
+ stopAt: options.stopAt,
8284
+ maxRetries: parseInt(options.maxRetries, 10),
8285
+ dryRun: options.dryRun,
8286
+ verbose: options.verbose
8287
+ }));
8288
+ tonight.command("status").description("Check tonight mode status").action(tonightStatusCommand);
8289
+ tonight.command("stop").description("Stop all tonight agents and generate report").action(tonightStopCommand);
8290
+ tonight.command("report").description("Show latest tonight report").action(tonightReportCommand);
5775
8291
  program.command("login").description("Log in to Squads (Pro & Enterprise)").action(loginCommand);
5776
8292
  program.command("logout").description("Log out from Squads").action(logoutCommand);
5777
8293
  program.command("whoami").description("Show current logged in user").action(whoamiCommand);
5778
8294
  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();
8295
+ program.command("version").description("Show version information").action(() => {
8296
+ console.log(`squads-cli ${version}`);
8297
+ });
8298
+ function handleError(error) {
8299
+ const err = error instanceof Error ? error : new Error(String(error));
8300
+ if (err.message.includes("ECONNREFUSED") || err.message.includes("fetch failed")) {
8301
+ console.error(chalk4.red("\nConnection error:"), err.message);
8302
+ console.error(chalk4.dim("\nPossible fixes:"));
8303
+ console.error(chalk4.dim(" 1. Check if Docker containers are running: squads stack status"));
8304
+ console.error(chalk4.dim(" 2. Start the stack: squads stack up"));
8305
+ console.error(chalk4.dim(" 3. Check your network connection"));
8306
+ } else if (err.message.includes("ENOENT")) {
8307
+ console.error(chalk4.red("\nFile not found:"), err.message);
8308
+ console.error(chalk4.dim("\nPossible fixes:"));
8309
+ console.error(chalk4.dim(" 1. Make sure you are in the correct directory"));
8310
+ console.error(chalk4.dim(" 2. Initialize the project: squads init"));
8311
+ } else if (err.message.includes("permission denied") || err.message.includes("EACCES")) {
8312
+ console.error(chalk4.red("\nPermission denied:"), err.message);
8313
+ console.error(chalk4.dim("\nPossible fixes:"));
8314
+ console.error(chalk4.dim(" 1. Check file permissions"));
8315
+ console.error(chalk4.dim(" 2. Avoid running with sudo if not needed"));
8316
+ } else if (err.message.includes("rate limit") || err.message.includes("429")) {
8317
+ console.error(chalk4.red("\nRate limit exceeded"));
8318
+ console.error(chalk4.dim("\nPossible fixes:"));
8319
+ console.error(chalk4.dim(" 1. Wait a few minutes and try again"));
8320
+ console.error(chalk4.dim(" 2. Check your API usage: squads dash"));
8321
+ } else {
8322
+ console.error(chalk4.red("\nError:"), err.message);
8323
+ if (process.env.DEBUG || process.env.VERBOSE) {
8324
+ console.error(chalk4.dim("\nStack trace:"));
8325
+ console.error(chalk4.dim(err.stack));
8326
+ } else {
8327
+ console.error(chalk4.dim("\nRun with DEBUG=1 for more details"));
8328
+ }
8329
+ }
8330
+ console.error(chalk4.dim("\nIf this persists, please report at:"));
8331
+ console.error(chalk4.cyan(" https://github.com/agents-squads/squads-cli/issues\n"));
8332
+ process.exit(1);
8333
+ }
8334
+ process.on("uncaughtException", handleError);
8335
+ process.on("unhandledRejection", handleError);
8336
+ try {
8337
+ await program.parseAsync();
8338
+ } catch (error) {
8339
+ handleError(error);
8340
+ }
5780
8341
  if (!process.argv.slice(2).length) {
5781
8342
  console.log(`
5782
8343
  ${chalk4.bold.magenta("squads")} - AI agent squad management