squads-cli 0.2.1 → 0.2.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.
Files changed (129) hide show
  1. package/dist/{autonomy-PSVZVX7A.js → autonomy-GARI6J2J.js} +4 -4
  2. package/dist/chunk-NP5BDPE6.js +240 -0
  3. package/dist/chunk-NP5BDPE6.js.map +1 -0
  4. package/dist/chunk-O632SBON.js +62 -0
  5. package/dist/chunk-O632SBON.js.map +1 -0
  6. package/dist/{chunk-QHNUMM4V.js → chunk-QRNR4GIT.js} +3 -2
  7. package/dist/chunk-QRNR4GIT.js.map +1 -0
  8. package/dist/chunk-XTHZT53Y.js +364 -0
  9. package/dist/chunk-XTHZT53Y.js.map +1 -0
  10. package/dist/cli.js +1026 -88
  11. package/dist/cli.js.map +1 -1
  12. package/dist/{context-GWPF4SEY.js → context-PYTO2UQG.js} +7 -7
  13. package/dist/{context-feed-AJGVAR6H.js → context-feed-TLVZZ24S.js} +15 -15
  14. package/dist/{cost-XBCDJ7XC.js → cost-OALPURUQ.js} +7 -7
  15. package/dist/{dashboard-LGT2B2BL.js → dashboard-HQIEHTZC.js} +14 -14
  16. package/dist/{doctor-XPUIIBHJ.js → doctor-TWHMR23W.js} +4 -4
  17. package/dist/{exec-OUXM7JBF.js → exec-DYLI4TXY.js} +2 -2
  18. package/dist/{feedback-KNAOG5QK.js → feedback-5AEACUX6.js} +8 -8
  19. package/dist/{goal-BVHV5573.js → goal-XUNV3CKV.js} +8 -8
  20. package/dist/{health-4UXN44PF.js → health-ZF3HSA4W.js} +4 -4
  21. package/dist/{history-ILH3SWHB.js → history-WP6R5BNG.js} +5 -5
  22. package/dist/history-WP6R5BNG.js.map +1 -0
  23. package/dist/{init-XQZ7BOGT.js → init-BQSCG57S.js} +115 -6
  24. package/dist/init-BQSCG57S.js.map +1 -0
  25. package/dist/{kpi-RQIU7WGK.js → kpi-VBGDO4GI.js} +6 -6
  26. package/dist/{learn-OIFUVZAS.js → learn-C4B2PQ5J.js} +8 -8
  27. package/dist/{login-DXZANWZY.js → login-F6ITE7PR.js} +7 -7
  28. package/dist/{memory-T3ACCS7E.js → memory-33HYD6AN.js} +11 -11
  29. package/dist/observability-CL23L7LD.js +20 -0
  30. package/dist/observability-CL23L7LD.js.map +1 -0
  31. package/dist/org-cycle-Q74OT4I4.js +130 -0
  32. package/dist/org-cycle-Q74OT4I4.js.map +1 -0
  33. package/dist/{progress-DAUZMT3N.js → progress-P2EIZBKP.js} +5 -5
  34. package/dist/{providers-3P5D2XL5.js → providers-LE744DM6.js} +2 -2
  35. package/dist/repo-enforcement-JJQMKDAU.js +75 -0
  36. package/dist/repo-enforcement-JJQMKDAU.js.map +1 -0
  37. package/dist/{results-UECWGLTB.js → results-6TH33HPN.js} +6 -6
  38. package/dist/{run-I6KAXU6U.js → run-DOY5SGF3.js} +3713 -3688
  39. package/dist/run-DOY5SGF3.js.map +1 -0
  40. package/dist/run-context-GB6GUCKZ.js +26 -0
  41. package/dist/run-context-GB6GUCKZ.js.map +1 -0
  42. package/dist/{status-AQNLDZVN.js → status-PFFB2NV6.js} +16 -16
  43. package/dist/{sync-ZI3MHA4G.js → sync-FR6LQJ4C.js} +12 -12
  44. package/dist/templates/seed/config/SYSTEM.md +6 -0
  45. package/dist/templates/seed/idp/catalog/service.yaml.template +25 -0
  46. package/dist/templates/seed/memory/_squad/goals.md +23 -0
  47. package/dist/templates/seed/memory/_squad/priorities.md +25 -0
  48. package/dist/templates/seed/memory/company/company.md +31 -0
  49. package/dist/templates/seed/skills/squads-cli/SKILL.md +302 -57
  50. package/dist/templates/seed/skills/squads-cli/references/commands.md +181 -0
  51. package/dist/templates/seed/squads/company/company-critic.md +12 -4
  52. package/dist/templates/seed/squads/company/company-eval.md +12 -4
  53. package/dist/templates/seed/squads/company/event-dispatcher.md +14 -4
  54. package/dist/templates/seed/squads/company/goal-tracker.md +12 -4
  55. package/dist/templates/seed/squads/company/manager.md +17 -11
  56. package/dist/templates/seed/squads/engineering/code-reviewer.md +14 -2
  57. package/dist/templates/seed/squads/engineering/issue-solver.md +10 -2
  58. package/dist/templates/seed/squads/engineering/test-writer.md +15 -5
  59. package/dist/templates/seed/squads/intelligence/intel-critic.md +19 -2
  60. package/dist/templates/seed/squads/intelligence/intel-eval.md +18 -1
  61. package/dist/templates/seed/squads/intelligence/intel-lead.md +12 -4
  62. package/dist/templates/seed/squads/marketing/content-drafter.md +14 -4
  63. package/dist/templates/seed/squads/marketing/growth-analyst.md +14 -2
  64. package/dist/templates/seed/squads/marketing/social-poster.md +15 -3
  65. package/dist/templates/seed/squads/operations/finance-tracker.md +11 -3
  66. package/dist/templates/seed/squads/operations/goal-tracker.md +14 -2
  67. package/dist/templates/seed/squads/operations/ops-lead.md +14 -4
  68. package/dist/templates/seed/squads/product/lead.md +11 -3
  69. package/dist/templates/seed/squads/product/scanner.md +12 -4
  70. package/dist/templates/seed/squads/product/worker.md +12 -4
  71. package/dist/templates/seed/squads/research/analyst.md +12 -4
  72. package/dist/templates/seed/squads/research/lead.md +11 -5
  73. package/dist/templates/seed/squads/research/synthesizer.md +12 -4
  74. package/dist/tier-detect-YX2HPNNR.js +15 -0
  75. package/dist/tier-detect-YX2HPNNR.js.map +1 -0
  76. package/package.json +1 -1
  77. package/templates/seed/config/SYSTEM.md +6 -0
  78. package/templates/seed/idp/catalog/service.yaml.template +25 -0
  79. package/templates/seed/memory/_squad/goals.md +23 -0
  80. package/templates/seed/memory/_squad/priorities.md +25 -0
  81. package/templates/seed/memory/company/company.md +31 -0
  82. package/templates/seed/skills/squads-cli/SKILL.md +302 -57
  83. package/templates/seed/skills/squads-cli/references/commands.md +181 -0
  84. package/templates/seed/squads/company/company-critic.md +12 -4
  85. package/templates/seed/squads/company/company-eval.md +12 -4
  86. package/templates/seed/squads/company/event-dispatcher.md +14 -4
  87. package/templates/seed/squads/company/goal-tracker.md +12 -4
  88. package/templates/seed/squads/company/manager.md +17 -11
  89. package/templates/seed/squads/engineering/code-reviewer.md +14 -2
  90. package/templates/seed/squads/engineering/issue-solver.md +10 -2
  91. package/templates/seed/squads/engineering/test-writer.md +15 -5
  92. package/templates/seed/squads/intelligence/intel-critic.md +19 -2
  93. package/templates/seed/squads/intelligence/intel-eval.md +18 -1
  94. package/templates/seed/squads/intelligence/intel-lead.md +12 -4
  95. package/templates/seed/squads/marketing/content-drafter.md +14 -4
  96. package/templates/seed/squads/marketing/growth-analyst.md +14 -2
  97. package/templates/seed/squads/marketing/social-poster.md +15 -3
  98. package/templates/seed/squads/operations/finance-tracker.md +11 -3
  99. package/templates/seed/squads/operations/goal-tracker.md +14 -2
  100. package/templates/seed/squads/operations/ops-lead.md +14 -4
  101. package/templates/seed/squads/product/lead.md +11 -3
  102. package/templates/seed/squads/product/scanner.md +12 -4
  103. package/templates/seed/squads/product/worker.md +12 -4
  104. package/templates/seed/squads/research/analyst.md +12 -4
  105. package/templates/seed/squads/research/lead.md +11 -5
  106. package/templates/seed/squads/research/synthesizer.md +12 -4
  107. package/dist/chunk-QHNUMM4V.js.map +0 -1
  108. package/dist/history-ILH3SWHB.js.map +0 -1
  109. package/dist/init-XQZ7BOGT.js.map +0 -1
  110. package/dist/run-I6KAXU6U.js.map +0 -1
  111. /package/dist/{autonomy-PSVZVX7A.js.map → autonomy-GARI6J2J.js.map} +0 -0
  112. /package/dist/{context-GWPF4SEY.js.map → context-PYTO2UQG.js.map} +0 -0
  113. /package/dist/{context-feed-AJGVAR6H.js.map → context-feed-TLVZZ24S.js.map} +0 -0
  114. /package/dist/{cost-XBCDJ7XC.js.map → cost-OALPURUQ.js.map} +0 -0
  115. /package/dist/{dashboard-LGT2B2BL.js.map → dashboard-HQIEHTZC.js.map} +0 -0
  116. /package/dist/{doctor-XPUIIBHJ.js.map → doctor-TWHMR23W.js.map} +0 -0
  117. /package/dist/{exec-OUXM7JBF.js.map → exec-DYLI4TXY.js.map} +0 -0
  118. /package/dist/{feedback-KNAOG5QK.js.map → feedback-5AEACUX6.js.map} +0 -0
  119. /package/dist/{goal-BVHV5573.js.map → goal-XUNV3CKV.js.map} +0 -0
  120. /package/dist/{health-4UXN44PF.js.map → health-ZF3HSA4W.js.map} +0 -0
  121. /package/dist/{kpi-RQIU7WGK.js.map → kpi-VBGDO4GI.js.map} +0 -0
  122. /package/dist/{learn-OIFUVZAS.js.map → learn-C4B2PQ5J.js.map} +0 -0
  123. /package/dist/{login-DXZANWZY.js.map → login-F6ITE7PR.js.map} +0 -0
  124. /package/dist/{memory-T3ACCS7E.js.map → memory-33HYD6AN.js.map} +0 -0
  125. /package/dist/{progress-DAUZMT3N.js.map → progress-P2EIZBKP.js.map} +0 -0
  126. /package/dist/{providers-3P5D2XL5.js.map → providers-LE744DM6.js.map} +0 -0
  127. /package/dist/{results-UECWGLTB.js.map → results-6TH33HPN.js.map} +0 -0
  128. /package/dist/{status-AQNLDZVN.js.map → status-PFFB2NV6.js.map} +0 -0
  129. /package/dist/{sync-ZI3MHA4G.js.map → sync-FR6LQJ4C.js.map} +0 -0
