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