squads-cli 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +521 -288
- package/dist/auth-YW3UPFSB.js +23 -0
- package/dist/auth-YW3UPFSB.js.map +1 -0
- package/dist/autonomy-PSVZVX7A.js +105 -0
- package/dist/autonomy-PSVZVX7A.js.map +1 -0
- package/dist/chunk-67RO2HKR.js +174 -0
- package/dist/chunk-67RO2HKR.js.map +1 -0
- package/dist/chunk-7OCVIDC7.js +12 -0
- package/dist/chunk-7OCVIDC7.js.map +1 -0
- package/dist/chunk-BODLDQY7.js +452 -0
- package/dist/chunk-BODLDQY7.js.map +1 -0
- package/dist/chunk-EHQJHRIW.js +103 -0
- package/dist/chunk-EHQJHRIW.js.map +1 -0
- package/dist/chunk-FFFCFZ6A.js +121 -0
- package/dist/chunk-FFFCFZ6A.js.map +1 -0
- package/dist/chunk-FIWT2NMM.js +165 -0
- package/dist/chunk-FIWT2NMM.js.map +1 -0
- package/dist/chunk-HF4WR7RA.js +154 -0
- package/dist/chunk-HF4WR7RA.js.map +1 -0
- package/dist/chunk-J6QF4ZQX.js +230 -0
- package/dist/chunk-J6QF4ZQX.js.map +1 -0
- package/dist/chunk-LOA3KWYJ.js +294 -0
- package/dist/chunk-LOA3KWYJ.js.map +1 -0
- package/dist/chunk-M5FXNY6Y.js +384 -0
- package/dist/chunk-M5FXNY6Y.js.map +1 -0
- package/dist/chunk-QHNUMM4V.js +87 -0
- package/dist/chunk-QHNUMM4V.js.map +1 -0
- package/dist/chunk-QJ7C7CMB.js +223 -0
- package/dist/chunk-QJ7C7CMB.js.map +1 -0
- package/dist/chunk-RM6BWILN.js +74 -0
- package/dist/chunk-RM6BWILN.js.map +1 -0
- package/dist/chunk-TYFTF53O.js +613 -0
- package/dist/chunk-TYFTF53O.js.map +1 -0
- package/dist/chunk-TZXD6WFN.js +420 -0
- package/dist/chunk-TZXD6WFN.js.map +1 -0
- package/dist/chunk-WVOIY5GW.js +621 -0
- package/dist/chunk-WVOIY5GW.js.map +1 -0
- package/dist/chunk-Z2UKDBNL.js +162 -0
- package/dist/chunk-Z2UKDBNL.js.map +1 -0
- package/dist/chunk-ZTQ7ISUR.js +338 -0
- package/dist/chunk-ZTQ7ISUR.js.map +1 -0
- package/dist/cli.js +2483 -5902
- package/dist/cli.js.map +1 -1
- package/dist/context-GWPF4SEY.js +291 -0
- package/dist/context-GWPF4SEY.js.map +1 -0
- package/dist/context-feed-AJGVAR6H.js +394 -0
- package/dist/context-feed-AJGVAR6H.js.map +1 -0
- package/dist/cost-XBCDJ7XC.js +275 -0
- package/dist/cost-XBCDJ7XC.js.map +1 -0
- package/dist/create-BLFGG6PF.js +286 -0
- package/dist/create-BLFGG6PF.js.map +1 -0
- package/dist/dashboard-LGT2B2BL.js +951 -0
- package/dist/dashboard-LGT2B2BL.js.map +1 -0
- package/dist/dashboard-RMK2BOD2.js +794 -0
- package/dist/dashboard-RMK2BOD2.js.map +1 -0
- package/dist/doctor-XPUIIBHJ.js +374 -0
- package/dist/doctor-XPUIIBHJ.js.map +1 -0
- package/dist/env-config-SQEI3Y7Y.js +21 -0
- package/dist/env-config-SQEI3Y7Y.js.map +1 -0
- package/dist/exec-OUXM7JBF.js +223 -0
- package/dist/exec-OUXM7JBF.js.map +1 -0
- package/dist/feedback-KNAOG5QK.js +229 -0
- package/dist/feedback-KNAOG5QK.js.map +1 -0
- package/dist/github-UQTM5KMS.js +23 -0
- package/dist/github-UQTM5KMS.js.map +1 -0
- package/dist/goal-BVHV5573.js +168 -0
- package/dist/goal-BVHV5573.js.map +1 -0
- package/dist/health-4UXN44PF.js +218 -0
- package/dist/health-4UXN44PF.js.map +1 -0
- package/dist/history-ILH3SWHB.js +232 -0
- package/dist/history-ILH3SWHB.js.map +1 -0
- package/dist/index.d.ts +736 -8
- package/dist/index.js +1312 -6
- package/dist/index.js.map +1 -1
- package/dist/init-XQZ7BOGT.js +812 -0
- package/dist/init-XQZ7BOGT.js.map +1 -0
- package/dist/kpi-RQIU7WGK.js +413 -0
- package/dist/kpi-RQIU7WGK.js.map +1 -0
- package/dist/learn-OIFUVZAS.js +269 -0
- package/dist/learn-OIFUVZAS.js.map +1 -0
- package/dist/login-DXZANWZY.js +155 -0
- package/dist/login-DXZANWZY.js.map +1 -0
- package/dist/memory-T3ACCS7E.js +560 -0
- package/dist/memory-T3ACCS7E.js.map +1 -0
- package/dist/memory-VNF2VFRB.js +23 -0
- package/dist/memory-VNF2VFRB.js.map +1 -0
- package/dist/progress-DAUZMT3N.js +202 -0
- package/dist/progress-DAUZMT3N.js.map +1 -0
- package/dist/providers-3P5D2XL5.js +65 -0
- package/dist/providers-3P5D2XL5.js.map +1 -0
- package/dist/results-UECWGLTB.js +224 -0
- package/dist/results-UECWGLTB.js.map +1 -0
- package/dist/run-I6KAXU6U.js +4049 -0
- package/dist/run-I6KAXU6U.js.map +1 -0
- package/dist/session-HBU6KZOD.js +64 -0
- package/dist/session-HBU6KZOD.js.map +1 -0
- package/dist/sessions-CK25VGPL.js +333 -0
- package/dist/sessions-CK25VGPL.js.map +1 -0
- package/dist/squad-parser-DCG65BJS.js +35 -0
- package/dist/squad-parser-DCG65BJS.js.map +1 -0
- package/dist/stats-G6NAU5BD.js +334 -0
- package/dist/stats-G6NAU5BD.js.map +1 -0
- package/dist/status-AQNLDZVN.js +352 -0
- package/dist/status-AQNLDZVN.js.map +1 -0
- package/dist/sync-ZI3MHA4G.js +836 -0
- package/dist/sync-ZI3MHA4G.js.map +1 -0
- package/dist/templates/core/AGENTS.md.template +51 -0
- package/dist/templates/core/BUSINESS_BRIEF.md.template +29 -0
- package/dist/templates/core/CLAUDE.md.template +48 -0
- package/dist/templates/core/provider.yaml.template +5 -0
- package/dist/templates/first-squad/SQUAD.md.template +23 -0
- package/dist/templates/first-squad/lead.md.template +44 -0
- package/dist/templates/memory/getting-started/state.md.template +19 -0
- package/dist/templates/seed/BUSINESS_BRIEF.md.template +27 -0
- package/dist/templates/seed/CLAUDE.md.template +119 -0
- package/dist/templates/seed/README.md.template +42 -0
- package/dist/templates/seed/config/SYSTEM.md +52 -0
- package/dist/templates/seed/config/provider.yaml +4 -0
- package/dist/templates/seed/hooks/settings.json.template +31 -0
- package/dist/templates/seed/memory/company/directives.md +37 -0
- package/dist/templates/seed/memory/company/manager/state.md +16 -0
- package/dist/templates/seed/memory/engineering/issue-solver/state.md +12 -0
- package/dist/templates/seed/memory/intelligence/intel-lead/state.md +9 -0
- package/dist/templates/seed/memory/marketing/content-drafter/state.md +12 -0
- package/dist/templates/seed/memory/operations/ops-lead/state.md +12 -0
- package/dist/templates/seed/memory/product/lead/state.md +14 -0
- package/dist/templates/seed/memory/research/lead/state.md +14 -0
- package/dist/templates/seed/skills/gh/SKILL.md +57 -0
- package/dist/templates/seed/skills/squads-cli/SKILL.md +84 -0
- package/dist/templates/seed/squads/company/SQUAD.md +51 -0
- package/dist/templates/seed/squads/company/company-critic.md +49 -0
- package/dist/templates/seed/squads/company/company-eval.md +49 -0
- package/dist/templates/seed/squads/company/event-dispatcher.md +43 -0
- package/dist/templates/seed/squads/company/goal-tracker.md +43 -0
- package/dist/templates/seed/squads/company/manager.md +54 -0
- package/dist/templates/seed/squads/engineering/SQUAD.md +48 -0
- package/dist/templates/seed/squads/engineering/code-reviewer.md +57 -0
- package/dist/templates/seed/squads/engineering/issue-solver.md +58 -0
- package/dist/templates/seed/squads/engineering/test-writer.md +50 -0
- package/dist/templates/seed/squads/intelligence/SQUAD.md +38 -0
- package/dist/templates/seed/squads/intelligence/intel-critic.md +36 -0
- package/dist/templates/seed/squads/intelligence/intel-eval.md +31 -0
- package/dist/templates/seed/squads/intelligence/intel-lead.md +71 -0
- package/dist/templates/seed/squads/marketing/SQUAD.md +47 -0
- package/dist/templates/seed/squads/marketing/content-drafter.md +71 -0
- package/dist/templates/seed/squads/marketing/growth-analyst.md +49 -0
- package/dist/templates/seed/squads/marketing/social-poster.md +44 -0
- package/dist/templates/seed/squads/operations/SQUAD.md +45 -0
- package/dist/templates/seed/squads/operations/finance-tracker.md +47 -0
- package/dist/templates/seed/squads/operations/goal-tracker.md +48 -0
- package/dist/templates/seed/squads/operations/ops-lead.md +58 -0
- package/dist/templates/seed/squads/product/SQUAD.md +41 -0
- package/dist/templates/seed/squads/product/lead.md +56 -0
- package/dist/templates/seed/squads/product/scanner.md +50 -0
- package/dist/templates/seed/squads/product/worker.md +55 -0
- package/dist/templates/seed/squads/research/SQUAD.md +38 -0
- package/dist/templates/seed/squads/research/analyst.md +50 -0
- package/dist/templates/seed/squads/research/lead.md +52 -0
- package/dist/templates/seed/squads/research/synthesizer.md +59 -0
- package/dist/templates/skills/squads-learn/SKILL.md +86 -0
- package/dist/templates/skills/squads-workflow/instruction.md +70 -0
- package/dist/terminal-FBQFQTKZ.js +55 -0
- package/dist/terminal-FBQFQTKZ.js.map +1 -0
- package/dist/update-D7CGIZ3M.js +18 -0
- package/dist/update-D7CGIZ3M.js.map +1 -0
- package/dist/update-STU276HR.js +83 -0
- package/dist/update-STU276HR.js.map +1 -0
- package/package.json +31 -13
- package/templates/core/AGENTS.md.template +51 -0
- package/templates/core/BUSINESS_BRIEF.md.template +29 -0
- package/templates/core/CLAUDE.md.template +48 -0
- package/templates/core/provider.yaml.template +5 -0
- package/templates/first-squad/SQUAD.md.template +23 -0
- package/templates/first-squad/lead.md.template +44 -0
- package/templates/memory/getting-started/state.md.template +19 -0
- package/templates/seed/BUSINESS_BRIEF.md.template +27 -0
- package/templates/seed/CLAUDE.md.template +119 -0
- package/templates/seed/README.md.template +42 -0
- package/templates/seed/config/SYSTEM.md +52 -0
- package/templates/seed/config/provider.yaml +4 -0
- package/templates/seed/hooks/settings.json.template +31 -0
- package/templates/seed/memory/company/directives.md +37 -0
- package/templates/seed/memory/company/manager/state.md +16 -0
- package/templates/seed/memory/engineering/issue-solver/state.md +12 -0
- package/templates/seed/memory/intelligence/intel-lead/state.md +9 -0
- package/templates/seed/memory/marketing/content-drafter/state.md +12 -0
- package/templates/seed/memory/operations/ops-lead/state.md +12 -0
- package/templates/seed/memory/product/lead/state.md +14 -0
- package/templates/seed/memory/research/lead/state.md +14 -0
- package/templates/seed/skills/gh/SKILL.md +57 -0
- package/templates/seed/skills/squads-cli/SKILL.md +84 -0
- package/templates/seed/squads/company/SQUAD.md +51 -0
- package/templates/seed/squads/company/company-critic.md +49 -0
- package/templates/seed/squads/company/company-eval.md +49 -0
- package/templates/seed/squads/company/event-dispatcher.md +43 -0
- package/templates/seed/squads/company/goal-tracker.md +43 -0
- package/templates/seed/squads/company/manager.md +54 -0
- package/templates/seed/squads/engineering/SQUAD.md +48 -0
- package/templates/seed/squads/engineering/code-reviewer.md +57 -0
- package/templates/seed/squads/engineering/issue-solver.md +58 -0
- package/templates/seed/squads/engineering/test-writer.md +50 -0
- package/templates/seed/squads/intelligence/SQUAD.md +38 -0
- package/templates/seed/squads/intelligence/intel-critic.md +36 -0
- package/templates/seed/squads/intelligence/intel-eval.md +31 -0
- package/templates/seed/squads/intelligence/intel-lead.md +71 -0
- package/templates/seed/squads/marketing/SQUAD.md +47 -0
- package/templates/seed/squads/marketing/content-drafter.md +71 -0
- package/templates/seed/squads/marketing/growth-analyst.md +49 -0
- package/templates/seed/squads/marketing/social-poster.md +44 -0
- package/templates/seed/squads/operations/SQUAD.md +45 -0
- package/templates/seed/squads/operations/finance-tracker.md +47 -0
- package/templates/seed/squads/operations/goal-tracker.md +48 -0
- package/templates/seed/squads/operations/ops-lead.md +58 -0
- package/templates/seed/squads/product/SQUAD.md +41 -0
- package/templates/seed/squads/product/lead.md +56 -0
- package/templates/seed/squads/product/scanner.md +50 -0
- package/templates/seed/squads/product/worker.md +55 -0
- package/templates/seed/squads/research/SQUAD.md +38 -0
- package/templates/seed/squads/research/analyst.md +50 -0
- package/templates/seed/squads/research/lead.md +52 -0
- package/templates/seed/squads/research/synthesizer.md +59 -0
- package/templates/skills/squads-learn/SKILL.md +86 -0
- package/templates/skills/squads-workflow/instruction.md +70 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/plan.ts","../src/lib/costs.ts"],"sourcesContent":["/**\n * Anthropic plan detection\n * Determines whether the user is on a Max (flat-fee) or Usage (pay-per-token) plan.\n */\n\n/**\n * Anthropic plan types:\n * - 'max': Flat fee subscription ($200/mo), no overage - only rate limits matter\n * - 'usage': Pay-per-token, budget tracking matters\n * - 'unknown': Not configured yet\n */\nexport type PlanType = 'max' | 'usage' | 'unknown';\n\n/**\n * Plan detection result with confidence and reason\n */\nexport interface PlanDetection {\n plan: PlanType;\n confidence: 'explicit' | 'inferred';\n reason: string;\n}\n\n/**\n * Detect the Anthropic plan type using multiple signals:\n *\n * Priority order:\n * 1. Explicit SQUADS_PLAN_TYPE env var (highest confidence)\n * 2. ANTHROPIC_BUDGET_DAILY set → usage plan (user cares about budget)\n * 3. Tier 4 + no budget → likely Max plan (heavy user)\n * 4. Low tier (1-2) → usage plan (new user, pay-as-you-go)\n * 5. Default: max (assumes professional use)\n */\nexport function detectPlan(): PlanDetection {\n // 1. Explicit configuration (highest priority)\n const explicitPlan = process.env.SQUADS_PLAN_TYPE?.toLowerCase();\n if (explicitPlan === 'usage') {\n return { plan: 'usage', confidence: 'explicit', reason: 'SQUADS_PLAN_TYPE=usage' };\n }\n if (explicitPlan === 'max') {\n return { plan: 'max', confidence: 'explicit', reason: 'SQUADS_PLAN_TYPE=max' };\n }\n\n // 2. Budget explicitly set → user cares about costs → usage plan\n const budgetSet = process.env.ANTHROPIC_BUDGET_DAILY || process.env.SQUADS_DAILY_BUDGET;\n if (budgetSet) {\n return { plan: 'usage', confidence: 'inferred', reason: `Budget set ($${budgetSet}/day)` };\n }\n\n // 3. Check tier - Tier 4 usually indicates Max plan user\n const tier = parseInt(process.env.ANTHROPIC_TIER || '0', 10);\n if (tier >= 4) {\n return { plan: 'max', confidence: 'inferred', reason: `Tier ${tier} (high usage)` };\n }\n\n // 4. Low tier (1-2) → likely new user on usage plan\n if (tier >= 1 && tier <= 2) {\n return { plan: 'usage', confidence: 'inferred', reason: `Tier ${tier} (new user)` };\n }\n\n // 5. No API key = OAuth (Claude Code subscription) → treat as Max plan\n // Users authenticated via OAuth have a flat subscription and don't need cost tracking.\n if (!process.env.ANTHROPIC_API_KEY) {\n return { plan: 'max', confidence: 'inferred', reason: 'OAuth (Claude Code subscription)' };\n }\n\n // 6. API key set but no other signals → usage plan (pay-per-token)\n return { plan: 'usage', confidence: 'inferred', reason: 'API key (pay-per-token)' };\n}\n\n/**\n * Get the current Anthropic plan type\n * Use detectPlan() for full details including confidence\n */\nexport function getPlanType(): PlanType {\n return detectPlan().plan;\n}\n\n/**\n * Check if we're on a flat-fee plan where budget doesn't matter\n */\nexport function isMaxPlan(): boolean {\n return getPlanType() === 'max';\n}\n\n/**\n * Get human-readable plan description for dashboard display\n */\nexport function getPlanDescription(): string {\n const detection = detectPlan();\n const planName = detection.plan === 'max'\n ? 'Max ($200 flat)'\n : detection.plan === 'usage'\n ? 'Usage (pay-per-token)'\n : 'Unknown';\n const confidence = detection.confidence === 'explicit' ? '' : ` [${detection.reason}]`;\n return `${planName}${confidence}`;\n}\n","/**\n * Cost tracking via API or Langfuse\n * Primary: Squads API → PostgreSQL\n * Fallback: Langfuse API (if API unavailable)\n */\n\nimport {\n ProviderName,\n ProviderDetection,\n detectProviderFromModel,\n detectProvidersFromEnv,\n calcCost as calcProviderCost,\n getProviderDisplayName,\n} from './providers.js';\nimport { getEnv } from './env-config.js';\n\n// Re-export provider types for convenience\nexport { ProviderName, ProviderDetection, detectProviderFromModel, detectProvidersFromEnv, getProviderDisplayName };\n\n// Re-export plan detection from dedicated module\nexport { PlanType, PlanDetection, detectPlan, getPlanType, isMaxPlan, getPlanDescription } from './plan.js';\n\ninterface SquadCosts {\n squad: string;\n calls: number;\n inputTokens: number;\n outputTokens: number;\n cachedTokens: number;\n cost: number;\n models: Record<string, number>;\n}\n\nexport interface ProviderCosts {\n provider: ProviderName;\n displayName: string;\n calls: number;\n inputTokens: number;\n outputTokens: number;\n cost: number;\n plan?: string;\n confidence?: 'explicit' | 'inferred';\n reason?: string;\n}\n\nexport interface CostSummary {\n totalCost: number;\n dailyBudget: number;\n usedPercent: number;\n idleBudget: number;\n totalCalls: number;\n dailyCallLimit: number;\n callsPercent: number;\n totalCachedTokens: number;\n totalInputTokens: number;\n cacheHitRate: number;\n bySquad: SquadCosts[];\n byProvider: ProviderCosts[];\n source: 'postgres' | 'langfuse' | 'none';\n}\n\n// Legacy MODEL_PRICING kept for backward compatibility\n// New code should use getModelPricing() from providers.ts\nconst _MODEL_PRICING: Record<string, { input: number; output: number }> = {\n 'claude-opus-4-5-20251101': { input: 15.0, output: 75.0 },\n 'claude-sonnet-4-20250514': { input: 3.0, output: 15.0 },\n 'claude-haiku-4-5-20251001': { input: 0.80, output: 4.0 },\n 'claude-3-5-sonnet-20241022': { input: 3.0, output: 15.0 },\n 'claude-3-5-haiku-20241022': { input: 0.80, output: 4.0 },\n default: { input: 3.0, output: 15.0 },\n};\n\nconst DEFAULT_DAILY_BUDGET = 200.0;\nconst DEFAULT_DAILY_CALL_LIMIT = 1000; // Default API call limit per day\nconst BRIDGE_URL = getEnv().bridge_url;\nconst FETCH_TIMEOUT_MS = 2000; // 2 second timeout for all fetch calls\n\n/**\n * Fetch with timeout to prevent hanging when services are down\n */\nasync function fetchWithTimeout(url: string, options: RequestInit = {}, timeoutMs = FETCH_TIMEOUT_MS): Promise<Response> {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeoutMs);\n\n try {\n const response = await fetch(url, { ...options, signal: controller.signal });\n clearTimeout(timeoutId);\n return response;\n } catch (error) {\n clearTimeout(timeoutId);\n throw error;\n }\n}\n\nfunction calcCost(model: string, inputTokens: number, outputTokens: number): number {\n // Use provider-aware pricing\n const provider = detectProviderFromModel(model);\n return calcProviderCost(provider, model, inputTokens, outputTokens);\n}\n\n/**\n * Fetch cost summary from Squads Bridge (postgres)\n */\nasync function fetchFromBridge(period: 'day' | 'week' | 'month' = 'day'): Promise<CostSummary | null> {\n try {\n const response = await fetchWithTimeout(`${BRIDGE_URL}/api/cost/summary?period=${period}`, {\n headers: { 'Content-Type': 'application/json' },\n });\n\n if (!response.ok) {\n return null;\n }\n\n const data = await response.json() as { totals?: { cost_usd?: number }; by_squad?: Record<string, unknown>[] };\n const dailyBudget = parseFloat(process.env.SQUADS_DAILY_BUDGET || '') || DEFAULT_DAILY_BUDGET;\n const totalCost = data.totals?.cost_usd || 0;\n\n const bySquad: SquadCosts[] = (data.by_squad || []).map((s: Record<string, unknown>) => ({\n squad: s.squad as string,\n calls: (s.generations as number) || 0,\n inputTokens: (s.input_tokens as number) || 0,\n outputTokens: (s.output_tokens as number) || 0,\n cachedTokens: (s.cached_tokens as number) || 0,\n cost: (s.cost_usd as number) || 0,\n models: {},\n }));\n\n const totalCalls = bySquad.reduce((sum, s) => sum + s.calls, 0);\n const dailyCallLimit = parseFloat(process.env.SQUADS_DAILY_CALL_LIMIT || '') || DEFAULT_DAILY_CALL_LIMIT;\n const totalCachedTokens = bySquad.reduce((sum, s) => sum + s.cachedTokens, 0);\n const totalInputTokens = bySquad.reduce((sum, s) => sum + s.inputTokens, 0);\n const totalAllInput = totalInputTokens + totalCachedTokens;\n const cacheHitRate = totalAllInput > 0 ? (totalCachedTokens / totalAllInput) * 100 : 0;\n\n // Build provider summary from detected providers in env\n const detectedProviders = detectProvidersFromEnv();\n const byProvider: ProviderCosts[] = detectedProviders.map((p) => ({\n provider: p.provider,\n displayName: getProviderDisplayName(p.provider),\n calls: 0, // Bridge doesn't track by provider yet\n inputTokens: 0,\n outputTokens: 0,\n cost: p.provider === 'anthropic' ? totalCost : 0, // Assume all cost is Anthropic for now\n plan: p.plan,\n confidence: p.confidence,\n reason: p.reason,\n }));\n\n return {\n totalCost,\n dailyBudget,\n usedPercent: (totalCost / dailyBudget) * 100,\n idleBudget: dailyBudget - totalCost,\n totalCalls,\n dailyCallLimit,\n callsPercent: (totalCalls / dailyCallLimit) * 100,\n totalCachedTokens,\n totalInputTokens,\n cacheHitRate,\n bySquad,\n byProvider,\n source: 'postgres',\n };\n } catch {\n return null;\n }\n}\n\n/**\n * Fetch cost summary from Langfuse API (fallback)\n */\nasync function fetchFromLangfuse(limit = 100): Promise<CostSummary | null> {\n const publicKey = process.env.LANGFUSE_PUBLIC_KEY;\n const secretKey = process.env.LANGFUSE_SECRET_KEY;\n const host = process.env.LANGFUSE_HOST || process.env.LANGFUSE_BASE_URL || 'https://us.cloud.langfuse.com';\n\n if (!publicKey || !secretKey) {\n return null;\n }\n\n try {\n const auth = Buffer.from(`${publicKey}:${secretKey}`).toString('base64');\n const url = `${host}/api/public/observations?limit=${limit}`;\n\n const response = await fetchWithTimeout(url, {\n headers: {\n Authorization: `Basic ${auth}`,\n 'Content-Type': 'application/json',\n },\n });\n\n if (!response.ok) {\n return null;\n }\n\n interface LangfuseObs {\n type?: string;\n model?: string;\n metadata?: { squad?: string };\n usage?: { input?: number; output?: number };\n }\n const data = await response.json() as { data?: LangfuseObs[] };\n const observations = data.data || [];\n\n // Group by squad\n const bySquad: Record<string, SquadCosts> = {};\n\n for (const obs of observations) {\n if (obs.type !== 'GENERATION') continue;\n\n const metadata = obs.metadata || {};\n const squad = metadata.squad || 'unknown';\n const model = obs.model || 'unknown';\n const usage = obs.usage || {};\n\n const inputTokens = usage.input || 0;\n const outputTokens = usage.output || 0;\n const cost = calcCost(model, inputTokens, outputTokens);\n\n if (!bySquad[squad]) {\n bySquad[squad] = {\n squad,\n calls: 0,\n inputTokens: 0,\n outputTokens: 0,\n cachedTokens: 0,\n cost: 0,\n models: {},\n };\n }\n\n bySquad[squad].calls += 1;\n bySquad[squad].inputTokens += inputTokens;\n bySquad[squad].outputTokens += outputTokens;\n bySquad[squad].cost += cost;\n bySquad[squad].models[model] = (bySquad[squad].models[model] || 0) + 1;\n }\n\n const squadList = Object.values(bySquad).sort((a, b) => b.cost - a.cost);\n const totalCost = squadList.reduce((sum, s) => sum + s.cost, 0);\n const dailyBudget = parseFloat(process.env.SQUADS_DAILY_BUDGET || '') || DEFAULT_DAILY_BUDGET;\n\n const totalCalls = squadList.reduce((sum, s) => sum + s.calls, 0);\n const dailyCallLimit = parseFloat(process.env.SQUADS_DAILY_CALL_LIMIT || '') || DEFAULT_DAILY_CALL_LIMIT;\n const totalCachedTokens = squadList.reduce((sum, s) => sum + s.cachedTokens, 0);\n const totalInputTokens = squadList.reduce((sum, s) => sum + s.inputTokens, 0);\n const totalAllInput = totalInputTokens + totalCachedTokens;\n const cacheHitRate = totalAllInput > 0 ? (totalCachedTokens / totalAllInput) * 100 : 0;\n\n // Build provider summary - Langfuse can track by model, so group by provider\n const providerMap: Record<string, ProviderCosts> = {};\n for (const obs of observations) {\n if (obs.type !== 'GENERATION') continue;\n const model = obs.model || 'unknown';\n const provider = detectProviderFromModel(model);\n const usage = obs.usage || {};\n const inputTokens = usage.input || 0;\n const outputTokens = usage.output || 0;\n const cost = calcCost(model, inputTokens, outputTokens);\n\n if (!providerMap[provider]) {\n const detection = detectProvidersFromEnv().find((p) => p.provider === provider);\n providerMap[provider] = {\n provider,\n displayName: getProviderDisplayName(provider),\n calls: 0,\n inputTokens: 0,\n outputTokens: 0,\n cost: 0,\n plan: detection?.plan,\n confidence: detection?.confidence,\n reason: detection?.reason,\n };\n }\n providerMap[provider].calls += 1;\n providerMap[provider].inputTokens += inputTokens;\n providerMap[provider].outputTokens += outputTokens;\n providerMap[provider].cost += cost;\n }\n const byProvider = Object.values(providerMap).sort((a, b) => b.cost - a.cost);\n\n return {\n totalCost,\n dailyBudget,\n usedPercent: (totalCost / dailyBudget) * 100,\n idleBudget: dailyBudget - totalCost,\n totalCalls,\n dailyCallLimit,\n callsPercent: (totalCalls / dailyCallLimit) * 100,\n totalCachedTokens,\n totalInputTokens,\n cacheHitRate,\n bySquad: squadList,\n byProvider,\n source: 'langfuse',\n };\n } catch {\n return null;\n }\n}\n\n/**\n * Fetch cost summary - tries postgres first, falls back to Langfuse\n */\nexport async function fetchCostSummary(\n limit = 100,\n period: 'day' | 'week' | 'month' = 'day'\n): Promise<CostSummary | null> {\n // Try postgres (via bridge) first\n const bridgeResult = await fetchFromBridge(period);\n if (bridgeResult) {\n return bridgeResult;\n }\n\n // Fall back to Langfuse\n const langfuseResult = await fetchFromLangfuse(limit);\n if (langfuseResult) {\n return langfuseResult;\n }\n\n // No data source available - still detect providers from env\n const defaultBudget = parseFloat(process.env.SQUADS_DAILY_BUDGET || '') || DEFAULT_DAILY_BUDGET;\n const detectedProviders = detectProvidersFromEnv();\n const byProvider: ProviderCosts[] = detectedProviders.map((p) => ({\n provider: p.provider,\n displayName: getProviderDisplayName(p.provider),\n calls: 0,\n inputTokens: 0,\n outputTokens: 0,\n cost: 0,\n plan: p.plan,\n confidence: p.confidence,\n reason: p.reason,\n }));\n\n return {\n totalCost: 0,\n dailyBudget: defaultBudget,\n usedPercent: 0,\n idleBudget: defaultBudget,\n totalCalls: 0,\n dailyCallLimit: DEFAULT_DAILY_CALL_LIMIT,\n callsPercent: 0,\n totalCachedTokens: 0,\n totalInputTokens: 0,\n cacheHitRate: 0,\n bySquad: [],\n byProvider,\n source: 'none',\n };\n}\n\nexport function formatCostBar(usedPercent: number, width = 20): string {\n const filled = Math.min(Math.round((usedPercent / 100) * width), width);\n const empty = width - filled;\n return '█'.repeat(filled) + '░'.repeat(empty);\n}\n\n/**\n * Bridge stats from /stats endpoint (Redis real-time or Postgres fallback)\n */\nexport interface BridgeStats {\n status: string;\n source: 'redis' | 'postgres' | 'none';\n today: {\n generations: number;\n inputTokens: number;\n outputTokens: number;\n costUsd: number;\n };\n week?: {\n generations: number;\n inputTokens: number;\n outputTokens: number;\n costUsd: number;\n byModel?: Array<{\n model: string;\n generations: number;\n costUsd: number;\n }>;\n };\n budget: {\n daily: number;\n used: number;\n remaining: number;\n usedPct: number;\n };\n bySquad: Array<{\n squad: string;\n costUsd: number;\n generations: number;\n }>;\n byModel?: Array<{\n model: string;\n generations: number;\n costUsd: number;\n }>;\n health: {\n postgres: string;\n redis: string;\n langfuse: string;\n };\n}\n\n/**\n * Fetch real-time stats from Squads Bridge\n * All HTTP calls are made in parallel for optimal performance\n */\nexport async function fetchBridgeStats(): Promise<BridgeStats | null> {\n try {\n interface StatsData {\n status?: string;\n source?: string;\n today?: {\n generations?: number;\n input_tokens?: number;\n output_tokens?: number;\n cost_usd?: number;\n };\n budget?: {\n daily?: number;\n used?: number;\n remaining?: number;\n used_pct?: number;\n };\n by_squad?: Array<{\n squad?: string;\n cost_usd?: number;\n generations?: number;\n }>;\n }\n\n interface HealthData {\n postgres?: string;\n redis?: string;\n langfuse?: string;\n }\n\n interface CostData {\n totals?: {\n generations?: number;\n input_tokens?: number;\n output_tokens?: number;\n cost_usd?: number;\n };\n by_model?: Array<{\n model?: string;\n generations?: number;\n cost_usd?: number;\n }>;\n }\n\n // Fetch ALL endpoints in parallel (4 requests -> 1 round trip)\n const [statsResponse, healthResponse, costResponse, weekResponse] = await Promise.all([\n fetchWithTimeout(`${BRIDGE_URL}/stats`, {\n headers: { 'Content-Type': 'application/json' },\n }),\n fetchWithTimeout(`${BRIDGE_URL}/health`, {\n headers: { 'Content-Type': 'application/json' },\n }),\n fetchWithTimeout(`${BRIDGE_URL}/api/cost/summary?period=day`, {\n headers: { 'Content-Type': 'application/json' },\n }),\n fetchWithTimeout(`${BRIDGE_URL}/api/cost/summary?period=week`, {\n headers: { 'Content-Type': 'application/json' },\n }),\n ]);\n\n if (!statsResponse.ok) {\n return null;\n }\n\n // Parse all responses in parallel\n const [stats, health, costData, weekData] = await Promise.all([\n statsResponse.json() as Promise<StatsData>,\n healthResponse.ok ? healthResponse.json() as Promise<HealthData> : Promise.resolve({} as HealthData),\n costResponse.ok ? costResponse.json() as Promise<CostData> : Promise.resolve({} as CostData),\n weekResponse.ok ? weekResponse.json() as Promise<CostData> : Promise.resolve({} as CostData),\n ]);\n\n return {\n status: stats.status || 'unknown',\n source: (stats.source as 'redis' | 'postgres' | 'none') || 'none',\n today: {\n generations: stats.today?.generations || 0,\n inputTokens: stats.today?.input_tokens || 0,\n outputTokens: stats.today?.output_tokens || 0,\n costUsd: stats.today?.cost_usd || 0,\n },\n week: weekData.totals ? {\n generations: weekData.totals.generations || 0,\n inputTokens: weekData.totals.input_tokens || 0,\n outputTokens: weekData.totals.output_tokens || 0,\n costUsd: weekData.totals.cost_usd || 0,\n byModel: (weekData.by_model || []).map(m => ({\n model: m.model || 'unknown',\n generations: m.generations || 0,\n costUsd: m.cost_usd || 0,\n })),\n } : undefined,\n budget: {\n daily: stats.budget?.daily || DEFAULT_DAILY_BUDGET,\n used: stats.budget?.used || 0,\n remaining: stats.budget?.remaining || DEFAULT_DAILY_BUDGET,\n usedPct: stats.budget?.used_pct || 0,\n },\n bySquad: (stats.by_squad || []).map(s => ({\n squad: s.squad || 'unknown',\n costUsd: s.cost_usd || 0,\n generations: s.generations || 0,\n })),\n byModel: (costData.by_model || []).map(m => ({\n model: m.model || 'unknown',\n generations: m.generations || 0,\n costUsd: m.cost_usd || 0,\n })),\n health: {\n postgres: health.postgres || 'unknown',\n redis: health.redis || 'unknown',\n langfuse: health.langfuse || 'unknown',\n },\n };\n } catch {\n return null;\n }\n}\n\n/**\n * Monthly quota/autonomy data from bridge\n */\nexport interface QuotaInfo {\n monthlyUsed: number;\n monthlyQuota: number;\n quotaPct: number;\n autonomyScore: number;\n confidenceLevel: string;\n learningCount: number;\n}\n\nexport async function fetchQuotaInfo(): Promise<QuotaInfo | null> {\n try {\n const response = await fetchWithTimeout(`${BRIDGE_URL}/api/autonomy/score`);\n if (!response.ok) return null;\n\n const data = await response.json() as {\n overall_score: number;\n confidence_level: string;\n execution_stats: {\n monthly_used: number;\n monthly_quota: number;\n quota_pct: number;\n learning_count: number;\n };\n };\n\n return {\n monthlyUsed: data.execution_stats.monthly_used,\n monthlyQuota: data.execution_stats.monthly_quota,\n quotaPct: data.execution_stats.quota_pct,\n autonomyScore: data.overall_score,\n confidenceLevel: data.confidence_level,\n learningCount: data.execution_stats.learning_count,\n };\n } catch {\n return null;\n }\n}\n\n/**\n * Rate limit data from Anthropic API headers\n */\nexport interface RateLimitInfo {\n model: string;\n requestsLimit: number;\n requestsRemaining: number;\n requestsReset?: string;\n tokensLimit: number;\n tokensRemaining: number;\n tokensReset?: string;\n inputTokensLimit?: number;\n inputTokensRemaining?: number;\n outputTokensLimit?: number;\n outputTokensRemaining?: number;\n capturedAt: string;\n}\n\nexport interface RateLimits {\n limits: Record<string, RateLimitInfo>;\n source: 'proxy' | 'none';\n}\n\n/**\n * Fetch real rate limits from the Anthropic proxy (via bridge)\n */\nexport async function fetchRateLimits(): Promise<RateLimits> {\n try {\n const response = await fetchWithTimeout(`${BRIDGE_URL}/api/rate-limits`, {\n headers: { 'Content-Type': 'application/json' },\n });\n\n if (!response.ok) {\n return { limits: {}, source: 'none' };\n }\n\n interface RateLimitResponse {\n rate_limits?: Record<string, {\n model?: string;\n requests_limit?: number;\n requests_remaining?: number;\n requests_reset?: string;\n tokens_limit?: number;\n tokens_remaining?: number;\n tokens_reset?: string;\n input_tokens_limit?: number;\n input_tokens_remaining?: number;\n output_tokens_limit?: number;\n output_tokens_remaining?: number;\n captured_at?: string;\n }>;\n }\n\n const data = await response.json() as RateLimitResponse;\n const rateLimits = data.rate_limits || {};\n\n const limits: Record<string, RateLimitInfo> = {};\n for (const [key, value] of Object.entries(rateLimits)) {\n limits[key] = {\n model: value.model || key,\n requestsLimit: value.requests_limit || 0,\n requestsRemaining: value.requests_remaining || 0,\n requestsReset: value.requests_reset,\n tokensLimit: value.tokens_limit || 0,\n tokensRemaining: value.tokens_remaining || 0,\n tokensReset: value.tokens_reset,\n inputTokensLimit: value.input_tokens_limit,\n inputTokensRemaining: value.input_tokens_remaining,\n outputTokensLimit: value.output_tokens_limit,\n outputTokensRemaining: value.output_tokens_remaining,\n capturedAt: value.captured_at || new Date().toISOString(),\n };\n }\n\n return { limits, source: 'proxy' };\n } catch {\n return { limits: {}, source: 'none' };\n }\n}\n\n/**\n * Task and quality insights\n */\nexport interface TaskMetrics {\n squad: string;\n tasksTotal: number;\n tasksCompleted: number;\n tasksFailed: number;\n successRate: number;\n totalRetries: number;\n tasksWithRetries: number;\n avgRetries: number;\n avgDurationMs: number;\n avgTokens: number;\n avgCost: number;\n avgContextPct: number;\n maxContextTokens: number;\n}\n\nexport interface ToolMetrics {\n toolName: string;\n usageCount: number;\n successRate: number;\n avgDurationMs: number;\n}\n\nexport interface QualityMetrics {\n squad: string;\n feedbackCount: number;\n avgQuality: number;\n helpfulPct: number;\n fixRequiredPct: number;\n}\n\nexport interface Insights {\n period: string;\n days: number;\n taskMetrics: TaskMetrics[];\n qualityMetrics: QualityMetrics[];\n topTools: ToolMetrics[];\n toolFailureRate: number;\n source: 'bridge' | 'none';\n}\n\n/**\n * Fetch insights from the bridge\n */\nexport async function fetchInsights(period: 'day' | 'week' | 'month' = 'week'): Promise<Insights> {\n try {\n const response = await fetchWithTimeout(`${BRIDGE_URL}/api/insights?period=${period}`, {\n headers: { 'Content-Type': 'application/json' },\n });\n\n if (!response.ok) {\n return {\n period,\n days: period === 'day' ? 1 : period === 'week' ? 7 : 30,\n taskMetrics: [],\n qualityMetrics: [],\n topTools: [],\n toolFailureRate: 0,\n source: 'none',\n };\n }\n\n interface InsightsResponse {\n period: string;\n days: number;\n task_metrics?: Array<{\n squad: string;\n tasks_total: number;\n tasks_completed: number;\n tasks_failed: number;\n success_rate: number;\n total_retries: number;\n tasks_with_retries: number;\n avg_retries: number;\n avg_duration_ms: number;\n avg_tokens: number;\n avg_cost: number;\n avg_context_pct: number;\n max_context_tokens: number;\n }>;\n quality_metrics?: Array<{\n squad: string;\n feedback_count: number;\n avg_quality: number;\n helpful_pct: number;\n fix_required_pct: number;\n }>;\n top_tools?: Array<{\n tool_name: string;\n usage_count: number;\n success_rate: number;\n avg_duration_ms: number;\n }>;\n tool_failure_rate?: number;\n }\n\n const data = await response.json() as InsightsResponse;\n\n return {\n period: data.period || period,\n days: data.days || 7,\n taskMetrics: (data.task_metrics || []).map(t => ({\n squad: t.squad,\n tasksTotal: t.tasks_total || 0,\n tasksCompleted: t.tasks_completed || 0,\n tasksFailed: t.tasks_failed || 0,\n successRate: t.success_rate || 0,\n totalRetries: t.total_retries || 0,\n tasksWithRetries: t.tasks_with_retries || 0,\n avgRetries: t.avg_retries || 0,\n avgDurationMs: t.avg_duration_ms || 0,\n avgTokens: t.avg_tokens || 0,\n avgCost: t.avg_cost || 0,\n avgContextPct: t.avg_context_pct || 0,\n maxContextTokens: t.max_context_tokens || 0,\n })),\n qualityMetrics: (data.quality_metrics || []).map(q => ({\n squad: q.squad,\n feedbackCount: q.feedback_count || 0,\n avgQuality: q.avg_quality || 0,\n helpfulPct: q.helpful_pct || 0,\n fixRequiredPct: q.fix_required_pct || 0,\n })),\n topTools: (data.top_tools || []).map(t => ({\n toolName: t.tool_name,\n usageCount: t.usage_count || 0,\n successRate: t.success_rate || 0,\n avgDurationMs: t.avg_duration_ms || 0,\n })),\n toolFailureRate: data.tool_failure_rate || 0,\n source: 'bridge',\n };\n } catch {\n return {\n period,\n days: period === 'day' ? 1 : period === 'week' ? 7 : 30,\n taskMetrics: [],\n qualityMetrics: [],\n topTools: [],\n toolFailureRate: 0,\n source: 'none',\n };\n }\n}\n\n// === NPM Stats for Acquisition Tracking ===\n\nexport interface NpmStats {\n package: string;\n downloads: {\n lastDay: number;\n lastWeek: number;\n lastMonth: number;\n };\n weekOverWeek: number; // percentage change\n}\n\nexport async function fetchNpmStats(packageName: string = process.env.SQUADS_NPM_PACKAGE || 'squads-cli'): Promise<NpmStats | null> {\n try {\n const [dayRes, weekRes, monthRes] = await Promise.all([\n fetchWithTimeout(`https://api.npmjs.org/downloads/point/last-day/${packageName}`, {}, 3000),\n fetchWithTimeout(`https://api.npmjs.org/downloads/point/last-week/${packageName}`, {}, 3000),\n fetchWithTimeout(`https://api.npmjs.org/downloads/point/last-month/${packageName}`, {}, 3000),\n ]);\n\n if (!dayRes.ok || !weekRes.ok || !monthRes.ok) return null;\n\n const [dayData, weekData, monthData] = await Promise.all([\n dayRes.json() as Promise<{ downloads: number }>,\n weekRes.json() as Promise<{ downloads: number }>,\n monthRes.json() as Promise<{ downloads: number }>,\n ]);\n\n // Calculate week-over-week growth (rough estimate: this week vs avg of month)\n const avgWeeklyFromMonth = monthData.downloads / 4;\n const weekOverWeek = avgWeeklyFromMonth > 0\n ? Math.round(((weekData.downloads - avgWeeklyFromMonth) / avgWeeklyFromMonth) * 100)\n : 0;\n\n return {\n package: packageName,\n downloads: {\n lastDay: dayData.downloads,\n lastWeek: weekData.downloads,\n lastMonth: monthData.downloads,\n },\n weekOverWeek,\n };\n } catch {\n return null;\n }\n}\n\n/**\n * Claude Code subscription capacity data\n * Read from ~/.claude/stats-cache.json\n */\nexport interface ClaudeCodeCapacity {\n // Weekly capacity (from stats-cache.json token data)\n weeklyTokensUsed: number;\n weeklyTokensLimit: number;\n weeklyCapacityPct: number;\n // Session capacity (estimated from current session)\n sessionTokensUsed: number;\n sessionTokensLimit: number;\n sessionCapacityPct: number;\n // By model breakdown\n opusTokensUsed: number;\n sonnetTokensUsed: number;\n haikuTokensUsed: number;\n // Reset info\n weeklyResetDate: string;\n sessionResetTime: string;\n}\n\n// Claude Code subscription capacity estimation\n// The actual limits use \"usage units\" not raw tokens\n// Opus costs ~5x more usage units than Sonnet/Haiku\n// Based on user reports: Max 20x gives ~24-40 hours Opus/week\n// Conservative estimate: ~4M \"weighted tokens\" per week for Max 20x\nconst OPUS_WEIGHT = 5; // Opus costs ~5x more against limit\nconst SONNET_WEIGHT = 1; // Sonnet is the baseline\nconst HAIKU_WEIGHT = 0.25; // Haiku is cheaper\n\n// Effective weekly limit in weighted tokens (conservative for Max 20x)\n// Adjust via SQUADS_WEEKLY_LIMIT env var if your plan differs\nconst DEFAULT_WEEKLY_WEIGHTED_LIMIT = 4_000_000;\nconst MAX5_SESSION_TOKEN_LIMIT = 2_000_000; // ~2M tokens/session (raw)\n\n/**\n * Fetch Claude Code capacity from stats-cache.json\n */\nexport async function fetchClaudeCodeCapacity(): Promise<ClaudeCodeCapacity | null> {\n const { readFile } = await import('fs/promises');\n const { homedir } = await import('os');\n const { join } = await import('path');\n\n try {\n const cacheFile = join(homedir(), '.claude', 'stats-cache.json');\n const content = await readFile(cacheFile, 'utf-8');\n const data = JSON.parse(content) as {\n dailyModelTokens?: Array<{\n date: string;\n tokensByModel: Record<string, number>;\n }>;\n modelUsage?: Record<string, {\n inputTokens?: number;\n outputTokens?: number;\n cacheReadInputTokens?: number;\n cacheCreationInputTokens?: number;\n }>;\n };\n\n // Calculate weekly token usage (last 7 days)\n const now = new Date();\n const weekAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);\n const weekStart = weekAgo.toISOString().split('T')[0];\n\n let weeklyOpus = 0;\n\n if (data.dailyModelTokens) {\n for (const day of data.dailyModelTokens) {\n if (day.date >= weekStart) {\n for (const [model, tokens] of Object.entries(day.tokensByModel)) {\n if (model.includes('opus')) {\n weeklyOpus += tokens;\n }\n }\n }\n }\n }\n\n // Calculate weighted usage (Opus costs 5x more against limit)\n // This better reflects actual subscription capacity consumption\n const weeklyLimit = parseInt(process.env.SQUADS_WEEKLY_LIMIT || '', 10) || DEFAULT_WEEKLY_WEIGHTED_LIMIT;\n\n // Calculate session usage (today's tokens as proxy)\n const today = now.toISOString().split('T')[0];\n let sessionTokens = 0;\n if (data.dailyModelTokens) {\n const todayData = data.dailyModelTokens.find(d => d.date === today);\n if (todayData) {\n sessionTokens = Object.values(todayData.tokensByModel).reduce((a, b) => a + b, 0);\n }\n }\n\n // Calculate weekly reset (next Sunday 9:59pm in local timezone)\n const daysUntilSunday = (7 - now.getDay()) % 7 || 7;\n const nextSunday = new Date(now);\n nextSunday.setDate(now.getDate() + daysUntilSunday);\n nextSunday.setHours(21, 59, 0, 0);\n\n // Session reset (end of current session - approximate as end of day)\n const sessionReset = new Date(now);\n sessionReset.setHours(18, 59, 0, 0);\n if (sessionReset < now) {\n sessionReset.setDate(sessionReset.getDate() + 1);\n }\n\n // Calculate Sonnet/Haiku usage\n let sonnetTokens = 0;\n let haikuTokens = 0;\n if (data.dailyModelTokens) {\n for (const day of data.dailyModelTokens) {\n if (day.date >= weekStart) {\n for (const [model, tokens] of Object.entries(day.tokensByModel)) {\n if (model.includes('sonnet')) {\n sonnetTokens += tokens;\n } else if (model.includes('haiku')) {\n haikuTokens += tokens;\n }\n }\n }\n }\n }\n\n // Calculate weighted weekly usage\n const weeklyWeighted = Math.round(\n (weeklyOpus * OPUS_WEIGHT) +\n (sonnetTokens * SONNET_WEIGHT) +\n (haikuTokens * HAIKU_WEIGHT)\n );\n\n return {\n weeklyTokensUsed: weeklyWeighted, // Now weighted, not raw\n weeklyTokensLimit: weeklyLimit,\n weeklyCapacityPct: Math.round((weeklyWeighted / weeklyLimit) * 100),\n sessionTokensUsed: sessionTokens,\n sessionTokensLimit: MAX5_SESSION_TOKEN_LIMIT,\n sessionCapacityPct: Math.round((sessionTokens / MAX5_SESSION_TOKEN_LIMIT) * 100),\n opusTokensUsed: weeklyOpus,\n sonnetTokensUsed: sonnetTokens,\n haikuTokensUsed: haikuTokens,\n weeklyResetDate: nextSunday.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }),\n sessionResetTime: sessionReset.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' }),\n };\n } catch {\n return null;\n }\n}\n\n// === ROI and Cost Projection Types ===\n\n/**\n * ROI metrics for measuring value delivered vs cost\n */\nexport interface ROIMetrics {\n totalCostUsd: number;\n costPerGoal: number;\n costPerCommit: number;\n costPerPR: number;\n estimatedValueUsd: number;\n roiMultiplier: number;\n dailyProjectedCost: number;\n weeklyProjectedCost: number;\n monthlyProjectedCost: number;\n hoursTracked: number;\n costPerHour: number;\n}\n\n/**\n * Before/after comparison for a period\n */\nexport interface BeforeAfterMetrics {\n periodStart: string;\n periodEnd: string;\n baselineCostUsd: number;\n baselineGoals: number;\n baselineCommits: number;\n baselinePRs: number;\n baselineTokens: number;\n currentCostUsd: number;\n currentGoals: number;\n currentCommits: number;\n currentPRs: number;\n currentTokens: number;\n costDelta: number;\n goalsDelta: number;\n commitsDelta: number;\n prsDelta: number;\n tokensDelta: number;\n costPerGoalBefore: number;\n costPerGoalAfter: number;\n efficiencyImprovement: number;\n}\n\n/**\n * Squad-level cost projection\n */\nexport interface SquadCostProjection {\n squad: string;\n currentDailyCost: number;\n projectedDailyCost: number;\n projectedWeeklyCost: number;\n projectedMonthlyCost: number;\n costTrend: 'increasing' | 'stable' | 'decreasing';\n trendPct: number;\n}\n\n/**\n * Calculate ROI metrics from cost and activity data\n */\nexport function calculateROIMetrics(\n costs: CostSummary | null,\n goalsCompleted: number,\n commits: number,\n prsMerged: number,\n hoursTracked: number = 0\n): ROIMetrics {\n const totalCost = costs?.totalCost || 0;\n const costPerGoal = goalsCompleted > 0 ? totalCost / goalsCompleted : 0;\n const costPerCommit = commits > 0 ? totalCost / commits : 0;\n const costPerPR = prsMerged > 0 ? totalCost / prsMerged : 0;\n\n const GOAL_VALUE = parseFloat(process.env.SQUADS_GOAL_VALUE || '100');\n const PR_VALUE = parseFloat(process.env.SQUADS_PR_VALUE || '200');\n const COMMIT_VALUE = parseFloat(process.env.SQUADS_COMMIT_VALUE || '25');\n\n const estimatedValue = (goalsCompleted * GOAL_VALUE) + (prsMerged * PR_VALUE) + (commits * COMMIT_VALUE);\n const roiMultiplier = totalCost > 0 ? estimatedValue / totalCost : 0;\n\n const now = new Date();\n const hoursElapsed = hoursTracked > 0 ? hoursTracked : Math.max(now.getHours() + now.getMinutes() / 60, 1);\n const costPerHour = totalCost / hoursElapsed;\n\n return {\n totalCostUsd: totalCost,\n costPerGoal,\n costPerCommit,\n costPerPR,\n estimatedValueUsd: estimatedValue,\n roiMultiplier,\n dailyProjectedCost: costPerHour * 24,\n weeklyProjectedCost: costPerHour * 24 * 7,\n monthlyProjectedCost: costPerHour * 24 * 30,\n hoursTracked: hoursElapsed,\n costPerHour,\n };\n}\n\n/**\n * Calculate cost projections per squad\n */\nexport function calculateSquadCostProjections(\n bridgeStats: BridgeStats | null,\n _history: Array<{ squad: string; costUsd: number }[]> | null\n): SquadCostProjection[] {\n if (!bridgeStats || bridgeStats.bySquad.length === 0) {\n return [];\n }\n\n const now = new Date();\n const hoursElapsed = Math.max(now.getHours() + now.getMinutes() / 60, 1);\n\n return bridgeStats.bySquad.map(squad => {\n const hourlyRate = squad.costUsd / hoursElapsed;\n return {\n squad: squad.squad,\n currentDailyCost: squad.costUsd,\n projectedDailyCost: hourlyRate * 24,\n projectedWeeklyCost: hourlyRate * 24 * 7,\n projectedMonthlyCost: hourlyRate * 24 * 30,\n costTrend: 'stable' as const,\n trendPct: 0,\n };\n });\n}\n"],"mappings":"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","names":["calcCost"]}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/lib/auth.ts
|
|
4
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, unlinkSync } from "fs";
|
|
5
|
+
import { join } from "path";
|
|
6
|
+
import { homedir } from "os";
|
|
7
|
+
import http from "http";
|
|
8
|
+
var PERSONAL_DOMAINS = [
|
|
9
|
+
"gmail.com",
|
|
10
|
+
"googlemail.com",
|
|
11
|
+
"yahoo.com",
|
|
12
|
+
"yahoo.co.uk",
|
|
13
|
+
"yahoo.fr",
|
|
14
|
+
"hotmail.com",
|
|
15
|
+
"outlook.com",
|
|
16
|
+
"live.com",
|
|
17
|
+
"msn.com",
|
|
18
|
+
"icloud.com",
|
|
19
|
+
"me.com",
|
|
20
|
+
"mac.com",
|
|
21
|
+
"aol.com",
|
|
22
|
+
"protonmail.com",
|
|
23
|
+
"proton.me",
|
|
24
|
+
"zoho.com",
|
|
25
|
+
"mail.com",
|
|
26
|
+
"yandex.com",
|
|
27
|
+
"yandex.ru",
|
|
28
|
+
"gmx.com",
|
|
29
|
+
"gmx.de",
|
|
30
|
+
"fastmail.com",
|
|
31
|
+
"tutanota.com",
|
|
32
|
+
"hey.com"
|
|
33
|
+
];
|
|
34
|
+
var AUTH_DIR = join(homedir(), ".squads-cli");
|
|
35
|
+
var AUTH_PATH = join(AUTH_DIR, "auth.json");
|
|
36
|
+
function isPersonalEmail(email) {
|
|
37
|
+
const domain = email.split("@")[1]?.toLowerCase();
|
|
38
|
+
return PERSONAL_DOMAINS.includes(domain);
|
|
39
|
+
}
|
|
40
|
+
function getEmailDomain(email) {
|
|
41
|
+
return email.split("@")[1]?.toLowerCase() || "";
|
|
42
|
+
}
|
|
43
|
+
function saveSession(session) {
|
|
44
|
+
if (!existsSync(AUTH_DIR)) {
|
|
45
|
+
mkdirSync(AUTH_DIR, { recursive: true, mode: 448 });
|
|
46
|
+
}
|
|
47
|
+
writeFileSync(AUTH_PATH, JSON.stringify(session, null, 2), { mode: 384 });
|
|
48
|
+
}
|
|
49
|
+
function loadSession() {
|
|
50
|
+
if (!existsSync(AUTH_PATH)) return null;
|
|
51
|
+
try {
|
|
52
|
+
return JSON.parse(readFileSync(AUTH_PATH, "utf-8"));
|
|
53
|
+
} catch {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function clearSession() {
|
|
58
|
+
try {
|
|
59
|
+
if (existsSync(AUTH_PATH)) {
|
|
60
|
+
unlinkSync(AUTH_PATH);
|
|
61
|
+
}
|
|
62
|
+
} catch {
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function isLoggedIn() {
|
|
66
|
+
const session = loadSession();
|
|
67
|
+
return session !== null && session.status === "active";
|
|
68
|
+
}
|
|
69
|
+
async function verifyToken(apiUrl, token) {
|
|
70
|
+
const controller = new AbortController();
|
|
71
|
+
const timeout = setTimeout(() => controller.abort(), 5e3);
|
|
72
|
+
try {
|
|
73
|
+
const response = await fetch(`${apiUrl}/auth/cli/verify`, {
|
|
74
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
75
|
+
signal: controller.signal
|
|
76
|
+
});
|
|
77
|
+
clearTimeout(timeout);
|
|
78
|
+
if (!response.ok) return null;
|
|
79
|
+
const data = await response.json();
|
|
80
|
+
return {
|
|
81
|
+
email: String(data.email ?? ""),
|
|
82
|
+
tenantId: Number(data.tenant_id ?? 0),
|
|
83
|
+
tenantSlug: String(data.tenant_slug ?? ""),
|
|
84
|
+
tenantName: String(data.tenant_name ?? ""),
|
|
85
|
+
status: String(data.status ?? "")
|
|
86
|
+
};
|
|
87
|
+
} catch {
|
|
88
|
+
clearTimeout(timeout);
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function escapeHtml(str) {
|
|
93
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
94
|
+
}
|
|
95
|
+
function startAuthCallbackServer(port = 54321) {
|
|
96
|
+
return new Promise((resolve, reject) => {
|
|
97
|
+
const server = http.createServer((req, res) => {
|
|
98
|
+
const url = new URL(req.url || "", `http://localhost:${port}`);
|
|
99
|
+
if (url.pathname === "/callback") {
|
|
100
|
+
const email = url.searchParams.get("email");
|
|
101
|
+
const token = url.searchParams.get("token");
|
|
102
|
+
const error = url.searchParams.get("error");
|
|
103
|
+
if (error) {
|
|
104
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
105
|
+
res.end(`
|
|
106
|
+
<html>
|
|
107
|
+
<body style="font-family: system-ui; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0;">
|
|
108
|
+
<div style="text-align: center;">
|
|
109
|
+
<h1 style="color: #ef4444;">Authentication failed: ${escapeHtml(error)}</h1>
|
|
110
|
+
<p>You can close this window.</p>
|
|
111
|
+
</div>
|
|
112
|
+
</body>
|
|
113
|
+
</html>
|
|
114
|
+
`);
|
|
115
|
+
server.close();
|
|
116
|
+
reject(new Error(error));
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (email && token) {
|
|
120
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
121
|
+
res.end(`
|
|
122
|
+
<html>
|
|
123
|
+
<body style="font-family: system-ui; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0;">
|
|
124
|
+
<div style="text-align: center;">
|
|
125
|
+
<h1 style="color: #10b981;">Logged in!</h1>
|
|
126
|
+
<p>Welcome, ${escapeHtml(email)}</p>
|
|
127
|
+
<p style="color: #6b7280;">You can close this window and return to your terminal.</p>
|
|
128
|
+
</div>
|
|
129
|
+
</body>
|
|
130
|
+
</html>
|
|
131
|
+
`);
|
|
132
|
+
server.close();
|
|
133
|
+
resolve({ email, token });
|
|
134
|
+
} else {
|
|
135
|
+
res.writeHead(400, { "Content-Type": "text/plain" });
|
|
136
|
+
res.end("Missing email or token");
|
|
137
|
+
}
|
|
138
|
+
} else {
|
|
139
|
+
res.writeHead(404);
|
|
140
|
+
res.end();
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
server.listen(port, () => {
|
|
144
|
+
});
|
|
145
|
+
setTimeout(() => {
|
|
146
|
+
server.close();
|
|
147
|
+
reject(new Error("Login timed out"));
|
|
148
|
+
}, 5 * 60 * 1e3);
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export {
|
|
153
|
+
isPersonalEmail,
|
|
154
|
+
getEmailDomain,
|
|
155
|
+
saveSession,
|
|
156
|
+
loadSession,
|
|
157
|
+
clearSession,
|
|
158
|
+
isLoggedIn,
|
|
159
|
+
verifyToken,
|
|
160
|
+
startAuthCallbackServer
|
|
161
|
+
};
|
|
162
|
+
//# sourceMappingURL=chunk-Z2UKDBNL.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/auth.ts"],"sourcesContent":["import { existsSync, readFileSync, writeFileSync, mkdirSync, unlinkSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\nimport http from 'http';\n\n// Personal email domains to reject\nconst PERSONAL_DOMAINS = [\n 'gmail.com', 'googlemail.com',\n 'yahoo.com', 'yahoo.co.uk', 'yahoo.fr',\n 'hotmail.com', 'outlook.com', 'live.com', 'msn.com',\n 'icloud.com', 'me.com', 'mac.com',\n 'aol.com',\n 'protonmail.com', 'proton.me',\n 'zoho.com',\n 'mail.com',\n 'yandex.com', 'yandex.ru',\n 'gmx.com', 'gmx.de',\n 'fastmail.com',\n 'tutanota.com',\n 'hey.com',\n];\n\nconst AUTH_DIR = join(homedir(), '.squads-cli');\nconst AUTH_PATH = join(AUTH_DIR, 'auth.json');\n\nexport interface AuthSession {\n email: string;\n domain: string;\n status: 'pending' | 'contacted' | 'active';\n createdAt: string;\n accessToken?: string;\n}\n\nexport function isPersonalEmail(email: string): boolean {\n const domain = email.split('@')[1]?.toLowerCase();\n return PERSONAL_DOMAINS.includes(domain);\n}\n\nexport function getEmailDomain(email: string): string {\n return email.split('@')[1]?.toLowerCase() || '';\n}\n\nexport function saveSession(session: AuthSession): void {\n if (!existsSync(AUTH_DIR)) {\n mkdirSync(AUTH_DIR, { recursive: true, mode: 0o700 });\n }\n writeFileSync(AUTH_PATH, JSON.stringify(session, null, 2), { mode: 0o600 });\n}\n\nexport function loadSession(): AuthSession | null {\n if (!existsSync(AUTH_PATH)) return null;\n try {\n return JSON.parse(readFileSync(AUTH_PATH, 'utf-8'));\n } catch {\n return null;\n }\n}\n\nexport function clearSession(): void {\n try {\n if (existsSync(AUTH_PATH)) {\n unlinkSync(AUTH_PATH);\n }\n } catch {\n // File already removed or inaccessible\n }\n}\n\nexport function isLoggedIn(): boolean {\n const session = loadSession();\n return session !== null && session.status === 'active';\n}\n\nexport interface VerifiedUser {\n email: string;\n tenantId: number;\n tenantSlug: string;\n tenantName: string;\n status: string;\n}\n\n// Verify a token against the API and return user data (null on failure)\nexport async function verifyToken(apiUrl: string, token: string): Promise<VerifiedUser | null> {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 5000);\n try {\n const response = await fetch(`${apiUrl}/auth/cli/verify`, {\n headers: { Authorization: `Bearer ${token}` },\n signal: controller.signal,\n });\n clearTimeout(timeout);\n if (!response.ok) return null;\n const data = await response.json() as Record<string, unknown>;\n return {\n email: String(data.email ?? ''),\n tenantId: Number(data.tenant_id ?? 0),\n tenantSlug: String(data.tenant_slug ?? ''),\n tenantName: String(data.tenant_name ?? ''),\n status: String(data.status ?? ''),\n };\n } catch {\n clearTimeout(timeout);\n return null;\n }\n}\n\n// Escape HTML special characters to prevent XSS\nfunction escapeHtml(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\n\n// Local callback server for OAuth flow\nexport function startAuthCallbackServer(port: number = 54321): Promise<{ email: string; token: string }> {\n return new Promise((resolve, reject) => {\n const server = http.createServer((req, res) => {\n const url = new URL(req.url || '', `http://localhost:${port}`);\n\n if (url.pathname === '/callback') {\n const email = url.searchParams.get('email');\n const token = url.searchParams.get('token');\n const error = url.searchParams.get('error');\n\n if (error) {\n res.writeHead(200, { 'Content-Type': 'text/html' });\n res.end(`\n <html>\n <body style=\"font-family: system-ui; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0;\">\n <div style=\"text-align: center;\">\n <h1 style=\"color: #ef4444;\">Authentication failed: ${escapeHtml(error)}</h1>\n <p>You can close this window.</p>\n </div>\n </body>\n </html>\n `);\n server.close();\n reject(new Error(error));\n return;\n }\n\n if (email && token) {\n res.writeHead(200, { 'Content-Type': 'text/html' });\n res.end(`\n <html>\n <body style=\"font-family: system-ui; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0;\">\n <div style=\"text-align: center;\">\n <h1 style=\"color: #10b981;\">Logged in!</h1>\n <p>Welcome, ${escapeHtml(email)}</p>\n <p style=\"color: #6b7280;\">You can close this window and return to your terminal.</p>\n </div>\n </body>\n </html>\n `);\n server.close();\n resolve({ email, token });\n } else {\n res.writeHead(400, { 'Content-Type': 'text/plain' });\n res.end('Missing email or token');\n }\n } else {\n res.writeHead(404);\n res.end();\n }\n });\n\n server.listen(port, () => {\n // Server started\n });\n\n // Timeout after 5 minutes\n setTimeout(() => {\n server.close();\n reject(new Error('Login timed out'));\n }, 5 * 60 * 1000);\n });\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
__require
|
|
4
|
+
} from "./chunk-7OCVIDC7.js";
|
|
5
|
+
|
|
6
|
+
// src/lib/memory.ts
|
|
7
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync2, readdirSync, mkdirSync as mkdirSync2 } from "fs";
|
|
8
|
+
import { join as join2, dirname as dirname2 } from "path";
|
|
9
|
+
|
|
10
|
+
// src/lib/lock.ts
|
|
11
|
+
import { existsSync, writeFileSync, unlinkSync, mkdirSync, statSync, readFileSync, openSync, closeSync } from "fs";
|
|
12
|
+
import { join, dirname } from "path";
|
|
13
|
+
import { createHash } from "crypto";
|
|
14
|
+
var LOCK_TTL_MS = 3e4;
|
|
15
|
+
var LOCK_RETRY_DELAY_MS = 100;
|
|
16
|
+
var LOCK_MAX_RETRIES = 50;
|
|
17
|
+
function generateLockId() {
|
|
18
|
+
return `${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
19
|
+
}
|
|
20
|
+
function getFileLockPath(filePath) {
|
|
21
|
+
const lockDir = join(dirname(filePath), ".locks");
|
|
22
|
+
const hash = createHash("md5").update(filePath).digest("hex").slice(0, 12);
|
|
23
|
+
return join(lockDir, `${hash}.lock`);
|
|
24
|
+
}
|
|
25
|
+
function sleep(ms) {
|
|
26
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
27
|
+
}
|
|
28
|
+
function acquireFileLock(lockPath, lockId) {
|
|
29
|
+
try {
|
|
30
|
+
const lockDir = dirname(lockPath);
|
|
31
|
+
if (!existsSync(lockDir)) {
|
|
32
|
+
mkdirSync(lockDir, { recursive: true });
|
|
33
|
+
}
|
|
34
|
+
if (existsSync(lockPath)) {
|
|
35
|
+
const stats = statSync(lockPath);
|
|
36
|
+
const ageMs = Date.now() - stats.mtimeMs;
|
|
37
|
+
if (ageMs > LOCK_TTL_MS) {
|
|
38
|
+
unlinkSync(lockPath);
|
|
39
|
+
} else {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const fd = openSync(lockPath, "wx");
|
|
44
|
+
writeFileSync(fd, lockId);
|
|
45
|
+
closeSync(fd);
|
|
46
|
+
return true;
|
|
47
|
+
} catch {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function releaseFileLock(lockPath, lockId) {
|
|
52
|
+
try {
|
|
53
|
+
if (!existsSync(lockPath)) return true;
|
|
54
|
+
const currentId = readFileSync(lockPath, "utf-8").trim();
|
|
55
|
+
if (currentId === lockId) {
|
|
56
|
+
unlinkSync(lockPath);
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
return false;
|
|
60
|
+
} catch {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
async function acquireLock(filePath) {
|
|
65
|
+
const lockId = generateLockId();
|
|
66
|
+
const fileLockPath = getFileLockPath(filePath);
|
|
67
|
+
let gotLock = false;
|
|
68
|
+
for (let attempt = 0; attempt < LOCK_MAX_RETRIES; attempt++) {
|
|
69
|
+
if (acquireFileLock(fileLockPath, lockId)) {
|
|
70
|
+
gotLock = true;
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
if (attempt < LOCK_MAX_RETRIES - 1) {
|
|
74
|
+
await sleep(LOCK_RETRY_DELAY_MS);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (!gotLock) {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
return async () => {
|
|
81
|
+
releaseFileLock(fileLockPath, lockId);
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
async function withLock(filePath, fn) {
|
|
85
|
+
const release = await acquireLock(filePath);
|
|
86
|
+
if (!release) {
|
|
87
|
+
throw new Error(`Failed to acquire lock for: ${filePath}`);
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
return await fn();
|
|
91
|
+
} finally {
|
|
92
|
+
await release();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// src/lib/memory.ts
|
|
97
|
+
function findMemoryDir() {
|
|
98
|
+
let dir = process.cwd();
|
|
99
|
+
for (let i = 0; i < 5; i++) {
|
|
100
|
+
const memoryPath = join2(dir, ".agents", "memory");
|
|
101
|
+
if (existsSync2(memoryPath)) {
|
|
102
|
+
return memoryPath;
|
|
103
|
+
}
|
|
104
|
+
const parent = join2(dir, "..");
|
|
105
|
+
if (parent === dir) break;
|
|
106
|
+
dir = parent;
|
|
107
|
+
}
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
function listMemoryEntries(memoryDir) {
|
|
111
|
+
const entries = [];
|
|
112
|
+
const squads = readdirSync(memoryDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
113
|
+
for (const squad of squads) {
|
|
114
|
+
const squadPath = join2(memoryDir, squad);
|
|
115
|
+
const agents = readdirSync(squadPath, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
116
|
+
for (const agent of agents) {
|
|
117
|
+
const agentPath = join2(squadPath, agent);
|
|
118
|
+
const files = readdirSync(agentPath).filter((f) => f.endsWith(".md"));
|
|
119
|
+
for (const file of files) {
|
|
120
|
+
const filePath = join2(agentPath, file);
|
|
121
|
+
const type = file.replace(".md", "");
|
|
122
|
+
entries.push({
|
|
123
|
+
squad,
|
|
124
|
+
agent,
|
|
125
|
+
type,
|
|
126
|
+
content: readFileSync2(filePath, "utf-8"),
|
|
127
|
+
path: filePath
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return entries;
|
|
133
|
+
}
|
|
134
|
+
var SEMANTIC_EXPANSIONS = {
|
|
135
|
+
"pricing": ["price", "cost", "$", "revenue", "fee", "rate"],
|
|
136
|
+
"price": ["pricing", "cost", "$", "fee"],
|
|
137
|
+
"revenue": ["income", "sales", "mrr", "arr", "$"],
|
|
138
|
+
"cost": ["expense", "spend", "budget", "$", "price"],
|
|
139
|
+
"customer": ["client", "lead", "prospect", "user"],
|
|
140
|
+
"client": ["customer", "lead", "prospect"],
|
|
141
|
+
"lead": ["prospect", "customer", "client", "pipeline"],
|
|
142
|
+
"agent": ["squad", "bot", "ai"],
|
|
143
|
+
"squad": ["team", "agent", "group"],
|
|
144
|
+
"status": ["state", "progress", "health"],
|
|
145
|
+
"bug": ["issue", "error", "problem", "fix"],
|
|
146
|
+
"feature": ["capability", "function", "ability"]
|
|
147
|
+
};
|
|
148
|
+
function expandQuery(query) {
|
|
149
|
+
const words = query.toLowerCase().split(/\s+/);
|
|
150
|
+
const expanded = new Set(words);
|
|
151
|
+
for (const word of words) {
|
|
152
|
+
if (SEMANTIC_EXPANSIONS[word]) {
|
|
153
|
+
SEMANTIC_EXPANSIONS[word].forEach((syn) => expanded.add(syn));
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return Array.from(expanded);
|
|
157
|
+
}
|
|
158
|
+
function getFileAge(filePath) {
|
|
159
|
+
try {
|
|
160
|
+
const { statSync: statSync2 } = __require("fs");
|
|
161
|
+
const stats = statSync2(filePath);
|
|
162
|
+
const ageMs = Date.now() - stats.mtimeMs;
|
|
163
|
+
const ageDays = ageMs / (1e3 * 60 * 60 * 24);
|
|
164
|
+
return ageDays;
|
|
165
|
+
} catch {
|
|
166
|
+
return 999;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
function searchMemory(query, memoryDir) {
|
|
170
|
+
const dir = memoryDir || findMemoryDir();
|
|
171
|
+
if (!dir) return [];
|
|
172
|
+
const entries = listMemoryEntries(dir);
|
|
173
|
+
const results = [];
|
|
174
|
+
const queryLower = query.toLowerCase();
|
|
175
|
+
const expandedTerms = expandQuery(queryLower);
|
|
176
|
+
for (const entry of entries) {
|
|
177
|
+
const contentLower = entry.content.toLowerCase();
|
|
178
|
+
const lines = entry.content.split("\n");
|
|
179
|
+
const matches = [];
|
|
180
|
+
let score = 0;
|
|
181
|
+
let directHits = 0;
|
|
182
|
+
let expandedHits = 0;
|
|
183
|
+
const directWords = queryLower.split(/\s+/);
|
|
184
|
+
for (const word of directWords) {
|
|
185
|
+
if (contentLower.includes(word)) {
|
|
186
|
+
directHits += 1;
|
|
187
|
+
score += 2;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
for (const term of expandedTerms) {
|
|
191
|
+
if (contentLower.includes(term)) {
|
|
192
|
+
expandedHits += 1;
|
|
193
|
+
score += 0.5;
|
|
194
|
+
for (let i = 0; i < lines.length; i++) {
|
|
195
|
+
const line = lines[i];
|
|
196
|
+
if (line.toLowerCase().includes(term) && line.trim() && !matches.includes(line.trim())) {
|
|
197
|
+
matches.push(line.trim());
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (contentLower.includes(queryLower)) {
|
|
203
|
+
score += 5;
|
|
204
|
+
}
|
|
205
|
+
const ageDays = getFileAge(entry.path);
|
|
206
|
+
if (ageDays < 1) {
|
|
207
|
+
score *= 1.5;
|
|
208
|
+
} else if (ageDays < 7) {
|
|
209
|
+
score *= 1.2;
|
|
210
|
+
} else if (ageDays > 30) {
|
|
211
|
+
score *= 0.8;
|
|
212
|
+
}
|
|
213
|
+
const typeWeights = {
|
|
214
|
+
"state": 1.2,
|
|
215
|
+
// Current state slightly preferred
|
|
216
|
+
"learnings": 1.1,
|
|
217
|
+
// Learnings are valuable
|
|
218
|
+
"output": 1,
|
|
219
|
+
// Recent outputs
|
|
220
|
+
"feedback": 0.9
|
|
221
|
+
// Feedback less commonly needed
|
|
222
|
+
};
|
|
223
|
+
score *= typeWeights[entry.type] || 1;
|
|
224
|
+
if (score > 0 && (directHits > 0 || expandedHits > 1)) {
|
|
225
|
+
results.push({ entry, matches: matches.slice(0, 7), score });
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return results.sort((a, b) => b.score - a.score);
|
|
229
|
+
}
|
|
230
|
+
function getSquadState(squadName) {
|
|
231
|
+
const memoryDir = findMemoryDir();
|
|
232
|
+
if (!memoryDir) return [];
|
|
233
|
+
const squadPath = join2(memoryDir, squadName);
|
|
234
|
+
if (!existsSync2(squadPath)) return [];
|
|
235
|
+
const entries = [];
|
|
236
|
+
const agents = readdirSync(squadPath, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
237
|
+
for (const agent of agents) {
|
|
238
|
+
const statePath = join2(squadPath, agent, "state.md");
|
|
239
|
+
if (existsSync2(statePath)) {
|
|
240
|
+
entries.push({
|
|
241
|
+
squad: squadName,
|
|
242
|
+
agent,
|
|
243
|
+
type: "state",
|
|
244
|
+
content: readFileSync2(statePath, "utf-8"),
|
|
245
|
+
path: statePath
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return entries;
|
|
250
|
+
}
|
|
251
|
+
async function updateMemory(squadName, agentName, type, content) {
|
|
252
|
+
const memoryDir = findMemoryDir();
|
|
253
|
+
if (!memoryDir) {
|
|
254
|
+
throw new Error("No .agents/memory directory found");
|
|
255
|
+
}
|
|
256
|
+
const filePath = join2(memoryDir, squadName, agentName, `${type}.md`);
|
|
257
|
+
const dir = dirname2(filePath);
|
|
258
|
+
await withLock(filePath, () => {
|
|
259
|
+
if (!existsSync2(dir)) {
|
|
260
|
+
mkdirSync2(dir, { recursive: true });
|
|
261
|
+
}
|
|
262
|
+
writeFileSync2(filePath, content);
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
function updateMemorySync(squadName, agentName, type, content) {
|
|
266
|
+
const memoryDir = findMemoryDir();
|
|
267
|
+
if (!memoryDir) {
|
|
268
|
+
throw new Error("No .agents/memory directory found");
|
|
269
|
+
}
|
|
270
|
+
const filePath = join2(memoryDir, squadName, agentName, `${type}.md`);
|
|
271
|
+
const dir = dirname2(filePath);
|
|
272
|
+
if (!existsSync2(dir)) {
|
|
273
|
+
mkdirSync2(dir, { recursive: true });
|
|
274
|
+
}
|
|
275
|
+
writeFileSync2(filePath, content);
|
|
276
|
+
}
|
|
277
|
+
async function appendToMemory(squadName, agentName, type, addition) {
|
|
278
|
+
const memoryDir = findMemoryDir();
|
|
279
|
+
if (!memoryDir) {
|
|
280
|
+
throw new Error("No .agents/memory directory found");
|
|
281
|
+
}
|
|
282
|
+
const filePath = join2(memoryDir, squadName, agentName, `${type}.md`);
|
|
283
|
+
await withLock(filePath, () => {
|
|
284
|
+
let existing = "";
|
|
285
|
+
if (existsSync2(filePath)) {
|
|
286
|
+
existing = readFileSync2(filePath, "utf-8");
|
|
287
|
+
}
|
|
288
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
289
|
+
const lines = addition.trim().split("\n");
|
|
290
|
+
const heading = lines[0].replace(/^#+\s*/, "").replace(/^\*\*/, "").replace(/\*\*$/, "");
|
|
291
|
+
const body = lines.slice(1).join("\n").trim();
|
|
292
|
+
const entry = body ? `## ${timestamp}: ${heading}
|
|
293
|
+
|
|
294
|
+
${body}` : `## ${timestamp}: ${heading}`;
|
|
295
|
+
const newContent = existing + `
|
|
296
|
+
|
|
297
|
+
${entry}`;
|
|
298
|
+
const dir = dirname2(filePath);
|
|
299
|
+
if (!existsSync2(dir)) {
|
|
300
|
+
mkdirSync2(dir, { recursive: true });
|
|
301
|
+
}
|
|
302
|
+
writeFileSync2(filePath, newContent.trim());
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
function appendToMemorySync(squadName, agentName, type, addition) {
|
|
306
|
+
const memoryDir = findMemoryDir();
|
|
307
|
+
if (!memoryDir) {
|
|
308
|
+
throw new Error("No .agents/memory directory found");
|
|
309
|
+
}
|
|
310
|
+
const filePath = join2(memoryDir, squadName, agentName, `${type}.md`);
|
|
311
|
+
let existing = "";
|
|
312
|
+
if (existsSync2(filePath)) {
|
|
313
|
+
existing = readFileSync2(filePath, "utf-8");
|
|
314
|
+
}
|
|
315
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
316
|
+
const lines = addition.trim().split("\n");
|
|
317
|
+
const heading = lines[0].replace(/^#+\s*/, "").replace(/^\*\*/, "").replace(/\*\*$/, "");
|
|
318
|
+
const body = lines.slice(1).join("\n").trim();
|
|
319
|
+
const entry = body ? `## ${timestamp}: ${heading}
|
|
320
|
+
|
|
321
|
+
${body}` : `## ${timestamp}: ${heading}`;
|
|
322
|
+
const newContent = existing + `
|
|
323
|
+
|
|
324
|
+
${entry}`;
|
|
325
|
+
updateMemorySync(squadName, agentName, type, newContent.trim());
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
export {
|
|
329
|
+
findMemoryDir,
|
|
330
|
+
listMemoryEntries,
|
|
331
|
+
searchMemory,
|
|
332
|
+
getSquadState,
|
|
333
|
+
updateMemory,
|
|
334
|
+
updateMemorySync,
|
|
335
|
+
appendToMemory,
|
|
336
|
+
appendToMemorySync
|
|
337
|
+
};
|
|
338
|
+
//# sourceMappingURL=chunk-ZTQ7ISUR.js.map
|