@@ -1,7 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import {
3
- getEnv
4
- } from "./chunk-EHQJHRIW.js";
5
2
  import {
6
3
  Events,
7
4
  track
@@ -14,6 +11,9 @@ import {
14
11
  icons,
15
12
  writeLine
16
13
  } from "./chunk-M5FXNY6Y.js";
14
+ import {
15
+ getEnv
16
+ } from "./chunk-EHQJHRIW.js";
17
17
  import "./chunk-7OCVIDC7.js";
18
18
 
19
19
  // src/commands/autonomy.ts
@@ -102,4 +102,4 @@ async function autonomyCommand(options = {}) {
102
102
  export {
103
103
  autonomyCommand
104
104
  };
105
- //# sourceMappingURL=autonomy-PSVZVX7A.js.map
105
+ //# sourceMappingURL=autonomy-GARI6J2J.js.map
@@ -0,0 +1,240 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ findProjectRoot
4
+ } from "./chunk-TYFTF53O.js";
5
+
6
+ // src/lib/observability.ts
7
+ import { existsSync, readFileSync, appendFileSync, mkdirSync, readdirSync, statSync } from "fs";
8
+ import { join, dirname } from "path";
9
+ var MODEL_PRICING = {
10
+ "claude-opus-4-6": { input: 15, output: 75, cache_read: 1.5, cache_write: 18.75 },
11
+ "claude-opus-4-5-20251101": { input: 15, output: 75, cache_read: 1.5, cache_write: 18.75 },
12
+ "claude-sonnet-4-6": { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 },
13
+ "claude-sonnet-4-5-20250514": { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 },
14
+ "claude-sonnet-4-20250514": { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 },
15
+ "claude-haiku-4-5-20251001": { input: 0.8, output: 4, cache_read: 0.08, cache_write: 1 },
16
+ "default": { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 }
17
+ };
18
+ function getObservabilityDir() {
19
+ const root = findProjectRoot();
20
+ if (!root) return null;
21
+ return join(root, ".agents", "observability");
22
+ }
23
+ function getLogPath() {
24
+ const dir = getObservabilityDir();
25
+ if (!dir) return null;
26
+ return join(dir, "executions.jsonl");
27
+ }
28
+ function findRecentSessionFile(afterTimestamp) {
29
+ const home = process.env.HOME || "";
30
+ const projectsDir = join(home, ".claude", "projects");
31
+ if (!existsSync(projectsDir)) return null;
32
+ let newest = null;
33
+ try {
34
+ for (const projDir of readdirSync(projectsDir)) {
35
+ const projPath = join(projectsDir, projDir);
36
+ try {
37
+ if (!statSync(projPath).isDirectory()) continue;
38
+ } catch {
39
+ continue;
40
+ }
41
+ for (const file of readdirSync(projPath)) {
42
+ if (!file.endsWith(".jsonl")) continue;
43
+ const filePath = join(projPath, file);
44
+ try {
45
+ const mtime = statSync(filePath).mtimeMs;
46
+ if (mtime > afterTimestamp && (!newest || mtime > newest.mtime)) {
47
+ newest = { path: filePath, mtime };
48
+ }
49
+ } catch {
50
+ continue;
51
+ }
52
+ }
53
+ }
54
+ } catch {
55
+ }
56
+ return newest?.path || null;
57
+ }
58
+ function parseSessionUsage(sessionPath) {
59
+ try {
60
+ const content = readFileSync(sessionPath, "utf-8");
61
+ const lines = content.split("\n").filter(Boolean);
62
+ const usage = {
63
+ model: "unknown",
64
+ input_tokens: 0,
65
+ output_tokens: 0,
66
+ cache_read_tokens: 0,
67
+ cache_write_tokens: 0,
68
+ cost_usd: 0,
69
+ messages: 0
70
+ };
71
+ for (const line of lines) {
72
+ try {
73
+ const record = JSON.parse(line);
74
+ if (record.type === "assistant") {
75
+ const msg = record.message || {};
76
+ const u = msg.usage || {};
77
+ if (u.input_tokens || u.output_tokens) {
78
+ usage.messages++;
79
+ usage.input_tokens += u.input_tokens || 0;
80
+ usage.output_tokens += u.output_tokens || 0;
81
+ usage.cache_read_tokens += u.cache_read_input_tokens || 0;
82
+ usage.cache_write_tokens += u.cache_creation_input_tokens || 0;
83
+ }
84
+ if (!usage.model || usage.model === "unknown") {
85
+ usage.model = msg.model || "unknown";
86
+ }
87
+ }
88
+ if (record.costUSD) {
89
+ usage.cost_usd += record.costUSD;
90
+ }
91
+ } catch {
92
+ }
93
+ }
94
+ if (usage.messages === 0) return null;
95
+ if (usage.cost_usd === 0) {
96
+ const pricing = MODEL_PRICING[usage.model] || MODEL_PRICING["default"];
97
+ usage.cost_usd = usage.input_tokens / 1e6 * pricing.input + usage.output_tokens / 1e6 * pricing.output + usage.cache_read_tokens / 1e6 * pricing.cache_read + usage.cache_write_tokens / 1e6 * pricing.cache_write;
98
+ }
99
+ return usage;
100
+ } catch {
101
+ return null;
102
+ }
103
+ }
104
+ function snapshotGoals(squadName) {
105
+ const root = findProjectRoot();
106
+ if (!root) return {};
107
+ const goalsPath = join(root, ".agents", "memory", squadName, "goals.md");
108
+ if (!existsSync(goalsPath)) return {};
109
+ const content = readFileSync(goalsPath, "utf-8");
110
+ const goals = {};
111
+ const lines = content.split("\n");
112
+ for (const line of lines) {
113
+ const match = line.match(/\*\*([^*]+)\*\*.*status:\s*(\S+)/);
114
+ if (match) {
115
+ goals[match[1].trim()] = match[2].trim();
116
+ }
117
+ }
118
+ return goals;
119
+ }
120
+ function diffGoals(before, after) {
121
+ const changes = [];
122
+ for (const [name, afterStatus] of Object.entries(after)) {
123
+ const beforeStatus = before[name] || "new";
124
+ if (beforeStatus !== afterStatus) {
125
+ changes.push({ name, before: beforeStatus, after: afterStatus });
126
+ }
127
+ }
128
+ for (const [name, beforeStatus] of Object.entries(before)) {
129
+ if (!(name in after)) {
130
+ changes.push({ name, before: beforeStatus, after: "removed" });
131
+ }
132
+ }
133
+ return changes;
134
+ }
135
+ function captureSessionUsage(runStartedAt) {
136
+ const sessionFile = findRecentSessionFile(runStartedAt);
137
+ if (!sessionFile) return null;
138
+ return parseSessionUsage(sessionFile);
139
+ }
140
+ async function pushToApi(record) {
141
+ try {
142
+ const { isTier2, getTierSync } = await import("./tier-detect-YX2HPNNR.js");
143
+ if (!isTier2()) return;
144
+ const apiUrl = getTierSync().urls.api;
145
+ if (!apiUrl) return;
146
+ await fetch(`${apiUrl}/agent-executions`, {
147
+ method: "POST",
148
+ headers: { "Content-Type": "application/json" },
149
+ body: JSON.stringify({
150
+ execution_id: record.id,
151
+ squad: record.squad,
152
+ agent: record.agent,
153
+ model: record.model,
154
+ status: record.status,
155
+ input_tokens: record.input_tokens,
156
+ output_tokens: record.output_tokens,
157
+ cache_read_tokens: record.cache_read_tokens,
158
+ cache_write_tokens: record.cache_write_tokens,
159
+ cost_usd: record.cost_usd,
160
+ duration_seconds: Math.round(record.duration_ms / 1e3),
161
+ error_message: record.error || null,
162
+ metadata: { trigger: record.trigger, provider: record.provider }
163
+ }),
164
+ signal: AbortSignal.timeout(5e3)
165
+ });
166
+ } catch {
167
+ }
168
+ }
169
+ function logObservability(record) {
170
+ const logPath = getLogPath();
171
+ if (!logPath) return;
172
+ const dir = dirname(logPath);
173
+ if (!existsSync(dir)) {
174
+ mkdirSync(dir, { recursive: true });
175
+ }
176
+ appendFileSync(logPath, JSON.stringify(record) + "\n");
177
+ pushToApi(record).catch(() => {
178
+ });
179
+ }
180
+ function queryExecutions(opts = {}) {
181
+ const logPath = getLogPath();
182
+ if (!logPath || !existsSync(logPath)) return [];
183
+ const lines = readFileSync(logPath, "utf-8").trim().split("\n").filter(Boolean);
184
+ let records = [];
185
+ for (const line of lines) {
186
+ try {
187
+ records.push(JSON.parse(line));
188
+ } catch {
189
+ }
190
+ }
191
+ if (opts.squad) records = records.filter((r) => r.squad === opts.squad);
192
+ if (opts.agent) records = records.filter((r) => r.agent === opts.agent);
193
+ if (opts.status) records = records.filter((r) => r.status === opts.status);
194
+ if (opts.since) {
195
+ const since = new Date(opts.since).getTime();
196
+ records = records.filter((r) => new Date(r.ts).getTime() >= since);
197
+ }
198
+ records.sort((a, b) => new Date(b.ts).getTime() - new Date(a.ts).getTime());
199
+ if (opts.limit) records = records.slice(0, opts.limit);
200
+ return records;
201
+ }
202
+ function calculateCostSummary(period = "7d") {
203
+ const now = Date.now();
204
+ const cutoffs = {
205
+ "today": now - 24 * 60 * 60 * 1e3,
206
+ "7d": now - 7 * 24 * 60 * 60 * 1e3,
207
+ "30d": now - 30 * 24 * 60 * 60 * 1e3,
208
+ "all": 0
209
+ };
210
+ const since = new Date(cutoffs[period] || cutoffs["7d"]).toISOString();
211
+ const records = queryExecutions({ since });
212
+ const bySquad = {};
213
+ const byModel = {};
214
+ let totalCost = 0, totalInput = 0, totalOutput = 0;
215
+ for (const r of records) {
216
+ totalCost += r.cost_usd;
217
+ totalInput += r.input_tokens;
218
+ totalOutput += r.output_tokens;
219
+ if (!bySquad[r.squad]) bySquad[r.squad] = { cost: 0, runs: 0, avg_cost: 0 };
220
+ bySquad[r.squad].cost += r.cost_usd;
221
+ bySquad[r.squad].runs += 1;
222
+ if (!byModel[r.model]) byModel[r.model] = { cost: 0, runs: 0 };
223
+ byModel[r.model].cost += r.cost_usd;
224
+ byModel[r.model].runs += 1;
225
+ }
226
+ for (const squad of Object.values(bySquad)) {
227
+ squad.avg_cost = squad.runs > 0 ? squad.cost / squad.runs : 0;
228
+ }
229
+ return { period, total_cost: totalCost, total_runs: records.length, total_input_tokens: totalInput, total_output_tokens: totalOutput, by_squad: bySquad, by_model: byModel };
230
+ }
231
+
232
+ export {
233
+ snapshotGoals,
234
+ diffGoals,
235
+ captureSessionUsage,
236
+ logObservability,
237
+ queryExecutions,
238
+ calculateCostSummary
239
+ };
240
+ //# sourceMappingURL=chunk-NP5BDPE6.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/observability.ts"],"sourcesContent":["/**\n * Local observability — execution logging to JSONL with token capture.\n *\n * Every squads run appends one record to .agents/observability/executions.jsonl.\n * Token/cost data is captured from Claude Code's session JSONL files after run.\n *\n * No external dependencies. Git-tracked. Readable by agents and humans.\n */\n\nimport { existsSync, readFileSync, appendFileSync, mkdirSync, readdirSync, statSync } from 'fs';\nimport { join, dirname } from 'path';\nimport { findProjectRoot } from './squad-parser.js';\n\n// ── Types ────────────────────────────────────────────────────────────\n\nexport interface GoalChange {\n name: string;\n before: string; // status before run\n after: string; // status after run\n}\n\nexport interface ObservabilityRecord {\n ts: string;\n id: string;\n squad: string;\n agent: string;\n provider: string;\n model: string;\n trigger: 'manual' | 'scheduled' | 'event' | 'smart';\n status: 'completed' | 'failed' | 'timeout';\n duration_ms: number;\n input_tokens: number;\n output_tokens: number;\n cache_read_tokens: number;\n cache_write_tokens: number;\n cost_usd: number;\n context_tokens: number;\n error?: string;\n task?: string;\n // Goal tracking\n goals_before?: Record<string, string>; // name → status before run\n goals_after?: Record<string, string>; // name → status after run\n goals_changed?: GoalChange[]; // what moved\n // Quality scoring (from COO eval)\n grade?: string; // A/B/C/D/F\n grade_score?: number; // 0-100\n}\n\nexport interface QueryOptions {\n squad?: string;\n agent?: string;\n status?: string;\n since?: string;\n limit?: number;\n}\n\nexport interface CostSummary {\n period: string;\n total_cost: number;\n total_runs: number;\n total_input_tokens: number;\n total_output_tokens: number;\n by_squad: Record<string, { cost: number; runs: number; avg_cost: number }>;\n by_model: Record<string, { cost: number; runs: number }>;\n}\n\n// ── Model Pricing (per 1M tokens) ────────────────────────────────────\n\nconst MODEL_PRICING: Record<string, { input: number; output: number; cache_read: number; cache_write: number }> = {\n 'claude-opus-4-6': { input: 15.0, output: 75.0, cache_read: 1.5, cache_write: 18.75 },\n 'claude-opus-4-5-20251101': { input: 15.0, output: 75.0, cache_read: 1.5, cache_write: 18.75 },\n 'claude-sonnet-4-6': { input: 3.0, output: 15.0, cache_read: 0.3, cache_write: 3.75 },\n 'claude-sonnet-4-5-20250514': { input: 3.0, output: 15.0, cache_read: 0.3, cache_write: 3.75 },\n 'claude-sonnet-4-20250514': { input: 3.0, output: 15.0, cache_read: 0.3, cache_write: 3.75 },\n 'claude-haiku-4-5-20251001': { input: 0.80, output: 4.0, cache_read: 0.08, cache_write: 1.0 },\n 'default': { input: 3.0, output: 15.0, cache_read: 0.3, cache_write: 3.75 },\n};\n\n// ── Paths ────────────────────────────────────────────────────────────\n\nfunction getObservabilityDir(): string | null {\n const root = findProjectRoot();\n if (!root) return null;\n return join(root, '.agents', 'observability');\n}\n\nfunction getLogPath(): string | null {\n const dir = getObservabilityDir();\n if (!dir) return null;\n return join(dir, 'executions.jsonl');\n}\n\n// ── Claude Code Session Parsing ──────────────────────────────────────\n\ninterface SessionUsage {\n model: string;\n input_tokens: number;\n output_tokens: number;\n cache_read_tokens: number;\n cache_write_tokens: number;\n cost_usd: number;\n messages: number;\n}\n\n/**\n * Find the most recently modified Claude Code session JSONL file.\n * Claude Code writes sessions to ~/.claude/projects/<hash>/*.jsonl\n */\nfunction findRecentSessionFile(afterTimestamp: number): string | null {\n const home = process.env.HOME || '';\n const projectsDir = join(home, '.claude', 'projects');\n if (!existsSync(projectsDir)) return null;\n\n let newest: { path: string; mtime: number } | null = null;\n\n try {\n for (const projDir of readdirSync(projectsDir)) {\n const projPath = join(projectsDir, projDir);\n try {\n if (!statSync(projPath).isDirectory()) continue;\n } catch { continue; }\n\n for (const file of readdirSync(projPath)) {\n if (!file.endsWith('.jsonl')) continue;\n const filePath = join(projPath, file);\n try {\n const mtime = statSync(filePath).mtimeMs;\n // Only consider files modified after the run started\n if (mtime > afterTimestamp && (!newest || mtime > newest.mtime)) {\n newest = { path: filePath, mtime };\n }\n } catch { continue; }\n }\n }\n } catch { /* projects dir read error */ }\n\n return newest?.path || null;\n}\n\n/**\n * Parse a Claude Code session JSONL file and extract usage totals.\n */\nfunction parseSessionUsage(sessionPath: string): SessionUsage | null {\n try {\n const content = readFileSync(sessionPath, 'utf-8');\n const lines = content.split('\\n').filter(Boolean);\n\n const usage: SessionUsage = {\n model: 'unknown',\n input_tokens: 0,\n output_tokens: 0,\n cache_read_tokens: 0,\n cache_write_tokens: 0,\n cost_usd: 0,\n messages: 0,\n };\n\n for (const line of lines) {\n try {\n const record = JSON.parse(line);\n\n if (record.type === 'assistant') {\n const msg = record.message || {};\n const u = msg.usage || {};\n\n if (u.input_tokens || u.output_tokens) {\n usage.messages++;\n usage.input_tokens += u.input_tokens || 0;\n usage.output_tokens += u.output_tokens || 0;\n usage.cache_read_tokens += u.cache_read_input_tokens || 0;\n usage.cache_write_tokens += u.cache_creation_input_tokens || 0;\n }\n\n if (!usage.model || usage.model === 'unknown') {\n usage.model = msg.model || 'unknown';\n }\n }\n\n // Capture cost if directly available\n if (record.costUSD) {\n usage.cost_usd += record.costUSD;\n }\n } catch { /* skip malformed lines */ }\n }\n\n if (usage.messages === 0) return null;\n\n // Calculate cost from tokens if not directly available\n if (usage.cost_usd === 0) {\n const pricing = MODEL_PRICING[usage.model] || MODEL_PRICING['default'];\n usage.cost_usd = (\n (usage.input_tokens / 1_000_000) * pricing.input +\n (usage.output_tokens / 1_000_000) * pricing.output +\n (usage.cache_read_tokens / 1_000_000) * pricing.cache_read +\n (usage.cache_write_tokens / 1_000_000) * pricing.cache_write\n );\n }\n\n return usage;\n } catch {\n return null;\n }\n}\n\n// ── Goal Tracking ────────────────────────────────────────────────────\n\n/**\n * Parse goals from a squad's goals.md file.\n * Returns a map of goal name → status.\n */\nexport function snapshotGoals(squadName: string): Record<string, string> {\n const root = findProjectRoot();\n if (!root) return {};\n\n const goalsPath = join(root, '.agents', 'memory', squadName, 'goals.md');\n if (!existsSync(goalsPath)) return {};\n\n const content = readFileSync(goalsPath, 'utf-8');\n const goals: Record<string, string> = {};\n\n // Parse: **Goal name** — metric: X | ... | status: Y\n const lines = content.split('\\n');\n for (const line of lines) {\n const match = line.match(/\\*\\*([^*]+)\\*\\*.*status:\\s*(\\S+)/);\n if (match) {\n goals[match[1].trim()] = match[2].trim();\n }\n }\n\n return goals;\n}\n\n/**\n * Compare two goal snapshots and return what changed.\n */\nexport function diffGoals(\n before: Record<string, string>,\n after: Record<string, string>\n): GoalChange[] {\n const changes: GoalChange[] = [];\n\n for (const [name, afterStatus] of Object.entries(after)) {\n const beforeStatus = before[name] || 'new';\n if (beforeStatus !== afterStatus) {\n changes.push({ name, before: beforeStatus, after: afterStatus });\n }\n }\n\n // Goals that disappeared (moved to achieved/abandoned)\n for (const [name, beforeStatus] of Object.entries(before)) {\n if (!(name in after)) {\n changes.push({ name, before: beforeStatus, after: 'removed' });\n }\n }\n\n return changes;\n}\n\n/**\n * Capture usage from the most recent Claude Code session.\n * Call this after a foreground run completes.\n */\nexport function captureSessionUsage(runStartedAt: number): SessionUsage | null {\n const sessionFile = findRecentSessionFile(runStartedAt);\n if (!sessionFile) return null;\n return parseSessionUsage(sessionFile);\n}\n\n// ── Write ────────────────────────────────────────────────────────────\n\n/**\n * Push record to API (Tier 2 only). Fire-and-forget.\n */\nasync function pushToApi(record: ObservabilityRecord): Promise<void> {\n try {\n const { isTier2, getTierSync } = await import('./tier-detect.js');\n if (!isTier2()) return;\n\n const apiUrl = getTierSync().urls.api;\n if (!apiUrl) return;\n\n await fetch(`${apiUrl}/agent-executions`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n execution_id: record.id,\n squad: record.squad,\n agent: record.agent,\n model: record.model,\n status: record.status,\n input_tokens: record.input_tokens,\n output_tokens: record.output_tokens,\n cache_read_tokens: record.cache_read_tokens,\n cache_write_tokens: record.cache_write_tokens,\n cost_usd: record.cost_usd,\n duration_seconds: Math.round(record.duration_ms / 1000),\n error_message: record.error || null,\n metadata: { trigger: record.trigger, provider: record.provider },\n }),\n signal: AbortSignal.timeout(5000),\n });\n } catch {\n // Silent — Tier 2 API down, JSONL is the fallback\n }\n}\n\nexport function logObservability(record: ObservabilityRecord): void {\n const logPath = getLogPath();\n if (!logPath) return;\n\n const dir = dirname(logPath);\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n\n appendFileSync(logPath, JSON.stringify(record) + '\\n');\n\n // Dual-write: also push to API when Tier 2 is active (fire-and-forget)\n pushToApi(record).catch(() => {});\n}\n\n// ── Read ─────────────────────────────────────────────────────────────\n\nexport function queryExecutions(opts: QueryOptions = {}): ObservabilityRecord[] {\n const logPath = getLogPath();\n if (!logPath || !existsSync(logPath)) return [];\n\n const lines = readFileSync(logPath, 'utf-8').trim().split('\\n').filter(Boolean);\n let records: ObservabilityRecord[] = [];\n\n for (const line of lines) {\n try { records.push(JSON.parse(line)); } catch { /* skip */ }\n }\n\n if (opts.squad) records = records.filter(r => r.squad === opts.squad);\n if (opts.agent) records = records.filter(r => r.agent === opts.agent);\n if (opts.status) records = records.filter(r => r.status === opts.status);\n if (opts.since) {\n const since = new Date(opts.since).getTime();\n records = records.filter(r => new Date(r.ts).getTime() >= since);\n }\n\n records.sort((a, b) => new Date(b.ts).getTime() - new Date(a.ts).getTime());\n if (opts.limit) records = records.slice(0, opts.limit);\n\n return records;\n}\n\nexport function calculateCostSummary(period: 'today' | '7d' | '30d' | 'all' = '7d'): CostSummary {\n const now = Date.now();\n const cutoffs: Record<string, number> = {\n 'today': now - 24 * 60 * 60 * 1000,\n '7d': now - 7 * 24 * 60 * 60 * 1000,\n '30d': now - 30 * 24 * 60 * 60 * 1000,\n 'all': 0,\n };\n\n const since = new Date(cutoffs[period] || cutoffs['7d']).toISOString();\n const records = queryExecutions({ since });\n\n const bySquad: Record<string, { cost: number; runs: number; avg_cost: number }> = {};\n const byModel: Record<string, { cost: number; runs: number }> = {};\n let totalCost = 0, totalInput = 0, totalOutput = 0;\n\n for (const r of records) {\n totalCost += r.cost_usd;\n totalInput += r.input_tokens;\n totalOutput += r.output_tokens;\n\n if (!bySquad[r.squad]) bySquad[r.squad] = { cost: 0, runs: 0, avg_cost: 0 };\n bySquad[r.squad].cost += r.cost_usd;\n bySquad[r.squad].runs += 1;\n\n if (!byModel[r.model]) byModel[r.model] = { cost: 0, runs: 0 };\n byModel[r.model].cost += r.cost_usd;\n byModel[r.model].runs += 1;\n }\n\n for (const squad of Object.values(bySquad)) {\n squad.avg_cost = squad.runs > 0 ? squad.cost / squad.runs : 0;\n }\n\n return { period, total_cost: totalCost, total_runs: records.length, total_input_tokens: totalInput, total_output_tokens: totalOutput, by_squad: bySquad, by_model: byModel };\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/lib/tier-detect.ts
4
+ var DEFAULT_API_URL = "http://localhost:8090";
5
+ var DEFAULT_BRIDGE_URL = "http://localhost:8088";
6
+ var PROBE_TIMEOUT_MS = 1500;
7
+ var cached = null;
8
+ async function probe(url) {
9
+ try {
10
+ const response = await fetch(`${url}/health`, {
11
+ signal: AbortSignal.timeout(PROBE_TIMEOUT_MS)
12
+ });
13
+ return response.ok;
14
+ } catch {
15
+ return false;
16
+ }
17
+ }
18
+ async function detectTier() {
19
+ if (cached) return cached;
20
+ const [apiOk, bridgeOk] = await Promise.all([
21
+ probe(DEFAULT_API_URL),
22
+ probe(DEFAULT_BRIDGE_URL)
23
+ ]);
24
+ const tier = apiOk ? 2 : 1;
25
+ cached = {
26
+ tier,
27
+ services: {
28
+ api: apiOk,
29
+ bridge: bridgeOk,
30
+ postgres: apiOk,
31
+ // If API is up, Postgres is up (API depends on it)
32
+ redis: apiOk
33
+ // If API is up, Redis is up (API depends on it)
34
+ },
35
+ urls: {
36
+ api: apiOk ? DEFAULT_API_URL : null,
37
+ bridge: bridgeOk ? DEFAULT_BRIDGE_URL : null
38
+ }
39
+ };
40
+ return cached;
41
+ }
42
+ function getTierSync() {
43
+ return cached || {
44
+ tier: 1,
45
+ services: { api: false, bridge: false, postgres: false, redis: false },
46
+ urls: { api: null, bridge: null }
47
+ };
48
+ }
49
+ function isTier2() {
50
+ return getTierSync().tier === 2;
51
+ }
52
+ function resetTierCache() {
53
+ cached = null;
54
+ }
55
+
56
+ export {
57
+ detectTier,
58
+ getTierSync,
59
+ isTier2,
60
+ resetTierCache
61
+ };
62
+ //# sourceMappingURL=chunk-O632SBON.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/tier-detect.ts"],"sourcesContent":["/**\n * Tier detection — determines which infrastructure tier is active.\n *\n * Tier 1: File-based only (JSONL, markdown, git). Zero dependencies.\n * Tier 2: Local services (Postgres, Redis, API, Bridge via Docker).\n *\n * Cached per process. First call probes services (async), subsequent\n * calls return cached result (sync).\n */\n\nexport interface TierInfo {\n tier: 1 | 2;\n services: {\n api: boolean;\n bridge: boolean;\n postgres: boolean;\n redis: boolean;\n };\n urls: {\n api: string | null;\n bridge: string | null;\n };\n}\n\nconst DEFAULT_API_URL = 'http://localhost:8090';\nconst DEFAULT_BRIDGE_URL = 'http://localhost:8088';\nconst PROBE_TIMEOUT_MS = 1500;\n\nlet cached: TierInfo | null = null;\n\n/** Probe a URL for health (returns true if 2xx) */\nasync function probe(url: string): Promise<boolean> {\n try {\n const response = await fetch(`${url}/health`, {\n signal: AbortSignal.timeout(PROBE_TIMEOUT_MS),\n });\n return response.ok;\n } catch {\n return false;\n }\n}\n\n/**\n * Detect the active tier. First call probes services (async).\n * Subsequent calls return cached result.\n */\nexport async function detectTier(): Promise<TierInfo> {\n if (cached) return cached;\n\n // Probe API and Bridge in parallel\n const [apiOk, bridgeOk] = await Promise.all([\n probe(DEFAULT_API_URL),\n probe(DEFAULT_BRIDGE_URL),\n ]);\n\n // Tier 2 requires at least the API to be healthy\n const tier = apiOk ? 2 : 1;\n\n cached = {\n tier,\n services: {\n api: apiOk,\n bridge: bridgeOk,\n postgres: apiOk, // If API is up, Postgres is up (API depends on it)\n redis: apiOk, // If API is up, Redis is up (API depends on it)\n },\n urls: {\n api: apiOk ? DEFAULT_API_URL : null,\n bridge: bridgeOk ? DEFAULT_BRIDGE_URL : null,\n },\n };\n\n return cached;\n}\n\n/**\n * Get cached tier info synchronously. Returns Tier 1 if not yet detected.\n * Use this in hot paths where async is not possible.\n */\nexport function getTierSync(): TierInfo {\n return cached || {\n tier: 1,\n services: { api: false, bridge: false, postgres: false, redis: false },\n urls: { api: null, bridge: null },\n };\n}\n\n/** Check if Tier 2 services are available */\nexport function isTier2(): boolean {\n return getTierSync().tier === 2;\n}\n\n/** Reset cache (for testing) */\nexport function resetTierCache(): void {\n cached = null;\n}\n"],"mappings":";;;AAwBA,IAAM,kBAAkB;AACxB,IAAM,qBAAqB;AAC3B,IAAM,mBAAmB;AAEzB,IAAI,SAA0B;AAG9B,eAAe,MAAM,KAA+B;AAClD,MAAI;AACF,UAAM,WAAW,MAAM,MAAM,GAAG,GAAG,WAAW;AAAA,MAC5C,QAAQ,YAAY,QAAQ,gBAAgB;AAAA,IAC9C,CAAC;AACD,WAAO,SAAS;AAAA,EAClB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAMA,eAAsB,aAAgC;AACpD,MAAI,OAAQ,QAAO;AAGnB,QAAM,CAAC,OAAO,QAAQ,IAAI,MAAM,QAAQ,IAAI;AAAA,IAC1C,MAAM,eAAe;AAAA,IACrB,MAAM,kBAAkB;AAAA,EAC1B,CAAC;AAGD,QAAM,OAAO,QAAQ,IAAI;AAEzB,WAAS;AAAA,IACP;AAAA,IACA,UAAU;AAAA,MACR,KAAK;AAAA,MACL,QAAQ;AAAA,MACR,UAAU;AAAA;AAAA,MACV,OAAO;AAAA;AAAA,IACT;AAAA,IACA,MAAM;AAAA,MACJ,KAAK,QAAQ,kBAAkB;AAAA,MAC/B,QAAQ,WAAW,qBAAqB;AAAA,IAC1C;AAAA,EACF;AAEA,SAAO;AACT;AAMO,SAAS,cAAwB;AACtC,SAAO,UAAU;AAAA,IACf,MAAM;AAAA,IACN,UAAU,EAAE,KAAK,OAAO,QAAQ,OAAO,UAAU,OAAO,OAAO,MAAM;AAAA,IACrE,MAAM,EAAE,KAAK,MAAM,QAAQ,KAAK;AAAA,EAClC;AACF;AAGO,SAAS,UAAmB;AACjC,SAAO,YAAY,EAAE,SAAS;AAChC;AAGO,SAAS,iBAAuB;AACrC,WAAS;AACX;","names":[]}
@@ -58,7 +58,8 @@ var LLM_CLIS = {
58
58
  displayName: "Ollama (Local)",
59
59
  command: "ollama",
60
60
  install: "brew install ollama",
61
- buildArgs: (prompt, opts) => ["run", opts?.model || "llama3.1", prompt]
61
+ buildArgs: (_prompt, opts) => ["run", opts?.model || "llama3.1"],
62
+ stdinPrompt: true
62
63
  }
63
64
  };
