squads-cli 0.2.0 → 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.
- package/README.md +521 -288
- package/dist/auth-YW3UPFSB.js +23 -0
- package/dist/auth-YW3UPFSB.js.map +1 -0
- package/dist/autonomy-GARI6J2J.js +105 -0
- package/dist/autonomy-GARI6J2J.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-NP5BDPE6.js +240 -0
- package/dist/chunk-NP5BDPE6.js.map +1 -0
- package/dist/chunk-O632SBON.js +62 -0
- package/dist/chunk-O632SBON.js.map +1 -0
- package/dist/chunk-QJ7C7CMB.js +223 -0
- package/dist/chunk-QJ7C7CMB.js.map +1 -0
- package/dist/chunk-QRNR4GIT.js +88 -0
- package/dist/chunk-QRNR4GIT.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-XTHZT53Y.js +364 -0
- package/dist/chunk-XTHZT53Y.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 +3371 -5852
- package/dist/cli.js.map +1 -1
- package/dist/context-PYTO2UQG.js +291 -0
- package/dist/context-PYTO2UQG.js.map +1 -0
- package/dist/context-feed-TLVZZ24S.js +394 -0
- package/dist/context-feed-TLVZZ24S.js.map +1 -0
- package/dist/cost-OALPURUQ.js +275 -0
- package/dist/cost-OALPURUQ.js.map +1 -0
- package/dist/create-BLFGG6PF.js +286 -0
- package/dist/create-BLFGG6PF.js.map +1 -0
- package/dist/dashboard-HQIEHTZC.js +951 -0
- package/dist/dashboard-HQIEHTZC.js.map +1 -0
- package/dist/dashboard-RMK2BOD2.js +794 -0
- package/dist/dashboard-RMK2BOD2.js.map +1 -0
- package/dist/doctor-TWHMR23W.js +374 -0
- package/dist/doctor-TWHMR23W.js.map +1 -0
- package/dist/env-config-SQEI3Y7Y.js +21 -0
- package/dist/env-config-SQEI3Y7Y.js.map +1 -0
- package/dist/exec-DYLI4TXY.js +223 -0
- package/dist/exec-DYLI4TXY.js.map +1 -0
- package/dist/feedback-5AEACUX6.js +229 -0
- package/dist/feedback-5AEACUX6.js.map +1 -0
- package/dist/github-UQTM5KMS.js +23 -0
- package/dist/github-UQTM5KMS.js.map +1 -0
- package/dist/goal-XUNV3CKV.js +168 -0
- package/dist/goal-XUNV3CKV.js.map +1 -0
- package/dist/health-ZF3HSA4W.js +218 -0
- package/dist/health-ZF3HSA4W.js.map +1 -0
- package/dist/history-WP6R5BNG.js +232 -0
- package/dist/history-WP6R5BNG.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-BQSCG57S.js +921 -0
- package/dist/init-BQSCG57S.js.map +1 -0
- package/dist/kpi-VBGDO4GI.js +413 -0
- package/dist/kpi-VBGDO4GI.js.map +1 -0
- package/dist/learn-C4B2PQ5J.js +269 -0
- package/dist/learn-C4B2PQ5J.js.map +1 -0
- package/dist/login-F6ITE7PR.js +155 -0
- package/dist/login-F6ITE7PR.js.map +1 -0
- package/dist/memory-33HYD6AN.js +560 -0
- package/dist/memory-33HYD6AN.js.map +1 -0
- package/dist/memory-VNF2VFRB.js +23 -0
- package/dist/memory-VNF2VFRB.js.map +1 -0
- package/dist/observability-CL23L7LD.js +20 -0
- package/dist/observability-CL23L7LD.js.map +1 -0
- package/dist/org-cycle-Q74OT4I4.js +130 -0
- package/dist/org-cycle-Q74OT4I4.js.map +1 -0
- package/dist/progress-P2EIZBKP.js +202 -0
- package/dist/progress-P2EIZBKP.js.map +1 -0
- package/dist/providers-LE744DM6.js +65 -0
- package/dist/providers-LE744DM6.js.map +1 -0
- package/dist/repo-enforcement-JJQMKDAU.js +75 -0
- package/dist/repo-enforcement-JJQMKDAU.js.map +1 -0
- package/dist/results-6TH33HPN.js +224 -0
- package/dist/results-6TH33HPN.js.map +1 -0
- package/dist/run-DOY5SGF3.js +4074 -0
- package/dist/run-DOY5SGF3.js.map +1 -0
- package/dist/run-context-GB6GUCKZ.js +26 -0
- package/dist/run-context-GB6GUCKZ.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-PFFB2NV6.js +352 -0
- package/dist/status-PFFB2NV6.js.map +1 -0
- package/dist/sync-FR6LQJ4C.js +836 -0
- package/dist/sync-FR6LQJ4C.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 +58 -0
- package/dist/templates/seed/config/provider.yaml +4 -0
- package/dist/templates/seed/hooks/settings.json.template +31 -0
- package/dist/templates/seed/idp/catalog/service.yaml.template +25 -0
- package/dist/templates/seed/memory/_squad/goals.md +23 -0
- package/dist/templates/seed/memory/_squad/priorities.md +25 -0
- package/dist/templates/seed/memory/company/company.md +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 +329 -0
- package/dist/templates/seed/skills/squads-cli/references/commands.md +181 -0
- package/dist/templates/seed/squads/company/SQUAD.md +51 -0
- package/dist/templates/seed/squads/company/company-critic.md +57 -0
- package/dist/templates/seed/squads/company/company-eval.md +57 -0
- package/dist/templates/seed/squads/company/event-dispatcher.md +53 -0
- package/dist/templates/seed/squads/company/goal-tracker.md +51 -0
- package/dist/templates/seed/squads/company/manager.md +60 -0
- package/dist/templates/seed/squads/engineering/SQUAD.md +48 -0
- package/dist/templates/seed/squads/engineering/code-reviewer.md +69 -0
- package/dist/templates/seed/squads/engineering/issue-solver.md +66 -0
- package/dist/templates/seed/squads/engineering/test-writer.md +60 -0
- package/dist/templates/seed/squads/intelligence/SQUAD.md +38 -0
- package/dist/templates/seed/squads/intelligence/intel-critic.md +53 -0
- package/dist/templates/seed/squads/intelligence/intel-eval.md +48 -0
- package/dist/templates/seed/squads/intelligence/intel-lead.md +79 -0
- package/dist/templates/seed/squads/marketing/SQUAD.md +47 -0
- package/dist/templates/seed/squads/marketing/content-drafter.md +81 -0
- package/dist/templates/seed/squads/marketing/growth-analyst.md +61 -0
- package/dist/templates/seed/squads/marketing/social-poster.md +56 -0
- package/dist/templates/seed/squads/operations/SQUAD.md +45 -0
- package/dist/templates/seed/squads/operations/finance-tracker.md +55 -0
- package/dist/templates/seed/squads/operations/goal-tracker.md +60 -0
- package/dist/templates/seed/squads/operations/ops-lead.md +68 -0
- package/dist/templates/seed/squads/product/SQUAD.md +41 -0
- package/dist/templates/seed/squads/product/lead.md +64 -0
- package/dist/templates/seed/squads/product/scanner.md +58 -0
- package/dist/templates/seed/squads/product/worker.md +63 -0
- package/dist/templates/seed/squads/research/SQUAD.md +38 -0
- package/dist/templates/seed/squads/research/analyst.md +58 -0
- package/dist/templates/seed/squads/research/lead.md +58 -0
- package/dist/templates/seed/squads/research/synthesizer.md +67 -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/tier-detect-YX2HPNNR.js +15 -0
- package/dist/tier-detect-YX2HPNNR.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 +58 -0
- package/templates/seed/config/provider.yaml +4 -0
- package/templates/seed/hooks/settings.json.template +31 -0
- package/templates/seed/idp/catalog/service.yaml.template +25 -0
- package/templates/seed/memory/_squad/goals.md +23 -0
- package/templates/seed/memory/_squad/priorities.md +25 -0
- package/templates/seed/memory/company/company.md +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 +329 -0
- package/templates/seed/skills/squads-cli/references/commands.md +181 -0
- package/templates/seed/squads/company/SQUAD.md +51 -0
- package/templates/seed/squads/company/company-critic.md +57 -0
- package/templates/seed/squads/company/company-eval.md +57 -0
- package/templates/seed/squads/company/event-dispatcher.md +53 -0
- package/templates/seed/squads/company/goal-tracker.md +51 -0
- package/templates/seed/squads/company/manager.md +60 -0
- package/templates/seed/squads/engineering/SQUAD.md +48 -0
- package/templates/seed/squads/engineering/code-reviewer.md +69 -0
- package/templates/seed/squads/engineering/issue-solver.md +66 -0
- package/templates/seed/squads/engineering/test-writer.md +60 -0
- package/templates/seed/squads/intelligence/SQUAD.md +38 -0
- package/templates/seed/squads/intelligence/intel-critic.md +53 -0
- package/templates/seed/squads/intelligence/intel-eval.md +48 -0
- package/templates/seed/squads/intelligence/intel-lead.md +79 -0
- package/templates/seed/squads/marketing/SQUAD.md +47 -0
- package/templates/seed/squads/marketing/content-drafter.md +81 -0
- package/templates/seed/squads/marketing/growth-analyst.md +61 -0
- package/templates/seed/squads/marketing/social-poster.md +56 -0
- package/templates/seed/squads/operations/SQUAD.md +45 -0
- package/templates/seed/squads/operations/finance-tracker.md +55 -0
- package/templates/seed/squads/operations/goal-tracker.md +60 -0
- package/templates/seed/squads/operations/ops-lead.md +68 -0
- package/templates/seed/squads/product/SQUAD.md +41 -0
- package/templates/seed/squads/product/lead.md +64 -0
- package/templates/seed/squads/product/scanner.md +58 -0
- package/templates/seed/squads/product/worker.md +63 -0
- package/templates/seed/squads/research/SQUAD.md +38 -0
- package/templates/seed/squads/research/analyst.md +58 -0
- package/templates/seed/squads/research/lead.md +58 -0
- package/templates/seed/squads/research/synthesizer.md +67 -0
- package/templates/skills/squads-learn/SKILL.md +86 -0
- package/templates/skills/squads-workflow/instruction.md +70 -0
|
@@ -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":[]}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/version.ts
|
|
4
|
+
import { createRequire } from "module";
|
|
5
|
+
var require2 = createRequire(import.meta.url);
|
|
6
|
+
var pkg = require2("../package.json");
|
|
7
|
+
var version = pkg.version;
|
|
8
|
+
|
|
9
|
+
// src/lib/telemetry.ts
|
|
10
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
11
|
+
import { join } from "path";
|
|
12
|
+
import { homedir, platform, release } from "os";
|
|
13
|
+
import { randomUUID } from "crypto";
|
|
14
|
+
var TELEMETRY_DIR = join(homedir(), ".squads-cli");
|
|
15
|
+
var CONFIG_PATH = join(TELEMETRY_DIR, "telemetry.json");
|
|
16
|
+
var EVENTS_PATH = join(TELEMETRY_DIR, "events.json");
|
|
17
|
+
var TELEMETRY_ENDPOINT = process.env.SQUADS_TELEMETRY_ENDPOINT || Buffer.from(
|
|
18
|
+
"aHR0cHM6Ly9zcXVhZHMtdGVsZW1ldHJ5LTk3ODg3MTgxNzYxMC51cy1jZW50cmFsMS5ydW4uYXBwL3Bpbmc=",
|
|
19
|
+
"base64"
|
|
20
|
+
).toString();
|
|
21
|
+
var TELEMETRY_KEY = process.env.SQUADS_TELEMETRY_KEY || "";
|
|
22
|
+
var eventQueue = [];
|
|
23
|
+
var flushScheduled = false;
|
|
24
|
+
var cachedSystemContext = null;
|
|
25
|
+
function detectUserType() {
|
|
26
|
+
if (process.env.SQUADS_AGENT) {
|
|
27
|
+
return "agent";
|
|
28
|
+
}
|
|
29
|
+
if (process.env.CI === "true" || process.env.GITHUB_ACTIONS === "true" || process.env.GITLAB_CI === "true" || process.env.JENKINS_URL || process.env.BUILDKITE === "true" || process.env.CIRCLECI === "true" || process.env.AZURE_PIPELINES === "true" || process.env.TF_BUILD === "True") {
|
|
30
|
+
return "ci";
|
|
31
|
+
}
|
|
32
|
+
return "human";
|
|
33
|
+
}
|
|
34
|
+
function getSystemContext() {
|
|
35
|
+
if (cachedSystemContext) return cachedSystemContext;
|
|
36
|
+
cachedSystemContext = {
|
|
37
|
+
os: platform(),
|
|
38
|
+
// darwin, linux, win32
|
|
39
|
+
osVersion: release(),
|
|
40
|
+
nodeVersion: process.version,
|
|
41
|
+
shell: process.env.SHELL?.split("/").pop() || process.env.ComSpec?.split("\\").pop(),
|
|
42
|
+
terminal: process.env.TERM_PROGRAM || void 0,
|
|
43
|
+
ci: process.env.CI === "true" ? "true" : void 0,
|
|
44
|
+
userType: detectUserType(),
|
|
45
|
+
// Agent context (set by squads run)
|
|
46
|
+
squad: process.env.SQUADS_SQUAD || void 0,
|
|
47
|
+
agent: process.env.SQUADS_AGENT || void 0,
|
|
48
|
+
executionId: process.env.SQUADS_EXECUTION_ID || void 0
|
|
49
|
+
};
|
|
50
|
+
return cachedSystemContext;
|
|
51
|
+
}
|
|
52
|
+
function ensureDir() {
|
|
53
|
+
if (!existsSync(TELEMETRY_DIR)) {
|
|
54
|
+
mkdirSync(TELEMETRY_DIR, { recursive: true });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function getConfig() {
|
|
58
|
+
ensureDir();
|
|
59
|
+
if (!existsSync(CONFIG_PATH)) {
|
|
60
|
+
const config = {
|
|
61
|
+
enabled: true,
|
|
62
|
+
// Opt-out by default (common for CLIs)
|
|
63
|
+
anonymousId: randomUUID(),
|
|
64
|
+
firstRun: (/* @__PURE__ */ new Date()).toISOString()
|
|
65
|
+
};
|
|
66
|
+
writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
|
|
67
|
+
return config;
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
return JSON.parse(readFileSync(CONFIG_PATH, "utf-8"));
|
|
71
|
+
} catch {
|
|
72
|
+
return { enabled: false, anonymousId: "", firstRun: "" };
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function isEnabled() {
|
|
76
|
+
if (process.env.SQUADS_TELEMETRY_DISABLED === "1") {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
if (process.env.DO_NOT_TRACK === "1") {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
return getConfig().enabled;
|
|
83
|
+
}
|
|
84
|
+
async function track(event, properties) {
|
|
85
|
+
if (!isEnabled()) return;
|
|
86
|
+
const config = getConfig();
|
|
87
|
+
const telemetryEvent = {
|
|
88
|
+
event,
|
|
89
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
90
|
+
properties: {
|
|
91
|
+
...properties,
|
|
92
|
+
...getSystemContext(),
|
|
93
|
+
anonymousId: config.anonymousId,
|
|
94
|
+
cliVersion: version
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
storeEventLocally(telemetryEvent);
|
|
98
|
+
eventQueue.push(telemetryEvent);
|
|
99
|
+
if (TELEMETRY_ENDPOINT && !flushScheduled) {
|
|
100
|
+
flushScheduled = true;
|
|
101
|
+
setImmediate(() => {
|
|
102
|
+
flushEvents().catch(() => {
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
async function flushEvents() {
|
|
108
|
+
if (!TELEMETRY_ENDPOINT || !TELEMETRY_KEY || eventQueue.length === 0) {
|
|
109
|
+
flushScheduled = false;
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const batch = [...eventQueue];
|
|
113
|
+
eventQueue = [];
|
|
114
|
+
flushScheduled = false;
|
|
115
|
+
try {
|
|
116
|
+
await fetch(TELEMETRY_ENDPOINT, {
|
|
117
|
+
method: "POST",
|
|
118
|
+
headers: {
|
|
119
|
+
"Content-Type": "application/json",
|
|
120
|
+
"X-Squads-Key": TELEMETRY_KEY
|
|
121
|
+
},
|
|
122
|
+
body: JSON.stringify({ events: batch }),
|
|
123
|
+
signal: AbortSignal.timeout(5e3)
|
|
124
|
+
});
|
|
125
|
+
} catch {
|
|
126
|
+
eventQueue = [...batch, ...eventQueue].slice(-100);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
function storeEventLocally(event) {
|
|
130
|
+
ensureDir();
|
|
131
|
+
let events = [];
|
|
132
|
+
if (existsSync(EVENTS_PATH)) {
|
|
133
|
+
try {
|
|
134
|
+
events = JSON.parse(readFileSync(EVENTS_PATH, "utf-8"));
|
|
135
|
+
} catch {
|
|
136
|
+
events = [];
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
events.push(event);
|
|
140
|
+
if (events.length > 1e3) {
|
|
141
|
+
events = events.slice(-1e3);
|
|
142
|
+
}
|
|
143
|
+
writeFileSync(EVENTS_PATH, JSON.stringify(events, null, 2));
|
|
144
|
+
}
|
|
145
|
+
var Events = {
|
|
146
|
+
// Lifecycle
|
|
147
|
+
CLI_INIT: "cli.init",
|
|
148
|
+
CLI_ERROR: "cli.error",
|
|
149
|
+
// Commands
|
|
150
|
+
CLI_RUN: "cli.run",
|
|
151
|
+
CLI_STATUS: "cli.status",
|
|
152
|
+
CLI_DASHBOARD: "cli.dashboard",
|
|
153
|
+
CLI_WORKERS: "cli.workers",
|
|
154
|
+
CLI_TONIGHT: "cli.tonight",
|
|
155
|
+
CLI_CONTEXT: "cli.context",
|
|
156
|
+
CLI_COST: "cli.cost",
|
|
157
|
+
CLI_EXEC: "cli.exec",
|
|
158
|
+
CLI_BASELINE: "cli.baseline",
|
|
159
|
+
// Goals
|
|
160
|
+
CLI_GOAL_SET: "cli.goal.set",
|
|
161
|
+
CLI_GOAL_LIST: "cli.goal.list",
|
|
162
|
+
CLI_GOAL_COMPLETE: "cli.goal.complete",
|
|
163
|
+
CLI_GOAL_PROGRESS: "cli.goal.progress",
|
|
164
|
+
// Memory
|
|
165
|
+
CLI_MEMORY_QUERY: "cli.memory.query",
|
|
166
|
+
CLI_MEMORY_SHOW: "cli.memory.show",
|
|
167
|
+
CLI_MEMORY_UPDATE: "cli.memory.update",
|
|
168
|
+
CLI_MEMORY_LIST: "cli.memory.list",
|
|
169
|
+
CLI_MEMORY_SYNC: "cli.memory.sync",
|
|
170
|
+
// Feedback
|
|
171
|
+
CLI_FEEDBACK_ADD: "cli.feedback.add",
|
|
172
|
+
CLI_FEEDBACK_SHOW: "cli.feedback.show",
|
|
173
|
+
CLI_FEEDBACK_STATS: "cli.feedback.stats",
|
|
174
|
+
// Learnings
|
|
175
|
+
CLI_LEARN: "cli.learn",
|
|
176
|
+
CLI_LEARN_SHOW: "cli.learn.show",
|
|
177
|
+
CLI_LEARN_SEARCH: "cli.learn.search",
|
|
178
|
+
// Auth
|
|
179
|
+
CLI_LOGIN: "cli.login",
|
|
180
|
+
CLI_LOGOUT: "cli.logout",
|
|
181
|
+
// Providers
|
|
182
|
+
CLI_PROVIDERS: "cli.providers",
|
|
183
|
+
// KPIs
|
|
184
|
+
CLI_KPI_SHOW: "cli.kpi.show",
|
|
185
|
+
CLI_KPI_RECORD: "cli.kpi.record",
|
|
186
|
+
CLI_KPI_TREND: "cli.kpi.trend",
|
|
187
|
+
CLI_KPI_INSIGHTS: "cli.kpi.insights",
|
|
188
|
+
CLI_KPI_LIST: "cli.kpi.list",
|
|
189
|
+
// Cycle Sync
|
|
190
|
+
CLI_SYNC_CYCLE: "cli.sync.cycle",
|
|
191
|
+
// Context Condenser
|
|
192
|
+
CONDENSER_COMPRESS: "condenser.compress",
|
|
193
|
+
CONDENSER_DEDUPE: "condenser.dedupe",
|
|
194
|
+
CONDENSER_PRUNE: "condenser.prune",
|
|
195
|
+
CONDENSER_SUMMARIZE: "condenser.summarize"
|
|
196
|
+
};
|
|
197
|
+
var exitHandlerRegistered = false;
|
|
198
|
+
function registerExitHandler() {
|
|
199
|
+
if (exitHandlerRegistered) return;
|
|
200
|
+
exitHandlerRegistered = true;
|
|
201
|
+
process.on("beforeExit", async () => {
|
|
202
|
+
if (eventQueue.length > 0) {
|
|
203
|
+
await flushEvents();
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
const signalHandler = async (_signal) => {
|
|
207
|
+
if (eventQueue.length > 0) {
|
|
208
|
+
await flushEvents();
|
|
209
|
+
}
|
|
210
|
+
process.exit(0);
|
|
211
|
+
};
|
|
212
|
+
process.on("SIGINT", () => signalHandler("SIGINT"));
|
|
213
|
+
process.on("SIGTERM", () => signalHandler("SIGTERM"));
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export {
|
|
217
|
+
version,
|
|
218
|
+
track,
|
|
219
|
+
flushEvents,
|
|
220
|
+
Events,
|
|
221
|
+
registerExitHandler
|
|
222
|
+
};
|
|
223
|
+
//# sourceMappingURL=chunk-QJ7C7CMB.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/version.ts","../src/lib/telemetry.ts"],"sourcesContent":["import { createRequire } from 'module';\nconst require = createRequire(import.meta.url);\nconst pkg = require('../package.json');\nexport const version: string = pkg.version;\n","import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';\nimport { join } from 'path';\nimport { homedir, platform, release } from 'os';\nimport { randomUUID } from 'crypto';\nimport { version as cliVersion } from '../version.js';\n\ninterface TelemetryEvent {\n event: string;\n timestamp: string;\n properties?: Record<string, string | number | boolean | undefined>;\n}\n\ninterface TelemetryConfig {\n enabled: boolean;\n anonymousId: string;\n firstRun: string;\n}\n\nconst TELEMETRY_DIR = join(homedir(), '.squads-cli');\nconst CONFIG_PATH = join(TELEMETRY_DIR, 'telemetry.json');\nconst EVENTS_PATH = join(TELEMETRY_DIR, 'events.json');\n\n// Telemetry endpoint - locked to Agents Squads infrastructure\n// Users can opt-out but cannot redirect telemetry\nconst TELEMETRY_ENDPOINT = process.env.SQUADS_TELEMETRY_ENDPOINT || Buffer.from(\n 'aHR0cHM6Ly9zcXVhZHMtdGVsZW1ldHJ5LTk3ODg3MTgxNzYxMC51cy1jZW50cmFsMS5ydW4uYXBwL3Bpbmc=',\n 'base64'\n).toString();\n\n// API key for endpoint validation — must be set via environment variable\n// NEVER hardcode API keys in source (see: engineering#51)\nconst TELEMETRY_KEY = process.env.SQUADS_TELEMETRY_KEY || '';\n\n// Event queue for batch flushing\nlet eventQueue: TelemetryEvent[] = [];\nlet flushScheduled = false;\n\n// Cached system context (computed once per session)\nlet cachedSystemContext: Record<string, string | undefined> | null = null;\n\n/**\n * Detect user type for segmentation.\n * - 'agent': Autonomous agent execution (via squads run)\n * - 'ci': CI/CD environment (GitHub Actions, Azure Pipelines, etc)\n * - 'human': Interactive terminal usage\n */\nfunction detectUserType(): 'human' | 'agent' | 'ci' {\n // Agent execution (set by squads run command)\n // SQUADS_AGENT contains the agent name when running via squads run\n if (process.env.SQUADS_AGENT) {\n return 'agent';\n }\n\n // CI/CD environments\n if (\n process.env.CI === 'true' ||\n process.env.GITHUB_ACTIONS === 'true' ||\n process.env.GITLAB_CI === 'true' ||\n process.env.JENKINS_URL ||\n process.env.BUILDKITE === 'true' ||\n process.env.CIRCLECI === 'true' ||\n process.env.AZURE_PIPELINES === 'true' ||\n process.env.TF_BUILD === 'True'\n ) {\n return 'ci';\n }\n\n return 'human';\n}\n\n/**\n * Get minimal system context for error identification.\n * Computed once per session for performance.\n */\nfunction getSystemContext(): Record<string, string | undefined> {\n if (cachedSystemContext) return cachedSystemContext;\n\n cachedSystemContext = {\n os: platform(), // darwin, linux, win32\n osVersion: release(),\n nodeVersion: process.version,\n shell: process.env.SHELL?.split('/').pop() || process.env.ComSpec?.split('\\\\').pop(),\n terminal: process.env.TERM_PROGRAM || undefined,\n ci: process.env.CI === 'true' ? 'true' : undefined,\n userType: detectUserType(),\n // Agent context (set by squads run)\n squad: process.env.SQUADS_SQUAD || undefined,\n agent: process.env.SQUADS_AGENT || undefined,\n executionId: process.env.SQUADS_EXECUTION_ID || undefined,\n };\n\n return cachedSystemContext;\n}\n\nfunction ensureDir(): void {\n if (!existsSync(TELEMETRY_DIR)) {\n mkdirSync(TELEMETRY_DIR, { recursive: true });\n }\n}\n\nfunction getConfig(): TelemetryConfig {\n ensureDir();\n\n if (!existsSync(CONFIG_PATH)) {\n const config: TelemetryConfig = {\n enabled: true, // Opt-out by default (common for CLIs)\n anonymousId: randomUUID(),\n firstRun: new Date().toISOString(),\n };\n writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));\n return config;\n }\n\n try {\n return JSON.parse(readFileSync(CONFIG_PATH, 'utf-8'));\n } catch {\n return { enabled: false, anonymousId: '', firstRun: '' };\n }\n}\n\nfunction saveConfig(config: TelemetryConfig): void {\n ensureDir();\n writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));\n}\n\n/**\n * Check if telemetry is enabled.\n * Telemetry is disabled if SQUADS_TELEMETRY_DISABLED=1 or DO_NOT_TRACK=1.\n * @returns true if telemetry collection is enabled\n */\nexport function isEnabled(): boolean {\n // Check environment variable first (allows CI/testing override)\n if (process.env.SQUADS_TELEMETRY_DISABLED === '1') {\n return false;\n }\n if (process.env.DO_NOT_TRACK === '1') {\n return false;\n }\n\n return getConfig().enabled;\n}\n\n/**\n * Enable telemetry collection.\n * Persists the setting to ~/.squads-cli/telemetry.json\n */\nexport function enable(): void {\n const config = getConfig();\n config.enabled = true;\n saveConfig(config);\n}\n\n/**\n * Disable telemetry collection.\n * Persists the setting to ~/.squads-cli/telemetry.json\n */\nexport function disable(): void {\n const config = getConfig();\n config.enabled = false;\n saveConfig(config);\n}\n\n/**\n * Get the anonymous identifier for this CLI installation.\n * Generated once on first run and persisted.\n * @returns UUID string\n */\nexport function getAnonymousId(): string {\n return getConfig().anonymousId;\n}\n\n/**\n * Track a telemetry event with optional properties.\n * Events are batched and flushed asynchronously.\n * @param event - Event name (e.g., 'cli.status', 'cli.error')\n * @param properties - Optional key-value pairs of event metadata\n */\nexport async function track(event: string, properties?: Record<string, string | number | boolean | undefined>): Promise<void> {\n if (!isEnabled()) return;\n\n const config = getConfig();\n\n const telemetryEvent: TelemetryEvent = {\n event,\n timestamp: new Date().toISOString(),\n properties: {\n ...properties,\n ...getSystemContext(),\n anonymousId: config.anonymousId,\n cliVersion,\n },\n };\n\n // Store locally (for debugging/review)\n storeEventLocally(telemetryEvent);\n\n // Queue for batch sending\n eventQueue.push(telemetryEvent);\n\n // Schedule flush if not already scheduled\n if (TELEMETRY_ENDPOINT && !flushScheduled) {\n flushScheduled = true;\n // Flush on next tick to batch events from same command\n setImmediate(() => {\n flushEvents().catch(() => {});\n });\n }\n}\n\n/**\n * Flush queued events to the telemetry endpoint\n */\nexport async function flushEvents(): Promise<void> {\n if (!TELEMETRY_ENDPOINT || !TELEMETRY_KEY || eventQueue.length === 0) {\n flushScheduled = false;\n return;\n }\n\n const batch = [...eventQueue];\n eventQueue = [];\n flushScheduled = false;\n\n try {\n await fetch(TELEMETRY_ENDPOINT, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'X-Squads-Key': TELEMETRY_KEY,\n },\n body: JSON.stringify({ events: batch }),\n signal: AbortSignal.timeout(5000),\n });\n } catch {\n // Restore events on failure (will retry on next track)\n eventQueue = [...batch, ...eventQueue].slice(-100); // Keep max 100\n }\n}\n\n/**\n * Track an error event\n */\nexport async function trackError(\n command: string,\n error: Error,\n context?: Record<string, string | number | boolean>\n): Promise<void> {\n await track(Events.CLI_ERROR, {\n command,\n errorType: error.constructor.name,\n errorMessage: error.message.slice(0, 100), // Truncate for privacy\n ...context,\n });\n}\n\n/**\n * Wrap an async command function with telemetry\n */\nexport function instrumentCommand<T>(\n name: string,\n fn: () => Promise<T>\n): () => Promise<T> {\n return async () => {\n const start = Date.now();\n try {\n const result = await fn();\n await track(`cli.${name}`, {\n durationMs: Date.now() - start,\n success: true,\n });\n return result;\n } catch (error) {\n await trackError(name, error as Error, {\n durationMs: Date.now() - start,\n });\n throw error;\n }\n };\n}\n\nfunction storeEventLocally(event: TelemetryEvent): void {\n ensureDir();\n\n let events: TelemetryEvent[] = [];\n\n if (existsSync(EVENTS_PATH)) {\n try {\n events = JSON.parse(readFileSync(EVENTS_PATH, 'utf-8'));\n } catch {\n events = [];\n }\n }\n\n // Keep last 1000 events\n events.push(event);\n if (events.length > 1000) {\n events = events.slice(-1000);\n }\n\n writeFileSync(EVENTS_PATH, JSON.stringify(events, null, 2));\n}\n\n/**\n * Pre-defined event names for consistency across the CLI.\n * Use these constants instead of string literals.\n */\nexport const Events = {\n // Lifecycle\n CLI_INIT: 'cli.init',\n CLI_ERROR: 'cli.error',\n\n // Commands\n CLI_RUN: 'cli.run',\n CLI_STATUS: 'cli.status',\n CLI_DASHBOARD: 'cli.dashboard',\n CLI_WORKERS: 'cli.workers',\n CLI_TONIGHT: 'cli.tonight',\n CLI_CONTEXT: 'cli.context',\n CLI_COST: 'cli.cost',\n CLI_EXEC: 'cli.exec',\n CLI_BASELINE: 'cli.baseline',\n\n // Goals\n CLI_GOAL_SET: 'cli.goal.set',\n CLI_GOAL_LIST: 'cli.goal.list',\n CLI_GOAL_COMPLETE: 'cli.goal.complete',\n CLI_GOAL_PROGRESS: 'cli.goal.progress',\n\n // Memory\n CLI_MEMORY_QUERY: 'cli.memory.query',\n CLI_MEMORY_SHOW: 'cli.memory.show',\n CLI_MEMORY_UPDATE: 'cli.memory.update',\n CLI_MEMORY_LIST: 'cli.memory.list',\n CLI_MEMORY_SYNC: 'cli.memory.sync',\n\n // Feedback\n CLI_FEEDBACK_ADD: 'cli.feedback.add',\n CLI_FEEDBACK_SHOW: 'cli.feedback.show',\n CLI_FEEDBACK_STATS: 'cli.feedback.stats',\n\n // Learnings\n CLI_LEARN: 'cli.learn',\n CLI_LEARN_SHOW: 'cli.learn.show',\n CLI_LEARN_SEARCH: 'cli.learn.search',\n\n // Auth\n CLI_LOGIN: 'cli.login',\n CLI_LOGOUT: 'cli.logout',\n\n // Providers\n CLI_PROVIDERS: 'cli.providers',\n\n // KPIs\n CLI_KPI_SHOW: 'cli.kpi.show',\n CLI_KPI_RECORD: 'cli.kpi.record',\n CLI_KPI_TREND: 'cli.kpi.trend',\n CLI_KPI_INSIGHTS: 'cli.kpi.insights',\n CLI_KPI_LIST: 'cli.kpi.list',\n\n // Cycle Sync\n CLI_SYNC_CYCLE: 'cli.sync.cycle',\n\n // Context Condenser\n CONDENSER_COMPRESS: 'condenser.compress',\n CONDENSER_DEDUPE: 'condenser.dedupe',\n CONDENSER_PRUNE: 'condenser.prune',\n CONDENSER_SUMMARIZE: 'condenser.summarize',\n} as const;\n\n/**\n * Track command execution time.\n * Call at start of command, returns function to call when command completes.\n * @param command - Command name (without 'cli.' prefix)\n * @returns Callback to invoke when command completes\n * @example\n * const done = trackCommand('status');\n * // ... execute command ...\n * done(); // Records duration\n */\nexport function trackCommand(command: string): () => void {\n const start = Date.now();\n\n return () => {\n const duration = Date.now() - start;\n track(`cli.${command}`, { durationMs: duration });\n };\n}\n\n// Register exit handler to flush remaining events\nlet exitHandlerRegistered = false;\n\n/**\n * Register process exit handlers to flush pending telemetry events.\n * Call once at CLI startup. Handles SIGINT, SIGTERM, and normal exit.\n */\nexport function registerExitHandler(): void {\n if (exitHandlerRegistered) return;\n exitHandlerRegistered = true;\n\n // beforeExit allows async operations (unlike 'exit')\n process.on('beforeExit', async () => {\n if (eventQueue.length > 0) {\n await flushEvents();\n }\n });\n\n // For signals, we need to handle manually\n const signalHandler = async (_signal: string) => {\n if (eventQueue.length > 0) {\n await flushEvents();\n }\n process.exit(0);\n };\n\n process.on('SIGINT', () => signalHandler('SIGINT'));\n process.on('SIGTERM', () => signalHandler('SIGTERM'));\n}\n"],"mappings":"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","names":["require"]}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/lib/llm-clis.ts
|
|
4
|
+
import { execSync } from "child_process";
|
|
5
|
+
function commandExists(command) {
|
|
6
|
+
try {
|
|
7
|
+
execSync(`which ${command}`, { stdio: "pipe" });
|
|
8
|
+
return true;
|
|
9
|
+
} catch {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
var LLM_CLIS = {
|
|
14
|
+
anthropic: {
|
|
15
|
+
provider: "anthropic",
|
|
16
|
+
displayName: "Anthropic",
|
|
17
|
+
command: "claude",
|
|
18
|
+
install: "npm i -g @anthropic-ai/claude-code",
|
|
19
|
+
buildArgs: (prompt) => ["--print", prompt]
|
|
20
|
+
},
|
|
21
|
+
google: {
|
|
22
|
+
provider: "google",
|
|
23
|
+
displayName: "Google",
|
|
24
|
+
command: "gemini",
|
|
25
|
+
install: "npm i -g @google/gemini-cli",
|
|
26
|
+
buildArgs: (prompt) => ["--yolo", "--prompt", prompt]
|
|
27
|
+
},
|
|
28
|
+
openai: {
|
|
29
|
+
provider: "openai",
|
|
30
|
+
displayName: "OpenAI",
|
|
31
|
+
command: "codex",
|
|
32
|
+
install: "npm i -g @openai/codex",
|
|
33
|
+
buildArgs: (prompt) => ["exec", prompt]
|
|
34
|
+
},
|
|
35
|
+
mistral: {
|
|
36
|
+
provider: "mistral",
|
|
37
|
+
displayName: "Mistral",
|
|
38
|
+
command: "vibe",
|
|
39
|
+
install: "curl -LsSf https://mistral.ai/vibe/install.sh | bash",
|
|
40
|
+
buildArgs: (prompt) => ["--prompt", prompt, "--auto-approve"]
|
|
41
|
+
},
|
|
42
|
+
xai: {
|
|
43
|
+
provider: "xai",
|
|
44
|
+
displayName: "xAI",
|
|
45
|
+
command: "grok",
|
|
46
|
+
install: "bun add -g @vibe-kit/grok-cli",
|
|
47
|
+
buildArgs: (prompt) => ["--prompt", prompt]
|
|
48
|
+
},
|
|
49
|
+
aider: {
|
|
50
|
+
provider: "aider",
|
|
51
|
+
displayName: "Aider (Multi)",
|
|
52
|
+
command: "aider",
|
|
53
|
+
install: "pip install aider-install && aider-install",
|
|
54
|
+
buildArgs: (prompt) => ["--message", prompt, "--yes"]
|
|
55
|
+
},
|
|
56
|
+
ollama: {
|
|
57
|
+
provider: "ollama",
|
|
58
|
+
displayName: "Ollama (Local)",
|
|
59
|
+
command: "ollama",
|
|
60
|
+
install: "brew install ollama",
|
|
61
|
+
buildArgs: (_prompt, opts) => ["run", opts?.model || "llama3.1"],
|
|
62
|
+
stdinPrompt: true
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
function getAllCLIStatus() {
|
|
66
|
+
return Object.values(LLM_CLIS).map((cli) => ({
|
|
67
|
+
provider: cli.provider,
|
|
68
|
+
displayName: cli.displayName,
|
|
69
|
+
command: cli.command,
|
|
70
|
+
available: commandExists(cli.command),
|
|
71
|
+
install: cli.install
|
|
72
|
+
}));
|
|
73
|
+
}
|
|
74
|
+
function getCLIConfig(provider) {
|
|
75
|
+
return LLM_CLIS[provider];
|
|
76
|
+
}
|
|
77
|
+
function isProviderCLIAvailable(provider) {
|
|
78
|
+
const config = LLM_CLIS[provider];
|
|
79
|
+
if (!config) return false;
|
|
80
|
+
return commandExists(config.command);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export {
|
|
84
|
+
getAllCLIStatus,
|
|
85
|
+
getCLIConfig,
|
|
86
|
+
isProviderCLIAvailable
|
|
87
|
+
};
|
|
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":[]}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/lib/cron.ts
|
|
4
|
+
function cronMatches(cron, date) {
|
|
5
|
+
const parts = cron.trim().split(/\s+/);
|
|
6
|
+
if (parts.length < 5) return false;
|
|
7
|
+
const fields = [
|
|
8
|
+
{ value: date.getMinutes(), field: parts[0], min: 0, max: 59 },
|
|
9
|
+
{ value: date.getHours(), field: parts[1], min: 0, max: 23 },
|
|
10
|
+
{ value: date.getDate(), field: parts[2], min: 1, max: 31 },
|
|
11
|
+
{ value: date.getMonth() + 1, field: parts[3], min: 1, max: 12 },
|
|
12
|
+
{ value: date.getDay(), field: parts[4], min: 0, max: 6 }
|
|
13
|
+
];
|
|
14
|
+
return fields.every(
|
|
15
|
+
({ value, field, min, max }) => fieldMatches(field, value, min, max)
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
function fieldMatches(field, value, min, max) {
|
|
19
|
+
if (field.includes(",")) {
|
|
20
|
+
return field.split(",").some((part) => fieldMatches(part.trim(), value, min, max));
|
|
21
|
+
}
|
|
22
|
+
if (field.includes("/")) {
|
|
23
|
+
const [range, stepStr] = field.split("/");
|
|
24
|
+
const step = parseInt(stepStr);
|
|
25
|
+
if (isNaN(step) || step <= 0) return false;
|
|
26
|
+
let start = min;
|
|
27
|
+
let end = max;
|
|
28
|
+
if (range !== "*") {
|
|
29
|
+
if (range.includes("-")) {
|
|
30
|
+
[start, end] = range.split("-").map(Number);
|
|
31
|
+
} else {
|
|
32
|
+
start = parseInt(range);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (value < start || value > end) return false;
|
|
36
|
+
return (value - start) % step === 0;
|
|
37
|
+
}
|
|
38
|
+
if (field.includes("-")) {
|
|
39
|
+
const [start, end] = field.split("-").map(Number);
|
|
40
|
+
return value >= start && value <= end;
|
|
41
|
+
}
|
|
42
|
+
if (field === "*") return true;
|
|
43
|
+
return parseInt(field) === value;
|
|
44
|
+
}
|
|
45
|
+
function getNextCronRun(cron, after = /* @__PURE__ */ new Date()) {
|
|
46
|
+
const next = new Date(after);
|
|
47
|
+
next.setSeconds(0, 0);
|
|
48
|
+
next.setMinutes(next.getMinutes() + 1);
|
|
49
|
+
const maxIterations = 60 * 24 * 8;
|
|
50
|
+
for (let i = 0; i < maxIterations; i++) {
|
|
51
|
+
if (cronMatches(cron, next)) return next;
|
|
52
|
+
next.setMinutes(next.getMinutes() + 1);
|
|
53
|
+
}
|
|
54
|
+
const fallback = new Date(after);
|
|
55
|
+
fallback.setDate(fallback.getDate() + 1);
|
|
56
|
+
return fallback;
|
|
57
|
+
}
|
|
58
|
+
function parseCooldown(cooldown) {
|
|
59
|
+
const match = cooldown.match(/^(\d+)\s*(m|min|minutes?|h|hours?|d|days?)$/i);
|
|
60
|
+
if (!match) return 0;
|
|
61
|
+
const value = parseInt(match[1]);
|
|
62
|
+
const unit = match[2].toLowerCase();
|
|
63
|
+
if (unit.startsWith("m")) return value * 60 * 1e3;
|
|
64
|
+
if (unit.startsWith("h")) return value * 60 * 60 * 1e3;
|
|
65
|
+
if (unit.startsWith("d")) return value * 24 * 60 * 60 * 1e3;
|
|
66
|
+
return 0;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export {
|
|
70
|
+
cronMatches,
|
|
71
|
+
getNextCronRun,
|
|
72
|
+
parseCooldown
|
|
73
|
+
};
|
|
74
|
+
//# sourceMappingURL=chunk-RM6BWILN.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/cron.ts"],"sourcesContent":["/**\n * Zero-dependency cron evaluator utilities\n * Extracted from autonomous.ts for reusability and testing\n */\n\n/**\n * Check if a cron expression matches a given date\n * @param cron - Cron expression (minute hour day month weekday)\n * @param date - Date to check against\n * @returns true if the cron matches the date\n */\nexport function cronMatches(cron: string, date: Date): boolean {\n const parts = cron.trim().split(/\\s+/);\n if (parts.length < 5) return false;\n\n const fields = [\n { value: date.getMinutes(), field: parts[0], min: 0, max: 59 },\n { value: date.getHours(), field: parts[1], min: 0, max: 23 },\n { value: date.getDate(), field: parts[2], min: 1, max: 31 },\n { value: date.getMonth() + 1, field: parts[3], min: 1, max: 12 },\n { value: date.getDay(), field: parts[4], min: 0, max: 6 },\n ];\n\n return fields.every(({ value, field, min, max }) =>\n fieldMatches(field, value, min, max)\n );\n}\n\n/**\n * Check if a cron field matches a value\n * Handles wildcards, ranges, steps, and lists\n */\nexport function fieldMatches(\n field: string,\n value: number,\n min: number,\n max: number\n): boolean {\n // Handle lists: \"1,3,5\"\n if (field.includes(\",\")) {\n return field.split(\",\").some((part) => fieldMatches(part.trim(), value, min, max));\n }\n\n // Handle step: \"*/5\" or \"1-10/2\"\n if (field.includes(\"/\")) {\n const [range, stepStr] = field.split(\"/\");\n const step = parseInt(stepStr);\n if (isNaN(step) || step <= 0) return false;\n\n let start = min;\n let end = max;\n if (range !== \"*\") {\n if (range.includes(\"-\")) {\n [start, end] = range.split(\"-\").map(Number);\n } else {\n start = parseInt(range);\n }\n }\n if (value < start || value > end) return false;\n return (value - start) % step === 0;\n }\n\n // Handle range: \"1-5\"\n if (field.includes(\"-\")) {\n const [start, end] = field.split(\"-\").map(Number);\n return value >= start && value <= end;\n }\n\n // Wildcard\n if (field === \"*\") return true;\n\n // Exact match\n return parseInt(field) === value;\n}\n\n/**\n * Get the next occurrence of a cron expression after `after`.\n * Brute-forces minute by minute (max 48h lookahead).\n */\nexport function getNextCronRun(cron: string, after: Date = new Date()): Date {\n const next = new Date(after);\n next.setSeconds(0, 0);\n next.setMinutes(next.getMinutes() + 1); // Start from next minute\n\n const maxIterations = 60 * 24 * 8; // 8 days (covers weekly schedules)\n for (let i = 0; i < maxIterations; i++) {\n if (cronMatches(cron, next)) return next;\n next.setMinutes(next.getMinutes() + 1);\n }\n\n // Fallback: 24h from now\n const fallback = new Date(after);\n fallback.setDate(fallback.getDate() + 1);\n return fallback;\n}\n\n/**\n * Parse a cooldown string like \"30m\", \"6 hours\", \"7d\" into milliseconds\n * @param cooldown - String like \"30m\", \"6 hours\", \"7d\"\n * @returns milliseconds, or 0 if invalid\n */\nexport function parseCooldown(cooldown: string): number {\n const match = cooldown.match(/^(\\d+)\\s*(m|min|minutes?|h|hours?|d|days?)$/i);\n if (!match) return 0;\n\n const value = parseInt(match[1]);\n const unit = match[2].toLowerCase();\n\n if (unit.startsWith(\"m\")) return value * 60 * 1000;\n if (unit.startsWith(\"h\")) return value * 60 * 60 * 1000;\n if (unit.startsWith(\"d\")) return value * 24 * 60 * 60 * 1000;\n return 0;\n}\n"],"mappings":"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","names":[]}
|