squads-cli 0.1.2
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/LICENSE +21 -0
- package/README.md +266 -0
- package/dist/cli.js +4706 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/package.json +79 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,4706 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
3
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
4
|
+
}) : x)(function(x) {
|
|
5
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
6
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
// src/cli.ts
|
|
10
|
+
import { config } from "dotenv";
|
|
11
|
+
import { existsSync as existsSync14 } from "fs";
|
|
12
|
+
import { join as join14 } from "path";
|
|
13
|
+
import { homedir as homedir4 } from "os";
|
|
14
|
+
import { Command } from "commander";
|
|
15
|
+
import chalk3 from "chalk";
|
|
16
|
+
|
|
17
|
+
// src/version.ts
|
|
18
|
+
var version = "0.1.0";
|
|
19
|
+
|
|
20
|
+
// src/commands/init.ts
|
|
21
|
+
import chalk from "chalk";
|
|
22
|
+
import ora from "ora";
|
|
23
|
+
import fs from "fs/promises";
|
|
24
|
+
import path from "path";
|
|
25
|
+
|
|
26
|
+
// src/lib/git.ts
|
|
27
|
+
import { execSync } from "child_process";
|
|
28
|
+
import { existsSync } from "fs";
|
|
29
|
+
import { join } from "path";
|
|
30
|
+
function checkGitStatus(cwd = process.cwd()) {
|
|
31
|
+
const status = {
|
|
32
|
+
isGitRepo: false,
|
|
33
|
+
hasRemote: false,
|
|
34
|
+
isDirty: false,
|
|
35
|
+
uncommittedCount: 0
|
|
36
|
+
};
|
|
37
|
+
if (!existsSync(join(cwd, ".git"))) {
|
|
38
|
+
return status;
|
|
39
|
+
}
|
|
40
|
+
status.isGitRepo = true;
|
|
41
|
+
try {
|
|
42
|
+
const branch = execSync("git rev-parse --abbrev-ref HEAD", {
|
|
43
|
+
cwd,
|
|
44
|
+
encoding: "utf-8",
|
|
45
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
46
|
+
}).trim();
|
|
47
|
+
status.branch = branch;
|
|
48
|
+
const remotes = execSync("git remote -v", {
|
|
49
|
+
cwd,
|
|
50
|
+
encoding: "utf-8",
|
|
51
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
52
|
+
}).trim();
|
|
53
|
+
if (remotes) {
|
|
54
|
+
status.hasRemote = true;
|
|
55
|
+
const lines = remotes.split("\n");
|
|
56
|
+
if (lines.length > 0) {
|
|
57
|
+
const parts = lines[0].split(/\s+/);
|
|
58
|
+
status.remoteName = parts[0];
|
|
59
|
+
status.remoteUrl = parts[1];
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const statusOutput = execSync("git status --porcelain", {
|
|
63
|
+
cwd,
|
|
64
|
+
encoding: "utf-8",
|
|
65
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
66
|
+
}).trim();
|
|
67
|
+
if (statusOutput) {
|
|
68
|
+
status.isDirty = true;
|
|
69
|
+
status.uncommittedCount = statusOutput.split("\n").filter((l) => l.trim()).length;
|
|
70
|
+
}
|
|
71
|
+
} catch {
|
|
72
|
+
}
|
|
73
|
+
return status;
|
|
74
|
+
}
|
|
75
|
+
function getRepoName(remoteUrl) {
|
|
76
|
+
if (!remoteUrl) return null;
|
|
77
|
+
const match = remoteUrl.match(/[:/]([^/]+\/[^/]+?)(?:\.git)?$/);
|
|
78
|
+
return match ? match[1] : null;
|
|
79
|
+
}
|
|
80
|
+
var SQUAD_REPOS = ["hq", "agents-squads-web", "squads-cli", "company", "product", "engineering", "research", "intelligence", "customer", "finance", "marketing"];
|
|
81
|
+
var SQUAD_REPO_MAP = {
|
|
82
|
+
website: ["agents-squads-web"],
|
|
83
|
+
product: ["squads-cli"],
|
|
84
|
+
engineering: ["hq", "squads-cli"],
|
|
85
|
+
research: ["research"],
|
|
86
|
+
intelligence: ["intelligence"],
|
|
87
|
+
customer: ["customer"],
|
|
88
|
+
finance: ["finance"],
|
|
89
|
+
company: ["company", "hq"],
|
|
90
|
+
marketing: ["marketing", "agents-squads-web"]
|
|
91
|
+
};
|
|
92
|
+
var SQUAD_LABELS = {
|
|
93
|
+
website: ["website", "web", "frontend", "ui"],
|
|
94
|
+
product: ["product", "cli", "feature"],
|
|
95
|
+
engineering: ["engineering", "infra", "backend", "bug"],
|
|
96
|
+
research: ["research", "analysis"],
|
|
97
|
+
intelligence: ["intel", "monitoring"],
|
|
98
|
+
customer: ["customer", "sales", "lead"],
|
|
99
|
+
finance: ["finance", "cost", "billing"],
|
|
100
|
+
company: ["company", "strategy"],
|
|
101
|
+
marketing: ["marketing", "content", "seo"]
|
|
102
|
+
};
|
|
103
|
+
function getGitHubStats(basePath, days = 30) {
|
|
104
|
+
const stats = {
|
|
105
|
+
prsOpened: 0,
|
|
106
|
+
prsMerged: 0,
|
|
107
|
+
issuesClosed: 0,
|
|
108
|
+
issuesOpen: 0,
|
|
109
|
+
bySquad: /* @__PURE__ */ new Map()
|
|
110
|
+
};
|
|
111
|
+
for (const squad of Object.keys(SQUAD_REPO_MAP)) {
|
|
112
|
+
stats.bySquad.set(squad, {
|
|
113
|
+
prsOpened: 0,
|
|
114
|
+
prsMerged: 0,
|
|
115
|
+
issuesClosed: 0,
|
|
116
|
+
issuesOpen: 0,
|
|
117
|
+
commits: 0,
|
|
118
|
+
recentIssues: [],
|
|
119
|
+
recentPRs: []
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
const repos = ["hq", "agents-squads-web"];
|
|
123
|
+
const since = new Date(Date.now() - days * 24 * 60 * 60 * 1e3).toISOString();
|
|
124
|
+
for (const repo of repos) {
|
|
125
|
+
const repoPath = join(basePath, repo);
|
|
126
|
+
if (!existsSync(repoPath)) continue;
|
|
127
|
+
try {
|
|
128
|
+
const prsOutput = execSync(
|
|
129
|
+
`gh pr list --state all --json number,title,createdAt,mergedAt,labels --limit 100 2>/dev/null`,
|
|
130
|
+
{ cwd: repoPath, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
131
|
+
);
|
|
132
|
+
const prs = JSON.parse(prsOutput || "[]");
|
|
133
|
+
for (const pr of prs) {
|
|
134
|
+
const created = new Date(pr.createdAt);
|
|
135
|
+
if (created < new Date(since)) continue;
|
|
136
|
+
stats.prsOpened++;
|
|
137
|
+
if (pr.mergedAt) stats.prsMerged++;
|
|
138
|
+
const squad = detectSquadFromPR(pr, repo);
|
|
139
|
+
const squadStats = stats.bySquad.get(squad);
|
|
140
|
+
if (squadStats) {
|
|
141
|
+
squadStats.prsOpened++;
|
|
142
|
+
if (pr.mergedAt) squadStats.prsMerged++;
|
|
143
|
+
if (squadStats.recentPRs.length < 3) {
|
|
144
|
+
squadStats.recentPRs.push({
|
|
145
|
+
title: pr.title,
|
|
146
|
+
number: pr.number,
|
|
147
|
+
merged: !!pr.mergedAt
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
const issuesOutput = execSync(
|
|
153
|
+
`gh issue list --state all --json number,title,state,closedAt,labels --limit 100 2>/dev/null`,
|
|
154
|
+
{ cwd: repoPath, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
155
|
+
);
|
|
156
|
+
const issues = JSON.parse(issuesOutput || "[]");
|
|
157
|
+
for (const issue of issues) {
|
|
158
|
+
const squad = detectSquadFromIssue(issue, repo);
|
|
159
|
+
const squadStats = stats.bySquad.get(squad);
|
|
160
|
+
if (issue.state === "CLOSED") {
|
|
161
|
+
const closed = new Date(issue.closedAt);
|
|
162
|
+
if (closed >= new Date(since)) {
|
|
163
|
+
stats.issuesClosed++;
|
|
164
|
+
if (squadStats) {
|
|
165
|
+
squadStats.issuesClosed++;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
} else {
|
|
169
|
+
stats.issuesOpen++;
|
|
170
|
+
if (squadStats) {
|
|
171
|
+
squadStats.issuesOpen++;
|
|
172
|
+
if (squadStats.recentIssues.length < 3) {
|
|
173
|
+
squadStats.recentIssues.push({
|
|
174
|
+
title: issue.title,
|
|
175
|
+
number: issue.number,
|
|
176
|
+
state: issue.state
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
} catch {
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
const gitStats = getMultiRepoGitStats(basePath, days);
|
|
186
|
+
for (const [repo, commits] of gitStats.commitsByRepo) {
|
|
187
|
+
for (const [squad, repos2] of Object.entries(SQUAD_REPO_MAP)) {
|
|
188
|
+
if (repos2.includes(repo)) {
|
|
189
|
+
const squadStats = stats.bySquad.get(squad);
|
|
190
|
+
if (squadStats) {
|
|
191
|
+
squadStats.commits += commits;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return stats;
|
|
197
|
+
}
|
|
198
|
+
function detectSquadFromPR(pr, repo) {
|
|
199
|
+
for (const label of pr.labels || []) {
|
|
200
|
+
const labelLower = label.name.toLowerCase();
|
|
201
|
+
for (const [squad, patterns] of Object.entries(SQUAD_LABELS)) {
|
|
202
|
+
if (patterns.some((p) => labelLower.includes(p))) {
|
|
203
|
+
return squad;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
const titleLower = pr.title.toLowerCase();
|
|
208
|
+
for (const [squad, patterns] of Object.entries(SQUAD_LABELS)) {
|
|
209
|
+
if (patterns.some((p) => titleLower.includes(p))) {
|
|
210
|
+
return squad;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
if (repo === "agents-squads-web") return "website";
|
|
214
|
+
if (repo === "squads-cli") return "product";
|
|
215
|
+
return "engineering";
|
|
216
|
+
}
|
|
217
|
+
function detectSquadFromIssue(issue, repo) {
|
|
218
|
+
for (const label of issue.labels || []) {
|
|
219
|
+
const labelLower = label.name.toLowerCase();
|
|
220
|
+
if (labelLower.startsWith("squad:")) {
|
|
221
|
+
return labelLower.replace("squad:", "");
|
|
222
|
+
}
|
|
223
|
+
for (const [squad, patterns] of Object.entries(SQUAD_LABELS)) {
|
|
224
|
+
if (patterns.some((p) => labelLower.includes(p))) {
|
|
225
|
+
return squad;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
const titleLower = issue.title.toLowerCase();
|
|
230
|
+
for (const [squad, patterns] of Object.entries(SQUAD_LABELS)) {
|
|
231
|
+
if (patterns.some((p) => titleLower.includes(p))) {
|
|
232
|
+
return squad;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
if (repo === "agents-squads-web") return "website";
|
|
236
|
+
if (repo === "squads-cli") return "product";
|
|
237
|
+
return "engineering";
|
|
238
|
+
}
|
|
239
|
+
function getMultiRepoGitStats(basePath, days = 30) {
|
|
240
|
+
const stats = {
|
|
241
|
+
totalCommits: 0,
|
|
242
|
+
commitsByDay: /* @__PURE__ */ new Map(),
|
|
243
|
+
commitsByAuthor: /* @__PURE__ */ new Map(),
|
|
244
|
+
commitsByRepo: /* @__PURE__ */ new Map(),
|
|
245
|
+
activeDays: 0,
|
|
246
|
+
avgCommitsPerDay: 0,
|
|
247
|
+
peakDay: null,
|
|
248
|
+
repos: []
|
|
249
|
+
};
|
|
250
|
+
for (const repo of SQUAD_REPOS) {
|
|
251
|
+
const repoPath = join(basePath, repo);
|
|
252
|
+
if (!existsSync(repoPath) || !existsSync(join(repoPath, ".git"))) {
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
try {
|
|
256
|
+
const logOutput = execSync(
|
|
257
|
+
`git log --since="${days} days ago" --format="%H|%aN|%ad|%s" --date=short 2>/dev/null`,
|
|
258
|
+
{ cwd: repoPath, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
259
|
+
).trim();
|
|
260
|
+
if (!logOutput) continue;
|
|
261
|
+
const commits = logOutput.split("\n").filter((l) => l.trim());
|
|
262
|
+
const authors = /* @__PURE__ */ new Set();
|
|
263
|
+
let lastCommit = "";
|
|
264
|
+
for (const line of commits) {
|
|
265
|
+
const [hash, author, date, message] = line.split("|");
|
|
266
|
+
if (!hash) continue;
|
|
267
|
+
stats.totalCommits++;
|
|
268
|
+
authors.add(author);
|
|
269
|
+
if (!lastCommit) lastCommit = date;
|
|
270
|
+
const dayCount = stats.commitsByDay.get(date) || 0;
|
|
271
|
+
stats.commitsByDay.set(date, dayCount + 1);
|
|
272
|
+
const authorCount = stats.commitsByAuthor.get(author) || 0;
|
|
273
|
+
stats.commitsByAuthor.set(author, authorCount + 1);
|
|
274
|
+
const repoCount = stats.commitsByRepo.get(repo) || 0;
|
|
275
|
+
stats.commitsByRepo.set(repo, repoCount + 1);
|
|
276
|
+
}
|
|
277
|
+
stats.repos.push({
|
|
278
|
+
name: repo,
|
|
279
|
+
path: repoPath,
|
|
280
|
+
commits: commits.length,
|
|
281
|
+
lastCommit,
|
|
282
|
+
authors: Array.from(authors)
|
|
283
|
+
});
|
|
284
|
+
} catch {
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
stats.activeDays = stats.commitsByDay.size;
|
|
288
|
+
stats.avgCommitsPerDay = stats.activeDays > 0 ? Math.round(stats.totalCommits / days * 10) / 10 : 0;
|
|
289
|
+
let peakCount = 0;
|
|
290
|
+
let peakDate = "";
|
|
291
|
+
for (const [date, count] of stats.commitsByDay) {
|
|
292
|
+
if (count > peakCount) {
|
|
293
|
+
peakCount = count;
|
|
294
|
+
peakDate = date;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
if (peakDate) {
|
|
298
|
+
stats.peakDay = { date: peakDate, count: peakCount };
|
|
299
|
+
}
|
|
300
|
+
return stats;
|
|
301
|
+
}
|
|
302
|
+
function getActivitySparkline(basePath, days = 7) {
|
|
303
|
+
const activity = [];
|
|
304
|
+
const now = /* @__PURE__ */ new Date();
|
|
305
|
+
for (let i = days - 1; i >= 0; i--) {
|
|
306
|
+
const date = new Date(now);
|
|
307
|
+
date.setDate(date.getDate() - i);
|
|
308
|
+
const dateStr = date.toISOString().split("T")[0];
|
|
309
|
+
activity.push(0);
|
|
310
|
+
}
|
|
311
|
+
for (const repo of SQUAD_REPOS) {
|
|
312
|
+
const repoPath = join(basePath, repo);
|
|
313
|
+
if (!existsSync(repoPath) || !existsSync(join(repoPath, ".git"))) {
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
try {
|
|
317
|
+
const logOutput = execSync(
|
|
318
|
+
`git log --since="${days} days ago" --format="%ad" --date=short 2>/dev/null`,
|
|
319
|
+
{ cwd: repoPath, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
320
|
+
).trim();
|
|
321
|
+
if (!logOutput) continue;
|
|
322
|
+
for (const dateStr of logOutput.split("\n")) {
|
|
323
|
+
const commitDate = new Date(dateStr);
|
|
324
|
+
const daysAgo = Math.floor((now.getTime() - commitDate.getTime()) / (1e3 * 60 * 60 * 24));
|
|
325
|
+
const index = days - 1 - daysAgo;
|
|
326
|
+
if (index >= 0 && index < days) {
|
|
327
|
+
activity[index]++;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
} catch {
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return activity;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// src/lib/telemetry.ts
|
|
337
|
+
import { existsSync as existsSync2, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
338
|
+
import { join as join2 } from "path";
|
|
339
|
+
import { homedir } from "os";
|
|
340
|
+
import { randomUUID } from "crypto";
|
|
341
|
+
var TELEMETRY_DIR = join2(homedir(), ".squads-cli");
|
|
342
|
+
var CONFIG_PATH = join2(TELEMETRY_DIR, "telemetry.json");
|
|
343
|
+
var EVENTS_PATH = join2(TELEMETRY_DIR, "events.json");
|
|
344
|
+
var TELEMETRY_ENDPOINT = process.env.SQUADS_TELEMETRY_URL || (process.env.SQUADS_BRIDGE_URL ? `${process.env.SQUADS_BRIDGE_URL}/api/telemetry` : null);
|
|
345
|
+
var eventQueue = [];
|
|
346
|
+
var flushScheduled = false;
|
|
347
|
+
function ensureDir() {
|
|
348
|
+
if (!existsSync2(TELEMETRY_DIR)) {
|
|
349
|
+
mkdirSync(TELEMETRY_DIR, { recursive: true });
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
function getConfig() {
|
|
353
|
+
ensureDir();
|
|
354
|
+
if (!existsSync2(CONFIG_PATH)) {
|
|
355
|
+
const config2 = {
|
|
356
|
+
enabled: true,
|
|
357
|
+
// Opt-out by default (common for CLIs)
|
|
358
|
+
anonymousId: randomUUID(),
|
|
359
|
+
firstRun: (/* @__PURE__ */ new Date()).toISOString()
|
|
360
|
+
};
|
|
361
|
+
writeFileSync(CONFIG_PATH, JSON.stringify(config2, null, 2));
|
|
362
|
+
return config2;
|
|
363
|
+
}
|
|
364
|
+
try {
|
|
365
|
+
return JSON.parse(readFileSync(CONFIG_PATH, "utf-8"));
|
|
366
|
+
} catch {
|
|
367
|
+
return { enabled: false, anonymousId: "", firstRun: "" };
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
function isEnabled() {
|
|
371
|
+
if (process.env.SQUADS_TELEMETRY_DISABLED === "1") {
|
|
372
|
+
return false;
|
|
373
|
+
}
|
|
374
|
+
if (process.env.DO_NOT_TRACK === "1") {
|
|
375
|
+
return false;
|
|
376
|
+
}
|
|
377
|
+
return getConfig().enabled;
|
|
378
|
+
}
|
|
379
|
+
async function track(event, properties) {
|
|
380
|
+
if (!isEnabled()) return;
|
|
381
|
+
const config2 = getConfig();
|
|
382
|
+
const telemetryEvent = {
|
|
383
|
+
event,
|
|
384
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
385
|
+
properties: {
|
|
386
|
+
...properties,
|
|
387
|
+
anonymousId: config2.anonymousId,
|
|
388
|
+
cliVersion: process.env.npm_package_version || "unknown"
|
|
389
|
+
}
|
|
390
|
+
};
|
|
391
|
+
storeEventLocally(telemetryEvent);
|
|
392
|
+
eventQueue.push(telemetryEvent);
|
|
393
|
+
if (TELEMETRY_ENDPOINT && !flushScheduled) {
|
|
394
|
+
flushScheduled = true;
|
|
395
|
+
setImmediate(() => {
|
|
396
|
+
flushEvents().catch(() => {
|
|
397
|
+
});
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
async function flushEvents() {
|
|
402
|
+
if (!TELEMETRY_ENDPOINT || eventQueue.length === 0) {
|
|
403
|
+
flushScheduled = false;
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
const batch = [...eventQueue];
|
|
407
|
+
eventQueue = [];
|
|
408
|
+
flushScheduled = false;
|
|
409
|
+
try {
|
|
410
|
+
await fetch(TELEMETRY_ENDPOINT, {
|
|
411
|
+
method: "POST",
|
|
412
|
+
headers: { "Content-Type": "application/json" },
|
|
413
|
+
body: JSON.stringify({ events: batch })
|
|
414
|
+
});
|
|
415
|
+
} catch {
|
|
416
|
+
eventQueue = [...batch, ...eventQueue].slice(-100);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
function storeEventLocally(event) {
|
|
420
|
+
ensureDir();
|
|
421
|
+
let events = [];
|
|
422
|
+
if (existsSync2(EVENTS_PATH)) {
|
|
423
|
+
try {
|
|
424
|
+
events = JSON.parse(readFileSync(EVENTS_PATH, "utf-8"));
|
|
425
|
+
} catch {
|
|
426
|
+
events = [];
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
events.push(event);
|
|
430
|
+
if (events.length > 1e3) {
|
|
431
|
+
events = events.slice(-1e3);
|
|
432
|
+
}
|
|
433
|
+
writeFileSync(EVENTS_PATH, JSON.stringify(events, null, 2));
|
|
434
|
+
}
|
|
435
|
+
var Events = {
|
|
436
|
+
// Lifecycle
|
|
437
|
+
CLI_INIT: "cli.init",
|
|
438
|
+
CLI_ERROR: "cli.error",
|
|
439
|
+
// Commands
|
|
440
|
+
CLI_RUN: "cli.run",
|
|
441
|
+
CLI_STATUS: "cli.status",
|
|
442
|
+
CLI_DASHBOARD: "cli.dashboard",
|
|
443
|
+
CLI_WORKERS: "cli.workers",
|
|
444
|
+
// Goals
|
|
445
|
+
CLI_GOAL_SET: "cli.goal.set",
|
|
446
|
+
CLI_GOAL_LIST: "cli.goal.list",
|
|
447
|
+
CLI_GOAL_COMPLETE: "cli.goal.complete",
|
|
448
|
+
CLI_GOAL_PROGRESS: "cli.goal.progress",
|
|
449
|
+
// Memory
|
|
450
|
+
CLI_MEMORY_QUERY: "cli.memory.query",
|
|
451
|
+
CLI_MEMORY_SHOW: "cli.memory.show",
|
|
452
|
+
CLI_MEMORY_UPDATE: "cli.memory.update",
|
|
453
|
+
CLI_MEMORY_LIST: "cli.memory.list",
|
|
454
|
+
CLI_MEMORY_SYNC: "cli.memory.sync",
|
|
455
|
+
// Feedback
|
|
456
|
+
CLI_FEEDBACK_ADD: "cli.feedback.add",
|
|
457
|
+
CLI_FEEDBACK_SHOW: "cli.feedback.show",
|
|
458
|
+
CLI_FEEDBACK_STATS: "cli.feedback.stats",
|
|
459
|
+
// Auth
|
|
460
|
+
CLI_LOGIN: "cli.login",
|
|
461
|
+
CLI_LOGOUT: "cli.logout"
|
|
462
|
+
};
|
|
463
|
+
var exitHandlerRegistered = false;
|
|
464
|
+
function registerExitHandler() {
|
|
465
|
+
if (exitHandlerRegistered) return;
|
|
466
|
+
exitHandlerRegistered = true;
|
|
467
|
+
const cleanup = () => {
|
|
468
|
+
if (eventQueue.length > 0 && TELEMETRY_ENDPOINT) {
|
|
469
|
+
storeEventLocally({
|
|
470
|
+
event: "cli.exit",
|
|
471
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
472
|
+
properties: { pendingEvents: eventQueue.length }
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
};
|
|
476
|
+
process.on("exit", cleanup);
|
|
477
|
+
process.on("SIGINT", () => {
|
|
478
|
+
cleanup();
|
|
479
|
+
process.exit(0);
|
|
480
|
+
});
|
|
481
|
+
process.on("SIGTERM", () => {
|
|
482
|
+
cleanup();
|
|
483
|
+
process.exit(0);
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// src/commands/init.ts
|
|
488
|
+
async function initCommand(options) {
|
|
489
|
+
const cwd = process.cwd();
|
|
490
|
+
console.log(chalk.dim("Checking project setup...\n"));
|
|
491
|
+
const gitStatus = checkGitStatus(cwd);
|
|
492
|
+
if (!gitStatus.isGitRepo) {
|
|
493
|
+
console.log(chalk.yellow("\u26A0 No git repository found."));
|
|
494
|
+
console.log(chalk.dim(" Squads work best with git for version control and GitHub integration."));
|
|
495
|
+
console.log(chalk.dim(" Run: git init && git remote add origin <your-repo-url>\n"));
|
|
496
|
+
} else {
|
|
497
|
+
console.log(chalk.green("\u2713 Git repository detected"));
|
|
498
|
+
if (gitStatus.hasRemote) {
|
|
499
|
+
const repoName = getRepoName(gitStatus.remoteUrl);
|
|
500
|
+
console.log(chalk.green(`\u2713 Remote: ${chalk.cyan(repoName || gitStatus.remoteUrl)}`));
|
|
501
|
+
} else {
|
|
502
|
+
console.log(chalk.yellow("\u26A0 No remote configured."));
|
|
503
|
+
console.log(chalk.dim(" Add a remote: git remote add origin <your-repo-url>\n"));
|
|
504
|
+
}
|
|
505
|
+
if (gitStatus.isDirty) {
|
|
506
|
+
console.log(chalk.yellow(`\u26A0 ${gitStatus.uncommittedCount} uncommitted changes`));
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
console.log();
|
|
510
|
+
const spinner = ora("Initializing squad project...").start();
|
|
511
|
+
try {
|
|
512
|
+
const dirs = [
|
|
513
|
+
".agents/squads",
|
|
514
|
+
".agents/memory",
|
|
515
|
+
".agents/outputs",
|
|
516
|
+
".claude"
|
|
517
|
+
];
|
|
518
|
+
for (const dir of dirs) {
|
|
519
|
+
await fs.mkdir(path.join(cwd, dir), { recursive: true });
|
|
520
|
+
}
|
|
521
|
+
const commitTemplate = `
|
|
522
|
+
|
|
523
|
+
# \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
524
|
+
# Commit message format (delete this comment block):
|
|
525
|
+
#
|
|
526
|
+
# <type>(<scope>): <subject>
|
|
527
|
+
#
|
|
528
|
+
# <body>
|
|
529
|
+
#
|
|
530
|
+
# \u{1F916} Generated with [Agents Squads](https://agents-squads.com)
|
|
531
|
+
#
|
|
532
|
+
# Co-Authored-By: <model> <email>
|
|
533
|
+
# \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
534
|
+
# AI Models (add those that contributed to this commit):
|
|
535
|
+
#
|
|
536
|
+
# Model | Email | API Key
|
|
537
|
+
# --------------------|----------------------------|------------------
|
|
538
|
+
# Claude Opus 4.5 | <noreply@anthropic.com> | ANTHROPIC_API_KEY
|
|
539
|
+
# Claude Sonnet 4 | <noreply@anthropic.com> | ANTHROPIC_API_KEY
|
|
540
|
+
# Claude Haiku 3.5 | <noreply@anthropic.com> | ANTHROPIC_API_KEY
|
|
541
|
+
# GPT-4o | <noreply@openai.com> | OPENAI_API_KEY
|
|
542
|
+
# GPT-o1 | <noreply@openai.com> | OPENAI_API_KEY
|
|
543
|
+
# Gemini 2.0 Flash | <noreply@google.com> | GEMINI_API_KEY
|
|
544
|
+
# Grok 3 | <noreply@x.ai> | XAI_API_KEY
|
|
545
|
+
# Perplexity | <noreply@perplexity.ai> | PERPLEXITY_API_KEY
|
|
546
|
+
# Manus AI | <noreply@manus.im> | MANUS_API_KEY
|
|
547
|
+
#
|
|
548
|
+
# Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
|
|
549
|
+
# Co-Authored-By: Claude Sonnet 4 <noreply@anthropic.com>
|
|
550
|
+
# Co-Authored-By: Claude Haiku 3.5 <noreply@anthropic.com>
|
|
551
|
+
# Co-Authored-By: GPT-4o <noreply@openai.com>
|
|
552
|
+
# Co-Authored-By: GPT-o1 <noreply@openai.com>
|
|
553
|
+
# Co-Authored-By: Gemini 2.0 Flash <noreply@google.com>
|
|
554
|
+
# Co-Authored-By: Grok 3 <noreply@x.ai>
|
|
555
|
+
# Co-Authored-By: Perplexity <noreply@perplexity.ai>
|
|
556
|
+
# Co-Authored-By: Manus AI <noreply@manus.im>
|
|
557
|
+
# \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
558
|
+
`;
|
|
559
|
+
await fs.writeFile(
|
|
560
|
+
path.join(cwd, ".agents/commit-template.txt"),
|
|
561
|
+
commitTemplate
|
|
562
|
+
);
|
|
563
|
+
const mailmap = `# Git author name consolidation
|
|
564
|
+
# Format: Proper Name <proper@email> Commit Name <commit@email>
|
|
565
|
+
# Add entries to consolidate multiple git identities
|
|
566
|
+
|
|
567
|
+
# AI Contributors
|
|
568
|
+
Agents Squads <agents@agents-squads.com> Agents Squads <agents@agents-squads.com>
|
|
569
|
+
`;
|
|
570
|
+
await fs.writeFile(path.join(cwd, ".mailmap"), mailmap);
|
|
571
|
+
const claudeSettings = {
|
|
572
|
+
hooks: {
|
|
573
|
+
SessionStart: [
|
|
574
|
+
{
|
|
575
|
+
hooks: [
|
|
576
|
+
{
|
|
577
|
+
type: "command",
|
|
578
|
+
command: "squads status",
|
|
579
|
+
timeout: 10
|
|
580
|
+
}
|
|
581
|
+
]
|
|
582
|
+
}
|
|
583
|
+
],
|
|
584
|
+
Stop: [
|
|
585
|
+
{
|
|
586
|
+
hooks: [
|
|
587
|
+
{
|
|
588
|
+
type: "command",
|
|
589
|
+
command: "squads memory sync",
|
|
590
|
+
timeout: 15
|
|
591
|
+
}
|
|
592
|
+
]
|
|
593
|
+
}
|
|
594
|
+
]
|
|
595
|
+
}
|
|
596
|
+
};
|
|
597
|
+
await fs.writeFile(
|
|
598
|
+
path.join(cwd, ".claude/settings.json"),
|
|
599
|
+
JSON.stringify(claudeSettings, null, 2)
|
|
600
|
+
);
|
|
601
|
+
const exampleAgent = `# Example Agent
|
|
602
|
+
|
|
603
|
+
## Purpose
|
|
604
|
+
Demonstrate basic agent structure.
|
|
605
|
+
|
|
606
|
+
## Model
|
|
607
|
+
claude-sonnet-4
|
|
608
|
+
|
|
609
|
+
## Tools
|
|
610
|
+
- Read
|
|
611
|
+
- Write
|
|
612
|
+
- WebSearch
|
|
613
|
+
|
|
614
|
+
## Instructions
|
|
615
|
+
1. Greet the user
|
|
616
|
+
2. Ask how you can help
|
|
617
|
+
3. Execute the task
|
|
618
|
+
|
|
619
|
+
## Output
|
|
620
|
+
Markdown summary of actions taken.
|
|
621
|
+
`;
|
|
622
|
+
await fs.writeFile(
|
|
623
|
+
path.join(cwd, ".agents/squads/example-agent.md"),
|
|
624
|
+
exampleAgent
|
|
625
|
+
);
|
|
626
|
+
const claudeMdPath = path.join(cwd, "CLAUDE.md");
|
|
627
|
+
try {
|
|
628
|
+
await fs.access(claudeMdPath);
|
|
629
|
+
} catch {
|
|
630
|
+
await fs.writeFile(
|
|
631
|
+
claudeMdPath,
|
|
632
|
+
`# Project Instructions
|
|
633
|
+
|
|
634
|
+
## Squads CLI
|
|
635
|
+
|
|
636
|
+
This project uses AI agent squads. The \`squads\` CLI provides persistent memory across sessions.
|
|
637
|
+
|
|
638
|
+
### Key Commands
|
|
639
|
+
|
|
640
|
+
| Command | Purpose |
|
|
641
|
+
|---------|---------|
|
|
642
|
+
| \`squads status\` | Overview of all squads (runs on session start) |
|
|
643
|
+
| \`squads dash\` | Full operational dashboard |
|
|
644
|
+
| \`squads dash --ceo\` | Executive summary with P0/P1 priorities |
|
|
645
|
+
| \`squads goal list\` | View all active goals |
|
|
646
|
+
| \`squads memory query "<topic>"\` | Search squad memory before researching |
|
|
647
|
+
| \`squads run <squad>\` | Execute a squad |
|
|
648
|
+
|
|
649
|
+
### Workflow
|
|
650
|
+
|
|
651
|
+
1. **Session Start**: \`squads status\` runs automatically via hook
|
|
652
|
+
2. **Before Research**: Query memory to avoid re-doing work
|
|
653
|
+
3. **Session End**: Memory syncs automatically from git commits
|
|
654
|
+
|
|
655
|
+
### Git Commit Format
|
|
656
|
+
|
|
657
|
+
All commits should use the Agents Squads format:
|
|
658
|
+
|
|
659
|
+
\`\`\`
|
|
660
|
+
<type>(<scope>): <subject>
|
|
661
|
+
|
|
662
|
+
<body>
|
|
663
|
+
|
|
664
|
+
\u{1F916} Generated with [Agents Squads](https://agents-squads.com)
|
|
665
|
+
|
|
666
|
+
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
|
|
667
|
+
\`\`\`
|
|
668
|
+
|
|
669
|
+
**AI Models** (include those that contributed):
|
|
670
|
+
|
|
671
|
+
| Model | Co-Author Email | API Key Required |
|
|
672
|
+
|-------|-----------------|------------------|
|
|
673
|
+
| Claude Opus 4.5 | \`<noreply@anthropic.com>\` | \`ANTHROPIC_API_KEY\` |
|
|
674
|
+
| Claude Sonnet 4 | \`<noreply@anthropic.com>\` | \`ANTHROPIC_API_KEY\` |
|
|
675
|
+
| Claude Haiku 3.5 | \`<noreply@anthropic.com>\` | \`ANTHROPIC_API_KEY\` |
|
|
676
|
+
| GPT-4o | \`<noreply@openai.com>\` | \`OPENAI_API_KEY\` |
|
|
677
|
+
| GPT-o1 | \`<noreply@openai.com>\` | \`OPENAI_API_KEY\` |
|
|
678
|
+
| Gemini 2.0 Flash | \`<noreply@google.com>\` | \`GEMINI_API_KEY\` |
|
|
679
|
+
| Grok 3 | \`<noreply@x.ai>\` | \`XAI_API_KEY\` |
|
|
680
|
+
| Perplexity | \`<noreply@perplexity.ai>\` | \`PERPLEXITY_API_KEY\` |
|
|
681
|
+
| Manus AI | \`<noreply@manus.im>\` | \`MANUS_API_KEY\` |
|
|
682
|
+
|
|
683
|
+
### For Reports
|
|
684
|
+
|
|
685
|
+
Always use CLI commands for status reports:
|
|
686
|
+
- Executive summary: \`squads dash --ceo\`
|
|
687
|
+
- Operational view: \`squads dash\`
|
|
688
|
+
- Goals: \`squads goal list\`
|
|
689
|
+
`
|
|
690
|
+
);
|
|
691
|
+
}
|
|
692
|
+
spinner.succeed("Squad project initialized!");
|
|
693
|
+
await track(Events.CLI_INIT, {
|
|
694
|
+
hasGit: gitStatus.isGitRepo,
|
|
695
|
+
hasRemote: gitStatus.hasRemote,
|
|
696
|
+
template: options.template
|
|
697
|
+
});
|
|
698
|
+
console.log(`
|
|
699
|
+
${chalk.green("Success!")} Created squad project structure:
|
|
700
|
+
|
|
701
|
+
${chalk.cyan(".agents/")}
|
|
702
|
+
${chalk.dim("\u251C\u2500\u2500")} ${chalk.cyan("squads/")} Squad & agent definitions
|
|
703
|
+
${chalk.dim("\u251C\u2500\u2500")} ${chalk.cyan("memory/")} Persistent squad memory
|
|
704
|
+
${chalk.dim("\u251C\u2500\u2500")} ${chalk.cyan("outputs/")} Squad outputs
|
|
705
|
+
${chalk.dim("\u2514\u2500\u2500")} ${chalk.cyan("commit-template.txt")} Git commit format
|
|
706
|
+
|
|
707
|
+
${chalk.cyan(".mailmap")} Author name consolidation
|
|
708
|
+
|
|
709
|
+
${chalk.dim("Commit format:")}
|
|
710
|
+
\u{1F916} Generated with [Agents Squads](https://agents-squads.com)
|
|
711
|
+
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
|
|
712
|
+
|
|
713
|
+
${chalk.dim("Next steps:")}
|
|
714
|
+
${chalk.cyan("1.")} Create a squad: ${chalk.yellow("mkdir .agents/squads/my-squad && touch .agents/squads/my-squad/SQUAD.md")}
|
|
715
|
+
${chalk.cyan("2.")} Set a goal: ${chalk.yellow('squads goal set my-squad "Solve a problem"')}
|
|
716
|
+
${chalk.cyan("3.")} Run it: ${chalk.yellow("squads run my-squad")}
|
|
717
|
+
|
|
718
|
+
${chalk.dim("Put your first squad to work solving a problem in minutes.")}
|
|
719
|
+
`);
|
|
720
|
+
} catch (error) {
|
|
721
|
+
spinner.fail("Failed to initialize project");
|
|
722
|
+
console.error(chalk.red(error));
|
|
723
|
+
process.exit(1);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
// src/commands/run.ts
|
|
728
|
+
import ora2 from "ora";
|
|
729
|
+
import { spawn } from "child_process";
|
|
730
|
+
import { join as join5, dirname as dirname2 } from "path";
|
|
731
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync4, mkdirSync as mkdirSync3 } from "fs";
|
|
732
|
+
|
|
733
|
+
// src/lib/squad-parser.ts
|
|
734
|
+
import { readFileSync as readFileSync2, existsSync as existsSync3, readdirSync, writeFileSync as writeFileSync2 } from "fs";
|
|
735
|
+
import { join as join3, basename } from "path";
|
|
736
|
+
function findSquadsDir() {
|
|
737
|
+
let dir = process.cwd();
|
|
738
|
+
for (let i = 0; i < 5; i++) {
|
|
739
|
+
const squadsPath = join3(dir, ".agents", "squads");
|
|
740
|
+
if (existsSync3(squadsPath)) {
|
|
741
|
+
return squadsPath;
|
|
742
|
+
}
|
|
743
|
+
const parent = join3(dir, "..");
|
|
744
|
+
if (parent === dir) break;
|
|
745
|
+
dir = parent;
|
|
746
|
+
}
|
|
747
|
+
return null;
|
|
748
|
+
}
|
|
749
|
+
function listSquads(squadsDir) {
|
|
750
|
+
const squads = [];
|
|
751
|
+
const entries = readdirSync(squadsDir, { withFileTypes: true });
|
|
752
|
+
for (const entry of entries) {
|
|
753
|
+
if (entry.isDirectory() && !entry.name.startsWith("_")) {
|
|
754
|
+
const squadFile = join3(squadsDir, entry.name, "SQUAD.md");
|
|
755
|
+
if (existsSync3(squadFile)) {
|
|
756
|
+
squads.push(entry.name);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
return squads;
|
|
761
|
+
}
|
|
762
|
+
function listAgents(squadsDir, squadName) {
|
|
763
|
+
const agents = [];
|
|
764
|
+
const dirs = squadName ? [squadName] : readdirSync(squadsDir, { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith("_")).map((e) => e.name);
|
|
765
|
+
for (const dir of dirs) {
|
|
766
|
+
const squadPath = join3(squadsDir, dir);
|
|
767
|
+
if (!existsSync3(squadPath)) continue;
|
|
768
|
+
const files = readdirSync(squadPath);
|
|
769
|
+
for (const file of files) {
|
|
770
|
+
if (file.endsWith(".md") && file !== "SQUAD.md") {
|
|
771
|
+
const agentName = file.replace(".md", "");
|
|
772
|
+
agents.push({
|
|
773
|
+
name: agentName,
|
|
774
|
+
role: `Agent in ${dir}`,
|
|
775
|
+
trigger: "manual",
|
|
776
|
+
filePath: join3(squadPath, file)
|
|
777
|
+
});
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
return agents;
|
|
782
|
+
}
|
|
783
|
+
function parseSquadFile(filePath) {
|
|
784
|
+
const content = readFileSync2(filePath, "utf-8");
|
|
785
|
+
const lines = content.split("\n");
|
|
786
|
+
const squad = {
|
|
787
|
+
name: basename(filePath).replace(".md", ""),
|
|
788
|
+
mission: "",
|
|
789
|
+
agents: [],
|
|
790
|
+
pipelines: [],
|
|
791
|
+
triggers: { scheduled: [], event: [], manual: [] },
|
|
792
|
+
dependencies: [],
|
|
793
|
+
outputPath: "",
|
|
794
|
+
goals: []
|
|
795
|
+
};
|
|
796
|
+
let currentSection = "";
|
|
797
|
+
let inTable = false;
|
|
798
|
+
let tableHeaders = [];
|
|
799
|
+
for (const line of lines) {
|
|
800
|
+
if (line.startsWith("# Squad:")) {
|
|
801
|
+
squad.name = line.replace("# Squad:", "").trim().toLowerCase();
|
|
802
|
+
continue;
|
|
803
|
+
}
|
|
804
|
+
if (line.startsWith("## ")) {
|
|
805
|
+
currentSection = line.replace("## ", "").trim().toLowerCase();
|
|
806
|
+
inTable = false;
|
|
807
|
+
continue;
|
|
808
|
+
}
|
|
809
|
+
if (currentSection === "mission" && line.trim() && !line.startsWith("#")) {
|
|
810
|
+
if (!squad.mission) {
|
|
811
|
+
squad.mission = line.trim();
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
if (currentSection.includes("agent") || currentSection.includes("orchestrator") || currentSection.includes("evaluator") || currentSection.includes("builder") || currentSection.includes("priority")) {
|
|
815
|
+
if (line.includes("|") && line.includes("Agent")) {
|
|
816
|
+
inTable = true;
|
|
817
|
+
tableHeaders = line.split("|").map((h) => h.trim().toLowerCase());
|
|
818
|
+
continue;
|
|
819
|
+
}
|
|
820
|
+
if (inTable && line.includes("|") && !line.includes("---")) {
|
|
821
|
+
const cells = line.split("|").map((c) => c.trim().replace(/`/g, ""));
|
|
822
|
+
const agentIdx = tableHeaders.findIndex((h) => h === "agent");
|
|
823
|
+
const roleIdx = tableHeaders.findIndex((h) => h === "role");
|
|
824
|
+
const triggerIdx = tableHeaders.findIndex((h) => h === "trigger");
|
|
825
|
+
const statusIdx = tableHeaders.findIndex((h) => h === "status");
|
|
826
|
+
if (agentIdx >= 0 && cells[agentIdx]) {
|
|
827
|
+
squad.agents.push({
|
|
828
|
+
name: cells[agentIdx],
|
|
829
|
+
role: roleIdx >= 0 ? cells[roleIdx] : "",
|
|
830
|
+
trigger: triggerIdx >= 0 ? cells[triggerIdx] : "manual",
|
|
831
|
+
status: statusIdx >= 0 ? cells[statusIdx] : "active"
|
|
832
|
+
});
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
if (line.includes("\u2192") && line.includes("`")) {
|
|
837
|
+
const pipelineMatch = line.match(/`([^`]+)`\s*→\s*`([^`]+)`/g);
|
|
838
|
+
if (pipelineMatch) {
|
|
839
|
+
const agentNames = line.match(/`([^`]+)`/g)?.map((m) => m.replace(/`/g, "")) || [];
|
|
840
|
+
if (agentNames.length >= 2) {
|
|
841
|
+
squad.pipelines.push({
|
|
842
|
+
name: "default",
|
|
843
|
+
agents: agentNames
|
|
844
|
+
});
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
if (line.toLowerCase().includes("pipeline:")) {
|
|
849
|
+
const pipelineContent = line.split(":")[1];
|
|
850
|
+
if (pipelineContent && pipelineContent.includes("\u2192")) {
|
|
851
|
+
const agentNames = pipelineContent.match(/`([^`]+)`/g)?.map((m) => m.replace(/`/g, "")) || [];
|
|
852
|
+
if (agentNames.length >= 2) {
|
|
853
|
+
squad.pipelines.push({
|
|
854
|
+
name: "default",
|
|
855
|
+
agents: agentNames
|
|
856
|
+
});
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
if (line.toLowerCase().includes("primary") && line.includes("`")) {
|
|
861
|
+
const match = line.match(/`([^`]+)`/);
|
|
862
|
+
if (match) {
|
|
863
|
+
squad.outputPath = match[1].replace(/\/$/, "");
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
if (currentSection === "goals") {
|
|
867
|
+
const goalMatch = line.match(/^-\s*\[([ x])\]\s*(.+)$/);
|
|
868
|
+
if (goalMatch) {
|
|
869
|
+
const completed = goalMatch[1] === "x";
|
|
870
|
+
let description = goalMatch[2].trim();
|
|
871
|
+
let progress2;
|
|
872
|
+
const progressMatch = description.match(/\(progress:\s*([^)]+)\)/i);
|
|
873
|
+
if (progressMatch) {
|
|
874
|
+
progress2 = progressMatch[1];
|
|
875
|
+
description = description.replace(progressMatch[0], "").trim();
|
|
876
|
+
}
|
|
877
|
+
squad.goals.push({
|
|
878
|
+
description,
|
|
879
|
+
completed,
|
|
880
|
+
progress: progress2
|
|
881
|
+
});
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
return squad;
|
|
886
|
+
}
|
|
887
|
+
function loadSquad(squadName) {
|
|
888
|
+
const squadsDir = findSquadsDir();
|
|
889
|
+
if (!squadsDir) return null;
|
|
890
|
+
const squadFile = join3(squadsDir, squadName, "SQUAD.md");
|
|
891
|
+
if (!existsSync3(squadFile)) return null;
|
|
892
|
+
return parseSquadFile(squadFile);
|
|
893
|
+
}
|
|
894
|
+
function loadAgentDefinition(agentPath) {
|
|
895
|
+
if (!existsSync3(agentPath)) return "";
|
|
896
|
+
return readFileSync2(agentPath, "utf-8");
|
|
897
|
+
}
|
|
898
|
+
function addGoalToSquad(squadName, goal2) {
|
|
899
|
+
const squadsDir = findSquadsDir();
|
|
900
|
+
if (!squadsDir) return false;
|
|
901
|
+
const squadFile = join3(squadsDir, squadName, "SQUAD.md");
|
|
902
|
+
if (!existsSync3(squadFile)) return false;
|
|
903
|
+
let content = readFileSync2(squadFile, "utf-8");
|
|
904
|
+
if (!content.includes("## Goals")) {
|
|
905
|
+
const insertPoint = content.indexOf("## Dependencies");
|
|
906
|
+
if (insertPoint > 0) {
|
|
907
|
+
content = content.slice(0, insertPoint) + `## Goals
|
|
908
|
+
|
|
909
|
+
- [ ] ${goal2}
|
|
910
|
+
|
|
911
|
+
` + content.slice(insertPoint);
|
|
912
|
+
} else {
|
|
913
|
+
content += `
|
|
914
|
+
## Goals
|
|
915
|
+
|
|
916
|
+
- [ ] ${goal2}
|
|
917
|
+
`;
|
|
918
|
+
}
|
|
919
|
+
} else {
|
|
920
|
+
const goalsIdx = content.indexOf("## Goals");
|
|
921
|
+
const nextSectionIdx = content.indexOf("\n## ", goalsIdx + 1);
|
|
922
|
+
const endIdx = nextSectionIdx > 0 ? nextSectionIdx : content.length;
|
|
923
|
+
const goalsSection = content.slice(goalsIdx, endIdx);
|
|
924
|
+
const lastGoalMatch = goalsSection.match(/^-\s*\[[ x]\].+$/gm);
|
|
925
|
+
if (lastGoalMatch) {
|
|
926
|
+
const lastGoal = lastGoalMatch[lastGoalMatch.length - 1];
|
|
927
|
+
const lastGoalIdx = content.lastIndexOf(lastGoal, endIdx);
|
|
928
|
+
const insertPos = lastGoalIdx + lastGoal.length;
|
|
929
|
+
content = content.slice(0, insertPos) + `
|
|
930
|
+
- [ ] ${goal2}` + content.slice(insertPos);
|
|
931
|
+
} else {
|
|
932
|
+
const headerEnd = goalsIdx + "## Goals".length;
|
|
933
|
+
content = content.slice(0, headerEnd) + `
|
|
934
|
+
|
|
935
|
+
- [ ] ${goal2}` + content.slice(headerEnd);
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
writeFileSync2(squadFile, content);
|
|
939
|
+
return true;
|
|
940
|
+
}
|
|
941
|
+
function updateGoalInSquad(squadName, goalIndex, updates) {
|
|
942
|
+
const squadsDir = findSquadsDir();
|
|
943
|
+
if (!squadsDir) return false;
|
|
944
|
+
const squadFile = join3(squadsDir, squadName, "SQUAD.md");
|
|
945
|
+
if (!existsSync3(squadFile)) return false;
|
|
946
|
+
const content = readFileSync2(squadFile, "utf-8");
|
|
947
|
+
const lines = content.split("\n");
|
|
948
|
+
let currentSection = "";
|
|
949
|
+
let goalCount = 0;
|
|
950
|
+
for (let i = 0; i < lines.length; i++) {
|
|
951
|
+
const line = lines[i];
|
|
952
|
+
if (line.startsWith("## ")) {
|
|
953
|
+
currentSection = line.replace("## ", "").trim().toLowerCase();
|
|
954
|
+
continue;
|
|
955
|
+
}
|
|
956
|
+
if (currentSection === "goals") {
|
|
957
|
+
const goalMatch = line.match(/^-\s*\[([ x])\]\s*(.+)$/);
|
|
958
|
+
if (goalMatch) {
|
|
959
|
+
if (goalCount === goalIndex) {
|
|
960
|
+
let newLine = "- [" + (updates.completed ? "x" : " ") + "] " + goalMatch[2];
|
|
961
|
+
if (updates.progress !== void 0) {
|
|
962
|
+
newLine = newLine.replace(/\s*\(progress:\s*[^)]+\)/i, "");
|
|
963
|
+
if (updates.progress) {
|
|
964
|
+
newLine += ` (progress: ${updates.progress})`;
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
lines[i] = newLine;
|
|
968
|
+
writeFileSync2(squadFile, lines.join("\n"));
|
|
969
|
+
return true;
|
|
970
|
+
}
|
|
971
|
+
goalCount++;
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
return false;
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
// src/lib/memory.ts
|
|
979
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, existsSync as existsSync4, readdirSync as readdirSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
980
|
+
import { join as join4, dirname } from "path";
|
|
981
|
+
function findMemoryDir() {
|
|
982
|
+
let dir = process.cwd();
|
|
983
|
+
for (let i = 0; i < 5; i++) {
|
|
984
|
+
const memoryPath = join4(dir, ".agents", "memory");
|
|
985
|
+
if (existsSync4(memoryPath)) {
|
|
986
|
+
return memoryPath;
|
|
987
|
+
}
|
|
988
|
+
const parent = join4(dir, "..");
|
|
989
|
+
if (parent === dir) break;
|
|
990
|
+
dir = parent;
|
|
991
|
+
}
|
|
992
|
+
return null;
|
|
993
|
+
}
|
|
994
|
+
function listMemoryEntries(memoryDir) {
|
|
995
|
+
const entries = [];
|
|
996
|
+
const squads = readdirSync2(memoryDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
997
|
+
for (const squad of squads) {
|
|
998
|
+
const squadPath = join4(memoryDir, squad);
|
|
999
|
+
const agents = readdirSync2(squadPath, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
1000
|
+
for (const agent of agents) {
|
|
1001
|
+
const agentPath = join4(squadPath, agent);
|
|
1002
|
+
const files = readdirSync2(agentPath).filter((f) => f.endsWith(".md"));
|
|
1003
|
+
for (const file of files) {
|
|
1004
|
+
const filePath = join4(agentPath, file);
|
|
1005
|
+
const type = file.replace(".md", "");
|
|
1006
|
+
entries.push({
|
|
1007
|
+
squad,
|
|
1008
|
+
agent,
|
|
1009
|
+
type,
|
|
1010
|
+
content: readFileSync3(filePath, "utf-8"),
|
|
1011
|
+
path: filePath
|
|
1012
|
+
});
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
return entries;
|
|
1017
|
+
}
|
|
1018
|
+
var SEMANTIC_EXPANSIONS = {
|
|
1019
|
+
"pricing": ["price", "cost", "$", "revenue", "fee", "rate"],
|
|
1020
|
+
"price": ["pricing", "cost", "$", "fee"],
|
|
1021
|
+
"revenue": ["income", "sales", "mrr", "arr", "$"],
|
|
1022
|
+
"cost": ["expense", "spend", "budget", "$", "price"],
|
|
1023
|
+
"customer": ["client", "lead", "prospect", "user"],
|
|
1024
|
+
"client": ["customer", "lead", "prospect"],
|
|
1025
|
+
"lead": ["prospect", "customer", "client", "pipeline"],
|
|
1026
|
+
"agent": ["squad", "bot", "ai"],
|
|
1027
|
+
"squad": ["team", "agent", "group"],
|
|
1028
|
+
"status": ["state", "progress", "health"],
|
|
1029
|
+
"bug": ["issue", "error", "problem", "fix"],
|
|
1030
|
+
"feature": ["capability", "function", "ability"]
|
|
1031
|
+
};
|
|
1032
|
+
function expandQuery(query) {
|
|
1033
|
+
const words = query.toLowerCase().split(/\s+/);
|
|
1034
|
+
const expanded = new Set(words);
|
|
1035
|
+
for (const word of words) {
|
|
1036
|
+
if (SEMANTIC_EXPANSIONS[word]) {
|
|
1037
|
+
SEMANTIC_EXPANSIONS[word].forEach((syn) => expanded.add(syn));
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
return Array.from(expanded);
|
|
1041
|
+
}
|
|
1042
|
+
function getFileAge(filePath) {
|
|
1043
|
+
try {
|
|
1044
|
+
const { statSync: statSync3 } = __require("fs");
|
|
1045
|
+
const stats = statSync3(filePath);
|
|
1046
|
+
const ageMs = Date.now() - stats.mtimeMs;
|
|
1047
|
+
const ageDays = ageMs / (1e3 * 60 * 60 * 24);
|
|
1048
|
+
return ageDays;
|
|
1049
|
+
} catch {
|
|
1050
|
+
return 999;
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
function searchMemory(query, memoryDir) {
|
|
1054
|
+
const dir = memoryDir || findMemoryDir();
|
|
1055
|
+
if (!dir) return [];
|
|
1056
|
+
const entries = listMemoryEntries(dir);
|
|
1057
|
+
const results = [];
|
|
1058
|
+
const queryLower = query.toLowerCase();
|
|
1059
|
+
const expandedTerms = expandQuery(queryLower);
|
|
1060
|
+
for (const entry of entries) {
|
|
1061
|
+
const contentLower = entry.content.toLowerCase();
|
|
1062
|
+
const lines = entry.content.split("\n");
|
|
1063
|
+
const matches = [];
|
|
1064
|
+
let score = 0;
|
|
1065
|
+
let directHits = 0;
|
|
1066
|
+
let expandedHits = 0;
|
|
1067
|
+
const directWords = queryLower.split(/\s+/);
|
|
1068
|
+
for (const word of directWords) {
|
|
1069
|
+
if (contentLower.includes(word)) {
|
|
1070
|
+
directHits += 1;
|
|
1071
|
+
score += 2;
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
for (const term of expandedTerms) {
|
|
1075
|
+
if (contentLower.includes(term)) {
|
|
1076
|
+
expandedHits += 1;
|
|
1077
|
+
score += 0.5;
|
|
1078
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1079
|
+
const line = lines[i];
|
|
1080
|
+
if (line.toLowerCase().includes(term) && line.trim() && !matches.includes(line.trim())) {
|
|
1081
|
+
matches.push(line.trim());
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
if (contentLower.includes(queryLower)) {
|
|
1087
|
+
score += 5;
|
|
1088
|
+
}
|
|
1089
|
+
const ageDays = getFileAge(entry.path);
|
|
1090
|
+
if (ageDays < 1) {
|
|
1091
|
+
score *= 1.5;
|
|
1092
|
+
} else if (ageDays < 7) {
|
|
1093
|
+
score *= 1.2;
|
|
1094
|
+
} else if (ageDays > 30) {
|
|
1095
|
+
score *= 0.8;
|
|
1096
|
+
}
|
|
1097
|
+
const typeWeights = {
|
|
1098
|
+
"state": 1.2,
|
|
1099
|
+
// Current state slightly preferred
|
|
1100
|
+
"learnings": 1.1,
|
|
1101
|
+
// Learnings are valuable
|
|
1102
|
+
"output": 1,
|
|
1103
|
+
// Recent outputs
|
|
1104
|
+
"feedback": 0.9
|
|
1105
|
+
// Feedback less commonly needed
|
|
1106
|
+
};
|
|
1107
|
+
score *= typeWeights[entry.type] || 1;
|
|
1108
|
+
if (score > 0 && (directHits > 0 || expandedHits > 1)) {
|
|
1109
|
+
results.push({ entry, matches: matches.slice(0, 7), score });
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
return results.sort((a, b) => b.score - a.score);
|
|
1113
|
+
}
|
|
1114
|
+
function getSquadState(squadName) {
|
|
1115
|
+
const memoryDir = findMemoryDir();
|
|
1116
|
+
if (!memoryDir) return [];
|
|
1117
|
+
const squadPath = join4(memoryDir, squadName);
|
|
1118
|
+
if (!existsSync4(squadPath)) return [];
|
|
1119
|
+
const entries = [];
|
|
1120
|
+
const agents = readdirSync2(squadPath, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
1121
|
+
for (const agent of agents) {
|
|
1122
|
+
const statePath = join4(squadPath, agent, "state.md");
|
|
1123
|
+
if (existsSync4(statePath)) {
|
|
1124
|
+
entries.push({
|
|
1125
|
+
squad: squadName,
|
|
1126
|
+
agent,
|
|
1127
|
+
type: "state",
|
|
1128
|
+
content: readFileSync3(statePath, "utf-8"),
|
|
1129
|
+
path: statePath
|
|
1130
|
+
});
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
return entries;
|
|
1134
|
+
}
|
|
1135
|
+
function updateMemory(squadName, agentName, type, content) {
|
|
1136
|
+
const memoryDir = findMemoryDir();
|
|
1137
|
+
if (!memoryDir) {
|
|
1138
|
+
throw new Error("No .agents/memory directory found");
|
|
1139
|
+
}
|
|
1140
|
+
const filePath = join4(memoryDir, squadName, agentName, `${type}.md`);
|
|
1141
|
+
const dir = dirname(filePath);
|
|
1142
|
+
if (!existsSync4(dir)) {
|
|
1143
|
+
mkdirSync2(dir, { recursive: true });
|
|
1144
|
+
}
|
|
1145
|
+
writeFileSync3(filePath, content);
|
|
1146
|
+
}
|
|
1147
|
+
function appendToMemory(squadName, agentName, type, addition) {
|
|
1148
|
+
const memoryDir = findMemoryDir();
|
|
1149
|
+
if (!memoryDir) {
|
|
1150
|
+
throw new Error("No .agents/memory directory found");
|
|
1151
|
+
}
|
|
1152
|
+
const filePath = join4(memoryDir, squadName, agentName, `${type}.md`);
|
|
1153
|
+
let existing = "";
|
|
1154
|
+
if (existsSync4(filePath)) {
|
|
1155
|
+
existing = readFileSync3(filePath, "utf-8");
|
|
1156
|
+
}
|
|
1157
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
1158
|
+
const newContent = existing + `
|
|
1159
|
+
|
|
1160
|
+
---
|
|
1161
|
+
_Added: ${timestamp}_
|
|
1162
|
+
|
|
1163
|
+
${addition}`;
|
|
1164
|
+
updateMemory(squadName, agentName, type, newContent.trim());
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
// src/lib/terminal.ts
|
|
1168
|
+
var ESC = "\x1B[";
|
|
1169
|
+
var RESET = `${ESC}0m`;
|
|
1170
|
+
var rgb = (r, g, b) => `${ESC}38;2;${r};${g};${b}m`;
|
|
1171
|
+
var colors = {
|
|
1172
|
+
purple: rgb(168, 85, 247),
|
|
1173
|
+
// #a855f7
|
|
1174
|
+
pink: rgb(236, 72, 153),
|
|
1175
|
+
// #ec4899
|
|
1176
|
+
cyan: rgb(6, 182, 212),
|
|
1177
|
+
// #06b6d4
|
|
1178
|
+
green: rgb(16, 185, 129),
|
|
1179
|
+
// #10b981
|
|
1180
|
+
yellow: rgb(234, 179, 8),
|
|
1181
|
+
// #eab308
|
|
1182
|
+
red: rgb(239, 68, 68),
|
|
1183
|
+
// #ef4444
|
|
1184
|
+
gray: rgb(107, 114, 128),
|
|
1185
|
+
// #6b7280
|
|
1186
|
+
dim: rgb(75, 85, 99),
|
|
1187
|
+
// #4b5563
|
|
1188
|
+
white: rgb(255, 255, 255)
|
|
1189
|
+
};
|
|
1190
|
+
var bold = `${ESC}1m`;
|
|
1191
|
+
var dim = `${ESC}2m`;
|
|
1192
|
+
var cursor = {
|
|
1193
|
+
hide: `${ESC}?25l`,
|
|
1194
|
+
show: `${ESC}?25h`,
|
|
1195
|
+
up: (n = 1) => `${ESC}${n}A`,
|
|
1196
|
+
down: (n = 1) => `${ESC}${n}B`,
|
|
1197
|
+
left: (n = 1) => `${ESC}${n}D`,
|
|
1198
|
+
right: (n = 1) => `${ESC}${n}C`,
|
|
1199
|
+
to: (x, y) => `${ESC}${y};${x}H`,
|
|
1200
|
+
save: `${ESC}s`,
|
|
1201
|
+
restore: `${ESC}u`
|
|
1202
|
+
};
|
|
1203
|
+
var clear = {
|
|
1204
|
+
line: `${ESC}2K`,
|
|
1205
|
+
toEnd: `${ESC}0K`,
|
|
1206
|
+
screen: `${ESC}2J${ESC}0;0H`
|
|
1207
|
+
};
|
|
1208
|
+
function gradient(text) {
|
|
1209
|
+
const stops = [
|
|
1210
|
+
[168, 85, 247],
|
|
1211
|
+
// purple
|
|
1212
|
+
[192, 132, 252],
|
|
1213
|
+
// purple-light
|
|
1214
|
+
[232, 121, 249],
|
|
1215
|
+
// pink
|
|
1216
|
+
[244, 114, 182],
|
|
1217
|
+
// pink-light
|
|
1218
|
+
[251, 113, 133]
|
|
1219
|
+
// rose
|
|
1220
|
+
];
|
|
1221
|
+
let result = "";
|
|
1222
|
+
for (let i = 0; i < text.length; i++) {
|
|
1223
|
+
const t = i / Math.max(text.length - 1, 1);
|
|
1224
|
+
const stopIndex = t * (stops.length - 1);
|
|
1225
|
+
const lower = Math.floor(stopIndex);
|
|
1226
|
+
const upper = Math.min(lower + 1, stops.length - 1);
|
|
1227
|
+
const blend = stopIndex - lower;
|
|
1228
|
+
const r = Math.round(stops[lower][0] + (stops[upper][0] - stops[lower][0]) * blend);
|
|
1229
|
+
const g = Math.round(stops[lower][1] + (stops[upper][1] - stops[lower][1]) * blend);
|
|
1230
|
+
const b = Math.round(stops[lower][2] + (stops[upper][2] - stops[lower][2]) * blend);
|
|
1231
|
+
result += rgb(r, g, b) + text[i];
|
|
1232
|
+
}
|
|
1233
|
+
return result + RESET;
|
|
1234
|
+
}
|
|
1235
|
+
function progressBar(percent, width = 20) {
|
|
1236
|
+
const filled = Math.round(percent / 100 * width);
|
|
1237
|
+
const empty = width - filled;
|
|
1238
|
+
let bar = "";
|
|
1239
|
+
for (let i = 0; i < filled; i++) {
|
|
1240
|
+
const t = i / Math.max(filled - 1, 1);
|
|
1241
|
+
const r = Math.round(16 + (168 - 16) * t);
|
|
1242
|
+
const g = Math.round(185 + (85 - 185) * t);
|
|
1243
|
+
const b = Math.round(129 + (247 - 129) * t);
|
|
1244
|
+
bar += rgb(r, g, b) + "\u2501";
|
|
1245
|
+
}
|
|
1246
|
+
bar += colors.dim + "\u2501".repeat(empty) + RESET;
|
|
1247
|
+
return bar;
|
|
1248
|
+
}
|
|
1249
|
+
var box = {
|
|
1250
|
+
topLeft: "\u250C",
|
|
1251
|
+
topRight: "\u2510",
|
|
1252
|
+
bottomLeft: "\u2514",
|
|
1253
|
+
bottomRight: "\u2518",
|
|
1254
|
+
horizontal: "\u2500",
|
|
1255
|
+
vertical: "\u2502",
|
|
1256
|
+
teeRight: "\u251C",
|
|
1257
|
+
teeLeft: "\u2524"
|
|
1258
|
+
};
|
|
1259
|
+
function padEnd(str, len) {
|
|
1260
|
+
const visible = str.replace(/\x1b\[[0-9;]*m/g, "");
|
|
1261
|
+
const pad = Math.max(0, len - visible.length);
|
|
1262
|
+
return str + " ".repeat(pad);
|
|
1263
|
+
}
|
|
1264
|
+
function truncate(str, len) {
|
|
1265
|
+
const visible = str.replace(/\x1b\[[0-9;]*m/g, "");
|
|
1266
|
+
if (visible.length <= len) return str;
|
|
1267
|
+
let result = "";
|
|
1268
|
+
let count = 0;
|
|
1269
|
+
let i = 0;
|
|
1270
|
+
while (i < str.length && count < len - 1) {
|
|
1271
|
+
if (str[i] === "\x1B") {
|
|
1272
|
+
const end = str.indexOf("m", i);
|
|
1273
|
+
if (end !== -1) {
|
|
1274
|
+
result += str.slice(i, end + 1);
|
|
1275
|
+
i = end + 1;
|
|
1276
|
+
continue;
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
result += str[i];
|
|
1280
|
+
count++;
|
|
1281
|
+
i++;
|
|
1282
|
+
}
|
|
1283
|
+
return result + colors.dim + "\u2026" + RESET;
|
|
1284
|
+
}
|
|
1285
|
+
var icons = {
|
|
1286
|
+
success: `${colors.green}\u25CF${RESET}`,
|
|
1287
|
+
warning: `${colors.yellow}\u25CB${RESET}`,
|
|
1288
|
+
error: `${colors.red}\u25CF${RESET}`,
|
|
1289
|
+
pending: `${colors.dim}\u25CB${RESET}`,
|
|
1290
|
+
active: `${colors.green}\u25CF${RESET}`,
|
|
1291
|
+
progress: `${colors.cyan}\u25C6${RESET}`,
|
|
1292
|
+
empty: `${colors.dim}\u25C7${RESET}`
|
|
1293
|
+
};
|
|
1294
|
+
function writeLine(str = "") {
|
|
1295
|
+
process.stdout.write(str + "\n");
|
|
1296
|
+
}
|
|
1297
|
+
function sparkline(values, width) {
|
|
1298
|
+
if (values.length === 0) return "";
|
|
1299
|
+
const blocks = ["\u2581", "\u2582", "\u2583", "\u2584", "\u2585", "\u2586", "\u2587", "\u2588"];
|
|
1300
|
+
const max = Math.max(...values, 1);
|
|
1301
|
+
let result = "";
|
|
1302
|
+
for (const val of values) {
|
|
1303
|
+
const normalized = val / max;
|
|
1304
|
+
const blockIndex = Math.min(Math.floor(normalized * blocks.length), blocks.length - 1);
|
|
1305
|
+
const intensity = normalized;
|
|
1306
|
+
if (normalized === 0) {
|
|
1307
|
+
result += colors.dim + blocks[0];
|
|
1308
|
+
} else if (normalized < 0.5) {
|
|
1309
|
+
result += colors.cyan + blocks[blockIndex];
|
|
1310
|
+
} else {
|
|
1311
|
+
result += colors.green + blocks[blockIndex];
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
return result + RESET;
|
|
1315
|
+
}
|
|
1316
|
+
function barChart(value, max, width = 20, label) {
|
|
1317
|
+
const filled = Math.round(value / max * width);
|
|
1318
|
+
const empty = width - filled;
|
|
1319
|
+
let bar = "";
|
|
1320
|
+
for (let i = 0; i < filled; i++) {
|
|
1321
|
+
const t = i / Math.max(filled - 1, 1);
|
|
1322
|
+
const r = Math.round(16 + (6 - 16) * t);
|
|
1323
|
+
const g = Math.round(185 + (182 - 185) * t);
|
|
1324
|
+
const b = Math.round(129 + (212 - 129) * t);
|
|
1325
|
+
bar += rgb(r, g, b) + "\u2501";
|
|
1326
|
+
}
|
|
1327
|
+
bar += colors.dim + "\u2501".repeat(empty) + RESET;
|
|
1328
|
+
if (label) {
|
|
1329
|
+
return `${bar} ${label}`;
|
|
1330
|
+
}
|
|
1331
|
+
return bar;
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
// src/commands/run.ts
|
|
1335
|
+
function getExecutionLogPath(squadName, agentName) {
|
|
1336
|
+
const memoryDir = findMemoryDir();
|
|
1337
|
+
if (!memoryDir) return null;
|
|
1338
|
+
return join5(memoryDir, squadName, agentName, "executions.md");
|
|
1339
|
+
}
|
|
1340
|
+
function logExecution(record) {
|
|
1341
|
+
const logPath = getExecutionLogPath(record.squadName, record.agentName);
|
|
1342
|
+
if (!logPath) return;
|
|
1343
|
+
const dir = dirname2(logPath);
|
|
1344
|
+
if (!existsSync5(dir)) {
|
|
1345
|
+
mkdirSync3(dir, { recursive: true });
|
|
1346
|
+
}
|
|
1347
|
+
let content = "";
|
|
1348
|
+
if (existsSync5(logPath)) {
|
|
1349
|
+
content = readFileSync4(logPath, "utf-8");
|
|
1350
|
+
} else {
|
|
1351
|
+
content = `# ${record.squadName}/${record.agentName} - Execution Log
|
|
1352
|
+
|
|
1353
|
+
`;
|
|
1354
|
+
}
|
|
1355
|
+
const entry = `
|
|
1356
|
+
---
|
|
1357
|
+
**${record.startTime}** | Status: ${record.status}
|
|
1358
|
+
${record.endTime ? `Completed: ${record.endTime}` : ""}
|
|
1359
|
+
${record.outcome ? `Outcome: ${record.outcome}` : ""}
|
|
1360
|
+
`;
|
|
1361
|
+
writeFileSync4(logPath, content + entry);
|
|
1362
|
+
}
|
|
1363
|
+
function updateExecutionStatus(squadName, agentName, status, outcome) {
|
|
1364
|
+
const logPath = getExecutionLogPath(squadName, agentName);
|
|
1365
|
+
if (!logPath || !existsSync5(logPath)) return;
|
|
1366
|
+
let content = readFileSync4(logPath, "utf-8");
|
|
1367
|
+
const endTime = (/* @__PURE__ */ new Date()).toISOString();
|
|
1368
|
+
content = content.replace(
|
|
1369
|
+
/Status: running\n$/,
|
|
1370
|
+
`Status: ${status}
|
|
1371
|
+
Completed: ${endTime}
|
|
1372
|
+
${outcome ? `Outcome: ${outcome}
|
|
1373
|
+
` : ""}`
|
|
1374
|
+
);
|
|
1375
|
+
writeFileSync4(logPath, content);
|
|
1376
|
+
}
|
|
1377
|
+
async function runCommand(target, options) {
|
|
1378
|
+
const squadsDir = findSquadsDir();
|
|
1379
|
+
if (!squadsDir) {
|
|
1380
|
+
writeLine(` ${colors.red}No .agents/squads directory found${RESET}`);
|
|
1381
|
+
writeLine(` ${colors.dim}Run \`squads init\` to create one.${RESET}`);
|
|
1382
|
+
process.exit(1);
|
|
1383
|
+
}
|
|
1384
|
+
const squad = loadSquad(target);
|
|
1385
|
+
if (squad) {
|
|
1386
|
+
await track(Events.CLI_RUN, { type: "squad", target: squad.name });
|
|
1387
|
+
await runSquad(squad, squadsDir, options);
|
|
1388
|
+
} else {
|
|
1389
|
+
const agents = listAgents(squadsDir);
|
|
1390
|
+
const agent = agents.find((a) => a.name === target);
|
|
1391
|
+
if (agent && agent.filePath) {
|
|
1392
|
+
const pathParts = agent.filePath.split("/");
|
|
1393
|
+
const squadIdx = pathParts.indexOf("squads");
|
|
1394
|
+
const squadName = squadIdx >= 0 ? pathParts[squadIdx + 1] : "unknown";
|
|
1395
|
+
await runAgent(agent.name, agent.filePath, squadName, options);
|
|
1396
|
+
} else {
|
|
1397
|
+
writeLine(` ${colors.red}Squad or agent "${target}" not found${RESET}`);
|
|
1398
|
+
writeLine(` ${colors.dim}Run \`squads list\` to see available squads and agents.${RESET}`);
|
|
1399
|
+
process.exit(1);
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
async function runSquad(squad, squadsDir, options) {
|
|
1404
|
+
if (!squad) return;
|
|
1405
|
+
const startTime = (/* @__PURE__ */ new Date()).toISOString();
|
|
1406
|
+
writeLine();
|
|
1407
|
+
writeLine(` ${gradient("squads")} ${colors.dim}run${RESET} ${colors.cyan}${squad.name}${RESET}`);
|
|
1408
|
+
writeLine();
|
|
1409
|
+
if (squad.mission) {
|
|
1410
|
+
writeLine(` ${colors.dim}${squad.mission}${RESET}`);
|
|
1411
|
+
writeLine();
|
|
1412
|
+
}
|
|
1413
|
+
writeLine(` ${colors.dim}Started: ${startTime}${RESET}`);
|
|
1414
|
+
writeLine();
|
|
1415
|
+
if (squad.pipelines.length > 0) {
|
|
1416
|
+
const pipeline = squad.pipelines[0];
|
|
1417
|
+
writeLine(` ${bold}Pipeline${RESET} ${colors.dim}${pipeline.agents.join(" \u2192 ")}${RESET}`);
|
|
1418
|
+
writeLine();
|
|
1419
|
+
for (let i = 0; i < pipeline.agents.length; i++) {
|
|
1420
|
+
const agentName = pipeline.agents[i];
|
|
1421
|
+
const agentPath = join5(squadsDir, squad.name, `${agentName}.md`);
|
|
1422
|
+
if (existsSync5(agentPath)) {
|
|
1423
|
+
writeLine(` ${colors.dim}[${i + 1}/${pipeline.agents.length}]${RESET}`);
|
|
1424
|
+
await runAgent(agentName, agentPath, squad.name, options);
|
|
1425
|
+
writeLine();
|
|
1426
|
+
} else {
|
|
1427
|
+
writeLine(` ${icons.warning} ${colors.yellow}Agent ${agentName} not found, skipping${RESET}`);
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
} else {
|
|
1431
|
+
const orchestrator = squad.agents.find(
|
|
1432
|
+
(a) => a.name.includes("lead") || a.trigger === "Manual"
|
|
1433
|
+
);
|
|
1434
|
+
if (orchestrator) {
|
|
1435
|
+
const agentPath = join5(squadsDir, squad.name, `${orchestrator.name}.md`);
|
|
1436
|
+
if (existsSync5(agentPath)) {
|
|
1437
|
+
await runAgent(orchestrator.name, agentPath, squad.name, options);
|
|
1438
|
+
}
|
|
1439
|
+
} else {
|
|
1440
|
+
writeLine(` ${colors.dim}No pipeline defined. Available agents:${RESET}`);
|
|
1441
|
+
for (const agent of squad.agents) {
|
|
1442
|
+
writeLine(` ${icons.empty} ${colors.cyan}${agent.name}${RESET} ${colors.dim}${agent.role}${RESET}`);
|
|
1443
|
+
}
|
|
1444
|
+
writeLine();
|
|
1445
|
+
writeLine(` ${colors.dim}Run a specific agent:${RESET}`);
|
|
1446
|
+
writeLine(` ${colors.dim}$${RESET} squads run ${colors.cyan}${squad.name}${RESET} --agent ${colors.cyan}<name>${RESET}`);
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
writeLine();
|
|
1450
|
+
writeLine(` ${colors.dim}After execution, record outcome:${RESET}`);
|
|
1451
|
+
writeLine(` ${colors.dim}$${RESET} squads feedback add ${colors.cyan}${squad.name}${RESET} ${colors.cyan}<1-5>${RESET} ${colors.cyan}"<feedback>"${RESET}`);
|
|
1452
|
+
writeLine();
|
|
1453
|
+
}
|
|
1454
|
+
async function runAgent(agentName, agentPath, squadName, options) {
|
|
1455
|
+
const spinner = ora2(`Running agent: ${agentName}`).start();
|
|
1456
|
+
const startTime = (/* @__PURE__ */ new Date()).toISOString();
|
|
1457
|
+
const definition = loadAgentDefinition(agentPath);
|
|
1458
|
+
if (options.dryRun) {
|
|
1459
|
+
spinner.info(`[DRY RUN] Would run ${agentName}`);
|
|
1460
|
+
if (options.verbose) {
|
|
1461
|
+
writeLine(` ${colors.dim}Agent definition:${RESET}`);
|
|
1462
|
+
writeLine(` ${colors.dim}${definition.slice(0, 500)}...${RESET}`);
|
|
1463
|
+
}
|
|
1464
|
+
return;
|
|
1465
|
+
}
|
|
1466
|
+
logExecution({
|
|
1467
|
+
squadName,
|
|
1468
|
+
agentName,
|
|
1469
|
+
startTime,
|
|
1470
|
+
status: "running"
|
|
1471
|
+
});
|
|
1472
|
+
const prompt = `Execute the ${agentName} agent from squad ${squadName}.
|
|
1473
|
+
|
|
1474
|
+
Read the agent definition at ${agentPath} and follow its instructions exactly.
|
|
1475
|
+
|
|
1476
|
+
The agent definition contains:
|
|
1477
|
+
- Purpose/role
|
|
1478
|
+
- Tools it can use (MCP servers, skills)
|
|
1479
|
+
- Step-by-step instructions
|
|
1480
|
+
- Expected output format
|
|
1481
|
+
|
|
1482
|
+
After completion:
|
|
1483
|
+
1. Update the agent's memory in .agents/memory/${squadName}/${agentName}/state.md
|
|
1484
|
+
2. Log any learnings to learnings.md
|
|
1485
|
+
3. Report what was accomplished`;
|
|
1486
|
+
const claudeAvailable = await checkClaudeCliAvailable();
|
|
1487
|
+
if (options.execute && claudeAvailable) {
|
|
1488
|
+
spinner.text = `Executing ${agentName} with Claude Code...`;
|
|
1489
|
+
try {
|
|
1490
|
+
const result = await executeWithClaude(prompt, options.verbose);
|
|
1491
|
+
spinner.succeed(`Agent ${agentName} completed`);
|
|
1492
|
+
updateExecutionStatus(squadName, agentName, "completed", "Executed via Claude CLI");
|
|
1493
|
+
if (result) {
|
|
1494
|
+
writeLine(` ${colors.dim}Output:${RESET}`);
|
|
1495
|
+
writeLine(` ${result.slice(0, 500)}`);
|
|
1496
|
+
if (result.length > 500) writeLine(` ${colors.dim}... (truncated)${RESET}`);
|
|
1497
|
+
}
|
|
1498
|
+
} catch (error) {
|
|
1499
|
+
spinner.fail(`Agent ${agentName} failed`);
|
|
1500
|
+
updateExecutionStatus(squadName, agentName, "failed", String(error));
|
|
1501
|
+
writeLine(` ${colors.red}${String(error)}${RESET}`);
|
|
1502
|
+
}
|
|
1503
|
+
} else {
|
|
1504
|
+
spinner.succeed(`Agent ${agentName} ready`);
|
|
1505
|
+
writeLine(` ${colors.dim}Execution logged: ${startTime}${RESET}`);
|
|
1506
|
+
if (!claudeAvailable) {
|
|
1507
|
+
writeLine();
|
|
1508
|
+
writeLine(` ${colors.yellow}Claude CLI not found${RESET}`);
|
|
1509
|
+
writeLine(` ${colors.dim}Install: npm install -g @anthropic-ai/claude-code${RESET}`);
|
|
1510
|
+
}
|
|
1511
|
+
writeLine();
|
|
1512
|
+
writeLine(` ${colors.dim}To execute with Claude Code:${RESET}`);
|
|
1513
|
+
writeLine(` ${colors.dim}$${RESET} claude --print "${prompt.replace(/"/g, '\\"').replace(/\n/g, " ").slice(0, 80)}..."`);
|
|
1514
|
+
writeLine();
|
|
1515
|
+
writeLine(` ${colors.dim}Or run with --execute flag:${RESET}`);
|
|
1516
|
+
writeLine(` ${colors.dim}$${RESET} squads run ${colors.cyan}${squadName}${RESET} --execute`);
|
|
1517
|
+
writeLine();
|
|
1518
|
+
writeLine(` ${colors.dim}Or in Claude Code session:${RESET}`);
|
|
1519
|
+
writeLine(` ${colors.dim}$${RESET} Run the ${colors.cyan}${agentName}${RESET} agent from ${agentPath}`);
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
async function checkClaudeCliAvailable() {
|
|
1523
|
+
return new Promise((resolve) => {
|
|
1524
|
+
const check = spawn("which", ["claude"], { stdio: "pipe" });
|
|
1525
|
+
check.on("close", (code) => resolve(code === 0));
|
|
1526
|
+
check.on("error", () => resolve(false));
|
|
1527
|
+
});
|
|
1528
|
+
}
|
|
1529
|
+
async function executeWithClaude(prompt, verbose) {
|
|
1530
|
+
return new Promise((resolve, reject) => {
|
|
1531
|
+
const args = ["--print", prompt];
|
|
1532
|
+
if (verbose) {
|
|
1533
|
+
writeLine(` ${colors.dim}Spawning: claude ${args.slice(0, 1).join(" ")} ...${RESET}`);
|
|
1534
|
+
}
|
|
1535
|
+
const squadMatch = prompt.match(/squad (\w+)/);
|
|
1536
|
+
const agentMatch = prompt.match(/(\w+) agent/);
|
|
1537
|
+
const claude = spawn("claude", args, {
|
|
1538
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
1539
|
+
env: {
|
|
1540
|
+
...process.env,
|
|
1541
|
+
SQUADS_SQUAD: squadMatch?.[1] || "unknown",
|
|
1542
|
+
SQUADS_AGENT: agentMatch?.[1] || "unknown"
|
|
1543
|
+
}
|
|
1544
|
+
});
|
|
1545
|
+
let output = "";
|
|
1546
|
+
let error = "";
|
|
1547
|
+
claude.stdout?.on("data", (data) => {
|
|
1548
|
+
output += data.toString();
|
|
1549
|
+
if (verbose) {
|
|
1550
|
+
process.stdout.write(data.toString());
|
|
1551
|
+
}
|
|
1552
|
+
});
|
|
1553
|
+
claude.stderr?.on("data", (data) => {
|
|
1554
|
+
error += data.toString();
|
|
1555
|
+
});
|
|
1556
|
+
claude.on("close", (code) => {
|
|
1557
|
+
if (code === 0) {
|
|
1558
|
+
resolve(output);
|
|
1559
|
+
} else {
|
|
1560
|
+
reject(new Error(error || `Claude exited with code ${code}`));
|
|
1561
|
+
}
|
|
1562
|
+
});
|
|
1563
|
+
claude.on("error", (err) => {
|
|
1564
|
+
reject(err);
|
|
1565
|
+
});
|
|
1566
|
+
setTimeout(() => {
|
|
1567
|
+
claude.kill();
|
|
1568
|
+
reject(new Error("Execution timed out after 5 minutes"));
|
|
1569
|
+
}, 5 * 60 * 1e3);
|
|
1570
|
+
});
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1573
|
+
// src/commands/list.ts
|
|
1574
|
+
async function listCommand(options) {
|
|
1575
|
+
const squadsDir = findSquadsDir();
|
|
1576
|
+
if (!squadsDir) {
|
|
1577
|
+
writeLine(` ${colors.red}No .agents/squads directory found${RESET}`);
|
|
1578
|
+
writeLine(` ${colors.dim}Run \`squads init\` to create one.${RESET}`);
|
|
1579
|
+
process.exit(1);
|
|
1580
|
+
}
|
|
1581
|
+
const squads = listSquads(squadsDir);
|
|
1582
|
+
const allAgents = listAgents(squadsDir);
|
|
1583
|
+
writeLine();
|
|
1584
|
+
writeLine(` ${gradient("squads")} ${colors.dim}list${RESET}`);
|
|
1585
|
+
writeLine();
|
|
1586
|
+
writeLine(` ${colors.cyan}${squads.length}${RESET} squads ${colors.dim}\u2502${RESET} ${colors.cyan}${allAgents.length}${RESET} agents`);
|
|
1587
|
+
writeLine();
|
|
1588
|
+
if (!options.agents) {
|
|
1589
|
+
const w = { name: 16, agents: 8, lead: 24 };
|
|
1590
|
+
const tableWidth = w.name + w.agents + w.lead + 4;
|
|
1591
|
+
writeLine(` ${colors.purple}${box.topLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.topRight}${RESET}`);
|
|
1592
|
+
const header = ` ${colors.purple}${box.vertical}${RESET} ${bold}${padEnd("SQUAD", w.name)}${RESET}${bold}${padEnd("AGENTS", w.agents)}${RESET}${bold}LEAD${RESET} ${colors.purple}${box.vertical}${RESET}`;
|
|
1593
|
+
writeLine(header);
|
|
1594
|
+
writeLine(` ${colors.purple}${box.teeRight}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.teeLeft}${RESET}`);
|
|
1595
|
+
for (const squadName of squads) {
|
|
1596
|
+
const agents = listAgents(squadsDir, squadName);
|
|
1597
|
+
const lead = agents.find((a) => a.name.includes("lead"))?.name || agents[0]?.name || "-";
|
|
1598
|
+
const row = ` ${colors.purple}${box.vertical}${RESET} ${colors.cyan}${padEnd(squadName, w.name)}${RESET}${padEnd(String(agents.length), w.agents)}${colors.dim}${padEnd(lead, w.lead)}${RESET}${colors.purple}${box.vertical}${RESET}`;
|
|
1599
|
+
writeLine(row);
|
|
1600
|
+
}
|
|
1601
|
+
writeLine(` ${colors.purple}${box.bottomLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.bottomRight}${RESET}`);
|
|
1602
|
+
writeLine();
|
|
1603
|
+
}
|
|
1604
|
+
if (!options.squads && options.agents) {
|
|
1605
|
+
writeLine(` ${bold}Agents${RESET}`);
|
|
1606
|
+
writeLine();
|
|
1607
|
+
for (const agent of allAgents) {
|
|
1608
|
+
const squadPart = agent.squad ? `${colors.dim}${agent.squad}/${RESET}` : "";
|
|
1609
|
+
const statusIcon = agent.status?.toLowerCase() === "active" ? icons.active : icons.pending;
|
|
1610
|
+
writeLine(` ${statusIcon} ${squadPart}${colors.white}${agent.name}${RESET}`);
|
|
1611
|
+
if (agent.role) {
|
|
1612
|
+
writeLine(` ${colors.dim}\u2514 ${agent.role}${RESET}`);
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
writeLine();
|
|
1616
|
+
}
|
|
1617
|
+
writeLine(` ${colors.dim}$${RESET} squads status ${colors.cyan}<squad>${RESET} ${colors.dim}Squad details${RESET}`);
|
|
1618
|
+
writeLine(` ${colors.dim}$${RESET} squads run ${colors.cyan}<squad>${RESET} ${colors.dim}Execute a squad${RESET}`);
|
|
1619
|
+
writeLine();
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
// src/commands/status.ts
|
|
1623
|
+
import { existsSync as existsSync6, statSync } from "fs";
|
|
1624
|
+
import { join as join6 } from "path";
|
|
1625
|
+
async function statusCommand(squadName, options = {}) {
|
|
1626
|
+
const squadsDir = findSquadsDir();
|
|
1627
|
+
if (!squadsDir) {
|
|
1628
|
+
writeLine(`${colors.red}No .agents/squads directory found${RESET}`);
|
|
1629
|
+
writeLine(`${colors.dim}Run \`squads init\` to create one.${RESET}`);
|
|
1630
|
+
process.exit(1);
|
|
1631
|
+
}
|
|
1632
|
+
if (squadName) {
|
|
1633
|
+
await showSquadStatus(squadName, squadsDir, options);
|
|
1634
|
+
} else {
|
|
1635
|
+
await showOverallStatus(squadsDir, options);
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
async function showOverallStatus(squadsDir, _options) {
|
|
1639
|
+
const squads = listSquads(squadsDir);
|
|
1640
|
+
const memoryDir = findMemoryDir();
|
|
1641
|
+
writeLine();
|
|
1642
|
+
writeLine(` ${gradient("squads")} ${colors.dim}status${RESET}`);
|
|
1643
|
+
writeLine();
|
|
1644
|
+
const totalSquads = squads.length;
|
|
1645
|
+
const activeCount = squads.length;
|
|
1646
|
+
writeLine(` ${colors.cyan}${activeCount}${RESET}/${totalSquads} squads ${colors.dim}\u2502${RESET} ${colors.dim}memory: ${memoryDir ? "enabled" : "none"}${RESET}`);
|
|
1647
|
+
writeLine();
|
|
1648
|
+
const w = { name: 16, agents: 8, memory: 14, activity: 12 };
|
|
1649
|
+
const tableWidth = w.name + w.agents + w.memory + w.activity + 6;
|
|
1650
|
+
writeLine(` ${colors.purple}${box.topLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.topRight}${RESET}`);
|
|
1651
|
+
const header = ` ${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}`;
|
|
1652
|
+
writeLine(header);
|
|
1653
|
+
writeLine(` ${colors.purple}${box.teeRight}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.teeLeft}${RESET}`);
|
|
1654
|
+
for (const squadName of squads) {
|
|
1655
|
+
const agents = listAgents(squadsDir, squadName);
|
|
1656
|
+
let memoryStatus = `${colors.dim}none${RESET}`;
|
|
1657
|
+
let lastActivity = `${colors.dim}\u2014${RESET}`;
|
|
1658
|
+
let activityColor = colors.dim;
|
|
1659
|
+
if (memoryDir) {
|
|
1660
|
+
const squadMemoryPath = join6(memoryDir, squadName);
|
|
1661
|
+
if (existsSync6(squadMemoryPath)) {
|
|
1662
|
+
const states = getSquadState(squadName);
|
|
1663
|
+
memoryStatus = `${colors.green}${states.length} entries${RESET}`;
|
|
1664
|
+
let mostRecent = 0;
|
|
1665
|
+
for (const state of states) {
|
|
1666
|
+
const stat = statSync(state.path);
|
|
1667
|
+
if (stat.mtimeMs > mostRecent) {
|
|
1668
|
+
mostRecent = stat.mtimeMs;
|
|
1669
|
+
}
|
|
1670
|
+
}
|
|
1671
|
+
if (mostRecent > 0) {
|
|
1672
|
+
const daysAgo = Math.floor((Date.now() - mostRecent) / (1e3 * 60 * 60 * 24));
|
|
1673
|
+
if (daysAgo === 0) {
|
|
1674
|
+
lastActivity = "today";
|
|
1675
|
+
activityColor = colors.green;
|
|
1676
|
+
} else if (daysAgo === 1) {
|
|
1677
|
+
lastActivity = "yesterday";
|
|
1678
|
+
activityColor = colors.green;
|
|
1679
|
+
} else if (daysAgo < 7) {
|
|
1680
|
+
lastActivity = `${daysAgo}d ago`;
|
|
1681
|
+
activityColor = colors.yellow;
|
|
1682
|
+
} else {
|
|
1683
|
+
lastActivity = `${daysAgo}d ago`;
|
|
1684
|
+
activityColor = colors.dim;
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
const row = ` ${colors.purple}${box.vertical}${RESET} ${colors.cyan}${padEnd(squadName, w.name)}${RESET}${padEnd(String(agents.length), w.agents)}${padEnd(memoryStatus, w.memory)}${padEnd(`${activityColor}${lastActivity}${RESET}`, w.activity)}${colors.purple}${box.vertical}${RESET}`;
|
|
1690
|
+
writeLine(row);
|
|
1691
|
+
}
|
|
1692
|
+
writeLine(` ${colors.purple}${box.bottomLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.bottomRight}${RESET}`);
|
|
1693
|
+
writeLine();
|
|
1694
|
+
writeLine(` ${colors.dim}$${RESET} squads status ${colors.cyan}<squad>${RESET} ${colors.dim}Squad details${RESET}`);
|
|
1695
|
+
writeLine(` ${colors.dim}$${RESET} squads dash ${colors.dim}Full dashboard${RESET}`);
|
|
1696
|
+
writeLine(` ${colors.dim}$${RESET} squads run ${colors.cyan}<squad>${RESET} ${colors.dim}Execute a squad${RESET}`);
|
|
1697
|
+
writeLine();
|
|
1698
|
+
}
|
|
1699
|
+
async function showSquadStatus(squadName, squadsDir, options) {
|
|
1700
|
+
const squad = loadSquad(squadName);
|
|
1701
|
+
if (!squad) {
|
|
1702
|
+
writeLine(`${colors.red}Squad "${squadName}" not found.${RESET}`);
|
|
1703
|
+
process.exit(1);
|
|
1704
|
+
}
|
|
1705
|
+
writeLine();
|
|
1706
|
+
writeLine(` ${gradient("squads")} ${colors.dim}status${RESET} ${colors.cyan}${squad.name}${RESET}`);
|
|
1707
|
+
writeLine();
|
|
1708
|
+
if (squad.mission) {
|
|
1709
|
+
writeLine(` ${colors.dim}${squad.mission}${RESET}`);
|
|
1710
|
+
writeLine();
|
|
1711
|
+
}
|
|
1712
|
+
const agents = listAgents(squadsDir, squadName);
|
|
1713
|
+
const w = { name: 24, role: 36 };
|
|
1714
|
+
const tableWidth = w.name + w.role + 4;
|
|
1715
|
+
writeLine(` ${bold}Agents${RESET} ${colors.dim}(${agents.length})${RESET}`);
|
|
1716
|
+
writeLine();
|
|
1717
|
+
writeLine(` ${colors.purple}${box.topLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.topRight}${RESET}`);
|
|
1718
|
+
for (const agent of agents) {
|
|
1719
|
+
const status = agent.status?.toLowerCase() === "active" ? icons.active : icons.pending;
|
|
1720
|
+
const role = options.verbose && agent.role ? `${colors.dim}${agent.role.substring(0, w.role - 2)}${RESET}` : "";
|
|
1721
|
+
const row = ` ${colors.purple}${box.vertical}${RESET} ${status} ${padEnd(agent.name, w.name - 2)}${padEnd(role, w.role)}${colors.purple}${box.vertical}${RESET}`;
|
|
1722
|
+
writeLine(row);
|
|
1723
|
+
}
|
|
1724
|
+
writeLine(` ${colors.purple}${box.bottomLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.bottomRight}${RESET}`);
|
|
1725
|
+
if (squad.pipelines.length > 0) {
|
|
1726
|
+
writeLine();
|
|
1727
|
+
writeLine(` ${bold}Pipelines${RESET}`);
|
|
1728
|
+
for (const pipeline of squad.pipelines) {
|
|
1729
|
+
writeLine(` ${colors.dim}${pipeline.agents.join(" \u2192 ")}${RESET}`);
|
|
1730
|
+
}
|
|
1731
|
+
}
|
|
1732
|
+
const memoryDir = findMemoryDir();
|
|
1733
|
+
if (memoryDir) {
|
|
1734
|
+
const states = getSquadState(squadName);
|
|
1735
|
+
if (states.length > 0) {
|
|
1736
|
+
writeLine();
|
|
1737
|
+
writeLine(` ${bold}Memory${RESET} ${colors.dim}(${states.length} entries)${RESET}`);
|
|
1738
|
+
writeLine();
|
|
1739
|
+
for (const state of states) {
|
|
1740
|
+
const updated = state.content.match(/Updated:\s*(\S+)/)?.[1] || "unknown";
|
|
1741
|
+
writeLine(` ${icons.progress} ${colors.white}${state.agent}${RESET}`);
|
|
1742
|
+
writeLine(` ${colors.dim}\u2514 updated: ${updated}${RESET}`);
|
|
1743
|
+
if (options.verbose) {
|
|
1744
|
+
const signalsMatch = state.content.match(/## Active Signals([\s\S]*?)(?=##|$)/);
|
|
1745
|
+
if (signalsMatch) {
|
|
1746
|
+
const signalLines = signalsMatch[1].split("\n").filter((l) => l.match(/^\d+\./)).slice(0, 3);
|
|
1747
|
+
for (const sig of signalLines) {
|
|
1748
|
+
writeLine(` ${colors.dim} ${sig.trim()}${RESET}`);
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1755
|
+
writeLine();
|
|
1756
|
+
writeLine(` ${colors.dim}$${RESET} squads run ${colors.cyan}${squadName}${RESET} ${colors.dim}Run the squad${RESET}`);
|
|
1757
|
+
writeLine(` ${colors.dim}$${RESET} squads memory show ${colors.cyan}${squadName}${RESET} ${colors.dim}View full memory${RESET}`);
|
|
1758
|
+
writeLine(` ${colors.dim}$${RESET} squads status ${colors.cyan}${squadName}${RESET} -v ${colors.dim}Verbose status${RESET}`);
|
|
1759
|
+
writeLine();
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1762
|
+
// src/commands/memory.ts
|
|
1763
|
+
var SQUADS_BRIDGE_URL = process.env.SQUADS_BRIDGE_URL || "http://localhost:8088";
|
|
1764
|
+
async function memoryQueryCommand(query, options) {
|
|
1765
|
+
const memoryDir = findMemoryDir();
|
|
1766
|
+
if (!memoryDir) {
|
|
1767
|
+
writeLine(` ${colors.red}No .agents/memory directory found${RESET}`);
|
|
1768
|
+
writeLine(` ${colors.dim}Run \`squads init\` to create one.${RESET}`);
|
|
1769
|
+
process.exit(1);
|
|
1770
|
+
}
|
|
1771
|
+
writeLine();
|
|
1772
|
+
writeLine(` ${gradient("squads")} ${colors.dim}memory query${RESET} "${colors.cyan}${query}${RESET}"`);
|
|
1773
|
+
writeLine();
|
|
1774
|
+
const results = searchMemory(query, memoryDir);
|
|
1775
|
+
if (results.length === 0) {
|
|
1776
|
+
writeLine(` ${colors.yellow}No results found.${RESET}`);
|
|
1777
|
+
writeLine();
|
|
1778
|
+
return;
|
|
1779
|
+
}
|
|
1780
|
+
let filtered = results;
|
|
1781
|
+
if (options.squad) {
|
|
1782
|
+
filtered = filtered.filter((r) => r.entry.squad === options.squad);
|
|
1783
|
+
}
|
|
1784
|
+
if (options.agent) {
|
|
1785
|
+
filtered = filtered.filter((r) => r.entry.agent === options.agent);
|
|
1786
|
+
}
|
|
1787
|
+
writeLine(` ${colors.green}${filtered.length}${RESET} results found`);
|
|
1788
|
+
writeLine();
|
|
1789
|
+
const w = { location: 28, type: 10, score: 8 };
|
|
1790
|
+
const tableWidth = w.location + w.type + w.score + 4;
|
|
1791
|
+
writeLine(` ${colors.purple}${box.topLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.topRight}${RESET}`);
|
|
1792
|
+
const header = ` ${colors.purple}${box.vertical}${RESET} ${bold}${padEnd("LOCATION", w.location)}${RESET}${bold}${padEnd("TYPE", w.type)}${RESET}${bold}SCORE${RESET} ${colors.purple}${box.vertical}${RESET}`;
|
|
1793
|
+
writeLine(header);
|
|
1794
|
+
writeLine(` ${colors.purple}${box.teeRight}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.teeLeft}${RESET}`);
|
|
1795
|
+
for (const result of filtered.slice(0, 8)) {
|
|
1796
|
+
const { entry, score } = result;
|
|
1797
|
+
const location = `${entry.squad}/${entry.agent}`;
|
|
1798
|
+
const scoreColor = score > 5 ? colors.green : score > 2 ? colors.yellow : colors.dim;
|
|
1799
|
+
const row = ` ${colors.purple}${box.vertical}${RESET} ${colors.cyan}${padEnd(location, w.location)}${RESET}${colors.dim}${padEnd(entry.type, w.type)}${RESET}${scoreColor}${padEnd(score.toFixed(1), w.score)}${RESET}${colors.purple}${box.vertical}${RESET}`;
|
|
1800
|
+
writeLine(row);
|
|
1801
|
+
}
|
|
1802
|
+
writeLine(` ${colors.purple}${box.bottomLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.bottomRight}${RESET}`);
|
|
1803
|
+
writeLine();
|
|
1804
|
+
writeLine(` ${bold}Matches${RESET}`);
|
|
1805
|
+
writeLine();
|
|
1806
|
+
for (const result of filtered.slice(0, 5)) {
|
|
1807
|
+
const { entry, matches } = result;
|
|
1808
|
+
for (const match of matches.slice(0, 2)) {
|
|
1809
|
+
const highlighted = match.replace(
|
|
1810
|
+
new RegExp(query, "gi"),
|
|
1811
|
+
(m) => `${colors.yellow}${m}${RESET}`
|
|
1812
|
+
);
|
|
1813
|
+
writeLine(` ${icons.empty} ${truncate(highlighted, 60)}`);
|
|
1814
|
+
writeLine(` ${colors.dim}\u2514 ${entry.squad}/${entry.agent}${RESET}`);
|
|
1815
|
+
}
|
|
1816
|
+
}
|
|
1817
|
+
if (filtered.length > 5) {
|
|
1818
|
+
writeLine(` ${colors.dim} +${filtered.length - 5} more results${RESET}`);
|
|
1819
|
+
}
|
|
1820
|
+
writeLine();
|
|
1821
|
+
writeLine(` ${colors.dim}$${RESET} squads memory show ${colors.cyan}<squad>${RESET} ${colors.dim}View full memory${RESET}`);
|
|
1822
|
+
writeLine();
|
|
1823
|
+
}
|
|
1824
|
+
async function memoryShowCommand(squadName, _options) {
|
|
1825
|
+
const memoryDir = findMemoryDir();
|
|
1826
|
+
if (!memoryDir) {
|
|
1827
|
+
writeLine(` ${colors.red}No .agents/memory directory found${RESET}`);
|
|
1828
|
+
process.exit(1);
|
|
1829
|
+
}
|
|
1830
|
+
const states = getSquadState(squadName);
|
|
1831
|
+
if (states.length === 0) {
|
|
1832
|
+
writeLine(` ${colors.yellow}No memory found for squad: ${squadName}${RESET}`);
|
|
1833
|
+
return;
|
|
1834
|
+
}
|
|
1835
|
+
writeLine();
|
|
1836
|
+
writeLine(` ${gradient("squads")} ${colors.dim}memory${RESET} ${colors.cyan}${squadName}${RESET}`);
|
|
1837
|
+
writeLine();
|
|
1838
|
+
writeLine(` ${colors.dim}${states.length} entries${RESET}`);
|
|
1839
|
+
writeLine();
|
|
1840
|
+
for (const state of states) {
|
|
1841
|
+
writeLine(` ${icons.progress} ${colors.white}${state.agent}${RESET} ${colors.dim}(${state.type || "state"})${RESET}`);
|
|
1842
|
+
writeLine(` ${colors.dim}${box.horizontal.repeat(40)}${RESET}`);
|
|
1843
|
+
const lines = state.content.split("\n").slice(0, 12);
|
|
1844
|
+
for (const line of lines) {
|
|
1845
|
+
writeLine(` ${colors.dim}${truncate(line, 70)}${RESET}`);
|
|
1846
|
+
}
|
|
1847
|
+
if (state.content.split("\n").length > 12) {
|
|
1848
|
+
writeLine(` ${colors.dim}... (more content)${RESET}`);
|
|
1849
|
+
}
|
|
1850
|
+
writeLine();
|
|
1851
|
+
}
|
|
1852
|
+
writeLine(` ${colors.dim}$${RESET} squads memory query ${colors.cyan}"<term>"${RESET} ${colors.dim}Search memory${RESET}`);
|
|
1853
|
+
writeLine();
|
|
1854
|
+
}
|
|
1855
|
+
async function memoryUpdateCommand(squadName, content, options) {
|
|
1856
|
+
const agentName = options.agent || `${squadName}-lead`;
|
|
1857
|
+
const type = options.type || "learnings";
|
|
1858
|
+
writeLine();
|
|
1859
|
+
try {
|
|
1860
|
+
appendToMemory(squadName, agentName, type, content);
|
|
1861
|
+
writeLine(` ${icons.success} Updated ${colors.cyan}${type}${RESET} for ${colors.white}${squadName}/${agentName}${RESET}`);
|
|
1862
|
+
} catch (error) {
|
|
1863
|
+
writeLine(` ${icons.error} ${colors.red}Failed to update memory: ${error}${RESET}`);
|
|
1864
|
+
process.exit(1);
|
|
1865
|
+
}
|
|
1866
|
+
writeLine();
|
|
1867
|
+
}
|
|
1868
|
+
async function memoryListCommand() {
|
|
1869
|
+
const memoryDir = findMemoryDir();
|
|
1870
|
+
if (!memoryDir) {
|
|
1871
|
+
writeLine(` ${colors.red}No .agents/memory directory found${RESET}`);
|
|
1872
|
+
process.exit(1);
|
|
1873
|
+
}
|
|
1874
|
+
const entries = listMemoryEntries(memoryDir);
|
|
1875
|
+
const bySquad = {};
|
|
1876
|
+
for (const entry of entries) {
|
|
1877
|
+
if (!bySquad[entry.squad]) {
|
|
1878
|
+
bySquad[entry.squad] = [];
|
|
1879
|
+
}
|
|
1880
|
+
bySquad[entry.squad].push(entry);
|
|
1881
|
+
}
|
|
1882
|
+
writeLine();
|
|
1883
|
+
writeLine(` ${gradient("squads")} ${colors.dim}memory list${RESET}`);
|
|
1884
|
+
writeLine();
|
|
1885
|
+
const squadNames = Object.keys(bySquad);
|
|
1886
|
+
writeLine(` ${colors.cyan}${entries.length}${RESET} entries across ${squadNames.length} squads`);
|
|
1887
|
+
writeLine();
|
|
1888
|
+
const w = { squad: 16, agents: 8, types: 28 };
|
|
1889
|
+
const tableWidth = w.squad + w.agents + w.types + 4;
|
|
1890
|
+
writeLine(` ${colors.purple}${box.topLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.topRight}${RESET}`);
|
|
1891
|
+
const header = ` ${colors.purple}${box.vertical}${RESET} ${bold}${padEnd("SQUAD", w.squad)}${RESET}${bold}${padEnd("AGENTS", w.agents)}${RESET}${bold}TYPES${RESET} ${colors.purple}${box.vertical}${RESET}`;
|
|
1892
|
+
writeLine(header);
|
|
1893
|
+
writeLine(` ${colors.purple}${box.teeRight}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.teeLeft}${RESET}`);
|
|
1894
|
+
for (const [squad, squadEntries] of Object.entries(bySquad)) {
|
|
1895
|
+
const agents = new Set(squadEntries.map((e) => e.agent));
|
|
1896
|
+
const types = [...new Set(squadEntries.map((e) => e.type))].join(", ");
|
|
1897
|
+
const typesDisplay = truncate(types, w.types - 2);
|
|
1898
|
+
const row = ` ${colors.purple}${box.vertical}${RESET} ${colors.cyan}${padEnd(squad, w.squad)}${RESET}${padEnd(String(agents.size), w.agents)}${colors.dim}${padEnd(typesDisplay, w.types)}${RESET}${colors.purple}${box.vertical}${RESET}`;
|
|
1899
|
+
writeLine(row);
|
|
1900
|
+
}
|
|
1901
|
+
writeLine(` ${colors.purple}${box.bottomLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.bottomRight}${RESET}`);
|
|
1902
|
+
writeLine();
|
|
1903
|
+
writeLine(` ${colors.dim}$${RESET} squads memory show ${colors.cyan}<squad>${RESET} ${colors.dim}View squad memory${RESET}`);
|
|
1904
|
+
writeLine(` ${colors.dim}$${RESET} squads memory query ${colors.cyan}"<term>"${RESET} ${colors.dim}Search all memory${RESET}`);
|
|
1905
|
+
writeLine();
|
|
1906
|
+
}
|
|
1907
|
+
async function memorySearchCommand(query, options = {}) {
|
|
1908
|
+
writeLine();
|
|
1909
|
+
writeLine(` ${gradient("squads")} ${colors.dim}memory search${RESET} "${colors.cyan}${query}${RESET}"`);
|
|
1910
|
+
writeLine();
|
|
1911
|
+
const limit = options.limit || 10;
|
|
1912
|
+
const params = new URLSearchParams({ q: query, limit: String(limit) });
|
|
1913
|
+
if (options.role) {
|
|
1914
|
+
params.append("role", options.role);
|
|
1915
|
+
}
|
|
1916
|
+
if (options.importance) {
|
|
1917
|
+
params.append("importance", options.importance);
|
|
1918
|
+
}
|
|
1919
|
+
try {
|
|
1920
|
+
const response = await fetch(`${SQUADS_BRIDGE_URL}/api/conversations/search?${params}`);
|
|
1921
|
+
if (!response.ok) {
|
|
1922
|
+
if (response.status === 503) {
|
|
1923
|
+
writeLine(` ${colors.yellow}Database not available${RESET}`);
|
|
1924
|
+
writeLine(` ${colors.dim}Run: docker compose up -d${RESET}`);
|
|
1925
|
+
writeLine();
|
|
1926
|
+
return;
|
|
1927
|
+
}
|
|
1928
|
+
throw new Error(`HTTP ${response.status}`);
|
|
1929
|
+
}
|
|
1930
|
+
const data = await response.json();
|
|
1931
|
+
const conversations = data.results;
|
|
1932
|
+
const total = data.count;
|
|
1933
|
+
if (conversations.length === 0) {
|
|
1934
|
+
writeLine(` ${colors.yellow}No conversations found for "${query}"${RESET}`);
|
|
1935
|
+
writeLine();
|
|
1936
|
+
writeLine(` ${colors.dim}Conversations are captured via hooks. Make sure:${RESET}`);
|
|
1937
|
+
writeLine(` ${colors.dim} 1. squads-bridge is running (docker compose up)${RESET}`);
|
|
1938
|
+
writeLine(` ${colors.dim} 2. engram hook is configured in Claude settings${RESET}`);
|
|
1939
|
+
writeLine();
|
|
1940
|
+
return;
|
|
1941
|
+
}
|
|
1942
|
+
writeLine(` ${colors.green}${conversations.length}${RESET} of ${total} results`);
|
|
1943
|
+
writeLine();
|
|
1944
|
+
const w = { time: 12, role: 10, type: 10, content: 50 };
|
|
1945
|
+
const tableWidth = w.time + w.role + w.type + w.content + 6;
|
|
1946
|
+
writeLine(` ${colors.purple}${box.topLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.topRight}${RESET}`);
|
|
1947
|
+
const header = ` ${colors.purple}${box.vertical}${RESET} ${bold}${padEnd("TIME", w.time)}${RESET}${bold}${padEnd("ROLE", w.role)}${RESET}${bold}${padEnd("TYPE", w.type)}${RESET}${bold}${padEnd("CONTENT", w.content)}${RESET} ${colors.purple}${box.vertical}${RESET}`;
|
|
1948
|
+
writeLine(header);
|
|
1949
|
+
writeLine(` ${colors.purple}${box.teeRight}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.teeLeft}${RESET}`);
|
|
1950
|
+
for (const conv of conversations) {
|
|
1951
|
+
const time = new Date(conv.created_at).toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit" });
|
|
1952
|
+
const roleColor = conv.role === "user" ? colors.cyan : conv.role === "thinking" ? colors.yellow : colors.green;
|
|
1953
|
+
const importanceIcon = conv.importance === "high" ? icons.success : "";
|
|
1954
|
+
const contentPreview = truncate((conv.content || "").replace(/\n/g, " "), w.content - 2);
|
|
1955
|
+
const highlighted = contentPreview.replace(
|
|
1956
|
+
new RegExp(query, "gi"),
|
|
1957
|
+
(m) => `${colors.yellow}${m}${RESET}${colors.dim}`
|
|
1958
|
+
);
|
|
1959
|
+
const row = ` ${colors.purple}${box.vertical}${RESET} ${colors.dim}${padEnd(time, w.time)}${RESET}${roleColor}${padEnd(conv.role || "", w.role)}${RESET}${colors.dim}${padEnd(conv.type || "message", w.type)}${RESET}${colors.dim}${padEnd(highlighted + importanceIcon, w.content)}${RESET} ${colors.purple}${box.vertical}${RESET}`;
|
|
1960
|
+
writeLine(row);
|
|
1961
|
+
}
|
|
1962
|
+
writeLine(` ${colors.purple}${box.bottomLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.bottomRight}${RESET}`);
|
|
1963
|
+
writeLine();
|
|
1964
|
+
writeLine(` ${bold}Top Matches${RESET}`);
|
|
1965
|
+
writeLine();
|
|
1966
|
+
for (const conv of conversations.slice(0, 3)) {
|
|
1967
|
+
const time = new Date(conv.created_at).toLocaleString("en-US", {
|
|
1968
|
+
month: "short",
|
|
1969
|
+
day: "numeric",
|
|
1970
|
+
hour: "2-digit",
|
|
1971
|
+
minute: "2-digit"
|
|
1972
|
+
});
|
|
1973
|
+
const roleIcon = conv.role === "user" ? "\u{1F464}" : conv.role === "thinking" ? "\u{1F4AD}" : "\u{1F916}";
|
|
1974
|
+
writeLine(` ${roleIcon} ${colors.dim}${time}${RESET} ${conv.importance === "high" ? colors.yellow + "[high]" + RESET : ""}`);
|
|
1975
|
+
const content = conv.content || "";
|
|
1976
|
+
const preview = content.substring(0, 200).replace(/\n/g, " ");
|
|
1977
|
+
const highlighted = preview.replace(
|
|
1978
|
+
new RegExp(query, "gi"),
|
|
1979
|
+
(m) => `${colors.yellow}${m}${RESET}`
|
|
1980
|
+
);
|
|
1981
|
+
writeLine(` ${highlighted}${content.length > 200 ? "..." : ""}`);
|
|
1982
|
+
writeLine();
|
|
1983
|
+
}
|
|
1984
|
+
writeLine(` ${colors.dim}$${RESET} squads memory search ${colors.cyan}"<term>"${RESET} --role user ${colors.dim}Filter by role${RESET}`);
|
|
1985
|
+
writeLine(` ${colors.dim}$${RESET} squads memory search ${colors.cyan}"<term>"${RESET} --importance high ${colors.dim}Filter by importance${RESET}`);
|
|
1986
|
+
writeLine();
|
|
1987
|
+
} catch (error) {
|
|
1988
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1989
|
+
if (errorMessage.includes("ECONNREFUSED")) {
|
|
1990
|
+
writeLine(` ${colors.yellow}Cannot connect to squads-bridge${RESET}`);
|
|
1991
|
+
writeLine(` ${colors.dim}Run: docker compose up -d${RESET}`);
|
|
1992
|
+
} else {
|
|
1993
|
+
writeLine(` ${colors.red}Error searching conversations: ${errorMessage}${RESET}`);
|
|
1994
|
+
}
|
|
1995
|
+
writeLine();
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
1998
|
+
|
|
1999
|
+
// src/commands/sync.ts
|
|
2000
|
+
import { execSync as execSync2 } from "child_process";
|
|
2001
|
+
import { existsSync as existsSync7, readFileSync as readFileSync5, writeFileSync as writeFileSync5, mkdirSync as mkdirSync4, readdirSync as readdirSync3 } from "fs";
|
|
2002
|
+
import { join as join7 } from "path";
|
|
2003
|
+
var PATH_TO_SQUAD = {
|
|
2004
|
+
"squads-cli": "product",
|
|
2005
|
+
"agents-squads-web": "website",
|
|
2006
|
+
"research": "research",
|
|
2007
|
+
"intelligence": "intelligence",
|
|
2008
|
+
"customer": "customer",
|
|
2009
|
+
"finance": "finance",
|
|
2010
|
+
"engineering": "engineering",
|
|
2011
|
+
"product": "product",
|
|
2012
|
+
"company": "company",
|
|
2013
|
+
".agents/squads": "engineering",
|
|
2014
|
+
".agents/memory": "engineering"
|
|
2015
|
+
};
|
|
2016
|
+
var MESSAGE_TO_SQUAD = {
|
|
2017
|
+
"cli": "product",
|
|
2018
|
+
"website": "website",
|
|
2019
|
+
"web": "website",
|
|
2020
|
+
"homepage": "website",
|
|
2021
|
+
"research": "research",
|
|
2022
|
+
"intel": "intelligence",
|
|
2023
|
+
"lead": "customer",
|
|
2024
|
+
"finance": "finance",
|
|
2025
|
+
"cost": "finance",
|
|
2026
|
+
"engineering": "engineering",
|
|
2027
|
+
"infra": "engineering"
|
|
2028
|
+
};
|
|
2029
|
+
function getLastSyncTime(memoryDir) {
|
|
2030
|
+
const syncFile = join7(memoryDir, ".last-sync");
|
|
2031
|
+
if (existsSync7(syncFile)) {
|
|
2032
|
+
return readFileSync5(syncFile, "utf-8").trim();
|
|
2033
|
+
}
|
|
2034
|
+
return null;
|
|
2035
|
+
}
|
|
2036
|
+
function updateLastSyncTime(memoryDir) {
|
|
2037
|
+
const syncFile = join7(memoryDir, ".last-sync");
|
|
2038
|
+
writeFileSync5(syncFile, (/* @__PURE__ */ new Date()).toISOString());
|
|
2039
|
+
}
|
|
2040
|
+
function getRecentCommits(since) {
|
|
2041
|
+
const commits = [];
|
|
2042
|
+
try {
|
|
2043
|
+
const sinceArg = since ? `--since="${since}"` : "-n 20";
|
|
2044
|
+
const logOutput = execSync2(
|
|
2045
|
+
`git log ${sinceArg} --format="%H|%aI|%s" --name-only`,
|
|
2046
|
+
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
2047
|
+
).trim();
|
|
2048
|
+
if (!logOutput) return commits;
|
|
2049
|
+
const entries = logOutput.split("\n\n");
|
|
2050
|
+
for (const entry of entries) {
|
|
2051
|
+
const lines = entry.split("\n").filter((l) => l.trim());
|
|
2052
|
+
if (lines.length === 0) continue;
|
|
2053
|
+
const [header, ...fileLines] = lines;
|
|
2054
|
+
const [hash, date, ...messageParts] = header.split("|");
|
|
2055
|
+
const message = messageParts.join("|");
|
|
2056
|
+
if (hash && date && message) {
|
|
2057
|
+
commits.push({
|
|
2058
|
+
hash: hash.substring(0, 7),
|
|
2059
|
+
date: date.split("T")[0],
|
|
2060
|
+
message,
|
|
2061
|
+
files: fileLines.filter((f) => f && !f.includes("|"))
|
|
2062
|
+
});
|
|
2063
|
+
}
|
|
2064
|
+
}
|
|
2065
|
+
} catch (error) {
|
|
2066
|
+
}
|
|
2067
|
+
return commits;
|
|
2068
|
+
}
|
|
2069
|
+
function detectSquadsFromCommit(commit) {
|
|
2070
|
+
const squads = /* @__PURE__ */ new Set();
|
|
2071
|
+
for (const file of commit.files) {
|
|
2072
|
+
for (const [pathPattern, squad] of Object.entries(PATH_TO_SQUAD)) {
|
|
2073
|
+
if (file.includes(pathPattern)) {
|
|
2074
|
+
squads.add(squad);
|
|
2075
|
+
}
|
|
2076
|
+
}
|
|
2077
|
+
}
|
|
2078
|
+
const msgLower = commit.message.toLowerCase();
|
|
2079
|
+
for (const [keyword, squad] of Object.entries(MESSAGE_TO_SQUAD)) {
|
|
2080
|
+
if (msgLower.includes(keyword)) {
|
|
2081
|
+
squads.add(squad);
|
|
2082
|
+
}
|
|
2083
|
+
}
|
|
2084
|
+
return Array.from(squads);
|
|
2085
|
+
}
|
|
2086
|
+
function groupCommitsBySquad(commits) {
|
|
2087
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
2088
|
+
for (const commit of commits) {
|
|
2089
|
+
const squads = detectSquadsFromCommit(commit);
|
|
2090
|
+
for (const squad of squads) {
|
|
2091
|
+
if (!grouped.has(squad)) {
|
|
2092
|
+
grouped.set(squad, []);
|
|
2093
|
+
}
|
|
2094
|
+
grouped.get(squad).push(commit);
|
|
2095
|
+
}
|
|
2096
|
+
}
|
|
2097
|
+
return grouped;
|
|
2098
|
+
}
|
|
2099
|
+
function generateSummary(commits) {
|
|
2100
|
+
if (commits.length === 0) return "";
|
|
2101
|
+
const messages = commits.map((c) => `- ${c.message}`).join("\n");
|
|
2102
|
+
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
2103
|
+
return `
|
|
2104
|
+
## Session Update (${date})
|
|
2105
|
+
|
|
2106
|
+
${messages}
|
|
2107
|
+
`;
|
|
2108
|
+
}
|
|
2109
|
+
function appendToSquadMemory(memoryDir, squad, summary) {
|
|
2110
|
+
const squadMemoryDir = join7(memoryDir, squad);
|
|
2111
|
+
if (!existsSync7(squadMemoryDir)) {
|
|
2112
|
+
mkdirSync4(squadMemoryDir, { recursive: true });
|
|
2113
|
+
}
|
|
2114
|
+
let agentDir;
|
|
2115
|
+
const existingDirs = existsSync7(squadMemoryDir) ? readdirSync3(squadMemoryDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name) : [];
|
|
2116
|
+
if (existingDirs.length > 0) {
|
|
2117
|
+
agentDir = join7(squadMemoryDir, existingDirs[0]);
|
|
2118
|
+
} else {
|
|
2119
|
+
agentDir = join7(squadMemoryDir, `${squad}-lead`);
|
|
2120
|
+
mkdirSync4(agentDir, { recursive: true });
|
|
2121
|
+
}
|
|
2122
|
+
const statePath = join7(agentDir, "state.md");
|
|
2123
|
+
let content = "";
|
|
2124
|
+
if (existsSync7(statePath)) {
|
|
2125
|
+
content = readFileSync5(statePath, "utf-8");
|
|
2126
|
+
} else {
|
|
2127
|
+
content = `# ${squad} Squad - State
|
|
2128
|
+
|
|
2129
|
+
Updated: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}
|
|
2130
|
+
`;
|
|
2131
|
+
}
|
|
2132
|
+
content = content.replace(
|
|
2133
|
+
/Updated:\s*\d{4}-\d{2}-\d{2}/,
|
|
2134
|
+
`Updated: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}`
|
|
2135
|
+
);
|
|
2136
|
+
content += summary;
|
|
2137
|
+
writeFileSync5(statePath, content);
|
|
2138
|
+
return true;
|
|
2139
|
+
}
|
|
2140
|
+
async function syncCommand(options = {}) {
|
|
2141
|
+
const memoryDir = findMemoryDir();
|
|
2142
|
+
const squadsDir = findSquadsDir();
|
|
2143
|
+
if (!memoryDir) {
|
|
2144
|
+
writeLine(` ${colors.yellow}No .agents/memory directory found${RESET}`);
|
|
2145
|
+
writeLine(` ${colors.dim}Run \`squads init\` to create one.${RESET}`);
|
|
2146
|
+
return;
|
|
2147
|
+
}
|
|
2148
|
+
writeLine();
|
|
2149
|
+
writeLine(` ${gradient("squads")} ${colors.dim}memory sync${RESET}`);
|
|
2150
|
+
writeLine();
|
|
2151
|
+
const lastSync = getLastSyncTime(memoryDir);
|
|
2152
|
+
if (lastSync) {
|
|
2153
|
+
writeLine(` ${colors.dim}Last sync: ${lastSync.split("T")[0]}${RESET}`);
|
|
2154
|
+
} else {
|
|
2155
|
+
writeLine(` ${colors.dim}First sync${RESET}`);
|
|
2156
|
+
}
|
|
2157
|
+
writeLine();
|
|
2158
|
+
const commits = getRecentCommits(lastSync || void 0);
|
|
2159
|
+
if (commits.length === 0) {
|
|
2160
|
+
writeLine(` ${colors.yellow}No new commits since last sync${RESET}`);
|
|
2161
|
+
writeLine();
|
|
2162
|
+
return;
|
|
2163
|
+
}
|
|
2164
|
+
writeLine(` ${colors.cyan}${commits.length}${RESET} commits to process`);
|
|
2165
|
+
writeLine();
|
|
2166
|
+
const bySquad = groupCommitsBySquad(commits);
|
|
2167
|
+
if (bySquad.size === 0) {
|
|
2168
|
+
writeLine(` ${colors.yellow}No squad-related commits found${RESET}`);
|
|
2169
|
+
writeLine();
|
|
2170
|
+
updateLastSyncTime(memoryDir);
|
|
2171
|
+
return;
|
|
2172
|
+
}
|
|
2173
|
+
let updated = 0;
|
|
2174
|
+
for (const [squad, squadCommits] of bySquad) {
|
|
2175
|
+
const summary = generateSummary(squadCommits);
|
|
2176
|
+
if (options.verbose) {
|
|
2177
|
+
writeLine(` ${icons.progress} ${colors.cyan}${squad}${RESET}`);
|
|
2178
|
+
for (const commit of squadCommits) {
|
|
2179
|
+
writeLine(` ${colors.dim}${commit.hash} ${commit.message}${RESET}`);
|
|
2180
|
+
}
|
|
2181
|
+
}
|
|
2182
|
+
const success = appendToSquadMemory(memoryDir, squad, summary);
|
|
2183
|
+
if (success) {
|
|
2184
|
+
writeLine(` ${icons.success} ${colors.cyan}${squad}${RESET} ${colors.dim}(${squadCommits.length} commits)${RESET}`);
|
|
2185
|
+
updated++;
|
|
2186
|
+
}
|
|
2187
|
+
}
|
|
2188
|
+
writeLine();
|
|
2189
|
+
writeLine(` ${colors.green}${updated}${RESET} squad memories updated`);
|
|
2190
|
+
writeLine();
|
|
2191
|
+
updateLastSyncTime(memoryDir);
|
|
2192
|
+
writeLine(` ${colors.dim}$${RESET} squads memory show ${colors.cyan}<squad>${RESET} ${colors.dim}View updated memory${RESET}`);
|
|
2193
|
+
writeLine(` ${colors.dim}$${RESET} squads status ${colors.dim}See all squads${RESET}`);
|
|
2194
|
+
writeLine();
|
|
2195
|
+
}
|
|
2196
|
+
|
|
2197
|
+
// src/commands/goal.ts
|
|
2198
|
+
async function goalSetCommand(squadName, description, options) {
|
|
2199
|
+
const squad = loadSquad(squadName);
|
|
2200
|
+
if (!squad) {
|
|
2201
|
+
writeLine(` ${colors.red}Squad "${squadName}" not found${RESET}`);
|
|
2202
|
+
return;
|
|
2203
|
+
}
|
|
2204
|
+
let goalText = description;
|
|
2205
|
+
if (options.metric && options.metric.length > 0) {
|
|
2206
|
+
goalText += ` [metrics: ${options.metric.join(", ")}]`;
|
|
2207
|
+
}
|
|
2208
|
+
const success = addGoalToSquad(squadName, goalText);
|
|
2209
|
+
writeLine();
|
|
2210
|
+
if (success) {
|
|
2211
|
+
writeLine(` ${icons.success} Goal added to ${colors.cyan}${squadName}${RESET}`);
|
|
2212
|
+
writeLine(` ${bold}${description}${RESET}`);
|
|
2213
|
+
if (options.metric && options.metric.length > 0) {
|
|
2214
|
+
writeLine(` ${colors.dim}Metrics: ${options.metric.join(", ")}${RESET}`);
|
|
2215
|
+
}
|
|
2216
|
+
} else {
|
|
2217
|
+
writeLine(` ${colors.red}Failed to add goal${RESET}`);
|
|
2218
|
+
}
|
|
2219
|
+
writeLine();
|
|
2220
|
+
}
|
|
2221
|
+
async function goalListCommand(squadName, options = {}) {
|
|
2222
|
+
const squadsDir = findSquadsDir();
|
|
2223
|
+
if (!squadsDir) {
|
|
2224
|
+
writeLine(` ${colors.red}No .agents/squads directory found${RESET}`);
|
|
2225
|
+
return;
|
|
2226
|
+
}
|
|
2227
|
+
const squadsToCheck = squadName ? [squadName] : listSquads(squadsDir);
|
|
2228
|
+
let totalActive = 0;
|
|
2229
|
+
let totalCompleted = 0;
|
|
2230
|
+
let hasGoals = false;
|
|
2231
|
+
writeLine();
|
|
2232
|
+
writeLine(` ${gradient("squads")} ${colors.dim}goal list${RESET}`);
|
|
2233
|
+
writeLine();
|
|
2234
|
+
for (const name of squadsToCheck) {
|
|
2235
|
+
const squad = loadSquad(name);
|
|
2236
|
+
if (!squad || squad.goals.length === 0) {
|
|
2237
|
+
if (squadName) {
|
|
2238
|
+
writeLine(` ${colors.yellow}No goals set for ${name}${RESET}`);
|
|
2239
|
+
}
|
|
2240
|
+
continue;
|
|
2241
|
+
}
|
|
2242
|
+
hasGoals = true;
|
|
2243
|
+
const activeGoals = squad.goals.filter((g) => !g.completed);
|
|
2244
|
+
const completedGoals = squad.goals.filter((g) => g.completed);
|
|
2245
|
+
totalActive += activeGoals.length;
|
|
2246
|
+
totalCompleted += completedGoals.length;
|
|
2247
|
+
if (activeGoals.length === 0 && !options.all) continue;
|
|
2248
|
+
writeLine(` ${colors.cyan}${name}${RESET}`);
|
|
2249
|
+
if (squad.mission) {
|
|
2250
|
+
writeLine(` ${colors.dim}${truncate(squad.mission, 60)}${RESET}`);
|
|
2251
|
+
}
|
|
2252
|
+
writeLine();
|
|
2253
|
+
for (const goal2 of activeGoals) {
|
|
2254
|
+
const globalIdx = squad.goals.indexOf(goal2) + 1;
|
|
2255
|
+
writeLine(` ${icons.active} ${colors.dim}[${globalIdx}]${RESET} ${goal2.description}`);
|
|
2256
|
+
if (goal2.progress) {
|
|
2257
|
+
writeLine(` ${colors.dim}\u2514 ${goal2.progress}${RESET}`);
|
|
2258
|
+
}
|
|
2259
|
+
}
|
|
2260
|
+
if (options.all && completedGoals.length > 0) {
|
|
2261
|
+
for (const goal2 of completedGoals) {
|
|
2262
|
+
const globalIdx = squad.goals.indexOf(goal2) + 1;
|
|
2263
|
+
writeLine(` ${icons.success} ${colors.dim}[${globalIdx}] ${goal2.description}${RESET}`);
|
|
2264
|
+
}
|
|
2265
|
+
}
|
|
2266
|
+
writeLine();
|
|
2267
|
+
}
|
|
2268
|
+
if (hasGoals) {
|
|
2269
|
+
writeLine(` ${colors.green}${totalActive}${RESET} active ${colors.dim}\u2502${RESET} ${colors.dim}${totalCompleted} completed${RESET}`);
|
|
2270
|
+
} else if (!squadName) {
|
|
2271
|
+
writeLine(` ${colors.yellow}No goals defined yet${RESET}`);
|
|
2272
|
+
writeLine();
|
|
2273
|
+
writeLine(` ${colors.dim}$${RESET} squads goal set ${colors.cyan}<squad>${RESET} ${colors.cyan}"<goal>"${RESET}`);
|
|
2274
|
+
}
|
|
2275
|
+
writeLine();
|
|
2276
|
+
}
|
|
2277
|
+
async function goalCompleteCommand(squadName, goalIndex) {
|
|
2278
|
+
const squad = loadSquad(squadName);
|
|
2279
|
+
if (!squad) {
|
|
2280
|
+
writeLine(` ${colors.red}Squad "${squadName}" not found${RESET}`);
|
|
2281
|
+
return;
|
|
2282
|
+
}
|
|
2283
|
+
const idx = parseInt(goalIndex) - 1;
|
|
2284
|
+
if (idx < 0 || idx >= squad.goals.length) {
|
|
2285
|
+
writeLine(` ${colors.red}Invalid goal index: ${goalIndex}${RESET}`);
|
|
2286
|
+
writeLine(` ${colors.dim}Squad has ${squad.goals.length} goal(s)${RESET}`);
|
|
2287
|
+
return;
|
|
2288
|
+
}
|
|
2289
|
+
const success = updateGoalInSquad(squadName, idx, { completed: true });
|
|
2290
|
+
writeLine();
|
|
2291
|
+
if (success) {
|
|
2292
|
+
writeLine(` ${icons.success} Goal completed: ${colors.cyan}${squad.goals[idx].description}${RESET}`);
|
|
2293
|
+
} else {
|
|
2294
|
+
writeLine(` ${colors.red}Failed to update goal${RESET}`);
|
|
2295
|
+
}
|
|
2296
|
+
writeLine();
|
|
2297
|
+
}
|
|
2298
|
+
async function goalProgressCommand(squadName, goalIndex, progress2) {
|
|
2299
|
+
const squad = loadSquad(squadName);
|
|
2300
|
+
if (!squad) {
|
|
2301
|
+
writeLine(` ${colors.red}Squad "${squadName}" not found${RESET}`);
|
|
2302
|
+
return;
|
|
2303
|
+
}
|
|
2304
|
+
const idx = parseInt(goalIndex) - 1;
|
|
2305
|
+
if (idx < 0 || idx >= squad.goals.length) {
|
|
2306
|
+
writeLine(` ${colors.red}Invalid goal index: ${goalIndex}${RESET}`);
|
|
2307
|
+
return;
|
|
2308
|
+
}
|
|
2309
|
+
const success = updateGoalInSquad(squadName, idx, { progress: progress2 });
|
|
2310
|
+
writeLine();
|
|
2311
|
+
if (success) {
|
|
2312
|
+
writeLine(` ${icons.success} Progress updated: ${colors.cyan}${squad.goals[idx].description}${RESET}`);
|
|
2313
|
+
writeLine(` ${colors.dim}${progress2}${RESET}`);
|
|
2314
|
+
} else {
|
|
2315
|
+
writeLine(` ${colors.red}Failed to update progress${RESET}`);
|
|
2316
|
+
}
|
|
2317
|
+
writeLine();
|
|
2318
|
+
}
|
|
2319
|
+
|
|
2320
|
+
// src/commands/feedback.ts
|
|
2321
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync6, existsSync as existsSync8, mkdirSync as mkdirSync5, readdirSync as readdirSync4 } from "fs";
|
|
2322
|
+
import { join as join8, dirname as dirname4 } from "path";
|
|
2323
|
+
function getFeedbackPath(squadName) {
|
|
2324
|
+
const memoryDir = findMemoryDir();
|
|
2325
|
+
if (!memoryDir) return null;
|
|
2326
|
+
const squad = loadSquad(squadName);
|
|
2327
|
+
const agentName = squad?.agents[0]?.name || `${squadName}-lead`;
|
|
2328
|
+
return join8(memoryDir, squadName, agentName, "feedback.md");
|
|
2329
|
+
}
|
|
2330
|
+
function getOutputPath(squadName) {
|
|
2331
|
+
const memoryDir = findMemoryDir();
|
|
2332
|
+
if (!memoryDir) return null;
|
|
2333
|
+
const squad = loadSquad(squadName);
|
|
2334
|
+
const agentName = squad?.agents[0]?.name || `${squadName}-lead`;
|
|
2335
|
+
return join8(memoryDir, squadName, agentName, "output.md");
|
|
2336
|
+
}
|
|
2337
|
+
function getLastExecution(squadName) {
|
|
2338
|
+
const outputPath = getOutputPath(squadName);
|
|
2339
|
+
if (!outputPath || !existsSync8(outputPath)) {
|
|
2340
|
+
return null;
|
|
2341
|
+
}
|
|
2342
|
+
const content = readFileSync6(outputPath, "utf-8");
|
|
2343
|
+
const lines = content.split("\n");
|
|
2344
|
+
let date = "unknown";
|
|
2345
|
+
let summary = lines.slice(0, 5).join("\n");
|
|
2346
|
+
const dateMatch = content.match(/(\d{4}-\d{2}-\d{2})/);
|
|
2347
|
+
if (dateMatch) {
|
|
2348
|
+
date = dateMatch[1];
|
|
2349
|
+
}
|
|
2350
|
+
const titleMatch = content.match(/^#\s+(.+)$/m);
|
|
2351
|
+
if (titleMatch) {
|
|
2352
|
+
summary = titleMatch[1];
|
|
2353
|
+
}
|
|
2354
|
+
return { date, summary };
|
|
2355
|
+
}
|
|
2356
|
+
function parseFeedbackHistory(content) {
|
|
2357
|
+
const entries = [];
|
|
2358
|
+
const sections = content.split(/---\n/).filter((s) => s.trim());
|
|
2359
|
+
for (const section of sections) {
|
|
2360
|
+
const dateMatch = section.match(/_Date:\s*(.+)_/);
|
|
2361
|
+
const ratingMatch = section.match(/\*\*Rating\*\*:\s*(\d)\/5/);
|
|
2362
|
+
const feedbackMatch = section.match(/\*\*Feedback\*\*:\s*(.+)/);
|
|
2363
|
+
const executionMatch = section.match(/\*\*Execution\*\*:\s*(.+)/);
|
|
2364
|
+
if (dateMatch && ratingMatch) {
|
|
2365
|
+
const entry = {
|
|
2366
|
+
date: dateMatch[1],
|
|
2367
|
+
execution: executionMatch?.[1] || "unknown",
|
|
2368
|
+
rating: parseInt(ratingMatch[1]),
|
|
2369
|
+
feedback: feedbackMatch?.[1] || "",
|
|
2370
|
+
learnings: []
|
|
2371
|
+
};
|
|
2372
|
+
const learningsMatch = section.match(/\*\*Learnings\*\*:\n((?:- .+\n?)+)/);
|
|
2373
|
+
if (learningsMatch) {
|
|
2374
|
+
entry.learnings = learningsMatch[1].split("\n").filter((l) => l.startsWith("- ")).map((l) => l.replace(/^- /, ""));
|
|
2375
|
+
}
|
|
2376
|
+
entries.push(entry);
|
|
2377
|
+
}
|
|
2378
|
+
}
|
|
2379
|
+
return entries;
|
|
2380
|
+
}
|
|
2381
|
+
async function feedbackAddCommand(squadName, rating, feedback2, options) {
|
|
2382
|
+
const feedbackPath = getFeedbackPath(squadName);
|
|
2383
|
+
if (!feedbackPath) {
|
|
2384
|
+
writeLine(` ${colors.red}Could not find memory directory${RESET}`);
|
|
2385
|
+
return;
|
|
2386
|
+
}
|
|
2387
|
+
const ratingNum = parseInt(rating);
|
|
2388
|
+
if (isNaN(ratingNum) || ratingNum < 1 || ratingNum > 5) {
|
|
2389
|
+
writeLine(` ${colors.red}Rating must be 1-5${RESET}`);
|
|
2390
|
+
return;
|
|
2391
|
+
}
|
|
2392
|
+
const lastExec = getLastExecution(squadName);
|
|
2393
|
+
const dir = dirname4(feedbackPath);
|
|
2394
|
+
if (!existsSync8(dir)) {
|
|
2395
|
+
mkdirSync5(dir, { recursive: true });
|
|
2396
|
+
}
|
|
2397
|
+
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
2398
|
+
let entry = `
|
|
2399
|
+
---
|
|
2400
|
+
_Date: ${date}_
|
|
2401
|
+
|
|
2402
|
+
`;
|
|
2403
|
+
entry += `**Execution**: ${lastExec?.summary || "Manual feedback"}
|
|
2404
|
+
`;
|
|
2405
|
+
entry += `**Rating**: ${ratingNum}/5 ${"\u2605".repeat(ratingNum)}${"\u2606".repeat(5 - ratingNum)}
|
|
2406
|
+
`;
|
|
2407
|
+
entry += `**Feedback**: ${feedback2}
|
|
2408
|
+
`;
|
|
2409
|
+
if (options.learning && options.learning.length > 0) {
|
|
2410
|
+
entry += `**Learnings**:
|
|
2411
|
+
`;
|
|
2412
|
+
for (const learning of options.learning) {
|
|
2413
|
+
entry += `- ${learning}
|
|
2414
|
+
`;
|
|
2415
|
+
}
|
|
2416
|
+
const squad = loadSquad(squadName);
|
|
2417
|
+
const agentName = squad?.agents[0]?.name || `${squadName}-lead`;
|
|
2418
|
+
for (const learning of options.learning) {
|
|
2419
|
+
appendToMemory(squadName, agentName, "learnings", `From feedback (${date}): ${learning}`);
|
|
2420
|
+
}
|
|
2421
|
+
}
|
|
2422
|
+
let existing = "";
|
|
2423
|
+
if (existsSync8(feedbackPath)) {
|
|
2424
|
+
existing = readFileSync6(feedbackPath, "utf-8");
|
|
2425
|
+
} else {
|
|
2426
|
+
existing = `# ${squadName} - Feedback Log
|
|
2427
|
+
|
|
2428
|
+
> Execution feedback and learnings
|
|
2429
|
+
`;
|
|
2430
|
+
}
|
|
2431
|
+
writeFileSync6(feedbackPath, existing + entry);
|
|
2432
|
+
const stars = `${colors.yellow}${"\u2605".repeat(ratingNum)}${"\u2606".repeat(5 - ratingNum)}${RESET}`;
|
|
2433
|
+
writeLine();
|
|
2434
|
+
writeLine(` ${icons.success} Feedback recorded for ${colors.cyan}${squadName}${RESET}`);
|
|
2435
|
+
writeLine(` Rating: ${stars}`);
|
|
2436
|
+
writeLine(` ${feedback2}`);
|
|
2437
|
+
if (options.learning && options.learning.length > 0) {
|
|
2438
|
+
writeLine(` ${colors.dim}+ ${options.learning.length} learning(s) added${RESET}`);
|
|
2439
|
+
}
|
|
2440
|
+
writeLine();
|
|
2441
|
+
}
|
|
2442
|
+
async function feedbackShowCommand(squadName, options) {
|
|
2443
|
+
const feedbackPath = getFeedbackPath(squadName);
|
|
2444
|
+
if (!feedbackPath || !existsSync8(feedbackPath)) {
|
|
2445
|
+
writeLine(` ${colors.yellow}No feedback recorded for ${squadName}${RESET}`);
|
|
2446
|
+
return;
|
|
2447
|
+
}
|
|
2448
|
+
const content = readFileSync6(feedbackPath, "utf-8");
|
|
2449
|
+
const entries = parseFeedbackHistory(content);
|
|
2450
|
+
const limit = options.limit ? parseInt(options.limit) : 5;
|
|
2451
|
+
const recent = entries.slice(-limit).reverse();
|
|
2452
|
+
writeLine();
|
|
2453
|
+
writeLine(` ${gradient("squads")} ${colors.dim}feedback${RESET} ${colors.cyan}${squadName}${RESET}`);
|
|
2454
|
+
writeLine();
|
|
2455
|
+
if (recent.length === 0) {
|
|
2456
|
+
writeLine(` ${colors.dim}No feedback entries yet${RESET}`);
|
|
2457
|
+
return;
|
|
2458
|
+
}
|
|
2459
|
+
const avgRating = entries.reduce((sum, e) => sum + e.rating, 0) / entries.length;
|
|
2460
|
+
writeLine(` ${colors.dim}Average: ${avgRating.toFixed(1)}/5 (${entries.length} entries)${RESET}`);
|
|
2461
|
+
writeLine();
|
|
2462
|
+
for (const entry of recent) {
|
|
2463
|
+
const stars = `${colors.yellow}${"\u2605".repeat(entry.rating)}${"\u2606".repeat(5 - entry.rating)}${RESET}`;
|
|
2464
|
+
writeLine(` ${colors.dim}${entry.date}${RESET} ${stars}`);
|
|
2465
|
+
writeLine(` ${entry.feedback}`);
|
|
2466
|
+
if (entry.learnings && entry.learnings.length > 0) {
|
|
2467
|
+
for (const learning of entry.learnings) {
|
|
2468
|
+
writeLine(` ${colors.green}\u2192 ${learning}${RESET}`);
|
|
2469
|
+
}
|
|
2470
|
+
}
|
|
2471
|
+
writeLine();
|
|
2472
|
+
}
|
|
2473
|
+
}
|
|
2474
|
+
async function feedbackStatsCommand() {
|
|
2475
|
+
const memoryDir = findMemoryDir();
|
|
2476
|
+
if (!memoryDir) {
|
|
2477
|
+
writeLine(` ${colors.red}Could not find memory directory${RESET}`);
|
|
2478
|
+
return;
|
|
2479
|
+
}
|
|
2480
|
+
writeLine();
|
|
2481
|
+
writeLine(` ${gradient("squads")} ${colors.dim}feedback stats${RESET}`);
|
|
2482
|
+
writeLine();
|
|
2483
|
+
const squads = readdirSync4(memoryDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
2484
|
+
const w = { squad: 18, avg: 12, count: 8, trend: 6 };
|
|
2485
|
+
const tableWidth = w.squad + w.avg + w.count + w.trend + 4;
|
|
2486
|
+
writeLine(` ${colors.purple}${box.topLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.topRight}${RESET}`);
|
|
2487
|
+
const header = ` ${colors.purple}${box.vertical}${RESET} ${bold}${padEnd("SQUAD", w.squad)}${RESET}${bold}${padEnd("AVG", w.avg)}${RESET}${bold}${padEnd("COUNT", w.count)}${RESET}${bold}TREND${RESET} ${colors.purple}${box.vertical}${RESET}`;
|
|
2488
|
+
writeLine(header);
|
|
2489
|
+
writeLine(` ${colors.purple}${box.teeRight}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.teeLeft}${RESET}`);
|
|
2490
|
+
for (const squad of squads) {
|
|
2491
|
+
const feedbackPath = getFeedbackPath(squad);
|
|
2492
|
+
if (!feedbackPath || !existsSync8(feedbackPath)) {
|
|
2493
|
+
continue;
|
|
2494
|
+
}
|
|
2495
|
+
const content = readFileSync6(feedbackPath, "utf-8");
|
|
2496
|
+
const entries = parseFeedbackHistory(content);
|
|
2497
|
+
if (entries.length === 0) continue;
|
|
2498
|
+
const avgRating = entries.reduce((sum, e) => sum + e.rating, 0) / entries.length;
|
|
2499
|
+
let trend = `${colors.dim}\u2192${RESET}`;
|
|
2500
|
+
if (entries.length >= 4) {
|
|
2501
|
+
const recent = entries.slice(-3).reduce((s, e) => s + e.rating, 0) / 3;
|
|
2502
|
+
const older = entries.slice(-6, -3).reduce((s, e) => s + e.rating, 0) / Math.min(3, entries.slice(-6, -3).length);
|
|
2503
|
+
if (recent > older + 0.3) trend = `${colors.green}\u2191${RESET}`;
|
|
2504
|
+
else if (recent < older - 0.3) trend = `${colors.red}\u2193${RESET}`;
|
|
2505
|
+
}
|
|
2506
|
+
const stars = `${colors.yellow}${"\u2605".repeat(Math.round(avgRating))}${"\u2606".repeat(5 - Math.round(avgRating))}${RESET}`;
|
|
2507
|
+
const row = ` ${colors.purple}${box.vertical}${RESET} ${colors.cyan}${padEnd(squad, w.squad)}${RESET}${padEnd(stars, w.avg + 20)}${padEnd(String(entries.length), w.count)}${trend} ${colors.purple}${box.vertical}${RESET}`;
|
|
2508
|
+
writeLine(row);
|
|
2509
|
+
}
|
|
2510
|
+
writeLine(` ${colors.purple}${box.bottomLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.bottomRight}${RESET}`);
|
|
2511
|
+
writeLine();
|
|
2512
|
+
writeLine(` ${colors.dim}$${RESET} squads feedback show ${colors.cyan}<squad>${RESET} ${colors.dim}View squad feedback${RESET}`);
|
|
2513
|
+
writeLine(` ${colors.dim}$${RESET} squads feedback add ${colors.cyan}<squad>${RESET} ${colors.dim}Add new feedback${RESET}`);
|
|
2514
|
+
writeLine();
|
|
2515
|
+
}
|
|
2516
|
+
|
|
2517
|
+
// src/commands/dashboard.ts
|
|
2518
|
+
import { readdirSync as readdirSync5, existsSync as existsSync9, statSync as statSync2 } from "fs";
|
|
2519
|
+
import { join as join9 } from "path";
|
|
2520
|
+
import { homedir as homedir2 } from "os";
|
|
2521
|
+
|
|
2522
|
+
// src/lib/costs.ts
|
|
2523
|
+
var MODEL_PRICING = {
|
|
2524
|
+
"claude-opus-4-5-20251101": { input: 15, output: 75 },
|
|
2525
|
+
"claude-sonnet-4-20250514": { input: 3, output: 15 },
|
|
2526
|
+
"claude-haiku-4-5-20251001": { input: 0.8, output: 4 },
|
|
2527
|
+
"claude-3-5-sonnet-20241022": { input: 3, output: 15 },
|
|
2528
|
+
"claude-3-5-haiku-20241022": { input: 0.8, output: 4 },
|
|
2529
|
+
default: { input: 3, output: 15 }
|
|
2530
|
+
};
|
|
2531
|
+
var DEFAULT_DAILY_BUDGET = 50;
|
|
2532
|
+
var DEFAULT_DAILY_CALL_LIMIT = 1e3;
|
|
2533
|
+
var BRIDGE_URL = process.env.SQUADS_BRIDGE_URL || "http://localhost:8088";
|
|
2534
|
+
function calcCost(model, inputTokens, outputTokens) {
|
|
2535
|
+
const pricing = MODEL_PRICING[model] || MODEL_PRICING.default;
|
|
2536
|
+
return inputTokens / 1e6 * pricing.input + outputTokens / 1e6 * pricing.output;
|
|
2537
|
+
}
|
|
2538
|
+
async function fetchFromBridge(period = "day") {
|
|
2539
|
+
try {
|
|
2540
|
+
const response = await fetch(`${BRIDGE_URL}/api/cost/summary?period=${period}`, {
|
|
2541
|
+
headers: { "Content-Type": "application/json" }
|
|
2542
|
+
});
|
|
2543
|
+
if (!response.ok) {
|
|
2544
|
+
return null;
|
|
2545
|
+
}
|
|
2546
|
+
const data = await response.json();
|
|
2547
|
+
const dailyBudget = parseFloat(process.env.SQUADS_DAILY_BUDGET || "") || DEFAULT_DAILY_BUDGET;
|
|
2548
|
+
const totalCost = data.totals?.cost_usd || 0;
|
|
2549
|
+
const bySquad = (data.by_squad || []).map((s) => ({
|
|
2550
|
+
squad: s.squad,
|
|
2551
|
+
calls: s.generations || 0,
|
|
2552
|
+
inputTokens: s.input_tokens || 0,
|
|
2553
|
+
outputTokens: s.output_tokens || 0,
|
|
2554
|
+
cachedTokens: s.cached_tokens || 0,
|
|
2555
|
+
cost: s.cost_usd || 0,
|
|
2556
|
+
models: {}
|
|
2557
|
+
}));
|
|
2558
|
+
const totalCalls = bySquad.reduce((sum, s) => sum + s.calls, 0);
|
|
2559
|
+
const dailyCallLimit = parseFloat(process.env.SQUADS_DAILY_CALL_LIMIT || "") || DEFAULT_DAILY_CALL_LIMIT;
|
|
2560
|
+
const totalCachedTokens = bySquad.reduce((sum, s) => sum + s.cachedTokens, 0);
|
|
2561
|
+
const totalInputTokens = bySquad.reduce((sum, s) => sum + s.inputTokens, 0);
|
|
2562
|
+
const totalAllInput = totalInputTokens + totalCachedTokens;
|
|
2563
|
+
const cacheHitRate = totalAllInput > 0 ? totalCachedTokens / totalAllInput * 100 : 0;
|
|
2564
|
+
return {
|
|
2565
|
+
totalCost,
|
|
2566
|
+
dailyBudget,
|
|
2567
|
+
usedPercent: totalCost / dailyBudget * 100,
|
|
2568
|
+
idleBudget: dailyBudget - totalCost,
|
|
2569
|
+
totalCalls,
|
|
2570
|
+
dailyCallLimit,
|
|
2571
|
+
callsPercent: totalCalls / dailyCallLimit * 100,
|
|
2572
|
+
totalCachedTokens,
|
|
2573
|
+
totalInputTokens,
|
|
2574
|
+
cacheHitRate,
|
|
2575
|
+
bySquad,
|
|
2576
|
+
source: "postgres"
|
|
2577
|
+
};
|
|
2578
|
+
} catch {
|
|
2579
|
+
return null;
|
|
2580
|
+
}
|
|
2581
|
+
}
|
|
2582
|
+
async function fetchFromLangfuse(limit = 100) {
|
|
2583
|
+
const publicKey = process.env.LANGFUSE_PUBLIC_KEY;
|
|
2584
|
+
const secretKey = process.env.LANGFUSE_SECRET_KEY;
|
|
2585
|
+
const host = process.env.LANGFUSE_HOST || process.env.LANGFUSE_BASE_URL || "https://us.cloud.langfuse.com";
|
|
2586
|
+
if (!publicKey || !secretKey) {
|
|
2587
|
+
return null;
|
|
2588
|
+
}
|
|
2589
|
+
try {
|
|
2590
|
+
const auth = Buffer.from(`${publicKey}:${secretKey}`).toString("base64");
|
|
2591
|
+
const url = `${host}/api/public/observations?limit=${limit}`;
|
|
2592
|
+
const response = await fetch(url, {
|
|
2593
|
+
headers: {
|
|
2594
|
+
Authorization: `Basic ${auth}`,
|
|
2595
|
+
"Content-Type": "application/json"
|
|
2596
|
+
}
|
|
2597
|
+
});
|
|
2598
|
+
if (!response.ok) {
|
|
2599
|
+
return null;
|
|
2600
|
+
}
|
|
2601
|
+
const data = await response.json();
|
|
2602
|
+
const observations = data.data || [];
|
|
2603
|
+
const bySquad = {};
|
|
2604
|
+
for (const obs of observations) {
|
|
2605
|
+
if (obs.type !== "GENERATION") continue;
|
|
2606
|
+
const metadata = obs.metadata || {};
|
|
2607
|
+
const squad = metadata.squad || "unknown";
|
|
2608
|
+
const model = obs.model || "unknown";
|
|
2609
|
+
const usage = obs.usage || {};
|
|
2610
|
+
const inputTokens = usage.input || 0;
|
|
2611
|
+
const outputTokens = usage.output || 0;
|
|
2612
|
+
const cost = calcCost(model, inputTokens, outputTokens);
|
|
2613
|
+
if (!bySquad[squad]) {
|
|
2614
|
+
bySquad[squad] = {
|
|
2615
|
+
squad,
|
|
2616
|
+
calls: 0,
|
|
2617
|
+
inputTokens: 0,
|
|
2618
|
+
outputTokens: 0,
|
|
2619
|
+
cachedTokens: 0,
|
|
2620
|
+
cost: 0,
|
|
2621
|
+
models: {}
|
|
2622
|
+
};
|
|
2623
|
+
}
|
|
2624
|
+
bySquad[squad].calls += 1;
|
|
2625
|
+
bySquad[squad].inputTokens += inputTokens;
|
|
2626
|
+
bySquad[squad].outputTokens += outputTokens;
|
|
2627
|
+
bySquad[squad].cost += cost;
|
|
2628
|
+
bySquad[squad].models[model] = (bySquad[squad].models[model] || 0) + 1;
|
|
2629
|
+
}
|
|
2630
|
+
const squadList = Object.values(bySquad).sort((a, b) => b.cost - a.cost);
|
|
2631
|
+
const totalCost = squadList.reduce((sum, s) => sum + s.cost, 0);
|
|
2632
|
+
const dailyBudget = parseFloat(process.env.SQUADS_DAILY_BUDGET || "") || DEFAULT_DAILY_BUDGET;
|
|
2633
|
+
const totalCalls = squadList.reduce((sum, s) => sum + s.calls, 0);
|
|
2634
|
+
const dailyCallLimit = parseFloat(process.env.SQUADS_DAILY_CALL_LIMIT || "") || DEFAULT_DAILY_CALL_LIMIT;
|
|
2635
|
+
const totalCachedTokens = squadList.reduce((sum, s) => sum + s.cachedTokens, 0);
|
|
2636
|
+
const totalInputTokens = squadList.reduce((sum, s) => sum + s.inputTokens, 0);
|
|
2637
|
+
const totalAllInput = totalInputTokens + totalCachedTokens;
|
|
2638
|
+
const cacheHitRate = totalAllInput > 0 ? totalCachedTokens / totalAllInput * 100 : 0;
|
|
2639
|
+
return {
|
|
2640
|
+
totalCost,
|
|
2641
|
+
dailyBudget,
|
|
2642
|
+
usedPercent: totalCost / dailyBudget * 100,
|
|
2643
|
+
idleBudget: dailyBudget - totalCost,
|
|
2644
|
+
totalCalls,
|
|
2645
|
+
dailyCallLimit,
|
|
2646
|
+
callsPercent: totalCalls / dailyCallLimit * 100,
|
|
2647
|
+
totalCachedTokens,
|
|
2648
|
+
totalInputTokens,
|
|
2649
|
+
cacheHitRate,
|
|
2650
|
+
bySquad: squadList,
|
|
2651
|
+
source: "langfuse"
|
|
2652
|
+
};
|
|
2653
|
+
} catch {
|
|
2654
|
+
return null;
|
|
2655
|
+
}
|
|
2656
|
+
}
|
|
2657
|
+
async function fetchCostSummary(limit = 100, period = "day") {
|
|
2658
|
+
const bridgeResult = await fetchFromBridge(period);
|
|
2659
|
+
if (bridgeResult) {
|
|
2660
|
+
return bridgeResult;
|
|
2661
|
+
}
|
|
2662
|
+
const langfuseResult = await fetchFromLangfuse(limit);
|
|
2663
|
+
if (langfuseResult) {
|
|
2664
|
+
return langfuseResult;
|
|
2665
|
+
}
|
|
2666
|
+
const defaultBudget = parseFloat(process.env.SQUADS_DAILY_BUDGET || "") || DEFAULT_DAILY_BUDGET;
|
|
2667
|
+
return {
|
|
2668
|
+
totalCost: 0,
|
|
2669
|
+
dailyBudget: defaultBudget,
|
|
2670
|
+
usedPercent: 0,
|
|
2671
|
+
idleBudget: defaultBudget,
|
|
2672
|
+
totalCalls: 0,
|
|
2673
|
+
dailyCallLimit: DEFAULT_DAILY_CALL_LIMIT,
|
|
2674
|
+
callsPercent: 0,
|
|
2675
|
+
totalCachedTokens: 0,
|
|
2676
|
+
totalInputTokens: 0,
|
|
2677
|
+
cacheHitRate: 0,
|
|
2678
|
+
bySquad: [],
|
|
2679
|
+
source: "none"
|
|
2680
|
+
};
|
|
2681
|
+
}
|
|
2682
|
+
function formatCostBar(usedPercent, width = 20) {
|
|
2683
|
+
const filled = Math.min(Math.round(usedPercent / 100 * width), width);
|
|
2684
|
+
const empty = width - filled;
|
|
2685
|
+
return "\u2588".repeat(filled) + "\u2591".repeat(empty);
|
|
2686
|
+
}
|
|
2687
|
+
async function fetchRateLimits() {
|
|
2688
|
+
try {
|
|
2689
|
+
const response = await fetch(`${BRIDGE_URL}/api/rate-limits`, {
|
|
2690
|
+
headers: { "Content-Type": "application/json" }
|
|
2691
|
+
});
|
|
2692
|
+
if (!response.ok) {
|
|
2693
|
+
return { limits: {}, source: "none" };
|
|
2694
|
+
}
|
|
2695
|
+
const data = await response.json();
|
|
2696
|
+
const rateLimits = data.rate_limits || {};
|
|
2697
|
+
const limits = {};
|
|
2698
|
+
for (const [key, value] of Object.entries(rateLimits)) {
|
|
2699
|
+
limits[key] = {
|
|
2700
|
+
model: value.model || key,
|
|
2701
|
+
requestsLimit: value.requests_limit || 0,
|
|
2702
|
+
requestsRemaining: value.requests_remaining || 0,
|
|
2703
|
+
requestsReset: value.requests_reset,
|
|
2704
|
+
tokensLimit: value.tokens_limit || 0,
|
|
2705
|
+
tokensRemaining: value.tokens_remaining || 0,
|
|
2706
|
+
tokensReset: value.tokens_reset,
|
|
2707
|
+
inputTokensLimit: value.input_tokens_limit,
|
|
2708
|
+
inputTokensRemaining: value.input_tokens_remaining,
|
|
2709
|
+
outputTokensLimit: value.output_tokens_limit,
|
|
2710
|
+
outputTokensRemaining: value.output_tokens_remaining,
|
|
2711
|
+
capturedAt: value.captured_at || (/* @__PURE__ */ new Date()).toISOString()
|
|
2712
|
+
};
|
|
2713
|
+
}
|
|
2714
|
+
return { limits, source: "proxy" };
|
|
2715
|
+
} catch {
|
|
2716
|
+
return { limits: {}, source: "none" };
|
|
2717
|
+
}
|
|
2718
|
+
}
|
|
2719
|
+
async function fetchInsights(period = "week") {
|
|
2720
|
+
try {
|
|
2721
|
+
const response = await fetch(`${BRIDGE_URL}/api/insights?period=${period}`, {
|
|
2722
|
+
headers: { "Content-Type": "application/json" }
|
|
2723
|
+
});
|
|
2724
|
+
if (!response.ok) {
|
|
2725
|
+
return {
|
|
2726
|
+
period,
|
|
2727
|
+
days: period === "day" ? 1 : period === "week" ? 7 : 30,
|
|
2728
|
+
taskMetrics: [],
|
|
2729
|
+
qualityMetrics: [],
|
|
2730
|
+
topTools: [],
|
|
2731
|
+
toolFailureRate: 0,
|
|
2732
|
+
source: "none"
|
|
2733
|
+
};
|
|
2734
|
+
}
|
|
2735
|
+
const data = await response.json();
|
|
2736
|
+
return {
|
|
2737
|
+
period: data.period || period,
|
|
2738
|
+
days: data.days || 7,
|
|
2739
|
+
taskMetrics: (data.task_metrics || []).map((t) => ({
|
|
2740
|
+
squad: t.squad,
|
|
2741
|
+
tasksTotal: t.tasks_total || 0,
|
|
2742
|
+
tasksCompleted: t.tasks_completed || 0,
|
|
2743
|
+
tasksFailed: t.tasks_failed || 0,
|
|
2744
|
+
successRate: t.success_rate || 0,
|
|
2745
|
+
totalRetries: t.total_retries || 0,
|
|
2746
|
+
tasksWithRetries: t.tasks_with_retries || 0,
|
|
2747
|
+
avgRetries: t.avg_retries || 0,
|
|
2748
|
+
avgDurationMs: t.avg_duration_ms || 0,
|
|
2749
|
+
avgTokens: t.avg_tokens || 0,
|
|
2750
|
+
avgCost: t.avg_cost || 0,
|
|
2751
|
+
avgContextPct: t.avg_context_pct || 0,
|
|
2752
|
+
maxContextTokens: t.max_context_tokens || 0
|
|
2753
|
+
})),
|
|
2754
|
+
qualityMetrics: (data.quality_metrics || []).map((q) => ({
|
|
2755
|
+
squad: q.squad,
|
|
2756
|
+
feedbackCount: q.feedback_count || 0,
|
|
2757
|
+
avgQuality: q.avg_quality || 0,
|
|
2758
|
+
helpfulPct: q.helpful_pct || 0,
|
|
2759
|
+
fixRequiredPct: q.fix_required_pct || 0
|
|
2760
|
+
})),
|
|
2761
|
+
topTools: (data.top_tools || []).map((t) => ({
|
|
2762
|
+
toolName: t.tool_name,
|
|
2763
|
+
usageCount: t.usage_count || 0,
|
|
2764
|
+
successRate: t.success_rate || 0,
|
|
2765
|
+
avgDurationMs: t.avg_duration_ms || 0
|
|
2766
|
+
})),
|
|
2767
|
+
toolFailureRate: data.tool_failure_rate || 0,
|
|
2768
|
+
source: "bridge"
|
|
2769
|
+
};
|
|
2770
|
+
} catch {
|
|
2771
|
+
return {
|
|
2772
|
+
period,
|
|
2773
|
+
days: period === "day" ? 1 : period === "week" ? 7 : 30,
|
|
2774
|
+
taskMetrics: [],
|
|
2775
|
+
qualityMetrics: [],
|
|
2776
|
+
topTools: [],
|
|
2777
|
+
toolFailureRate: 0,
|
|
2778
|
+
source: "none"
|
|
2779
|
+
};
|
|
2780
|
+
}
|
|
2781
|
+
}
|
|
2782
|
+
|
|
2783
|
+
// src/lib/db.ts
|
|
2784
|
+
import { createRequire } from "module";
|
|
2785
|
+
var require2 = createRequire(import.meta.url);
|
|
2786
|
+
var pg = require2("pg");
|
|
2787
|
+
var { Pool } = pg;
|
|
2788
|
+
var DATABASE_URL = process.env.SQUADS_DATABASE_URL || "postgresql://squads:squads_local_dev@localhost:5433/squads";
|
|
2789
|
+
var pool = null;
|
|
2790
|
+
function getPool() {
|
|
2791
|
+
if (!pool) {
|
|
2792
|
+
pool = new Pool({
|
|
2793
|
+
connectionString: DATABASE_URL,
|
|
2794
|
+
max: 5,
|
|
2795
|
+
idleTimeoutMillis: 3e4,
|
|
2796
|
+
connectionTimeoutMillis: 5e3
|
|
2797
|
+
});
|
|
2798
|
+
pool.on("error", (err) => {
|
|
2799
|
+
console.error("Unexpected database pool error:", err);
|
|
2800
|
+
});
|
|
2801
|
+
}
|
|
2802
|
+
return pool;
|
|
2803
|
+
}
|
|
2804
|
+
async function isDatabaseAvailable() {
|
|
2805
|
+
try {
|
|
2806
|
+
const pool2 = getPool();
|
|
2807
|
+
const client = await pool2.connect();
|
|
2808
|
+
await client.query("SELECT 1");
|
|
2809
|
+
client.release();
|
|
2810
|
+
return true;
|
|
2811
|
+
} catch (err) {
|
|
2812
|
+
if (process.env.DEBUG) {
|
|
2813
|
+
console.error("DB availability check failed:", err);
|
|
2814
|
+
}
|
|
2815
|
+
return false;
|
|
2816
|
+
}
|
|
2817
|
+
}
|
|
2818
|
+
async function saveDashboardSnapshot(snapshot) {
|
|
2819
|
+
try {
|
|
2820
|
+
const pool2 = getPool();
|
|
2821
|
+
const client = await pool2.connect();
|
|
2822
|
+
const result = await client.query(`
|
|
2823
|
+
INSERT INTO squads.dashboard_snapshots (
|
|
2824
|
+
total_squads, total_commits, total_prs_merged, total_issues_closed, total_issues_open,
|
|
2825
|
+
goal_progress_pct, cost_usd, daily_budget_usd, input_tokens, output_tokens,
|
|
2826
|
+
commits_30d, avg_commits_per_day, active_days, peak_commits, peak_date,
|
|
2827
|
+
squads_data, authors_data, repos_data
|
|
2828
|
+
) VALUES (
|
|
2829
|
+
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18
|
|
2830
|
+
) RETURNING id
|
|
2831
|
+
`, [
|
|
2832
|
+
snapshot.totalSquads,
|
|
2833
|
+
snapshot.totalCommits,
|
|
2834
|
+
snapshot.totalPrsMerged,
|
|
2835
|
+
snapshot.totalIssuesClosed,
|
|
2836
|
+
snapshot.totalIssuesOpen,
|
|
2837
|
+
snapshot.goalProgressPct,
|
|
2838
|
+
snapshot.costUsd,
|
|
2839
|
+
snapshot.dailyBudgetUsd,
|
|
2840
|
+
snapshot.inputTokens,
|
|
2841
|
+
snapshot.outputTokens,
|
|
2842
|
+
snapshot.commits30d,
|
|
2843
|
+
snapshot.avgCommitsPerDay,
|
|
2844
|
+
snapshot.activeDays,
|
|
2845
|
+
snapshot.peakCommits,
|
|
2846
|
+
snapshot.peakDate,
|
|
2847
|
+
JSON.stringify(snapshot.squadsData),
|
|
2848
|
+
JSON.stringify(snapshot.authorsData),
|
|
2849
|
+
JSON.stringify(snapshot.reposData)
|
|
2850
|
+
]);
|
|
2851
|
+
client.release();
|
|
2852
|
+
return result.rows[0]?.id ?? null;
|
|
2853
|
+
} catch (err) {
|
|
2854
|
+
if (process.env.DEBUG) {
|
|
2855
|
+
console.error("DB save error:", err);
|
|
2856
|
+
}
|
|
2857
|
+
return null;
|
|
2858
|
+
}
|
|
2859
|
+
}
|
|
2860
|
+
async function getDashboardHistory(limit = 30) {
|
|
2861
|
+
try {
|
|
2862
|
+
const client = await getPool().connect();
|
|
2863
|
+
const result = await client.query(`
|
|
2864
|
+
SELECT
|
|
2865
|
+
total_squads, total_commits, total_prs_merged, total_issues_closed, total_issues_open,
|
|
2866
|
+
goal_progress_pct, cost_usd, daily_budget_usd, input_tokens, output_tokens,
|
|
2867
|
+
commits_30d, avg_commits_per_day, active_days, peak_commits, peak_date,
|
|
2868
|
+
squads_data, authors_data, repos_data, captured_at
|
|
2869
|
+
FROM squads.dashboard_snapshots
|
|
2870
|
+
ORDER BY captured_at DESC
|
|
2871
|
+
LIMIT $1
|
|
2872
|
+
`, [limit]);
|
|
2873
|
+
client.release();
|
|
2874
|
+
return result.rows.map((row) => ({
|
|
2875
|
+
totalSquads: row.total_squads,
|
|
2876
|
+
totalCommits: row.total_commits,
|
|
2877
|
+
totalPrsMerged: row.total_prs_merged,
|
|
2878
|
+
totalIssuesClosed: row.total_issues_closed,
|
|
2879
|
+
totalIssuesOpen: row.total_issues_open,
|
|
2880
|
+
goalProgressPct: row.goal_progress_pct,
|
|
2881
|
+
costUsd: parseFloat(String(row.cost_usd)),
|
|
2882
|
+
dailyBudgetUsd: parseFloat(String(row.daily_budget_usd)),
|
|
2883
|
+
inputTokens: row.input_tokens,
|
|
2884
|
+
outputTokens: row.output_tokens,
|
|
2885
|
+
commits30d: row.commits_30d,
|
|
2886
|
+
avgCommitsPerDay: parseFloat(String(row.avg_commits_per_day)),
|
|
2887
|
+
activeDays: row.active_days,
|
|
2888
|
+
peakCommits: row.peak_commits,
|
|
2889
|
+
peakDate: row.peak_date,
|
|
2890
|
+
squadsData: row.squads_data || [],
|
|
2891
|
+
authorsData: row.authors_data || [],
|
|
2892
|
+
reposData: row.repos_data || []
|
|
2893
|
+
}));
|
|
2894
|
+
} catch {
|
|
2895
|
+
return [];
|
|
2896
|
+
}
|
|
2897
|
+
}
|
|
2898
|
+
|
|
2899
|
+
// src/commands/dashboard.ts
|
|
2900
|
+
function getLastActivityDate(squadName) {
|
|
2901
|
+
const memoryDir = findMemoryDir();
|
|
2902
|
+
if (!memoryDir) return "unknown";
|
|
2903
|
+
const squadMemory = join9(memoryDir, squadName);
|
|
2904
|
+
if (!existsSync9(squadMemory)) return "\u2014";
|
|
2905
|
+
let latestTime = 0;
|
|
2906
|
+
try {
|
|
2907
|
+
const agents = readdirSync5(squadMemory, { withFileTypes: true }).filter((e) => e.isDirectory());
|
|
2908
|
+
for (const agent of agents) {
|
|
2909
|
+
const agentPath = join9(squadMemory, agent.name);
|
|
2910
|
+
const files = readdirSync5(agentPath).filter((f) => f.endsWith(".md"));
|
|
2911
|
+
for (const file of files) {
|
|
2912
|
+
const filePath = join9(agentPath, file);
|
|
2913
|
+
const stats = statSync2(filePath);
|
|
2914
|
+
if (stats.mtimeMs > latestTime) {
|
|
2915
|
+
latestTime = stats.mtimeMs;
|
|
2916
|
+
}
|
|
2917
|
+
}
|
|
2918
|
+
}
|
|
2919
|
+
} catch {
|
|
2920
|
+
return "\u2014";
|
|
2921
|
+
}
|
|
2922
|
+
if (latestTime === 0) return "\u2014";
|
|
2923
|
+
const ageMs = Date.now() - latestTime;
|
|
2924
|
+
const ageDays = Math.floor(ageMs / (1e3 * 60 * 60 * 24));
|
|
2925
|
+
if (ageDays === 0) return "today";
|
|
2926
|
+
if (ageDays === 1) return "1d";
|
|
2927
|
+
if (ageDays < 7) return `${ageDays}d`;
|
|
2928
|
+
return `${Math.floor(ageDays / 7)}w`;
|
|
2929
|
+
}
|
|
2930
|
+
async function dashboardCommand(options = {}) {
|
|
2931
|
+
const squadsDir = findSquadsDir();
|
|
2932
|
+
if (!squadsDir) {
|
|
2933
|
+
writeLine(`${colors.red}No .agents/squads directory found${RESET}`);
|
|
2934
|
+
return;
|
|
2935
|
+
}
|
|
2936
|
+
if (options.ceo) {
|
|
2937
|
+
await renderCeoReport(squadsDir);
|
|
2938
|
+
return;
|
|
2939
|
+
}
|
|
2940
|
+
const squadNames = listSquads(squadsDir);
|
|
2941
|
+
const squadData = [];
|
|
2942
|
+
const baseDir = findAgentsSquadsDir();
|
|
2943
|
+
const ghStats = baseDir ? getGitHubStats(baseDir, 30) : null;
|
|
2944
|
+
for (const name of squadNames) {
|
|
2945
|
+
const squad = loadSquad(name);
|
|
2946
|
+
if (!squad) continue;
|
|
2947
|
+
const lastActivity = getLastActivityDate(name);
|
|
2948
|
+
const github = ghStats?.bySquad.get(name) || null;
|
|
2949
|
+
let status = "active";
|
|
2950
|
+
const activeGoals2 = squad.goals.filter((g) => !g.completed);
|
|
2951
|
+
if (activeGoals2.length === 0) {
|
|
2952
|
+
status = "needs-goal";
|
|
2953
|
+
} else if (lastActivity.includes("w") || lastActivity === "\u2014") {
|
|
2954
|
+
status = "stale";
|
|
2955
|
+
}
|
|
2956
|
+
const totalGoals2 = squad.goals.length;
|
|
2957
|
+
const completedGoals2 = squad.goals.filter((g) => g.completed).length;
|
|
2958
|
+
const hasProgress = squad.goals.filter((g) => g.progress).length;
|
|
2959
|
+
const goalProgress = totalGoals2 > 0 ? Math.round((completedGoals2 + hasProgress * 0.3) / totalGoals2 * 100) : 0;
|
|
2960
|
+
squadData.push({
|
|
2961
|
+
name,
|
|
2962
|
+
mission: squad.mission,
|
|
2963
|
+
goals: squad.goals,
|
|
2964
|
+
lastActivity,
|
|
2965
|
+
status,
|
|
2966
|
+
github,
|
|
2967
|
+
goalProgress
|
|
2968
|
+
});
|
|
2969
|
+
}
|
|
2970
|
+
const totalGoals = squadData.reduce((sum, s) => sum + s.goals.length, 0);
|
|
2971
|
+
const activeGoals = squadData.reduce((sum, s) => sum + s.goals.filter((g) => !g.completed).length, 0);
|
|
2972
|
+
const completedGoals = totalGoals - activeGoals;
|
|
2973
|
+
const completionRate = totalGoals > 0 ? Math.round(completedGoals / totalGoals * 100) : 0;
|
|
2974
|
+
const activeSquads = squadData.filter((s) => s.status === "active").length;
|
|
2975
|
+
const totalPRs = ghStats ? ghStats.prsMerged : 0;
|
|
2976
|
+
const totalIssuesClosed = ghStats ? ghStats.issuesClosed : 0;
|
|
2977
|
+
const totalIssuesOpen = ghStats ? ghStats.issuesOpen : 0;
|
|
2978
|
+
writeLine();
|
|
2979
|
+
writeLine(` ${gradient("squads")} ${colors.dim}dashboard${RESET}`);
|
|
2980
|
+
writeLine();
|
|
2981
|
+
const stats = [
|
|
2982
|
+
`${colors.cyan}${activeSquads}${RESET}/${squadData.length} squads`,
|
|
2983
|
+
`${colors.green}${totalPRs}${RESET} PRs merged`,
|
|
2984
|
+
`${colors.purple}${totalIssuesClosed}${RESET} closed`,
|
|
2985
|
+
`${colors.yellow}${totalIssuesOpen}${RESET} open`
|
|
2986
|
+
].join(` ${colors.dim}\u2502${RESET} `);
|
|
2987
|
+
writeLine(` ${stats}`);
|
|
2988
|
+
writeLine();
|
|
2989
|
+
const overallProgress = squadData.length > 0 ? Math.round(squadData.reduce((sum, s) => sum + s.goalProgress, 0) / squadData.length) : 0;
|
|
2990
|
+
writeLine(` ${progressBar(overallProgress, 32)} ${colors.dim}${overallProgress}% goal progress${RESET}`);
|
|
2991
|
+
writeLine();
|
|
2992
|
+
const w = { name: 13, commits: 7, prs: 4, issues: 6, goals: 6, bar: 10 };
|
|
2993
|
+
const tableWidth = w.name + w.commits + w.prs + w.issues + w.goals + w.bar + 12;
|
|
2994
|
+
writeLine(` ${colors.purple}${box.topLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.topRight}${RESET}`);
|
|
2995
|
+
const header = ` ${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}`;
|
|
2996
|
+
writeLine(header);
|
|
2997
|
+
writeLine(` ${colors.purple}${box.teeRight}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.teeLeft}${RESET}`);
|
|
2998
|
+
const sortedSquads = [...squadData].sort((a, b) => {
|
|
2999
|
+
const aActivity = (a.github?.commits || 0) + (a.github?.prsMerged || 0) * 5;
|
|
3000
|
+
const bActivity = (b.github?.commits || 0) + (b.github?.prsMerged || 0) * 5;
|
|
3001
|
+
return bActivity - aActivity;
|
|
3002
|
+
});
|
|
3003
|
+
for (const squad of sortedSquads) {
|
|
3004
|
+
const gh = squad.github;
|
|
3005
|
+
const commits = gh?.commits || 0;
|
|
3006
|
+
const prs = gh?.prsMerged || 0;
|
|
3007
|
+
const issuesClosed = gh?.issuesClosed || 0;
|
|
3008
|
+
const issuesOpen = gh?.issuesOpen || 0;
|
|
3009
|
+
const activeCount = squad.goals.filter((g) => !g.completed).length;
|
|
3010
|
+
const totalCount = squad.goals.length;
|
|
3011
|
+
const commitColor = commits > 10 ? colors.green : commits > 0 ? colors.cyan : colors.dim;
|
|
3012
|
+
const prColor = prs > 0 ? colors.green : colors.dim;
|
|
3013
|
+
const issueColor = issuesClosed > 0 ? colors.green : colors.dim;
|
|
3014
|
+
const issuesDisplay = `${issuesClosed}/${issuesOpen}`;
|
|
3015
|
+
const row = ` ${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(issuesDisplay, w.issues)}${RESET}${padEnd(`${activeCount}/${totalCount}`, w.goals)}${progressBar(squad.goalProgress, 8)} ${colors.purple}${box.vertical}${RESET}`;
|
|
3016
|
+
writeLine(row);
|
|
3017
|
+
}
|
|
3018
|
+
writeLine(` ${colors.purple}${box.bottomLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.bottomRight}${RESET}`);
|
|
3019
|
+
writeLine();
|
|
3020
|
+
await renderGitPerformance();
|
|
3021
|
+
await renderTokenEconomics(squadData.map((s) => s.name));
|
|
3022
|
+
await renderHistoricalTrends();
|
|
3023
|
+
await renderInsights();
|
|
3024
|
+
const allActiveGoals = squadData.flatMap(
|
|
3025
|
+
(s) => s.goals.filter((g) => !g.completed).map((g) => ({ squad: s.name, goal: g }))
|
|
3026
|
+
);
|
|
3027
|
+
if (allActiveGoals.length > 0) {
|
|
3028
|
+
writeLine(` ${bold}Goals${RESET}`);
|
|
3029
|
+
writeLine();
|
|
3030
|
+
const maxGoals = 6;
|
|
3031
|
+
for (const { squad, goal: goal2 } of allActiveGoals.slice(0, maxGoals)) {
|
|
3032
|
+
const hasProgress = goal2.progress && goal2.progress.length > 0;
|
|
3033
|
+
const icon = hasProgress ? icons.progress : icons.empty;
|
|
3034
|
+
const squadLabel = `${colors.dim}${squad}${RESET}`;
|
|
3035
|
+
const goalText = truncate(goal2.description, 48);
|
|
3036
|
+
writeLine(` ${icon} ${squadLabel} ${goalText}`);
|
|
3037
|
+
if (hasProgress) {
|
|
3038
|
+
const progressText = truncate(goal2.progress, 52);
|
|
3039
|
+
writeLine(` ${colors.dim}\u2514${RESET} ${colors.green}${progressText}${RESET}`);
|
|
3040
|
+
}
|
|
3041
|
+
}
|
|
3042
|
+
if (allActiveGoals.length > maxGoals) {
|
|
3043
|
+
writeLine(` ${colors.dim} +${allActiveGoals.length - maxGoals} more${RESET}`);
|
|
3044
|
+
}
|
|
3045
|
+
writeLine();
|
|
3046
|
+
}
|
|
3047
|
+
writeLine(` ${colors.dim}$${RESET} squads run ${colors.cyan}<squad>${RESET} ${colors.dim}Execute a squad${RESET}`);
|
|
3048
|
+
writeLine(` ${colors.dim}$${RESET} squads goal set ${colors.dim}Add a goal${RESET}`);
|
|
3049
|
+
writeLine();
|
|
3050
|
+
await saveSnapshot(squadData, ghStats, baseDir);
|
|
3051
|
+
}
|
|
3052
|
+
async function saveSnapshot(squadData, ghStats, baseDir) {
|
|
3053
|
+
const dbAvailable = await isDatabaseAvailable();
|
|
3054
|
+
if (!dbAvailable) return;
|
|
3055
|
+
const gitStats = baseDir ? getMultiRepoGitStats(baseDir, 30) : null;
|
|
3056
|
+
const costs = await fetchCostSummary(100);
|
|
3057
|
+
const squadsData = squadData.map((s) => ({
|
|
3058
|
+
name: s.name,
|
|
3059
|
+
commits: s.github?.commits || 0,
|
|
3060
|
+
prsOpened: s.github?.prsOpened || 0,
|
|
3061
|
+
prsMerged: s.github?.prsMerged || 0,
|
|
3062
|
+
issuesClosed: s.github?.issuesClosed || 0,
|
|
3063
|
+
issuesOpen: s.github?.issuesOpen || 0,
|
|
3064
|
+
goalsActive: s.goals.filter((g) => !g.completed).length,
|
|
3065
|
+
goalsTotal: s.goals.length,
|
|
3066
|
+
progress: s.goalProgress
|
|
3067
|
+
}));
|
|
3068
|
+
const authorsData = gitStats ? Array.from(gitStats.commitsByAuthor.entries()).sort((a, b) => b[1] - a[1]).slice(0, 10).map(([name, commits]) => ({ name, commits })) : [];
|
|
3069
|
+
const reposData = gitStats ? Array.from(gitStats.commitsByRepo.entries()).sort((a, b) => b[1] - a[1]).map(([name, commits]) => ({ name, commits })) : [];
|
|
3070
|
+
const totalInputTokens = costs?.bySquad.reduce((sum, s) => sum + s.inputTokens, 0) || 0;
|
|
3071
|
+
const totalOutputTokens = costs?.bySquad.reduce((sum, s) => sum + s.outputTokens, 0) || 0;
|
|
3072
|
+
const overallProgress = squadData.length > 0 ? Math.round(squadData.reduce((sum, s) => sum + s.goalProgress, 0) / squadData.length) : 0;
|
|
3073
|
+
const snapshot = {
|
|
3074
|
+
totalSquads: squadData.length,
|
|
3075
|
+
totalCommits: gitStats?.totalCommits || 0,
|
|
3076
|
+
totalPrsMerged: ghStats?.prsMerged || 0,
|
|
3077
|
+
totalIssuesClosed: ghStats?.issuesClosed || 0,
|
|
3078
|
+
totalIssuesOpen: ghStats?.issuesOpen || 0,
|
|
3079
|
+
goalProgressPct: overallProgress,
|
|
3080
|
+
costUsd: costs?.totalCost || 0,
|
|
3081
|
+
dailyBudgetUsd: costs?.dailyBudget || 50,
|
|
3082
|
+
inputTokens: totalInputTokens,
|
|
3083
|
+
outputTokens: totalOutputTokens,
|
|
3084
|
+
commits30d: gitStats?.totalCommits || 0,
|
|
3085
|
+
avgCommitsPerDay: gitStats?.avgCommitsPerDay || 0,
|
|
3086
|
+
activeDays: gitStats?.activeDays || 0,
|
|
3087
|
+
peakCommits: gitStats?.peakDay?.count || 0,
|
|
3088
|
+
peakDate: gitStats?.peakDay?.date || null,
|
|
3089
|
+
squadsData,
|
|
3090
|
+
authorsData,
|
|
3091
|
+
reposData
|
|
3092
|
+
};
|
|
3093
|
+
await saveDashboardSnapshot(snapshot);
|
|
3094
|
+
}
|
|
3095
|
+
function findAgentsSquadsDir() {
|
|
3096
|
+
const candidates = [
|
|
3097
|
+
join9(process.cwd(), ".."),
|
|
3098
|
+
join9(homedir2(), "agents-squads")
|
|
3099
|
+
];
|
|
3100
|
+
for (const dir of candidates) {
|
|
3101
|
+
if (existsSync9(join9(dir, "hq"))) {
|
|
3102
|
+
return dir;
|
|
3103
|
+
}
|
|
3104
|
+
}
|
|
3105
|
+
return null;
|
|
3106
|
+
}
|
|
3107
|
+
async function renderGitPerformance() {
|
|
3108
|
+
const baseDir = findAgentsSquadsDir();
|
|
3109
|
+
if (!baseDir) {
|
|
3110
|
+
writeLine(` ${bold}Git Activity${RESET} ${colors.dim}(no repos found)${RESET}`);
|
|
3111
|
+
writeLine();
|
|
3112
|
+
return;
|
|
3113
|
+
}
|
|
3114
|
+
const stats = getMultiRepoGitStats(baseDir, 30);
|
|
3115
|
+
const activity = getActivitySparkline(baseDir, 14);
|
|
3116
|
+
if (stats.totalCommits === 0) {
|
|
3117
|
+
writeLine(` ${bold}Git Activity${RESET} ${colors.dim}(no commits in 30d)${RESET}`);
|
|
3118
|
+
writeLine();
|
|
3119
|
+
return;
|
|
3120
|
+
}
|
|
3121
|
+
writeLine(` ${bold}Git Activity${RESET} ${colors.dim}(30d)${RESET}`);
|
|
3122
|
+
writeLine();
|
|
3123
|
+
const spark = sparkline(activity);
|
|
3124
|
+
writeLine(` ${colors.dim}Last 14d:${RESET} ${spark}`);
|
|
3125
|
+
writeLine();
|
|
3126
|
+
const metrics = [
|
|
3127
|
+
`${colors.cyan}${stats.totalCommits}${RESET} commits`,
|
|
3128
|
+
`${colors.green}${stats.avgCommitsPerDay}${RESET}/day`,
|
|
3129
|
+
`${colors.purple}${stats.activeDays}${RESET} active days`
|
|
3130
|
+
];
|
|
3131
|
+
if (stats.peakDay) {
|
|
3132
|
+
metrics.push(`${colors.yellow}${stats.peakDay.count}${RESET} peak ${colors.dim}(${stats.peakDay.date})${RESET}`);
|
|
3133
|
+
}
|
|
3134
|
+
writeLine(` ${metrics.join(` ${colors.dim}\u2502${RESET} `)}`);
|
|
3135
|
+
writeLine();
|
|
3136
|
+
const sortedRepos = Array.from(stats.commitsByRepo.entries()).sort((a, b) => b[1] - a[1]).slice(0, 5);
|
|
3137
|
+
if (sortedRepos.length > 0) {
|
|
3138
|
+
const maxRepoCommits = sortedRepos[0][1];
|
|
3139
|
+
for (const [repo, commits] of sortedRepos) {
|
|
3140
|
+
const bar = barChart(commits, maxRepoCommits, 12);
|
|
3141
|
+
writeLine(` ${colors.cyan}${padEnd(repo, 20)}${RESET}${bar} ${colors.dim}${commits}${RESET}`);
|
|
3142
|
+
}
|
|
3143
|
+
writeLine();
|
|
3144
|
+
}
|
|
3145
|
+
const sortedAuthors = Array.from(stats.commitsByAuthor.entries()).sort((a, b) => b[1] - a[1]).slice(0, 3);
|
|
3146
|
+
if (sortedAuthors.length > 0) {
|
|
3147
|
+
const authorLine = sortedAuthors.map(([author, count]) => `${colors.dim}${truncate(author, 15)}${RESET} ${colors.cyan}${count}${RESET}`).join(` ${colors.dim}\u2502${RESET} `);
|
|
3148
|
+
writeLine(` ${colors.dim}By author:${RESET} ${authorLine}`);
|
|
3149
|
+
writeLine();
|
|
3150
|
+
}
|
|
3151
|
+
}
|
|
3152
|
+
async function renderTokenEconomics(squadNames) {
|
|
3153
|
+
const costs = await fetchCostSummary(100);
|
|
3154
|
+
if (!costs) {
|
|
3155
|
+
writeLine(` ${bold}Token Economics${RESET} ${colors.dim}(no data)${RESET}`);
|
|
3156
|
+
writeLine(` ${colors.dim}Set LANGFUSE_PUBLIC_KEY & LANGFUSE_SECRET_KEY for cost tracking${RESET}`);
|
|
3157
|
+
writeLine();
|
|
3158
|
+
return;
|
|
3159
|
+
}
|
|
3160
|
+
writeLine(` ${bold}Token Economics${RESET} ${colors.dim}(last 100 calls)${RESET}`);
|
|
3161
|
+
writeLine();
|
|
3162
|
+
const barWidth = 32;
|
|
3163
|
+
const costBar = formatCostBar(costs.usedPercent, barWidth);
|
|
3164
|
+
writeLine(` ${colors.dim}Budget $${costs.dailyBudget}${RESET} [${costBar}] ${costs.usedPercent.toFixed(1)}%`);
|
|
3165
|
+
writeLine(` ${colors.green}$${costs.totalCost.toFixed(2)}${RESET} used ${colors.dim}\u2502${RESET} ${colors.cyan}$${costs.idleBudget.toFixed(2)}${RESET} idle`);
|
|
3166
|
+
writeLine();
|
|
3167
|
+
const tier = parseInt(process.env.ANTHROPIC_TIER || "4", 10);
|
|
3168
|
+
const rpmByTier = { 1: 50, 2: 1e3, 3: 2e3, 4: 4e3 };
|
|
3169
|
+
const rpmLimit = rpmByTier[tier] || 4e3;
|
|
3170
|
+
const tokenLimits = {
|
|
3171
|
+
1: { opus: { itpm: 3e4, otpm: 8e3 }, sonnet: { itpm: 3e4, otpm: 8e3 }, haiku: { itpm: 5e4, otpm: 1e4 } },
|
|
3172
|
+
2: { opus: { itpm: 45e4, otpm: 9e4 }, sonnet: { itpm: 45e4, otpm: 9e4 }, haiku: { itpm: 45e4, otpm: 9e4 } },
|
|
3173
|
+
3: { opus: { itpm: 8e5, otpm: 16e4 }, sonnet: { itpm: 8e5, otpm: 16e4 }, haiku: { itpm: 1e6, otpm: 2e5 } },
|
|
3174
|
+
4: { opus: { itpm: 2e6, otpm: 4e5 }, sonnet: { itpm: 2e6, otpm: 4e5 }, haiku: { itpm: 4e6, otpm: 8e5 } }
|
|
3175
|
+
};
|
|
3176
|
+
const modelShortNames = {
|
|
3177
|
+
"claude-opus-4-5-20251101": "opus-4.5",
|
|
3178
|
+
"claude-sonnet-4-20250514": "sonnet-4",
|
|
3179
|
+
"claude-haiku-4-5-20251001": "haiku-4.5",
|
|
3180
|
+
"claude-3-5-sonnet-20241022": "sonnet-3.5",
|
|
3181
|
+
"claude-3-5-haiku-20241022": "haiku-3.5"
|
|
3182
|
+
};
|
|
3183
|
+
const modelToFamily = {
|
|
3184
|
+
"claude-opus-4-5-20251101": "opus",
|
|
3185
|
+
"claude-sonnet-4-20250514": "sonnet",
|
|
3186
|
+
"claude-haiku-4-5-20251001": "haiku",
|
|
3187
|
+
"claude-3-5-sonnet-20241022": "sonnet",
|
|
3188
|
+
"claude-3-5-haiku-20241022": "haiku"
|
|
3189
|
+
};
|
|
3190
|
+
const modelStats = {};
|
|
3191
|
+
for (const squad of costs.bySquad) {
|
|
3192
|
+
for (const [model, count] of Object.entries(squad.models)) {
|
|
3193
|
+
if (!modelStats[model]) {
|
|
3194
|
+
modelStats[model] = { calls: 0, input: 0, output: 0, cached: 0 };
|
|
3195
|
+
}
|
|
3196
|
+
modelStats[model].calls += count;
|
|
3197
|
+
}
|
|
3198
|
+
const totalCalls2 = Object.values(squad.models).reduce((a, b) => a + b, 0);
|
|
3199
|
+
for (const [model, count] of Object.entries(squad.models)) {
|
|
3200
|
+
const ratio = totalCalls2 > 0 ? count / totalCalls2 : 0;
|
|
3201
|
+
modelStats[model].input += Math.round(squad.inputTokens * ratio);
|
|
3202
|
+
modelStats[model].output += Math.round(squad.outputTokens * ratio);
|
|
3203
|
+
}
|
|
3204
|
+
}
|
|
3205
|
+
const totalInput = costs.bySquad.reduce((sum, s) => sum + s.inputTokens, 0);
|
|
3206
|
+
const totalOutput = costs.bySquad.reduce((sum, s) => sum + s.outputTokens, 0);
|
|
3207
|
+
const totalCalls = costs.bySquad.reduce((sum, s) => sum + s.calls, 0);
|
|
3208
|
+
const hourlyRate = costs.totalCost;
|
|
3209
|
+
const dailyProjection = hourlyRate * 24;
|
|
3210
|
+
const monthlyProjection = dailyProjection * 30;
|
|
3211
|
+
const rateLimits = await fetchRateLimits();
|
|
3212
|
+
const hasRealLimits = rateLimits.source === "proxy" && Object.keys(rateLimits.limits).length > 0;
|
|
3213
|
+
if (hasRealLimits) {
|
|
3214
|
+
writeLine(` ${colors.dim}Rate Limits${RESET} ${colors.green}(live)${RESET}`);
|
|
3215
|
+
for (const [family, limits] of Object.entries(rateLimits.limits)) {
|
|
3216
|
+
const name = family === "opus" ? "opus" : family === "sonnet" ? "sonnet" : family === "haiku" ? "haiku" : family;
|
|
3217
|
+
const reqUsed = limits.requestsLimit - limits.requestsRemaining;
|
|
3218
|
+
const reqPct = limits.requestsLimit > 0 ? reqUsed / limits.requestsLimit * 100 : 0;
|
|
3219
|
+
const reqColor = reqPct > 80 ? colors.red : reqPct > 50 ? colors.yellow : colors.green;
|
|
3220
|
+
const tokUsed = limits.tokensLimit - limits.tokensRemaining;
|
|
3221
|
+
const tokPct = limits.tokensLimit > 0 ? tokUsed / limits.tokensLimit * 100 : 0;
|
|
3222
|
+
const tokColor = tokPct > 80 ? colors.red : tokPct > 50 ? colors.yellow : colors.green;
|
|
3223
|
+
writeLine(` ${colors.cyan}${padEnd(name, 8)}${RESET} ${reqColor}${String(reqUsed).padStart(4)}${RESET}${colors.dim}/${limits.requestsLimit}req${RESET} ${tokColor}${formatK(tokUsed)}${RESET}${colors.dim}/${formatK(limits.tokensLimit)}tok${RESET}`);
|
|
3224
|
+
}
|
|
3225
|
+
} else {
|
|
3226
|
+
writeLine(` ${colors.dim}Rate Limits (Tier ${tier})${RESET}`);
|
|
3227
|
+
const sortedModels = Object.entries(modelStats).sort((a, b) => b[1].calls - a[1].calls);
|
|
3228
|
+
for (const [model, stats] of sortedModels.slice(0, 3)) {
|
|
3229
|
+
const name = modelShortNames[model] || model.split("-").slice(1, 3).join("-");
|
|
3230
|
+
const family = modelToFamily[model] || "sonnet";
|
|
3231
|
+
const limits = tokenLimits[tier]?.[family] || { itpm: 1e6, otpm: 2e5 };
|
|
3232
|
+
const rpmPct = stats.calls / rpmLimit * 100;
|
|
3233
|
+
const rpmColor = rpmPct > 80 ? colors.red : rpmPct > 50 ? colors.yellow : colors.green;
|
|
3234
|
+
writeLine(` ${colors.cyan}${padEnd(name, 11)}${RESET} ${rpmColor}${String(stats.calls).padStart(4)}${RESET}${colors.dim}rpm${RESET} ${colors.dim}${formatK(stats.input)}${RESET}${colors.dim}/${formatK(limits.itpm)}i${RESET} ${colors.dim}${formatK(stats.output)}${RESET}${colors.dim}/${formatK(limits.otpm)}o${RESET}`);
|
|
3235
|
+
}
|
|
3236
|
+
}
|
|
3237
|
+
writeLine();
|
|
3238
|
+
if (costs.totalCachedTokens > 0 || costs.cacheHitRate > 0) {
|
|
3239
|
+
const cacheColor = costs.cacheHitRate > 50 ? colors.green : costs.cacheHitRate > 20 ? colors.yellow : colors.red;
|
|
3240
|
+
writeLine(` ${colors.dim}Cache:${RESET} ${cacheColor}${costs.cacheHitRate.toFixed(1)}%${RESET} hit rate ${colors.dim}(${formatK(costs.totalCachedTokens)} cached / ${formatK(costs.totalInputTokens + costs.totalCachedTokens)} total)${RESET}`);
|
|
3241
|
+
writeLine();
|
|
3242
|
+
}
|
|
3243
|
+
writeLine(` ${colors.dim}Projections${RESET}`);
|
|
3244
|
+
const projColor = dailyProjection > costs.dailyBudget ? colors.red : colors.green;
|
|
3245
|
+
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}`);
|
|
3246
|
+
if (dailyProjection > costs.dailyBudget * 0.8) {
|
|
3247
|
+
writeLine(` ${colors.yellow}\u26A0${RESET} ${colors.yellow}Projected to exceed daily budget${RESET}`);
|
|
3248
|
+
}
|
|
3249
|
+
if (costs.usedPercent > 80) {
|
|
3250
|
+
writeLine(` ${colors.red}\u26A0${RESET} ${colors.red}${costs.usedPercent.toFixed(0)}% of daily budget used${RESET}`);
|
|
3251
|
+
}
|
|
3252
|
+
writeLine();
|
|
3253
|
+
}
|
|
3254
|
+
function formatK(n) {
|
|
3255
|
+
if (n >= 1e6) return (n / 1e6).toFixed(1) + "M";
|
|
3256
|
+
if (n >= 1e3) return (n / 1e3).toFixed(0) + "k";
|
|
3257
|
+
return String(n);
|
|
3258
|
+
}
|
|
3259
|
+
async function renderHistoricalTrends() {
|
|
3260
|
+
const dbAvailable = await isDatabaseAvailable();
|
|
3261
|
+
if (!dbAvailable) return;
|
|
3262
|
+
const history = await getDashboardHistory(14);
|
|
3263
|
+
if (history.length < 2) return;
|
|
3264
|
+
writeLine(` ${bold}Usage Trends${RESET} ${colors.dim}(${history.length}d history)${RESET}`);
|
|
3265
|
+
writeLine();
|
|
3266
|
+
const dailyCosts = history.map((h) => h.costUsd).reverse();
|
|
3267
|
+
const costSparkStr = sparkline(dailyCosts);
|
|
3268
|
+
const totalSpend = dailyCosts.reduce((sum, c) => sum + c, 0);
|
|
3269
|
+
const avgDaily = totalSpend / dailyCosts.length;
|
|
3270
|
+
writeLine(` ${colors.dim}Cost:${RESET} ${costSparkStr} ${colors.green}$${totalSpend.toFixed(2)}${RESET} total ${colors.dim}($${avgDaily.toFixed(2)}/day avg)${RESET}`);
|
|
3271
|
+
const inputTokens = history.map((h) => h.inputTokens).reverse();
|
|
3272
|
+
const totalInput = inputTokens.reduce((sum, t) => sum + t, 0);
|
|
3273
|
+
const tokenSparkStr = sparkline(inputTokens);
|
|
3274
|
+
writeLine(` ${colors.dim}Tokens:${RESET} ${tokenSparkStr} ${colors.cyan}${formatK(totalInput)}${RESET} input ${colors.dim}(${formatK(Math.round(totalInput / inputTokens.length))}/day)${RESET}`);
|
|
3275
|
+
const goalProgress = history.map((h) => h.goalProgressPct).reverse();
|
|
3276
|
+
const latestProgress = goalProgress[goalProgress.length - 1] || 0;
|
|
3277
|
+
const earliestProgress = goalProgress[0] || 0;
|
|
3278
|
+
const progressDelta = latestProgress - earliestProgress;
|
|
3279
|
+
const progressColor = progressDelta > 0 ? colors.green : progressDelta < 0 ? colors.red : colors.dim;
|
|
3280
|
+
const progressSign = progressDelta > 0 ? "+" : "";
|
|
3281
|
+
writeLine(` ${colors.dim}Goals:${RESET} ${sparkline(goalProgress)} ${colors.purple}${latestProgress}%${RESET} ${progressColor}${progressSign}${progressDelta.toFixed(0)}%${RESET}${colors.dim} vs start${RESET}`);
|
|
3282
|
+
writeLine();
|
|
3283
|
+
}
|
|
3284
|
+
async function renderInsights() {
|
|
3285
|
+
const insights = await fetchInsights("week");
|
|
3286
|
+
if (insights.source === "none" || insights.taskMetrics.length === 0) {
|
|
3287
|
+
return;
|
|
3288
|
+
}
|
|
3289
|
+
writeLine(` ${bold}Agent Insights${RESET} ${colors.dim}(${insights.days}d)${RESET}`);
|
|
3290
|
+
writeLine();
|
|
3291
|
+
const totals = insights.taskMetrics.reduce(
|
|
3292
|
+
(acc, t) => ({
|
|
3293
|
+
tasks: acc.tasks + t.tasksTotal,
|
|
3294
|
+
completed: acc.completed + t.tasksCompleted,
|
|
3295
|
+
failed: acc.failed + t.tasksFailed,
|
|
3296
|
+
retries: acc.retries + t.totalRetries,
|
|
3297
|
+
withRetries: acc.withRetries + t.tasksWithRetries
|
|
3298
|
+
}),
|
|
3299
|
+
{ tasks: 0, completed: 0, failed: 0, retries: 0, withRetries: 0 }
|
|
3300
|
+
);
|
|
3301
|
+
if (totals.tasks > 0) {
|
|
3302
|
+
const successRate = totals.tasks > 0 ? (totals.completed / totals.tasks * 100).toFixed(0) : "0";
|
|
3303
|
+
const successColor = parseInt(successRate) >= 80 ? colors.green : parseInt(successRate) >= 60 ? colors.yellow : colors.red;
|
|
3304
|
+
writeLine(` ${colors.dim}Tasks:${RESET} ${colors.green}${totals.completed}${RESET}${colors.dim}/${totals.tasks} completed${RESET} ${successColor}${successRate}%${RESET}${colors.dim} success${RESET} ${colors.red}${totals.failed}${RESET}${colors.dim} failed${RESET}`);
|
|
3305
|
+
if (totals.retries > 0) {
|
|
3306
|
+
const retryRate = totals.tasks > 0 ? (totals.withRetries / totals.tasks * 100).toFixed(0) : "0";
|
|
3307
|
+
const retryColor = parseInt(retryRate) > 30 ? colors.red : parseInt(retryRate) > 15 ? colors.yellow : colors.green;
|
|
3308
|
+
writeLine(` ${colors.dim}Retries:${RESET} ${retryColor}${totals.retries}${RESET}${colors.dim} total${RESET} ${retryColor}${retryRate}%${RESET}${colors.dim} of tasks needed retry${RESET}`);
|
|
3309
|
+
}
|
|
3310
|
+
}
|
|
3311
|
+
const qualityTotals = insights.qualityMetrics.reduce(
|
|
3312
|
+
(acc, q) => ({
|
|
3313
|
+
feedback: acc.feedback + q.feedbackCount,
|
|
3314
|
+
qualitySum: acc.qualitySum + q.avgQuality * q.feedbackCount,
|
|
3315
|
+
helpfulSum: acc.helpfulSum + q.helpfulPct * q.feedbackCount / 100,
|
|
3316
|
+
fixSum: acc.fixSum + q.fixRequiredPct * q.feedbackCount / 100
|
|
3317
|
+
}),
|
|
3318
|
+
{ feedback: 0, qualitySum: 0, helpfulSum: 0, fixSum: 0 }
|
|
3319
|
+
);
|
|
3320
|
+
if (qualityTotals.feedback > 0) {
|
|
3321
|
+
const avgQuality = qualityTotals.qualitySum / qualityTotals.feedback;
|
|
3322
|
+
const helpfulPct = qualityTotals.helpfulSum / qualityTotals.feedback * 100;
|
|
3323
|
+
const fixPct = qualityTotals.fixSum / qualityTotals.feedback * 100;
|
|
3324
|
+
const qualityColor = avgQuality >= 4 ? colors.green : avgQuality >= 3 ? colors.yellow : colors.red;
|
|
3325
|
+
const stars = "\u2605".repeat(Math.round(avgQuality)) + "\u2606".repeat(5 - Math.round(avgQuality));
|
|
3326
|
+
writeLine(` ${colors.dim}Quality:${RESET} ${qualityColor}${stars}${RESET} ${colors.dim}(${avgQuality.toFixed(1)}/5)${RESET} ${colors.green}${helpfulPct.toFixed(0)}%${RESET}${colors.dim} helpful${RESET} ${fixPct > 20 ? colors.red : colors.dim}${fixPct.toFixed(0)}% needed fixes${RESET}`);
|
|
3327
|
+
}
|
|
3328
|
+
const contextMetrics = insights.taskMetrics.filter((t) => t.avgContextPct > 0);
|
|
3329
|
+
if (contextMetrics.length > 0) {
|
|
3330
|
+
const avgContext = contextMetrics.reduce((sum, t) => sum + t.avgContextPct, 0) / contextMetrics.length;
|
|
3331
|
+
const maxContext = Math.max(...contextMetrics.map((t) => t.maxContextTokens));
|
|
3332
|
+
const contextColor = avgContext < 40 ? colors.green : avgContext < 70 ? colors.yellow : colors.red;
|
|
3333
|
+
const contextStatus = avgContext < 40 ? "lean" : avgContext < 70 ? "moderate" : "heavy";
|
|
3334
|
+
writeLine(` ${colors.dim}Context:${RESET} ${contextColor}${avgContext.toFixed(0)}%${RESET}${colors.dim} avg utilization (${contextStatus})${RESET} ${colors.dim}peak ${formatK(maxContext)} tokens${RESET}`);
|
|
3335
|
+
}
|
|
3336
|
+
writeLine();
|
|
3337
|
+
if (insights.topTools.length > 0) {
|
|
3338
|
+
const toolLine = insights.topTools.slice(0, 5).map((t) => {
|
|
3339
|
+
const successColor = t.successRate >= 95 ? colors.green : t.successRate >= 80 ? colors.yellow : colors.red;
|
|
3340
|
+
return `${colors.dim}${t.toolName.replace("mcp__", "").slice(0, 12)}${RESET} ${successColor}${t.successRate.toFixed(0)}%${RESET}`;
|
|
3341
|
+
}).join(" ");
|
|
3342
|
+
writeLine(` ${colors.dim}Tools:${RESET} ${toolLine}`);
|
|
3343
|
+
if (insights.toolFailureRate > 5) {
|
|
3344
|
+
writeLine(` ${colors.yellow}\u26A0${RESET} ${colors.yellow}${insights.toolFailureRate.toFixed(1)}% tool failure rate${RESET}`);
|
|
3345
|
+
}
|
|
3346
|
+
writeLine();
|
|
3347
|
+
}
|
|
3348
|
+
}
|
|
3349
|
+
var P0_KEYWORDS = ["revenue", "first", "launch", "publish", "ship", "critical", "urgent"];
|
|
3350
|
+
var P1_KEYWORDS = ["track", "establish", "identify", "define", "fix"];
|
|
3351
|
+
function inferPriority(goal2) {
|
|
3352
|
+
const lower = goal2.toLowerCase();
|
|
3353
|
+
if (P0_KEYWORDS.some((k) => lower.includes(k))) return "P0";
|
|
3354
|
+
if (P1_KEYWORDS.some((k) => lower.includes(k))) return "P1";
|
|
3355
|
+
return "P2";
|
|
3356
|
+
}
|
|
3357
|
+
async function renderCeoReport(squadsDir) {
|
|
3358
|
+
const squadNames = listSquads(squadsDir);
|
|
3359
|
+
const allGoals = [];
|
|
3360
|
+
const blockers = [];
|
|
3361
|
+
let activeSquads = 0;
|
|
3362
|
+
let staleSquads = 0;
|
|
3363
|
+
for (const name of squadNames) {
|
|
3364
|
+
const squad = loadSquad(name);
|
|
3365
|
+
if (!squad) continue;
|
|
3366
|
+
const lastActivity = getLastActivityDate(name);
|
|
3367
|
+
const activeGoals = squad.goals.filter((g) => !g.completed);
|
|
3368
|
+
if (activeGoals.length === 0) {
|
|
3369
|
+
blockers.push(`${name}: No active goals`);
|
|
3370
|
+
} else if (lastActivity.includes("w") || lastActivity === "\u2014") {
|
|
3371
|
+
blockers.push(`${name}: Stale (${lastActivity})`);
|
|
3372
|
+
staleSquads++;
|
|
3373
|
+
} else {
|
|
3374
|
+
activeSquads++;
|
|
3375
|
+
}
|
|
3376
|
+
for (const goal2 of activeGoals) {
|
|
3377
|
+
allGoals.push({
|
|
3378
|
+
squad: name,
|
|
3379
|
+
goal: goal2,
|
|
3380
|
+
priority: inferPriority(goal2.description)
|
|
3381
|
+
});
|
|
3382
|
+
}
|
|
3383
|
+
}
|
|
3384
|
+
allGoals.sort((a, b) => {
|
|
3385
|
+
const order = { P0: 0, P1: 1, P2: 2 };
|
|
3386
|
+
return order[a.priority] - order[b.priority];
|
|
3387
|
+
});
|
|
3388
|
+
const p0Goals = allGoals.filter((g) => g.priority === "P0");
|
|
3389
|
+
const p1Goals = allGoals.filter((g) => g.priority === "P1");
|
|
3390
|
+
writeLine();
|
|
3391
|
+
writeLine(` ${gradient("squads")} ${colors.dim}CEO Report${RESET}`);
|
|
3392
|
+
writeLine(` ${colors.dim}${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}${RESET}`);
|
|
3393
|
+
writeLine();
|
|
3394
|
+
const w = { label: 20, value: 12 };
|
|
3395
|
+
const tableWidth = w.label + w.value + 4;
|
|
3396
|
+
writeLine(` ${colors.purple}${box.topLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.topRight}${RESET}`);
|
|
3397
|
+
writeLine(` ${colors.purple}${box.vertical}${RESET} ${bold}${padEnd("METRIC", w.label)}${RESET}${bold}VALUE${RESET} ${colors.purple}${box.vertical}${RESET}`);
|
|
3398
|
+
writeLine(` ${colors.purple}${box.teeRight}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.teeLeft}${RESET}`);
|
|
3399
|
+
writeLine(` ${colors.purple}${box.vertical}${RESET} ${padEnd("Active Squads", w.label)}${colors.green}${padEnd(`${activeSquads}/${squadNames.length}`, w.value)}${RESET}${colors.purple}${box.vertical}${RESET}`);
|
|
3400
|
+
writeLine(` ${colors.purple}${box.vertical}${RESET} ${padEnd("P0 Goals", w.label)}${colors.red}${padEnd(String(p0Goals.length), w.value)}${RESET}${colors.purple}${box.vertical}${RESET}`);
|
|
3401
|
+
writeLine(` ${colors.purple}${box.vertical}${RESET} ${padEnd("P1 Goals", w.label)}${colors.yellow}${padEnd(String(p1Goals.length), w.value)}${RESET}${colors.purple}${box.vertical}${RESET}`);
|
|
3402
|
+
writeLine(` ${colors.purple}${box.vertical}${RESET} ${padEnd("Blockers", w.label)}${blockers.length > 0 ? colors.red : colors.green}${padEnd(String(blockers.length), w.value)}${RESET}${colors.purple}${box.vertical}${RESET}`);
|
|
3403
|
+
const costs = await fetchCostSummary(100);
|
|
3404
|
+
if (costs) {
|
|
3405
|
+
const spendStr = `$${costs.totalCost.toFixed(2)} / $${costs.dailyBudget}`;
|
|
3406
|
+
writeLine(` ${colors.purple}${box.vertical}${RESET} ${padEnd("Daily Spend", w.label)}${colors.green}${padEnd(spendStr, w.value)}${RESET}${colors.purple}${box.vertical}${RESET}`);
|
|
3407
|
+
}
|
|
3408
|
+
writeLine(` ${colors.purple}${box.bottomLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.bottomRight}${RESET}`);
|
|
3409
|
+
writeLine();
|
|
3410
|
+
if (p0Goals.length > 0) {
|
|
3411
|
+
writeLine(` ${bold}${colors.red}P0${RESET} ${bold}Priorities${RESET} ${colors.dim}(revenue/launch critical)${RESET}`);
|
|
3412
|
+
writeLine();
|
|
3413
|
+
for (const { squad, goal: goal2 } of p0Goals.slice(0, 5)) {
|
|
3414
|
+
writeLine(` ${icons.error} ${colors.cyan}${squad}${RESET} ${goal2.description}`);
|
|
3415
|
+
if (goal2.progress) {
|
|
3416
|
+
writeLine(` ${colors.dim}\u2514 ${goal2.progress}${RESET}`);
|
|
3417
|
+
}
|
|
3418
|
+
}
|
|
3419
|
+
writeLine();
|
|
3420
|
+
}
|
|
3421
|
+
if (p1Goals.length > 0) {
|
|
3422
|
+
writeLine(` ${bold}${colors.yellow}P1${RESET} ${bold}Important${RESET} ${colors.dim}(tracking/foundations)${RESET}`);
|
|
3423
|
+
writeLine();
|
|
3424
|
+
for (const { squad, goal: goal2 } of p1Goals.slice(0, 3)) {
|
|
3425
|
+
writeLine(` ${icons.warning} ${colors.cyan}${squad}${RESET} ${truncate(goal2.description, 50)}`);
|
|
3426
|
+
}
|
|
3427
|
+
if (p1Goals.length > 3) {
|
|
3428
|
+
writeLine(` ${colors.dim} +${p1Goals.length - 3} more${RESET}`);
|
|
3429
|
+
}
|
|
3430
|
+
writeLine();
|
|
3431
|
+
}
|
|
3432
|
+
if (blockers.length > 0) {
|
|
3433
|
+
writeLine(` ${bold}Blockers${RESET}`);
|
|
3434
|
+
writeLine();
|
|
3435
|
+
for (const blocker of blockers.slice(0, 3)) {
|
|
3436
|
+
writeLine(` ${icons.error} ${colors.red}${blocker}${RESET}`);
|
|
3437
|
+
}
|
|
3438
|
+
writeLine();
|
|
3439
|
+
}
|
|
3440
|
+
writeLine(` ${bold}Next Steps${RESET}`);
|
|
3441
|
+
writeLine();
|
|
3442
|
+
if (p0Goals.length > 0) {
|
|
3443
|
+
writeLine(` ${icons.active} Focus on P0: ${colors.cyan}${p0Goals[0].squad}${RESET} - ${truncate(p0Goals[0].goal.description, 40)}`);
|
|
3444
|
+
}
|
|
3445
|
+
if (blockers.length > 0) {
|
|
3446
|
+
writeLine(` ${icons.warning} Unblock: ${colors.yellow}${blockers[0]}${RESET}`);
|
|
3447
|
+
}
|
|
3448
|
+
if (staleSquads > 0) {
|
|
3449
|
+
writeLine(` ${icons.progress} Revive ${staleSquads} stale squad(s)`);
|
|
3450
|
+
}
|
|
3451
|
+
writeLine();
|
|
3452
|
+
writeLine(` ${colors.dim}$${RESET} squads dash ${colors.dim}Full operational view${RESET}`);
|
|
3453
|
+
writeLine(` ${colors.dim}$${RESET} squads goal list ${colors.dim}All active goals${RESET}`);
|
|
3454
|
+
writeLine();
|
|
3455
|
+
}
|
|
3456
|
+
|
|
3457
|
+
// src/commands/issues.ts
|
|
3458
|
+
import { execSync as execSync3 } from "child_process";
|
|
3459
|
+
var DEFAULT_ORG = "agents-squads";
|
|
3460
|
+
var DEFAULT_REPOS = ["hq", "agents-squads-web", "squads-cli"];
|
|
3461
|
+
async function issuesCommand(options = {}) {
|
|
3462
|
+
const org = options.org || DEFAULT_ORG;
|
|
3463
|
+
const repos = options.repos ? options.repos.split(",") : DEFAULT_REPOS;
|
|
3464
|
+
writeLine();
|
|
3465
|
+
writeLine(` ${gradient("squads")} ${colors.dim}issues${RESET}`);
|
|
3466
|
+
writeLine();
|
|
3467
|
+
try {
|
|
3468
|
+
execSync3("gh --version", { stdio: "pipe" });
|
|
3469
|
+
} catch {
|
|
3470
|
+
writeLine(` ${colors.red}GitHub CLI (gh) not found${RESET}`);
|
|
3471
|
+
writeLine(` ${colors.dim}Install: brew install gh${RESET}`);
|
|
3472
|
+
writeLine();
|
|
3473
|
+
return;
|
|
3474
|
+
}
|
|
3475
|
+
const repoData = [];
|
|
3476
|
+
let totalOpen = 0;
|
|
3477
|
+
for (const repo of repos) {
|
|
3478
|
+
try {
|
|
3479
|
+
const result = execSync3(
|
|
3480
|
+
`gh issue list -R ${org}/${repo} --state open --json number,title,state,labels,createdAt --limit 50`,
|
|
3481
|
+
{ stdio: "pipe", encoding: "utf-8" }
|
|
3482
|
+
);
|
|
3483
|
+
const issues = JSON.parse(result);
|
|
3484
|
+
repoData.push({ repo, issues });
|
|
3485
|
+
totalOpen += issues.length;
|
|
3486
|
+
} catch (e) {
|
|
3487
|
+
repoData.push({ repo, issues: [], error: "not found or no access" });
|
|
3488
|
+
}
|
|
3489
|
+
}
|
|
3490
|
+
const reposWithIssues = repoData.filter((r) => r.issues.length > 0).length;
|
|
3491
|
+
writeLine(` ${colors.cyan}${totalOpen}${RESET} open issues ${colors.dim}\u2502${RESET} ${reposWithIssues}/${repos.length} repos`);
|
|
3492
|
+
writeLine();
|
|
3493
|
+
const w = { repo: 20, open: 6, latest: 40 };
|
|
3494
|
+
const tableWidth = w.repo + w.open + w.latest + 4;
|
|
3495
|
+
writeLine(` ${colors.purple}${box.topLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.topRight}${RESET}`);
|
|
3496
|
+
const header = ` ${colors.purple}${box.vertical}${RESET} ${bold}${padEnd("REPO", w.repo)}${RESET}${bold}${padEnd("OPEN", w.open)}${RESET}${bold}LATEST${RESET} ${colors.purple}${box.vertical}${RESET}`;
|
|
3497
|
+
writeLine(header);
|
|
3498
|
+
writeLine(` ${colors.purple}${box.teeRight}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.teeLeft}${RESET}`);
|
|
3499
|
+
for (const { repo, issues, error } of repoData) {
|
|
3500
|
+
const count = issues.length;
|
|
3501
|
+
const countColor = count > 5 ? colors.red : count > 0 ? colors.yellow : colors.green;
|
|
3502
|
+
let latest = `${colors.dim}\u2014${RESET}`;
|
|
3503
|
+
if (error) {
|
|
3504
|
+
latest = `${colors.dim}${error}${RESET}`;
|
|
3505
|
+
} else if (issues.length > 0) {
|
|
3506
|
+
latest = truncate(issues[0].title, w.latest - 2);
|
|
3507
|
+
}
|
|
3508
|
+
const row = ` ${colors.purple}${box.vertical}${RESET} ${colors.cyan}${padEnd(repo, w.repo)}${RESET}${countColor}${padEnd(String(count), w.open)}${RESET}${padEnd(latest, w.latest + 10)}${colors.purple}${box.vertical}${RESET}`;
|
|
3509
|
+
writeLine(row);
|
|
3510
|
+
}
|
|
3511
|
+
writeLine(` ${colors.purple}${box.bottomLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.bottomRight}${RESET}`);
|
|
3512
|
+
writeLine();
|
|
3513
|
+
const allIssues = repoData.flatMap((r) => r.issues.map((i) => ({ ...i, repo: r.repo }))).sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()).slice(0, 6);
|
|
3514
|
+
if (allIssues.length > 0) {
|
|
3515
|
+
writeLine(` ${bold}Recent${RESET}`);
|
|
3516
|
+
writeLine();
|
|
3517
|
+
for (const issue of allIssues) {
|
|
3518
|
+
const labelStr = issue.labels.length > 0 ? `${colors.dim}[${issue.labels.map((l) => l.name || l).join(", ")}]${RESET}` : "";
|
|
3519
|
+
writeLine(` ${icons.empty} ${colors.dim}#${issue.number}${RESET} ${truncate(issue.title, 50)} ${labelStr}`);
|
|
3520
|
+
writeLine(` ${colors.dim}\u2514 ${issue.repo}${RESET}`);
|
|
3521
|
+
}
|
|
3522
|
+
writeLine();
|
|
3523
|
+
}
|
|
3524
|
+
writeLine(` ${colors.dim}$${RESET} gh issue list -R ${colors.cyan}${org}/<repo>${RESET} ${colors.dim}View repo issues${RESET}`);
|
|
3525
|
+
writeLine(` ${colors.dim}$${RESET} gh issue create -R ${colors.cyan}${org}/<repo>${RESET} ${colors.dim}Create issue${RESET}`);
|
|
3526
|
+
writeLine();
|
|
3527
|
+
}
|
|
3528
|
+
|
|
3529
|
+
// src/commands/solve-issues.ts
|
|
3530
|
+
import { execSync as execSync4, spawn as spawn2 } from "child_process";
|
|
3531
|
+
import ora3 from "ora";
|
|
3532
|
+
var DEFAULT_ORG2 = "agents-squads";
|
|
3533
|
+
var DEFAULT_REPOS2 = ["hq", "agents-squads-web", "squads-cli", "agents-squads"];
|
|
3534
|
+
async function solveIssuesCommand(options = {}) {
|
|
3535
|
+
const repos = options.repo ? [options.repo] : DEFAULT_REPOS2;
|
|
3536
|
+
writeLine();
|
|
3537
|
+
writeLine(` ${gradient("squads")} ${colors.dim}solve-issues${RESET}`);
|
|
3538
|
+
writeLine();
|
|
3539
|
+
try {
|
|
3540
|
+
execSync4("gh --version", { stdio: "pipe" });
|
|
3541
|
+
} catch {
|
|
3542
|
+
writeLine(` ${colors.red}GitHub CLI (gh) not found${RESET}`);
|
|
3543
|
+
writeLine(` ${colors.dim}Install: brew install gh${RESET}`);
|
|
3544
|
+
return;
|
|
3545
|
+
}
|
|
3546
|
+
const issuesToSolve = [];
|
|
3547
|
+
if (options.issue) {
|
|
3548
|
+
const repo = options.repo || "hq";
|
|
3549
|
+
try {
|
|
3550
|
+
const result = execSync4(
|
|
3551
|
+
`gh issue view ${options.issue} -R ${DEFAULT_ORG2}/${repo} --json number,title,labels,body`,
|
|
3552
|
+
{ stdio: "pipe", encoding: "utf-8" }
|
|
3553
|
+
);
|
|
3554
|
+
const issue = JSON.parse(result);
|
|
3555
|
+
issuesToSolve.push({ ...issue, repo });
|
|
3556
|
+
} catch {
|
|
3557
|
+
writeLine(` ${colors.red}Issue #${options.issue} not found in ${repo}${RESET}`);
|
|
3558
|
+
return;
|
|
3559
|
+
}
|
|
3560
|
+
} else {
|
|
3561
|
+
for (const repo of repos) {
|
|
3562
|
+
try {
|
|
3563
|
+
const result = execSync4(
|
|
3564
|
+
`gh issue list -R ${DEFAULT_ORG2}/${repo} --label "ready-to-fix" --state open --json number,title,labels --limit 20`,
|
|
3565
|
+
{ stdio: "pipe", encoding: "utf-8" }
|
|
3566
|
+
);
|
|
3567
|
+
const issues = JSON.parse(result);
|
|
3568
|
+
for (const issue of issues) {
|
|
3569
|
+
issuesToSolve.push({ ...issue, repo });
|
|
3570
|
+
}
|
|
3571
|
+
} catch {
|
|
3572
|
+
}
|
|
3573
|
+
}
|
|
3574
|
+
}
|
|
3575
|
+
if (issuesToSolve.length === 0) {
|
|
3576
|
+
writeLine(` ${colors.yellow}No issues with 'ready-to-fix' label found${RESET}`);
|
|
3577
|
+
writeLine();
|
|
3578
|
+
writeLine(` ${colors.dim}Label an issue to make it solvable:${RESET}`);
|
|
3579
|
+
writeLine(` ${colors.dim}$${RESET} gh issue edit ${colors.cyan}<number>${RESET} -R ${colors.cyan}${DEFAULT_ORG2}/<repo>${RESET} --add-label ready-to-fix`);
|
|
3580
|
+
writeLine();
|
|
3581
|
+
return;
|
|
3582
|
+
}
|
|
3583
|
+
writeLine(` ${colors.cyan}${issuesToSolve.length}${RESET} issue${issuesToSolve.length > 1 ? "s" : ""} to solve`);
|
|
3584
|
+
writeLine();
|
|
3585
|
+
for (const issue of issuesToSolve) {
|
|
3586
|
+
const labels = issue.labels.map((l) => l.name).join(", ");
|
|
3587
|
+
writeLine(` ${icons.empty} ${colors.dim}#${issue.number}${RESET} ${truncate(issue.title, 50)}`);
|
|
3588
|
+
writeLine(` ${colors.dim}\u2514 ${issue.repo} [${labels}]${RESET}`);
|
|
3589
|
+
}
|
|
3590
|
+
writeLine();
|
|
3591
|
+
if (options.dryRun) {
|
|
3592
|
+
writeLine(` ${colors.yellow}[DRY RUN] Would solve ${issuesToSolve.length} issues${RESET}`);
|
|
3593
|
+
return;
|
|
3594
|
+
}
|
|
3595
|
+
if (options.execute) {
|
|
3596
|
+
await solveWithClaude(issuesToSolve);
|
|
3597
|
+
} else {
|
|
3598
|
+
showSolveInstructions(issuesToSolve);
|
|
3599
|
+
}
|
|
3600
|
+
}
|
|
3601
|
+
function showSolveInstructions(issues) {
|
|
3602
|
+
writeLine(` ${bold}To solve these issues:${RESET}`);
|
|
3603
|
+
writeLine();
|
|
3604
|
+
writeLine(` ${colors.dim}Option 1: In Claude Code session${RESET}`);
|
|
3605
|
+
writeLine(` ${colors.dim}$${RESET} /solve-issue ${colors.cyan}${issues[0].repo}${RESET}`);
|
|
3606
|
+
writeLine();
|
|
3607
|
+
writeLine(` ${colors.dim}Option 2: Execute with Claude CLI${RESET}`);
|
|
3608
|
+
writeLine(` ${colors.dim}$${RESET} squads solve-issues --execute`);
|
|
3609
|
+
writeLine();
|
|
3610
|
+
writeLine(` ${colors.dim}Option 3: Solve specific issue${RESET}`);
|
|
3611
|
+
writeLine(` ${colors.dim}$${RESET} squads solve-issues --repo ${colors.cyan}${issues[0].repo}${RESET} --issue ${colors.cyan}${issues[0].number}${RESET} --execute`);
|
|
3612
|
+
writeLine();
|
|
3613
|
+
}
|
|
3614
|
+
async function solveWithClaude(issues) {
|
|
3615
|
+
const spinner = ora3("Starting issue solver...").start();
|
|
3616
|
+
try {
|
|
3617
|
+
execSync4("which claude", { stdio: "pipe" });
|
|
3618
|
+
} catch {
|
|
3619
|
+
spinner.fail("Claude CLI not found");
|
|
3620
|
+
writeLine(` ${colors.dim}Install: npm install -g @anthropic-ai/claude-code${RESET}`);
|
|
3621
|
+
return;
|
|
3622
|
+
}
|
|
3623
|
+
for (const issue of issues) {
|
|
3624
|
+
spinner.text = `Solving #${issue.number}: ${truncate(issue.title, 40)}`;
|
|
3625
|
+
const prompt = buildSolvePrompt(issue);
|
|
3626
|
+
try {
|
|
3627
|
+
const result = await executeClaudePrompt(prompt);
|
|
3628
|
+
spinner.succeed(`Solved #${issue.number}`);
|
|
3629
|
+
const prMatch = result.match(/https:\/\/github\.com\/[^\s]+\/pull\/\d+/);
|
|
3630
|
+
if (prMatch) {
|
|
3631
|
+
writeLine(` ${colors.green}PR: ${prMatch[0]}${RESET}`);
|
|
3632
|
+
}
|
|
3633
|
+
} catch (error) {
|
|
3634
|
+
spinner.fail(`Failed #${issue.number}: ${error}`);
|
|
3635
|
+
}
|
|
3636
|
+
}
|
|
3637
|
+
writeLine();
|
|
3638
|
+
writeLine(` ${colors.dim}View results:${RESET}`);
|
|
3639
|
+
writeLine(` ${colors.dim}$${RESET} gh pr list -R ${colors.cyan}${DEFAULT_ORG2}/<repo>${RESET}`);
|
|
3640
|
+
writeLine();
|
|
3641
|
+
}
|
|
3642
|
+
function buildSolvePrompt(issue) {
|
|
3643
|
+
return `Solve GitHub issue #${issue.number} in ${DEFAULT_ORG2}/${issue.repo}.
|
|
3644
|
+
|
|
3645
|
+
Issue: ${issue.title}
|
|
3646
|
+
|
|
3647
|
+
## CRITICAL: Work Decisively (No Verification Loops!)
|
|
3648
|
+
|
|
3649
|
+
Read each file ONCE, edit ONCE, commit IMMEDIATELY.
|
|
3650
|
+
Verify with git diff, NOT by re-reading files.
|
|
3651
|
+
If reading the same file twice, STOP and move forward.
|
|
3652
|
+
|
|
3653
|
+
## Instructions
|
|
3654
|
+
|
|
3655
|
+
1. Read the issue: gh issue view ${issue.number} --repo ${DEFAULT_ORG2}/${issue.repo}
|
|
3656
|
+
|
|
3657
|
+
2. Set up worktree:
|
|
3658
|
+
git fetch origin main
|
|
3659
|
+
git worktree add ../.worktrees/issue-${issue.number} -b solve/issue-${issue.number} origin/main
|
|
3660
|
+
cd ../.worktrees/issue-${issue.number}
|
|
3661
|
+
|
|
3662
|
+
3. Find and fix the problem (minimal changes)
|
|
3663
|
+
|
|
3664
|
+
4. Commit: git add -A && git commit -m "fix: ..."
|
|
3665
|
+
|
|
3666
|
+
5. Push and create PR:
|
|
3667
|
+
git push -u origin solve/issue-${issue.number}
|
|
3668
|
+
gh pr create --title "fix: ..." --body "Fixes #${issue.number}" --repo ${DEFAULT_ORG2}/${issue.repo}
|
|
3669
|
+
|
|
3670
|
+
6. Clean up: git worktree remove ../.worktrees/issue-${issue.number} --force
|
|
3671
|
+
|
|
3672
|
+
Return the PR URL when done.`;
|
|
3673
|
+
}
|
|
3674
|
+
function executeClaudePrompt(prompt) {
|
|
3675
|
+
return new Promise((resolve, reject) => {
|
|
3676
|
+
const claude = spawn2("claude", ["--print", prompt], {
|
|
3677
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
3678
|
+
});
|
|
3679
|
+
let output = "";
|
|
3680
|
+
let error = "";
|
|
3681
|
+
claude.stdout?.on("data", (data) => {
|
|
3682
|
+
output += data.toString();
|
|
3683
|
+
});
|
|
3684
|
+
claude.stderr?.on("data", (data) => {
|
|
3685
|
+
error += data.toString();
|
|
3686
|
+
});
|
|
3687
|
+
claude.on("close", (code) => {
|
|
3688
|
+
if (code === 0) {
|
|
3689
|
+
resolve(output);
|
|
3690
|
+
} else {
|
|
3691
|
+
reject(new Error(error || `Exited with code ${code}`));
|
|
3692
|
+
}
|
|
3693
|
+
});
|
|
3694
|
+
claude.on("error", reject);
|
|
3695
|
+
setTimeout(() => {
|
|
3696
|
+
claude.kill();
|
|
3697
|
+
reject(new Error("Timeout after 10 minutes"));
|
|
3698
|
+
}, 10 * 60 * 1e3);
|
|
3699
|
+
});
|
|
3700
|
+
}
|
|
3701
|
+
|
|
3702
|
+
// src/commands/open-issues.ts
|
|
3703
|
+
import { execSync as execSync5, spawn as spawn3 } from "child_process";
|
|
3704
|
+
import { readdirSync as readdirSync6 } from "fs";
|
|
3705
|
+
import { join as join10 } from "path";
|
|
3706
|
+
import ora4 from "ora";
|
|
3707
|
+
var ISSUE_FINDER_PATTERNS = [
|
|
3708
|
+
"*-eval.md",
|
|
3709
|
+
"*-critic.md",
|
|
3710
|
+
"*-auditor.md",
|
|
3711
|
+
"site-tester.md"
|
|
3712
|
+
];
|
|
3713
|
+
async function openIssuesCommand(options = {}) {
|
|
3714
|
+
writeLine();
|
|
3715
|
+
writeLine(` ${gradient("squads")} ${colors.dim}open-issues${RESET}`);
|
|
3716
|
+
writeLine();
|
|
3717
|
+
const squadsDir = findSquadsDir();
|
|
3718
|
+
if (!squadsDir) {
|
|
3719
|
+
writeLine(` ${colors.red}No .agents/squads directory found${RESET}`);
|
|
3720
|
+
return;
|
|
3721
|
+
}
|
|
3722
|
+
const evalAgents = findEvalAgents(squadsDir, options.squad);
|
|
3723
|
+
if (evalAgents.length === 0) {
|
|
3724
|
+
writeLine(` ${colors.yellow}No evaluator agents found${RESET}`);
|
|
3725
|
+
writeLine(` ${colors.dim}Evaluators match: *-eval.md, *-critic.md, *-auditor.md${RESET}`);
|
|
3726
|
+
return;
|
|
3727
|
+
}
|
|
3728
|
+
const agents = options.agent ? evalAgents.filter((a) => a.name === options.agent || a.name === `${options.agent}.md`) : evalAgents;
|
|
3729
|
+
if (agents.length === 0) {
|
|
3730
|
+
writeLine(` ${colors.red}Agent '${options.agent}' not found${RESET}`);
|
|
3731
|
+
writeLine(` ${colors.dim}Available: ${evalAgents.map((a) => a.name).join(", ")}${RESET}`);
|
|
3732
|
+
return;
|
|
3733
|
+
}
|
|
3734
|
+
const bySquad = agents.reduce((acc, agent) => {
|
|
3735
|
+
if (!acc[agent.squad]) acc[agent.squad] = [];
|
|
3736
|
+
acc[agent.squad].push(agent);
|
|
3737
|
+
return acc;
|
|
3738
|
+
}, {});
|
|
3739
|
+
writeLine(` ${colors.cyan}${agents.length}${RESET} evaluator${agents.length > 1 ? "s" : ""} ready`);
|
|
3740
|
+
writeLine();
|
|
3741
|
+
for (const [squad, squadAgents] of Object.entries(bySquad)) {
|
|
3742
|
+
writeLine(` ${bold}${squad}${RESET}`);
|
|
3743
|
+
for (const agent of squadAgents) {
|
|
3744
|
+
writeLine(` ${icons.empty} ${colors.cyan}${agent.name.replace(".md", "")}${RESET}`);
|
|
3745
|
+
}
|
|
3746
|
+
}
|
|
3747
|
+
writeLine();
|
|
3748
|
+
if (options.dryRun) {
|
|
3749
|
+
writeLine(` ${colors.yellow}[DRY RUN] Would run ${agents.length} evaluators${RESET}`);
|
|
3750
|
+
return;
|
|
3751
|
+
}
|
|
3752
|
+
if (options.execute) {
|
|
3753
|
+
await runEvaluators(agents);
|
|
3754
|
+
} else {
|
|
3755
|
+
showRunInstructions(agents);
|
|
3756
|
+
}
|
|
3757
|
+
}
|
|
3758
|
+
function findEvalAgents(squadsDir, filterSquad) {
|
|
3759
|
+
const agents = [];
|
|
3760
|
+
const squads = readdirSync6(squadsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).filter((d) => !filterSquad || d.name === filterSquad).map((d) => d.name);
|
|
3761
|
+
for (const squad of squads) {
|
|
3762
|
+
const squadPath = join10(squadsDir, squad);
|
|
3763
|
+
const files = readdirSync6(squadPath).filter((f) => f.endsWith(".md"));
|
|
3764
|
+
for (const file of files) {
|
|
3765
|
+
const isEval = ISSUE_FINDER_PATTERNS.some((pattern) => {
|
|
3766
|
+
const regex = new RegExp("^" + pattern.replace("*", ".*") + "$");
|
|
3767
|
+
return regex.test(file);
|
|
3768
|
+
});
|
|
3769
|
+
if (isEval) {
|
|
3770
|
+
agents.push({
|
|
3771
|
+
name: file,
|
|
3772
|
+
squad,
|
|
3773
|
+
path: join10(squadPath, file)
|
|
3774
|
+
});
|
|
3775
|
+
}
|
|
3776
|
+
}
|
|
3777
|
+
}
|
|
3778
|
+
return agents;
|
|
3779
|
+
}
|
|
3780
|
+
function showRunInstructions(agents) {
|
|
3781
|
+
writeLine(` ${bold}To run evaluators:${RESET}`);
|
|
3782
|
+
writeLine();
|
|
3783
|
+
writeLine(` ${colors.dim}Option 1: In Claude Code session${RESET}`);
|
|
3784
|
+
writeLine(` ${colors.dim}$${RESET} /website-eval`);
|
|
3785
|
+
writeLine();
|
|
3786
|
+
writeLine(` ${colors.dim}Option 2: Execute with Claude CLI${RESET}`);
|
|
3787
|
+
writeLine(` ${colors.dim}$${RESET} squads open-issues --execute`);
|
|
3788
|
+
writeLine();
|
|
3789
|
+
writeLine(` ${colors.dim}Option 3: Run specific evaluator${RESET}`);
|
|
3790
|
+
writeLine(` ${colors.dim}$${RESET} squads open-issues --squad ${colors.cyan}${agents[0].squad}${RESET} --agent ${colors.cyan}${agents[0].name.replace(".md", "")}${RESET} --execute`);
|
|
3791
|
+
writeLine();
|
|
3792
|
+
writeLine(` ${colors.dim}Option 4: Via squads run${RESET}`);
|
|
3793
|
+
writeLine(` ${colors.dim}$${RESET} squads run ${colors.cyan}${agents[0].squad}/${agents[0].name.replace(".md", "")}${RESET} --execute`);
|
|
3794
|
+
writeLine();
|
|
3795
|
+
}
|
|
3796
|
+
async function runEvaluators(agents) {
|
|
3797
|
+
const spinner = ora4("Starting evaluators...").start();
|
|
3798
|
+
try {
|
|
3799
|
+
execSync5("which claude", { stdio: "pipe" });
|
|
3800
|
+
} catch {
|
|
3801
|
+
spinner.fail("Claude CLI not found");
|
|
3802
|
+
writeLine(` ${colors.dim}Install: npm install -g @anthropic-ai/claude-code${RESET}`);
|
|
3803
|
+
return;
|
|
3804
|
+
}
|
|
3805
|
+
let issuesCreated = 0;
|
|
3806
|
+
for (const agent of agents) {
|
|
3807
|
+
spinner.text = `Running ${agent.squad}/${agent.name.replace(".md", "")}...`;
|
|
3808
|
+
const prompt = buildEvalPrompt(agent);
|
|
3809
|
+
try {
|
|
3810
|
+
const result = await executeClaudePrompt2(prompt);
|
|
3811
|
+
spinner.succeed(`${agent.name.replace(".md", "")}`);
|
|
3812
|
+
const issueMatches = result.match(/Created issue #\d+/g) || [];
|
|
3813
|
+
issuesCreated += issueMatches.length;
|
|
3814
|
+
if (issueMatches.length > 0) {
|
|
3815
|
+
writeLine(` ${colors.green}Created ${issueMatches.length} issue(s)${RESET}`);
|
|
3816
|
+
} else {
|
|
3817
|
+
writeLine(` ${colors.dim}No issues found${RESET}`);
|
|
3818
|
+
}
|
|
3819
|
+
} catch (error) {
|
|
3820
|
+
spinner.fail(`${agent.name.replace(".md", "")}: ${error}`);
|
|
3821
|
+
}
|
|
3822
|
+
}
|
|
3823
|
+
writeLine();
|
|
3824
|
+
writeLine(` ${bold}Summary${RESET}`);
|
|
3825
|
+
writeLine(` ${colors.cyan}${issuesCreated}${RESET} new issues created`);
|
|
3826
|
+
writeLine();
|
|
3827
|
+
writeLine(` ${colors.dim}View issues:${RESET}`);
|
|
3828
|
+
writeLine(` ${colors.dim}$${RESET} squads issues`);
|
|
3829
|
+
writeLine();
|
|
3830
|
+
writeLine(` ${colors.dim}Solve them:${RESET}`);
|
|
3831
|
+
writeLine(` ${colors.dim}$${RESET} squads solve-issues`);
|
|
3832
|
+
writeLine();
|
|
3833
|
+
}
|
|
3834
|
+
function buildEvalPrompt(agent) {
|
|
3835
|
+
return `Execute the ${agent.name.replace(".md", "")} evaluator from squad ${agent.squad}.
|
|
3836
|
+
|
|
3837
|
+
Read the agent definition at ${agent.path} and follow its instructions exactly.
|
|
3838
|
+
|
|
3839
|
+
## CRITICAL: Work Decisively
|
|
3840
|
+
|
|
3841
|
+
- Evaluate the target (website, code, etc.)
|
|
3842
|
+
- For each finding, create a GitHub issue
|
|
3843
|
+
- Use: gh issue create --repo agents-squads/{repo} --title "..." --body "..." --label "type:...,priority:P1/P2/P3,squad:${agent.squad}"
|
|
3844
|
+
- Report how many issues were created
|
|
3845
|
+
|
|
3846
|
+
Do NOT get stuck re-reading files. Evaluate, report findings, create issues, done.`;
|
|
3847
|
+
}
|
|
3848
|
+
function executeClaudePrompt2(prompt) {
|
|
3849
|
+
return new Promise((resolve, reject) => {
|
|
3850
|
+
const claude = spawn3("claude", ["--print", prompt], {
|
|
3851
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
3852
|
+
});
|
|
3853
|
+
let output = "";
|
|
3854
|
+
let error = "";
|
|
3855
|
+
claude.stdout?.on("data", (data) => {
|
|
3856
|
+
output += data.toString();
|
|
3857
|
+
});
|
|
3858
|
+
claude.stderr?.on("data", (data) => {
|
|
3859
|
+
error += data.toString();
|
|
3860
|
+
});
|
|
3861
|
+
claude.on("close", (code) => {
|
|
3862
|
+
if (code === 0) {
|
|
3863
|
+
resolve(output);
|
|
3864
|
+
} else {
|
|
3865
|
+
reject(new Error(error || `Exited with code ${code}`));
|
|
3866
|
+
}
|
|
3867
|
+
});
|
|
3868
|
+
claude.on("error", reject);
|
|
3869
|
+
setTimeout(() => {
|
|
3870
|
+
claude.kill();
|
|
3871
|
+
reject(new Error("Timeout after 5 minutes"));
|
|
3872
|
+
}, 5 * 60 * 1e3);
|
|
3873
|
+
});
|
|
3874
|
+
}
|
|
3875
|
+
|
|
3876
|
+
// src/commands/login.ts
|
|
3877
|
+
import chalk2 from "chalk";
|
|
3878
|
+
import ora5 from "ora";
|
|
3879
|
+
import open from "open";
|
|
3880
|
+
|
|
3881
|
+
// src/lib/auth.ts
|
|
3882
|
+
import { createClient } from "@supabase/supabase-js";
|
|
3883
|
+
import { existsSync as existsSync11, readFileSync as readFileSync8, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6 } from "fs";
|
|
3884
|
+
import { join as join11 } from "path";
|
|
3885
|
+
import { homedir as homedir3 } from "os";
|
|
3886
|
+
import http from "http";
|
|
3887
|
+
var PERSONAL_DOMAINS = [
|
|
3888
|
+
"gmail.com",
|
|
3889
|
+
"googlemail.com",
|
|
3890
|
+
"yahoo.com",
|
|
3891
|
+
"yahoo.co.uk",
|
|
3892
|
+
"yahoo.fr",
|
|
3893
|
+
"hotmail.com",
|
|
3894
|
+
"outlook.com",
|
|
3895
|
+
"live.com",
|
|
3896
|
+
"msn.com",
|
|
3897
|
+
"icloud.com",
|
|
3898
|
+
"me.com",
|
|
3899
|
+
"mac.com",
|
|
3900
|
+
"aol.com",
|
|
3901
|
+
"protonmail.com",
|
|
3902
|
+
"proton.me",
|
|
3903
|
+
"zoho.com",
|
|
3904
|
+
"mail.com",
|
|
3905
|
+
"yandex.com",
|
|
3906
|
+
"yandex.ru",
|
|
3907
|
+
"gmx.com",
|
|
3908
|
+
"gmx.de",
|
|
3909
|
+
"fastmail.com",
|
|
3910
|
+
"tutanota.com",
|
|
3911
|
+
"hey.com"
|
|
3912
|
+
];
|
|
3913
|
+
var AUTH_DIR = join11(homedir3(), ".squads-cli");
|
|
3914
|
+
var AUTH_PATH = join11(AUTH_DIR, "auth.json");
|
|
3915
|
+
function isPersonalEmail(email) {
|
|
3916
|
+
const domain = email.split("@")[1]?.toLowerCase();
|
|
3917
|
+
return PERSONAL_DOMAINS.includes(domain);
|
|
3918
|
+
}
|
|
3919
|
+
function getEmailDomain(email) {
|
|
3920
|
+
return email.split("@")[1]?.toLowerCase() || "";
|
|
3921
|
+
}
|
|
3922
|
+
function saveSession(session) {
|
|
3923
|
+
if (!existsSync11(AUTH_DIR)) {
|
|
3924
|
+
mkdirSync6(AUTH_DIR, { recursive: true });
|
|
3925
|
+
}
|
|
3926
|
+
writeFileSync7(AUTH_PATH, JSON.stringify(session, null, 2));
|
|
3927
|
+
}
|
|
3928
|
+
function loadSession() {
|
|
3929
|
+
if (!existsSync11(AUTH_PATH)) return null;
|
|
3930
|
+
try {
|
|
3931
|
+
return JSON.parse(readFileSync8(AUTH_PATH, "utf-8"));
|
|
3932
|
+
} catch {
|
|
3933
|
+
return null;
|
|
3934
|
+
}
|
|
3935
|
+
}
|
|
3936
|
+
function clearSession() {
|
|
3937
|
+
if (existsSync11(AUTH_PATH)) {
|
|
3938
|
+
writeFileSync7(AUTH_PATH, "");
|
|
3939
|
+
}
|
|
3940
|
+
}
|
|
3941
|
+
function startAuthCallbackServer(port = 54321) {
|
|
3942
|
+
return new Promise((resolve, reject) => {
|
|
3943
|
+
const server = http.createServer((req, res) => {
|
|
3944
|
+
const url = new URL(req.url || "", `http://localhost:${port}`);
|
|
3945
|
+
if (url.pathname === "/callback") {
|
|
3946
|
+
const email = url.searchParams.get("email");
|
|
3947
|
+
const token = url.searchParams.get("token");
|
|
3948
|
+
const error = url.searchParams.get("error");
|
|
3949
|
+
if (error) {
|
|
3950
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
3951
|
+
res.end(`
|
|
3952
|
+
<html>
|
|
3953
|
+
<body style="font-family: system-ui; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0;">
|
|
3954
|
+
<div style="text-align: center;">
|
|
3955
|
+
<h1 style="color: #ef4444;">\u274C ${error}</h1>
|
|
3956
|
+
<p>You can close this window.</p>
|
|
3957
|
+
</div>
|
|
3958
|
+
</body>
|
|
3959
|
+
</html>
|
|
3960
|
+
`);
|
|
3961
|
+
server.close();
|
|
3962
|
+
reject(new Error(error));
|
|
3963
|
+
return;
|
|
3964
|
+
}
|
|
3965
|
+
if (email && token) {
|
|
3966
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
3967
|
+
res.end(`
|
|
3968
|
+
<html>
|
|
3969
|
+
<body style="font-family: system-ui; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0;">
|
|
3970
|
+
<div style="text-align: center;">
|
|
3971
|
+
<h1 style="color: #10b981;">\u2713 Logged in!</h1>
|
|
3972
|
+
<p>Welcome, ${email}</p>
|
|
3973
|
+
<p style="color: #6b7280;">You can close this window and return to your terminal.</p>
|
|
3974
|
+
</div>
|
|
3975
|
+
</body>
|
|
3976
|
+
</html>
|
|
3977
|
+
`);
|
|
3978
|
+
server.close();
|
|
3979
|
+
resolve({ email, token });
|
|
3980
|
+
} else {
|
|
3981
|
+
res.writeHead(400, { "Content-Type": "text/plain" });
|
|
3982
|
+
res.end("Missing email or token");
|
|
3983
|
+
}
|
|
3984
|
+
} else {
|
|
3985
|
+
res.writeHead(404);
|
|
3986
|
+
res.end();
|
|
3987
|
+
}
|
|
3988
|
+
});
|
|
3989
|
+
server.listen(port, () => {
|
|
3990
|
+
});
|
|
3991
|
+
setTimeout(() => {
|
|
3992
|
+
server.close();
|
|
3993
|
+
reject(new Error("Login timed out"));
|
|
3994
|
+
}, 5 * 60 * 1e3);
|
|
3995
|
+
});
|
|
3996
|
+
}
|
|
3997
|
+
|
|
3998
|
+
// src/commands/login.ts
|
|
3999
|
+
var AUTH_URL = process.env.SQUADS_AUTH_URL || "https://app.agents-squads.com/auth";
|
|
4000
|
+
var CALLBACK_PORT = 54321;
|
|
4001
|
+
async function loginCommand() {
|
|
4002
|
+
const existingSession = loadSession();
|
|
4003
|
+
if (existingSession && existingSession.status === "active") {
|
|
4004
|
+
console.log(chalk2.green(`\u2713 Already logged in as ${existingSession.email}`));
|
|
4005
|
+
console.log(chalk2.dim(` Domain: ${existingSession.domain}`));
|
|
4006
|
+
console.log(chalk2.dim(` Run 'squads logout' to sign out.`));
|
|
4007
|
+
return;
|
|
4008
|
+
}
|
|
4009
|
+
console.log(`
|
|
4010
|
+
${chalk2.bold.magenta("Squads CLI Login")}
|
|
4011
|
+
${chalk2.dim("\u2500".repeat(40))}
|
|
4012
|
+
|
|
4013
|
+
Opening browser to authenticate...
|
|
4014
|
+
`);
|
|
4015
|
+
const spinner = ora5("Waiting for authentication...").start();
|
|
4016
|
+
try {
|
|
4017
|
+
const callbackPromise = startAuthCallbackServer(CALLBACK_PORT);
|
|
4018
|
+
const authUrl = `${AUTH_URL}?callback=http://localhost:${CALLBACK_PORT}/callback`;
|
|
4019
|
+
await open(authUrl);
|
|
4020
|
+
const { email, token } = await callbackPromise;
|
|
4021
|
+
if (isPersonalEmail(email)) {
|
|
4022
|
+
spinner.fail("Personal emails not supported");
|
|
4023
|
+
console.log(`
|
|
4024
|
+
${chalk2.yellow("\u26A0 Squads CLI is for Pro & Enterprise teams only.")}
|
|
4025
|
+
|
|
4026
|
+
Personal email domains (Gmail, Yahoo, etc.) are not supported.
|
|
4027
|
+
|
|
4028
|
+
${chalk2.dim("Want to stay updated?")}
|
|
4029
|
+
\u2192 Get our free research report: ${chalk2.cyan("https://agents-squads.com/research")}
|
|
4030
|
+
\u2192 Follow us: ${chalk2.cyan("https://x.com/agents_squads")}
|
|
4031
|
+
`);
|
|
4032
|
+
await track("cli.login.personal_email", { domain: getEmailDomain(email) });
|
|
4033
|
+
return;
|
|
4034
|
+
}
|
|
4035
|
+
const session = {
|
|
4036
|
+
email,
|
|
4037
|
+
domain: getEmailDomain(email),
|
|
4038
|
+
status: "pending",
|
|
4039
|
+
// Will be 'active' after sales contact
|
|
4040
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4041
|
+
accessToken: token
|
|
4042
|
+
};
|
|
4043
|
+
saveSession(session);
|
|
4044
|
+
spinner.succeed(`Logged in as ${chalk2.cyan(email)}`);
|
|
4045
|
+
await track("cli.login.success", { domain: session.domain });
|
|
4046
|
+
console.log(`
|
|
4047
|
+
${chalk2.green("\u2713 Thanks for signing up!")}
|
|
4048
|
+
|
|
4049
|
+
${chalk2.bold("What happens next:")}
|
|
4050
|
+
1. Our team will reach out within 24 hours
|
|
4051
|
+
2. We'll discuss your AI agent needs
|
|
4052
|
+
3. You'll get access to Pro features
|
|
4053
|
+
|
|
4054
|
+
${chalk2.dim("In the meantime:")}
|
|
4055
|
+
\u2192 Explore squads: ${chalk2.cyan("squads status")}
|
|
4056
|
+
\u2192 Set goals: ${chalk2.cyan('squads goal set <squad> "<goal>"')}
|
|
4057
|
+
\u2192 Read our research: ${chalk2.cyan("https://agents-squads.com/research")}
|
|
4058
|
+
|
|
4059
|
+
${chalk2.dim("Questions? Email us at")} ${chalk2.cyan("hello@agents-squads.com")}
|
|
4060
|
+
`);
|
|
4061
|
+
} catch (error) {
|
|
4062
|
+
spinner.fail("Login failed");
|
|
4063
|
+
console.error(chalk2.red(error instanceof Error ? error.message : "Unknown error"));
|
|
4064
|
+
}
|
|
4065
|
+
}
|
|
4066
|
+
async function logoutCommand() {
|
|
4067
|
+
const session = loadSession();
|
|
4068
|
+
if (!session) {
|
|
4069
|
+
console.log(chalk2.yellow("Not logged in."));
|
|
4070
|
+
return;
|
|
4071
|
+
}
|
|
4072
|
+
clearSession();
|
|
4073
|
+
console.log(chalk2.green(`\u2713 Logged out from ${session.email}`));
|
|
4074
|
+
await track("cli.logout");
|
|
4075
|
+
}
|
|
4076
|
+
async function whoamiCommand() {
|
|
4077
|
+
const session = loadSession();
|
|
4078
|
+
if (!session) {
|
|
4079
|
+
console.log(chalk2.yellow("Not logged in."));
|
|
4080
|
+
console.log(chalk2.dim("Run: squads login"));
|
|
4081
|
+
return;
|
|
4082
|
+
}
|
|
4083
|
+
console.log(`
|
|
4084
|
+
${chalk2.bold("Current Session")}
|
|
4085
|
+
${chalk2.dim("\u2500".repeat(30))}
|
|
4086
|
+
Email: ${chalk2.cyan(session.email)}
|
|
4087
|
+
Domain: ${session.domain}
|
|
4088
|
+
Status: ${session.status === "active" ? chalk2.green("Active") : chalk2.yellow("Pending")}
|
|
4089
|
+
Since: ${new Date(session.createdAt).toLocaleDateString()}
|
|
4090
|
+
`);
|
|
4091
|
+
}
|
|
4092
|
+
|
|
4093
|
+
// src/commands/progress.ts
|
|
4094
|
+
import { execSync as execSync6 } from "child_process";
|
|
4095
|
+
import { existsSync as existsSync12, readFileSync as readFileSync9, writeFileSync as writeFileSync8, mkdirSync as mkdirSync7 } from "fs";
|
|
4096
|
+
import { join as join12 } from "path";
|
|
4097
|
+
function getTasksFilePath() {
|
|
4098
|
+
const memoryDir = findMemoryDir();
|
|
4099
|
+
if (!memoryDir) {
|
|
4100
|
+
const cwd = process.cwd();
|
|
4101
|
+
const agentsDir = join12(cwd, ".agents");
|
|
4102
|
+
if (!existsSync12(agentsDir)) {
|
|
4103
|
+
mkdirSync7(agentsDir, { recursive: true });
|
|
4104
|
+
}
|
|
4105
|
+
return join12(agentsDir, "tasks.json");
|
|
4106
|
+
}
|
|
4107
|
+
return join12(memoryDir, "..", "tasks.json");
|
|
4108
|
+
}
|
|
4109
|
+
function loadTasks() {
|
|
4110
|
+
const tasksPath = getTasksFilePath();
|
|
4111
|
+
if (existsSync12(tasksPath)) {
|
|
4112
|
+
try {
|
|
4113
|
+
return JSON.parse(readFileSync9(tasksPath, "utf-8"));
|
|
4114
|
+
} catch {
|
|
4115
|
+
return { tasks: [], lastUpdated: (/* @__PURE__ */ new Date()).toISOString() };
|
|
4116
|
+
}
|
|
4117
|
+
}
|
|
4118
|
+
return { tasks: [], lastUpdated: (/* @__PURE__ */ new Date()).toISOString() };
|
|
4119
|
+
}
|
|
4120
|
+
function saveTasks(data) {
|
|
4121
|
+
const tasksPath = getTasksFilePath();
|
|
4122
|
+
data.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
|
|
4123
|
+
writeFileSync8(tasksPath, JSON.stringify(data, null, 2));
|
|
4124
|
+
}
|
|
4125
|
+
function getRecentActivity() {
|
|
4126
|
+
const activity = [];
|
|
4127
|
+
const squadKeywords = {
|
|
4128
|
+
website: ["website", "web", "homepage", "astro", "page"],
|
|
4129
|
+
product: ["cli", "squads-cli", "command"],
|
|
4130
|
+
research: ["research", "report", "analysis"],
|
|
4131
|
+
engineering: ["infra", "engineering", "build"],
|
|
4132
|
+
intelligence: ["intel", "monitor", "competitor"],
|
|
4133
|
+
customer: ["lead", "customer", "outreach"],
|
|
4134
|
+
finance: ["cost", "finance", "budget"],
|
|
4135
|
+
company: ["company", "strategy", "mission"],
|
|
4136
|
+
marketing: ["marketing", "content", "social"]
|
|
4137
|
+
};
|
|
4138
|
+
try {
|
|
4139
|
+
const logOutput = execSync6(
|
|
4140
|
+
'git log --since="24 hours ago" --format="%h|%aI|%s" 2>/dev/null',
|
|
4141
|
+
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
4142
|
+
).trim();
|
|
4143
|
+
if (!logOutput) return activity;
|
|
4144
|
+
for (const line of logOutput.split("\n")) {
|
|
4145
|
+
const [hash, date, ...msgParts] = line.split("|");
|
|
4146
|
+
const message = msgParts.join("|");
|
|
4147
|
+
if (!hash || !message) continue;
|
|
4148
|
+
const msgLower = message.toLowerCase();
|
|
4149
|
+
let detectedSquad = "unknown";
|
|
4150
|
+
for (const [squad, keywords] of Object.entries(squadKeywords)) {
|
|
4151
|
+
if (keywords.some((k) => msgLower.includes(k))) {
|
|
4152
|
+
detectedSquad = squad;
|
|
4153
|
+
break;
|
|
4154
|
+
}
|
|
4155
|
+
}
|
|
4156
|
+
activity.push({
|
|
4157
|
+
squad: detectedSquad,
|
|
4158
|
+
message,
|
|
4159
|
+
hash,
|
|
4160
|
+
date: date.split("T")[0]
|
|
4161
|
+
});
|
|
4162
|
+
}
|
|
4163
|
+
} catch {
|
|
4164
|
+
}
|
|
4165
|
+
return activity;
|
|
4166
|
+
}
|
|
4167
|
+
async function progressCommand(options = {}) {
|
|
4168
|
+
writeLine();
|
|
4169
|
+
writeLine(` ${gradient("squads")} ${colors.dim}progress${RESET}`);
|
|
4170
|
+
writeLine();
|
|
4171
|
+
const tasksData = loadTasks();
|
|
4172
|
+
const recentActivity = getRecentActivity();
|
|
4173
|
+
const activeTasks = tasksData.tasks.filter((t) => t.status === "active");
|
|
4174
|
+
const completedToday = tasksData.tasks.filter(
|
|
4175
|
+
(t) => t.status === "completed" && t.completedAt?.startsWith((/* @__PURE__ */ new Date()).toISOString().split("T")[0])
|
|
4176
|
+
);
|
|
4177
|
+
const stats = [
|
|
4178
|
+
`${colors.cyan}${activeTasks.length}${RESET} active`,
|
|
4179
|
+
`${colors.green}${completedToday.length}${RESET} done today`,
|
|
4180
|
+
`${colors.purple}${recentActivity.length}${RESET} commits (24h)`
|
|
4181
|
+
].join(` ${colors.dim}\u2502${RESET} `);
|
|
4182
|
+
writeLine(` ${stats}`);
|
|
4183
|
+
writeLine();
|
|
4184
|
+
if (activeTasks.length > 0) {
|
|
4185
|
+
writeLine(` ${bold}Active Tasks${RESET}`);
|
|
4186
|
+
writeLine();
|
|
4187
|
+
for (const task of activeTasks) {
|
|
4188
|
+
const elapsed = getElapsedTime(task.startedAt);
|
|
4189
|
+
writeLine(` ${icons.progress} ${colors.cyan}${task.squad}${RESET} ${truncate(task.description, 45)}`);
|
|
4190
|
+
writeLine(` ${colors.dim}started ${elapsed} ago${RESET}`);
|
|
4191
|
+
}
|
|
4192
|
+
writeLine();
|
|
4193
|
+
} else {
|
|
4194
|
+
writeLine(` ${colors.dim}No active tasks${RESET}`);
|
|
4195
|
+
writeLine();
|
|
4196
|
+
}
|
|
4197
|
+
if (recentActivity.length > 0) {
|
|
4198
|
+
writeLine(` ${bold}Recent Activity${RESET} ${colors.dim}(last 24h)${RESET}`);
|
|
4199
|
+
writeLine();
|
|
4200
|
+
const w = { squad: 12, message: 50 };
|
|
4201
|
+
const tableWidth = w.squad + w.message + 4;
|
|
4202
|
+
writeLine(` ${colors.purple}${box.topLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.topRight}${RESET}`);
|
|
4203
|
+
writeLine(` ${colors.purple}${box.vertical}${RESET} ${bold}${padEnd("SQUAD", w.squad)}${RESET}${bold}COMMIT${RESET}${" ".repeat(w.message - 6)} ${colors.purple}${box.vertical}${RESET}`);
|
|
4204
|
+
writeLine(` ${colors.purple}${box.teeRight}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.teeLeft}${RESET}`);
|
|
4205
|
+
const maxRows = options.verbose ? 15 : 8;
|
|
4206
|
+
for (const act of recentActivity.slice(0, maxRows)) {
|
|
4207
|
+
const squadColor = act.squad === "unknown" ? colors.dim : colors.cyan;
|
|
4208
|
+
const row = ` ${colors.purple}${box.vertical}${RESET} ${squadColor}${padEnd(act.squad, w.squad)}${RESET}${truncate(act.message, w.message - 2)} ${colors.purple}${box.vertical}${RESET}`;
|
|
4209
|
+
writeLine(row);
|
|
4210
|
+
}
|
|
4211
|
+
writeLine(` ${colors.purple}${box.bottomLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.bottomRight}${RESET}`);
|
|
4212
|
+
if (recentActivity.length > maxRows) {
|
|
4213
|
+
writeLine(` ${colors.dim}+${recentActivity.length - maxRows} more commits${RESET}`);
|
|
4214
|
+
}
|
|
4215
|
+
writeLine();
|
|
4216
|
+
}
|
|
4217
|
+
if (completedToday.length > 0) {
|
|
4218
|
+
writeLine(` ${bold}Completed Today${RESET}`);
|
|
4219
|
+
writeLine();
|
|
4220
|
+
for (const task of completedToday.slice(0, 5)) {
|
|
4221
|
+
writeLine(` ${icons.success} ${colors.cyan}${task.squad}${RESET} ${truncate(task.description, 50)}`);
|
|
4222
|
+
}
|
|
4223
|
+
if (completedToday.length > 5) {
|
|
4224
|
+
writeLine(` ${colors.dim}+${completedToday.length - 5} more${RESET}`);
|
|
4225
|
+
}
|
|
4226
|
+
writeLine();
|
|
4227
|
+
}
|
|
4228
|
+
writeLine(` ${colors.dim}$${RESET} squads results ${colors.dim}KPI goals vs actuals${RESET}`);
|
|
4229
|
+
writeLine(` ${colors.dim}$${RESET} squads dash ${colors.dim}Full dashboard${RESET}`);
|
|
4230
|
+
writeLine();
|
|
4231
|
+
}
|
|
4232
|
+
async function progressStartCommand(squad, description) {
|
|
4233
|
+
const tasksData = loadTasks();
|
|
4234
|
+
const id = Math.random().toString(36).substring(2, 9);
|
|
4235
|
+
tasksData.tasks.push({
|
|
4236
|
+
id,
|
|
4237
|
+
squad,
|
|
4238
|
+
description,
|
|
4239
|
+
status: "active",
|
|
4240
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4241
|
+
});
|
|
4242
|
+
saveTasks(tasksData);
|
|
4243
|
+
writeLine(` ${icons.active} Task ${colors.cyan}${id}${RESET} started for ${colors.purple}${squad}${RESET}`);
|
|
4244
|
+
}
|
|
4245
|
+
async function progressCompleteCommand(taskId, options = {}) {
|
|
4246
|
+
const tasksData = loadTasks();
|
|
4247
|
+
const task = tasksData.tasks.find((t) => t.id === taskId);
|
|
4248
|
+
if (!task) {
|
|
4249
|
+
writeLine(` ${icons.error} Task ${colors.red}${taskId}${RESET} not found`);
|
|
4250
|
+
return;
|
|
4251
|
+
}
|
|
4252
|
+
task.status = options.failed ? "failed" : "completed";
|
|
4253
|
+
task.completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4254
|
+
saveTasks(tasksData);
|
|
4255
|
+
const icon = options.failed ? icons.error : icons.success;
|
|
4256
|
+
const status = options.failed ? "failed" : "completed";
|
|
4257
|
+
writeLine(` ${icon} Task ${colors.cyan}${taskId}${RESET} ${status}`);
|
|
4258
|
+
}
|
|
4259
|
+
function getElapsedTime(startTime) {
|
|
4260
|
+
const start = new Date(startTime).getTime();
|
|
4261
|
+
const now = Date.now();
|
|
4262
|
+
const diffMs = now - start;
|
|
4263
|
+
const minutes = Math.floor(diffMs / 6e4);
|
|
4264
|
+
const hours = Math.floor(minutes / 60);
|
|
4265
|
+
const days = Math.floor(hours / 24);
|
|
4266
|
+
if (days > 0) return `${days}d`;
|
|
4267
|
+
if (hours > 0) return `${hours}h`;
|
|
4268
|
+
if (minutes > 0) return `${minutes}m`;
|
|
4269
|
+
return "<1m";
|
|
4270
|
+
}
|
|
4271
|
+
|
|
4272
|
+
// src/commands/results.ts
|
|
4273
|
+
import { execSync as execSync7 } from "child_process";
|
|
4274
|
+
function getGitStats(days = 7) {
|
|
4275
|
+
const stats = /* @__PURE__ */ new Map();
|
|
4276
|
+
const squadKeywords = {
|
|
4277
|
+
website: ["agents-squads-web", "website", "homepage"],
|
|
4278
|
+
product: ["squads-cli", "cli"],
|
|
4279
|
+
research: ["research"],
|
|
4280
|
+
engineering: ["engineering", ".agents"],
|
|
4281
|
+
intelligence: ["intelligence"],
|
|
4282
|
+
customer: ["customer"],
|
|
4283
|
+
finance: ["finance"],
|
|
4284
|
+
company: ["company"],
|
|
4285
|
+
marketing: ["marketing"]
|
|
4286
|
+
};
|
|
4287
|
+
try {
|
|
4288
|
+
const logOutput = execSync7(
|
|
4289
|
+
`git log --since="${days} days ago" --format="%s" --name-only 2>/dev/null`,
|
|
4290
|
+
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
4291
|
+
).trim();
|
|
4292
|
+
if (!logOutput) return stats;
|
|
4293
|
+
const entries = logOutput.split("\n\n");
|
|
4294
|
+
for (const entry of entries) {
|
|
4295
|
+
const lines = entry.split("\n").filter((l) => l.trim());
|
|
4296
|
+
if (lines.length === 0) continue;
|
|
4297
|
+
const message = lines[0];
|
|
4298
|
+
const files = lines.slice(1);
|
|
4299
|
+
const msgLower = message.toLowerCase();
|
|
4300
|
+
let detectedSquad = "other";
|
|
4301
|
+
for (const [squad, keywords] of Object.entries(squadKeywords)) {
|
|
4302
|
+
const inMessage = keywords.some((k) => msgLower.includes(k));
|
|
4303
|
+
const inFiles = files.some(
|
|
4304
|
+
(f) => keywords.some((k) => f.toLowerCase().includes(k))
|
|
4305
|
+
);
|
|
4306
|
+
if (inMessage || inFiles) {
|
|
4307
|
+
detectedSquad = squad;
|
|
4308
|
+
break;
|
|
4309
|
+
}
|
|
4310
|
+
}
|
|
4311
|
+
if (!stats.has(detectedSquad)) {
|
|
4312
|
+
stats.set(detectedSquad, { commits: 0, files: [] });
|
|
4313
|
+
}
|
|
4314
|
+
const squadStats = stats.get(detectedSquad);
|
|
4315
|
+
squadStats.commits++;
|
|
4316
|
+
squadStats.files.push(...files);
|
|
4317
|
+
}
|
|
4318
|
+
} catch {
|
|
4319
|
+
}
|
|
4320
|
+
return stats;
|
|
4321
|
+
}
|
|
4322
|
+
function getGitHubStats2(days = 7) {
|
|
4323
|
+
const prsOpened = /* @__PURE__ */ new Map();
|
|
4324
|
+
const prsMerged = /* @__PURE__ */ new Map();
|
|
4325
|
+
const issuesClosed = /* @__PURE__ */ new Map();
|
|
4326
|
+
try {
|
|
4327
|
+
const prsOutput = execSync7(
|
|
4328
|
+
`gh pr list --state all --json title,createdAt,mergedAt --limit 50 2>/dev/null`,
|
|
4329
|
+
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
4330
|
+
);
|
|
4331
|
+
const prs = JSON.parse(prsOutput || "[]");
|
|
4332
|
+
const since = new Date(Date.now() - days * 24 * 60 * 60 * 1e3);
|
|
4333
|
+
for (const pr of prs) {
|
|
4334
|
+
const created = new Date(pr.createdAt);
|
|
4335
|
+
if (created < since) continue;
|
|
4336
|
+
const squad = detectSquadFromTitle(pr.title);
|
|
4337
|
+
prsOpened.set(squad, (prsOpened.get(squad) || 0) + 1);
|
|
4338
|
+
if (pr.mergedAt) {
|
|
4339
|
+
prsMerged.set(squad, (prsMerged.get(squad) || 0) + 1);
|
|
4340
|
+
}
|
|
4341
|
+
}
|
|
4342
|
+
const issuesOutput = execSync7(
|
|
4343
|
+
`gh issue list --state closed --json title,closedAt --limit 50 2>/dev/null`,
|
|
4344
|
+
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
4345
|
+
);
|
|
4346
|
+
const issues = JSON.parse(issuesOutput || "[]");
|
|
4347
|
+
for (const issue of issues) {
|
|
4348
|
+
const closed = new Date(issue.closedAt);
|
|
4349
|
+
if (closed < since) continue;
|
|
4350
|
+
const squad = detectSquadFromTitle(issue.title);
|
|
4351
|
+
issuesClosed.set(squad, (issuesClosed.get(squad) || 0) + 1);
|
|
4352
|
+
}
|
|
4353
|
+
} catch {
|
|
4354
|
+
}
|
|
4355
|
+
return { prsOpened, prsMerged, issuesClosed };
|
|
4356
|
+
}
|
|
4357
|
+
function detectSquadFromTitle(title) {
|
|
4358
|
+
const lower = title.toLowerCase();
|
|
4359
|
+
const mapping = {
|
|
4360
|
+
website: ["website", "web", "homepage", "page"],
|
|
4361
|
+
product: ["cli", "squads", "command"],
|
|
4362
|
+
research: ["research", "report"],
|
|
4363
|
+
engineering: ["infra", "build", "ci"],
|
|
4364
|
+
intelligence: ["intel", "monitor"],
|
|
4365
|
+
customer: ["lead", "customer"],
|
|
4366
|
+
finance: ["cost", "finance"],
|
|
4367
|
+
marketing: ["marketing", "content"]
|
|
4368
|
+
};
|
|
4369
|
+
for (const [squad, keywords] of Object.entries(mapping)) {
|
|
4370
|
+
if (keywords.some((k) => lower.includes(k))) {
|
|
4371
|
+
return squad;
|
|
4372
|
+
}
|
|
4373
|
+
}
|
|
4374
|
+
return "other";
|
|
4375
|
+
}
|
|
4376
|
+
function parseMetrics(goal2) {
|
|
4377
|
+
const metrics = [];
|
|
4378
|
+
if (goal2.metrics && goal2.metrics.length > 0) {
|
|
4379
|
+
return goal2.metrics;
|
|
4380
|
+
}
|
|
4381
|
+
const desc = goal2.description.toLowerCase();
|
|
4382
|
+
if (desc.includes("revenue")) metrics.push("revenue_usd");
|
|
4383
|
+
if (desc.includes("lead")) metrics.push("leads_count");
|
|
4384
|
+
if (desc.includes("traffic") || desc.includes("visit")) metrics.push("page_views");
|
|
4385
|
+
if (desc.includes("signup") || desc.includes("email")) metrics.push("signups");
|
|
4386
|
+
if (desc.includes("cost")) metrics.push("cost_usd");
|
|
4387
|
+
if (desc.includes("publish") || desc.includes("launch")) metrics.push("shipped");
|
|
4388
|
+
if (desc.includes("demo")) metrics.push("demos_booked");
|
|
4389
|
+
return metrics.length > 0 ? metrics : ["progress"];
|
|
4390
|
+
}
|
|
4391
|
+
async function resultsCommand(options = {}) {
|
|
4392
|
+
const squadsDir = findSquadsDir();
|
|
4393
|
+
if (!squadsDir) {
|
|
4394
|
+
writeLine(`${colors.red}No .agents/squads directory found${RESET}`);
|
|
4395
|
+
return;
|
|
4396
|
+
}
|
|
4397
|
+
const days = parseInt(options.days || "7", 10);
|
|
4398
|
+
writeLine();
|
|
4399
|
+
writeLine(` ${gradient("squads")} ${colors.dim}results${RESET} ${colors.dim}(${days}d)${RESET}`);
|
|
4400
|
+
writeLine();
|
|
4401
|
+
const squadNames = options.squad ? [options.squad] : listSquads(squadsDir);
|
|
4402
|
+
const gitStats = getGitStats(days);
|
|
4403
|
+
const ghStats = getGitHubStats2(days);
|
|
4404
|
+
const results = [];
|
|
4405
|
+
for (const name of squadNames) {
|
|
4406
|
+
const squad = loadSquad(name);
|
|
4407
|
+
if (!squad) continue;
|
|
4408
|
+
const git = gitStats.get(name) || { commits: 0, files: [] };
|
|
4409
|
+
const activeGoals = squad.goals.filter((g) => !g.completed);
|
|
4410
|
+
results.push({
|
|
4411
|
+
name,
|
|
4412
|
+
commits: git.commits,
|
|
4413
|
+
prsOpened: ghStats.prsOpened.get(name) || 0,
|
|
4414
|
+
prsMerged: ghStats.prsMerged.get(name) || 0,
|
|
4415
|
+
issuesClosed: ghStats.issuesClosed.get(name) || 0,
|
|
4416
|
+
goals: activeGoals.map((g) => ({
|
|
4417
|
+
description: g.description,
|
|
4418
|
+
metrics: parseMetrics(g),
|
|
4419
|
+
progress: g.progress,
|
|
4420
|
+
completed: g.completed
|
|
4421
|
+
}))
|
|
4422
|
+
});
|
|
4423
|
+
}
|
|
4424
|
+
const totalCommits = results.reduce((sum, r) => sum + r.commits, 0);
|
|
4425
|
+
const totalPRs = results.reduce((sum, r) => sum + r.prsMerged, 0);
|
|
4426
|
+
const totalGoals = results.reduce((sum, r) => sum + r.goals.length, 0);
|
|
4427
|
+
const stats = [
|
|
4428
|
+
`${colors.cyan}${totalCommits}${RESET} commits`,
|
|
4429
|
+
`${colors.green}${totalPRs}${RESET} PRs merged`,
|
|
4430
|
+
`${colors.purple}${totalGoals}${RESET} active goals`
|
|
4431
|
+
].join(` ${colors.dim}\u2502${RESET} `);
|
|
4432
|
+
writeLine(` ${stats}`);
|
|
4433
|
+
writeLine();
|
|
4434
|
+
const w = { squad: 14, commits: 8, prs: 6, goals: 5, kpi: 20 };
|
|
4435
|
+
const tableWidth = w.squad + w.commits + w.prs + w.goals + w.kpi + 8;
|
|
4436
|
+
writeLine(` ${colors.purple}${box.topLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.topRight}${RESET}`);
|
|
4437
|
+
writeLine(` ${colors.purple}${box.vertical}${RESET} ${bold}${padEnd("SQUAD", w.squad)}${RESET}${bold}${padEnd("COMMITS", w.commits)}${RESET}${bold}${padEnd("PRs", w.prs)}${RESET}${bold}${padEnd("GOALS", w.goals)}${RESET}${bold}KEY METRIC${RESET} ${colors.purple}${box.vertical}${RESET}`);
|
|
4438
|
+
writeLine(` ${colors.purple}${box.teeRight}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.teeLeft}${RESET}`);
|
|
4439
|
+
for (const result of results) {
|
|
4440
|
+
const keyMetric = result.goals.length > 0 ? result.goals[0].metrics[0] || "\u2014" : "\u2014";
|
|
4441
|
+
const commitColor = result.commits > 0 ? colors.green : colors.dim;
|
|
4442
|
+
const prColor = result.prsMerged > 0 ? colors.green : colors.dim;
|
|
4443
|
+
const row = ` ${colors.purple}${box.vertical}${RESET} ${colors.cyan}${padEnd(result.name, w.squad)}${RESET}${commitColor}${padEnd(String(result.commits), w.commits)}${RESET}${prColor}${padEnd(String(result.prsMerged), w.prs)}${RESET}${padEnd(String(result.goals.length), w.goals)}${colors.dim}${truncate(keyMetric, w.kpi)}${RESET} ${colors.purple}${box.vertical}${RESET}`;
|
|
4444
|
+
writeLine(row);
|
|
4445
|
+
}
|
|
4446
|
+
writeLine(` ${colors.purple}${box.bottomLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.bottomRight}${RESET}`);
|
|
4447
|
+
writeLine();
|
|
4448
|
+
if (options.verbose || options.squad) {
|
|
4449
|
+
writeLine(` ${bold}Goals & KPIs${RESET}`);
|
|
4450
|
+
writeLine();
|
|
4451
|
+
for (const result of results) {
|
|
4452
|
+
if (result.goals.length === 0) continue;
|
|
4453
|
+
writeLine(` ${colors.cyan}${result.name}${RESET}`);
|
|
4454
|
+
for (const goal2 of result.goals) {
|
|
4455
|
+
const statusIcon = goal2.progress ? icons.progress : icons.empty;
|
|
4456
|
+
writeLine(` ${statusIcon} ${truncate(goal2.description, 55)}`);
|
|
4457
|
+
if (goal2.metrics.length > 0) {
|
|
4458
|
+
const metricsStr = goal2.metrics.map((m) => `${colors.purple}${m}${RESET}`).join(", ");
|
|
4459
|
+
writeLine(` ${colors.dim}metrics:${RESET} ${metricsStr}`);
|
|
4460
|
+
}
|
|
4461
|
+
if (goal2.progress) {
|
|
4462
|
+
writeLine(` ${colors.dim}progress:${RESET} ${colors.green}${goal2.progress}${RESET}`);
|
|
4463
|
+
}
|
|
4464
|
+
}
|
|
4465
|
+
writeLine();
|
|
4466
|
+
}
|
|
4467
|
+
}
|
|
4468
|
+
writeLine(` ${colors.dim}$${RESET} squads results ${colors.cyan}<squad>${RESET} -v ${colors.dim}Detailed squad KPIs${RESET}`);
|
|
4469
|
+
writeLine(` ${colors.dim}$${RESET} squads goal progress ${colors.dim}Update goal progress${RESET}`);
|
|
4470
|
+
writeLine();
|
|
4471
|
+
}
|
|
4472
|
+
|
|
4473
|
+
// src/commands/workers.ts
|
|
4474
|
+
import { execSync as execSync8 } from "child_process";
|
|
4475
|
+
import { existsSync as existsSync13, readFileSync as readFileSync10 } from "fs";
|
|
4476
|
+
import { join as join13 } from "path";
|
|
4477
|
+
function getTasksFilePath2() {
|
|
4478
|
+
const memoryDir = findMemoryDir();
|
|
4479
|
+
if (!memoryDir) return null;
|
|
4480
|
+
return join13(memoryDir, "..", "tasks.json");
|
|
4481
|
+
}
|
|
4482
|
+
function loadActiveTasks() {
|
|
4483
|
+
const tasksPath = getTasksFilePath2();
|
|
4484
|
+
if (!tasksPath || !existsSync13(tasksPath)) return [];
|
|
4485
|
+
try {
|
|
4486
|
+
const data = JSON.parse(readFileSync10(tasksPath, "utf-8"));
|
|
4487
|
+
return data.tasks?.filter((t) => t.status === "active") || [];
|
|
4488
|
+
} catch {
|
|
4489
|
+
return [];
|
|
4490
|
+
}
|
|
4491
|
+
}
|
|
4492
|
+
function getRunningProcesses() {
|
|
4493
|
+
const processes = [];
|
|
4494
|
+
try {
|
|
4495
|
+
const psOutput = execSync8(
|
|
4496
|
+
'ps aux | grep -E "claude|squads|astro|node.*agent" | grep -v grep',
|
|
4497
|
+
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
4498
|
+
).trim();
|
|
4499
|
+
if (!psOutput) return processes;
|
|
4500
|
+
for (const line of psOutput.split("\n")) {
|
|
4501
|
+
const parts = line.trim().split(/\s+/);
|
|
4502
|
+
if (parts.length < 11) continue;
|
|
4503
|
+
const pid = parts[1];
|
|
4504
|
+
const cpu = parts[2];
|
|
4505
|
+
const mem = parts[3];
|
|
4506
|
+
const time = parts[9];
|
|
4507
|
+
const command = parts.slice(10).join(" ");
|
|
4508
|
+
let type = "agent";
|
|
4509
|
+
if (command.includes("claude")) type = "claude";
|
|
4510
|
+
else if (command.includes("squads")) type = "hook";
|
|
4511
|
+
else if (command.includes("astro")) type = "dev-server";
|
|
4512
|
+
if (command.includes("grep")) continue;
|
|
4513
|
+
processes.push({ pid, cpu, mem, time, command, type });
|
|
4514
|
+
}
|
|
4515
|
+
} catch {
|
|
4516
|
+
}
|
|
4517
|
+
return processes;
|
|
4518
|
+
}
|
|
4519
|
+
function categorizeProcesses(processes) {
|
|
4520
|
+
return {
|
|
4521
|
+
claude: processes.filter((p) => p.type === "claude"),
|
|
4522
|
+
hooks: processes.filter((p) => p.type === "hook"),
|
|
4523
|
+
devServers: processes.filter((p) => p.type === "dev-server"),
|
|
4524
|
+
agents: processes.filter((p) => p.type === "agent")
|
|
4525
|
+
};
|
|
4526
|
+
}
|
|
4527
|
+
function formatCommand(cmd, maxLen = 45) {
|
|
4528
|
+
if (cmd.includes("claude")) {
|
|
4529
|
+
return truncate("claude (session)", maxLen);
|
|
4530
|
+
}
|
|
4531
|
+
if (cmd.includes("astro dev")) {
|
|
4532
|
+
return truncate("astro dev server", maxLen);
|
|
4533
|
+
}
|
|
4534
|
+
if (cmd.includes("squads")) {
|
|
4535
|
+
const match = cmd.match(/squads\s+(\S+)/);
|
|
4536
|
+
return truncate(`squads ${match?.[1] || "command"}`, maxLen);
|
|
4537
|
+
}
|
|
4538
|
+
return truncate(cmd, maxLen);
|
|
4539
|
+
}
|
|
4540
|
+
async function workersCommand(options = {}) {
|
|
4541
|
+
writeLine();
|
|
4542
|
+
writeLine(` ${gradient("squads")} ${colors.dim}workers${RESET}`);
|
|
4543
|
+
writeLine();
|
|
4544
|
+
if (options.kill) {
|
|
4545
|
+
try {
|
|
4546
|
+
execSync8(`kill ${options.kill}`, { stdio: "pipe" });
|
|
4547
|
+
writeLine(` ${icons.success} Killed process ${colors.cyan}${options.kill}${RESET}`);
|
|
4548
|
+
writeLine();
|
|
4549
|
+
return;
|
|
4550
|
+
} catch {
|
|
4551
|
+
writeLine(` ${icons.error} Failed to kill process ${colors.red}${options.kill}${RESET}`);
|
|
4552
|
+
writeLine();
|
|
4553
|
+
return;
|
|
4554
|
+
}
|
|
4555
|
+
}
|
|
4556
|
+
const activeTasks = loadActiveTasks();
|
|
4557
|
+
const processes = getRunningProcesses();
|
|
4558
|
+
const categorized = categorizeProcesses(processes);
|
|
4559
|
+
const stats = [
|
|
4560
|
+
`${colors.cyan}${categorized.claude.length}${RESET} claude`,
|
|
4561
|
+
`${colors.green}${activeTasks.length}${RESET} tasks`,
|
|
4562
|
+
`${colors.purple}${categorized.devServers.length}${RESET} dev servers`
|
|
4563
|
+
].join(` ${colors.dim}\u2502${RESET} `);
|
|
4564
|
+
writeLine(` ${stats}`);
|
|
4565
|
+
writeLine();
|
|
4566
|
+
if (categorized.claude.length > 0) {
|
|
4567
|
+
writeLine(` ${bold}Claude Sessions${RESET} ${colors.dim}(terminal tabs)${RESET}`);
|
|
4568
|
+
writeLine();
|
|
4569
|
+
const w = { pid: 8, cpu: 6, mem: 6, time: 8, cmd: 30 };
|
|
4570
|
+
const tableWidth = w.pid + w.cpu + w.mem + w.time + w.cmd + 8;
|
|
4571
|
+
writeLine(` ${colors.purple}${box.topLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.topRight}${RESET}`);
|
|
4572
|
+
writeLine(` ${colors.purple}${box.vertical}${RESET} ${bold}${padEnd("PID", w.pid)}${padEnd("CPU%", w.cpu)}${padEnd("MEM%", w.mem)}${padEnd("TIME", w.time)}${padEnd("STATUS", w.cmd)}${RESET} ${colors.purple}${box.vertical}${RESET}`);
|
|
4573
|
+
writeLine(` ${colors.purple}${box.teeRight}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.teeLeft}${RESET}`);
|
|
4574
|
+
for (const proc of categorized.claude) {
|
|
4575
|
+
const cpuColor = parseFloat(proc.cpu) > 50 ? colors.yellow : colors.dim;
|
|
4576
|
+
const status = parseFloat(proc.cpu) > 10 ? `${icons.active} active` : `${icons.pending} idle`;
|
|
4577
|
+
const row = ` ${colors.purple}${box.vertical}${RESET} ${colors.cyan}${padEnd(proc.pid, w.pid)}${RESET}${cpuColor}${padEnd(proc.cpu, w.cpu)}${RESET}${colors.dim}${padEnd(proc.mem, w.mem)}${RESET}${colors.dim}${padEnd(proc.time, w.time)}${RESET}${padEnd(status, w.cmd)} ${colors.purple}${box.vertical}${RESET}`;
|
|
4578
|
+
writeLine(row);
|
|
4579
|
+
}
|
|
4580
|
+
writeLine(` ${colors.purple}${box.bottomLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.bottomRight}${RESET}`);
|
|
4581
|
+
writeLine();
|
|
4582
|
+
}
|
|
4583
|
+
if (activeTasks.length > 0) {
|
|
4584
|
+
writeLine(` ${bold}Registered Tasks${RESET} ${colors.dim}(squads progress)${RESET}`);
|
|
4585
|
+
writeLine();
|
|
4586
|
+
for (const task of activeTasks) {
|
|
4587
|
+
const elapsed = getElapsedTime2(task.startedAt);
|
|
4588
|
+
writeLine(` ${icons.progress} ${colors.cyan}${task.squad}${RESET} ${truncate(task.description, 40)}`);
|
|
4589
|
+
writeLine(` ${colors.dim}id: ${task.id} \xB7 started ${elapsed} ago${RESET}`);
|
|
4590
|
+
}
|
|
4591
|
+
writeLine();
|
|
4592
|
+
}
|
|
4593
|
+
if (categorized.devServers.length > 0) {
|
|
4594
|
+
writeLine(` ${bold}Dev Servers${RESET}`);
|
|
4595
|
+
writeLine();
|
|
4596
|
+
for (const proc of categorized.devServers) {
|
|
4597
|
+
const name = formatCommand(proc.command);
|
|
4598
|
+
writeLine(` ${icons.active} ${colors.green}${name}${RESET} ${colors.dim}(pid: ${proc.pid})${RESET}`);
|
|
4599
|
+
}
|
|
4600
|
+
writeLine();
|
|
4601
|
+
}
|
|
4602
|
+
if (categorized.hooks.length > 0) {
|
|
4603
|
+
writeLine(` ${bold}Hook Processes${RESET}`);
|
|
4604
|
+
writeLine();
|
|
4605
|
+
for (const proc of categorized.hooks) {
|
|
4606
|
+
const name = formatCommand(proc.command);
|
|
4607
|
+
writeLine(` ${icons.pending} ${colors.yellow}${name}${RESET} ${colors.dim}(pid: ${proc.pid})${RESET}`);
|
|
4608
|
+
}
|
|
4609
|
+
writeLine();
|
|
4610
|
+
}
|
|
4611
|
+
if (categorized.claude.length === 0 && activeTasks.length === 0) {
|
|
4612
|
+
writeLine(` ${colors.dim}No active workers${RESET}`);
|
|
4613
|
+
writeLine();
|
|
4614
|
+
}
|
|
4615
|
+
writeLine(` ${colors.dim}$${RESET} squads workers --kill <pid> ${colors.dim}Kill a process${RESET}`);
|
|
4616
|
+
writeLine(` ${colors.dim}$${RESET} squads progress ${colors.dim}Task history${RESET}`);
|
|
4617
|
+
writeLine();
|
|
4618
|
+
}
|
|
4619
|
+
function getElapsedTime2(startTime) {
|
|
4620
|
+
const start = new Date(startTime).getTime();
|
|
4621
|
+
const now = Date.now();
|
|
4622
|
+
const diffMs = now - start;
|
|
4623
|
+
const minutes = Math.floor(diffMs / 6e4);
|
|
4624
|
+
const hours = Math.floor(minutes / 60);
|
|
4625
|
+
const days = Math.floor(hours / 24);
|
|
4626
|
+
if (days > 0) return `${days}d`;
|
|
4627
|
+
if (hours > 0) return `${hours}h`;
|
|
4628
|
+
if (minutes > 0) return `${minutes}m`;
|
|
4629
|
+
return "<1m";
|
|
4630
|
+
}
|
|
4631
|
+
|
|
4632
|
+
// src/cli.ts
|
|
4633
|
+
var envPaths = [
|
|
4634
|
+
join14(process.cwd(), ".env"),
|
|
4635
|
+
join14(process.cwd(), "..", "hq", ".env"),
|
|
4636
|
+
join14(homedir4(), "agents-squads", "hq", ".env")
|
|
4637
|
+
];
|
|
4638
|
+
for (const envPath of envPaths) {
|
|
4639
|
+
if (existsSync14(envPath)) {
|
|
4640
|
+
config({ path: envPath, quiet: true });
|
|
4641
|
+
break;
|
|
4642
|
+
}
|
|
4643
|
+
}
|
|
4644
|
+
registerExitHandler();
|
|
4645
|
+
var program = new Command();
|
|
4646
|
+
program.name("squads").description("A CLI for humans and agents").version(version);
|
|
4647
|
+
program.command("init").description("Initialize a new squad project").option("-t, --template <template>", "Project template", "default").action(initCommand);
|
|
4648
|
+
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").action(runCommand);
|
|
4649
|
+
program.command("list").description("List agents and squads").option("-s, --squads", "List squads only").option("-a, --agents", "List agents only").action(listCommand);
|
|
4650
|
+
program.command("status [squad]").description("Show squad status and state").option("-v, --verbose", "Show detailed status").action(statusCommand);
|
|
4651
|
+
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").action(dashboardCommand);
|
|
4652
|
+
program.command("issues").description("Show GitHub issues across repos").option("-o, --org <org>", "GitHub organization", "agents-squads").option("-r, --repos <repos>", "Comma-separated repo names").action(issuesCommand);
|
|
4653
|
+
program.command("solve-issues").description("Solve ready-to-fix issues by creating PRs").option("-r, --repo <repo>", "Target repo (hq, agents-squads-web)").option("-i, --issue <number>", "Specific issue number", parseInt).option("-d, --dry-run", "Show what would be solved").option("-e, --execute", "Execute with Claude CLI").action(solveIssuesCommand);
|
|
4654
|
+
program.command("open-issues").description("Run evaluators/critics to find and create issues").option("-s, --squad <squad>", "Target squad (website, engineering, etc.)").option("-a, --agent <agent>", "Specific evaluator agent").option("-d, --dry-run", "Show what would run").option("-e, --execute", "Execute with Claude CLI").action(openIssuesCommand);
|
|
4655
|
+
var progress = program.command("progress").description("Track active and completed agent tasks").option("-v, --verbose", "Show more activity").action(progressCommand);
|
|
4656
|
+
progress.command("start <squad> <description>").description("Register a new active task").action(progressStartCommand);
|
|
4657
|
+
progress.command("complete <taskId>").description("Mark a task as completed").option("-f, --failed", "Mark as failed instead").action(progressCompleteCommand);
|
|
4658
|
+
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 }));
|
|
4659
|
+
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);
|
|
4660
|
+
var memory = program.command("memory").description("Query and manage squad memory");
|
|
4661
|
+
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);
|
|
4662
|
+
memory.command("show <squad>").description("Show memory for a squad").action(memoryShowCommand);
|
|
4663
|
+
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);
|
|
4664
|
+
memory.command("list").description("List all memory entries").action(memoryListCommand);
|
|
4665
|
+
memory.command("sync").description("Sync memory from recent git commits (auto-update)").option("-v, --verbose", "Show detailed commit info").action(syncCommand);
|
|
4666
|
+
memory.command("search <query>").description("Search conversations stored in postgres (via squads-bridge)").option("-l, --limit <limit>", "Number of results", "10").option("-r, --role <role>", "Filter by role: user, assistant, thinking").option("-i, --importance <importance>", "Filter by importance: low, normal, high").action((query, opts) => memorySearchCommand(query, {
|
|
4667
|
+
limit: parseInt(opts.limit, 10),
|
|
4668
|
+
role: opts.role,
|
|
4669
|
+
importance: opts.importance
|
|
4670
|
+
}));
|
|
4671
|
+
var goal = program.command("goal").description("Manage squad goals");
|
|
4672
|
+
goal.command("set <squad> <description>").description("Set a goal for a squad").option("-m, --metric <metrics...>", "Metrics to track").action(goalSetCommand);
|
|
4673
|
+
goal.command("list [squad]").description("List goals for squad(s)").option("-a, --all", "Show completed goals too").action(goalListCommand);
|
|
4674
|
+
goal.command("complete <squad> <index>").description("Mark a goal as completed").action(goalCompleteCommand);
|
|
4675
|
+
goal.command("progress <squad> <index> <progress>").description("Update goal progress").action(goalProgressCommand);
|
|
4676
|
+
var feedback = program.command("feedback").description("Record and view execution feedback");
|
|
4677
|
+
feedback.command("add <squad> <rating> <feedback>").description("Add feedback for last execution (rating 1-5)").option("-l, --learning <learnings...>", "Learnings to extract").action(feedbackAddCommand);
|
|
4678
|
+
feedback.command("show <squad>").description("Show feedback history").option("-n, --limit <n>", "Number of entries to show", "5").action(feedbackShowCommand);
|
|
4679
|
+
feedback.command("stats").description("Show feedback summary across all squads").action(feedbackStatsCommand);
|
|
4680
|
+
program.command("login").description("Log in to Squads (Pro & Enterprise)").action(loginCommand);
|
|
4681
|
+
program.command("logout").description("Log out from Squads").action(logoutCommand);
|
|
4682
|
+
program.command("whoami").description("Show current logged in user").action(whoamiCommand);
|
|
4683
|
+
await program.parseAsync();
|
|
4684
|
+
if (!process.argv.slice(2).length) {
|
|
4685
|
+
console.log(`
|
|
4686
|
+
${chalk3.bold.magenta("squads")} - AI agent squad management
|
|
4687
|
+
|
|
4688
|
+
${chalk3.dim("Quick start:")}
|
|
4689
|
+
${chalk3.cyan("squads status")} View all squads status
|
|
4690
|
+
${chalk3.cyan("squads run <squad>")} Run a squad
|
|
4691
|
+
${chalk3.cyan('squads memory query "<term>"')} Search squad memory
|
|
4692
|
+
|
|
4693
|
+
${chalk3.dim("Goals & Feedback:")}
|
|
4694
|
+
${chalk3.cyan('squads goal set <squad> "<goal>"')} Set a goal
|
|
4695
|
+
${chalk3.cyan("squads goal list")} View active goals
|
|
4696
|
+
${chalk3.cyan('squads feedback add <squad> 4 "msg"')} Rate last execution
|
|
4697
|
+
|
|
4698
|
+
${chalk3.dim("Examples:")}
|
|
4699
|
+
${chalk3.cyan("squads run website")} Run website squad
|
|
4700
|
+
${chalk3.cyan('squads goal set finance "Track costs"')} Set finance goal
|
|
4701
|
+
${chalk3.cyan("squads feedback stats")} View feedback summary
|
|
4702
|
+
|
|
4703
|
+
${chalk3.dim("Run")} ${chalk3.cyan("squads --help")} ${chalk3.dim("for all commands.")}
|
|
4704
|
+
`);
|
|
4705
|
+
}
|
|
4706
|
+
//# sourceMappingURL=cli.js.map
|