64
65
  function getAllCLIStatus() {
@@ -84,4 +85,4 @@ export {
84
85
  getCLIConfig,
85
86
  isProviderCLIAvailable
86
87
  };
87
- //# sourceMappingURL=chunk-QHNUMM4V.js.map
88
+ //# sourceMappingURL=chunk-QRNR4GIT.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/llm-clis.ts"],"sourcesContent":["/**\n * Multi-LLM CLI Support\n *\n * Enables squads to use different LLM providers by delegating to their native CLIs.\n * Unix-style composition: each provider maintains their own CLI, we orchestrate.\n *\n * @see specs/multi-llm.md\n */\n\nimport { execSync } from 'child_process';\n\nexport interface CLIConfig {\n /** Provider identifier (matches provider field in SQUAD.md/agent.md) */\n provider: string;\n\n /** Display name for UI */\n displayName: string;\n\n /** CLI command name */\n command: string;\n\n /** Install instructions */\n install: string;\n\n /** Build non-interactive args for execution */\n buildArgs: (prompt: string, options?: RunOptions) => string[];\n\n /** If true, pipe prompt via stdin instead of CLI arg (avoids shell arg length limits) */\n stdinPrompt?: boolean;\n}\n\nexport interface RunOptions {\n /** Model override (for providers that support it) */\n model?: string;\n\n /** Working directory */\n cwd?: string;\n\n /** Dry run - just show what would execute */\n dryRun?: boolean;\n}\n\n/**\n * Check if a command exists in PATH\n */\nexport function commandExists(command: string): boolean {\n try {\n execSync(`which ${command}`, { stdio: 'pipe' });\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * LLM CLI registry\n * Maps provider IDs to their CLI configurations\n */\nexport const LLM_CLIS: Record<string, CLIConfig> = {\n anthropic: {\n provider: 'anthropic',\n displayName: 'Anthropic',\n command: 'claude',\n install: 'npm i -g @anthropic-ai/claude-code',\n buildArgs: (prompt) => ['--print', prompt],\n },\n\n google: {\n provider: 'google',\n displayName: 'Google',\n command: 'gemini',\n install: 'npm i -g @google/gemini-cli',\n buildArgs: (prompt) => ['--yolo', '--prompt', prompt],\n },\n\n openai: {\n provider: 'openai',\n displayName: 'OpenAI',\n command: 'codex',\n install: 'npm i -g @openai/codex',\n buildArgs: (prompt) => ['exec', prompt],\n },\n\n mistral: {\n provider: 'mistral',\n displayName: 'Mistral',\n command: 'vibe',\n install: 'curl -LsSf https://mistral.ai/vibe/install.sh | bash',\n buildArgs: (prompt) => ['--prompt', prompt, '--auto-approve'],\n },\n\n xai: {\n provider: 'xai',\n displayName: 'xAI',\n command: 'grok',\n install: 'bun add -g @vibe-kit/grok-cli',\n buildArgs: (prompt) => ['--prompt', prompt],\n },\n\n aider: {\n provider: 'aider',\n displayName: 'Aider (Multi)',\n command: 'aider',\n install: 'pip install aider-install && aider-install',\n buildArgs: (prompt) => ['--message', prompt, '--yes'],\n },\n\n ollama: {\n provider: 'ollama',\n displayName: 'Ollama (Local)',\n command: 'ollama',\n install: 'brew install ollama',\n buildArgs: (_prompt, opts) => ['run', opts?.model || 'llama3.1'],\n stdinPrompt: true,\n },\n};\n\nexport interface CLIStatus {\n provider: string;\n displayName: string;\n command: string;\n available: boolean;\n install: string;\n}\n\n/**\n * Get status of all LLM CLIs\n */\nexport function getAllCLIStatus(): CLIStatus[] {\n return Object.values(LLM_CLIS).map((cli) => ({\n provider: cli.provider,\n displayName: cli.displayName,\n command: cli.command,\n available: commandExists(cli.command),\n install: cli.install,\n }));\n}\n\n/**\n * Get CLI config for a provider\n */\nexport function getCLIConfig(provider: string): CLIConfig | undefined {\n return LLM_CLIS[provider];\n}\n\n/**\n * Check if a provider's CLI is available\n */\nexport function isProviderCLIAvailable(provider: string): boolean {\n const config = LLM_CLIS[provider];\n if (!config) return false;\n return commandExists(config.command);\n}\n"],"mappings":";;;AASA,SAAS,gBAAgB;AAoClB,SAAS,cAAc,SAA0B;AACtD,MAAI;AACF,aAAS,SAAS,OAAO,IAAI,EAAE,OAAO,OAAO,CAAC;AAC9C,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAMO,IAAM,WAAsC;AAAA,EACjD,WAAW;AAAA,IACT,UAAU;AAAA,IACV,aAAa;AAAA,IACb,SAAS;AAAA,IACT,SAAS;AAAA,IACT,WAAW,CAAC,WAAW,CAAC,WAAW,MAAM;AAAA,EAC3C;AAAA,EAEA,QAAQ;AAAA,IACN,UAAU;AAAA,IACV,aAAa;AAAA,IACb,SAAS;AAAA,IACT,SAAS;AAAA,IACT,WAAW,CAAC,WAAW,CAAC,UAAU,YAAY,MAAM;AAAA,EACtD;AAAA,EAEA,QAAQ;AAAA,IACN,UAAU;AAAA,IACV,aAAa;AAAA,IACb,SAAS;AAAA,IACT,SAAS;AAAA,IACT,WAAW,CAAC,WAAW,CAAC,QAAQ,MAAM;AAAA,EACxC;AAAA,EAEA,SAAS;AAAA,IACP,UAAU;AAAA,IACV,aAAa;AAAA,IACb,SAAS;AAAA,IACT,SAAS;AAAA,IACT,WAAW,CAAC,WAAW,CAAC,YAAY,QAAQ,gBAAgB;AAAA,EAC9D;AAAA,EAEA,KAAK;AAAA,IACH,UAAU;AAAA,IACV,aAAa;AAAA,IACb,SAAS;AAAA,IACT,SAAS;AAAA,IACT,WAAW,CAAC,WAAW,CAAC,YAAY,MAAM;AAAA,EAC5C;AAAA,EAEA,OAAO;AAAA,IACL,UAAU;AAAA,IACV,aAAa;AAAA,IACb,SAAS;AAAA,IACT,SAAS;AAAA,IACT,WAAW,CAAC,WAAW,CAAC,aAAa,QAAQ,OAAO;AAAA,EACtD;AAAA,EAEA,QAAQ;AAAA,IACN,UAAU;AAAA,IACV,aAAa;AAAA,IACb,SAAS;AAAA,IACT,SAAS;AAAA,IACT,WAAW,CAAC,SAAS,SAAS,CAAC,OAAO,MAAM,SAAS,UAAU;AAAA,IAC/D,aAAa;AAAA,EACf;AACF;AAaO,SAAS,kBAA+B;AAC7C,SAAO,OAAO,OAAO,QAAQ,EAAE,IAAI,CAAC,SAAS;AAAA,IAC3C,UAAU,IAAI;AAAA,IACd,aAAa,IAAI;AAAA,IACjB,SAAS,IAAI;AAAA,IACb,WAAW,cAAc,IAAI,OAAO;AAAA,IACpC,SAAS,IAAI;AAAA,EACf,EAAE;AACJ;AAKO,SAAS,aAAa,UAAyC;AACpE,SAAO,SAAS,QAAQ;AAC1B;AAKO,SAAS,uBAAuB,UAA2B;AAChE,QAAM,SAAS,SAAS,QAAQ;AAChC,MAAI,CAAC,OAAQ,QAAO;AACpB,SAAO,cAAc,OAAO,OAAO;AACrC;","names":[]}