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/README.md +468 -75
- package/dist/{chunk-266URT5W.js → chunk-G63RBKDH.js} +94 -29
- package/dist/chunk-G63RBKDH.js.map +1 -0
- package/dist/cli.js +2965 -404
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +2 -4
- package/dist/index.js +1 -11
- package/dist/index.js.map +1 -1
- package/dist/{sessions-UR3YGSLR.js → sessions-JCQ34BEU.js} +2 -2
- package/docker/squads-bridge/squads_bridge.py +153 -0
- package/package.json +4 -1
- package/dist/chunk-266URT5W.js.map +0 -1
- /package/dist/{sessions-UR3YGSLR.js.map → sessions-JCQ34BEU.js.map} +0 -0
package/dist/cli.js
CHANGED
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
cleanupStaleSessions,
|
|
15
15
|
colors,
|
|
16
16
|
detectSquad,
|
|
17
|
-
|
|
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-
|
|
32
|
-
import
|
|
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
|
|
37
|
-
import { join as
|
|
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.
|
|
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
|
|
68
|
-
|
|
69
|
-
encoding: "utf-8",
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
|
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 (
|
|
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(
|
|
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("
|
|
529
|
-
console.log(
|
|
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
|
|
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(
|
|
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("
|
|
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
|
|
829
|
+
const demoSquadMd = `# Demo Squad
|
|
556
830
|
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
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/
|
|
595
|
-
|
|
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
|
|
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
|
-
|
|
602
|
-
|
|
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(
|
|
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
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
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/
|
|
658
|
-
|
|
959
|
+
path.join(cwd, ".agents/commit-template.txt"),
|
|
960
|
+
commitTemplate
|
|
659
961
|
);
|
|
660
962
|
const claudeMdPath = path.join(cwd, "CLAUDE.md");
|
|
661
|
-
|
|
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
|
|
970
|
+
This project uses AI agent squads for automation.
|
|
671
971
|
|
|
672
|
-
###
|
|
972
|
+
### Quick Start
|
|
673
973
|
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
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
|
-
|
|
983
|
+
Squads have persistent memory across sessions:
|
|
684
984
|
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
985
|
+
\`\`\`bash
|
|
986
|
+
squads memory query "<topic>" # Search memory
|
|
987
|
+
squads memory show <squad> # View squad memory
|
|
988
|
+
\`\`\`
|
|
688
989
|
|
|
689
|
-
###
|
|
990
|
+
### Creating Agents
|
|
690
991
|
|
|
691
|
-
|
|
992
|
+
Agents are markdown files in \`.agents/squads/<squad>/\`:
|
|
692
993
|
|
|
693
|
-
\`\`\`
|
|
694
|
-
|
|
994
|
+
\`\`\`markdown
|
|
995
|
+
# My Agent
|
|
695
996
|
|
|
696
|
-
|
|
997
|
+
## Purpose
|
|
998
|
+
What this agent does.
|
|
697
999
|
|
|
698
|
-
|
|
1000
|
+
## Instructions
|
|
1001
|
+
1. Step one
|
|
1002
|
+
2. Step two
|
|
699
1003
|
|
|
700
|
-
|
|
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
|
|
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
|
|
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
|
|
1144
|
-
|
|
1145
|
-
const
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
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.
|
|
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
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
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 =
|
|
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,
|
|
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
|
|
1220
|
-
const
|
|
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}
|
|
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
|
|
1225
|
-
const
|
|
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
|
-
...
|
|
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
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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
|
-
|
|
2829
|
-
const status =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
2855
|
-
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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/
|
|
3285
|
-
var
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
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
|
|
3310
|
-
const
|
|
3311
|
-
return
|
|
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 =
|
|
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
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
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
|
-
|
|
3484
|
-
|
|
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
|
-
|
|
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:
|
|
3877
|
-
const tableWidth = w.name + w.commits + w.prs + w.issues + w.goals + w.bar +
|
|
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
|
-
|
|
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
|
|
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}
|
|
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
|
-
|
|
4007
|
-
writeLine();
|
|
4008
|
-
|
|
4009
|
-
const
|
|
4010
|
-
const
|
|
4011
|
-
const
|
|
4012
|
-
|
|
4013
|
-
writeLine(` ${colors.dim}
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
|
|
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
|
-
|
|
4020
|
-
|
|
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(
|
|
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
|
|
4042
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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(
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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 (!
|
|
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 (!
|
|
5777
|
+
if (!existsSync11(AUTH_PATH)) return null;
|
|
4736
5778
|
try {
|
|
4737
|
-
return JSON.parse(
|
|
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 (
|
|
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
|
|
4901
|
-
async function
|
|
4902
|
-
const rl =
|
|
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
|
|
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
|
|
4967
|
-
import { existsSync as
|
|
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 (!
|
|
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 (
|
|
6025
|
+
if (existsSync12(tasksPath)) {
|
|
4984
6026
|
try {
|
|
4985
|
-
return JSON.parse(
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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/
|
|
5346
|
-
import {
|
|
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
|
-
|
|
5350
|
-
|
|
5351
|
-
|
|
5352
|
-
|
|
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
|
|
5359
|
-
|
|
6396
|
+
const response = await fetch(url, { signal: controller.signal });
|
|
6397
|
+
clearTimeout(timeoutId);
|
|
6398
|
+
return response;
|
|
5360
6399
|
} catch {
|
|
5361
|
-
|
|
6400
|
+
clearTimeout(timeoutId);
|
|
6401
|
+
throw new Error("Request timed out");
|
|
5362
6402
|
}
|
|
5363
6403
|
}
|
|
5364
|
-
function
|
|
5365
|
-
const processes = [];
|
|
6404
|
+
async function fetchFromBridge2(days, squad) {
|
|
5366
6405
|
try {
|
|
5367
|
-
const
|
|
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
|
-
|
|
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(
|
|
5550
|
-
const res = await fetch(`${SCHEDULER_URL}${
|
|
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:
|
|
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
|
|
5597
|
-
|
|
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
|
-
|
|
5680
|
-
|
|
5681
|
-
|
|
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 (
|
|
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
|
-
|
|
5694
|
-
|
|
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
|
-
|
|
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-
|
|
8243
|
+
const { buildCurrentSessionSummary } = await import("./sessions-JCQ34BEU.js");
|
|
5739
8244
|
let data;
|
|
5740
8245
|
if (options.file) {
|
|
5741
|
-
const { readFileSync:
|
|
5742
|
-
data = JSON.parse(
|
|
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
|
-
|
|
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
|