squads-cli 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +521 -288
- package/dist/auth-YW3UPFSB.js +23 -0
- package/dist/auth-YW3UPFSB.js.map +1 -0
- package/dist/autonomy-PSVZVX7A.js +105 -0
- package/dist/autonomy-PSVZVX7A.js.map +1 -0
- package/dist/chunk-67RO2HKR.js +174 -0
- package/dist/chunk-67RO2HKR.js.map +1 -0
- package/dist/chunk-7OCVIDC7.js +12 -0
- package/dist/chunk-7OCVIDC7.js.map +1 -0
- package/dist/chunk-BODLDQY7.js +452 -0
- package/dist/chunk-BODLDQY7.js.map +1 -0
- package/dist/chunk-EHQJHRIW.js +103 -0
- package/dist/chunk-EHQJHRIW.js.map +1 -0
- package/dist/chunk-FFFCFZ6A.js +121 -0
- package/dist/chunk-FFFCFZ6A.js.map +1 -0
- package/dist/chunk-FIWT2NMM.js +165 -0
- package/dist/chunk-FIWT2NMM.js.map +1 -0
- package/dist/chunk-HF4WR7RA.js +154 -0
- package/dist/chunk-HF4WR7RA.js.map +1 -0
- package/dist/chunk-J6QF4ZQX.js +230 -0
- package/dist/chunk-J6QF4ZQX.js.map +1 -0
- package/dist/chunk-LOA3KWYJ.js +294 -0
- package/dist/chunk-LOA3KWYJ.js.map +1 -0
- package/dist/chunk-M5FXNY6Y.js +384 -0
- package/dist/chunk-M5FXNY6Y.js.map +1 -0
- package/dist/chunk-QHNUMM4V.js +87 -0
- package/dist/chunk-QHNUMM4V.js.map +1 -0
- package/dist/chunk-QJ7C7CMB.js +223 -0
- package/dist/chunk-QJ7C7CMB.js.map +1 -0
- package/dist/chunk-RM6BWILN.js +74 -0
- package/dist/chunk-RM6BWILN.js.map +1 -0
- package/dist/chunk-TYFTF53O.js +613 -0
- package/dist/chunk-TYFTF53O.js.map +1 -0
- package/dist/chunk-TZXD6WFN.js +420 -0
- package/dist/chunk-TZXD6WFN.js.map +1 -0
- package/dist/chunk-WVOIY5GW.js +621 -0
- package/dist/chunk-WVOIY5GW.js.map +1 -0
- package/dist/chunk-Z2UKDBNL.js +162 -0
- package/dist/chunk-Z2UKDBNL.js.map +1 -0
- package/dist/chunk-ZTQ7ISUR.js +338 -0
- package/dist/chunk-ZTQ7ISUR.js.map +1 -0
- package/dist/cli.js +2483 -5902
- package/dist/cli.js.map +1 -1
- package/dist/context-GWPF4SEY.js +291 -0
- package/dist/context-GWPF4SEY.js.map +1 -0
- package/dist/context-feed-AJGVAR6H.js +394 -0
- package/dist/context-feed-AJGVAR6H.js.map +1 -0
- package/dist/cost-XBCDJ7XC.js +275 -0
- package/dist/cost-XBCDJ7XC.js.map +1 -0
- package/dist/create-BLFGG6PF.js +286 -0
- package/dist/create-BLFGG6PF.js.map +1 -0
- package/dist/dashboard-LGT2B2BL.js +951 -0
- package/dist/dashboard-LGT2B2BL.js.map +1 -0
- package/dist/dashboard-RMK2BOD2.js +794 -0
- package/dist/dashboard-RMK2BOD2.js.map +1 -0
- package/dist/doctor-XPUIIBHJ.js +374 -0
- package/dist/doctor-XPUIIBHJ.js.map +1 -0
- package/dist/env-config-SQEI3Y7Y.js +21 -0
- package/dist/env-config-SQEI3Y7Y.js.map +1 -0
- package/dist/exec-OUXM7JBF.js +223 -0
- package/dist/exec-OUXM7JBF.js.map +1 -0
- package/dist/feedback-KNAOG5QK.js +229 -0
- package/dist/feedback-KNAOG5QK.js.map +1 -0
- package/dist/github-UQTM5KMS.js +23 -0
- package/dist/github-UQTM5KMS.js.map +1 -0
- package/dist/goal-BVHV5573.js +168 -0
- package/dist/goal-BVHV5573.js.map +1 -0
- package/dist/health-4UXN44PF.js +218 -0
- package/dist/health-4UXN44PF.js.map +1 -0
- package/dist/history-ILH3SWHB.js +232 -0
- package/dist/history-ILH3SWHB.js.map +1 -0
- package/dist/index.d.ts +736 -8
- package/dist/index.js +1312 -6
- package/dist/index.js.map +1 -1
- package/dist/init-XQZ7BOGT.js +812 -0
- package/dist/init-XQZ7BOGT.js.map +1 -0
- package/dist/kpi-RQIU7WGK.js +413 -0
- package/dist/kpi-RQIU7WGK.js.map +1 -0
- package/dist/learn-OIFUVZAS.js +269 -0
- package/dist/learn-OIFUVZAS.js.map +1 -0
- package/dist/login-DXZANWZY.js +155 -0
- package/dist/login-DXZANWZY.js.map +1 -0
- package/dist/memory-T3ACCS7E.js +560 -0
- package/dist/memory-T3ACCS7E.js.map +1 -0
- package/dist/memory-VNF2VFRB.js +23 -0
- package/dist/memory-VNF2VFRB.js.map +1 -0
- package/dist/progress-DAUZMT3N.js +202 -0
- package/dist/progress-DAUZMT3N.js.map +1 -0
- package/dist/providers-3P5D2XL5.js +65 -0
- package/dist/providers-3P5D2XL5.js.map +1 -0
- package/dist/results-UECWGLTB.js +224 -0
- package/dist/results-UECWGLTB.js.map +1 -0
- package/dist/run-I6KAXU6U.js +4049 -0
- package/dist/run-I6KAXU6U.js.map +1 -0
- package/dist/session-HBU6KZOD.js +64 -0
- package/dist/session-HBU6KZOD.js.map +1 -0
- package/dist/sessions-CK25VGPL.js +333 -0
- package/dist/sessions-CK25VGPL.js.map +1 -0
- package/dist/squad-parser-DCG65BJS.js +35 -0
- package/dist/squad-parser-DCG65BJS.js.map +1 -0
- package/dist/stats-G6NAU5BD.js +334 -0
- package/dist/stats-G6NAU5BD.js.map +1 -0
- package/dist/status-AQNLDZVN.js +352 -0
- package/dist/status-AQNLDZVN.js.map +1 -0
- package/dist/sync-ZI3MHA4G.js +836 -0
- package/dist/sync-ZI3MHA4G.js.map +1 -0
- package/dist/templates/core/AGENTS.md.template +51 -0
- package/dist/templates/core/BUSINESS_BRIEF.md.template +29 -0
- package/dist/templates/core/CLAUDE.md.template +48 -0
- package/dist/templates/core/provider.yaml.template +5 -0
- package/dist/templates/first-squad/SQUAD.md.template +23 -0
- package/dist/templates/first-squad/lead.md.template +44 -0
- package/dist/templates/memory/getting-started/state.md.template +19 -0
- package/dist/templates/seed/BUSINESS_BRIEF.md.template +27 -0
- package/dist/templates/seed/CLAUDE.md.template +119 -0
- package/dist/templates/seed/README.md.template +42 -0
- package/dist/templates/seed/config/SYSTEM.md +52 -0
- package/dist/templates/seed/config/provider.yaml +4 -0
- package/dist/templates/seed/hooks/settings.json.template +31 -0
- package/dist/templates/seed/memory/company/directives.md +37 -0
- package/dist/templates/seed/memory/company/manager/state.md +16 -0
- package/dist/templates/seed/memory/engineering/issue-solver/state.md +12 -0
- package/dist/templates/seed/memory/intelligence/intel-lead/state.md +9 -0
- package/dist/templates/seed/memory/marketing/content-drafter/state.md +12 -0
- package/dist/templates/seed/memory/operations/ops-lead/state.md +12 -0
- package/dist/templates/seed/memory/product/lead/state.md +14 -0
- package/dist/templates/seed/memory/research/lead/state.md +14 -0
- package/dist/templates/seed/skills/gh/SKILL.md +57 -0
- package/dist/templates/seed/skills/squads-cli/SKILL.md +84 -0
- package/dist/templates/seed/squads/company/SQUAD.md +51 -0
- package/dist/templates/seed/squads/company/company-critic.md +49 -0
- package/dist/templates/seed/squads/company/company-eval.md +49 -0
- package/dist/templates/seed/squads/company/event-dispatcher.md +43 -0
- package/dist/templates/seed/squads/company/goal-tracker.md +43 -0
- package/dist/templates/seed/squads/company/manager.md +54 -0
- package/dist/templates/seed/squads/engineering/SQUAD.md +48 -0
- package/dist/templates/seed/squads/engineering/code-reviewer.md +57 -0
- package/dist/templates/seed/squads/engineering/issue-solver.md +58 -0
- package/dist/templates/seed/squads/engineering/test-writer.md +50 -0
- package/dist/templates/seed/squads/intelligence/SQUAD.md +38 -0
- package/dist/templates/seed/squads/intelligence/intel-critic.md +36 -0
- package/dist/templates/seed/squads/intelligence/intel-eval.md +31 -0
- package/dist/templates/seed/squads/intelligence/intel-lead.md +71 -0
- package/dist/templates/seed/squads/marketing/SQUAD.md +47 -0
- package/dist/templates/seed/squads/marketing/content-drafter.md +71 -0
- package/dist/templates/seed/squads/marketing/growth-analyst.md +49 -0
- package/dist/templates/seed/squads/marketing/social-poster.md +44 -0
- package/dist/templates/seed/squads/operations/SQUAD.md +45 -0
- package/dist/templates/seed/squads/operations/finance-tracker.md +47 -0
- package/dist/templates/seed/squads/operations/goal-tracker.md +48 -0
- package/dist/templates/seed/squads/operations/ops-lead.md +58 -0
- package/dist/templates/seed/squads/product/SQUAD.md +41 -0
- package/dist/templates/seed/squads/product/lead.md +56 -0
- package/dist/templates/seed/squads/product/scanner.md +50 -0
- package/dist/templates/seed/squads/product/worker.md +55 -0
- package/dist/templates/seed/squads/research/SQUAD.md +38 -0
- package/dist/templates/seed/squads/research/analyst.md +50 -0
- package/dist/templates/seed/squads/research/lead.md +52 -0
- package/dist/templates/seed/squads/research/synthesizer.md +59 -0
- package/dist/templates/skills/squads-learn/SKILL.md +86 -0
- package/dist/templates/skills/squads-workflow/instruction.md +70 -0
- package/dist/terminal-FBQFQTKZ.js +55 -0
- package/dist/terminal-FBQFQTKZ.js.map +1 -0
- package/dist/update-D7CGIZ3M.js +18 -0
- package/dist/update-D7CGIZ3M.js.map +1 -0
- package/dist/update-STU276HR.js +83 -0
- package/dist/update-STU276HR.js.map +1 -0
- package/package.json +31 -13
- package/templates/core/AGENTS.md.template +51 -0
- package/templates/core/BUSINESS_BRIEF.md.template +29 -0
- package/templates/core/CLAUDE.md.template +48 -0
- package/templates/core/provider.yaml.template +5 -0
- package/templates/first-squad/SQUAD.md.template +23 -0
- package/templates/first-squad/lead.md.template +44 -0
- package/templates/memory/getting-started/state.md.template +19 -0
- package/templates/seed/BUSINESS_BRIEF.md.template +27 -0
- package/templates/seed/CLAUDE.md.template +119 -0
- package/templates/seed/README.md.template +42 -0
- package/templates/seed/config/SYSTEM.md +52 -0
- package/templates/seed/config/provider.yaml +4 -0
- package/templates/seed/hooks/settings.json.template +31 -0
- package/templates/seed/memory/company/directives.md +37 -0
- package/templates/seed/memory/company/manager/state.md +16 -0
- package/templates/seed/memory/engineering/issue-solver/state.md +12 -0
- package/templates/seed/memory/intelligence/intel-lead/state.md +9 -0
- package/templates/seed/memory/marketing/content-drafter/state.md +12 -0
- package/templates/seed/memory/operations/ops-lead/state.md +12 -0
- package/templates/seed/memory/product/lead/state.md +14 -0
- package/templates/seed/memory/research/lead/state.md +14 -0
- package/templates/seed/skills/gh/SKILL.md +57 -0
- package/templates/seed/skills/squads-cli/SKILL.md +84 -0
- package/templates/seed/squads/company/SQUAD.md +51 -0
- package/templates/seed/squads/company/company-critic.md +49 -0
- package/templates/seed/squads/company/company-eval.md +49 -0
- package/templates/seed/squads/company/event-dispatcher.md +43 -0
- package/templates/seed/squads/company/goal-tracker.md +43 -0
- package/templates/seed/squads/company/manager.md +54 -0
- package/templates/seed/squads/engineering/SQUAD.md +48 -0
- package/templates/seed/squads/engineering/code-reviewer.md +57 -0
- package/templates/seed/squads/engineering/issue-solver.md +58 -0
- package/templates/seed/squads/engineering/test-writer.md +50 -0
- package/templates/seed/squads/intelligence/SQUAD.md +38 -0
- package/templates/seed/squads/intelligence/intel-critic.md +36 -0
- package/templates/seed/squads/intelligence/intel-eval.md +31 -0
- package/templates/seed/squads/intelligence/intel-lead.md +71 -0
- package/templates/seed/squads/marketing/SQUAD.md +47 -0
- package/templates/seed/squads/marketing/content-drafter.md +71 -0
- package/templates/seed/squads/marketing/growth-analyst.md +49 -0
- package/templates/seed/squads/marketing/social-poster.md +44 -0
- package/templates/seed/squads/operations/SQUAD.md +45 -0
- package/templates/seed/squads/operations/finance-tracker.md +47 -0
- package/templates/seed/squads/operations/goal-tracker.md +48 -0
- package/templates/seed/squads/operations/ops-lead.md +58 -0
- package/templates/seed/squads/product/SQUAD.md +41 -0
- package/templates/seed/squads/product/lead.md +56 -0
- package/templates/seed/squads/product/scanner.md +50 -0
- package/templates/seed/squads/product/worker.md +55 -0
- package/templates/seed/squads/research/SQUAD.md +38 -0
- package/templates/seed/squads/research/analyst.md +50 -0
- package/templates/seed/squads/research/lead.md +52 -0
- package/templates/seed/squads/research/synthesizer.md +59 -0
- package/templates/skills/squads-learn/SKILL.md +86 -0
- package/templates/skills/squads-workflow/instruction.md +70 -0
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
detectPlan,
|
|
4
|
+
fetchBridgeStats
|
|
5
|
+
} from "./chunk-WVOIY5GW.js";
|
|
6
|
+
import "./chunk-LOA3KWYJ.js";
|
|
7
|
+
import "./chunk-EHQJHRIW.js";
|
|
8
|
+
import {
|
|
9
|
+
Events,
|
|
10
|
+
track
|
|
11
|
+
} from "./chunk-QJ7C7CMB.js";
|
|
12
|
+
import {
|
|
13
|
+
findSquadsDir,
|
|
14
|
+
listSquads,
|
|
15
|
+
loadSquad
|
|
16
|
+
} from "./chunk-TYFTF53O.js";
|
|
17
|
+
import {
|
|
18
|
+
RESET,
|
|
19
|
+
bold,
|
|
20
|
+
box,
|
|
21
|
+
colors,
|
|
22
|
+
gradient,
|
|
23
|
+
padEnd,
|
|
24
|
+
writeLine
|
|
25
|
+
} from "./chunk-M5FXNY6Y.js";
|
|
26
|
+
import "./chunk-7OCVIDC7.js";
|
|
27
|
+
|
|
28
|
+
// src/commands/cost.ts
|
|
29
|
+
function getBudgetStatus(squadName, spent, dailyBudget, weeklyBudget) {
|
|
30
|
+
let status = "no-budget";
|
|
31
|
+
let dailyPercent = null;
|
|
32
|
+
let weeklyPercent = null;
|
|
33
|
+
if (dailyBudget !== null) {
|
|
34
|
+
dailyPercent = spent / dailyBudget * 100;
|
|
35
|
+
if (dailyPercent >= 100) {
|
|
36
|
+
status = "over";
|
|
37
|
+
} else if (dailyPercent >= 80) {
|
|
38
|
+
status = "warning";
|
|
39
|
+
} else {
|
|
40
|
+
status = "ok";
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (weeklyBudget !== null) {
|
|
44
|
+
weeklyPercent = spent / weeklyBudget * 100;
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
squad: squadName,
|
|
48
|
+
spent,
|
|
49
|
+
dailyBudget,
|
|
50
|
+
weeklyBudget,
|
|
51
|
+
dailyPercent,
|
|
52
|
+
weeklyPercent,
|
|
53
|
+
status
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
async function costCommand(options = {}) {
|
|
57
|
+
await track(Events.CLI_COST, { squad: options.squad || "all" });
|
|
58
|
+
const stats = await fetchBridgeStats();
|
|
59
|
+
const plan = detectPlan();
|
|
60
|
+
if (options.json) {
|
|
61
|
+
const result = buildJsonOutput(stats, plan, options.squad);
|
|
62
|
+
console.log(JSON.stringify(result, null, 2));
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
writeLine();
|
|
66
|
+
writeLine(` ${gradient("squads")} ${colors.dim}cost${RESET}${options.squad ? ` ${colors.cyan}${options.squad}${RESET}` : ""}`);
|
|
67
|
+
writeLine();
|
|
68
|
+
if (!stats) {
|
|
69
|
+
writeLine(` ${colors.yellow}\u26A0 Bridge unavailable${RESET}`);
|
|
70
|
+
writeLine(` ${colors.dim}Run \`squads login\` to connect to cloud services${RESET}`);
|
|
71
|
+
writeLine();
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const todaySection = ` ${bold}Today${RESET}`;
|
|
75
|
+
writeLine(todaySection);
|
|
76
|
+
writeLine(` ${colors.cyan}$${stats.today.costUsd.toFixed(2)}${RESET} ${colors.dim}|${RESET} ${stats.today.generations.toLocaleString()} calls ${colors.dim}|${RESET} ${formatTokens(stats.today.inputTokens + stats.today.outputTokens)} tokens`);
|
|
77
|
+
if (stats.byModel && stats.byModel.length > 0) {
|
|
78
|
+
const modelParts = stats.byModel.filter((m) => m.costUsd > 0).sort((a, b) => b.costUsd - a.costUsd).slice(0, 4).map((m) => `${colors.dim}${shortModelName(m.model)}${RESET} $${m.costUsd.toFixed(0)}`);
|
|
79
|
+
if (modelParts.length > 0) {
|
|
80
|
+
writeLine(` ${colors.dim}Models:${RESET} ${modelParts.join(" \xB7 ")}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
writeLine();
|
|
84
|
+
if (stats.week) {
|
|
85
|
+
writeLine(` ${bold}Week${RESET}`);
|
|
86
|
+
writeLine(` ${colors.purple}$${stats.week.costUsd.toFixed(2)}${RESET} ${colors.dim}|${RESET} ${stats.week.generations.toLocaleString()} calls ${colors.dim}|${RESET} ${formatTokens(stats.week.inputTokens + stats.week.outputTokens)} tokens`);
|
|
87
|
+
writeLine();
|
|
88
|
+
}
|
|
89
|
+
writeLine(` ${bold}Budget${RESET} ${colors.dim}(daily)${RESET}`);
|
|
90
|
+
const budgetBar = createBudgetBar(stats.budget.usedPct);
|
|
91
|
+
const budgetColor = stats.budget.usedPct >= 100 ? colors.red : stats.budget.usedPct >= 80 ? colors.yellow : colors.green;
|
|
92
|
+
writeLine(` ${budgetBar} ${budgetColor}$${stats.budget.used.toFixed(0)}${RESET}/${colors.dim}$${stats.budget.daily}${RESET} (${stats.budget.usedPct.toFixed(0)}%)`);
|
|
93
|
+
writeLine();
|
|
94
|
+
if (stats.bySquad && stats.bySquad.length > 0 && !options.squad) {
|
|
95
|
+
writeLine(` ${bold}By Squad${RESET}`);
|
|
96
|
+
writeLine();
|
|
97
|
+
const squadsDir = findSquadsDir();
|
|
98
|
+
const squadBudgets = /* @__PURE__ */ new Map();
|
|
99
|
+
if (squadsDir) {
|
|
100
|
+
for (const name of listSquads(squadsDir)) {
|
|
101
|
+
const squad = loadSquad(name);
|
|
102
|
+
if (squad?.context?.budget) {
|
|
103
|
+
squadBudgets.set(name, {
|
|
104
|
+
daily: squad.context.budget.daily || null,
|
|
105
|
+
weekly: squad.context.budget.weekly || null
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
const tableWidth = 52;
|
|
111
|
+
writeLine(` ${colors.purple}${box.topLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.topRight}${RESET}`);
|
|
112
|
+
const w = { name: 14, spent: 10, budget: 12, status: 14 };
|
|
113
|
+
const header = ` ${colors.purple}${box.vertical}${RESET} ${bold}${padEnd("SQUAD", w.name)}${RESET}${bold}${padEnd("SPENT", w.spent)}${RESET}${bold}${padEnd("BUDGET", w.budget)}${RESET}${bold}STATUS${RESET} ${colors.purple}${box.vertical}${RESET}`;
|
|
114
|
+
writeLine(header);
|
|
115
|
+
writeLine(` ${colors.purple}${box.teeRight}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.teeLeft}${RESET}`);
|
|
116
|
+
for (const sq of stats.bySquad.sort((a, b) => b.costUsd - a.costUsd)) {
|
|
117
|
+
const budgetInfo = squadBudgets.get(sq.squad);
|
|
118
|
+
const dailyBudget = budgetInfo?.daily || null;
|
|
119
|
+
const spentStr = `$${sq.costUsd.toFixed(2)}`;
|
|
120
|
+
const budgetStr = dailyBudget ? `$${dailyBudget}/d` : `${colors.dim}\u2014${RESET}`;
|
|
121
|
+
let statusStr;
|
|
122
|
+
if (dailyBudget) {
|
|
123
|
+
const pct = sq.costUsd / dailyBudget * 100;
|
|
124
|
+
if (pct >= 100) {
|
|
125
|
+
statusStr = `${colors.red}\u25CF OVER${RESET}`;
|
|
126
|
+
} else if (pct >= 80) {
|
|
127
|
+
statusStr = `${colors.yellow}\u25CF ${pct.toFixed(0)}%${RESET}`;
|
|
128
|
+
} else {
|
|
129
|
+
statusStr = `${colors.green}\u2713 ${pct.toFixed(0)}%${RESET}`;
|
|
130
|
+
}
|
|
131
|
+
} else {
|
|
132
|
+
statusStr = `${colors.dim}no budget${RESET}`;
|
|
133
|
+
}
|
|
134
|
+
const row = ` ${colors.purple}${box.vertical}${RESET} ${colors.cyan}${padEnd(sq.squad, w.name)}${RESET}${padEnd(spentStr, w.spent)}${padEnd(budgetStr, w.budget)}${padEnd(statusStr, w.status)}${colors.purple}${box.vertical}${RESET}`;
|
|
135
|
+
writeLine(row);
|
|
136
|
+
}
|
|
137
|
+
writeLine(` ${colors.purple}${box.bottomLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.bottomRight}${RESET}`);
|
|
138
|
+
writeLine();
|
|
139
|
+
}
|
|
140
|
+
if (options.squad) {
|
|
141
|
+
const squadStats = stats.bySquad?.find((s) => s.squad === options.squad);
|
|
142
|
+
const squad = loadSquad(options.squad);
|
|
143
|
+
if (squadStats) {
|
|
144
|
+
writeLine(` ${bold}Squad: ${options.squad}${RESET}`);
|
|
145
|
+
writeLine(` Spent today: ${colors.cyan}$${squadStats.costUsd.toFixed(2)}${RESET}`);
|
|
146
|
+
writeLine(` Calls: ${squadStats.generations.toLocaleString()}`);
|
|
147
|
+
if (squad?.context?.budget) {
|
|
148
|
+
const daily = squad.context.budget.daily;
|
|
149
|
+
const weekly = squad.context.budget.weekly;
|
|
150
|
+
if (daily) {
|
|
151
|
+
const pct = squadStats.costUsd / daily * 100;
|
|
152
|
+
const statusIcon = pct >= 100 ? `${colors.red}\u25CF${RESET}` : pct >= 80 ? `${colors.yellow}\u25CF${RESET}` : `${colors.green}\u2713${RESET}`;
|
|
153
|
+
writeLine(` Daily budget: ${statusIcon} $${squadStats.costUsd.toFixed(2)}/$${daily} (${pct.toFixed(0)}%)`);
|
|
154
|
+
}
|
|
155
|
+
if (weekly) {
|
|
156
|
+
writeLine(` Weekly budget: $${weekly}/week`);
|
|
157
|
+
}
|
|
158
|
+
} else {
|
|
159
|
+
writeLine(` ${colors.dim}No budget defined in SQUAD.md frontmatter${RESET}`);
|
|
160
|
+
}
|
|
161
|
+
} else {
|
|
162
|
+
writeLine(` ${colors.dim}No cost data for squad "${options.squad}"${RESET}`);
|
|
163
|
+
}
|
|
164
|
+
writeLine();
|
|
165
|
+
}
|
|
166
|
+
writeLine(` ${colors.dim}Plan: ${plan.plan} (${plan.reason})${RESET}`);
|
|
167
|
+
writeLine();
|
|
168
|
+
}
|
|
169
|
+
async function budgetCheckCommand(squadName, options = {}) {
|
|
170
|
+
await track(Events.CLI_COST, { action: "budget-check", squad: squadName });
|
|
171
|
+
const stats = await fetchBridgeStats();
|
|
172
|
+
const squad = loadSquad(squadName);
|
|
173
|
+
if (!stats) {
|
|
174
|
+
if (options.json) {
|
|
175
|
+
console.log(JSON.stringify({ error: "Bridge unavailable" }));
|
|
176
|
+
} else {
|
|
177
|
+
writeLine(` ${colors.yellow}\u26A0 Bridge unavailable${RESET}`);
|
|
178
|
+
}
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
const squadStats = stats.bySquad?.find((s) => s.squad === squadName);
|
|
182
|
+
const spent = squadStats?.costUsd || 0;
|
|
183
|
+
const dailyBudget = squad?.context?.budget?.daily || null;
|
|
184
|
+
const weeklyBudget = squad?.context?.budget?.weekly || null;
|
|
185
|
+
const status = getBudgetStatus(squadName, spent, dailyBudget, weeklyBudget);
|
|
186
|
+
if (options.json) {
|
|
187
|
+
console.log(JSON.stringify(status, null, 2));
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
writeLine();
|
|
191
|
+
if (status.status === "no-budget") {
|
|
192
|
+
writeLine(` ${colors.dim}\u25CB${RESET} ${colors.cyan}${squadName}${RESET}: No budget defined`);
|
|
193
|
+
writeLine(` ${colors.dim}Add budget to SQUAD.md frontmatter:${RESET}`);
|
|
194
|
+
writeLine(` ${colors.dim} context:${RESET}`);
|
|
195
|
+
writeLine(` ${colors.dim} budget: { daily: 50 }${RESET}`);
|
|
196
|
+
} else if (status.status === "over") {
|
|
197
|
+
writeLine(` ${colors.red}\u25CF${RESET} ${colors.cyan}${squadName}${RESET}: ${colors.red}OVER BUDGET${RESET}`);
|
|
198
|
+
writeLine(` $${spent.toFixed(2)}/$${dailyBudget} daily (${status.dailyPercent?.toFixed(0)}%)`);
|
|
199
|
+
} else if (status.status === "warning") {
|
|
200
|
+
writeLine(` ${colors.yellow}\u25CF${RESET} ${colors.cyan}${squadName}${RESET}: ${colors.yellow}Approaching limit${RESET}`);
|
|
201
|
+
writeLine(` $${spent.toFixed(2)}/$${dailyBudget} daily (${status.dailyPercent?.toFixed(0)}%)`);
|
|
202
|
+
} else {
|
|
203
|
+
writeLine(` ${colors.green}\u2713${RESET} ${colors.cyan}${squadName}${RESET}: OK to proceed`);
|
|
204
|
+
writeLine(` $${spent.toFixed(2)}/$${dailyBudget} daily (${status.dailyPercent?.toFixed(0)}%)`);
|
|
205
|
+
}
|
|
206
|
+
writeLine();
|
|
207
|
+
}
|
|
208
|
+
function buildJsonOutput(stats, plan, squadFilter) {
|
|
209
|
+
if (!stats) {
|
|
210
|
+
return { error: "Bridge unavailable", plan };
|
|
211
|
+
}
|
|
212
|
+
const squadsDir = findSquadsDir();
|
|
213
|
+
const squadBudgets = {};
|
|
214
|
+
if (squadsDir) {
|
|
215
|
+
for (const name of listSquads(squadsDir)) {
|
|
216
|
+
const squad = loadSquad(name);
|
|
217
|
+
if (squad?.context?.budget) {
|
|
218
|
+
squadBudgets[name] = {
|
|
219
|
+
daily: squad.context.budget.daily || null,
|
|
220
|
+
weekly: squad.context.budget.weekly || null
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
const bySquadWithBudget = stats.bySquad?.map((sq) => ({
|
|
226
|
+
...sq,
|
|
227
|
+
budget: squadBudgets[sq.squad] || null,
|
|
228
|
+
budgetStatus: squadBudgets[sq.squad]?.daily ? getBudgetStatus(sq.squad, sq.costUsd, squadBudgets[sq.squad].daily, squadBudgets[sq.squad].weekly) : null
|
|
229
|
+
}));
|
|
230
|
+
if (squadFilter) {
|
|
231
|
+
const filtered = bySquadWithBudget?.find((s) => s.squad === squadFilter);
|
|
232
|
+
return {
|
|
233
|
+
squad: squadFilter,
|
|
234
|
+
...filtered,
|
|
235
|
+
plan
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
return {
|
|
239
|
+
today: stats.today,
|
|
240
|
+
week: stats.week,
|
|
241
|
+
budget: stats.budget,
|
|
242
|
+
bySquad: bySquadWithBudget,
|
|
243
|
+
byModel: stats.byModel,
|
|
244
|
+
plan,
|
|
245
|
+
source: stats.source
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
function shortModelName(model) {
|
|
249
|
+
if (model.includes("opus")) return "opus";
|
|
250
|
+
if (model.includes("sonnet")) return "sonnet";
|
|
251
|
+
if (model.includes("haiku")) return "haiku";
|
|
252
|
+
return model.split("-")[0];
|
|
253
|
+
}
|
|
254
|
+
function formatTokens(tokens) {
|
|
255
|
+
if (tokens >= 1e6) {
|
|
256
|
+
return `${(tokens / 1e6).toFixed(1)}M`;
|
|
257
|
+
}
|
|
258
|
+
if (tokens >= 1e3) {
|
|
259
|
+
return `${(tokens / 1e3).toFixed(0)}k`;
|
|
260
|
+
}
|
|
261
|
+
return tokens.toString();
|
|
262
|
+
}
|
|
263
|
+
function createBudgetBar(percent, width = 10) {
|
|
264
|
+
const filled = Math.min(Math.round(percent / 100 * width), width);
|
|
265
|
+
const empty = width - filled;
|
|
266
|
+
const filledChar = "\u2501";
|
|
267
|
+
const emptyChar = "\u2501";
|
|
268
|
+
const color = percent >= 100 ? colors.red : percent >= 80 ? colors.yellow : colors.green;
|
|
269
|
+
return `${color}${filledChar.repeat(filled)}${colors.dim}${emptyChar.repeat(empty)}${RESET}`;
|
|
270
|
+
}
|
|
271
|
+
export {
|
|
272
|
+
budgetCheckCommand,
|
|
273
|
+
costCommand
|
|
274
|
+
};
|
|
275
|
+
//# sourceMappingURL=cost-XBCDJ7XC.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/cost.ts"],"sourcesContent":["import {\n fetchBridgeStats,\n BridgeStats,\n detectPlan,\n PlanDetection,\n} from '../lib/costs.js';\nimport {\n findSquadsDir,\n loadSquad,\n listSquads,\n} from '../lib/squad-parser.js';\nimport { track, Events } from '../lib/telemetry.js';\nimport {\n colors,\n bold,\n RESET,\n gradient,\n box,\n padEnd,\n writeLine,\n} from '../lib/terminal.js';\n\ninterface CostOptions {\n squad?: string;\n json?: boolean;\n}\n\ninterface BudgetStatus {\n squad: string;\n spent: number;\n dailyBudget: number | null;\n weeklyBudget: number | null;\n dailyPercent: number | null;\n weeklyPercent: number | null;\n status: 'ok' | 'warning' | 'over' | 'no-budget';\n}\n\nfunction getBudgetStatus(\n squadName: string,\n spent: number,\n dailyBudget: number | null,\n weeklyBudget: number | null\n): BudgetStatus {\n let status: BudgetStatus['status'] = 'no-budget';\n let dailyPercent: number | null = null;\n let weeklyPercent: number | null = null;\n\n if (dailyBudget !== null) {\n dailyPercent = (spent / dailyBudget) * 100;\n if (dailyPercent >= 100) {\n status = 'over';\n } else if (dailyPercent >= 80) {\n status = 'warning';\n } else {\n status = 'ok';\n }\n }\n\n if (weeklyBudget !== null) {\n weeklyPercent = (spent / weeklyBudget) * 100;\n }\n\n return {\n squad: squadName,\n spent,\n dailyBudget,\n weeklyBudget,\n dailyPercent,\n weeklyPercent,\n status,\n };\n}\n\nexport async function costCommand(options: CostOptions = {}): Promise<void> {\n await track(Events.CLI_COST, { squad: options.squad || 'all' });\n\n const stats = await fetchBridgeStats();\n const plan = detectPlan();\n\n if (options.json) {\n const result = buildJsonOutput(stats, plan, options.squad);\n console.log(JSON.stringify(result, null, 2));\n return;\n }\n\n writeLine();\n writeLine(` ${gradient('squads')} ${colors.dim}cost${RESET}${options.squad ? ` ${colors.cyan}${options.squad}${RESET}` : ''}`);\n writeLine();\n\n if (!stats) {\n writeLine(` ${colors.yellow}⚠ Bridge unavailable${RESET}`);\n writeLine(` ${colors.dim}Run \\`squads login\\` to connect to cloud services${RESET}`);\n writeLine();\n return;\n }\n\n // Today summary\n const todaySection = ` ${bold}Today${RESET}`;\n writeLine(todaySection);\n writeLine(` ${colors.cyan}$${stats.today.costUsd.toFixed(2)}${RESET} ${colors.dim}|${RESET} ${stats.today.generations.toLocaleString()} calls ${colors.dim}|${RESET} ${formatTokens(stats.today.inputTokens + stats.today.outputTokens)} tokens`);\n\n // Model breakdown for today\n if (stats.byModel && stats.byModel.length > 0) {\n const modelParts = stats.byModel\n .filter(m => m.costUsd > 0)\n .sort((a, b) => b.costUsd - a.costUsd)\n .slice(0, 4)\n .map(m => `${colors.dim}${shortModelName(m.model)}${RESET} $${m.costUsd.toFixed(0)}`);\n if (modelParts.length > 0) {\n writeLine(` ${colors.dim}Models:${RESET} ${modelParts.join(' · ')}`);\n }\n }\n\n writeLine();\n\n // Week summary (if available)\n if (stats.week) {\n writeLine(` ${bold}Week${RESET}`);\n writeLine(` ${colors.purple}$${stats.week.costUsd.toFixed(2)}${RESET} ${colors.dim}|${RESET} ${stats.week.generations.toLocaleString()} calls ${colors.dim}|${RESET} ${formatTokens(stats.week.inputTokens + stats.week.outputTokens)} tokens`);\n writeLine();\n }\n\n // Budget status\n writeLine(` ${bold}Budget${RESET} ${colors.dim}(daily)${RESET}`);\n const budgetBar = createBudgetBar(stats.budget.usedPct);\n const budgetColor = stats.budget.usedPct >= 100 ? colors.red :\n stats.budget.usedPct >= 80 ? colors.yellow : colors.green;\n writeLine(` ${budgetBar} ${budgetColor}$${stats.budget.used.toFixed(0)}${RESET}/${colors.dim}$${stats.budget.daily}${RESET} (${stats.budget.usedPct.toFixed(0)}%)`);\n writeLine();\n\n // Per-squad breakdown with budget comparison\n if (stats.bySquad && stats.bySquad.length > 0 && !options.squad) {\n writeLine(` ${bold}By Squad${RESET}`);\n writeLine();\n\n const squadsDir = findSquadsDir();\n const squadBudgets = new Map<string, { daily: number | null; weekly: number | null }>();\n\n if (squadsDir) {\n for (const name of listSquads(squadsDir)) {\n const squad = loadSquad(name);\n if (squad?.context?.budget) {\n squadBudgets.set(name, {\n daily: squad.context.budget.daily || null,\n weekly: squad.context.budget.weekly || null,\n });\n }\n }\n }\n\n const tableWidth = 52;\n writeLine(` ${colors.purple}${box.topLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.topRight}${RESET}`);\n\n const w = { name: 14, spent: 10, budget: 12, status: 14 };\n const header = ` ${colors.purple}${box.vertical}${RESET} ` +\n `${bold}${padEnd('SQUAD', w.name)}${RESET}` +\n `${bold}${padEnd('SPENT', w.spent)}${RESET}` +\n `${bold}${padEnd('BUDGET', w.budget)}${RESET}` +\n `${bold}STATUS${RESET}` +\n ` ${colors.purple}${box.vertical}${RESET}`;\n writeLine(header);\n\n writeLine(` ${colors.purple}${box.teeRight}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.teeLeft}${RESET}`);\n\n for (const sq of stats.bySquad.sort((a, b) => b.costUsd - a.costUsd)) {\n const budgetInfo = squadBudgets.get(sq.squad);\n const dailyBudget = budgetInfo?.daily || null;\n\n const spentStr = `$${sq.costUsd.toFixed(2)}`;\n const budgetStr = dailyBudget ? `$${dailyBudget}/d` : `${colors.dim}—${RESET}`;\n\n let statusStr: string;\n if (dailyBudget) {\n const pct = (sq.costUsd / dailyBudget) * 100;\n if (pct >= 100) {\n statusStr = `${colors.red}● OVER${RESET}`;\n } else if (pct >= 80) {\n statusStr = `${colors.yellow}● ${pct.toFixed(0)}%${RESET}`;\n } else {\n statusStr = `${colors.green}✓ ${pct.toFixed(0)}%${RESET}`;\n }\n } else {\n statusStr = `${colors.dim}no budget${RESET}`;\n }\n\n const row = ` ${colors.purple}${box.vertical}${RESET} ` +\n `${colors.cyan}${padEnd(sq.squad, w.name)}${RESET}` +\n `${padEnd(spentStr, w.spent)}` +\n `${padEnd(budgetStr, w.budget)}` +\n `${padEnd(statusStr, w.status)}` +\n `${colors.purple}${box.vertical}${RESET}`;\n\n writeLine(row);\n }\n\n writeLine(` ${colors.purple}${box.bottomLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.bottomRight}${RESET}`);\n writeLine();\n }\n\n // Single squad detail\n if (options.squad) {\n const squadStats = stats.bySquad?.find(s => s.squad === options.squad);\n const squad = loadSquad(options.squad);\n\n if (squadStats) {\n writeLine(` ${bold}Squad: ${options.squad}${RESET}`);\n writeLine(` Spent today: ${colors.cyan}$${squadStats.costUsd.toFixed(2)}${RESET}`);\n writeLine(` Calls: ${squadStats.generations.toLocaleString()}`);\n\n if (squad?.context?.budget) {\n const daily = squad.context.budget.daily;\n const weekly = squad.context.budget.weekly;\n\n if (daily) {\n const pct = (squadStats.costUsd / daily) * 100;\n const statusIcon = pct >= 100 ? `${colors.red}●${RESET}` :\n pct >= 80 ? `${colors.yellow}●${RESET}` : `${colors.green}✓${RESET}`;\n writeLine(` Daily budget: ${statusIcon} $${squadStats.costUsd.toFixed(2)}/$${daily} (${pct.toFixed(0)}%)`);\n }\n\n if (weekly) {\n writeLine(` Weekly budget: $${weekly}/week`);\n }\n } else {\n writeLine(` ${colors.dim}No budget defined in SQUAD.md frontmatter${RESET}`);\n }\n } else {\n writeLine(` ${colors.dim}No cost data for squad \"${options.squad}\"${RESET}`);\n }\n writeLine();\n }\n\n // Plan info\n writeLine(` ${colors.dim}Plan: ${plan.plan} (${plan.reason})${RESET}`);\n writeLine();\n}\n\nexport async function budgetCheckCommand(\n squadName: string,\n options: { json?: boolean } = {}\n): Promise<void> {\n await track(Events.CLI_COST, { action: 'budget-check', squad: squadName });\n\n const stats = await fetchBridgeStats();\n const squad = loadSquad(squadName);\n\n if (!stats) {\n if (options.json) {\n console.log(JSON.stringify({ error: 'Bridge unavailable' }));\n } else {\n writeLine(` ${colors.yellow}⚠ Bridge unavailable${RESET}`);\n }\n return;\n }\n\n const squadStats = stats.bySquad?.find(s => s.squad === squadName);\n const spent = squadStats?.costUsd || 0;\n const dailyBudget = squad?.context?.budget?.daily || null;\n const weeklyBudget = squad?.context?.budget?.weekly || null;\n\n const status = getBudgetStatus(squadName, spent, dailyBudget, weeklyBudget);\n\n if (options.json) {\n console.log(JSON.stringify(status, null, 2));\n return;\n }\n\n writeLine();\n\n if (status.status === 'no-budget') {\n writeLine(` ${colors.dim}○${RESET} ${colors.cyan}${squadName}${RESET}: No budget defined`);\n writeLine(` ${colors.dim}Add budget to SQUAD.md frontmatter:${RESET}`);\n writeLine(` ${colors.dim} context:${RESET}`);\n writeLine(` ${colors.dim} budget: { daily: 50 }${RESET}`);\n } else if (status.status === 'over') {\n writeLine(` ${colors.red}●${RESET} ${colors.cyan}${squadName}${RESET}: ${colors.red}OVER BUDGET${RESET}`);\n writeLine(` $${spent.toFixed(2)}/$${dailyBudget} daily (${status.dailyPercent?.toFixed(0)}%)`);\n } else if (status.status === 'warning') {\n writeLine(` ${colors.yellow}●${RESET} ${colors.cyan}${squadName}${RESET}: ${colors.yellow}Approaching limit${RESET}`);\n writeLine(` $${spent.toFixed(2)}/$${dailyBudget} daily (${status.dailyPercent?.toFixed(0)}%)`);\n } else {\n writeLine(` ${colors.green}✓${RESET} ${colors.cyan}${squadName}${RESET}: OK to proceed`);\n writeLine(` $${spent.toFixed(2)}/$${dailyBudget} daily (${status.dailyPercent?.toFixed(0)}%)`);\n }\n\n writeLine();\n}\n\nfunction buildJsonOutput(\n stats: BridgeStats | null,\n plan: PlanDetection,\n squadFilter?: string\n): object {\n if (!stats) {\n return { error: 'Bridge unavailable', plan };\n }\n\n const squadsDir = findSquadsDir();\n const squadBudgets: Record<string, { daily: number | null; weekly: number | null }> = {};\n\n if (squadsDir) {\n for (const name of listSquads(squadsDir)) {\n const squad = loadSquad(name);\n if (squad?.context?.budget) {\n squadBudgets[name] = {\n daily: squad.context.budget.daily || null,\n weekly: squad.context.budget.weekly || null,\n };\n }\n }\n }\n\n const bySquadWithBudget = stats.bySquad?.map(sq => ({\n ...sq,\n budget: squadBudgets[sq.squad] || null,\n budgetStatus: squadBudgets[sq.squad]?.daily\n ? getBudgetStatus(sq.squad, sq.costUsd, squadBudgets[sq.squad].daily, squadBudgets[sq.squad].weekly)\n : null,\n }));\n\n if (squadFilter) {\n const filtered = bySquadWithBudget?.find(s => s.squad === squadFilter);\n return {\n squad: squadFilter,\n ...filtered,\n plan,\n };\n }\n\n return {\n today: stats.today,\n week: stats.week,\n budget: stats.budget,\n bySquad: bySquadWithBudget,\n byModel: stats.byModel,\n plan,\n source: stats.source,\n };\n}\n\nfunction shortModelName(model: string): string {\n if (model.includes('opus')) return 'opus';\n if (model.includes('sonnet')) return 'sonnet';\n if (model.includes('haiku')) return 'haiku';\n return model.split('-')[0];\n}\n\nfunction formatTokens(tokens: number): string {\n if (tokens >= 1_000_000) {\n return `${(tokens / 1_000_000).toFixed(1)}M`;\n }\n if (tokens >= 1_000) {\n return `${(tokens / 1_000).toFixed(0)}k`;\n }\n return tokens.toString();\n}\n\nfunction createBudgetBar(percent: number, width = 10): string {\n const filled = Math.min(Math.round((percent / 100) * width), width);\n const empty = width - filled;\n\n const filledChar = '━';\n const emptyChar = '━';\n\n const color = percent >= 100 ? colors.red :\n percent >= 80 ? colors.yellow : colors.green;\n\n return `${color}${filledChar.repeat(filled)}${colors.dim}${emptyChar.repeat(empty)}${RESET}`;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
loadTemplate,
|
|
4
|
+
toKebabCase,
|
|
5
|
+
toTitleCase
|
|
6
|
+
} from "./chunk-FFFCFZ6A.js";
|
|
7
|
+
import {
|
|
8
|
+
createGitHubRepo,
|
|
9
|
+
detectGitHubOrg
|
|
10
|
+
} from "./chunk-FIWT2NMM.js";
|
|
11
|
+
import {
|
|
12
|
+
track
|
|
13
|
+
} from "./chunk-QJ7C7CMB.js";
|
|
14
|
+
import {
|
|
15
|
+
findProjectRoot,
|
|
16
|
+
findSquadsDir,
|
|
17
|
+
listSquads
|
|
18
|
+
} from "./chunk-TYFTF53O.js";
|
|
19
|
+
import "./chunk-7OCVIDC7.js";
|
|
20
|
+
|
|
21
|
+
// src/commands/create.ts
|
|
22
|
+
import { existsSync, mkdirSync, writeFileSync } from "fs";
|
|
23
|
+
import { join } from "path";
|
|
24
|
+
import chalk from "chalk";
|
|
25
|
+
|
|
26
|
+
// src/lib/slack.ts
|
|
27
|
+
var SLACK_BOT_TOKEN = process.env.SLACK_BOT_TOKEN;
|
|
28
|
+
async function slackApi(method, endpoint, body) {
|
|
29
|
+
if (!SLACK_BOT_TOKEN) {
|
|
30
|
+
throw new Error("SLACK_BOT_TOKEN not set in environment");
|
|
31
|
+
}
|
|
32
|
+
const url = `https://slack.com/api/${endpoint}`;
|
|
33
|
+
const options = {
|
|
34
|
+
method,
|
|
35
|
+
headers: {
|
|
36
|
+
Authorization: `Bearer ${SLACK_BOT_TOKEN}`,
|
|
37
|
+
"Content-Type": "application/json"
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
if (body) {
|
|
41
|
+
options.body = JSON.stringify(body);
|
|
42
|
+
}
|
|
43
|
+
const res = await fetch(url, options);
|
|
44
|
+
const data = await res.json();
|
|
45
|
+
if (!data.ok) {
|
|
46
|
+
throw new Error(`Slack API error: ${data.error}`);
|
|
47
|
+
}
|
|
48
|
+
return data;
|
|
49
|
+
}
|
|
50
|
+
function isSlackConfigured() {
|
|
51
|
+
return !!SLACK_BOT_TOKEN;
|
|
52
|
+
}
|
|
53
|
+
async function getSquadChannelId(squad) {
|
|
54
|
+
const channelName = `squad-${squad}`;
|
|
55
|
+
try {
|
|
56
|
+
const response = await slackApi(
|
|
57
|
+
"GET",
|
|
58
|
+
"conversations.list?types=public_channel&limit=200"
|
|
59
|
+
);
|
|
60
|
+
const channel = response.channels?.find((c) => c.name === channelName);
|
|
61
|
+
return channel?.id || null;
|
|
62
|
+
} catch {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
async function createSquadChannel(squadId, topic) {
|
|
67
|
+
if (!isSlackConfigured()) return null;
|
|
68
|
+
const channelName = `squad-${squadId}`;
|
|
69
|
+
try {
|
|
70
|
+
const response = await slackApi("POST", "conversations.create", {
|
|
71
|
+
name: channelName,
|
|
72
|
+
is_private: false
|
|
73
|
+
});
|
|
74
|
+
const channelId = response.channel?.id || null;
|
|
75
|
+
if (channelId && topic) {
|
|
76
|
+
await slackApi("POST", "conversations.setTopic", {
|
|
77
|
+
channel: channelId,
|
|
78
|
+
topic
|
|
79
|
+
}).catch(() => {
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
return channelId;
|
|
83
|
+
} catch (err) {
|
|
84
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
85
|
+
if (message.includes("name_taken")) {
|
|
86
|
+
return getSquadChannelId(squadId);
|
|
87
|
+
}
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// src/commands/create.ts
|
|
93
|
+
async function createCommand(name, options) {
|
|
94
|
+
const squadId = toKebabCase(name);
|
|
95
|
+
const squadName = toTitleCase(squadId);
|
|
96
|
+
if (!squadId) {
|
|
97
|
+
console.error(chalk.red("\n Invalid squad name. Use letters, numbers, and hyphens.\n"));
|
|
98
|
+
process.exit(1);
|
|
99
|
+
}
|
|
100
|
+
const projectRoot = findProjectRoot();
|
|
101
|
+
if (!projectRoot) {
|
|
102
|
+
console.error(chalk.red("\n Not in a squads project. Run `squads init` first.\n"));
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
const squadsDir = findSquadsDir();
|
|
106
|
+
if (!squadsDir) {
|
|
107
|
+
console.error(chalk.red("\n No .agents/squads directory found. Run `squads init` first.\n"));
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
110
|
+
const squadDir = join(squadsDir, squadId);
|
|
111
|
+
if (existsSync(join(squadDir, "SQUAD.md")) && !options.force) {
|
|
112
|
+
console.error(chalk.red(`
|
|
113
|
+
Squad "${squadId}" already exists. Use --force to overwrite.
|
|
114
|
+
`));
|
|
115
|
+
process.exit(1);
|
|
116
|
+
}
|
|
117
|
+
let description = options.description;
|
|
118
|
+
let goal = options.goal;
|
|
119
|
+
if (!options.yes && (!description || !goal)) {
|
|
120
|
+
const inquirer = await import("inquirer");
|
|
121
|
+
if (!description) {
|
|
122
|
+
const answer = await inquirer.default.prompt([{
|
|
123
|
+
type: "input",
|
|
124
|
+
name: "description",
|
|
125
|
+
message: "Squad mission (one sentence):",
|
|
126
|
+
default: `The ${squadName} squad handles ${squadId}-related tasks.`
|
|
127
|
+
}]);
|
|
128
|
+
description = answer.description;
|
|
129
|
+
}
|
|
130
|
+
if (!goal) {
|
|
131
|
+
const answer = await inquirer.default.prompt([{
|
|
132
|
+
type: "input",
|
|
133
|
+
name: "goal",
|
|
134
|
+
message: "First goal:",
|
|
135
|
+
default: `Define ${squadId} squad objectives and deliver first results`
|
|
136
|
+
}]);
|
|
137
|
+
goal = answer.goal;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
description = description || `The ${squadName} squad handles ${squadId}-related tasks.`;
|
|
141
|
+
goal = goal || `Define ${squadId} squad objectives and deliver first results`;
|
|
142
|
+
const model = options.model || "sonnet";
|
|
143
|
+
const vars = {
|
|
144
|
+
SQUAD_NAME: squadName,
|
|
145
|
+
SQUAD_ID: squadId,
|
|
146
|
+
SQUAD_DESCRIPTION: description,
|
|
147
|
+
GOAL: goal
|
|
148
|
+
};
|
|
149
|
+
mkdirSync(squadDir, { recursive: true });
|
|
150
|
+
let squadContent;
|
|
151
|
+
try {
|
|
152
|
+
squadContent = loadTemplate("first-squad/SQUAD.md.template", vars);
|
|
153
|
+
} catch {
|
|
154
|
+
squadContent = `# ${squadName}
|
|
155
|
+
|
|
156
|
+
${description}
|
|
157
|
+
|
|
158
|
+
## Goals
|
|
159
|
+
|
|
160
|
+
- [ ] ${goal}
|
|
161
|
+
|
|
162
|
+
## Agents
|
|
163
|
+
|
|
164
|
+
| Agent | Purpose |
|
|
165
|
+
|-------|--------|
|
|
166
|
+
| lead | Orchestrates the squad and coordinates work |
|
|
167
|
+
|
|
168
|
+
## Pipeline
|
|
169
|
+
|
|
170
|
+
\`lead\` (orchestrates all work)
|
|
171
|
+
|
|
172
|
+
## Usage
|
|
173
|
+
|
|
174
|
+
\`\`\`bash
|
|
175
|
+
squads run ${squadId} --execute
|
|
176
|
+
\`\`\`
|
|
177
|
+
`;
|
|
178
|
+
}
|
|
179
|
+
writeFileSync(join(squadDir, "SQUAD.md"), squadContent);
|
|
180
|
+
let leadContent;
|
|
181
|
+
try {
|
|
182
|
+
leadContent = loadTemplate("first-squad/lead.md.template", vars);
|
|
183
|
+
} catch {
|
|
184
|
+
leadContent = `# Lead Agent
|
|
185
|
+
|
|
186
|
+
## Purpose
|
|
187
|
+
Orchestrate the ${squadName} squad to achieve its goals.
|
|
188
|
+
|
|
189
|
+
## Model
|
|
190
|
+
${model}
|
|
191
|
+
|
|
192
|
+
## Tools
|
|
193
|
+
- Read
|
|
194
|
+
- Write
|
|
195
|
+
- Edit
|
|
196
|
+
- Bash
|
|
197
|
+
- WebSearch
|
|
198
|
+
- WebFetch
|
|
199
|
+
- Task
|
|
200
|
+
|
|
201
|
+
## Instructions
|
|
202
|
+
|
|
203
|
+
You are the lead agent for the ${squadName} squad.
|
|
204
|
+
|
|
205
|
+
**Goal**: ${goal}
|
|
206
|
+
|
|
207
|
+
### Approach
|
|
208
|
+
|
|
209
|
+
1. **Understand the goal** - Break down what needs to be accomplished
|
|
210
|
+
2. **Plan the work** - Create a clear execution plan
|
|
211
|
+
3. **Execute** - Work through the plan step by step
|
|
212
|
+
4. **Verify** - Confirm the goal is achieved
|
|
213
|
+
5. **Document** - Update memory with learnings
|
|
214
|
+
|
|
215
|
+
## Output
|
|
216
|
+
Progress updates and work artifacts as appropriate.
|
|
217
|
+
|
|
218
|
+
## Labels
|
|
219
|
+
- lead
|
|
220
|
+
- orchestration
|
|
221
|
+
`;
|
|
222
|
+
}
|
|
223
|
+
writeFileSync(join(squadDir, "lead.md"), leadContent);
|
|
224
|
+
const memoryDir = join(projectRoot, ".agents", "memory", squadId, "lead");
|
|
225
|
+
mkdirSync(memoryDir, { recursive: true });
|
|
226
|
+
await track("cli.create", {
|
|
227
|
+
squad: squadId,
|
|
228
|
+
hasDescription: !!options.description,
|
|
229
|
+
hasGoal: !!options.goal,
|
|
230
|
+
force: !!options.force,
|
|
231
|
+
repo: !!options.repo
|
|
232
|
+
}).catch(() => {
|
|
233
|
+
});
|
|
234
|
+
let slackChannelId = null;
|
|
235
|
+
if (options.slack) {
|
|
236
|
+
slackChannelId = await createSquadChannel(squadId, `Channel for the ${squadName} squad`);
|
|
237
|
+
if (!slackChannelId) {
|
|
238
|
+
console.error(chalk.red("\n Slack channel creation failed (check SLACK_BOT_TOKEN).\n"));
|
|
239
|
+
console.error(chalk.dim(" Local squad was created. Create the channel manually.\n"));
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
let repoUrl;
|
|
243
|
+
if (options.repo) {
|
|
244
|
+
const org = options.org ?? detectGitHubOrg();
|
|
245
|
+
try {
|
|
246
|
+
const result = createGitHubRepo(squadId, { org, description });
|
|
247
|
+
repoUrl = result.url;
|
|
248
|
+
} catch (err) {
|
|
249
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
250
|
+
console.error(chalk.red(`
|
|
251
|
+
GitHub repo creation failed: ${message}
|
|
252
|
+
`));
|
|
253
|
+
console.error(chalk.dim(" Local squad was created. Create the repo manually with:"));
|
|
254
|
+
console.error(chalk.dim(` gh repo create ${org ? `${org}/` : ""}${squadId} --private
|
|
255
|
+
`));
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
const existing = listSquads(squadsDir);
|
|
259
|
+
console.log();
|
|
260
|
+
console.log(chalk.green(" \u2713 Squad created:"), chalk.cyan(squadId));
|
|
261
|
+
console.log();
|
|
262
|
+
console.log(chalk.dim(" Files:"));
|
|
263
|
+
console.log(` .agents/squads/${squadId}/SQUAD.md`);
|
|
264
|
+
console.log(` .agents/squads/${squadId}/lead.md`);
|
|
265
|
+
console.log(` .agents/memory/${squadId}/lead/`);
|
|
266
|
+
if (slackChannelId) {
|
|
267
|
+
console.log();
|
|
268
|
+
console.log(chalk.dim(" Slack channel:"));
|
|
269
|
+
console.log(` ${chalk.cyan(`#squad-${squadId}`)}`);
|
|
270
|
+
}
|
|
271
|
+
if (repoUrl) {
|
|
272
|
+
console.log();
|
|
273
|
+
console.log(chalk.dim(" GitHub repo:"));
|
|
274
|
+
console.log(` ${chalk.cyan(repoUrl)}`);
|
|
275
|
+
}
|
|
276
|
+
console.log();
|
|
277
|
+
console.log(chalk.dim(" Next steps:"));
|
|
278
|
+
console.log(` ${chalk.cyan("$")} squads run ${squadId} ${chalk.dim("# run the squad")}`);
|
|
279
|
+
console.log(` ${chalk.cyan("$")} squads status ${squadId} ${chalk.dim("# check status")}`);
|
|
280
|
+
console.log(` ${chalk.cyan("$")} squads status ${chalk.dim(`# ${existing.length} squads total`)}`);
|
|
281
|
+
console.log();
|
|
282
|
+
}
|
|
283
|
+
export {
|
|
284
|
+
createCommand
|
|
285
|
+
};
|
|
286
|
+
//# sourceMappingURL=create-BLFGG6PF.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/create.ts","../src/lib/slack.ts"],"sourcesContent":["/**\n * squads add <name> — Add a new squad with directory structure and starter files.\n *\n * Creates:\n * .agents/squads/<name>/SQUAD.md\n * .agents/squads/<name>/lead.md\n * .agents/memory/<name>/lead/ (empty, ready for state)\n *\n * Squad discovery is filesystem-based (squad-parser.ts), so creating the\n * directory + SQUAD.md is all that's needed for `squads status` to find it.\n */\n\nimport { existsSync, mkdirSync, writeFileSync } from 'fs';\nimport { join } from 'path';\nimport chalk from 'chalk';\nimport { findSquadsDir, findProjectRoot, listSquads } from '../lib/squad-parser.js';\nimport { loadTemplate, toKebabCase, toTitleCase, type TemplateVariables } from '../lib/templates.js';\nimport { track } from '../lib/telemetry.js';\nimport { createGitHubRepo, detectGitHubOrg } from '../lib/github.js';\nimport { createSquadChannel } from '../lib/slack.js';\n\ninterface CreateOptions {\n description?: string;\n goal?: string;\n model?: string;\n force?: boolean;\n yes?: boolean;\n repo?: boolean;\n org?: string;\n slack?: boolean;\n}\n\nexport async function createCommand(name: string, options: CreateOptions): Promise<void> {\n const squadId = toKebabCase(name);\n const squadName = toTitleCase(squadId);\n\n if (!squadId) {\n console.error(chalk.red('\\n Invalid squad name. Use letters, numbers, and hyphens.\\n'));\n process.exit(1);\n }\n\n // Find project root (where .agents/ lives)\n const projectRoot = findProjectRoot();\n if (!projectRoot) {\n console.error(chalk.red('\\n Not in a squads project. Run `squads init` first.\\n'));\n process.exit(1);\n }\n\n const squadsDir = findSquadsDir();\n if (!squadsDir) {\n console.error(chalk.red('\\n No .agents/squads directory found. Run `squads init` first.\\n'));\n process.exit(1);\n }\n\n // Check if squad already exists\n const squadDir = join(squadsDir, squadId);\n if (existsSync(join(squadDir, 'SQUAD.md')) && !options.force) {\n console.error(chalk.red(`\\n Squad \"${squadId}\" already exists. Use --force to overwrite.\\n`));\n process.exit(1);\n }\n\n // Collect description and goal — prompt interactively if not provided via flags\n let description = options.description;\n let goal = options.goal;\n\n if (!options.yes && (!description || !goal)) {\n const inquirer = await import('inquirer');\n\n if (!description) {\n const answer = await inquirer.default.prompt([{\n type: 'input',\n name: 'description',\n message: 'Squad mission (one sentence):',\n default: `The ${squadName} squad handles ${squadId}-related tasks.`,\n }]);\n description = answer.description;\n }\n\n if (!goal) {\n const answer = await inquirer.default.prompt([{\n type: 'input',\n name: 'goal',\n message: 'First goal:',\n default: `Define ${squadId} squad objectives and deliver first results`,\n }]);\n goal = answer.goal;\n }\n }\n\n // Defaults for non-interactive mode\n description = description || `The ${squadName} squad handles ${squadId}-related tasks.`;\n goal = goal || `Define ${squadId} squad objectives and deliver first results`;\n const model = options.model || 'sonnet';\n\n // Template variables\n const vars: TemplateVariables = {\n SQUAD_NAME: squadName,\n SQUAD_ID: squadId,\n SQUAD_DESCRIPTION: description,\n GOAL: goal,\n };\n\n // 1. Create squad directory\n mkdirSync(squadDir, { recursive: true });\n\n // 2. Create SQUAD.md from template\n let squadContent: string;\n try {\n squadContent = loadTemplate('first-squad/SQUAD.md.template', vars);\n } catch {\n // Fallback if template not found\n squadContent = `# ${squadName}\\n\\n${description}\\n\\n## Goals\\n\\n- [ ] ${goal}\\n\\n## Agents\\n\\n| Agent | Purpose |\\n|-------|--------|\\n| lead | Orchestrates the squad and coordinates work |\\n\\n## Pipeline\\n\\n\\`lead\\` (orchestrates all work)\\n\\n## Usage\\n\\n\\`\\`\\`bash\\nsquads run ${squadId} --execute\\n\\`\\`\\`\\n`;\n }\n writeFileSync(join(squadDir, 'SQUAD.md'), squadContent);\n\n // 3. Create lead agent from template\n let leadContent: string;\n try {\n leadContent = loadTemplate('first-squad/lead.md.template', vars);\n } catch {\n // Fallback if template not found\n leadContent = `# Lead Agent\\n\\n## Purpose\\nOrchestrate the ${squadName} squad to achieve its goals.\\n\\n## Model\\n${model}\\n\\n## Tools\\n- Read\\n- Write\\n- Edit\\n- Bash\\n- WebSearch\\n- WebFetch\\n- Task\\n\\n## Instructions\\n\\nYou are the lead agent for the ${squadName} squad.\\n\\n**Goal**: ${goal}\\n\\n### Approach\\n\\n1. **Understand the goal** - Break down what needs to be accomplished\\n2. **Plan the work** - Create a clear execution plan\\n3. **Execute** - Work through the plan step by step\\n4. **Verify** - Confirm the goal is achieved\\n5. **Document** - Update memory with learnings\\n\\n## Output\\nProgress updates and work artifacts as appropriate.\\n\\n## Labels\\n- lead\\n- orchestration\\n`;\n }\n writeFileSync(join(squadDir, 'lead.md'), leadContent);\n\n // 4. Create memory directories\n const memoryDir = join(projectRoot, '.agents', 'memory', squadId, 'lead');\n mkdirSync(memoryDir, { recursive: true });\n\n // Track creation event\n await track('cli.create', {\n squad: squadId,\n hasDescription: !!options.description,\n hasGoal: !!options.goal,\n force: !!options.force,\n repo: !!options.repo,\n }).catch(() => {});\n\n // 5. Create Slack channel if --slack flag is set\n let slackChannelId: string | null = null;\n if (options.slack) {\n slackChannelId = await createSquadChannel(squadId, `Channel for the ${squadName} squad`);\n if (!slackChannelId) {\n console.error(chalk.red('\\n Slack channel creation failed (check SLACK_BOT_TOKEN).\\n'));\n console.error(chalk.dim(' Local squad was created. Create the channel manually.\\n'));\n }\n }\n\n // 6. Create GitHub repo if --repo flag is set\n let repoUrl: string | undefined;\n if (options.repo) {\n const org = options.org ?? detectGitHubOrg();\n try {\n const result = createGitHubRepo(squadId, { org, description });\n repoUrl = result.url;\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n console.error(chalk.red(`\\n GitHub repo creation failed: ${message}\\n`));\n console.error(chalk.dim(' Local squad was created. Create the repo manually with:'));\n console.error(chalk.dim(` gh repo create ${org ? `${org}/` : ''}${squadId} --private\\n`));\n }\n }\n\n // 7. Show success\n const existing = listSquads(squadsDir);\n console.log();\n console.log(chalk.green(' ✓ Squad created:'), chalk.cyan(squadId));\n console.log();\n console.log(chalk.dim(' Files:'));\n console.log(` .agents/squads/${squadId}/SQUAD.md`);\n console.log(` .agents/squads/${squadId}/lead.md`);\n console.log(` .agents/memory/${squadId}/lead/`);\n if (slackChannelId) {\n console.log();\n console.log(chalk.dim(' Slack channel:'));\n console.log(` ${chalk.cyan(`#squad-${squadId}`)}`);\n }\n if (repoUrl) {\n console.log();\n console.log(chalk.dim(' GitHub repo:'));\n console.log(` ${chalk.cyan(repoUrl)}`);\n }\n console.log();\n console.log(chalk.dim(' Next steps:'));\n console.log(` ${chalk.cyan('$')} squads run ${squadId} ${chalk.dim('# run the squad')}`);\n console.log(` ${chalk.cyan('$')} squads status ${squadId} ${chalk.dim('# check status')}`);\n console.log(` ${chalk.cyan('$')} squads status ${chalk.dim(`# ${existing.length} squads total`)}`);\n console.log();\n}\n","/**\n * Slack integration for squad notifications and approvals.\n *\n * Provides:\n * - Session notifications (start/complete/fail)\n * - Approval requests with interactive buttons\n * - Approval polling (wait for human response)\n */\n\nimport { readFileSync, existsSync } from 'fs';\nimport { join } from 'path';\nimport { findSquadsDir } from './squad-parser.js';\n\nconst SLACK_BOT_TOKEN = process.env.SLACK_BOT_TOKEN;\n\n// Approval tiers from SQUAD.md\nexport type ApprovalTier = 'auto' | 'notify' | 'approve' | 'confirm';\n\nexport interface SlackChannel {\n id: string;\n name: string;\n}\n\ninterface SlackApiResponse {\n ok: boolean;\n error?: string;\n ts?: string;\n channel?: SlackChannel;\n channels?: SlackChannel[];\n messages?: SlackMessage[];\n}\n\ninterface SlackMessage {\n ts: string;\n text: string;\n blocks?: SlackBlock[];\n reactions?: { name: string }[];\n}\n\ninterface SlackBlock {\n type: string;\n elements?: { text?: string }[];\n}\n\n/**\n * Make a Slack API call\n */\nexport async function slackApi<T = SlackApiResponse>(\n method: 'GET' | 'POST',\n endpoint: string,\n body?: Record<string, unknown>\n): Promise<T> {\n if (!SLACK_BOT_TOKEN) {\n throw new Error('SLACK_BOT_TOKEN not set in environment');\n }\n\n const url = `https://slack.com/api/${endpoint}`;\n const options: RequestInit = {\n method,\n headers: {\n Authorization: `Bearer ${SLACK_BOT_TOKEN}`,\n 'Content-Type': 'application/json',\n },\n };\n\n if (body) {\n options.body = JSON.stringify(body);\n }\n\n const res = await fetch(url, options);\n const data = await res.json() as SlackApiResponse;\n\n if (!data.ok) {\n throw new Error(`Slack API error: ${data.error}`);\n }\n\n return data as T;\n}\n\n/**\n * Check if Slack is configured\n */\nexport function isSlackConfigured(): boolean {\n return !!SLACK_BOT_TOKEN;\n}\n\n/**\n * Get channel ID for a squad (format: squad-<name>)\n */\nexport async function getSquadChannelId(squad: string): Promise<string | null> {\n const channelName = `squad-${squad}`;\n\n try {\n const response = await slackApi<SlackApiResponse>(\n 'GET',\n 'conversations.list?types=public_channel&limit=200'\n );\n\n const channel = response.channels?.find((c) => c.name === channelName);\n return channel?.id || null;\n } catch {\n return null;\n }\n}\n\n/**\n * Parse approval tier for an action from SQUAD.md\n */\nexport function getApprovalTier(squad: string, action: string): ApprovalTier {\n const squadsDir = findSquadsDir();\n if (!squadsDir) return 'approve'; // Safe default\n\n const squadFile = join(squadsDir, squad, 'SQUAD.md');\n if (!existsSync(squadFile)) return 'approve';\n\n const content = readFileSync(squadFile, 'utf-8');\n\n // Extract YAML block (between ```yaml and ```)\n const yamlMatch = content.match(/```yaml\\n([\\s\\S]*?)```/);\n if (!yamlMatch) return 'approve';\n\n const yaml = yamlMatch[1];\n\n // Check each tier (order matters - most restrictive first)\n if (yaml.includes('confirm:') && yaml.includes(`- ${action}`)) {\n const confirmSection = yaml.split('confirm:')[1]?.split(/\\n\\s*\\w+:/)[0];\n if (confirmSection?.includes(`- ${action}`)) return 'confirm';\n }\n\n if (yaml.includes('approve:') && yaml.includes(`- ${action}`)) {\n const approveSection = yaml.split('approve:')[1]?.split(/\\n\\s*\\w+:/)[0];\n if (approveSection?.includes(`- ${action}`)) return 'approve';\n }\n\n if (yaml.includes('notify:') && yaml.includes(`- ${action}`)) {\n const notifySection = yaml.split('notify:')[1]?.split(/\\n\\s*\\w+:/)[0];\n if (notifySection?.includes(`- ${action}`)) return 'notify';\n }\n\n if (yaml.includes('auto:') && yaml.includes(`- ${action}`)) {\n const autoSection = yaml.split('auto:')[1]?.split(/\\n\\s*\\w+:/)[0];\n if (autoSection?.includes(`- ${action}`)) return 'auto';\n }\n\n // Default: require approval (safe default)\n return 'approve';\n}\n\n/**\n * Post a notification to a squad channel (no approval needed)\n */\nexport async function postNotification(\n squad: string,\n message: string,\n options?: {\n emoji?: string;\n context?: string;\n }\n): Promise<string | null> {\n if (!isSlackConfigured()) return null;\n\n const channelId = await getSquadChannelId(squad);\n if (!channelId) return null;\n\n const emoji = options?.emoji || ':robot_face:';\n // Type blocks array to accept different Slack block types\n const blocks: Array<Record<string, unknown>> = [\n {\n type: 'section',\n text: {\n type: 'mrkdwn',\n text: `${emoji} ${message}`,\n },\n },\n ];\n\n if (options?.context) {\n blocks.push({\n type: 'context',\n elements: [{ type: 'mrkdwn', text: options.context }],\n });\n }\n\n try {\n const response = await slackApi<SlackApiResponse>('POST', 'chat.postMessage', {\n channel: channelId,\n text: message,\n blocks,\n });\n return response.ts || null;\n } catch {\n return null;\n }\n}\n\n/**\n * Post an approval request with interactive buttons\n */\nexport async function postApprovalRequest(\n squad: string,\n action: string,\n description: string,\n options?: {\n agent?: string;\n tier?: ApprovalTier;\n }\n): Promise<{ ts: string; channelId: string } | null> {\n if (!isSlackConfigured()) return null;\n\n const channelId = await getSquadChannelId(squad);\n if (!channelId) return null;\n\n const tier = options?.tier || getApprovalTier(squad, action);\n\n // Tier-specific formatting\n let emoji = ':rotating_light:';\n let title = 'Approval Required';\n\n if (tier === 'confirm') {\n emoji = ':warning:';\n title = 'Confirmation Required';\n } else if (tier === 'notify') {\n emoji = ':bell:';\n title = 'Notification';\n }\n\n // Build fields\n const fields = [\n { type: 'mrkdwn', text: `*Squad:*\\n${squad}` },\n { type: 'mrkdwn', text: `*Action:*\\n\\`${action}\\`` },\n ];\n\n if (options?.agent) {\n fields.splice(1, 0, { type: 'mrkdwn', text: `*Agent:*\\n${options.agent}` });\n }\n\n // Build blocks\n const blocks: Record<string, unknown>[] = [\n {\n type: 'header',\n text: { type: 'plain_text', text: `${emoji} ${title}` },\n },\n {\n type: 'section',\n fields,\n },\n {\n type: 'section',\n text: { type: 'mrkdwn', text: description },\n },\n ];\n\n // Add buttons for approve/confirm tiers, not for notify\n if (tier !== 'notify') {\n blocks.push({\n type: 'actions',\n elements: [\n {\n type: 'button',\n text: { type: 'plain_text', text: 'Approve' },\n style: 'primary',\n value: `approve_${squad}_${action}`,\n action_id: 'approve_action',\n },\n {\n type: 'button',\n text: { type: 'plain_text', text: 'Reject' },\n style: 'danger',\n value: `reject_${squad}_${action}`,\n action_id: 'reject_action',\n },\n ],\n });\n } else {\n blocks.push({\n type: 'context',\n elements: [{ type: 'mrkdwn', text: '_Auto-proceeding (notify tier)_' }],\n });\n }\n\n try {\n const response = await slackApi<SlackApiResponse>('POST', 'chat.postMessage', {\n channel: channelId,\n text: `${title}: ${action}`,\n blocks,\n });\n\n if (response.ts) {\n return { ts: response.ts, channelId };\n }\n return null;\n } catch {\n return null;\n }\n}\n\n/**\n * Wait for approval by polling message state.\n * Returns true if approved, false if rejected, throws on timeout.\n */\nexport async function waitForApproval(\n channelId: string,\n messageTs: string,\n timeoutMs = 300000 // 5 minutes default\n): Promise<boolean> {\n const startTime = Date.now();\n const pollInterval = 3000; // 3 seconds\n\n while (Date.now() - startTime < timeoutMs) {\n try {\n const response = await slackApi<SlackApiResponse>(\n 'GET',\n `conversations.history?channel=${channelId}&oldest=${messageTs}&latest=${messageTs}&inclusive=true&limit=1`\n );\n\n const message = response.messages?.[0];\n if (!message) {\n await sleep(pollInterval);\n continue;\n }\n\n // Check if buttons were replaced (decision made via interactions endpoint)\n const hasActions = message.blocks?.some((b) => b.type === 'actions');\n const contextText = message.blocks\n ?.filter((b) => b.type === 'context')\n ?.flatMap((b) => b.elements?.map((e) => e.text) || [])\n ?.join(' ') || '';\n\n if (!hasActions) {\n // Buttons removed = decision made\n if (contextText.toLowerCase().includes('approved')) {\n return true;\n }\n if (contextText.toLowerCase().includes('rejected')) {\n return false;\n }\n }\n\n // Also check emoji reactions as backup\n const reactions = message.reactions?.map((r) => r.name) || [];\n if (reactions.includes('white_check_mark') || reactions.includes('heavy_check_mark')) {\n return true;\n }\n if (reactions.includes('x') || reactions.includes('no_entry')) {\n return false;\n }\n\n await sleep(pollInterval);\n } catch {\n await sleep(pollInterval);\n }\n }\n\n throw new Error(`Approval timeout after ${timeoutMs / 1000}s`);\n}\n\n/**\n * Request approval and wait for response.\n * Returns true if approved/auto, false if rejected.\n * For notify tier, posts notification and returns true immediately.\n */\nexport async function requestApprovalAndWait(\n squad: string,\n action: string,\n description: string,\n options?: {\n agent?: string;\n timeoutMs?: number;\n }\n): Promise<boolean> {\n const tier = getApprovalTier(squad, action);\n\n // Auto tier: no Slack interaction needed\n if (tier === 'auto') {\n return true;\n }\n\n // Notify tier: post and continue\n if (tier === 'notify') {\n await postApprovalRequest(squad, action, description, {\n agent: options?.agent,\n tier,\n });\n return true;\n }\n\n // Approve/Confirm tier: post and wait\n const result = await postApprovalRequest(squad, action, description, {\n agent: options?.agent,\n tier,\n });\n\n if (!result) {\n // If Slack isn't available, default to auto-approve\n console.warn(`[Slack] Could not post approval request, defaulting to approved`);\n return true;\n }\n\n return waitForApproval(result.channelId, result.ts, options?.timeoutMs);\n}\n\n// Helper\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Create a Slack channel for a squad (format: squad-<name>).\n * Returns the channel ID string, or null if creation fails.\n */\nexport async function createSquadChannel(squadId: string, topic?: string): Promise<string | null> {\n if (!isSlackConfigured()) return null;\n\n const channelName = `squad-${squadId}`;\n\n try {\n const response = await slackApi<SlackApiResponse & { channel?: SlackChannel }>('POST', 'conversations.create', {\n name: channelName,\n is_private: false,\n });\n const channelId = response.channel?.id || null;\n\n if (channelId && topic) {\n await slackApi('POST', 'conversations.setTopic', {\n channel: channelId,\n topic,\n }).catch(() => {});\n }\n\n return channelId;\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n if (message.includes('name_taken')) {\n return getSquadChannelId(squadId);\n }\n return null;\n }\n}\n\n/**\n * Post a \"tonight session started\" notification\n */\nexport async function notifyTonightStart(\n targets: string[],\n config: { costCap: number; stopAt: string }\n): Promise<void> {\n // Post to all relevant squad channels\n const squadsSeen = new Set<string>();\n\n for (const target of targets) {\n const squad = target.split('/')[0];\n if (squadsSeen.has(squad)) continue;\n squadsSeen.add(squad);\n\n await postNotification(\n squad,\n `*Tonight mode started*\\nTargets: ${targets.filter((t) => t.startsWith(squad)).join(', ')}`,\n {\n emoji: ':crescent_moon:',\n context: `Cost cap: $${config.costCap} | Stop at: ${config.stopAt}`,\n }\n );\n }\n}\n\n/**\n * Post a \"tonight session completed\" notification\n */\nexport async function notifyTonightComplete(\n targets: string[],\n stats: { duration: number; cost: number; completed: number; failed: number }\n): Promise<void> {\n const squadsSeen = new Set<string>();\n\n for (const target of targets) {\n const squad = target.split('/')[0];\n if (squadsSeen.has(squad)) continue;\n squadsSeen.add(squad);\n\n const emoji = stats.failed > 0 ? ':warning:' : ':white_check_mark:';\n await postNotification(\n squad,\n `*Tonight mode completed*\\n${stats.completed} completed, ${stats.failed} failed`,\n {\n emoji,\n context: `Duration: ${stats.duration}min | Cost: $${stats.cost.toFixed(2)}`,\n }\n );\n }\n}\n"],"mappings":"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","names":[]}
|