burnwatch 0.8.1 → 0.9.0

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  <p align="center">
2
2
  <h1 align="center">burnwatch</h1>
3
- <p align="center">Passive cost memory for vibe coding.</p>
3
+ <p align="center">Passive cost memory for AI-assisted development.</p>
4
4
  </p>
5
5
 
6
6
  <p align="center">
@@ -88,20 +88,28 @@ npx burnwatch init
88
88
  ✅ burnwatch initialized!
89
89
  ```
90
90
 
91
- ### 2. Add API keys and budgets
91
+ ### 2. Configure your services
92
+
93
+ **In Claude Code (recommended):** Just ask the agent:
94
+
95
+ ```
96
+ You: "Let's set up burnwatch for this project"
97
+ ```
98
+
99
+ The agent runs `/burnwatch-interview` — a conversational flow that walks through each detected service one at a time, confirms plans, sets budgets, and configures API keys. No terminal commands needed.
100
+
101
+ **From the terminal:**
92
102
 
93
103
  ```bash
94
- # LIVE tracking - real billing API data
95
- burnwatch add anthropic --key $ANTHROPIC_ADMIN_KEY --budget 100
96
- burnwatch add scrapfly --key $SCRAPFLY_KEY --budget 50
97
- burnwatch add vercel --token $VERCEL_TOKEN --budget 50
104
+ burnwatch init # interactive interview in TTY mode
105
+ ```
98
106
 
99
- # CALC tracking - flat-rate services
100
- burnwatch add posthog --plan-cost 0 --budget 0
101
- burnwatch add inngest --plan-cost 25 --budget 25
107
+ **Manual CLI (if you prefer):**
102
108
 
103
- # Just set a budget (tracking stays at detected tier)
104
- burnwatch add browserbase --budget 75
109
+ ```bash
110
+ burnwatch add anthropic --key $ANTHROPIC_ADMIN_KEY --budget 100
111
+ burnwatch add scrapfly --key $SCRAPFLY_KEY --budget 50
112
+ burnwatch configure --service posthog --plan "Free" --budget 0
105
113
  ```
106
114
 
107
115
  API keys are stored in `~/.config/burnwatch/` (global, `chmod 600`). They **never** touch your project directory. They never end up in git.
@@ -229,12 +237,51 @@ This feedback loop doesn't exist anywhere else today. The agent has cost memory.
229
237
 
230
238
  <br>
231
239
 
240
+ ## Claude Code Setup
241
+
242
+ burnwatch is designed to work natively with Claude Code. When you run `npx burnwatch init`, it:
243
+
244
+ 1. **Detects** all paid services in your project
245
+ 2. **Copies hooks** to `.burnwatch/hooks/` (session start, prompt submit, file change, stop)
246
+ 3. **Installs skills** to `.claude/skills/` — three agent skills become available:
247
+ - `/setup-burnwatch` — Guided onboarding that walks you through configuring all services
248
+ - `/burnwatch-interview` — Conversational interview that confirms plans, budgets, and API keys one service at a time
249
+ - `/spend` — Quick spend check (or `/spend scrapfly` for a specific service)
250
+ 4. **Registers hooks** in `.claude/settings.json`
251
+
252
+ ### Agent-driven configuration
253
+
254
+ Instead of running CLI commands manually, let the agent interview you:
255
+
256
+ ```
257
+ You: "Let's configure burnwatch"
258
+
259
+ Agent: I found 11 services in your project. Let me walk through each one.
260
+
261
+ First — Anthropic. Defaulted to "API Usage" at $100/mo.
262
+ I checked your API — you've spent $47.23 this month.
263
+ Does $100/mo feel right, or want to adjust?
264
+
265
+ You: That's good, keep it.
266
+
267
+ Agent: Got it — Anthropic set to $100/mo. ✅
268
+
269
+ Next — Scrapfly. Probe detected Pro plan, 250K/1M credits used.
270
+ Keep the $100/mo budget?
271
+ ```
272
+
273
+ The agent works through services by risk category (LLMs first, then usage-based, then infrastructure), batches free-tier services together, and writes each answer back immediately via `burnwatch configure`.
274
+
275
+ <br>
276
+
232
277
  ## CLI Reference
233
278
 
234
279
  ```
235
280
  burnwatch init Initialize in current project
236
281
  burnwatch setup Init + auto-configure all detected services
237
282
  burnwatch add <service> [options] Register a service for tracking
283
+ burnwatch configure [options] Update service config (plan, budget, key)
284
+ burnwatch interview --json Export current state as JSON (for agent use)
238
285
  burnwatch status Show current spend brief
239
286
  burnwatch services List all services in registry
240
287
  burnwatch reconcile Scan for untracked services
@@ -251,6 +298,16 @@ burnwatch version Show version
251
298
  | `--budget <N>` | Monthly budget in USD |
252
299
  | `--plan-cost <N>` | Monthly plan cost for CALC tracking |
253
300
 
301
+ ### `burnwatch configure` options
302
+
303
+ | Flag | Description |
304
+ |------|-------------|
305
+ | `--service <ID>` | Service to configure (e.g., `anthropic`, `scrapfly`) |
306
+ | `--plan <NAME>` | Set the plan tier |
307
+ | `--budget <N>` | Set monthly budget in USD |
308
+ | `--key <KEY>` | Set API key for LIVE tracking |
309
+ | `--exclude` | Exclude service from tracking |
310
+
254
311
  <br>
255
312
 
256
313
  ## Config Model
package/dist/cli.js CHANGED
@@ -1913,7 +1913,7 @@ async function cmdReconcile() {
1913
1913
  }
1914
1914
  function cmdHelp() {
1915
1915
  console.log(`
1916
- burnwatch \u2014 Passive cost memory for vibe coding
1916
+ burnwatch \u2014 Passive cost memory for AI-assisted development
1917
1917
 
1918
1918
  Usage:
1919
1919
  burnwatch init Interactive setup \u2014 pick plans per service
@@ -1987,6 +1987,26 @@ function registerHooks(projectRoot) {
1987
1987
  }
1988
1988
  }
1989
1989
  console.log(` Hook scripts copied to ${localHooksDir}`);
1990
+ const sourceSkillsDir = path5.resolve(
1991
+ path5.dirname(new URL(import.meta.url).pathname),
1992
+ "../skills"
1993
+ );
1994
+ const skillNames = ["setup-burnwatch", "burnwatch-interview", "spend"];
1995
+ const claudeSkillsDir = path5.join(projectRoot, ".claude", "skills");
1996
+ for (const skillName of skillNames) {
1997
+ const srcSkill = path5.join(sourceSkillsDir, skillName, "SKILL.md");
1998
+ const destDir = path5.join(claudeSkillsDir, skillName);
1999
+ const destSkill = path5.join(destDir, "SKILL.md");
2000
+ try {
2001
+ if (fs5.existsSync(srcSkill)) {
2002
+ fs5.mkdirSync(destDir, { recursive: true });
2003
+ fs5.copyFileSync(srcSkill, destSkill);
2004
+ }
2005
+ } catch (err) {
2006
+ console.error(` Warning: Could not copy skill ${skillName}: ${err instanceof Error ? err.message : err}`);
2007
+ }
2008
+ }
2009
+ console.log(` Skills installed to ${claudeSkillsDir}`);
1990
2010
  const claudeDir = path5.join(projectRoot, ".claude");
1991
2011
  const settingsPath = path5.join(claudeDir, "settings.json");
1992
2012
  fs5.mkdirSync(claudeDir, { recursive: true });
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/services/base.ts","../src/probes.ts","../src/cli.ts","../src/core/config.ts","../src/detection/detector.ts","../src/core/registry.ts","../src/services/anthropic.ts","../src/services/openai.ts","../src/services/vercel.ts","../src/services/scrapfly.ts","../src/services/index.ts","../src/core/types.ts","../src/core/brief.ts","../src/core/ledger.ts","../src/interactive-init.ts"],"sourcesContent":["import type { ConfidenceTier } from \"../core/types.js\";\n\n/** Result from polling a billing API. */\nexport interface BillingResult {\n serviceId: string;\n spend: number;\n isEstimate: boolean;\n tier: ConfidenceTier;\n raw?: Record<string, unknown>;\n error?: string;\n /** For credit-pool services: units consumed this period */\n unitsUsed?: number;\n /** For credit-pool services: total units in plan allowance */\n unitsTotal?: number;\n /** For credit-pool services: unit name (e.g., \"credits\") */\n unitName?: string;\n}\n\n/**\n * Base interface for service billing connectors.\n * Each LIVE service implements this to fetch real spend data.\n */\nexport interface BillingConnector {\n serviceId: string;\n /** Fetch current period spend. */\n fetchSpend(apiKey: string, options?: Record<string, string>): Promise<BillingResult>;\n}\n\n/**\n * Make an HTTP request and return JSON.\n * Uses native fetch (Node 18+). No external dependencies.\n */\nexport async function fetchJson<T>(\n url: string,\n options: {\n headers?: Record<string, string>;\n method?: string;\n body?: string;\n timeout?: number;\n } = {},\n): Promise<{ ok: boolean; status: number; data?: T; error?: string }> {\n try {\n const controller = new AbortController();\n const timeoutId = setTimeout(\n () => controller.abort(),\n options.timeout ?? 10_000,\n );\n\n const response = await fetch(url, {\n method: options.method ?? \"GET\",\n headers: options.headers,\n body: options.body,\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n if (!response.ok) {\n return {\n ok: false,\n status: response.status,\n error: `HTTP ${response.status}: ${response.statusText}`,\n };\n }\n\n const data = (await response.json()) as T;\n return { ok: true, status: response.status, data };\n } catch (err) {\n return {\n ok: false,\n status: 0,\n error: err instanceof Error ? err.message : \"Unknown error\",\n };\n }\n}\n","/**\n * Service probes — auto-detect plan, usage, and billing data from APIs.\n *\n * Each probe tries to discover as much as possible from a service's API:\n * 1. Plan tier (best case — \"You're on Pro\")\n * 2. Usage/spend data (good — \"You've used 850K credits\")\n * 3. Key validation (minimum — \"Your key works\")\n *\n * Probes are extensible: add a new entry to PROBES to support a new service.\n * The interview flow calls `probeService()` which looks up the right probe.\n */\n\nimport type { PlanTier } from \"./core/types.js\";\nimport { fetchJson } from \"./services/base.js\";\n\n/** Result from probing a service API */\nexport interface ProbeResult {\n /** Detected plan name (should match a registry PlanTier.name prefix) */\n planName?: string;\n /** Matched PlanTier from the registry */\n matchedPlan?: PlanTier;\n /** Usage/spend data discovered */\n usage?: {\n unitsUsed?: number;\n unitsTotal?: number;\n unitName?: string;\n spend?: number;\n currency?: string;\n };\n /** Human-readable summary of what was found */\n summary: string;\n /**\n * Discovery confidence:\n * high — plan tier identified (can skip plan selection)\n * medium — usage data found but plan unclear (show data, still ask plan)\n * low — key validates but no plan/usage info\n */\n confidence: \"high\" | \"medium\" | \"low\";\n}\n\n/** A probe function: given an API key and registry plans, discover what we can */\ntype ProbeFn = (\n apiKey: string,\n plans: PlanTier[],\n) => Promise<ProbeResult | null>;\n\n// ---------------------------------------------------------------------------\n// Service-specific probes\n// ---------------------------------------------------------------------------\n\n/** Match a detected plan name against registry plans */\nfunction matchPlan(\n detected: string,\n plans: PlanTier[],\n): PlanTier | undefined {\n const lower = detected.toLowerCase();\n return plans.find(\n (p) =>\n p.type !== \"exclude\" &&\n p.name.toLowerCase().includes(lower),\n );\n}\n\n/** Match by checking if the detected name appears as the first word of any plan */\nfunction matchPlanByPrefix(\n detected: string,\n plans: PlanTier[],\n): PlanTier | undefined {\n const lower = detected.toLowerCase();\n return plans.find((p) => {\n if (p.type === \"exclude\") return false;\n const firstWord = p.name.split(/[\\s(]/)[0]!.toLowerCase();\n return lower.includes(firstWord) || firstWord.includes(lower);\n });\n}\n\n// --- Scrapfly ---\nconst probeScrapfly: ProbeFn = async (apiKey, plans) => {\n const result = await fetchJson<{\n subscription?: {\n plan?: { name?: string };\n usage?: { scrape?: { used?: number; allowed?: number } };\n };\n account?: { credits_used?: number; credits_total?: number };\n }>(`https://api.scrapfly.io/account?key=${apiKey}`);\n\n if (!result.ok || !result.data) return null;\n\n const planName = result.data.subscription?.plan?.name;\n let unitsUsed = 0;\n let unitsTotal = 0;\n\n if (result.data.subscription?.usage?.scrape) {\n unitsUsed = result.data.subscription.usage.scrape.used ?? 0;\n unitsTotal = result.data.subscription.usage.scrape.allowed ?? 0;\n } else if (result.data.account) {\n unitsUsed = result.data.account.credits_used ?? 0;\n unitsTotal = result.data.account.credits_total ?? 0;\n }\n\n const matched = planName ? matchPlanByPrefix(planName, plans) : undefined;\n\n return {\n planName: planName ?? undefined,\n matchedPlan: matched,\n usage: {\n unitsUsed,\n unitsTotal,\n unitName: \"credits\",\n },\n summary: matched\n ? `${matched.name} — ${formatK(unitsUsed)}/${formatK(unitsTotal)} credits used`\n : `${formatK(unitsUsed)}/${formatK(unitsTotal)} credits used`,\n confidence: matched ? \"high\" : \"medium\",\n };\n};\n\n// --- Anthropic ---\nconst probeAnthropic: ProbeFn = async (apiKey, _plans) => {\n // Anthropic Admin API: GET /v1/organizations/cost_report\n const now = new Date();\n const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);\n const params = new URLSearchParams({\n start_date: startOfMonth.toISOString().split(\"T\")[0]!,\n end_date: now.toISOString().split(\"T\")[0]!,\n });\n\n const result = await fetchJson<{\n data?: Array<{ amount?: string; cost_type?: string }>;\n }>(`https://api.anthropic.com/v1/organizations/cost_report?${params}`, {\n headers: {\n \"x-api-key\": apiKey,\n \"anthropic-version\": \"2023-06-01\",\n },\n });\n\n if (!result.ok || !result.data?.data) return null;\n\n // Sum costs (returned in cents as strings)\n let totalCents = 0;\n for (const entry of result.data.data) {\n totalCents += parseFloat(entry.amount ?? \"0\");\n }\n const spend = totalCents / 100;\n\n return {\n usage: { spend, currency: \"USD\" },\n summary: `$${spend.toFixed(2)} spent this billing period`,\n confidence: \"medium\",\n };\n};\n\n// --- OpenAI ---\nconst probeOpenAI: ProbeFn = async (apiKey, _plans) => {\n // OpenAI Admin API: GET /v1/organization/usage/completions\n const now = new Date();\n const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);\n const params = new URLSearchParams({\n start_time: String(Math.floor(startOfMonth.getTime() / 1000)),\n end_time: String(Math.floor(now.getTime() / 1000)),\n });\n\n const result = await fetchJson<{\n data?: Array<{ results?: Array<{ amount?: { value?: number } }> }>;\n }>(`https://api.openai.com/v1/organization/usage/completions?${params}`, {\n headers: { Authorization: `Bearer ${apiKey}` },\n });\n\n if (!result.ok || !result.data?.data) return null;\n\n // Sum usage values\n let totalTokens = 0;\n for (const bucket of result.data.data) {\n for (const r of bucket.results ?? []) {\n totalTokens += r.amount?.value ?? 0;\n }\n }\n\n return {\n usage: { unitsUsed: totalTokens, unitName: \"tokens\" },\n summary: `${formatK(totalTokens)} tokens used this period`,\n confidence: \"medium\",\n };\n};\n\n// --- Vercel ---\nconst probeVercel: ProbeFn = async (apiKey, plans) => {\n // Try to get team info first\n const teamsResult = await fetchJson<{\n teams?: Array<{ id?: string; name?: string; billing?: { plan?: string } }>;\n }>(\"https://api.vercel.com/v2/teams\", {\n headers: { Authorization: `Bearer ${apiKey}` },\n });\n\n if (teamsResult.ok && teamsResult.data?.teams?.[0]) {\n const team = teamsResult.data.teams[0];\n const planName = team.billing?.plan;\n if (planName) {\n const matched = matchPlanByPrefix(planName, plans);\n return {\n planName,\n matchedPlan: matched,\n summary: `Team \"${team.name}\" on ${planName} plan`,\n confidence: matched ? \"high\" : \"medium\",\n };\n }\n }\n\n // Fallback: try user endpoint for hobby plan detection\n const userResult = await fetchJson<{\n user?: { billing?: { plan?: string }; name?: string };\n }>(\"https://api.vercel.com/v2/user\", {\n headers: { Authorization: `Bearer ${apiKey}` },\n });\n\n if (userResult.ok && userResult.data?.user) {\n const plan = userResult.data.user.billing?.plan ?? \"hobby\";\n const matched = matchPlanByPrefix(plan, plans);\n return {\n planName: plan,\n matchedPlan: matched,\n summary: `Personal account on ${plan} plan`,\n confidence: matched ? \"high\" : \"low\",\n };\n }\n\n return null;\n};\n\n// --- Supabase ---\nconst probeSupabase: ProbeFn = async (apiKey, plans) => {\n // Supabase Management API requires a PAT, not service_role_key\n const orgsResult = await fetchJson<\n Array<{ id?: string; name?: string; billing?: { plan?: string }; subscription_id?: string }>\n >(\"https://api.supabase.com/v1/organizations\", {\n headers: { Authorization: `Bearer ${apiKey}` },\n });\n\n if (!orgsResult.ok || !orgsResult.data || !Array.isArray(orgsResult.data)) return null;\n\n const org = orgsResult.data[0];\n if (!org) return null;\n\n const planName = org.billing?.plan;\n if (planName) {\n const matched = matchPlanByPrefix(planName, plans);\n return {\n planName,\n matchedPlan: matched,\n summary: `Org \"${org.name}\" on ${planName} plan`,\n confidence: matched ? \"high\" : \"medium\",\n };\n }\n\n return {\n summary: `Org \"${org.name}\" found (plan not detected)`,\n confidence: \"low\",\n };\n};\n\n// --- Stripe ---\nconst probeStripe: ProbeFn = async (apiKey, _plans) => {\n const result = await fetchJson<{\n available?: Array<{ amount?: number; currency?: string }>;\n pending?: Array<{ amount?: number; currency?: string }>;\n }>(\"https://api.stripe.com/v1/balance\", {\n headers: { Authorization: `Bearer ${apiKey}` },\n });\n\n if (!result.ok || !result.data) return null;\n\n const available = result.data.available?.[0];\n const pending = result.data.pending?.[0];\n const totalCents = (available?.amount ?? 0) + (pending?.amount ?? 0);\n const currency = (available?.currency ?? \"usd\").toUpperCase();\n\n return {\n usage: { spend: totalCents / 100, currency },\n summary: `Balance: ${currency} ${(totalCents / 100).toFixed(2)} (${((available?.amount ?? 0) / 100).toFixed(2)} available)`,\n confidence: \"medium\",\n };\n};\n\n// --- Browserbase ---\nconst probeBrowserbase: ProbeFn = async (apiKey, _plans) => {\n // Browserbase: GET /v1/projects (to get project ID), then usage\n const projResult = await fetchJson<\n Array<{ id?: string; name?: string }>\n >(\"https://api.browserbase.com/v1/projects\", {\n headers: { \"X-BB-API-Key\": apiKey },\n });\n\n if (!projResult.ok || !projResult.data?.[0]?.id) return null;\n\n const projectId = projResult.data[0].id;\n const usageResult = await fetchJson<{\n sessions_count?: number;\n browser_hours?: number;\n }>(`https://api.browserbase.com/v1/projects/${projectId}/usage`, {\n headers: { \"X-BB-API-Key\": apiKey },\n });\n\n if (!usageResult.ok || !usageResult.data) {\n return {\n summary: `Project \"${projResult.data[0].name}\" found`,\n confidence: \"low\",\n };\n }\n\n const sessions = usageResult.data.sessions_count ?? 0;\n const hours = usageResult.data.browser_hours ?? 0;\n\n return {\n usage: { unitsUsed: sessions, unitName: \"sessions\" },\n summary: `${sessions} sessions, ${hours.toFixed(1)} browser hours this period`,\n confidence: \"medium\",\n };\n};\n\n// --- Upstash ---\nconst probeUpstash: ProbeFn = async (apiKey, _plans) => {\n // Upstash uses email:api_key basic auth for the management API\n // The key from env is typically the Redis REST token, not management key\n // Try the databases list endpoint\n const result = await fetchJson<\n Array<{ database_id?: string; database_name?: string; region?: string }>\n >(\"https://api.upstash.com/v2/redis/databases\", {\n headers: {\n Authorization: `Basic ${Buffer.from(apiKey).toString(\"base64\")}`,\n },\n });\n\n if (!result.ok) return null;\n\n const dbCount = Array.isArray(result.data) ? result.data.length : 0;\n return {\n summary: `${dbCount} Redis database${dbCount !== 1 ? \"s\" : \"\"} found`,\n confidence: \"low\",\n };\n};\n\n// --- PostHog ---\nconst probePostHog: ProbeFn = async (apiKey, _plans) => {\n const result = await fetchJson<{\n results?: Array<{ id?: string; name?: string }>;\n }>(\"https://us.posthog.com/api/organizations/@current\", {\n headers: { Authorization: `Bearer ${apiKey}` },\n });\n\n if (!result.ok || !result.data) return null;\n\n return {\n summary: \"Organization found\",\n confidence: \"low\",\n };\n};\n\n// ---------------------------------------------------------------------------\n// Probe registry — maps service IDs to their probe function\n// ---------------------------------------------------------------------------\n\nconst PROBES: Map<string, ProbeFn> = new Map([\n [\"scrapfly\", probeScrapfly],\n [\"anthropic\", probeAnthropic],\n [\"openai\", probeOpenAI],\n [\"vercel\", probeVercel],\n [\"supabase\", probeSupabase],\n [\"stripe\", probeStripe],\n [\"browserbase\", probeBrowserbase],\n [\"upstash\", probeUpstash],\n [\"posthog\", probePostHog],\n]);\n\n/**\n * Probe a service using its API key.\n * Returns null if no probe exists for the service or the probe fails.\n */\nexport async function probeService(\n serviceId: string,\n apiKey: string,\n plans: PlanTier[],\n): Promise<ProbeResult | null> {\n const probe = PROBES.get(serviceId);\n if (!probe) return null;\n\n try {\n return await probe(apiKey, plans);\n } catch {\n return null;\n }\n}\n\n/** Check if a service has a probe available */\nexport function hasProbe(serviceId: string): boolean {\n return PROBES.has(serviceId);\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction formatK(n: number): string {\n if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(n % 1_000_000 === 0 ? 0 : 1)}M`;\n if (n >= 1_000) return `${(n / 1_000).toFixed(n % 1_000 === 0 ? 0 : 1)}K`;\n return String(n);\n}\n","#!/usr/bin/env node\n\n/**\n * burnwatch CLI\n *\n * Usage:\n * burnwatch init — Initialize in current project\n * burnwatch add <service> [options] — Register a service\n * burnwatch status — Show current spend brief\n * burnwatch reconcile — Scan for untracked sessions\n */\n\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport {\n ensureProjectDirs,\n readProjectConfig,\n writeProjectConfig,\n readGlobalConfig,\n writeGlobalConfig,\n projectConfigDir,\n isInitialized,\n} from \"./core/config.js\";\nimport type { ProjectConfig } from \"./core/config.js\";\nimport type { TrackedService } from \"./core/types.js\";\nimport { detectServices } from \"./detection/detector.js\";\nimport { pollAllServices } from \"./services/index.js\";\nimport { buildSnapshot, buildBrief, formatBrief } from \"./core/brief.js\";\nimport { writeLedger, saveSnapshot } from \"./core/ledger.js\";\nimport { getService, getAllServices } from \"./core/registry.js\";\nimport { runInteractiveInit, autoConfigureServices } from \"./interactive-init.js\";\n\nconst args = process.argv.slice(2);\nconst command = args[0];\nconst flags = new Set(args.slice(1));\n\nasync function main(): Promise<void> {\n switch (command) {\n case \"init\":\n case \"setup\":\n await cmdInit();\n break;\n case \"add\":\n await cmdAdd();\n break;\n case \"status\":\n await cmdStatus();\n break;\n case \"services\":\n cmdServices();\n break;\n case \"reconcile\":\n await cmdReconcile();\n break;\n case \"interview\":\n await cmdInterview();\n break;\n case \"configure\":\n await cmdConfigure();\n break;\n case \"help\":\n case \"--help\":\n case \"-h\":\n cmdHelp();\n break;\n case \"version\":\n case \"--version\":\n case \"-v\":\n cmdVersion();\n break;\n default:\n if (command) {\n console.error(`Unknown command: ${command}`);\n console.error('Run \"burnwatch help\" for usage.');\n process.exit(1);\n }\n cmdHelp();\n }\n}\n\n// --- Commands ---\n\nasync function cmdInit(): Promise<void> {\n const projectRoot = process.cwd();\n const nonInteractive = flags.has(\"--non-interactive\") || flags.has(\"--ni\");\n const alreadyInitialized = isInitialized(projectRoot);\n\n // Detect project name from package.json\n let projectName = path.basename(projectRoot);\n try {\n const pkgPath = path.join(projectRoot, \"package.json\");\n const pkg = JSON.parse(fs.readFileSync(pkgPath, \"utf-8\")) as {\n name?: string;\n };\n if (pkg.name) projectName = pkg.name;\n } catch {\n // Use directory name\n }\n\n // Create directories (idempotent)\n ensureProjectDirs(projectRoot);\n\n // Run detection\n console.log(\"🔍 Scanning project for paid services...\\n\");\n const detected = detectServices(projectRoot);\n\n // Load existing config or create new one\n const existingConfig = alreadyInitialized ? readProjectConfig(projectRoot) : null;\n const config: ProjectConfig = {\n projectName: existingConfig?.projectName ?? projectName,\n services: existingConfig?.services ?? {},\n createdAt: existingConfig?.createdAt ?? new Date().toISOString(),\n updatedAt: new Date().toISOString(),\n };\n\n if (detected.length === 0) {\n console.log(\" No paid services detected yet.\");\n console.log(\" Services will be detected as they enter your project.\\n\");\n } else if (nonInteractive) {\n // Explicit --non-interactive: minimal auto-register (CI/scripts)\n for (const det of detected) {\n if (!config.services[det.service.id]) {\n config.services[det.service.id] = {\n serviceId: det.service.id,\n detectedVia: det.sources,\n hasApiKey: false,\n firstDetected: new Date().toISOString(),\n budget: 0,\n };\n }\n }\n console.log(` Registered ${detected.length} services (non-interactive).\\n`);\n } else if (process.stdin.isTTY) {\n // TTY: full interactive interview with readline prompts\n const result = await runInteractiveInit(detected);\n config.services = result.services;\n } else {\n // No TTY (Claude Code, piped): auto-configure with smart defaults\n const result = await autoConfigureServices(detected);\n config.services = result.services;\n }\n\n writeProjectConfig(config, projectRoot);\n\n // Write initial .gitignore for the .burnwatch directory\n const gitignorePath = path.join(projectConfigDir(projectRoot), \".gitignore\");\n fs.writeFileSync(\n gitignorePath,\n [\n \"# Burnwatch — ignore cache and snapshots, keep ledger and config\",\n \"data/cache/\",\n \"data/snapshots/\",\n \"data/events.jsonl\",\n \"\",\n ].join(\"\\n\"),\n \"utf-8\",\n );\n\n // Register Claude Code hooks\n console.log(\"\\n🔗 Registering Claude Code hooks...\\n\");\n registerHooks(projectRoot);\n\n console.log(\"\\nburnwatch initialized.\\n\");\n console.log(\"Next steps:\");\n console.log(\" burnwatch status Show current spend\");\n console.log(\" burnwatch add <svc> Update a service's budget or API key\");\n console.log(\" burnwatch init Re-run this setup anytime\\n\");\n}\n\n/**\n * burnwatch interview --json\n *\n * Exports the current project state as structured JSON for an AI agent\n * to conduct the interview conversationally. The agent reads this,\n * asks the user questions, then writes answers back via `burnwatch configure`.\n */\nasync function cmdInterview(): Promise<void> {\n const projectRoot = process.cwd();\n\n if (!isInitialized(projectRoot)) {\n // Auto-init first\n ensureProjectDirs(projectRoot);\n const detected = detectServices(projectRoot);\n let projectName = path.basename(projectRoot);\n try {\n const pkg = JSON.parse(\n fs.readFileSync(path.join(projectRoot, \"package.json\"), \"utf-8\"),\n ) as { name?: string };\n if (pkg.name) projectName = pkg.name;\n } catch {\n // Use directory name\n }\n\n const config: ProjectConfig = {\n projectName,\n services: {},\n createdAt: new Date().toISOString(),\n updatedAt: new Date().toISOString(),\n };\n\n // Auto-configure with defaults first\n const result = await autoConfigureServices(detected);\n config.services = result.services;\n writeProjectConfig(config, projectRoot);\n }\n\n const config = readProjectConfig(projectRoot)!;\n const globalConfig = readGlobalConfig();\n const allRegistryServices = getAllServices(projectRoot);\n\n // Build structured export for each tracked service\n const serviceStates: Array<{\n serviceId: string;\n serviceName: string;\n currentPlan: string | null;\n currentBudget: number | null;\n hasApiKey: boolean;\n keySource: string | null;\n tier: string;\n excluded: boolean;\n hasProbe: boolean;\n probeResult: import(\"./probes.js\").ProbeResult | null;\n availablePlans: Array<{\n index: number;\n name: string;\n type: string;\n monthlyCost: number | null;\n includedUnits: number | null;\n unitName: string | null;\n suggestedBudget: number | null;\n isDefault: boolean;\n }>;\n riskCategory: string;\n billingModel: string;\n apiKeyHint: string | null;\n allowance: { included: number; unitName: string } | null;\n }> = [];\n\n for (const [serviceId, tracked] of Object.entries(config.services)) {\n const definition = allRegistryServices.find((s) => s.id === serviceId);\n if (!definition) continue;\n\n // Determine key source\n let keySource: string | null = null;\n const globalKey = globalConfig.services[serviceId]?.apiKey;\n if (globalKey) keySource = \"global_config\";\n else {\n for (const pattern of definition.envPatterns) {\n if (process.env[pattern]) {\n keySource = `env:${pattern}`;\n break;\n }\n }\n }\n\n // Try probing if we have a key\n let probeResult: import(\"./probes.js\").ProbeResult | null = null;\n const { probeService: probe, hasProbe: checkProbe } = await import(\"./probes.js\");\n const apiKey = globalKey ?? (keySource?.startsWith(\"env:\") ? process.env[keySource.slice(4)] : undefined);\n if (apiKey && checkProbe(serviceId)) {\n probeResult = await probe(serviceId, apiKey, definition.plans ?? []);\n }\n\n // Determine tier\n let tier = \"blind\";\n if (tracked.excluded) tier = \"excluded\";\n else if (tracked.hasApiKey) tier = \"live\";\n else if (tracked.planCost !== undefined) tier = \"calc\";\n\n // Risk category\n let riskCategory = \"flat\";\n if (definition.billingModel === \"token_usage\") riskCategory = \"llm\";\n else if ([\"credit_pool\", \"percentage\", \"per_unit\"].includes(definition.billingModel)) riskCategory = \"usage\";\n else if (definition.billingModel === \"compute\") riskCategory = \"infra\";\n\n const keyHints: Record<string, string> = {\n anthropic: \"Admin key from console.anthropic.com → Settings → Admin API Keys (sk-ant-admin-*)\",\n openai: \"Admin key from platform.openai.com → Settings → API Keys (sk-admin-*)\",\n vercel: \"Token from vercel.com/account/tokens\",\n supabase: \"PAT from supabase.com/dashboard → Account → Access Tokens (not service_role key)\",\n stripe: \"Secret key from dashboard.stripe.com → Developers → API Keys (sk_live_*)\",\n scrapfly: \"API key from scrapfly.io/dashboard\",\n browserbase: \"API key from browserbase.com → Settings → API Keys\",\n upstash: \"email:api_key from console.upstash.com → Account → Management API\",\n posthog: \"Personal API key from posthog.com → Settings → Personal API Keys\",\n };\n\n serviceStates.push({\n serviceId,\n serviceName: definition.name,\n currentPlan: tracked.planName ?? null,\n currentBudget: tracked.budget ?? null,\n hasApiKey: tracked.hasApiKey,\n keySource,\n tier,\n excluded: tracked.excluded ?? false,\n hasProbe: checkProbe(serviceId),\n probeResult,\n availablePlans: (definition.plans ?? []).map((p, i) => ({\n index: i + 1,\n name: p.name,\n type: p.type,\n monthlyCost: p.monthlyBase ?? null,\n includedUnits: p.includedUnits ?? null,\n unitName: p.unitName ?? null,\n suggestedBudget: p.suggestedBudget ?? null,\n isDefault: p.default ?? false,\n })),\n riskCategory,\n billingModel: definition.billingModel,\n apiKeyHint: keyHints[serviceId] ?? null,\n allowance: tracked.allowance ?? null,\n });\n }\n\n // Sort: llm first, then usage, infra, flat\n const riskOrder = [\"llm\", \"usage\", \"infra\", \"flat\"];\n serviceStates.sort(\n (a, b) => riskOrder.indexOf(a.riskCategory) - riskOrder.indexOf(b.riskCategory),\n );\n\n const output = {\n projectName: config.projectName,\n serviceCount: serviceStates.length,\n totalBudget: serviceStates.reduce((sum, s) => sum + (s.currentBudget ?? 0), 0),\n liveCount: serviceStates.filter((s) => s.tier === \"live\").length,\n blindCount: serviceStates.filter((s) => s.tier === \"blind\").length,\n services: serviceStates,\n configureCommand: \"burnwatch configure --service <id> [--plan <name>] [--budget <N>] [--key <KEY>] [--exclude]\",\n };\n\n if (flags.has(\"--json\")) {\n console.log(JSON.stringify(output, null, 2));\n } else {\n // Human-readable summary pointing to --json\n console.log(`\\n📋 Interview state for ${config.projectName}\\n`);\n console.log(` ${serviceStates.length} services detected`);\n console.log(` ${output.liveCount} with API keys (LIVE)`);\n console.log(` ${output.blindCount} without tracking (BLIND)`);\n console.log(` Total budget: $${output.totalBudget}/mo\\n`);\n console.log(` Use --json for machine-readable output.`);\n console.log(` Use 'burnwatch configure' to update services.\\n`);\n }\n}\n\n/**\n * burnwatch configure --service <id> [--plan <name>] [--budget <N>] [--key <KEY>] [--exclude]\n *\n * Agent-friendly command to write back interview answers for a single service.\n * Designed to be called by the AI agent after conversing with the user.\n */\nasync function cmdConfigure(): Promise<void> {\n const projectRoot = process.cwd();\n\n if (!isInitialized(projectRoot)) {\n console.error('❌ burnwatch not initialized. Run \"burnwatch init\" first.');\n process.exit(1);\n }\n\n // Parse named options\n const options: Record<string, string> = {};\n for (let i = 1; i < args.length; i++) {\n const arg = args[i]!;\n if (arg.startsWith(\"--\") && i + 1 < args.length) {\n options[arg.slice(2)] = args[i + 1]!;\n i++;\n } else if (arg === \"--exclude\") {\n options[\"exclude\"] = \"true\";\n }\n }\n\n const serviceId = options[\"service\"];\n if (!serviceId) {\n console.error(\"Usage: burnwatch configure --service <id> [--plan <name>] [--budget <N>] [--key <KEY>] [--exclude]\");\n process.exit(1);\n }\n\n const config = readProjectConfig(projectRoot)!;\n const definition = getService(serviceId, projectRoot);\n const globalConfig = readGlobalConfig();\n\n // Get or create tracked service entry\n let tracked = config.services[serviceId];\n if (!tracked) {\n tracked = {\n serviceId,\n detectedVia: [\"manual\"],\n hasApiKey: false,\n firstDetected: new Date().toISOString(),\n budget: 0,\n };\n }\n\n // Handle --exclude\n if (options[\"exclude\"] === \"true\") {\n tracked.excluded = true;\n tracked.planName = \"Don't track for this project\";\n delete tracked.budget;\n delete tracked.planCost;\n delete tracked.allowance;\n config.services[serviceId] = tracked;\n writeProjectConfig(config, projectRoot);\n console.log(JSON.stringify({ success: true, serviceId, action: \"excluded\" }));\n return;\n }\n\n // Handle --plan\n if (options[\"plan\"]) {\n const planSearch = options[\"plan\"].toLowerCase();\n const plans = definition?.plans ?? [];\n const matched = plans.find(\n (p) =>\n p.name.toLowerCase().includes(planSearch) ||\n p.name.toLowerCase().split(/[\\s(]/)[0] === planSearch,\n );\n\n if (matched) {\n tracked.planName = matched.name;\n tracked.excluded = false;\n\n if (matched.type === \"flat\" && matched.monthlyBase !== undefined) {\n tracked.planCost = matched.monthlyBase;\n // Default budget to plan cost if not explicitly set\n if (options[\"budget\"] === undefined && (tracked.budget === undefined || tracked.budget === 0)) {\n tracked.budget = matched.monthlyBase;\n }\n } else if (matched.suggestedBudget !== undefined && options[\"budget\"] === undefined) {\n if (tracked.budget === undefined || tracked.budget === 0) {\n tracked.budget = matched.suggestedBudget;\n }\n }\n\n // Set allowance for credit-pool plans\n if (matched.includedUnits !== undefined && matched.unitName) {\n tracked.allowance = {\n included: matched.includedUnits,\n unitName: matched.unitName,\n };\n } else {\n delete tracked.allowance;\n }\n } else {\n // Use as literal plan name\n tracked.planName = options[\"plan\"];\n }\n }\n\n // Handle --budget\n if (options[\"budget\"] !== undefined) {\n const parsed = parseFloat(options[\"budget\"]);\n if (!isNaN(parsed)) {\n tracked.budget = parsed;\n }\n }\n\n // Handle --key\n if (options[\"key\"]) {\n tracked.hasApiKey = true;\n if (!globalConfig.services[serviceId]) {\n globalConfig.services[serviceId] = {};\n }\n globalConfig.services[serviceId]!.apiKey = options[\"key\"];\n writeGlobalConfig(globalConfig);\n\n // Probe with the new key if possible\n const { probeService: probe, hasProbe: checkProbe } = await import(\"./probes.js\");\n if (checkProbe(serviceId) && definition?.plans) {\n const probeResult = await probe(serviceId, options[\"key\"], definition.plans);\n if (probeResult?.matchedPlan && probeResult.confidence === \"high\" && !options[\"plan\"]) {\n // Auto-apply detected plan\n const mp = probeResult.matchedPlan;\n tracked.planName = mp.name;\n if (mp.type === \"flat\" && mp.monthlyBase !== undefined) {\n tracked.planCost = mp.monthlyBase;\n if (options[\"budget\"] === undefined) tracked.budget = mp.monthlyBase;\n }\n if (mp.includedUnits !== undefined && mp.unitName) {\n tracked.allowance = { included: mp.includedUnits, unitName: mp.unitName };\n }\n }\n }\n }\n\n config.services[serviceId] = tracked;\n writeProjectConfig(config, projectRoot);\n\n // Determine final tier\n let tier = \"blind\";\n if (tracked.excluded) tier = \"excluded\";\n else if (tracked.hasApiKey) tier = \"live\";\n else if (tracked.planCost !== undefined) tier = \"calc\";\n\n const result = {\n success: true,\n serviceId,\n plan: tracked.planName ?? null,\n budget: tracked.budget ?? null,\n tier,\n hasApiKey: tracked.hasApiKey,\n allowance: tracked.allowance ?? null,\n };\n\n console.log(JSON.stringify(result));\n}\n\nasync function cmdAdd(): Promise<void> {\n const projectRoot = process.cwd();\n\n if (!isInitialized(projectRoot)) {\n console.error('❌ burnwatch not initialized. Run \"burnwatch init\" first.');\n process.exit(1);\n }\n\n const serviceId = args[1];\n if (!serviceId) {\n console.error(\"Usage: burnwatch add <service> [--key KEY] [--budget N]\");\n process.exit(1);\n }\n\n // Parse options\n const options: Record<string, string> = {};\n for (let i = 2; i < args.length; i++) {\n const arg = args[i]!;\n if (arg.startsWith(\"--\") && i + 1 < args.length) {\n options[arg.slice(2)] = args[i + 1]!;\n i++;\n }\n }\n\n const apiKey = options[\"key\"] ?? options[\"token\"];\n const budget = options[\"budget\"] ? parseFloat(options[\"budget\"]) : undefined;\n const planCost = options[\"plan-cost\"]\n ? parseFloat(options[\"plan-cost\"])\n : undefined;\n\n // Check if service is in registry\n const definition = getService(serviceId, projectRoot);\n if (!definition) {\n console.error(\n `⚠️ \"${serviceId}\" not found in registry. Adding as custom service.`,\n );\n }\n\n // Update project config\n const config = readProjectConfig(projectRoot)!;\n const existing = config.services[serviceId];\n\n const tracked: TrackedService = {\n serviceId,\n detectedVia: existing?.detectedVia ?? [\"manual\"],\n budget: budget ?? existing?.budget,\n hasApiKey: !!apiKey || (existing?.hasApiKey ?? false),\n planCost: planCost ?? existing?.planCost,\n firstDetected: existing?.firstDetected ?? new Date().toISOString(),\n };\n\n config.services[serviceId] = tracked;\n writeProjectConfig(config, projectRoot);\n\n // Save API key to global config (never in project dir)\n if (apiKey) {\n const globalConfig = readGlobalConfig();\n if (!globalConfig.services[serviceId]) {\n globalConfig.services[serviceId] = {};\n }\n globalConfig.services[serviceId]!.apiKey = apiKey;\n writeGlobalConfig(globalConfig);\n console.log(`🔐 API key saved to global config (never stored in project)`);\n }\n\n let tierLabel: string;\n if (!definition) {\n tierLabel = \"🔴 BLIND\";\n } else if (apiKey) {\n tierLabel = \"✅ LIVE\";\n } else if (planCost !== undefined) {\n tierLabel = \"🟡 CALC\";\n } else if (definition.apiTier === \"est\") {\n tierLabel = \"🟠 EST\";\n } else if (definition.apiTier === \"calc\") {\n tierLabel = \"🟡 CALC\";\n } else if (definition.apiTier === \"live\" && !apiKey) {\n tierLabel = `🔴 BLIND (add --key for ✅ LIVE)`;\n } else {\n tierLabel = \"🔴 BLIND\";\n }\n\n console.log(`\\n✅ ${serviceId} configured:`);\n console.log(` Tier: ${tierLabel}`);\n if (budget) console.log(` Budget: $${budget}/mo`);\n if (planCost) console.log(` Plan cost: $${planCost}/mo`);\n console.log(\"\");\n}\n\nasync function cmdStatus(): Promise<void> {\n const projectRoot = process.cwd();\n\n if (!isInitialized(projectRoot)) {\n console.error('❌ burnwatch not initialized. Run \"burnwatch init\" first.');\n process.exit(1);\n }\n\n const config = readProjectConfig(projectRoot)!;\n const trackedServices = Object.values(config.services);\n\n if (trackedServices.length === 0) {\n console.log(\"No services tracked yet.\");\n console.log('Run \"burnwatch add <service>\" to start tracking.');\n return;\n }\n\n console.log(\"📊 Polling services...\\n\");\n\n const results = await pollAllServices(trackedServices);\n const snapshots = results.map((r) => {\n const tracked = config.services[r.serviceId];\n const allowanceData = r.unitsUsed !== undefined && r.unitsTotal !== undefined && r.unitName\n ? { used: r.unitsUsed, included: r.unitsTotal, unitName: r.unitName }\n : tracked?.allowance\n ? { used: 0, included: tracked.allowance.included, unitName: tracked.allowance.unitName }\n : undefined;\n return buildSnapshot(r.serviceId, r.tier, r.spend, tracked?.budget, allowanceData);\n });\n\n const blindCount = snapshots.filter((s) => s.tier === \"blind\").length;\n const brief = buildBrief(config.projectName, snapshots, blindCount);\n\n // Save snapshot and update ledger\n saveSnapshot(brief, projectRoot);\n writeLedger(brief, projectRoot);\n\n // Display the brief\n console.log(formatBrief(brief));\n console.log(\"\");\n\n if (blindCount > 0) {\n console.log(`⚠️ ${blindCount} service${blindCount > 1 ? \"s\" : \"\"} untracked:`);\n for (const snap of snapshots.filter((s) => s.tier === \"blind\")) {\n console.log(` • ${snap.serviceId}`);\n }\n console.log(`\\n Run 'burnwatch init' to configure budgets and API keys.\\n`);\n }\n}\n\nasync function cmdSetup(): Promise<void> {\n const projectRoot = process.cwd();\n\n // Step 1: Init if needed\n if (!isInitialized(projectRoot)) {\n await cmdInit();\n }\n\n const config = readProjectConfig(projectRoot)!;\n const detected = Object.values(config.services);\n\n if (detected.length === 0) {\n console.log(\"No paid services detected. You're all set!\");\n return;\n }\n\n console.log(\"📋 Auto-configuring detected services...\\n\");\n\n // Step 2: Check global config for existing API keys\n const globalConfig = readGlobalConfig();\n\n // Step 3: Auto-configure each service based on registry tier + available keys\n const liveServices: string[] = [];\n const calcServices: string[] = [];\n const estServices: string[] = [];\n const blindServices: string[] = [];\n\n for (const tracked of detected) {\n const definition = getService(tracked.serviceId, projectRoot);\n if (!definition) continue;\n\n const hasKey = !!globalConfig.services[tracked.serviceId]?.apiKey;\n\n if (hasKey && definition.apiTier === \"live\") {\n tracked.hasApiKey = true;\n liveServices.push(`${definition.name}`);\n } else if (definition.apiTier === \"calc\") {\n calcServices.push(`${definition.name}`);\n } else if (definition.apiTier === \"est\") {\n estServices.push(`${definition.name}`);\n } else {\n blindServices.push(`${definition.name}`);\n }\n }\n\n writeProjectConfig(config, projectRoot);\n\n // Report\n if (liveServices.length > 0) {\n console.log(` ✅ LIVE (real billing data): ${liveServices.join(\", \")}`);\n }\n if (calcServices.length > 0) {\n console.log(` 🟡 CALC (flat-rate tracking): ${calcServices.join(\", \")}`);\n }\n if (estServices.length > 0) {\n console.log(` 🟠 EST (estimated from usage): ${estServices.join(\", \")}`);\n }\n if (blindServices.length > 0) {\n console.log(` 🔴 BLIND (detected, need API key): ${blindServices.join(\", \")}`);\n }\n\n console.log(\"\");\n\n if (blindServices.length > 0) {\n console.log(\"To upgrade BLIND services to LIVE, add API keys:\");\n for (const tracked of detected) {\n const definition = getService(tracked.serviceId, projectRoot);\n if (definition?.apiTier === \"live\" && !tracked.hasApiKey) {\n const envHint = definition.envPatterns[0] ?? \"YOUR_KEY\";\n console.log(` burnwatch add ${tracked.serviceId} --key $${envHint} --budget <N>`);\n }\n }\n console.log(\"\");\n }\n\n console.log(\"To set budgets for any service:\");\n console.log(\" burnwatch add <service> --budget <monthly_amount>\");\n console.log(\"\");\n console.log(\"Or use /setup-burnwatch in Claude Code for guided setup with budget suggestions.\\n\");\n\n // Show brief\n await cmdStatus();\n}\n\nfunction cmdServices(): void {\n const services = getAllServices();\n console.log(`\\n📋 Registry: ${services.length} services available\\n`);\n\n for (const svc of services) {\n const tierBadge =\n svc.apiTier === \"live\"\n ? \"✅ LIVE\"\n : svc.apiTier === \"calc\"\n ? \"🟡 CALC\"\n : svc.apiTier === \"est\"\n ? \"🟠 EST\"\n : \"🔴 BLIND\";\n\n console.log(` ${svc.name.padEnd(24)} ${tierBadge.padEnd(10)} ${svc.billingModel}`);\n }\n\n console.log(\"\");\n}\n\nasync function cmdReconcile(): Promise<void> {\n const projectRoot = process.cwd();\n\n if (!isInitialized(projectRoot)) {\n console.error('❌ burnwatch not initialized. Run \"burnwatch init\" first.');\n process.exit(1);\n }\n\n console.log(\"🔍 Scanning for untracked services and missed sessions...\\n\");\n\n // Re-run detection against current project state\n const detected = detectServices(projectRoot);\n const config = readProjectConfig(projectRoot)!;\n let newCount = 0;\n\n for (const det of detected) {\n if (!config.services[det.service.id]) {\n config.services[det.service.id] = {\n serviceId: det.service.id,\n detectedVia: det.sources,\n hasApiKey: false,\n firstDetected: new Date().toISOString(),\n };\n newCount++;\n console.log(` 🆕 ${det.service.name} — detected via ${det.details.join(\", \")}`);\n }\n }\n\n if (newCount > 0) {\n writeProjectConfig(config, projectRoot);\n console.log(\n `\\n✅ Found ${newCount} new service${newCount > 1 ? \"s\" : \"\"}. Run 'burnwatch status' to see updated brief.`,\n );\n } else {\n console.log(\" ✅ No new services found. All services already tracked.\");\n }\n\n console.log(\"\");\n}\n\nfunction cmdHelp(): void {\n console.log(`\nburnwatch — Passive cost memory for vibe coding\n\nUsage:\n burnwatch init Interactive setup — pick plans per service\n burnwatch init --non-interactive Auto-detect services, no prompts\n burnwatch setup Init + auto-configure all detected services\n burnwatch add <service> [options] Register a service for tracking\n burnwatch status Show current spend brief\n burnwatch services List all services in registry\n burnwatch reconcile Scan for untracked services\n burnwatch interview --json Export state for agent-driven interview\n burnwatch configure --service <id> [opts] Agent writes back interview answers\n\nOptions for 'configure':\n --service <ID> Service to configure (required)\n --plan <NAME> Plan name (fuzzy matches against registry)\n --budget <AMOUNT> Monthly budget in USD\n --key <API_KEY> API key for LIVE tracking\n --exclude Exclude this service from tracking\n\nOptions for 'add':\n --key <API_KEY> API key for LIVE tracking (saved to ~/.config/burnwatch/)\n --token <TOKEN> Same as --key (alias)\n --budget <AMOUNT> Monthly budget in USD\n --plan-cost <AMOUNT> Monthly plan cost for CALC tracking\n\nExamples:\n burnwatch init\n burnwatch interview --json\n burnwatch configure --service anthropic --plan \"API Usage\" --budget 100\n burnwatch configure --service supabase --plan pro --budget 25 --key sbp_xxx\n burnwatch configure --service posthog --plan free --budget 0\n burnwatch status\n`);\n}\n\nfunction cmdVersion(): void {\n try {\n const pkgPath = path.resolve(\n path.dirname(new URL(import.meta.url).pathname),\n \"../package.json\",\n );\n const pkg = JSON.parse(fs.readFileSync(pkgPath, \"utf-8\")) as {\n version: string;\n };\n console.log(`burnwatch v${pkg.version}`);\n } catch {\n console.log(\"burnwatch v0.1.0\");\n }\n}\n\n// --- Hook Registration ---\n\nfunction registerHooks(projectRoot: string): void {\n // Step 1: Copy hook scripts into .burnwatch/hooks/ for durability.\n // This avoids relying on ephemeral npx cache paths.\n const sourceHooksDir = path.resolve(\n path.dirname(new URL(import.meta.url).pathname),\n \"hooks\",\n );\n const localHooksDir = path.join(projectRoot, \".burnwatch\", \"hooks\");\n fs.mkdirSync(localHooksDir, { recursive: true });\n\n const hookFiles = [\n \"on-session-start.js\",\n \"on-prompt.js\",\n \"on-file-change.js\",\n \"on-stop.js\",\n ];\n\n for (const file of hookFiles) {\n const src = path.join(sourceHooksDir, file);\n const dest = path.join(localHooksDir, file);\n try {\n fs.copyFileSync(src, dest);\n // Also copy sourcemaps if they exist\n const mapSrc = src + \".map\";\n if (fs.existsSync(mapSrc)) {\n fs.copyFileSync(mapSrc, dest + \".map\");\n }\n } catch (err) {\n console.error(` Warning: Could not copy hook ${file}: ${err instanceof Error ? err.message : err}`);\n }\n }\n\n console.log(` Hook scripts copied to ${localHooksDir}`);\n\n // Step 2: Find or create .claude/settings.json — MERGE, never overwrite\n const claudeDir = path.join(projectRoot, \".claude\");\n const settingsPath = path.join(claudeDir, \"settings.json\");\n\n fs.mkdirSync(claudeDir, { recursive: true });\n\n // Read existing settings (preserve everything)\n let settings: Record<string, unknown> = {};\n try {\n const existing = fs.readFileSync(settingsPath, \"utf-8\");\n settings = JSON.parse(existing) as Record<string, unknown>;\n console.log(` Merging into existing ${settingsPath}`);\n } catch {\n // No existing settings — start fresh\n }\n\n // Ensure hooks object exists, preserve all existing hooks\n if (!settings[\"hooks\"] || typeof settings[\"hooks\"] !== \"object\") {\n settings[\"hooks\"] = {};\n }\n const hooks = settings[\"hooks\"] as Record<string, unknown[]>;\n\n // Use the local .burnwatch/hooks/ paths (durable, not ephemeral)\n const hooksDir = localHooksDir;\n\n // SessionStart hook\n if (!hooks[\"SessionStart\"]) hooks[\"SessionStart\"] = [];\n addHookIfMissing(hooks[\"SessionStart\"] as unknown[], \"SessionStart\", {\n matcher: \"startup|resume\",\n hooks: [\n {\n type: \"command\",\n command: `node \"${path.join(hooksDir, \"on-session-start.js\")}\"`,\n timeout: 15,\n },\n ],\n });\n\n // UserPromptSubmit hook\n if (!hooks[\"UserPromptSubmit\"]) hooks[\"UserPromptSubmit\"] = [];\n addHookIfMissing(\n hooks[\"UserPromptSubmit\"] as unknown[],\n \"UserPromptSubmit\",\n {\n hooks: [\n {\n type: \"command\",\n command: `node \"${path.join(hooksDir, \"on-prompt.js\")}\"`,\n timeout: 5,\n },\n ],\n },\n );\n\n // PostToolUse hook (Edit|Write only)\n if (!hooks[\"PostToolUse\"]) hooks[\"PostToolUse\"] = [];\n addHookIfMissing(hooks[\"PostToolUse\"] as unknown[], \"PostToolUse\", {\n matcher: \"Edit|Write\",\n hooks: [\n {\n type: \"command\",\n command: `node \"${path.join(hooksDir, \"on-file-change.js\")}\"`,\n timeout: 5,\n },\n ],\n });\n\n // Stop hook (async — don't block session end)\n if (!hooks[\"Stop\"]) hooks[\"Stop\"] = [];\n addHookIfMissing(hooks[\"Stop\"] as unknown[], \"Stop\", {\n hooks: [\n {\n type: \"command\",\n command: `node \"${path.join(hooksDir, \"on-stop.js\")}\"`,\n timeout: 15,\n async: true,\n },\n ],\n });\n\n settings[\"hooks\"] = hooks;\n fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + \"\\n\", \"utf-8\");\n console.log(` Hooks registered in ${settingsPath}`);\n}\n\nfunction addHookIfMissing(\n hookArray: unknown[],\n _eventName: string,\n hookConfig: unknown,\n): void {\n // Check if burnwatch hook is already registered\n const existing = hookArray.some((h) => {\n const hook = h as { hooks?: Array<{ command?: string }> };\n return hook.hooks?.some((inner) => inner.command?.includes(\"burnwatch\"));\n });\n\n if (!existing) {\n hookArray.push(hookConfig);\n }\n}\n\n// --- Entry ---\n\nmain().catch((err) => {\n console.error(\"Error:\", err instanceof Error ? err.message : err);\n process.exit(1);\n});\n","import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport * as os from \"node:os\";\nimport type { TrackedService } from \"./types.js\";\n\n/**\n * Paths for burnwatch configuration and data.\n *\n * Hybrid model:\n * - Global config (API keys, service credentials): ~/.config/burnwatch/\n * - Project config (budgets, tracked services): .burnwatch/\n * - Project data (ledger, events, cache): .burnwatch/data/\n */\n\n/** Global config directory — stores API keys, never in project dirs. */\nexport function globalConfigDir(): string {\n const xdgConfig = process.env[\"XDG_CONFIG_HOME\"];\n if (xdgConfig) return path.join(xdgConfig, \"burnwatch\");\n return path.join(os.homedir(), \".config\", \"burnwatch\");\n}\n\n/** Project config directory — stores budgets, tracked services. */\nexport function projectConfigDir(projectRoot?: string): string {\n const root = projectRoot ?? process.cwd();\n return path.join(root, \".burnwatch\");\n}\n\n/** Project data directory — stores ledger, events, cache. */\nexport function projectDataDir(projectRoot?: string): string {\n return path.join(projectConfigDir(projectRoot), \"data\");\n}\n\n// --- Global config (API keys) ---\n\nexport interface GlobalConfig {\n services: Record<\n string,\n {\n apiKey?: string;\n token?: string;\n orgId?: string;\n }\n >;\n}\n\nexport function readGlobalConfig(): GlobalConfig {\n const configPath = path.join(globalConfigDir(), \"config.json\");\n try {\n const raw = fs.readFileSync(configPath, \"utf-8\");\n return JSON.parse(raw) as GlobalConfig;\n } catch {\n return { services: {} };\n }\n}\n\nexport function writeGlobalConfig(config: GlobalConfig): void {\n const dir = globalConfigDir();\n fs.mkdirSync(dir, { recursive: true });\n const configPath = path.join(dir, \"config.json\");\n fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + \"\\n\", \"utf-8\");\n // Restrict permissions — this file contains API keys\n fs.chmodSync(configPath, 0o600);\n}\n\n// --- Project config (budgets, tracked services) ---\n\nexport interface ProjectConfig {\n projectName: string;\n services: Record<string, TrackedService>;\n createdAt: string;\n updatedAt: string;\n}\n\nexport function readProjectConfig(projectRoot?: string): ProjectConfig | null {\n const configPath = path.join(projectConfigDir(projectRoot), \"config.json\");\n try {\n const raw = fs.readFileSync(configPath, \"utf-8\");\n return JSON.parse(raw) as ProjectConfig;\n } catch {\n return null;\n }\n}\n\nexport function writeProjectConfig(\n config: ProjectConfig,\n projectRoot?: string,\n): void {\n const dir = projectConfigDir(projectRoot);\n fs.mkdirSync(dir, { recursive: true });\n config.updatedAt = new Date().toISOString();\n const configPath = path.join(dir, \"config.json\");\n fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + \"\\n\", \"utf-8\");\n}\n\n/** Ensure all project directories exist. */\nexport function ensureProjectDirs(projectRoot?: string): void {\n const dirs = [\n projectConfigDir(projectRoot),\n projectDataDir(projectRoot),\n path.join(projectDataDir(projectRoot), \"cache\"),\n path.join(projectDataDir(projectRoot), \"snapshots\"),\n ];\n for (const dir of dirs) {\n fs.mkdirSync(dir, { recursive: true });\n }\n}\n\n/** Check if burnwatch is initialized in the given project. */\nexport function isInitialized(projectRoot?: string): boolean {\n return readProjectConfig(projectRoot) !== null;\n}\n","import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport { loadRegistry } from \"../core/registry.js\";\nimport type { ServiceDefinition, DetectionSource } from \"../core/types.js\";\n\nexport interface DetectionResult {\n service: ServiceDefinition;\n sources: DetectionSource[];\n details: string[];\n}\n\n/**\n * Run all detection surfaces against the current project.\n * Returns services detected via any combination of:\n * - package.json dependencies (recursive — finds monorepo subdirectories)\n * - environment variable patterns (process.env + .env* files recursive)\n * - import statement scanning (recursive from project root)\n * - (prompt mention scanning is handled separately in hooks)\n */\nexport function detectServices(projectRoot: string): DetectionResult[] {\n const registry = loadRegistry(projectRoot);\n const results = new Map<string, DetectionResult>();\n\n // Surface 1: Package manifest scanning (recursive — finds all package.json files)\n const pkgDeps = scanAllPackageJsons(projectRoot);\n for (const [serviceId, service] of registry) {\n const matchedPkgs = service.packageNames.filter((pkg) =>\n pkgDeps.has(pkg),\n );\n if (matchedPkgs.length > 0) {\n getOrCreate(results, serviceId, service).sources.push(\"package_json\");\n getOrCreate(results, serviceId, service).details.push(\n `package.json: ${matchedPkgs.join(\", \")}`,\n );\n }\n }\n\n // Surface 2: Environment variable pattern matching\n // Check both process.env AND .env* files in the project tree\n const envVars = collectEnvVars(projectRoot);\n for (const [serviceId, service] of registry) {\n const matchedEnvs = service.envPatterns.filter((pattern) =>\n envVars.has(pattern),\n );\n if (matchedEnvs.length > 0) {\n getOrCreate(results, serviceId, service).sources.push(\"env_var\");\n getOrCreate(results, serviceId, service).details.push(\n `env vars: ${matchedEnvs.join(\", \")}`,\n );\n }\n }\n\n // Surface 3: Import statement analysis (recursive from project root)\n const importHits = scanImports(projectRoot);\n for (const [serviceId, service] of registry) {\n const matchedImports = service.importPatterns.filter((pattern) =>\n importHits.has(pattern),\n );\n if (matchedImports.length > 0) {\n if (\n !getOrCreate(results, serviceId, service).sources.includes(\n \"import_scan\",\n )\n ) {\n getOrCreate(results, serviceId, service).sources.push(\"import_scan\");\n getOrCreate(results, serviceId, service).details.push(\n `imports: ${matchedImports.join(\", \")}`,\n );\n }\n }\n }\n\n return Array.from(results.values());\n}\n\n/**\n * Detect services mentioned in a prompt string.\n * Used by the UserPromptSubmit hook.\n */\nexport function detectMentions(\n prompt: string,\n projectRoot?: string,\n): DetectionResult[] {\n const registry = loadRegistry(projectRoot);\n const results: DetectionResult[] = [];\n const promptLower = prompt.toLowerCase();\n\n for (const [, service] of registry) {\n const matched = service.mentionKeywords.some((keyword) =>\n promptLower.includes(keyword.toLowerCase()),\n );\n if (matched) {\n results.push({\n service,\n sources: [\"prompt_mention\"],\n details: [`mentioned in prompt`],\n });\n }\n }\n\n return results;\n}\n\n/**\n * Detect new services introduced in a file change.\n * Used by the PostToolUse hook for Write/Edit events.\n */\nexport function detectInFileChange(\n filePath: string,\n content: string,\n projectRoot?: string,\n): DetectionResult[] {\n const registry = loadRegistry(projectRoot);\n const results: DetectionResult[] = [];\n const fileName = path.basename(filePath);\n\n // Check if it's a package.json change\n if (fileName === \"package.json\") {\n try {\n const pkg = JSON.parse(content) as {\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n };\n const allDeps = new Set([\n ...Object.keys(pkg.dependencies ?? {}),\n ...Object.keys(pkg.devDependencies ?? {}),\n ]);\n\n for (const [, service] of registry) {\n const matched = service.packageNames.filter((p) => allDeps.has(p));\n if (matched.length > 0) {\n results.push({\n service,\n sources: [\"package_json\"],\n details: [`new dependency: ${matched.join(\", \")}`],\n });\n }\n }\n } catch {\n // Not valid JSON, skip\n }\n return results;\n }\n\n // Check if it's an env file change\n if (fileName.startsWith(\".env\")) {\n const envKeys = content\n .split(\"\\n\")\n .filter((line) => line.includes(\"=\") && !line.startsWith(\"#\"))\n .map((line) => line.split(\"=\")[0]!.trim());\n\n for (const [, service] of registry) {\n const matched = service.envPatterns.filter((p) => envKeys.includes(p));\n if (matched.length > 0) {\n results.push({\n service,\n sources: [\"env_var\"],\n details: [`new env var: ${matched.join(\", \")}`],\n });\n }\n }\n return results;\n }\n\n // Check for import statements in source files\n if (/\\.(ts|tsx|js|jsx|mjs|cjs)$/.test(filePath)) {\n for (const [, service] of registry) {\n const matched = service.importPatterns.filter(\n (pattern) =>\n content.includes(`from \"${pattern}`) ||\n content.includes(`from '${pattern}`) ||\n content.includes(`require(\"${pattern}`) ||\n content.includes(`require('${pattern}`),\n );\n if (matched.length > 0) {\n results.push({\n service,\n sources: [\"import_scan\"],\n details: [`import added: ${matched.join(\", \")}`],\n });\n }\n }\n }\n\n return results;\n}\n\n// --- Helpers ---\n\nfunction getOrCreate(\n map: Map<string, DetectionResult>,\n serviceId: string,\n service: ServiceDefinition,\n): DetectionResult {\n let result = map.get(serviceId);\n if (!result) {\n result = { service, sources: [], details: [] };\n map.set(serviceId, result);\n }\n return result;\n}\n\n/**\n * Recursively find and scan ALL package.json files in the project tree.\n * Handles monorepos where dependencies live in subdirectories.\n */\nfunction scanAllPackageJsons(projectRoot: string): Set<string> {\n const deps = new Set<string>();\n const pkgFiles = findFiles(projectRoot, \"package.json\", 4);\n\n for (const pkgPath of pkgFiles) {\n try {\n const raw = fs.readFileSync(pkgPath, \"utf-8\");\n const pkg = JSON.parse(raw) as {\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n };\n for (const name of Object.keys(pkg.dependencies ?? {})) deps.add(name);\n for (const name of Object.keys(pkg.devDependencies ?? {})) deps.add(name);\n } catch {\n // Skip malformed package.json\n }\n }\n\n return deps;\n}\n\n/**\n * Collect environment variable names from both process.env\n * and all .env* files found recursively in the project tree.\n */\nfunction collectEnvVars(projectRoot: string): Set<string> {\n const envVars = new Set(Object.keys(process.env));\n\n // Find all .env* files in the project tree\n const envFiles = findEnvFiles(projectRoot, 3);\n\n for (const envFile of envFiles) {\n try {\n const content = fs.readFileSync(envFile, \"utf-8\");\n const keys = content\n .split(\"\\n\")\n .filter((line) => line.includes(\"=\") && !line.startsWith(\"#\"))\n .map((line) => line.split(\"=\")[0]!.trim())\n .filter(Boolean);\n\n for (const key of keys) {\n envVars.add(key);\n }\n } catch {\n // Skip unreadable files\n }\n }\n\n return envVars;\n}\n\n/**\n * Find all .env* files recursively (but not in node_modules, .git, dist, etc.)\n */\nfunction findEnvFiles(dir: string, maxDepth: number): string[] {\n const results: string[] = [];\n if (maxDepth <= 0) return results;\n\n try {\n const entries = fs.readdirSync(dir, { withFileTypes: true });\n for (const entry of entries) {\n if (entry.name === \"node_modules\" || entry.name === \".git\" || entry.name === \"dist\") continue;\n const fullPath = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n results.push(...findEnvFiles(fullPath, maxDepth - 1));\n } else if (entry.name.startsWith(\".env\")) {\n results.push(fullPath);\n }\n }\n } catch {\n // Skip unreadable directories\n }\n\n return results;\n}\n\n/**\n * Find files with a specific name recursively.\n * Used to find package.json files across monorepo subdirectories.\n */\nfunction findFiles(dir: string, fileName: string, maxDepth: number): string[] {\n const results: string[] = [];\n if (maxDepth <= 0) return results;\n\n try {\n const entries = fs.readdirSync(dir, { withFileTypes: true });\n for (const entry of entries) {\n if (entry.name === \"node_modules\" || entry.name === \".git\" || entry.name === \"dist\") continue;\n const fullPath = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n results.push(...findFiles(fullPath, fileName, maxDepth - 1));\n } else if (entry.name === fileName) {\n results.push(fullPath);\n }\n }\n } catch {\n // Skip unreadable directories\n }\n\n return results;\n}\n\n/**\n * Lightweight import scanning.\n * Recursively scans the project for import/require statements.\n * Looks in src/, app/, lib/, pages/, and any other code directories.\n * Does NOT do a full AST parse — just string matching.\n */\nfunction scanImports(projectRoot: string): Set<string> {\n const imports = new Set<string>();\n\n // Scan common code directories + the root itself for source files\n const codeDirs = [\"src\", \"app\", \"lib\", \"pages\", \"components\", \"utils\", \"services\", \"hooks\"];\n const dirsToScan: string[] = [];\n\n for (const dir of codeDirs) {\n const fullPath = path.join(projectRoot, dir);\n if (fs.existsSync(fullPath)) {\n dirsToScan.push(fullPath);\n }\n }\n\n // Also check subdirectories (monorepo support)\n try {\n const entries = fs.readdirSync(projectRoot, { withFileTypes: true });\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n if (entry.name === \"node_modules\" || entry.name === \".git\" || entry.name === \"dist\" || entry.name.startsWith(\".\")) continue;\n\n // Check if this subdirectory has its own package.json (monorepo package)\n const subPkgPath = path.join(projectRoot, entry.name, \"package.json\");\n if (fs.existsSync(subPkgPath)) {\n // Scan this subpackage's code directories\n for (const dir of codeDirs) {\n const fullPath = path.join(projectRoot, entry.name, dir);\n if (fs.existsSync(fullPath)) {\n dirsToScan.push(fullPath);\n }\n }\n }\n }\n } catch {\n // Skip if root is unreadable\n }\n\n for (const dir of dirsToScan) {\n const files = walkDir(dir, /\\.(ts|tsx|js|jsx|mjs|cjs)$/);\n for (const file of files) {\n try {\n const content = fs.readFileSync(file, \"utf-8\");\n // Match: import ... from \"package\" or require(\"package\")\n const importRegex =\n /(?:from\\s+[\"']|require\\s*\\(\\s*[\"'])([^./][^\"']*?)(?:[\"'])/g;\n let match: RegExpExecArray | null;\n while ((match = importRegex.exec(content)) !== null) {\n const pkg = match[1];\n if (pkg) {\n // Normalize scoped packages: @scope/pkg/subpath -> @scope/pkg\n const parts = pkg.split(\"/\");\n if (parts[0]?.startsWith(\"@\") && parts.length >= 2) {\n imports.add(`${parts[0]}/${parts[1]}`);\n } else if (parts[0]) {\n imports.add(parts[0]);\n }\n }\n }\n } catch {\n // Skip unreadable files\n }\n }\n }\n\n return imports;\n}\n\n/** Recursively walk a directory, returning files matching the pattern. */\nfunction walkDir(dir: string, pattern: RegExp, maxDepth = 5): string[] {\n const results: string[] = [];\n if (maxDepth <= 0) return results;\n\n try {\n const entries = fs.readdirSync(dir, { withFileTypes: true });\n for (const entry of entries) {\n if (entry.name.startsWith(\".\") || entry.name === \"node_modules\") continue;\n const fullPath = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n results.push(...walkDir(fullPath, pattern, maxDepth - 1));\n } else if (pattern.test(entry.name)) {\n results.push(fullPath);\n }\n }\n } catch {\n // Skip unreadable directories\n }\n\n return results;\n}\n","import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport * as url from \"node:url\";\nimport type { ServiceDefinition } from \"./types.js\";\n\nconst __dirname = path.dirname(url.fileURLToPath(import.meta.url));\n\ninterface RegistryFile {\n version: string;\n lastUpdated: string;\n services: Record<string, ServiceDefinition>;\n}\n\nlet cachedRegistry: Map<string, ServiceDefinition> | null = null;\n\n/**\n * Load the service registry.\n * Checks project-local override first, then falls back to bundled registry.\n */\nexport function loadRegistry(projectRoot?: string): Map<string, ServiceDefinition> {\n if (cachedRegistry) return cachedRegistry;\n\n const registry = new Map<string, ServiceDefinition>();\n\n // Load bundled registry (shipped with package)\n // Try multiple possible locations — depends on whether running from src/ or dist/\n const candidates = [\n path.resolve(__dirname, \"../../registry.json\"), // from src/core/\n path.resolve(__dirname, \"../registry.json\"), // from dist/\n ];\n for (const candidate of candidates) {\n if (fs.existsSync(candidate)) {\n loadRegistryFile(candidate, registry);\n break;\n }\n }\n\n // Load project-local override (if exists)\n if (projectRoot) {\n const localPath = path.join(projectRoot, \".burnwatch\", \"registry.json\");\n if (fs.existsSync(localPath)) {\n loadRegistryFile(localPath, registry);\n }\n }\n\n cachedRegistry = registry;\n return registry;\n}\n\nfunction loadRegistryFile(\n filePath: string,\n registry: Map<string, ServiceDefinition>,\n): void {\n try {\n const raw = fs.readFileSync(filePath, \"utf-8\");\n const data = JSON.parse(raw) as RegistryFile;\n for (const [id, service] of Object.entries(data.services)) {\n registry.set(id, { ...service, id });\n }\n } catch {\n // Silently skip missing or malformed registry files\n }\n}\n\n/** Clear the cached registry (for testing). */\nexport function clearRegistryCache(): void {\n cachedRegistry = null;\n}\n\n/** Get a single service definition by ID. */\nexport function getService(\n id: string,\n projectRoot?: string,\n): ServiceDefinition | undefined {\n return loadRegistry(projectRoot).get(id);\n}\n\n/** Get all service definitions. */\nexport function getAllServices(\n projectRoot?: string,\n): ServiceDefinition[] {\n return Array.from(loadRegistry(projectRoot).values());\n}\n","import type { BillingConnector, BillingResult } from \"./base.js\";\nimport { fetchJson } from \"./base.js\";\n\n/**\n * Anthropic billing connector.\n * Uses the /v1/organizations/usage endpoint.\n * Requires an admin API key.\n */\nexport const anthropicConnector: BillingConnector = {\n serviceId: \"anthropic\",\n\n async fetchSpend(apiKey: string): Promise<BillingResult> {\n // Get current month date range\n const now = new Date();\n const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);\n const startDate = startOfMonth.toISOString().split(\"T\")[0]!;\n const endDate = now.toISOString().split(\"T\")[0]!;\n\n const url = `https://api.anthropic.com/v1/organizations/usage?start_date=${startDate}&end_date=${endDate}`;\n\n const result = await fetchJson<{\n data?: Array<{ total_cost_usd?: number; spend?: number }>;\n total_cost_usd?: number;\n }>(url, {\n headers: {\n \"x-api-key\": apiKey,\n \"anthropic-version\": \"2023-06-01\",\n },\n });\n\n if (!result.ok || !result.data) {\n return {\n serviceId: \"anthropic\",\n spend: 0,\n isEstimate: true,\n tier: \"est\",\n error: result.error ?? \"Failed to fetch Anthropic usage\",\n };\n }\n\n // Sum up usage across the period\n let totalSpend = 0;\n if (result.data.total_cost_usd !== undefined) {\n totalSpend = result.data.total_cost_usd;\n } else if (result.data.data) {\n totalSpend = result.data.data.reduce(\n (sum, entry) => sum + (entry.total_cost_usd ?? entry.spend ?? 0),\n 0,\n );\n }\n\n return {\n serviceId: \"anthropic\",\n spend: totalSpend,\n isEstimate: false,\n tier: \"live\",\n raw: result.data as unknown as Record<string, unknown>,\n };\n },\n};\n","import type { BillingConnector, BillingResult } from \"./base.js\";\nimport { fetchJson } from \"./base.js\";\n\n/**\n * OpenAI billing connector.\n * Uses the /v1/organization/costs endpoint.\n * Requires an organization-level API key.\n */\nexport const openaiConnector: BillingConnector = {\n serviceId: \"openai\",\n\n async fetchSpend(apiKey: string): Promise<BillingResult> {\n const now = new Date();\n const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);\n // OpenAI uses Unix timestamps\n const startTime = Math.floor(startOfMonth.getTime() / 1000);\n\n const url = `https://api.openai.com/v1/organization/costs?start_time=${startTime}`;\n\n const result = await fetchJson<{\n data?: Array<{\n results?: Array<{\n amount?: { value?: number };\n }>;\n }>;\n object?: string;\n }>(url, {\n headers: {\n Authorization: `Bearer ${apiKey}`,\n },\n });\n\n if (!result.ok || !result.data) {\n return {\n serviceId: \"openai\",\n spend: 0,\n isEstimate: true,\n tier: \"est\",\n error: result.error ?? \"Failed to fetch OpenAI usage\",\n };\n }\n\n // Sum all cost buckets\n let totalSpend = 0;\n if (result.data.data) {\n for (const bucket of result.data.data) {\n if (bucket.results) {\n for (const r of bucket.results) {\n totalSpend += r.amount?.value ?? 0;\n }\n }\n }\n }\n\n // OpenAI returns costs in cents, convert to dollars\n totalSpend = totalSpend / 100;\n\n return {\n serviceId: \"openai\",\n spend: totalSpend,\n isEstimate: false,\n tier: \"live\",\n raw: result.data as unknown as Record<string, unknown>,\n };\n },\n};\n","import type { BillingConnector, BillingResult } from \"./base.js\";\nimport { fetchJson } from \"./base.js\";\n\n/**\n * Vercel billing connector.\n * Uses the Vercel billing API.\n * Requires a Vercel token (personal or team-scoped).\n */\nexport const vercelConnector: BillingConnector = {\n serviceId: \"vercel\",\n\n async fetchSpend(\n token: string,\n options?: Record<string, string>,\n ): Promise<BillingResult> {\n const teamId = options?.[\"teamId\"] ?? \"\";\n const teamParam = teamId ? `?teamId=${teamId}` : \"\";\n\n // Fetch current billing period usage\n const url = `https://api.vercel.com/v2/usage${teamParam}`;\n\n const result = await fetchJson<{\n usage?: {\n total?: number;\n bandwidth?: { total?: number };\n serverlessFunctionExecution?: { total?: number };\n edgeFunctionExecution?: { total?: number };\n imageOptimization?: { total?: number };\n };\n billing?: {\n plan?: string;\n period?: { start?: string; end?: string };\n invoiceItems?: Array<{ amount?: number }>;\n };\n }>(url, {\n headers: {\n Authorization: `Bearer ${token}`,\n },\n });\n\n if (!result.ok || !result.data) {\n return {\n serviceId: \"vercel\",\n spend: 0,\n isEstimate: true,\n tier: \"est\",\n error: result.error ?? \"Failed to fetch Vercel usage\",\n };\n }\n\n // Sum up usage costs\n let totalSpend = 0;\n if (result.data.usage?.total !== undefined) {\n totalSpend = result.data.usage.total;\n } else if (result.data.billing?.invoiceItems) {\n totalSpend = result.data.billing.invoiceItems.reduce(\n (sum, item) => sum + (item.amount ?? 0),\n 0,\n );\n }\n\n return {\n serviceId: \"vercel\",\n spend: totalSpend,\n isEstimate: false,\n tier: \"live\",\n raw: result.data as unknown as Record<string, unknown>,\n };\n },\n};\n","import type { BillingConnector, BillingResult } from \"./base.js\";\nimport { fetchJson } from \"./base.js\";\n\n/**\n * Scrapfly billing connector.\n * Uses the /account endpoint which returns credits used/remaining.\n * Works with the standard API key — no special billing key needed.\n */\nexport const scrapflyConnector: BillingConnector = {\n serviceId: \"scrapfly\",\n\n async fetchSpend(apiKey: string): Promise<BillingResult> {\n const url = `https://api.scrapfly.io/account?key=${apiKey}`;\n\n const result = await fetchJson<{\n subscription?: {\n usage?: {\n scrape?: { used?: number; allowed?: number };\n };\n };\n account?: {\n credits_used?: number;\n credits_total?: number;\n };\n }>(url);\n\n if (!result.ok || !result.data) {\n return {\n serviceId: \"scrapfly\",\n spend: 0,\n isEstimate: true,\n tier: \"est\",\n error: result.error ?? \"Failed to fetch Scrapfly account\",\n };\n }\n\n // Extract credits used from the response\n let creditsUsed = 0;\n let creditsTotal = 0;\n\n if (result.data.subscription?.usage?.scrape) {\n creditsUsed = result.data.subscription.usage.scrape.used ?? 0;\n creditsTotal = result.data.subscription.usage.scrape.allowed ?? 0;\n } else if (result.data.account) {\n creditsUsed = result.data.account.credits_used ?? 0;\n creditsTotal = result.data.account.credits_total ?? 0;\n }\n\n // Convert credits to USD at registry rate\n const creditRate = 0.00015; // $0.00015 per credit\n const spend = creditsUsed * creditRate;\n\n return {\n serviceId: \"scrapfly\",\n spend,\n isEstimate: false,\n tier: \"live\",\n unitsUsed: creditsUsed,\n unitsTotal: creditsTotal,\n unitName: \"credits\",\n raw: {\n credits_used: creditsUsed,\n credits_total: creditsTotal,\n credit_rate: creditRate,\n ...(result.data as unknown as Record<string, unknown>),\n },\n };\n },\n};\n","import type { BillingConnector, BillingResult } from \"./base.js\";\nimport { anthropicConnector } from \"./anthropic.js\";\nimport { openaiConnector } from \"./openai.js\";\nimport { vercelConnector } from \"./vercel.js\";\nimport { scrapflyConnector } from \"./scrapfly.js\";\nimport type { TrackedService, ConfidenceTier } from \"../core/types.js\";\nimport { readGlobalConfig } from \"../core/config.js\";\nimport { getService } from \"../core/registry.js\";\n\n/** All available billing connectors, keyed by service ID. */\nconst connectors: Map<string, BillingConnector> = new Map([\n [\"anthropic\", anthropicConnector],\n [\"openai\", openaiConnector],\n [\"vercel\", vercelConnector],\n [\"scrapfly\", scrapflyConnector],\n]);\n\n/**\n * Poll spend for a single tracked service.\n * Returns the best available data based on connector availability and API keys.\n */\nexport async function pollService(\n tracked: TrackedService,\n): Promise<BillingResult> {\n const globalConfig = readGlobalConfig();\n const serviceConfig = globalConfig.services[tracked.serviceId];\n const connector = connectors.get(tracked.serviceId);\n const definition = getService(tracked.serviceId);\n\n // If we have a connector and an API key, try LIVE\n if (connector && serviceConfig?.apiKey) {\n try {\n const result = await connector.fetchSpend(\n serviceConfig.apiKey,\n serviceConfig as unknown as Record<string, string>,\n );\n if (!result.error) return result;\n // Fall through to lower tiers on error\n } catch {\n // Fall through\n }\n }\n\n // If user provided a plan cost, use CALC\n if (tracked.planCost !== undefined) {\n const now = new Date();\n const daysInMonth = new Date(\n now.getFullYear(),\n now.getMonth() + 1,\n 0,\n ).getDate();\n const dayOfMonth = now.getDate();\n const projectedSpend = (tracked.planCost / daysInMonth) * dayOfMonth;\n\n return {\n serviceId: tracked.serviceId,\n spend: projectedSpend,\n isEstimate: true,\n tier: \"calc\",\n };\n }\n\n // If service is in registry but we have no key and no plan cost\n if (definition) {\n let tier: ConfidenceTier;\n if (tracked.tierOverride) {\n tier = tracked.tierOverride;\n } else if (definition.apiTier === \"live\") {\n // Has a LIVE API but we don't have the key — mark as BLIND\n tier = \"blind\";\n } else {\n // EST, CALC, or BLIND — use the registry's declared tier\n tier = definition.apiTier;\n }\n\n return {\n serviceId: tracked.serviceId,\n spend: 0,\n isEstimate: tier !== \"live\",\n tier,\n error: tier === \"blind\" ? \"No API key configured\" : undefined,\n };\n }\n\n // Completely unknown service\n return {\n serviceId: tracked.serviceId,\n spend: 0,\n isEstimate: true,\n tier: \"blind\",\n error: \"Unknown service — not in registry\",\n };\n}\n\n/**\n * Poll all tracked services concurrently.\n * Returns results in the same order as input.\n */\nexport async function pollAllServices(\n services: TrackedService[],\n): Promise<BillingResult[]> {\n return Promise.all(services.map(pollService));\n}\n\nexport { type BillingConnector, type BillingResult } from \"./base.js\";\n","/**\n * Confidence tiers for spend tracking.\n *\n * LIVE — Real billing API data\n * CALC — Fixed monthly cost, user-entered\n * EST — Estimated from usage signals + pricing formula\n * BLIND — Detected in project, no tracking configured\n */\nexport type ConfidenceTier = \"live\" | \"calc\" | \"est\" | \"blind\" | \"excluded\";\n\nexport const CONFIDENCE_BADGES: Record<ConfidenceTier, string> = {\n live: \"✅ LIVE\",\n calc: \"🟡 CALC\",\n est: \"🟠 EST\",\n blind: \"🔴 BLIND\",\n excluded: \"⬚ SKIP\",\n};\n\n/** How a service charges — determines tracking strategy. */\nexport type BillingModel =\n | \"token_usage\" // Per-token (Anthropic, OpenAI, Gemini)\n | \"credit_pool\" // Fixed credit bucket (Scrapfly)\n | \"per_unit\" // Per-email, per-session, per-command (Resend, Browserbase, Upstash)\n | \"percentage\" // Percentage of transaction (Stripe)\n | \"flat_monthly\" // Fixed monthly subscription (PostHog, Inngest free tier)\n | \"tiered\" // Free up to X, then jumps (PostHog, Supabase)\n | \"compute\" // Compute-time based (Vercel, AWS)\n | \"unknown\";\n\n/** How cost scales — helps the agent reason about future spend. */\nexport type ScalingShape =\n | \"linear\" // Each unit costs the same\n | \"linear_burndown\" // Fixed pool, each use depletes it\n | \"tiered_jump\" // Free until threshold, then expensive\n | \"percentage\" // Proportional to revenue/volume\n | \"fixed\" // Flat monthly, no scaling\n | \"unknown\";\n\n/** A plan tier option for a service in the registry. */\nexport interface PlanTier {\n /** Human-readable plan name */\n name: string;\n /** Plan type: usage (pay-as-you-go), flat (fixed monthly), exclude (don't track) */\n type: \"usage\" | \"flat\" | \"exclude\";\n /** Monthly base cost for flat plans */\n monthlyBase?: number;\n /** Suggested starting budget for usage plans (reasonable dev-stage default) */\n suggestedBudget?: number;\n /** Whether this plan requires an API key for tracking */\n requiresKey?: boolean;\n /** Whether this is the default/most common plan */\n default?: boolean;\n /** Included units for credit-pool plans (e.g., 1M credits on Scrapfly Pro) */\n includedUnits?: number;\n /** Unit name for credit-pool plans (e.g., \"credits\", \"sessions\") */\n unitName?: string;\n}\n\n/** Risk category for service grouping in interactive init. */\nexport type ServiceRiskCategory = \"llm\" | \"usage\" | \"infra\" | \"flat\";\n\n/** A service definition from the registry. */\nexport interface ServiceDefinition {\n /** Unique service identifier */\n id: string;\n /** Human-readable name */\n name: string;\n /** Package names in npm/pip that indicate this service */\n packageNames: string[];\n /** Env var patterns that indicate this service */\n envPatterns: string[];\n /** Import patterns to scan for (regex strings) */\n importPatterns: string[];\n /** Keywords that indicate mentions in prompts */\n mentionKeywords: string[];\n /** Billing model */\n billingModel: BillingModel;\n /** How cost scales */\n scalingShape: ScalingShape;\n /** What tier of tracking is available */\n apiTier: ConfidenceTier;\n /** Billing API endpoint, if available */\n apiEndpoint?: string;\n /** Pricing details */\n pricing?: {\n /** Human-readable formula */\n formula?: string;\n /** Rate per unit, if applicable */\n unitRate?: number;\n /** Unit name (token, credit, email, session, etc.) */\n unitName?: string;\n /** Monthly base cost, if flat */\n monthlyBase?: number;\n };\n /** Known gotchas that affect cost */\n gotchas?: string[];\n /** Alternative services (free or cheaper) */\n alternatives?: string[];\n /** Documentation URL */\n docsUrl?: string;\n /** Last time pricing was verified */\n lastVerified?: string;\n /** Notes about recent pricing changes */\n pricingNotes?: string;\n /** Available plan tiers for interactive init */\n plans?: PlanTier[];\n /** Whether the plan can be auto-detected from an API key */\n autoDetectPlan?: boolean;\n}\n\n/** A tracked service instance — a service definition + user config. */\nexport interface TrackedService {\n /** Service definition ID */\n serviceId: string;\n /** How this service was detected */\n detectedVia: DetectionSource[];\n /** User-configured monthly budget */\n budget?: number;\n /** Whether the user has provided an API/billing key */\n hasApiKey: boolean;\n /** Override confidence tier (e.g., user provided billing key upgrades to LIVE) */\n tierOverride?: ConfidenceTier;\n /** User-entered monthly plan cost (for CALC tier) */\n planCost?: number;\n /** When this service was first detected */\n firstDetected: string;\n /** Explicitly excluded from tracking by user */\n excluded?: boolean;\n /** Plan name selected during interactive init */\n planName?: string;\n /** For credit-pool services: the unit allowance included in the plan */\n allowance?: {\n /** Total units included in the plan (e.g., 1000000 credits) */\n included: number;\n /** Unit name (e.g., \"credits\", \"sessions\", \"commands\") */\n unitName: string;\n };\n}\n\nexport type DetectionSource =\n | \"package_json\"\n | \"env_var\"\n | \"import_scan\"\n | \"prompt_mention\"\n | \"git_diff\"\n | \"manual\";\n\n/** A spend snapshot for a single service at a point in time. */\nexport interface SpendSnapshot {\n serviceId: string;\n /** Current period spend (or estimate) */\n spend: number;\n /** Is the spend figure exact or estimated? */\n isEstimate: boolean;\n /** Confidence tier for this reading */\n tier: ConfidenceTier;\n /** Budget allocated */\n budget?: number;\n /** Percentage of budget consumed */\n budgetPercent?: number;\n /** Budget status */\n status: \"healthy\" | \"caution\" | \"over\" | \"unknown\";\n /** Human-readable status label */\n statusLabel: string;\n /** Raw data from billing API, if available */\n raw?: Record<string, unknown>;\n /** Timestamp of this snapshot */\n timestamp: string;\n /** For credit-pool services: unit consumption tracking */\n allowance?: {\n /** Units consumed this period */\n used: number;\n /** Total units included in plan */\n included: number;\n /** Unit name (e.g., \"credits\") */\n unitName: string;\n /** Percentage of allowance consumed */\n percent: number;\n };\n}\n\n/** The full spend brief, injected at session start. */\nexport interface SpendBrief {\n projectName: string;\n generatedAt: string;\n period: string;\n services: SpendSnapshot[];\n totalSpend: number;\n totalIsEstimate: boolean;\n estimateMargin: number;\n untrackedCount: number;\n alerts: SpendAlert[];\n}\n\nexport interface SpendAlert {\n serviceId: string;\n type: \"over_budget\" | \"near_budget\" | \"new_service\" | \"stale_data\" | \"blind_service\";\n message: string;\n severity: \"warning\" | \"critical\" | \"info\";\n}\n\n/** Ledger entry — one row in spend-ledger.md */\nexport interface LedgerEntry {\n serviceId: string;\n serviceName: string;\n spend: number;\n isEstimate: boolean;\n tier: ConfidenceTier;\n budget?: number;\n statusLabel: string;\n}\n\n/** Event logged to events.jsonl */\nexport interface SpendEvent {\n timestamp: string;\n sessionId: string;\n type:\n | \"session_start\"\n | \"session_end\"\n | \"service_detected\"\n | \"service_mentioned\"\n | \"spend_polled\"\n | \"budget_alert\"\n | \"ledger_written\"\n | \"cost_impact\";\n data: Record<string, unknown>;\n}\n\n/** A cost impact estimate for a file change. */\nexport interface CostImpact {\n serviceId: string;\n serviceName: string;\n filePath: string;\n /** Number of SDK call sites found */\n callCount: number;\n /** Detected multipliers (loops, .map(), etc.) */\n multipliers: string[];\n /** Effective multiplier applied to call count */\n multiplierFactor: number;\n /** Estimated monthly invocations */\n monthlyInvocations: number;\n /** Low estimate monthly cost */\n costLow: number;\n /** High estimate monthly cost */\n costHigh: number;\n /** Gotcha-based cost range explanation */\n rangeExplanation?: string;\n}\n\n/**\n * Hook input — the JSON received via stdin from Claude Code.\n * Subset of fields we care about.\n */\nexport interface HookInput {\n session_id: string;\n transcript_path?: string;\n cwd: string;\n hook_event_name: string;\n // SessionStart\n source?: string;\n // UserPromptSubmit\n prompt?: string;\n // PostToolUse\n tool_name?: string;\n tool_input?: {\n file_path?: string;\n command?: string;\n content?: string;\n old_string?: string;\n new_string?: string;\n };\n}\n\n/**\n * Hook output — the JSON we write to stdout for Claude Code.\n */\nexport interface HookOutput {\n hookSpecificOutput?: {\n hookEventName: string;\n additionalContext?: string;\n };\n}\n","import type {\n SpendBrief,\n SpendSnapshot,\n SpendAlert,\n ConfidenceTier,\n} from \"./types.js\";\nimport { CONFIDENCE_BADGES } from \"./types.js\";\n\n/**\n * Format a spend brief as a text block for injection into Claude's context.\n */\nexport function formatBrief(brief: SpendBrief): string {\n const lines: string[] = [];\n const width = 62;\n const hrDouble = \"═\".repeat(width);\n const hrSingle = \"─\".repeat(width - 4);\n\n lines.push(`╔${hrDouble}╗`);\n lines.push(\n `║ BURNWATCH — ${brief.projectName} — ${brief.period}`.padEnd(\n width + 1,\n ) + \"║\",\n );\n lines.push(`╠${hrDouble}╣`);\n\n // Header\n lines.push(\n formatRow(\"Service\", \"Spend\", \"Conf\", \"Budget\", \"Left\", width),\n );\n lines.push(`║ ${hrSingle} ║`);\n\n // Service rows\n for (const svc of brief.services) {\n const spendStr = svc.isEstimate\n ? `~$${svc.spend.toFixed(2)}`\n : `$${svc.spend.toFixed(2)}`;\n const badge = CONFIDENCE_BADGES[svc.tier];\n const budgetStr = svc.budget ? `$${svc.budget}` : \"—\";\n const leftStr = formatLeft(svc);\n\n lines.push(formatRow(svc.serviceId, spendStr, badge, budgetStr, leftStr, width));\n\n // Show allowance consumption for credit-pool services\n if (svc.allowance) {\n const usedStr = formatCompact(svc.allowance.used);\n const totalStr = formatCompact(svc.allowance.included);\n const pctStr = svc.allowance.percent.toFixed(0);\n const warn = svc.allowance.percent >= 75 ? \" ⚠️\" : \"\";\n lines.push(\n `║ ↳ ${usedStr}/${totalStr} ${svc.allowance.unitName} (${pctStr}%)${warn}`.padEnd(width + 1) + \"║\",\n );\n }\n }\n\n // Footer\n lines.push(`╠${hrDouble}╣`);\n const totalStr = brief.totalIsEstimate\n ? `~$${brief.totalSpend.toFixed(2)}`\n : `$${brief.totalSpend.toFixed(2)}`;\n const marginStr = brief.estimateMargin > 0\n ? ` Est margin: ±$${brief.estimateMargin.toFixed(0)}`\n : \"\";\n const untrackedStr =\n brief.untrackedCount > 0\n ? `Untracked: ${brief.untrackedCount} ⚠️`\n : `Untracked: 0 ✅`;\n\n lines.push(\n `║ TOTAL: ${totalStr} ${untrackedStr}${marginStr}`.padEnd(\n width + 1,\n ) + \"║\",\n );\n\n // Alerts\n for (const alert of brief.alerts) {\n const icon = alert.severity === \"critical\" ? \"🚨\" : \"⚠️\";\n lines.push(\n `║ ${icon} ${alert.message}`.padEnd(width + 1) + \"║\",\n );\n }\n\n lines.push(`╚${hrDouble}╝`);\n\n return lines.join(\"\\n\");\n}\n\n/**\n * Format a single-service spend card for injection on mention.\n */\nexport function formatSpendCard(snapshot: SpendSnapshot): string {\n const badge = CONFIDENCE_BADGES[snapshot.tier];\n const spendStr = snapshot.isEstimate\n ? `~$${snapshot.spend.toFixed(2)}`\n : `$${snapshot.spend.toFixed(2)}`;\n const budgetStr = snapshot.budget\n ? `Budget: $${snapshot.budget}`\n : \"No budget set\";\n const statusStr = snapshot.statusLabel;\n\n const lines = [\n `[BURNWATCH] ${snapshot.serviceId} — current period`,\n ` Spend: ${spendStr} | ${budgetStr} | ${statusStr}`,\n ` Confidence: ${badge}`,\n ];\n\n if (snapshot.status === \"over\" && snapshot.budgetPercent) {\n lines.push(\n ` ⚠️ ${snapshot.budgetPercent.toFixed(0)}% of budget consumed`,\n );\n }\n\n return lines.join(\"\\n\");\n}\n\n/**\n * Build a SpendBrief from snapshots and project config.\n */\nexport function buildBrief(\n projectName: string,\n snapshots: SpendSnapshot[],\n blindCount: number,\n): SpendBrief {\n const now = new Date();\n const period = now.toLocaleDateString(\"en-US\", {\n month: \"long\",\n year: \"numeric\",\n });\n\n let totalSpend = 0;\n let hasEstimates = false;\n let estimateMargin = 0;\n const alerts: SpendAlert[] = [];\n\n for (const snap of snapshots) {\n totalSpend += snap.spend;\n if (snap.isEstimate) {\n hasEstimates = true;\n estimateMargin += snap.spend * 0.15; // ±15% margin on estimates\n }\n\n if (snap.status === \"over\") {\n alerts.push({\n serviceId: snap.serviceId,\n type: \"over_budget\",\n message: `${snap.serviceId.toUpperCase()} ${snap.budgetPercent?.toFixed(0) ?? \"?\"}% OVER BUDGET — review before use`,\n severity: \"critical\",\n });\n } else if (snap.status === \"caution\" && snap.budgetPercent && snap.budgetPercent >= 80) {\n alerts.push({\n serviceId: snap.serviceId,\n type: \"near_budget\",\n message: `${snap.serviceId} at ${snap.budgetPercent.toFixed(0)}% of budget`,\n severity: \"warning\",\n });\n }\n }\n\n if (blindCount > 0) {\n alerts.push({\n serviceId: \"_blind\",\n type: \"blind_service\",\n message: `${blindCount} service${blindCount > 1 ? \"s\" : \"\"} detected but untracked - run 'burnwatch init' to configure`,\n severity: \"warning\",\n });\n }\n\n return {\n projectName,\n generatedAt: now.toISOString(),\n period,\n services: snapshots,\n totalSpend,\n totalIsEstimate: hasEstimates,\n estimateMargin,\n untrackedCount: blindCount,\n alerts,\n };\n}\n\n// --- Helpers ---\n\nfunction formatRow(\n service: string,\n spend: string,\n conf: string,\n budget: string,\n left: string,\n width: number,\n): string {\n const row = ` ${service.padEnd(14)} ${spend.padEnd(11)} ${conf.padEnd(7)} ${budget.padEnd(7)} ${left}`;\n return `║${row}`.padEnd(width + 1) + \"║\";\n}\n\nfunction formatLeft(snap: SpendSnapshot): string {\n if (!snap.budget) return \"—\";\n if (snap.status === \"over\") return \"⚠️ OVR\";\n if (snap.budgetPercent !== undefined) {\n const remaining = 100 - snap.budgetPercent;\n return `${remaining.toFixed(0)}%`;\n }\n return \"—\";\n}\n\n/**\n * Build a SpendSnapshot from tracked service data.\n */\nexport function buildSnapshot(\n serviceId: string,\n tier: ConfidenceTier,\n spend: number,\n budget?: number,\n allowanceData?: { used: number; included: number; unitName: string },\n): SpendSnapshot {\n const isEstimate = tier === \"est\" || tier === \"calc\";\n const budgetPercent = budget ? (spend / budget) * 100 : undefined;\n\n let status: SpendSnapshot[\"status\"] = \"unknown\";\n let statusLabel = \"no budget\";\n\n if (budget) {\n if (budgetPercent! > 100) {\n status = \"over\";\n statusLabel = `⚠️ ${budgetPercent!.toFixed(0)}% over`;\n } else if (budgetPercent! >= 75) {\n status = \"caution\";\n statusLabel = `${(100 - budgetPercent!).toFixed(0)}% — caution`;\n } else {\n status = \"healthy\";\n statusLabel = `${(100 - budgetPercent!).toFixed(0)}% — healthy`;\n }\n }\n\n // For credit-pool services, allowance consumption drives the status\n let allowance: SpendSnapshot[\"allowance\"] | undefined;\n if (allowanceData && allowanceData.included > 0) {\n const percent = (allowanceData.used / allowanceData.included) * 100;\n allowance = { ...allowanceData, percent };\n\n // Override status based on allowance consumption\n if (percent > 100) {\n status = \"over\";\n statusLabel = `⚠️ ${percent.toFixed(0)}% of ${formatCompact(allowanceData.included)} ${allowanceData.unitName} used`;\n } else if (percent >= 75) {\n status = \"caution\";\n statusLabel = `${formatCompact(allowanceData.included - allowanceData.used)} ${allowanceData.unitName} left — caution`;\n } else {\n status = \"healthy\";\n statusLabel = `${formatCompact(allowanceData.included - allowanceData.used)} ${allowanceData.unitName} left`;\n }\n } else if (tier === \"calc\" && budget) {\n statusLabel = `flat — on plan`;\n status = \"healthy\";\n }\n\n return {\n serviceId,\n spend,\n isEstimate,\n tier,\n budget,\n budgetPercent,\n status,\n statusLabel,\n timestamp: new Date().toISOString(),\n allowance,\n };\n}\n\nfunction formatCompact(n: number): string {\n if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(n % 1_000_000 === 0 ? 0 : 1)}M`;\n if (n >= 1_000) return `${(n / 1_000).toFixed(n % 1_000 === 0 ? 0 : 1)}K`;\n return String(n);\n}\n","import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type { SpendBrief, SpendEvent } from \"./types.js\";\nimport { CONFIDENCE_BADGES } from \"./types.js\";\nimport { projectConfigDir, projectDataDir } from \"./config.js\";\n\n/**\n * Write the spend ledger as a human-readable markdown file.\n * Designed to be git-committable and readable in 10 seconds.\n */\nexport function writeLedger(brief: SpendBrief, projectRoot?: string): void {\n const now = new Date();\n const lines: string[] = [];\n\n lines.push(`# Burnwatch Ledger — ${brief.projectName}`);\n lines.push(`Last updated: ${now.toISOString()}`);\n lines.push(\"\");\n lines.push(`## This Month (${brief.period})`);\n lines.push(\"\");\n lines.push(\"| Service | Spend | Conf | Budget | Status |\");\n lines.push(\"|---------|-------|------|--------|--------|\");\n\n for (const svc of brief.services) {\n const spendStr = svc.isEstimate\n ? `~$${svc.spend.toFixed(2)}`\n : `$${svc.spend.toFixed(2)}`;\n const badge = CONFIDENCE_BADGES[svc.tier];\n const budgetStr = svc.budget ? `$${svc.budget}` : \"—\";\n\n lines.push(\n `| ${svc.serviceId} | ${spendStr} | ${badge} | ${budgetStr} | ${svc.statusLabel} |`,\n );\n }\n\n // Add projected impact row if session impacts exist in alerts\n const impactAlert = brief.alerts.find(\n (a) => a.serviceId === \"_session_impact\",\n );\n if (impactAlert) {\n lines.push(\n `| _projected impact_ | — | 📈 EST | — | ${impactAlert.message} |`,\n );\n }\n\n lines.push(\"\");\n const totalStr = brief.totalIsEstimate\n ? `~$${brief.totalSpend.toFixed(2)}`\n : `$${brief.totalSpend.toFixed(2)}`;\n const marginStr =\n brief.estimateMargin > 0\n ? ` (±$${brief.estimateMargin.toFixed(0)} estimated margin)`\n : \"\";\n lines.push(`## TOTAL: ${totalStr}${marginStr}`);\n lines.push(`## Untracked services: ${brief.untrackedCount}`);\n lines.push(\"\");\n\n if (brief.alerts.length > 0) {\n lines.push(\"## Alerts\");\n for (const alert of brief.alerts) {\n const icon = alert.severity === \"critical\" ? \"🚨\" : \"⚠️\";\n lines.push(`- ${icon} ${alert.message}`);\n }\n lines.push(\"\");\n }\n\n const ledgerPath = path.join(\n projectConfigDir(projectRoot),\n \"spend-ledger.md\",\n );\n fs.mkdirSync(path.dirname(ledgerPath), { recursive: true });\n fs.writeFileSync(ledgerPath, lines.join(\"\\n\") + \"\\n\", \"utf-8\");\n}\n\n/**\n * Append an event to the append-only event log.\n */\nexport function logEvent(event: SpendEvent, projectRoot?: string): void {\n const logPath = path.join(projectDataDir(projectRoot), \"events.jsonl\");\n fs.mkdirSync(path.dirname(logPath), { recursive: true });\n fs.appendFileSync(logPath, JSON.stringify(event) + \"\\n\", \"utf-8\");\n}\n\n/**\n * Read recent events from the event log.\n */\nexport function readRecentEvents(\n count: number,\n projectRoot?: string,\n): SpendEvent[] {\n const logPath = path.join(projectDataDir(projectRoot), \"events.jsonl\");\n try {\n const raw = fs.readFileSync(logPath, \"utf-8\");\n const lines = raw.trim().split(\"\\n\").filter(Boolean);\n return lines\n .slice(-count)\n .map((line) => JSON.parse(line) as SpendEvent);\n } catch {\n return [];\n }\n}\n\n/**\n * Save a spend snapshot to the snapshots directory.\n * Used for delta computation across sessions.\n */\nexport function saveSnapshot(brief: SpendBrief, projectRoot?: string): void {\n const snapshotDir = path.join(projectDataDir(projectRoot), \"snapshots\");\n fs.mkdirSync(snapshotDir, { recursive: true });\n const filename = `snapshot-${new Date().toISOString().replace(/[:.]/g, \"-\")}.json`;\n fs.writeFileSync(\n path.join(snapshotDir, filename),\n JSON.stringify(brief, null, 2) + \"\\n\",\n \"utf-8\",\n );\n}\n\n/**\n * Read the most recent snapshot, if any.\n */\nexport function readLatestSnapshot(\n projectRoot?: string,\n): SpendBrief | null {\n const snapshotDir = path.join(projectDataDir(projectRoot), \"snapshots\");\n try {\n const files = fs\n .readdirSync(snapshotDir)\n .filter((f) => f.startsWith(\"snapshot-\") && f.endsWith(\".json\"))\n .sort()\n .reverse();\n\n if (files.length === 0) return null;\n\n const raw = fs.readFileSync(\n path.join(snapshotDir, files[0]!),\n \"utf-8\",\n );\n return JSON.parse(raw) as SpendBrief;\n } catch {\n return null;\n }\n}\n","/**\n * Interactive init flow for burnwatch.\n *\n * Conducts a per-service interview: detects what it can automatically\n * (existing API keys, env vars), asks for plan selection, collects\n * API keys for LIVE tracking, and ensures every service exits with\n * a budget. No skipping.\n */\n\nimport * as readline from \"node:readline\";\nimport type {\n ServiceDefinition,\n PlanTier,\n TrackedService,\n ServiceRiskCategory,\n} from \"./core/types.js\";\nimport type { DetectionResult } from \"./detection/detector.js\";\nimport { readGlobalConfig, writeGlobalConfig } from \"./core/config.js\";\nimport { probeService, hasProbe } from \"./probes.js\";\n\n/** Format large numbers with K/M suffixes */\nfunction formatUnits(n: number): string {\n if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(n % 1_000_000 === 0 ? 0 : 1)}M`;\n if (n >= 1_000) return `${(n / 1_000).toFixed(n % 1_000 === 0 ? 0 : 1)}K`;\n return String(n);\n}\n\n/** Risk categories in display order: LLMs first, then usage-based, infra, flat-rate */\nconst RISK_ORDER: ServiceRiskCategory[] = [\"llm\", \"usage\", \"infra\", \"flat\"];\n\nconst RISK_LABELS: Record<ServiceRiskCategory, string> = {\n llm: \"LLM / AI Services (highest variable cost)\",\n usage: \"Usage-Based Services\",\n infra: \"Infrastructure & Compute\",\n flat: \"Flat-Rate / Free Tier Services\",\n};\n\n/** Where to find API keys for LIVE-capable services */\nconst API_KEY_HINTS: Record<string, string> = {\n anthropic: \"Admin key: console.anthropic.com -> Settings -> Admin API Keys\",\n openai: \"Org key: platform.openai.com -> Settings -> API Keys\",\n vercel: \"Token: vercel.com/account/tokens\",\n supabase: \"Service role key: supabase.com/dashboard -> Settings -> API\",\n stripe: \"Secret key: dashboard.stripe.com -> Developers -> API Keys\",\n scrapfly: \"API key: scrapfly.io/dashboard\",\n};\n\n/** Map service IDs to risk categories */\nfunction classifyRisk(service: ServiceDefinition): ServiceRiskCategory {\n if (service.billingModel === \"token_usage\") return \"llm\";\n if (\n service.billingModel === \"credit_pool\" ||\n service.billingModel === \"percentage\" ||\n service.billingModel === \"per_unit\"\n )\n return \"usage\";\n if (service.billingModel === \"compute\") return \"infra\";\n return \"flat\";\n}\n\n/** Group detection results by risk category */\nfunction groupByRisk(\n detected: DetectionResult[],\n): Map<ServiceRiskCategory, DetectionResult[]> {\n const groups = new Map<ServiceRiskCategory, DetectionResult[]>();\n for (const cat of RISK_ORDER) {\n groups.set(cat, []);\n }\n\n for (const det of detected) {\n const cat = classifyRisk(det.service);\n groups.get(cat)!.push(det);\n }\n\n return groups;\n}\n\n/** Prompt the user with a question and return their answer */\nfunction ask(rl: readline.Interface, question: string): Promise<string> {\n return new Promise((resolve) => {\n rl.question(question, (answer) => {\n resolve(answer.trim());\n });\n });\n}\n\n/** Scan environment for API keys matching service env patterns */\nfunction findEnvKey(service: ServiceDefinition): string | undefined {\n for (const pattern of service.envPatterns) {\n const val = process.env[pattern];\n if (val && val.length > 0) return val;\n }\n return undefined;\n}\n\nexport interface InteractiveInitResult {\n services: Record<string, TrackedService>;\n}\n\n/**\n * Auto-configure all services without prompts.\n *\n * Applies the same logic as the interactive interview but picks\n * defaults automatically: default plan, env var keys, budget = plan cost.\n * Used when stdin is not a TTY (e.g., Claude Code, piped input).\n */\nexport async function autoConfigureServices(\n detected: DetectionResult[],\n): Promise<InteractiveInitResult> {\n const services: Record<string, TrackedService> = {};\n const groups = groupByRisk(detected);\n const globalConfig = readGlobalConfig();\n\n console.log(\n `\\n Found ${detected.length} paid service${detected.length !== 1 ? \"s\" : \"\"}. Auto-configuring with defaults.\\n`,\n );\n console.log(\" Run 'burnwatch init' from your terminal for interactive setup.\\n\");\n\n for (const category of RISK_ORDER) {\n const group = groups.get(category)!;\n if (group.length === 0) continue;\n\n console.log(` ${RISK_LABELS[category]}`);\n\n for (const det of group) {\n const service = det.service;\n const plans = service.plans ?? [];\n const defaultPlan = plans.find((p) => p.default) ?? plans[0];\n\n const tracked: TrackedService = {\n serviceId: service.id,\n detectedVia: det.sources,\n hasApiKey: false,\n firstDetected: new Date().toISOString(),\n budget: 0,\n };\n\n if (defaultPlan && defaultPlan.type !== \"exclude\") {\n tracked.planName = defaultPlan.name;\n\n if (defaultPlan.type === \"flat\" && defaultPlan.monthlyBase !== undefined) {\n tracked.planCost = defaultPlan.monthlyBase;\n tracked.budget = defaultPlan.monthlyBase;\n } else if (defaultPlan.suggestedBudget !== undefined) {\n tracked.budget = defaultPlan.suggestedBudget;\n }\n\n // Credit-pool services: track unit allowance, not just dollars\n if (defaultPlan.includedUnits !== undefined && defaultPlan.unitName) {\n tracked.allowance = {\n included: defaultPlan.includedUnits,\n unitName: defaultPlan.unitName,\n };\n }\n }\n\n // Check for existing API key in global config or environment\n const existingKey = globalConfig.services[service.id]?.apiKey;\n const envKey = findEnvKey(service);\n let keySource = \"\";\n let apiKey: string | undefined;\n\n if (existingKey) {\n tracked.hasApiKey = true;\n apiKey = existingKey;\n keySource = \" (key: global config)\";\n } else if (envKey) {\n tracked.hasApiKey = true;\n apiKey = envKey;\n if (!globalConfig.services[service.id]) {\n globalConfig.services[service.id] = {};\n }\n globalConfig.services[service.id]!.apiKey = envKey;\n keySource = ` (key: ${service.envPatterns[0]})`;\n }\n\n // If we have a key and a probe, try to auto-detect the plan\n if (apiKey && hasProbe(service.id)) {\n try {\n const probe = await probeService(service.id, apiKey, plans);\n if (probe?.matchedPlan && probe.confidence === \"high\") {\n const mp = probe.matchedPlan;\n tracked.planName = mp.name;\n if (mp.type === \"flat\" && mp.monthlyBase !== undefined) {\n tracked.planCost = mp.monthlyBase;\n tracked.budget = mp.monthlyBase;\n } else if (mp.suggestedBudget !== undefined) {\n tracked.budget = mp.suggestedBudget;\n }\n if (mp.includedUnits !== undefined && mp.unitName) {\n tracked.allowance = { included: mp.includedUnits, unitName: mp.unitName };\n }\n }\n } catch {\n // Probe failed — use defaults\n }\n }\n\n const tierLabel = tracked.hasApiKey\n ? \"LIVE\"\n : tracked.planCost !== undefined\n ? \"CALC\"\n : \"BLIND\";\n const planStr = tracked.planName ? ` ${tracked.planName}` : \"\";\n const trackingStr = tracked.allowance\n ? `$${tracked.budget}/mo | ${formatUnits(tracked.allowance.included)} ${tracked.allowance.unitName}`\n : `$${tracked.budget}/mo`;\n console.log(\n ` ${service.name}:${planStr} | ${tierLabel} | ${trackingStr}${keySource}`,\n );\n\n services[service.id] = tracked;\n }\n console.log(\"\");\n }\n\n // Summary\n const trackedList = Object.values(services);\n const liveCount = trackedList.filter((s) => s.hasApiKey).length;\n const totalBudget = trackedList.reduce((sum, s) => sum + (s.budget ?? 0), 0);\n\n console.log(\" \" + \"-\".repeat(48));\n console.log(` ${trackedList.length} services configured | Total budget: $${totalBudget}/mo`);\n if (liveCount > 0) console.log(` ${liveCount} with real-time billing (LIVE)`);\n console.log(\"\");\n\n // Save discovered keys\n writeGlobalConfig(globalConfig);\n\n return { services };\n}\n\n/**\n * Run the interactive init flow.\n *\n * For each detected service:\n * 1. Ask which plan they're on\n * 2. If LIVE-capable, check for existing key or ask for one\n * 3. Set budget (defaults to plan cost, $0 for free - never skipped)\n */\nexport async function runInteractiveInit(\n detected: DetectionResult[],\n): Promise<InteractiveInitResult> {\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n const services: Record<string, TrackedService> = {};\n const groups = groupByRisk(detected);\n const globalConfig = readGlobalConfig();\n\n console.log(\n `\\n Found ${detected.length} paid service${detected.length !== 1 ? \"s\" : \"\"}. Let's configure each one.\\n`,\n );\n\n for (const category of RISK_ORDER) {\n const group = groups.get(category)!;\n if (group.length === 0) continue;\n\n console.log(`\\n ${RISK_LABELS[category]}`);\n console.log(\" \" + \"-\".repeat(48));\n\n for (const det of group) {\n const service = det.service;\n const plans = service.plans;\n\n console.log(`\\n ${service.name}`);\n console.log(` Detected via: ${det.details.join(\", \")}`);\n\n if (!plans || plans.length === 0) {\n // No plans defined - basic tracking with $0 budget\n services[service.id] = {\n serviceId: service.id,\n detectedVia: det.sources,\n hasApiKey: false,\n firstDetected: new Date().toISOString(),\n budget: 0,\n };\n console.log(\" -> Configured (no plan tiers in registry, budget: $0)\");\n continue;\n }\n\n // --- Step 1: Find API key (env vars, global config, or ask) ---\n let apiKey: string | undefined;\n\n const existingKey = globalConfig.services[service.id]?.apiKey;\n const envKey = findEnvKey(service);\n\n if (existingKey) {\n apiKey = existingKey;\n console.log(` API key: found in global config`);\n } else if (envKey) {\n apiKey = envKey;\n console.log(` API key: found in environment (${service.envPatterns[0]})`);\n if (!globalConfig.services[service.id]) {\n globalConfig.services[service.id] = {};\n }\n globalConfig.services[service.id]!.apiKey = envKey;\n }\n\n // --- Step 2: If we have a key AND a probe exists, try auto-discovery ---\n let chosen: PlanTier | undefined;\n\n if (apiKey && hasProbe(service.id)) {\n console.log(\" Probing API...\");\n const probe = await probeService(service.id, apiKey, plans);\n\n if (probe) {\n console.log(` ${probe.summary}`);\n\n if (probe.confidence === \"high\" && probe.matchedPlan) {\n // Plan detected — confirm with user\n const plan = probe.matchedPlan;\n const costStr = plan.monthlyBase !== undefined ? `$${plan.monthlyBase}/mo` : \"variable\";\n const unitsStr = plan.includedUnits && plan.unitName\n ? `, ${formatUnits(plan.includedUnits)} ${plan.unitName}`\n : \"\";\n const confirm = await ask(\n rl,\n ` Detected: ${plan.name} (${costStr}${unitsStr}). Correct? [Y/n]: `,\n );\n if (confirm === \"\" || confirm.toLowerCase().startsWith(\"y\")) {\n chosen = plan;\n }\n } else if (probe.confidence === \"medium\") {\n // Usage data found but plan not certain — show it, still ask\n if (probe.usage?.spend !== undefined) {\n console.log(` Current spend: $${probe.usage.spend.toFixed(2)}`);\n }\n // Fall through to plan selection with context\n }\n // \"low\" confidence — key works but no useful data, fall through\n }\n }\n\n // --- Step 3: If no auto-detect or user said no, show plan list ---\n if (!chosen) {\n const defaultIndex = plans.findIndex((p) => p.default);\n console.log(\"\");\n for (let i = 0; i < plans.length; i++) {\n const plan = plans[i]!;\n const marker = i === defaultIndex ? \" *\" : \"\";\n const costStr =\n plan.type === \"exclude\"\n ? \"\"\n : plan.monthlyBase !== undefined\n ? ` - $${plan.monthlyBase}/mo`\n : \" - variable\";\n console.log(` ${i + 1}) ${plan.name}${costStr}${marker}`);\n }\n\n const defaultChoice =\n defaultIndex >= 0 ? String(defaultIndex + 1) : \"1\";\n const answer = await ask(\n rl,\n ` Which plan? [${defaultChoice}]: `,\n );\n\n const choiceIndex = (answer === \"\" ? parseInt(defaultChoice) : parseInt(answer)) - 1;\n chosen =\n plans[choiceIndex] ?? plans[defaultIndex >= 0 ? defaultIndex : 0]!;\n }\n\n if (chosen.type === \"exclude\") {\n services[service.id] = {\n serviceId: service.id,\n detectedVia: det.sources,\n hasApiKey: false,\n firstDetected: new Date().toISOString(),\n excluded: true,\n planName: chosen.name,\n };\n console.log(` -> ${service.name}: excluded`);\n continue;\n }\n\n const tracked: TrackedService = {\n serviceId: service.id,\n detectedVia: det.sources,\n hasApiKey: !!apiKey,\n firstDetected: new Date().toISOString(),\n planName: chosen.name,\n };\n\n if (chosen.type === \"flat\" && chosen.monthlyBase !== undefined) {\n tracked.planCost = chosen.monthlyBase;\n }\n\n // Credit-pool services: track unit allowance\n if (chosen.includedUnits !== undefined && chosen.unitName) {\n tracked.allowance = {\n included: chosen.includedUnits,\n unitName: chosen.unitName,\n };\n }\n\n // --- Step 4: If we still don't have a key, offer to provide one ---\n if (!apiKey && hasProbe(service.id)) {\n const hint = API_KEY_HINTS[service.id];\n if (hint) console.log(` ${hint}`);\n const keyAnswer = await ask(\n rl,\n ` API key for real-time tracking (Enter to skip): `,\n );\n if (keyAnswer) {\n tracked.hasApiKey = true;\n apiKey = keyAnswer;\n if (!globalConfig.services[service.id]) {\n globalConfig.services[service.id] = {};\n }\n globalConfig.services[service.id]!.apiKey = keyAnswer;\n\n // Now that we have a key, probe to enrich tracking data\n if (hasProbe(service.id)) {\n const probe = await probeService(service.id, keyAnswer, plans);\n if (probe?.usage) {\n console.log(` ${probe.summary}`);\n }\n }\n }\n }\n\n // --- Step 5: Budget (always set, never skip) ---\n const defaultBudget = chosen.monthlyBase ?? chosen.suggestedBudget ?? 0;\n\n const budgetAnswer = await ask(\n rl,\n ` Monthly budget [$${defaultBudget}]: $`,\n );\n if (budgetAnswer) {\n const parsed = parseFloat(budgetAnswer);\n tracked.budget = !isNaN(parsed) ? parsed : defaultBudget;\n } else {\n tracked.budget = defaultBudget;\n }\n\n services[service.id] = tracked;\n\n const tierLabel = tracked.hasApiKey\n ? \"LIVE\"\n : tracked.planCost !== undefined\n ? \"CALC\"\n : \"BLIND\";\n const allowanceStr = tracked.allowance\n ? ` | ${formatUnits(tracked.allowance.included)} ${tracked.allowance.unitName}`\n : \"\";\n console.log(\n ` -> ${service.name}: ${tracked.planName} | ${tierLabel} | $${tracked.budget}/mo${allowanceStr}`,\n );\n }\n }\n\n // --- Summary ---\n const tracked = Object.values(services).filter((s) => !s.excluded);\n const excluded = Object.values(services).filter((s) => s.excluded);\n const liveCount = tracked.filter((s) => s.hasApiKey).length;\n const totalBudget = tracked.reduce((sum, s) => sum + (s.budget ?? 0), 0);\n\n console.log(\"\\n \" + \"=\".repeat(48));\n console.log(` ${tracked.length} services configured`);\n if (liveCount > 0) console.log(` ${liveCount} with real-time billing (LIVE)`);\n if (tracked.length - liveCount > 0) console.log(` ${tracked.length - liveCount} estimated/calculated`);\n if (excluded.length > 0) console.log(` ${excluded.length} excluded`);\n console.log(` Total monthly budget: $${totalBudget}`);\n console.log(\" \" + \"=\".repeat(48));\n\n // Save any collected API keys\n writeGlobalConfig(globalConfig);\n\n rl.close();\n\n return { services };\n}\n"],"mappings":"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","names":["url","fs","path","fs","path","fs","path","url","url","url","url","totalStr","fs","path","resolve","tracked","config"]}
1
+ {"version":3,"sources":["../src/services/base.ts","../src/probes.ts","../src/cli.ts","../src/core/config.ts","../src/detection/detector.ts","../src/core/registry.ts","../src/services/anthropic.ts","../src/services/openai.ts","../src/services/vercel.ts","../src/services/scrapfly.ts","../src/services/index.ts","../src/core/types.ts","../src/core/brief.ts","../src/core/ledger.ts","../src/interactive-init.ts"],"sourcesContent":["import type { ConfidenceTier } from \"../core/types.js\";\n\n/** Result from polling a billing API. */\nexport interface BillingResult {\n serviceId: string;\n spend: number;\n isEstimate: boolean;\n tier: ConfidenceTier;\n raw?: Record<string, unknown>;\n error?: string;\n /** For credit-pool services: units consumed this period */\n unitsUsed?: number;\n /** For credit-pool services: total units in plan allowance */\n unitsTotal?: number;\n /** For credit-pool services: unit name (e.g., \"credits\") */\n unitName?: string;\n}\n\n/**\n * Base interface for service billing connectors.\n * Each LIVE service implements this to fetch real spend data.\n */\nexport interface BillingConnector {\n serviceId: string;\n /** Fetch current period spend. */\n fetchSpend(apiKey: string, options?: Record<string, string>): Promise<BillingResult>;\n}\n\n/**\n * Make an HTTP request and return JSON.\n * Uses native fetch (Node 18+). No external dependencies.\n */\nexport async function fetchJson<T>(\n url: string,\n options: {\n headers?: Record<string, string>;\n method?: string;\n body?: string;\n timeout?: number;\n } = {},\n): Promise<{ ok: boolean; status: number; data?: T; error?: string }> {\n try {\n const controller = new AbortController();\n const timeoutId = setTimeout(\n () => controller.abort(),\n options.timeout ?? 10_000,\n );\n\n const response = await fetch(url, {\n method: options.method ?? \"GET\",\n headers: options.headers,\n body: options.body,\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n if (!response.ok) {\n return {\n ok: false,\n status: response.status,\n error: `HTTP ${response.status}: ${response.statusText}`,\n };\n }\n\n const data = (await response.json()) as T;\n return { ok: true, status: response.status, data };\n } catch (err) {\n return {\n ok: false,\n status: 0,\n error: err instanceof Error ? err.message : \"Unknown error\",\n };\n }\n}\n","/**\n * Service probes — auto-detect plan, usage, and billing data from APIs.\n *\n * Each probe tries to discover as much as possible from a service's API:\n * 1. Plan tier (best case — \"You're on Pro\")\n * 2. Usage/spend data (good — \"You've used 850K credits\")\n * 3. Key validation (minimum — \"Your key works\")\n *\n * Probes are extensible: add a new entry to PROBES to support a new service.\n * The interview flow calls `probeService()` which looks up the right probe.\n */\n\nimport type { PlanTier } from \"./core/types.js\";\nimport { fetchJson } from \"./services/base.js\";\n\n/** Result from probing a service API */\nexport interface ProbeResult {\n /** Detected plan name (should match a registry PlanTier.name prefix) */\n planName?: string;\n /** Matched PlanTier from the registry */\n matchedPlan?: PlanTier;\n /** Usage/spend data discovered */\n usage?: {\n unitsUsed?: number;\n unitsTotal?: number;\n unitName?: string;\n spend?: number;\n currency?: string;\n };\n /** Human-readable summary of what was found */\n summary: string;\n /**\n * Discovery confidence:\n * high — plan tier identified (can skip plan selection)\n * medium — usage data found but plan unclear (show data, still ask plan)\n * low — key validates but no plan/usage info\n */\n confidence: \"high\" | \"medium\" | \"low\";\n}\n\n/** A probe function: given an API key and registry plans, discover what we can */\ntype ProbeFn = (\n apiKey: string,\n plans: PlanTier[],\n) => Promise<ProbeResult | null>;\n\n// ---------------------------------------------------------------------------\n// Service-specific probes\n// ---------------------------------------------------------------------------\n\n/** Match a detected plan name against registry plans */\nfunction matchPlan(\n detected: string,\n plans: PlanTier[],\n): PlanTier | undefined {\n const lower = detected.toLowerCase();\n return plans.find(\n (p) =>\n p.type !== \"exclude\" &&\n p.name.toLowerCase().includes(lower),\n );\n}\n\n/** Match by checking if the detected name appears as the first word of any plan */\nfunction matchPlanByPrefix(\n detected: string,\n plans: PlanTier[],\n): PlanTier | undefined {\n const lower = detected.toLowerCase();\n return plans.find((p) => {\n if (p.type === \"exclude\") return false;\n const firstWord = p.name.split(/[\\s(]/)[0]!.toLowerCase();\n return lower.includes(firstWord) || firstWord.includes(lower);\n });\n}\n\n// --- Scrapfly ---\nconst probeScrapfly: ProbeFn = async (apiKey, plans) => {\n const result = await fetchJson<{\n subscription?: {\n plan?: { name?: string };\n usage?: { scrape?: { used?: number; allowed?: number } };\n };\n account?: { credits_used?: number; credits_total?: number };\n }>(`https://api.scrapfly.io/account?key=${apiKey}`);\n\n if (!result.ok || !result.data) return null;\n\n const planName = result.data.subscription?.plan?.name;\n let unitsUsed = 0;\n let unitsTotal = 0;\n\n if (result.data.subscription?.usage?.scrape) {\n unitsUsed = result.data.subscription.usage.scrape.used ?? 0;\n unitsTotal = result.data.subscription.usage.scrape.allowed ?? 0;\n } else if (result.data.account) {\n unitsUsed = result.data.account.credits_used ?? 0;\n unitsTotal = result.data.account.credits_total ?? 0;\n }\n\n const matched = planName ? matchPlanByPrefix(planName, plans) : undefined;\n\n return {\n planName: planName ?? undefined,\n matchedPlan: matched,\n usage: {\n unitsUsed,\n unitsTotal,\n unitName: \"credits\",\n },\n summary: matched\n ? `${matched.name} — ${formatK(unitsUsed)}/${formatK(unitsTotal)} credits used`\n : `${formatK(unitsUsed)}/${formatK(unitsTotal)} credits used`,\n confidence: matched ? \"high\" : \"medium\",\n };\n};\n\n// --- Anthropic ---\nconst probeAnthropic: ProbeFn = async (apiKey, _plans) => {\n // Anthropic Admin API: GET /v1/organizations/cost_report\n const now = new Date();\n const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);\n const params = new URLSearchParams({\n start_date: startOfMonth.toISOString().split(\"T\")[0]!,\n end_date: now.toISOString().split(\"T\")[0]!,\n });\n\n const result = await fetchJson<{\n data?: Array<{ amount?: string; cost_type?: string }>;\n }>(`https://api.anthropic.com/v1/organizations/cost_report?${params}`, {\n headers: {\n \"x-api-key\": apiKey,\n \"anthropic-version\": \"2023-06-01\",\n },\n });\n\n if (!result.ok || !result.data?.data) return null;\n\n // Sum costs (returned in cents as strings)\n let totalCents = 0;\n for (const entry of result.data.data) {\n totalCents += parseFloat(entry.amount ?? \"0\");\n }\n const spend = totalCents / 100;\n\n return {\n usage: { spend, currency: \"USD\" },\n summary: `$${spend.toFixed(2)} spent this billing period`,\n confidence: \"medium\",\n };\n};\n\n// --- OpenAI ---\nconst probeOpenAI: ProbeFn = async (apiKey, _plans) => {\n // OpenAI Admin API: GET /v1/organization/usage/completions\n const now = new Date();\n const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);\n const params = new URLSearchParams({\n start_time: String(Math.floor(startOfMonth.getTime() / 1000)),\n end_time: String(Math.floor(now.getTime() / 1000)),\n });\n\n const result = await fetchJson<{\n data?: Array<{ results?: Array<{ amount?: { value?: number } }> }>;\n }>(`https://api.openai.com/v1/organization/usage/completions?${params}`, {\n headers: { Authorization: `Bearer ${apiKey}` },\n });\n\n if (!result.ok || !result.data?.data) return null;\n\n // Sum usage values\n let totalTokens = 0;\n for (const bucket of result.data.data) {\n for (const r of bucket.results ?? []) {\n totalTokens += r.amount?.value ?? 0;\n }\n }\n\n return {\n usage: { unitsUsed: totalTokens, unitName: \"tokens\" },\n summary: `${formatK(totalTokens)} tokens used this period`,\n confidence: \"medium\",\n };\n};\n\n// --- Vercel ---\nconst probeVercel: ProbeFn = async (apiKey, plans) => {\n // Try to get team info first\n const teamsResult = await fetchJson<{\n teams?: Array<{ id?: string; name?: string; billing?: { plan?: string } }>;\n }>(\"https://api.vercel.com/v2/teams\", {\n headers: { Authorization: `Bearer ${apiKey}` },\n });\n\n if (teamsResult.ok && teamsResult.data?.teams?.[0]) {\n const team = teamsResult.data.teams[0];\n const planName = team.billing?.plan;\n if (planName) {\n const matched = matchPlanByPrefix(planName, plans);\n return {\n planName,\n matchedPlan: matched,\n summary: `Team \"${team.name}\" on ${planName} plan`,\n confidence: matched ? \"high\" : \"medium\",\n };\n }\n }\n\n // Fallback: try user endpoint for hobby plan detection\n const userResult = await fetchJson<{\n user?: { billing?: { plan?: string }; name?: string };\n }>(\"https://api.vercel.com/v2/user\", {\n headers: { Authorization: `Bearer ${apiKey}` },\n });\n\n if (userResult.ok && userResult.data?.user) {\n const plan = userResult.data.user.billing?.plan ?? \"hobby\";\n const matched = matchPlanByPrefix(plan, plans);\n return {\n planName: plan,\n matchedPlan: matched,\n summary: `Personal account on ${plan} plan`,\n confidence: matched ? \"high\" : \"low\",\n };\n }\n\n return null;\n};\n\n// --- Supabase ---\nconst probeSupabase: ProbeFn = async (apiKey, plans) => {\n // Supabase Management API requires a PAT, not service_role_key\n const orgsResult = await fetchJson<\n Array<{ id?: string; name?: string; billing?: { plan?: string }; subscription_id?: string }>\n >(\"https://api.supabase.com/v1/organizations\", {\n headers: { Authorization: `Bearer ${apiKey}` },\n });\n\n if (!orgsResult.ok || !orgsResult.data || !Array.isArray(orgsResult.data)) return null;\n\n const org = orgsResult.data[0];\n if (!org) return null;\n\n const planName = org.billing?.plan;\n if (planName) {\n const matched = matchPlanByPrefix(planName, plans);\n return {\n planName,\n matchedPlan: matched,\n summary: `Org \"${org.name}\" on ${planName} plan`,\n confidence: matched ? \"high\" : \"medium\",\n };\n }\n\n return {\n summary: `Org \"${org.name}\" found (plan not detected)`,\n confidence: \"low\",\n };\n};\n\n// --- Stripe ---\nconst probeStripe: ProbeFn = async (apiKey, _plans) => {\n const result = await fetchJson<{\n available?: Array<{ amount?: number; currency?: string }>;\n pending?: Array<{ amount?: number; currency?: string }>;\n }>(\"https://api.stripe.com/v1/balance\", {\n headers: { Authorization: `Bearer ${apiKey}` },\n });\n\n if (!result.ok || !result.data) return null;\n\n const available = result.data.available?.[0];\n const pending = result.data.pending?.[0];\n const totalCents = (available?.amount ?? 0) + (pending?.amount ?? 0);\n const currency = (available?.currency ?? \"usd\").toUpperCase();\n\n return {\n usage: { spend: totalCents / 100, currency },\n summary: `Balance: ${currency} ${(totalCents / 100).toFixed(2)} (${((available?.amount ?? 0) / 100).toFixed(2)} available)`,\n confidence: \"medium\",\n };\n};\n\n// --- Browserbase ---\nconst probeBrowserbase: ProbeFn = async (apiKey, _plans) => {\n // Browserbase: GET /v1/projects (to get project ID), then usage\n const projResult = await fetchJson<\n Array<{ id?: string; name?: string }>\n >(\"https://api.browserbase.com/v1/projects\", {\n headers: { \"X-BB-API-Key\": apiKey },\n });\n\n if (!projResult.ok || !projResult.data?.[0]?.id) return null;\n\n const projectId = projResult.data[0].id;\n const usageResult = await fetchJson<{\n sessions_count?: number;\n browser_hours?: number;\n }>(`https://api.browserbase.com/v1/projects/${projectId}/usage`, {\n headers: { \"X-BB-API-Key\": apiKey },\n });\n\n if (!usageResult.ok || !usageResult.data) {\n return {\n summary: `Project \"${projResult.data[0].name}\" found`,\n confidence: \"low\",\n };\n }\n\n const sessions = usageResult.data.sessions_count ?? 0;\n const hours = usageResult.data.browser_hours ?? 0;\n\n return {\n usage: { unitsUsed: sessions, unitName: \"sessions\" },\n summary: `${sessions} sessions, ${hours.toFixed(1)} browser hours this period`,\n confidence: \"medium\",\n };\n};\n\n// --- Upstash ---\nconst probeUpstash: ProbeFn = async (apiKey, _plans) => {\n // Upstash uses email:api_key basic auth for the management API\n // The key from env is typically the Redis REST token, not management key\n // Try the databases list endpoint\n const result = await fetchJson<\n Array<{ database_id?: string; database_name?: string; region?: string }>\n >(\"https://api.upstash.com/v2/redis/databases\", {\n headers: {\n Authorization: `Basic ${Buffer.from(apiKey).toString(\"base64\")}`,\n },\n });\n\n if (!result.ok) return null;\n\n const dbCount = Array.isArray(result.data) ? result.data.length : 0;\n return {\n summary: `${dbCount} Redis database${dbCount !== 1 ? \"s\" : \"\"} found`,\n confidence: \"low\",\n };\n};\n\n// --- PostHog ---\nconst probePostHog: ProbeFn = async (apiKey, _plans) => {\n const result = await fetchJson<{\n results?: Array<{ id?: string; name?: string }>;\n }>(\"https://us.posthog.com/api/organizations/@current\", {\n headers: { Authorization: `Bearer ${apiKey}` },\n });\n\n if (!result.ok || !result.data) return null;\n\n return {\n summary: \"Organization found\",\n confidence: \"low\",\n };\n};\n\n// ---------------------------------------------------------------------------\n// Probe registry — maps service IDs to their probe function\n// ---------------------------------------------------------------------------\n\nconst PROBES: Map<string, ProbeFn> = new Map([\n [\"scrapfly\", probeScrapfly],\n [\"anthropic\", probeAnthropic],\n [\"openai\", probeOpenAI],\n [\"vercel\", probeVercel],\n [\"supabase\", probeSupabase],\n [\"stripe\", probeStripe],\n [\"browserbase\", probeBrowserbase],\n [\"upstash\", probeUpstash],\n [\"posthog\", probePostHog],\n]);\n\n/**\n * Probe a service using its API key.\n * Returns null if no probe exists for the service or the probe fails.\n */\nexport async function probeService(\n serviceId: string,\n apiKey: string,\n plans: PlanTier[],\n): Promise<ProbeResult | null> {\n const probe = PROBES.get(serviceId);\n if (!probe) return null;\n\n try {\n return await probe(apiKey, plans);\n } catch {\n return null;\n }\n}\n\n/** Check if a service has a probe available */\nexport function hasProbe(serviceId: string): boolean {\n return PROBES.has(serviceId);\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction formatK(n: number): string {\n if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(n % 1_000_000 === 0 ? 0 : 1)}M`;\n if (n >= 1_000) return `${(n / 1_000).toFixed(n % 1_000 === 0 ? 0 : 1)}K`;\n return String(n);\n}\n","#!/usr/bin/env node\n\n/**\n * burnwatch CLI\n *\n * Usage:\n * burnwatch init — Initialize in current project\n * burnwatch add <service> [options] — Register a service\n * burnwatch status — Show current spend brief\n * burnwatch reconcile — Scan for untracked sessions\n */\n\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport {\n ensureProjectDirs,\n readProjectConfig,\n writeProjectConfig,\n readGlobalConfig,\n writeGlobalConfig,\n projectConfigDir,\n isInitialized,\n} from \"./core/config.js\";\nimport type { ProjectConfig } from \"./core/config.js\";\nimport type { TrackedService } from \"./core/types.js\";\nimport { detectServices } from \"./detection/detector.js\";\nimport { pollAllServices } from \"./services/index.js\";\nimport { buildSnapshot, buildBrief, formatBrief } from \"./core/brief.js\";\nimport { writeLedger, saveSnapshot } from \"./core/ledger.js\";\nimport { getService, getAllServices } from \"./core/registry.js\";\nimport { runInteractiveInit, autoConfigureServices } from \"./interactive-init.js\";\n\nconst args = process.argv.slice(2);\nconst command = args[0];\nconst flags = new Set(args.slice(1));\n\nasync function main(): Promise<void> {\n switch (command) {\n case \"init\":\n case \"setup\":\n await cmdInit();\n break;\n case \"add\":\n await cmdAdd();\n break;\n case \"status\":\n await cmdStatus();\n break;\n case \"services\":\n cmdServices();\n break;\n case \"reconcile\":\n await cmdReconcile();\n break;\n case \"interview\":\n await cmdInterview();\n break;\n case \"configure\":\n await cmdConfigure();\n break;\n case \"help\":\n case \"--help\":\n case \"-h\":\n cmdHelp();\n break;\n case \"version\":\n case \"--version\":\n case \"-v\":\n cmdVersion();\n break;\n default:\n if (command) {\n console.error(`Unknown command: ${command}`);\n console.error('Run \"burnwatch help\" for usage.');\n process.exit(1);\n }\n cmdHelp();\n }\n}\n\n// --- Commands ---\n\nasync function cmdInit(): Promise<void> {\n const projectRoot = process.cwd();\n const nonInteractive = flags.has(\"--non-interactive\") || flags.has(\"--ni\");\n const alreadyInitialized = isInitialized(projectRoot);\n\n // Detect project name from package.json\n let projectName = path.basename(projectRoot);\n try {\n const pkgPath = path.join(projectRoot, \"package.json\");\n const pkg = JSON.parse(fs.readFileSync(pkgPath, \"utf-8\")) as {\n name?: string;\n };\n if (pkg.name) projectName = pkg.name;\n } catch {\n // Use directory name\n }\n\n // Create directories (idempotent)\n ensureProjectDirs(projectRoot);\n\n // Run detection\n console.log(\"🔍 Scanning project for paid services...\\n\");\n const detected = detectServices(projectRoot);\n\n // Load existing config or create new one\n const existingConfig = alreadyInitialized ? readProjectConfig(projectRoot) : null;\n const config: ProjectConfig = {\n projectName: existingConfig?.projectName ?? projectName,\n services: existingConfig?.services ?? {},\n createdAt: existingConfig?.createdAt ?? new Date().toISOString(),\n updatedAt: new Date().toISOString(),\n };\n\n if (detected.length === 0) {\n console.log(\" No paid services detected yet.\");\n console.log(\" Services will be detected as they enter your project.\\n\");\n } else if (nonInteractive) {\n // Explicit --non-interactive: minimal auto-register (CI/scripts)\n for (const det of detected) {\n if (!config.services[det.service.id]) {\n config.services[det.service.id] = {\n serviceId: det.service.id,\n detectedVia: det.sources,\n hasApiKey: false,\n firstDetected: new Date().toISOString(),\n budget: 0,\n };\n }\n }\n console.log(` Registered ${detected.length} services (non-interactive).\\n`);\n } else if (process.stdin.isTTY) {\n // TTY: full interactive interview with readline prompts\n const result = await runInteractiveInit(detected);\n config.services = result.services;\n } else {\n // No TTY (Claude Code, piped): auto-configure with smart defaults\n const result = await autoConfigureServices(detected);\n config.services = result.services;\n }\n\n writeProjectConfig(config, projectRoot);\n\n // Write initial .gitignore for the .burnwatch directory\n const gitignorePath = path.join(projectConfigDir(projectRoot), \".gitignore\");\n fs.writeFileSync(\n gitignorePath,\n [\n \"# Burnwatch — ignore cache and snapshots, keep ledger and config\",\n \"data/cache/\",\n \"data/snapshots/\",\n \"data/events.jsonl\",\n \"\",\n ].join(\"\\n\"),\n \"utf-8\",\n );\n\n // Register Claude Code hooks\n console.log(\"\\n🔗 Registering Claude Code hooks...\\n\");\n registerHooks(projectRoot);\n\n console.log(\"\\nburnwatch initialized.\\n\");\n console.log(\"Next steps:\");\n console.log(\" burnwatch status Show current spend\");\n console.log(\" burnwatch add <svc> Update a service's budget or API key\");\n console.log(\" burnwatch init Re-run this setup anytime\\n\");\n}\n\n/**\n * burnwatch interview --json\n *\n * Exports the current project state as structured JSON for an AI agent\n * to conduct the interview conversationally. The agent reads this,\n * asks the user questions, then writes answers back via `burnwatch configure`.\n */\nasync function cmdInterview(): Promise<void> {\n const projectRoot = process.cwd();\n\n if (!isInitialized(projectRoot)) {\n // Auto-init first\n ensureProjectDirs(projectRoot);\n const detected = detectServices(projectRoot);\n let projectName = path.basename(projectRoot);\n try {\n const pkg = JSON.parse(\n fs.readFileSync(path.join(projectRoot, \"package.json\"), \"utf-8\"),\n ) as { name?: string };\n if (pkg.name) projectName = pkg.name;\n } catch {\n // Use directory name\n }\n\n const config: ProjectConfig = {\n projectName,\n services: {},\n createdAt: new Date().toISOString(),\n updatedAt: new Date().toISOString(),\n };\n\n // Auto-configure with defaults first\n const result = await autoConfigureServices(detected);\n config.services = result.services;\n writeProjectConfig(config, projectRoot);\n }\n\n const config = readProjectConfig(projectRoot)!;\n const globalConfig = readGlobalConfig();\n const allRegistryServices = getAllServices(projectRoot);\n\n // Build structured export for each tracked service\n const serviceStates: Array<{\n serviceId: string;\n serviceName: string;\n currentPlan: string | null;\n currentBudget: number | null;\n hasApiKey: boolean;\n keySource: string | null;\n tier: string;\n excluded: boolean;\n hasProbe: boolean;\n probeResult: import(\"./probes.js\").ProbeResult | null;\n availablePlans: Array<{\n index: number;\n name: string;\n type: string;\n monthlyCost: number | null;\n includedUnits: number | null;\n unitName: string | null;\n suggestedBudget: number | null;\n isDefault: boolean;\n }>;\n riskCategory: string;\n billingModel: string;\n apiKeyHint: string | null;\n allowance: { included: number; unitName: string } | null;\n }> = [];\n\n for (const [serviceId, tracked] of Object.entries(config.services)) {\n const definition = allRegistryServices.find((s) => s.id === serviceId);\n if (!definition) continue;\n\n // Determine key source\n let keySource: string | null = null;\n const globalKey = globalConfig.services[serviceId]?.apiKey;\n if (globalKey) keySource = \"global_config\";\n else {\n for (const pattern of definition.envPatterns) {\n if (process.env[pattern]) {\n keySource = `env:${pattern}`;\n break;\n }\n }\n }\n\n // Try probing if we have a key\n let probeResult: import(\"./probes.js\").ProbeResult | null = null;\n const { probeService: probe, hasProbe: checkProbe } = await import(\"./probes.js\");\n const apiKey = globalKey ?? (keySource?.startsWith(\"env:\") ? process.env[keySource.slice(4)] : undefined);\n if (apiKey && checkProbe(serviceId)) {\n probeResult = await probe(serviceId, apiKey, definition.plans ?? []);\n }\n\n // Determine tier\n let tier = \"blind\";\n if (tracked.excluded) tier = \"excluded\";\n else if (tracked.hasApiKey) tier = \"live\";\n else if (tracked.planCost !== undefined) tier = \"calc\";\n\n // Risk category\n let riskCategory = \"flat\";\n if (definition.billingModel === \"token_usage\") riskCategory = \"llm\";\n else if ([\"credit_pool\", \"percentage\", \"per_unit\"].includes(definition.billingModel)) riskCategory = \"usage\";\n else if (definition.billingModel === \"compute\") riskCategory = \"infra\";\n\n const keyHints: Record<string, string> = {\n anthropic: \"Admin key from console.anthropic.com → Settings → Admin API Keys (sk-ant-admin-*)\",\n openai: \"Admin key from platform.openai.com → Settings → API Keys (sk-admin-*)\",\n vercel: \"Token from vercel.com/account/tokens\",\n supabase: \"PAT from supabase.com/dashboard → Account → Access Tokens (not service_role key)\",\n stripe: \"Secret key from dashboard.stripe.com → Developers → API Keys (sk_live_*)\",\n scrapfly: \"API key from scrapfly.io/dashboard\",\n browserbase: \"API key from browserbase.com → Settings → API Keys\",\n upstash: \"email:api_key from console.upstash.com → Account → Management API\",\n posthog: \"Personal API key from posthog.com → Settings → Personal API Keys\",\n };\n\n serviceStates.push({\n serviceId,\n serviceName: definition.name,\n currentPlan: tracked.planName ?? null,\n currentBudget: tracked.budget ?? null,\n hasApiKey: tracked.hasApiKey,\n keySource,\n tier,\n excluded: tracked.excluded ?? false,\n hasProbe: checkProbe(serviceId),\n probeResult,\n availablePlans: (definition.plans ?? []).map((p, i) => ({\n index: i + 1,\n name: p.name,\n type: p.type,\n monthlyCost: p.monthlyBase ?? null,\n includedUnits: p.includedUnits ?? null,\n unitName: p.unitName ?? null,\n suggestedBudget: p.suggestedBudget ?? null,\n isDefault: p.default ?? false,\n })),\n riskCategory,\n billingModel: definition.billingModel,\n apiKeyHint: keyHints[serviceId] ?? null,\n allowance: tracked.allowance ?? null,\n });\n }\n\n // Sort: llm first, then usage, infra, flat\n const riskOrder = [\"llm\", \"usage\", \"infra\", \"flat\"];\n serviceStates.sort(\n (a, b) => riskOrder.indexOf(a.riskCategory) - riskOrder.indexOf(b.riskCategory),\n );\n\n const output = {\n projectName: config.projectName,\n serviceCount: serviceStates.length,\n totalBudget: serviceStates.reduce((sum, s) => sum + (s.currentBudget ?? 0), 0),\n liveCount: serviceStates.filter((s) => s.tier === \"live\").length,\n blindCount: serviceStates.filter((s) => s.tier === \"blind\").length,\n services: serviceStates,\n configureCommand: \"burnwatch configure --service <id> [--plan <name>] [--budget <N>] [--key <KEY>] [--exclude]\",\n };\n\n if (flags.has(\"--json\")) {\n console.log(JSON.stringify(output, null, 2));\n } else {\n // Human-readable summary pointing to --json\n console.log(`\\n📋 Interview state for ${config.projectName}\\n`);\n console.log(` ${serviceStates.length} services detected`);\n console.log(` ${output.liveCount} with API keys (LIVE)`);\n console.log(` ${output.blindCount} without tracking (BLIND)`);\n console.log(` Total budget: $${output.totalBudget}/mo\\n`);\n console.log(` Use --json for machine-readable output.`);\n console.log(` Use 'burnwatch configure' to update services.\\n`);\n }\n}\n\n/**\n * burnwatch configure --service <id> [--plan <name>] [--budget <N>] [--key <KEY>] [--exclude]\n *\n * Agent-friendly command to write back interview answers for a single service.\n * Designed to be called by the AI agent after conversing with the user.\n */\nasync function cmdConfigure(): Promise<void> {\n const projectRoot = process.cwd();\n\n if (!isInitialized(projectRoot)) {\n console.error('❌ burnwatch not initialized. Run \"burnwatch init\" first.');\n process.exit(1);\n }\n\n // Parse named options\n const options: Record<string, string> = {};\n for (let i = 1; i < args.length; i++) {\n const arg = args[i]!;\n if (arg.startsWith(\"--\") && i + 1 < args.length) {\n options[arg.slice(2)] = args[i + 1]!;\n i++;\n } else if (arg === \"--exclude\") {\n options[\"exclude\"] = \"true\";\n }\n }\n\n const serviceId = options[\"service\"];\n if (!serviceId) {\n console.error(\"Usage: burnwatch configure --service <id> [--plan <name>] [--budget <N>] [--key <KEY>] [--exclude]\");\n process.exit(1);\n }\n\n const config = readProjectConfig(projectRoot)!;\n const definition = getService(serviceId, projectRoot);\n const globalConfig = readGlobalConfig();\n\n // Get or create tracked service entry\n let tracked = config.services[serviceId];\n if (!tracked) {\n tracked = {\n serviceId,\n detectedVia: [\"manual\"],\n hasApiKey: false,\n firstDetected: new Date().toISOString(),\n budget: 0,\n };\n }\n\n // Handle --exclude\n if (options[\"exclude\"] === \"true\") {\n tracked.excluded = true;\n tracked.planName = \"Don't track for this project\";\n delete tracked.budget;\n delete tracked.planCost;\n delete tracked.allowance;\n config.services[serviceId] = tracked;\n writeProjectConfig(config, projectRoot);\n console.log(JSON.stringify({ success: true, serviceId, action: \"excluded\" }));\n return;\n }\n\n // Handle --plan\n if (options[\"plan\"]) {\n const planSearch = options[\"plan\"].toLowerCase();\n const plans = definition?.plans ?? [];\n const matched = plans.find(\n (p) =>\n p.name.toLowerCase().includes(planSearch) ||\n p.name.toLowerCase().split(/[\\s(]/)[0] === planSearch,\n );\n\n if (matched) {\n tracked.planName = matched.name;\n tracked.excluded = false;\n\n if (matched.type === \"flat\" && matched.monthlyBase !== undefined) {\n tracked.planCost = matched.monthlyBase;\n // Default budget to plan cost if not explicitly set\n if (options[\"budget\"] === undefined && (tracked.budget === undefined || tracked.budget === 0)) {\n tracked.budget = matched.monthlyBase;\n }\n } else if (matched.suggestedBudget !== undefined && options[\"budget\"] === undefined) {\n if (tracked.budget === undefined || tracked.budget === 0) {\n tracked.budget = matched.suggestedBudget;\n }\n }\n\n // Set allowance for credit-pool plans\n if (matched.includedUnits !== undefined && matched.unitName) {\n tracked.allowance = {\n included: matched.includedUnits,\n unitName: matched.unitName,\n };\n } else {\n delete tracked.allowance;\n }\n } else {\n // Use as literal plan name\n tracked.planName = options[\"plan\"];\n }\n }\n\n // Handle --budget\n if (options[\"budget\"] !== undefined) {\n const parsed = parseFloat(options[\"budget\"]);\n if (!isNaN(parsed)) {\n tracked.budget = parsed;\n }\n }\n\n // Handle --key\n if (options[\"key\"]) {\n tracked.hasApiKey = true;\n if (!globalConfig.services[serviceId]) {\n globalConfig.services[serviceId] = {};\n }\n globalConfig.services[serviceId]!.apiKey = options[\"key\"];\n writeGlobalConfig(globalConfig);\n\n // Probe with the new key if possible\n const { probeService: probe, hasProbe: checkProbe } = await import(\"./probes.js\");\n if (checkProbe(serviceId) && definition?.plans) {\n const probeResult = await probe(serviceId, options[\"key\"], definition.plans);\n if (probeResult?.matchedPlan && probeResult.confidence === \"high\" && !options[\"plan\"]) {\n // Auto-apply detected plan\n const mp = probeResult.matchedPlan;\n tracked.planName = mp.name;\n if (mp.type === \"flat\" && mp.monthlyBase !== undefined) {\n tracked.planCost = mp.monthlyBase;\n if (options[\"budget\"] === undefined) tracked.budget = mp.monthlyBase;\n }\n if (mp.includedUnits !== undefined && mp.unitName) {\n tracked.allowance = { included: mp.includedUnits, unitName: mp.unitName };\n }\n }\n }\n }\n\n config.services[serviceId] = tracked;\n writeProjectConfig(config, projectRoot);\n\n // Determine final tier\n let tier = \"blind\";\n if (tracked.excluded) tier = \"excluded\";\n else if (tracked.hasApiKey) tier = \"live\";\n else if (tracked.planCost !== undefined) tier = \"calc\";\n\n const result = {\n success: true,\n serviceId,\n plan: tracked.planName ?? null,\n budget: tracked.budget ?? null,\n tier,\n hasApiKey: tracked.hasApiKey,\n allowance: tracked.allowance ?? null,\n };\n\n console.log(JSON.stringify(result));\n}\n\nasync function cmdAdd(): Promise<void> {\n const projectRoot = process.cwd();\n\n if (!isInitialized(projectRoot)) {\n console.error('❌ burnwatch not initialized. Run \"burnwatch init\" first.');\n process.exit(1);\n }\n\n const serviceId = args[1];\n if (!serviceId) {\n console.error(\"Usage: burnwatch add <service> [--key KEY] [--budget N]\");\n process.exit(1);\n }\n\n // Parse options\n const options: Record<string, string> = {};\n for (let i = 2; i < args.length; i++) {\n const arg = args[i]!;\n if (arg.startsWith(\"--\") && i + 1 < args.length) {\n options[arg.slice(2)] = args[i + 1]!;\n i++;\n }\n }\n\n const apiKey = options[\"key\"] ?? options[\"token\"];\n const budget = options[\"budget\"] ? parseFloat(options[\"budget\"]) : undefined;\n const planCost = options[\"plan-cost\"]\n ? parseFloat(options[\"plan-cost\"])\n : undefined;\n\n // Check if service is in registry\n const definition = getService(serviceId, projectRoot);\n if (!definition) {\n console.error(\n `⚠️ \"${serviceId}\" not found in registry. Adding as custom service.`,\n );\n }\n\n // Update project config\n const config = readProjectConfig(projectRoot)!;\n const existing = config.services[serviceId];\n\n const tracked: TrackedService = {\n serviceId,\n detectedVia: existing?.detectedVia ?? [\"manual\"],\n budget: budget ?? existing?.budget,\n hasApiKey: !!apiKey || (existing?.hasApiKey ?? false),\n planCost: planCost ?? existing?.planCost,\n firstDetected: existing?.firstDetected ?? new Date().toISOString(),\n };\n\n config.services[serviceId] = tracked;\n writeProjectConfig(config, projectRoot);\n\n // Save API key to global config (never in project dir)\n if (apiKey) {\n const globalConfig = readGlobalConfig();\n if (!globalConfig.services[serviceId]) {\n globalConfig.services[serviceId] = {};\n }\n globalConfig.services[serviceId]!.apiKey = apiKey;\n writeGlobalConfig(globalConfig);\n console.log(`🔐 API key saved to global config (never stored in project)`);\n }\n\n let tierLabel: string;\n if (!definition) {\n tierLabel = \"🔴 BLIND\";\n } else if (apiKey) {\n tierLabel = \"✅ LIVE\";\n } else if (planCost !== undefined) {\n tierLabel = \"🟡 CALC\";\n } else if (definition.apiTier === \"est\") {\n tierLabel = \"🟠 EST\";\n } else if (definition.apiTier === \"calc\") {\n tierLabel = \"🟡 CALC\";\n } else if (definition.apiTier === \"live\" && !apiKey) {\n tierLabel = `🔴 BLIND (add --key for ✅ LIVE)`;\n } else {\n tierLabel = \"🔴 BLIND\";\n }\n\n console.log(`\\n✅ ${serviceId} configured:`);\n console.log(` Tier: ${tierLabel}`);\n if (budget) console.log(` Budget: $${budget}/mo`);\n if (planCost) console.log(` Plan cost: $${planCost}/mo`);\n console.log(\"\");\n}\n\nasync function cmdStatus(): Promise<void> {\n const projectRoot = process.cwd();\n\n if (!isInitialized(projectRoot)) {\n console.error('❌ burnwatch not initialized. Run \"burnwatch init\" first.');\n process.exit(1);\n }\n\n const config = readProjectConfig(projectRoot)!;\n const trackedServices = Object.values(config.services);\n\n if (trackedServices.length === 0) {\n console.log(\"No services tracked yet.\");\n console.log('Run \"burnwatch add <service>\" to start tracking.');\n return;\n }\n\n console.log(\"📊 Polling services...\\n\");\n\n const results = await pollAllServices(trackedServices);\n const snapshots = results.map((r) => {\n const tracked = config.services[r.serviceId];\n const allowanceData = r.unitsUsed !== undefined && r.unitsTotal !== undefined && r.unitName\n ? { used: r.unitsUsed, included: r.unitsTotal, unitName: r.unitName }\n : tracked?.allowance\n ? { used: 0, included: tracked.allowance.included, unitName: tracked.allowance.unitName }\n : undefined;\n return buildSnapshot(r.serviceId, r.tier, r.spend, tracked?.budget, allowanceData);\n });\n\n const blindCount = snapshots.filter((s) => s.tier === \"blind\").length;\n const brief = buildBrief(config.projectName, snapshots, blindCount);\n\n // Save snapshot and update ledger\n saveSnapshot(brief, projectRoot);\n writeLedger(brief, projectRoot);\n\n // Display the brief\n console.log(formatBrief(brief));\n console.log(\"\");\n\n if (blindCount > 0) {\n console.log(`⚠️ ${blindCount} service${blindCount > 1 ? \"s\" : \"\"} untracked:`);\n for (const snap of snapshots.filter((s) => s.tier === \"blind\")) {\n console.log(` • ${snap.serviceId}`);\n }\n console.log(`\\n Run 'burnwatch init' to configure budgets and API keys.\\n`);\n }\n}\n\nasync function cmdSetup(): Promise<void> {\n const projectRoot = process.cwd();\n\n // Step 1: Init if needed\n if (!isInitialized(projectRoot)) {\n await cmdInit();\n }\n\n const config = readProjectConfig(projectRoot)!;\n const detected = Object.values(config.services);\n\n if (detected.length === 0) {\n console.log(\"No paid services detected. You're all set!\");\n return;\n }\n\n console.log(\"📋 Auto-configuring detected services...\\n\");\n\n // Step 2: Check global config for existing API keys\n const globalConfig = readGlobalConfig();\n\n // Step 3: Auto-configure each service based on registry tier + available keys\n const liveServices: string[] = [];\n const calcServices: string[] = [];\n const estServices: string[] = [];\n const blindServices: string[] = [];\n\n for (const tracked of detected) {\n const definition = getService(tracked.serviceId, projectRoot);\n if (!definition) continue;\n\n const hasKey = !!globalConfig.services[tracked.serviceId]?.apiKey;\n\n if (hasKey && definition.apiTier === \"live\") {\n tracked.hasApiKey = true;\n liveServices.push(`${definition.name}`);\n } else if (definition.apiTier === \"calc\") {\n calcServices.push(`${definition.name}`);\n } else if (definition.apiTier === \"est\") {\n estServices.push(`${definition.name}`);\n } else {\n blindServices.push(`${definition.name}`);\n }\n }\n\n writeProjectConfig(config, projectRoot);\n\n // Report\n if (liveServices.length > 0) {\n console.log(` ✅ LIVE (real billing data): ${liveServices.join(\", \")}`);\n }\n if (calcServices.length > 0) {\n console.log(` 🟡 CALC (flat-rate tracking): ${calcServices.join(\", \")}`);\n }\n if (estServices.length > 0) {\n console.log(` 🟠 EST (estimated from usage): ${estServices.join(\", \")}`);\n }\n if (blindServices.length > 0) {\n console.log(` 🔴 BLIND (detected, need API key): ${blindServices.join(\", \")}`);\n }\n\n console.log(\"\");\n\n if (blindServices.length > 0) {\n console.log(\"To upgrade BLIND services to LIVE, add API keys:\");\n for (const tracked of detected) {\n const definition = getService(tracked.serviceId, projectRoot);\n if (definition?.apiTier === \"live\" && !tracked.hasApiKey) {\n const envHint = definition.envPatterns[0] ?? \"YOUR_KEY\";\n console.log(` burnwatch add ${tracked.serviceId} --key $${envHint} --budget <N>`);\n }\n }\n console.log(\"\");\n }\n\n console.log(\"To set budgets for any service:\");\n console.log(\" burnwatch add <service> --budget <monthly_amount>\");\n console.log(\"\");\n console.log(\"Or use /setup-burnwatch in Claude Code for guided setup with budget suggestions.\\n\");\n\n // Show brief\n await cmdStatus();\n}\n\nfunction cmdServices(): void {\n const services = getAllServices();\n console.log(`\\n📋 Registry: ${services.length} services available\\n`);\n\n for (const svc of services) {\n const tierBadge =\n svc.apiTier === \"live\"\n ? \"✅ LIVE\"\n : svc.apiTier === \"calc\"\n ? \"🟡 CALC\"\n : svc.apiTier === \"est\"\n ? \"🟠 EST\"\n : \"🔴 BLIND\";\n\n console.log(` ${svc.name.padEnd(24)} ${tierBadge.padEnd(10)} ${svc.billingModel}`);\n }\n\n console.log(\"\");\n}\n\nasync function cmdReconcile(): Promise<void> {\n const projectRoot = process.cwd();\n\n if (!isInitialized(projectRoot)) {\n console.error('❌ burnwatch not initialized. Run \"burnwatch init\" first.');\n process.exit(1);\n }\n\n console.log(\"🔍 Scanning for untracked services and missed sessions...\\n\");\n\n // Re-run detection against current project state\n const detected = detectServices(projectRoot);\n const config = readProjectConfig(projectRoot)!;\n let newCount = 0;\n\n for (const det of detected) {\n if (!config.services[det.service.id]) {\n config.services[det.service.id] = {\n serviceId: det.service.id,\n detectedVia: det.sources,\n hasApiKey: false,\n firstDetected: new Date().toISOString(),\n };\n newCount++;\n console.log(` 🆕 ${det.service.name} — detected via ${det.details.join(\", \")}`);\n }\n }\n\n if (newCount > 0) {\n writeProjectConfig(config, projectRoot);\n console.log(\n `\\n✅ Found ${newCount} new service${newCount > 1 ? \"s\" : \"\"}. Run 'burnwatch status' to see updated brief.`,\n );\n } else {\n console.log(\" ✅ No new services found. All services already tracked.\");\n }\n\n console.log(\"\");\n}\n\nfunction cmdHelp(): void {\n console.log(`\nburnwatch — Passive cost memory for AI-assisted development\n\nUsage:\n burnwatch init Interactive setup — pick plans per service\n burnwatch init --non-interactive Auto-detect services, no prompts\n burnwatch setup Init + auto-configure all detected services\n burnwatch add <service> [options] Register a service for tracking\n burnwatch status Show current spend brief\n burnwatch services List all services in registry\n burnwatch reconcile Scan for untracked services\n burnwatch interview --json Export state for agent-driven interview\n burnwatch configure --service <id> [opts] Agent writes back interview answers\n\nOptions for 'configure':\n --service <ID> Service to configure (required)\n --plan <NAME> Plan name (fuzzy matches against registry)\n --budget <AMOUNT> Monthly budget in USD\n --key <API_KEY> API key for LIVE tracking\n --exclude Exclude this service from tracking\n\nOptions for 'add':\n --key <API_KEY> API key for LIVE tracking (saved to ~/.config/burnwatch/)\n --token <TOKEN> Same as --key (alias)\n --budget <AMOUNT> Monthly budget in USD\n --plan-cost <AMOUNT> Monthly plan cost for CALC tracking\n\nExamples:\n burnwatch init\n burnwatch interview --json\n burnwatch configure --service anthropic --plan \"API Usage\" --budget 100\n burnwatch configure --service supabase --plan pro --budget 25 --key sbp_xxx\n burnwatch configure --service posthog --plan free --budget 0\n burnwatch status\n`);\n}\n\nfunction cmdVersion(): void {\n try {\n const pkgPath = path.resolve(\n path.dirname(new URL(import.meta.url).pathname),\n \"../package.json\",\n );\n const pkg = JSON.parse(fs.readFileSync(pkgPath, \"utf-8\")) as {\n version: string;\n };\n console.log(`burnwatch v${pkg.version}`);\n } catch {\n console.log(\"burnwatch v0.1.0\");\n }\n}\n\n// --- Hook Registration ---\n\nfunction registerHooks(projectRoot: string): void {\n // Step 1: Copy hook scripts into .burnwatch/hooks/ for durability.\n // This avoids relying on ephemeral npx cache paths.\n const sourceHooksDir = path.resolve(\n path.dirname(new URL(import.meta.url).pathname),\n \"hooks\",\n );\n const localHooksDir = path.join(projectRoot, \".burnwatch\", \"hooks\");\n fs.mkdirSync(localHooksDir, { recursive: true });\n\n const hookFiles = [\n \"on-session-start.js\",\n \"on-prompt.js\",\n \"on-file-change.js\",\n \"on-stop.js\",\n ];\n\n for (const file of hookFiles) {\n const src = path.join(sourceHooksDir, file);\n const dest = path.join(localHooksDir, file);\n try {\n fs.copyFileSync(src, dest);\n // Also copy sourcemaps if they exist\n const mapSrc = src + \".map\";\n if (fs.existsSync(mapSrc)) {\n fs.copyFileSync(mapSrc, dest + \".map\");\n }\n } catch (err) {\n console.error(` Warning: Could not copy hook ${file}: ${err instanceof Error ? err.message : err}`);\n }\n }\n\n console.log(` Hook scripts copied to ${localHooksDir}`);\n\n // Step 1b: Copy skills to .claude/skills/ so the agent can discover them.\n const sourceSkillsDir = path.resolve(\n path.dirname(new URL(import.meta.url).pathname),\n \"../skills\",\n );\n const skillNames = [\"setup-burnwatch\", \"burnwatch-interview\", \"spend\"];\n const claudeSkillsDir = path.join(projectRoot, \".claude\", \"skills\");\n\n for (const skillName of skillNames) {\n const srcSkill = path.join(sourceSkillsDir, skillName, \"SKILL.md\");\n const destDir = path.join(claudeSkillsDir, skillName);\n const destSkill = path.join(destDir, \"SKILL.md\");\n try {\n if (fs.existsSync(srcSkill)) {\n fs.mkdirSync(destDir, { recursive: true });\n fs.copyFileSync(srcSkill, destSkill);\n }\n } catch (err) {\n console.error(` Warning: Could not copy skill ${skillName}: ${err instanceof Error ? err.message : err}`);\n }\n }\n\n console.log(` Skills installed to ${claudeSkillsDir}`);\n\n // Step 2: Find or create .claude/settings.json — MERGE, never overwrite\n const claudeDir = path.join(projectRoot, \".claude\");\n const settingsPath = path.join(claudeDir, \"settings.json\");\n\n fs.mkdirSync(claudeDir, { recursive: true });\n\n // Read existing settings (preserve everything)\n let settings: Record<string, unknown> = {};\n try {\n const existing = fs.readFileSync(settingsPath, \"utf-8\");\n settings = JSON.parse(existing) as Record<string, unknown>;\n console.log(` Merging into existing ${settingsPath}`);\n } catch {\n // No existing settings — start fresh\n }\n\n // Ensure hooks object exists, preserve all existing hooks\n if (!settings[\"hooks\"] || typeof settings[\"hooks\"] !== \"object\") {\n settings[\"hooks\"] = {};\n }\n const hooks = settings[\"hooks\"] as Record<string, unknown[]>;\n\n // Use the local .burnwatch/hooks/ paths (durable, not ephemeral)\n const hooksDir = localHooksDir;\n\n // SessionStart hook\n if (!hooks[\"SessionStart\"]) hooks[\"SessionStart\"] = [];\n addHookIfMissing(hooks[\"SessionStart\"] as unknown[], \"SessionStart\", {\n matcher: \"startup|resume\",\n hooks: [\n {\n type: \"command\",\n command: `node \"${path.join(hooksDir, \"on-session-start.js\")}\"`,\n timeout: 15,\n },\n ],\n });\n\n // UserPromptSubmit hook\n if (!hooks[\"UserPromptSubmit\"]) hooks[\"UserPromptSubmit\"] = [];\n addHookIfMissing(\n hooks[\"UserPromptSubmit\"] as unknown[],\n \"UserPromptSubmit\",\n {\n hooks: [\n {\n type: \"command\",\n command: `node \"${path.join(hooksDir, \"on-prompt.js\")}\"`,\n timeout: 5,\n },\n ],\n },\n );\n\n // PostToolUse hook (Edit|Write only)\n if (!hooks[\"PostToolUse\"]) hooks[\"PostToolUse\"] = [];\n addHookIfMissing(hooks[\"PostToolUse\"] as unknown[], \"PostToolUse\", {\n matcher: \"Edit|Write\",\n hooks: [\n {\n type: \"command\",\n command: `node \"${path.join(hooksDir, \"on-file-change.js\")}\"`,\n timeout: 5,\n },\n ],\n });\n\n // Stop hook (async — don't block session end)\n if (!hooks[\"Stop\"]) hooks[\"Stop\"] = [];\n addHookIfMissing(hooks[\"Stop\"] as unknown[], \"Stop\", {\n hooks: [\n {\n type: \"command\",\n command: `node \"${path.join(hooksDir, \"on-stop.js\")}\"`,\n timeout: 15,\n async: true,\n },\n ],\n });\n\n settings[\"hooks\"] = hooks;\n fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + \"\\n\", \"utf-8\");\n console.log(` Hooks registered in ${settingsPath}`);\n}\n\nfunction addHookIfMissing(\n hookArray: unknown[],\n _eventName: string,\n hookConfig: unknown,\n): void {\n // Check if burnwatch hook is already registered\n const existing = hookArray.some((h) => {\n const hook = h as { hooks?: Array<{ command?: string }> };\n return hook.hooks?.some((inner) => inner.command?.includes(\"burnwatch\"));\n });\n\n if (!existing) {\n hookArray.push(hookConfig);\n }\n}\n\n// --- Entry ---\n\nmain().catch((err) => {\n console.error(\"Error:\", err instanceof Error ? err.message : err);\n process.exit(1);\n});\n","import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport * as os from \"node:os\";\nimport type { TrackedService } from \"./types.js\";\n\n/**\n * Paths for burnwatch configuration and data.\n *\n * Hybrid model:\n * - Global config (API keys, service credentials): ~/.config/burnwatch/\n * - Project config (budgets, tracked services): .burnwatch/\n * - Project data (ledger, events, cache): .burnwatch/data/\n */\n\n/** Global config directory — stores API keys, never in project dirs. */\nexport function globalConfigDir(): string {\n const xdgConfig = process.env[\"XDG_CONFIG_HOME\"];\n if (xdgConfig) return path.join(xdgConfig, \"burnwatch\");\n return path.join(os.homedir(), \".config\", \"burnwatch\");\n}\n\n/** Project config directory — stores budgets, tracked services. */\nexport function projectConfigDir(projectRoot?: string): string {\n const root = projectRoot ?? process.cwd();\n return path.join(root, \".burnwatch\");\n}\n\n/** Project data directory — stores ledger, events, cache. */\nexport function projectDataDir(projectRoot?: string): string {\n return path.join(projectConfigDir(projectRoot), \"data\");\n}\n\n// --- Global config (API keys) ---\n\nexport interface GlobalConfig {\n services: Record<\n string,\n {\n apiKey?: string;\n token?: string;\n orgId?: string;\n }\n >;\n}\n\nexport function readGlobalConfig(): GlobalConfig {\n const configPath = path.join(globalConfigDir(), \"config.json\");\n try {\n const raw = fs.readFileSync(configPath, \"utf-8\");\n return JSON.parse(raw) as GlobalConfig;\n } catch {\n return { services: {} };\n }\n}\n\nexport function writeGlobalConfig(config: GlobalConfig): void {\n const dir = globalConfigDir();\n fs.mkdirSync(dir, { recursive: true });\n const configPath = path.join(dir, \"config.json\");\n fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + \"\\n\", \"utf-8\");\n // Restrict permissions — this file contains API keys\n fs.chmodSync(configPath, 0o600);\n}\n\n// --- Project config (budgets, tracked services) ---\n\nexport interface ProjectConfig {\n projectName: string;\n services: Record<string, TrackedService>;\n createdAt: string;\n updatedAt: string;\n}\n\nexport function readProjectConfig(projectRoot?: string): ProjectConfig | null {\n const configPath = path.join(projectConfigDir(projectRoot), \"config.json\");\n try {\n const raw = fs.readFileSync(configPath, \"utf-8\");\n return JSON.parse(raw) as ProjectConfig;\n } catch {\n return null;\n }\n}\n\nexport function writeProjectConfig(\n config: ProjectConfig,\n projectRoot?: string,\n): void {\n const dir = projectConfigDir(projectRoot);\n fs.mkdirSync(dir, { recursive: true });\n config.updatedAt = new Date().toISOString();\n const configPath = path.join(dir, \"config.json\");\n fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + \"\\n\", \"utf-8\");\n}\n\n/** Ensure all project directories exist. */\nexport function ensureProjectDirs(projectRoot?: string): void {\n const dirs = [\n projectConfigDir(projectRoot),\n projectDataDir(projectRoot),\n path.join(projectDataDir(projectRoot), \"cache\"),\n path.join(projectDataDir(projectRoot), \"snapshots\"),\n ];\n for (const dir of dirs) {\n fs.mkdirSync(dir, { recursive: true });\n }\n}\n\n/** Check if burnwatch is initialized in the given project. */\nexport function isInitialized(projectRoot?: string): boolean {\n return readProjectConfig(projectRoot) !== null;\n}\n","import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport { loadRegistry } from \"../core/registry.js\";\nimport type { ServiceDefinition, DetectionSource } from \"../core/types.js\";\n\nexport interface DetectionResult {\n service: ServiceDefinition;\n sources: DetectionSource[];\n details: string[];\n}\n\n/**\n * Run all detection surfaces against the current project.\n * Returns services detected via any combination of:\n * - package.json dependencies (recursive — finds monorepo subdirectories)\n * - environment variable patterns (process.env + .env* files recursive)\n * - import statement scanning (recursive from project root)\n * - (prompt mention scanning is handled separately in hooks)\n */\nexport function detectServices(projectRoot: string): DetectionResult[] {\n const registry = loadRegistry(projectRoot);\n const results = new Map<string, DetectionResult>();\n\n // Surface 1: Package manifest scanning (recursive — finds all package.json files)\n const pkgDeps = scanAllPackageJsons(projectRoot);\n for (const [serviceId, service] of registry) {\n const matchedPkgs = service.packageNames.filter((pkg) =>\n pkgDeps.has(pkg),\n );\n if (matchedPkgs.length > 0) {\n getOrCreate(results, serviceId, service).sources.push(\"package_json\");\n getOrCreate(results, serviceId, service).details.push(\n `package.json: ${matchedPkgs.join(\", \")}`,\n );\n }\n }\n\n // Surface 2: Environment variable pattern matching\n // Check both process.env AND .env* files in the project tree\n const envVars = collectEnvVars(projectRoot);\n for (const [serviceId, service] of registry) {\n const matchedEnvs = service.envPatterns.filter((pattern) =>\n envVars.has(pattern),\n );\n if (matchedEnvs.length > 0) {\n getOrCreate(results, serviceId, service).sources.push(\"env_var\");\n getOrCreate(results, serviceId, service).details.push(\n `env vars: ${matchedEnvs.join(\", \")}`,\n );\n }\n }\n\n // Surface 3: Import statement analysis (recursive from project root)\n const importHits = scanImports(projectRoot);\n for (const [serviceId, service] of registry) {\n const matchedImports = service.importPatterns.filter((pattern) =>\n importHits.has(pattern),\n );\n if (matchedImports.length > 0) {\n if (\n !getOrCreate(results, serviceId, service).sources.includes(\n \"import_scan\",\n )\n ) {\n getOrCreate(results, serviceId, service).sources.push(\"import_scan\");\n getOrCreate(results, serviceId, service).details.push(\n `imports: ${matchedImports.join(\", \")}`,\n );\n }\n }\n }\n\n return Array.from(results.values());\n}\n\n/**\n * Detect services mentioned in a prompt string.\n * Used by the UserPromptSubmit hook.\n */\nexport function detectMentions(\n prompt: string,\n projectRoot?: string,\n): DetectionResult[] {\n const registry = loadRegistry(projectRoot);\n const results: DetectionResult[] = [];\n const promptLower = prompt.toLowerCase();\n\n for (const [, service] of registry) {\n const matched = service.mentionKeywords.some((keyword) =>\n promptLower.includes(keyword.toLowerCase()),\n );\n if (matched) {\n results.push({\n service,\n sources: [\"prompt_mention\"],\n details: [`mentioned in prompt`],\n });\n }\n }\n\n return results;\n}\n\n/**\n * Detect new services introduced in a file change.\n * Used by the PostToolUse hook for Write/Edit events.\n */\nexport function detectInFileChange(\n filePath: string,\n content: string,\n projectRoot?: string,\n): DetectionResult[] {\n const registry = loadRegistry(projectRoot);\n const results: DetectionResult[] = [];\n const fileName = path.basename(filePath);\n\n // Check if it's a package.json change\n if (fileName === \"package.json\") {\n try {\n const pkg = JSON.parse(content) as {\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n };\n const allDeps = new Set([\n ...Object.keys(pkg.dependencies ?? {}),\n ...Object.keys(pkg.devDependencies ?? {}),\n ]);\n\n for (const [, service] of registry) {\n const matched = service.packageNames.filter((p) => allDeps.has(p));\n if (matched.length > 0) {\n results.push({\n service,\n sources: [\"package_json\"],\n details: [`new dependency: ${matched.join(\", \")}`],\n });\n }\n }\n } catch {\n // Not valid JSON, skip\n }\n return results;\n }\n\n // Check if it's an env file change\n if (fileName.startsWith(\".env\")) {\n const envKeys = content\n .split(\"\\n\")\n .filter((line) => line.includes(\"=\") && !line.startsWith(\"#\"))\n .map((line) => line.split(\"=\")[0]!.trim());\n\n for (const [, service] of registry) {\n const matched = service.envPatterns.filter((p) => envKeys.includes(p));\n if (matched.length > 0) {\n results.push({\n service,\n sources: [\"env_var\"],\n details: [`new env var: ${matched.join(\", \")}`],\n });\n }\n }\n return results;\n }\n\n // Check for import statements in source files\n if (/\\.(ts|tsx|js|jsx|mjs|cjs)$/.test(filePath)) {\n for (const [, service] of registry) {\n const matched = service.importPatterns.filter(\n (pattern) =>\n content.includes(`from \"${pattern}`) ||\n content.includes(`from '${pattern}`) ||\n content.includes(`require(\"${pattern}`) ||\n content.includes(`require('${pattern}`),\n );\n if (matched.length > 0) {\n results.push({\n service,\n sources: [\"import_scan\"],\n details: [`import added: ${matched.join(\", \")}`],\n });\n }\n }\n }\n\n return results;\n}\n\n// --- Helpers ---\n\nfunction getOrCreate(\n map: Map<string, DetectionResult>,\n serviceId: string,\n service: ServiceDefinition,\n): DetectionResult {\n let result = map.get(serviceId);\n if (!result) {\n result = { service, sources: [], details: [] };\n map.set(serviceId, result);\n }\n return result;\n}\n\n/**\n * Recursively find and scan ALL package.json files in the project tree.\n * Handles monorepos where dependencies live in subdirectories.\n */\nfunction scanAllPackageJsons(projectRoot: string): Set<string> {\n const deps = new Set<string>();\n const pkgFiles = findFiles(projectRoot, \"package.json\", 4);\n\n for (const pkgPath of pkgFiles) {\n try {\n const raw = fs.readFileSync(pkgPath, \"utf-8\");\n const pkg = JSON.parse(raw) as {\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n };\n for (const name of Object.keys(pkg.dependencies ?? {})) deps.add(name);\n for (const name of Object.keys(pkg.devDependencies ?? {})) deps.add(name);\n } catch {\n // Skip malformed package.json\n }\n }\n\n return deps;\n}\n\n/**\n * Collect environment variable names from both process.env\n * and all .env* files found recursively in the project tree.\n */\nfunction collectEnvVars(projectRoot: string): Set<string> {\n const envVars = new Set(Object.keys(process.env));\n\n // Find all .env* files in the project tree\n const envFiles = findEnvFiles(projectRoot, 3);\n\n for (const envFile of envFiles) {\n try {\n const content = fs.readFileSync(envFile, \"utf-8\");\n const keys = content\n .split(\"\\n\")\n .filter((line) => line.includes(\"=\") && !line.startsWith(\"#\"))\n .map((line) => line.split(\"=\")[0]!.trim())\n .filter(Boolean);\n\n for (const key of keys) {\n envVars.add(key);\n }\n } catch {\n // Skip unreadable files\n }\n }\n\n return envVars;\n}\n\n/**\n * Find all .env* files recursively (but not in node_modules, .git, dist, etc.)\n */\nfunction findEnvFiles(dir: string, maxDepth: number): string[] {\n const results: string[] = [];\n if (maxDepth <= 0) return results;\n\n try {\n const entries = fs.readdirSync(dir, { withFileTypes: true });\n for (const entry of entries) {\n if (entry.name === \"node_modules\" || entry.name === \".git\" || entry.name === \"dist\") continue;\n const fullPath = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n results.push(...findEnvFiles(fullPath, maxDepth - 1));\n } else if (entry.name.startsWith(\".env\")) {\n results.push(fullPath);\n }\n }\n } catch {\n // Skip unreadable directories\n }\n\n return results;\n}\n\n/**\n * Find files with a specific name recursively.\n * Used to find package.json files across monorepo subdirectories.\n */\nfunction findFiles(dir: string, fileName: string, maxDepth: number): string[] {\n const results: string[] = [];\n if (maxDepth <= 0) return results;\n\n try {\n const entries = fs.readdirSync(dir, { withFileTypes: true });\n for (const entry of entries) {\n if (entry.name === \"node_modules\" || entry.name === \".git\" || entry.name === \"dist\") continue;\n const fullPath = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n results.push(...findFiles(fullPath, fileName, maxDepth - 1));\n } else if (entry.name === fileName) {\n results.push(fullPath);\n }\n }\n } catch {\n // Skip unreadable directories\n }\n\n return results;\n}\n\n/**\n * Lightweight import scanning.\n * Recursively scans the project for import/require statements.\n * Looks in src/, app/, lib/, pages/, and any other code directories.\n * Does NOT do a full AST parse — just string matching.\n */\nfunction scanImports(projectRoot: string): Set<string> {\n const imports = new Set<string>();\n\n // Scan common code directories + the root itself for source files\n const codeDirs = [\"src\", \"app\", \"lib\", \"pages\", \"components\", \"utils\", \"services\", \"hooks\"];\n const dirsToScan: string[] = [];\n\n for (const dir of codeDirs) {\n const fullPath = path.join(projectRoot, dir);\n if (fs.existsSync(fullPath)) {\n dirsToScan.push(fullPath);\n }\n }\n\n // Also check subdirectories (monorepo support)\n try {\n const entries = fs.readdirSync(projectRoot, { withFileTypes: true });\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n if (entry.name === \"node_modules\" || entry.name === \".git\" || entry.name === \"dist\" || entry.name.startsWith(\".\")) continue;\n\n // Check if this subdirectory has its own package.json (monorepo package)\n const subPkgPath = path.join(projectRoot, entry.name, \"package.json\");\n if (fs.existsSync(subPkgPath)) {\n // Scan this subpackage's code directories\n for (const dir of codeDirs) {\n const fullPath = path.join(projectRoot, entry.name, dir);\n if (fs.existsSync(fullPath)) {\n dirsToScan.push(fullPath);\n }\n }\n }\n }\n } catch {\n // Skip if root is unreadable\n }\n\n for (const dir of dirsToScan) {\n const files = walkDir(dir, /\\.(ts|tsx|js|jsx|mjs|cjs)$/);\n for (const file of files) {\n try {\n const content = fs.readFileSync(file, \"utf-8\");\n // Match: import ... from \"package\" or require(\"package\")\n const importRegex =\n /(?:from\\s+[\"']|require\\s*\\(\\s*[\"'])([^./][^\"']*?)(?:[\"'])/g;\n let match: RegExpExecArray | null;\n while ((match = importRegex.exec(content)) !== null) {\n const pkg = match[1];\n if (pkg) {\n // Normalize scoped packages: @scope/pkg/subpath -> @scope/pkg\n const parts = pkg.split(\"/\");\n if (parts[0]?.startsWith(\"@\") && parts.length >= 2) {\n imports.add(`${parts[0]}/${parts[1]}`);\n } else if (parts[0]) {\n imports.add(parts[0]);\n }\n }\n }\n } catch {\n // Skip unreadable files\n }\n }\n }\n\n return imports;\n}\n\n/** Recursively walk a directory, returning files matching the pattern. */\nfunction walkDir(dir: string, pattern: RegExp, maxDepth = 5): string[] {\n const results: string[] = [];\n if (maxDepth <= 0) return results;\n\n try {\n const entries = fs.readdirSync(dir, { withFileTypes: true });\n for (const entry of entries) {\n if (entry.name.startsWith(\".\") || entry.name === \"node_modules\") continue;\n const fullPath = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n results.push(...walkDir(fullPath, pattern, maxDepth - 1));\n } else if (pattern.test(entry.name)) {\n results.push(fullPath);\n }\n }\n } catch {\n // Skip unreadable directories\n }\n\n return results;\n}\n","import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport * as url from \"node:url\";\nimport type { ServiceDefinition } from \"./types.js\";\n\nconst __dirname = path.dirname(url.fileURLToPath(import.meta.url));\n\ninterface RegistryFile {\n version: string;\n lastUpdated: string;\n services: Record<string, ServiceDefinition>;\n}\n\nlet cachedRegistry: Map<string, ServiceDefinition> | null = null;\n\n/**\n * Load the service registry.\n * Checks project-local override first, then falls back to bundled registry.\n */\nexport function loadRegistry(projectRoot?: string): Map<string, ServiceDefinition> {\n if (cachedRegistry) return cachedRegistry;\n\n const registry = new Map<string, ServiceDefinition>();\n\n // Load bundled registry (shipped with package)\n // Try multiple possible locations — depends on whether running from src/ or dist/\n const candidates = [\n path.resolve(__dirname, \"../../registry.json\"), // from src/core/\n path.resolve(__dirname, \"../registry.json\"), // from dist/\n ];\n for (const candidate of candidates) {\n if (fs.existsSync(candidate)) {\n loadRegistryFile(candidate, registry);\n break;\n }\n }\n\n // Load project-local override (if exists)\n if (projectRoot) {\n const localPath = path.join(projectRoot, \".burnwatch\", \"registry.json\");\n if (fs.existsSync(localPath)) {\n loadRegistryFile(localPath, registry);\n }\n }\n\n cachedRegistry = registry;\n return registry;\n}\n\nfunction loadRegistryFile(\n filePath: string,\n registry: Map<string, ServiceDefinition>,\n): void {\n try {\n const raw = fs.readFileSync(filePath, \"utf-8\");\n const data = JSON.parse(raw) as RegistryFile;\n for (const [id, service] of Object.entries(data.services)) {\n registry.set(id, { ...service, id });\n }\n } catch {\n // Silently skip missing or malformed registry files\n }\n}\n\n/** Clear the cached registry (for testing). */\nexport function clearRegistryCache(): void {\n cachedRegistry = null;\n}\n\n/** Get a single service definition by ID. */\nexport function getService(\n id: string,\n projectRoot?: string,\n): ServiceDefinition | undefined {\n return loadRegistry(projectRoot).get(id);\n}\n\n/** Get all service definitions. */\nexport function getAllServices(\n projectRoot?: string,\n): ServiceDefinition[] {\n return Array.from(loadRegistry(projectRoot).values());\n}\n","import type { BillingConnector, BillingResult } from \"./base.js\";\nimport { fetchJson } from \"./base.js\";\n\n/**\n * Anthropic billing connector.\n * Uses the /v1/organizations/usage endpoint.\n * Requires an admin API key.\n */\nexport const anthropicConnector: BillingConnector = {\n serviceId: \"anthropic\",\n\n async fetchSpend(apiKey: string): Promise<BillingResult> {\n // Get current month date range\n const now = new Date();\n const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);\n const startDate = startOfMonth.toISOString().split(\"T\")[0]!;\n const endDate = now.toISOString().split(\"T\")[0]!;\n\n const url = `https://api.anthropic.com/v1/organizations/usage?start_date=${startDate}&end_date=${endDate}`;\n\n const result = await fetchJson<{\n data?: Array<{ total_cost_usd?: number; spend?: number }>;\n total_cost_usd?: number;\n }>(url, {\n headers: {\n \"x-api-key\": apiKey,\n \"anthropic-version\": \"2023-06-01\",\n },\n });\n\n if (!result.ok || !result.data) {\n return {\n serviceId: \"anthropic\",\n spend: 0,\n isEstimate: true,\n tier: \"est\",\n error: result.error ?? \"Failed to fetch Anthropic usage\",\n };\n }\n\n // Sum up usage across the period\n let totalSpend = 0;\n if (result.data.total_cost_usd !== undefined) {\n totalSpend = result.data.total_cost_usd;\n } else if (result.data.data) {\n totalSpend = result.data.data.reduce(\n (sum, entry) => sum + (entry.total_cost_usd ?? entry.spend ?? 0),\n 0,\n );\n }\n\n return {\n serviceId: \"anthropic\",\n spend: totalSpend,\n isEstimate: false,\n tier: \"live\",\n raw: result.data as unknown as Record<string, unknown>,\n };\n },\n};\n","import type { BillingConnector, BillingResult } from \"./base.js\";\nimport { fetchJson } from \"./base.js\";\n\n/**\n * OpenAI billing connector.\n * Uses the /v1/organization/costs endpoint.\n * Requires an organization-level API key.\n */\nexport const openaiConnector: BillingConnector = {\n serviceId: \"openai\",\n\n async fetchSpend(apiKey: string): Promise<BillingResult> {\n const now = new Date();\n const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);\n // OpenAI uses Unix timestamps\n const startTime = Math.floor(startOfMonth.getTime() / 1000);\n\n const url = `https://api.openai.com/v1/organization/costs?start_time=${startTime}`;\n\n const result = await fetchJson<{\n data?: Array<{\n results?: Array<{\n amount?: { value?: number };\n }>;\n }>;\n object?: string;\n }>(url, {\n headers: {\n Authorization: `Bearer ${apiKey}`,\n },\n });\n\n if (!result.ok || !result.data) {\n return {\n serviceId: \"openai\",\n spend: 0,\n isEstimate: true,\n tier: \"est\",\n error: result.error ?? \"Failed to fetch OpenAI usage\",\n };\n }\n\n // Sum all cost buckets\n let totalSpend = 0;\n if (result.data.data) {\n for (const bucket of result.data.data) {\n if (bucket.results) {\n for (const r of bucket.results) {\n totalSpend += r.amount?.value ?? 0;\n }\n }\n }\n }\n\n // OpenAI returns costs in cents, convert to dollars\n totalSpend = totalSpend / 100;\n\n return {\n serviceId: \"openai\",\n spend: totalSpend,\n isEstimate: false,\n tier: \"live\",\n raw: result.data as unknown as Record<string, unknown>,\n };\n },\n};\n","import type { BillingConnector, BillingResult } from \"./base.js\";\nimport { fetchJson } from \"./base.js\";\n\n/**\n * Vercel billing connector.\n * Uses the Vercel billing API.\n * Requires a Vercel token (personal or team-scoped).\n */\nexport const vercelConnector: BillingConnector = {\n serviceId: \"vercel\",\n\n async fetchSpend(\n token: string,\n options?: Record<string, string>,\n ): Promise<BillingResult> {\n const teamId = options?.[\"teamId\"] ?? \"\";\n const teamParam = teamId ? `?teamId=${teamId}` : \"\";\n\n // Fetch current billing period usage\n const url = `https://api.vercel.com/v2/usage${teamParam}`;\n\n const result = await fetchJson<{\n usage?: {\n total?: number;\n bandwidth?: { total?: number };\n serverlessFunctionExecution?: { total?: number };\n edgeFunctionExecution?: { total?: number };\n imageOptimization?: { total?: number };\n };\n billing?: {\n plan?: string;\n period?: { start?: string; end?: string };\n invoiceItems?: Array<{ amount?: number }>;\n };\n }>(url, {\n headers: {\n Authorization: `Bearer ${token}`,\n },\n });\n\n if (!result.ok || !result.data) {\n return {\n serviceId: \"vercel\",\n spend: 0,\n isEstimate: true,\n tier: \"est\",\n error: result.error ?? \"Failed to fetch Vercel usage\",\n };\n }\n\n // Sum up usage costs\n let totalSpend = 0;\n if (result.data.usage?.total !== undefined) {\n totalSpend = result.data.usage.total;\n } else if (result.data.billing?.invoiceItems) {\n totalSpend = result.data.billing.invoiceItems.reduce(\n (sum, item) => sum + (item.amount ?? 0),\n 0,\n );\n }\n\n return {\n serviceId: \"vercel\",\n spend: totalSpend,\n isEstimate: false,\n tier: \"live\",\n raw: result.data as unknown as Record<string, unknown>,\n };\n },\n};\n","import type { BillingConnector, BillingResult } from \"./base.js\";\nimport { fetchJson } from \"./base.js\";\n\n/**\n * Scrapfly billing connector.\n * Uses the /account endpoint which returns credits used/remaining.\n * Works with the standard API key — no special billing key needed.\n */\nexport const scrapflyConnector: BillingConnector = {\n serviceId: \"scrapfly\",\n\n async fetchSpend(apiKey: string): Promise<BillingResult> {\n const url = `https://api.scrapfly.io/account?key=${apiKey}`;\n\n const result = await fetchJson<{\n subscription?: {\n usage?: {\n scrape?: { used?: number; allowed?: number };\n };\n };\n account?: {\n credits_used?: number;\n credits_total?: number;\n };\n }>(url);\n\n if (!result.ok || !result.data) {\n return {\n serviceId: \"scrapfly\",\n spend: 0,\n isEstimate: true,\n tier: \"est\",\n error: result.error ?? \"Failed to fetch Scrapfly account\",\n };\n }\n\n // Extract credits used from the response\n let creditsUsed = 0;\n let creditsTotal = 0;\n\n if (result.data.subscription?.usage?.scrape) {\n creditsUsed = result.data.subscription.usage.scrape.used ?? 0;\n creditsTotal = result.data.subscription.usage.scrape.allowed ?? 0;\n } else if (result.data.account) {\n creditsUsed = result.data.account.credits_used ?? 0;\n creditsTotal = result.data.account.credits_total ?? 0;\n }\n\n // Convert credits to USD at registry rate\n const creditRate = 0.00015; // $0.00015 per credit\n const spend = creditsUsed * creditRate;\n\n return {\n serviceId: \"scrapfly\",\n spend,\n isEstimate: false,\n tier: \"live\",\n unitsUsed: creditsUsed,\n unitsTotal: creditsTotal,\n unitName: \"credits\",\n raw: {\n credits_used: creditsUsed,\n credits_total: creditsTotal,\n credit_rate: creditRate,\n ...(result.data as unknown as Record<string, unknown>),\n },\n };\n },\n};\n","import type { BillingConnector, BillingResult } from \"./base.js\";\nimport { anthropicConnector } from \"./anthropic.js\";\nimport { openaiConnector } from \"./openai.js\";\nimport { vercelConnector } from \"./vercel.js\";\nimport { scrapflyConnector } from \"./scrapfly.js\";\nimport type { TrackedService, ConfidenceTier } from \"../core/types.js\";\nimport { readGlobalConfig } from \"../core/config.js\";\nimport { getService } from \"../core/registry.js\";\n\n/** All available billing connectors, keyed by service ID. */\nconst connectors: Map<string, BillingConnector> = new Map([\n [\"anthropic\", anthropicConnector],\n [\"openai\", openaiConnector],\n [\"vercel\", vercelConnector],\n [\"scrapfly\", scrapflyConnector],\n]);\n\n/**\n * Poll spend for a single tracked service.\n * Returns the best available data based on connector availability and API keys.\n */\nexport async function pollService(\n tracked: TrackedService,\n): Promise<BillingResult> {\n const globalConfig = readGlobalConfig();\n const serviceConfig = globalConfig.services[tracked.serviceId];\n const connector = connectors.get(tracked.serviceId);\n const definition = getService(tracked.serviceId);\n\n // If we have a connector and an API key, try LIVE\n if (connector && serviceConfig?.apiKey) {\n try {\n const result = await connector.fetchSpend(\n serviceConfig.apiKey,\n serviceConfig as unknown as Record<string, string>,\n );\n if (!result.error) return result;\n // Fall through to lower tiers on error\n } catch {\n // Fall through\n }\n }\n\n // If user provided a plan cost, use CALC\n if (tracked.planCost !== undefined) {\n const now = new Date();\n const daysInMonth = new Date(\n now.getFullYear(),\n now.getMonth() + 1,\n 0,\n ).getDate();\n const dayOfMonth = now.getDate();\n const projectedSpend = (tracked.planCost / daysInMonth) * dayOfMonth;\n\n return {\n serviceId: tracked.serviceId,\n spend: projectedSpend,\n isEstimate: true,\n tier: \"calc\",\n };\n }\n\n // If service is in registry but we have no key and no plan cost\n if (definition) {\n let tier: ConfidenceTier;\n if (tracked.tierOverride) {\n tier = tracked.tierOverride;\n } else if (definition.apiTier === \"live\") {\n // Has a LIVE API but we don't have the key — mark as BLIND\n tier = \"blind\";\n } else {\n // EST, CALC, or BLIND — use the registry's declared tier\n tier = definition.apiTier;\n }\n\n return {\n serviceId: tracked.serviceId,\n spend: 0,\n isEstimate: tier !== \"live\",\n tier,\n error: tier === \"blind\" ? \"No API key configured\" : undefined,\n };\n }\n\n // Completely unknown service\n return {\n serviceId: tracked.serviceId,\n spend: 0,\n isEstimate: true,\n tier: \"blind\",\n error: \"Unknown service — not in registry\",\n };\n}\n\n/**\n * Poll all tracked services concurrently.\n * Returns results in the same order as input.\n */\nexport async function pollAllServices(\n services: TrackedService[],\n): Promise<BillingResult[]> {\n return Promise.all(services.map(pollService));\n}\n\nexport { type BillingConnector, type BillingResult } from \"./base.js\";\n","/**\n * Confidence tiers for spend tracking.\n *\n * LIVE — Real billing API data\n * CALC — Fixed monthly cost, user-entered\n * EST — Estimated from usage signals + pricing formula\n * BLIND — Detected in project, no tracking configured\n */\nexport type ConfidenceTier = \"live\" | \"calc\" | \"est\" | \"blind\" | \"excluded\";\n\nexport const CONFIDENCE_BADGES: Record<ConfidenceTier, string> = {\n live: \"✅ LIVE\",\n calc: \"🟡 CALC\",\n est: \"🟠 EST\",\n blind: \"🔴 BLIND\",\n excluded: \"⬚ SKIP\",\n};\n\n/** How a service charges — determines tracking strategy. */\nexport type BillingModel =\n | \"token_usage\" // Per-token (Anthropic, OpenAI, Gemini)\n | \"credit_pool\" // Fixed credit bucket (Scrapfly)\n | \"per_unit\" // Per-email, per-session, per-command (Resend, Browserbase, Upstash)\n | \"percentage\" // Percentage of transaction (Stripe)\n | \"flat_monthly\" // Fixed monthly subscription (PostHog, Inngest free tier)\n | \"tiered\" // Free up to X, then jumps (PostHog, Supabase)\n | \"compute\" // Compute-time based (Vercel, AWS)\n | \"unknown\";\n\n/** How cost scales — helps the agent reason about future spend. */\nexport type ScalingShape =\n | \"linear\" // Each unit costs the same\n | \"linear_burndown\" // Fixed pool, each use depletes it\n | \"tiered_jump\" // Free until threshold, then expensive\n | \"percentage\" // Proportional to revenue/volume\n | \"fixed\" // Flat monthly, no scaling\n | \"unknown\";\n\n/** A plan tier option for a service in the registry. */\nexport interface PlanTier {\n /** Human-readable plan name */\n name: string;\n /** Plan type: usage (pay-as-you-go), flat (fixed monthly), exclude (don't track) */\n type: \"usage\" | \"flat\" | \"exclude\";\n /** Monthly base cost for flat plans */\n monthlyBase?: number;\n /** Suggested starting budget for usage plans (reasonable dev-stage default) */\n suggestedBudget?: number;\n /** Whether this plan requires an API key for tracking */\n requiresKey?: boolean;\n /** Whether this is the default/most common plan */\n default?: boolean;\n /** Included units for credit-pool plans (e.g., 1M credits on Scrapfly Pro) */\n includedUnits?: number;\n /** Unit name for credit-pool plans (e.g., \"credits\", \"sessions\") */\n unitName?: string;\n}\n\n/** Risk category for service grouping in interactive init. */\nexport type ServiceRiskCategory = \"llm\" | \"usage\" | \"infra\" | \"flat\";\n\n/** A service definition from the registry. */\nexport interface ServiceDefinition {\n /** Unique service identifier */\n id: string;\n /** Human-readable name */\n name: string;\n /** Package names in npm/pip that indicate this service */\n packageNames: string[];\n /** Env var patterns that indicate this service */\n envPatterns: string[];\n /** Import patterns to scan for (regex strings) */\n importPatterns: string[];\n /** Keywords that indicate mentions in prompts */\n mentionKeywords: string[];\n /** Billing model */\n billingModel: BillingModel;\n /** How cost scales */\n scalingShape: ScalingShape;\n /** What tier of tracking is available */\n apiTier: ConfidenceTier;\n /** Billing API endpoint, if available */\n apiEndpoint?: string;\n /** Pricing details */\n pricing?: {\n /** Human-readable formula */\n formula?: string;\n /** Rate per unit, if applicable */\n unitRate?: number;\n /** Unit name (token, credit, email, session, etc.) */\n unitName?: string;\n /** Monthly base cost, if flat */\n monthlyBase?: number;\n };\n /** Known gotchas that affect cost */\n gotchas?: string[];\n /** Alternative services (free or cheaper) */\n alternatives?: string[];\n /** Documentation URL */\n docsUrl?: string;\n /** Last time pricing was verified */\n lastVerified?: string;\n /** Notes about recent pricing changes */\n pricingNotes?: string;\n /** Available plan tiers for interactive init */\n plans?: PlanTier[];\n /** Whether the plan can be auto-detected from an API key */\n autoDetectPlan?: boolean;\n}\n\n/** A tracked service instance — a service definition + user config. */\nexport interface TrackedService {\n /** Service definition ID */\n serviceId: string;\n /** How this service was detected */\n detectedVia: DetectionSource[];\n /** User-configured monthly budget */\n budget?: number;\n /** Whether the user has provided an API/billing key */\n hasApiKey: boolean;\n /** Override confidence tier (e.g., user provided billing key upgrades to LIVE) */\n tierOverride?: ConfidenceTier;\n /** User-entered monthly plan cost (for CALC tier) */\n planCost?: number;\n /** When this service was first detected */\n firstDetected: string;\n /** Explicitly excluded from tracking by user */\n excluded?: boolean;\n /** Plan name selected during interactive init */\n planName?: string;\n /** For credit-pool services: the unit allowance included in the plan */\n allowance?: {\n /** Total units included in the plan (e.g., 1000000 credits) */\n included: number;\n /** Unit name (e.g., \"credits\", \"sessions\", \"commands\") */\n unitName: string;\n };\n}\n\nexport type DetectionSource =\n | \"package_json\"\n | \"env_var\"\n | \"import_scan\"\n | \"prompt_mention\"\n | \"git_diff\"\n | \"manual\";\n\n/** A spend snapshot for a single service at a point in time. */\nexport interface SpendSnapshot {\n serviceId: string;\n /** Current period spend (or estimate) */\n spend: number;\n /** Is the spend figure exact or estimated? */\n isEstimate: boolean;\n /** Confidence tier for this reading */\n tier: ConfidenceTier;\n /** Budget allocated */\n budget?: number;\n /** Percentage of budget consumed */\n budgetPercent?: number;\n /** Budget status */\n status: \"healthy\" | \"caution\" | \"over\" | \"unknown\";\n /** Human-readable status label */\n statusLabel: string;\n /** Raw data from billing API, if available */\n raw?: Record<string, unknown>;\n /** Timestamp of this snapshot */\n timestamp: string;\n /** For credit-pool services: unit consumption tracking */\n allowance?: {\n /** Units consumed this period */\n used: number;\n /** Total units included in plan */\n included: number;\n /** Unit name (e.g., \"credits\") */\n unitName: string;\n /** Percentage of allowance consumed */\n percent: number;\n };\n}\n\n/** The full spend brief, injected at session start. */\nexport interface SpendBrief {\n projectName: string;\n generatedAt: string;\n period: string;\n services: SpendSnapshot[];\n totalSpend: number;\n totalIsEstimate: boolean;\n estimateMargin: number;\n untrackedCount: number;\n alerts: SpendAlert[];\n}\n\nexport interface SpendAlert {\n serviceId: string;\n type: \"over_budget\" | \"near_budget\" | \"new_service\" | \"stale_data\" | \"blind_service\";\n message: string;\n severity: \"warning\" | \"critical\" | \"info\";\n}\n\n/** Ledger entry — one row in spend-ledger.md */\nexport interface LedgerEntry {\n serviceId: string;\n serviceName: string;\n spend: number;\n isEstimate: boolean;\n tier: ConfidenceTier;\n budget?: number;\n statusLabel: string;\n}\n\n/** Event logged to events.jsonl */\nexport interface SpendEvent {\n timestamp: string;\n sessionId: string;\n type:\n | \"session_start\"\n | \"session_end\"\n | \"service_detected\"\n | \"service_mentioned\"\n | \"spend_polled\"\n | \"budget_alert\"\n | \"ledger_written\"\n | \"cost_impact\";\n data: Record<string, unknown>;\n}\n\n/** A cost impact estimate for a file change. */\nexport interface CostImpact {\n serviceId: string;\n serviceName: string;\n filePath: string;\n /** Number of SDK call sites found */\n callCount: number;\n /** Detected multipliers (loops, .map(), etc.) */\n multipliers: string[];\n /** Effective multiplier applied to call count */\n multiplierFactor: number;\n /** Estimated monthly invocations */\n monthlyInvocations: number;\n /** Low estimate monthly cost */\n costLow: number;\n /** High estimate monthly cost */\n costHigh: number;\n /** Gotcha-based cost range explanation */\n rangeExplanation?: string;\n}\n\n/**\n * Hook input — the JSON received via stdin from Claude Code.\n * Subset of fields we care about.\n */\nexport interface HookInput {\n session_id: string;\n transcript_path?: string;\n cwd: string;\n hook_event_name: string;\n // SessionStart\n source?: string;\n // UserPromptSubmit\n prompt?: string;\n // PostToolUse\n tool_name?: string;\n tool_input?: {\n file_path?: string;\n command?: string;\n content?: string;\n old_string?: string;\n new_string?: string;\n };\n}\n\n/**\n * Hook output — the JSON we write to stdout for Claude Code.\n */\nexport interface HookOutput {\n hookSpecificOutput?: {\n hookEventName: string;\n additionalContext?: string;\n };\n}\n","import type {\n SpendBrief,\n SpendSnapshot,\n SpendAlert,\n ConfidenceTier,\n} from \"./types.js\";\nimport { CONFIDENCE_BADGES } from \"./types.js\";\n\n/**\n * Format a spend brief as a text block for injection into Claude's context.\n */\nexport function formatBrief(brief: SpendBrief): string {\n const lines: string[] = [];\n const width = 62;\n const hrDouble = \"═\".repeat(width);\n const hrSingle = \"─\".repeat(width - 4);\n\n lines.push(`╔${hrDouble}╗`);\n lines.push(\n `║ BURNWATCH — ${brief.projectName} — ${brief.period}`.padEnd(\n width + 1,\n ) + \"║\",\n );\n lines.push(`╠${hrDouble}╣`);\n\n // Header\n lines.push(\n formatRow(\"Service\", \"Spend\", \"Conf\", \"Budget\", \"Left\", width),\n );\n lines.push(`║ ${hrSingle} ║`);\n\n // Service rows\n for (const svc of brief.services) {\n const spendStr = svc.isEstimate\n ? `~$${svc.spend.toFixed(2)}`\n : `$${svc.spend.toFixed(2)}`;\n const badge = CONFIDENCE_BADGES[svc.tier];\n const budgetStr = svc.budget ? `$${svc.budget}` : \"—\";\n const leftStr = formatLeft(svc);\n\n lines.push(formatRow(svc.serviceId, spendStr, badge, budgetStr, leftStr, width));\n\n // Show allowance consumption for credit-pool services\n if (svc.allowance) {\n const usedStr = formatCompact(svc.allowance.used);\n const totalStr = formatCompact(svc.allowance.included);\n const pctStr = svc.allowance.percent.toFixed(0);\n const warn = svc.allowance.percent >= 75 ? \" ⚠️\" : \"\";\n lines.push(\n `║ ↳ ${usedStr}/${totalStr} ${svc.allowance.unitName} (${pctStr}%)${warn}`.padEnd(width + 1) + \"║\",\n );\n }\n }\n\n // Footer\n lines.push(`╠${hrDouble}╣`);\n const totalStr = brief.totalIsEstimate\n ? `~$${brief.totalSpend.toFixed(2)}`\n : `$${brief.totalSpend.toFixed(2)}`;\n const marginStr = brief.estimateMargin > 0\n ? ` Est margin: ±$${brief.estimateMargin.toFixed(0)}`\n : \"\";\n const untrackedStr =\n brief.untrackedCount > 0\n ? `Untracked: ${brief.untrackedCount} ⚠️`\n : `Untracked: 0 ✅`;\n\n lines.push(\n `║ TOTAL: ${totalStr} ${untrackedStr}${marginStr}`.padEnd(\n width + 1,\n ) + \"║\",\n );\n\n // Alerts\n for (const alert of brief.alerts) {\n const icon = alert.severity === \"critical\" ? \"🚨\" : \"⚠️\";\n lines.push(\n `║ ${icon} ${alert.message}`.padEnd(width + 1) + \"║\",\n );\n }\n\n lines.push(`╚${hrDouble}╝`);\n\n return lines.join(\"\\n\");\n}\n\n/**\n * Format a single-service spend card for injection on mention.\n */\nexport function formatSpendCard(snapshot: SpendSnapshot): string {\n const badge = CONFIDENCE_BADGES[snapshot.tier];\n const spendStr = snapshot.isEstimate\n ? `~$${snapshot.spend.toFixed(2)}`\n : `$${snapshot.spend.toFixed(2)}`;\n const budgetStr = snapshot.budget\n ? `Budget: $${snapshot.budget}`\n : \"No budget set\";\n const statusStr = snapshot.statusLabel;\n\n const lines = [\n `[BURNWATCH] ${snapshot.serviceId} — current period`,\n ` Spend: ${spendStr} | ${budgetStr} | ${statusStr}`,\n ` Confidence: ${badge}`,\n ];\n\n if (snapshot.status === \"over\" && snapshot.budgetPercent) {\n lines.push(\n ` ⚠️ ${snapshot.budgetPercent.toFixed(0)}% of budget consumed`,\n );\n }\n\n return lines.join(\"\\n\");\n}\n\n/**\n * Build a SpendBrief from snapshots and project config.\n */\nexport function buildBrief(\n projectName: string,\n snapshots: SpendSnapshot[],\n blindCount: number,\n): SpendBrief {\n const now = new Date();\n const period = now.toLocaleDateString(\"en-US\", {\n month: \"long\",\n year: \"numeric\",\n });\n\n let totalSpend = 0;\n let hasEstimates = false;\n let estimateMargin = 0;\n const alerts: SpendAlert[] = [];\n\n for (const snap of snapshots) {\n totalSpend += snap.spend;\n if (snap.isEstimate) {\n hasEstimates = true;\n estimateMargin += snap.spend * 0.15; // ±15% margin on estimates\n }\n\n if (snap.status === \"over\") {\n alerts.push({\n serviceId: snap.serviceId,\n type: \"over_budget\",\n message: `${snap.serviceId.toUpperCase()} ${snap.budgetPercent?.toFixed(0) ?? \"?\"}% OVER BUDGET — review before use`,\n severity: \"critical\",\n });\n } else if (snap.status === \"caution\" && snap.budgetPercent && snap.budgetPercent >= 80) {\n alerts.push({\n serviceId: snap.serviceId,\n type: \"near_budget\",\n message: `${snap.serviceId} at ${snap.budgetPercent.toFixed(0)}% of budget`,\n severity: \"warning\",\n });\n }\n }\n\n if (blindCount > 0) {\n alerts.push({\n serviceId: \"_blind\",\n type: \"blind_service\",\n message: `${blindCount} service${blindCount > 1 ? \"s\" : \"\"} detected but untracked - run 'burnwatch init' to configure`,\n severity: \"warning\",\n });\n }\n\n return {\n projectName,\n generatedAt: now.toISOString(),\n period,\n services: snapshots,\n totalSpend,\n totalIsEstimate: hasEstimates,\n estimateMargin,\n untrackedCount: blindCount,\n alerts,\n };\n}\n\n// --- Helpers ---\n\nfunction formatRow(\n service: string,\n spend: string,\n conf: string,\n budget: string,\n left: string,\n width: number,\n): string {\n const row = ` ${service.padEnd(14)} ${spend.padEnd(11)} ${conf.padEnd(7)} ${budget.padEnd(7)} ${left}`;\n return `║${row}`.padEnd(width + 1) + \"║\";\n}\n\nfunction formatLeft(snap: SpendSnapshot): string {\n if (!snap.budget) return \"—\";\n if (snap.status === \"over\") return \"⚠️ OVR\";\n if (snap.budgetPercent !== undefined) {\n const remaining = 100 - snap.budgetPercent;\n return `${remaining.toFixed(0)}%`;\n }\n return \"—\";\n}\n\n/**\n * Build a SpendSnapshot from tracked service data.\n */\nexport function buildSnapshot(\n serviceId: string,\n tier: ConfidenceTier,\n spend: number,\n budget?: number,\n allowanceData?: { used: number; included: number; unitName: string },\n): SpendSnapshot {\n const isEstimate = tier === \"est\" || tier === \"calc\";\n const budgetPercent = budget ? (spend / budget) * 100 : undefined;\n\n let status: SpendSnapshot[\"status\"] = \"unknown\";\n let statusLabel = \"no budget\";\n\n if (budget) {\n if (budgetPercent! > 100) {\n status = \"over\";\n statusLabel = `⚠️ ${budgetPercent!.toFixed(0)}% over`;\n } else if (budgetPercent! >= 75) {\n status = \"caution\";\n statusLabel = `${(100 - budgetPercent!).toFixed(0)}% — caution`;\n } else {\n status = \"healthy\";\n statusLabel = `${(100 - budgetPercent!).toFixed(0)}% — healthy`;\n }\n }\n\n // For credit-pool services, allowance consumption drives the status\n let allowance: SpendSnapshot[\"allowance\"] | undefined;\n if (allowanceData && allowanceData.included > 0) {\n const percent = (allowanceData.used / allowanceData.included) * 100;\n allowance = { ...allowanceData, percent };\n\n // Override status based on allowance consumption\n if (percent > 100) {\n status = \"over\";\n statusLabel = `⚠️ ${percent.toFixed(0)}% of ${formatCompact(allowanceData.included)} ${allowanceData.unitName} used`;\n } else if (percent >= 75) {\n status = \"caution\";\n statusLabel = `${formatCompact(allowanceData.included - allowanceData.used)} ${allowanceData.unitName} left — caution`;\n } else {\n status = \"healthy\";\n statusLabel = `${formatCompact(allowanceData.included - allowanceData.used)} ${allowanceData.unitName} left`;\n }\n } else if (tier === \"calc\" && budget) {\n statusLabel = `flat — on plan`;\n status = \"healthy\";\n }\n\n return {\n serviceId,\n spend,\n isEstimate,\n tier,\n budget,\n budgetPercent,\n status,\n statusLabel,\n timestamp: new Date().toISOString(),\n allowance,\n };\n}\n\nfunction formatCompact(n: number): string {\n if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(n % 1_000_000 === 0 ? 0 : 1)}M`;\n if (n >= 1_000) return `${(n / 1_000).toFixed(n % 1_000 === 0 ? 0 : 1)}K`;\n return String(n);\n}\n","import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type { SpendBrief, SpendEvent } from \"./types.js\";\nimport { CONFIDENCE_BADGES } from \"./types.js\";\nimport { projectConfigDir, projectDataDir } from \"./config.js\";\n\n/**\n * Write the spend ledger as a human-readable markdown file.\n * Designed to be git-committable and readable in 10 seconds.\n */\nexport function writeLedger(brief: SpendBrief, projectRoot?: string): void {\n const now = new Date();\n const lines: string[] = [];\n\n lines.push(`# Burnwatch Ledger — ${brief.projectName}`);\n lines.push(`Last updated: ${now.toISOString()}`);\n lines.push(\"\");\n lines.push(`## This Month (${brief.period})`);\n lines.push(\"\");\n lines.push(\"| Service | Spend | Conf | Budget | Status |\");\n lines.push(\"|---------|-------|------|--------|--------|\");\n\n for (const svc of brief.services) {\n const spendStr = svc.isEstimate\n ? `~$${svc.spend.toFixed(2)}`\n : `$${svc.spend.toFixed(2)}`;\n const badge = CONFIDENCE_BADGES[svc.tier];\n const budgetStr = svc.budget ? `$${svc.budget}` : \"—\";\n\n lines.push(\n `| ${svc.serviceId} | ${spendStr} | ${badge} | ${budgetStr} | ${svc.statusLabel} |`,\n );\n }\n\n // Add projected impact row if session impacts exist in alerts\n const impactAlert = brief.alerts.find(\n (a) => a.serviceId === \"_session_impact\",\n );\n if (impactAlert) {\n lines.push(\n `| _projected impact_ | — | 📈 EST | — | ${impactAlert.message} |`,\n );\n }\n\n lines.push(\"\");\n const totalStr = brief.totalIsEstimate\n ? `~$${brief.totalSpend.toFixed(2)}`\n : `$${brief.totalSpend.toFixed(2)}`;\n const marginStr =\n brief.estimateMargin > 0\n ? ` (±$${brief.estimateMargin.toFixed(0)} estimated margin)`\n : \"\";\n lines.push(`## TOTAL: ${totalStr}${marginStr}`);\n lines.push(`## Untracked services: ${brief.untrackedCount}`);\n lines.push(\"\");\n\n if (brief.alerts.length > 0) {\n lines.push(\"## Alerts\");\n for (const alert of brief.alerts) {\n const icon = alert.severity === \"critical\" ? \"🚨\" : \"⚠️\";\n lines.push(`- ${icon} ${alert.message}`);\n }\n lines.push(\"\");\n }\n\n const ledgerPath = path.join(\n projectConfigDir(projectRoot),\n \"spend-ledger.md\",\n );\n fs.mkdirSync(path.dirname(ledgerPath), { recursive: true });\n fs.writeFileSync(ledgerPath, lines.join(\"\\n\") + \"\\n\", \"utf-8\");\n}\n\n/**\n * Append an event to the append-only event log.\n */\nexport function logEvent(event: SpendEvent, projectRoot?: string): void {\n const logPath = path.join(projectDataDir(projectRoot), \"events.jsonl\");\n fs.mkdirSync(path.dirname(logPath), { recursive: true });\n fs.appendFileSync(logPath, JSON.stringify(event) + \"\\n\", \"utf-8\");\n}\n\n/**\n * Read recent events from the event log.\n */\nexport function readRecentEvents(\n count: number,\n projectRoot?: string,\n): SpendEvent[] {\n const logPath = path.join(projectDataDir(projectRoot), \"events.jsonl\");\n try {\n const raw = fs.readFileSync(logPath, \"utf-8\");\n const lines = raw.trim().split(\"\\n\").filter(Boolean);\n return lines\n .slice(-count)\n .map((line) => JSON.parse(line) as SpendEvent);\n } catch {\n return [];\n }\n}\n\n/**\n * Save a spend snapshot to the snapshots directory.\n * Used for delta computation across sessions.\n */\nexport function saveSnapshot(brief: SpendBrief, projectRoot?: string): void {\n const snapshotDir = path.join(projectDataDir(projectRoot), \"snapshots\");\n fs.mkdirSync(snapshotDir, { recursive: true });\n const filename = `snapshot-${new Date().toISOString().replace(/[:.]/g, \"-\")}.json`;\n fs.writeFileSync(\n path.join(snapshotDir, filename),\n JSON.stringify(brief, null, 2) + \"\\n\",\n \"utf-8\",\n );\n}\n\n/**\n * Read the most recent snapshot, if any.\n */\nexport function readLatestSnapshot(\n projectRoot?: string,\n): SpendBrief | null {\n const snapshotDir = path.join(projectDataDir(projectRoot), \"snapshots\");\n try {\n const files = fs\n .readdirSync(snapshotDir)\n .filter((f) => f.startsWith(\"snapshot-\") && f.endsWith(\".json\"))\n .sort()\n .reverse();\n\n if (files.length === 0) return null;\n\n const raw = fs.readFileSync(\n path.join(snapshotDir, files[0]!),\n \"utf-8\",\n );\n return JSON.parse(raw) as SpendBrief;\n } catch {\n return null;\n }\n}\n","/**\n * Interactive init flow for burnwatch.\n *\n * Conducts a per-service interview: detects what it can automatically\n * (existing API keys, env vars), asks for plan selection, collects\n * API keys for LIVE tracking, and ensures every service exits with\n * a budget. No skipping.\n */\n\nimport * as readline from \"node:readline\";\nimport type {\n ServiceDefinition,\n PlanTier,\n TrackedService,\n ServiceRiskCategory,\n} from \"./core/types.js\";\nimport type { DetectionResult } from \"./detection/detector.js\";\nimport { readGlobalConfig, writeGlobalConfig } from \"./core/config.js\";\nimport { probeService, hasProbe } from \"./probes.js\";\n\n/** Format large numbers with K/M suffixes */\nfunction formatUnits(n: number): string {\n if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(n % 1_000_000 === 0 ? 0 : 1)}M`;\n if (n >= 1_000) return `${(n / 1_000).toFixed(n % 1_000 === 0 ? 0 : 1)}K`;\n return String(n);\n}\n\n/** Risk categories in display order: LLMs first, then usage-based, infra, flat-rate */\nconst RISK_ORDER: ServiceRiskCategory[] = [\"llm\", \"usage\", \"infra\", \"flat\"];\n\nconst RISK_LABELS: Record<ServiceRiskCategory, string> = {\n llm: \"LLM / AI Services (highest variable cost)\",\n usage: \"Usage-Based Services\",\n infra: \"Infrastructure & Compute\",\n flat: \"Flat-Rate / Free Tier Services\",\n};\n\n/** Where to find API keys for LIVE-capable services */\nconst API_KEY_HINTS: Record<string, string> = {\n anthropic: \"Admin key: console.anthropic.com -> Settings -> Admin API Keys\",\n openai: \"Org key: platform.openai.com -> Settings -> API Keys\",\n vercel: \"Token: vercel.com/account/tokens\",\n supabase: \"Service role key: supabase.com/dashboard -> Settings -> API\",\n stripe: \"Secret key: dashboard.stripe.com -> Developers -> API Keys\",\n scrapfly: \"API key: scrapfly.io/dashboard\",\n};\n\n/** Map service IDs to risk categories */\nfunction classifyRisk(service: ServiceDefinition): ServiceRiskCategory {\n if (service.billingModel === \"token_usage\") return \"llm\";\n if (\n service.billingModel === \"credit_pool\" ||\n service.billingModel === \"percentage\" ||\n service.billingModel === \"per_unit\"\n )\n return \"usage\";\n if (service.billingModel === \"compute\") return \"infra\";\n return \"flat\";\n}\n\n/** Group detection results by risk category */\nfunction groupByRisk(\n detected: DetectionResult[],\n): Map<ServiceRiskCategory, DetectionResult[]> {\n const groups = new Map<ServiceRiskCategory, DetectionResult[]>();\n for (const cat of RISK_ORDER) {\n groups.set(cat, []);\n }\n\n for (const det of detected) {\n const cat = classifyRisk(det.service);\n groups.get(cat)!.push(det);\n }\n\n return groups;\n}\n\n/** Prompt the user with a question and return their answer */\nfunction ask(rl: readline.Interface, question: string): Promise<string> {\n return new Promise((resolve) => {\n rl.question(question, (answer) => {\n resolve(answer.trim());\n });\n });\n}\n\n/** Scan environment for API keys matching service env patterns */\nfunction findEnvKey(service: ServiceDefinition): string | undefined {\n for (const pattern of service.envPatterns) {\n const val = process.env[pattern];\n if (val && val.length > 0) return val;\n }\n return undefined;\n}\n\nexport interface InteractiveInitResult {\n services: Record<string, TrackedService>;\n}\n\n/**\n * Auto-configure all services without prompts.\n *\n * Applies the same logic as the interactive interview but picks\n * defaults automatically: default plan, env var keys, budget = plan cost.\n * Used when stdin is not a TTY (e.g., Claude Code, piped input).\n */\nexport async function autoConfigureServices(\n detected: DetectionResult[],\n): Promise<InteractiveInitResult> {\n const services: Record<string, TrackedService> = {};\n const groups = groupByRisk(detected);\n const globalConfig = readGlobalConfig();\n\n console.log(\n `\\n Found ${detected.length} paid service${detected.length !== 1 ? \"s\" : \"\"}. Auto-configuring with defaults.\\n`,\n );\n console.log(\" Run 'burnwatch init' from your terminal for interactive setup.\\n\");\n\n for (const category of RISK_ORDER) {\n const group = groups.get(category)!;\n if (group.length === 0) continue;\n\n console.log(` ${RISK_LABELS[category]}`);\n\n for (const det of group) {\n const service = det.service;\n const plans = service.plans ?? [];\n const defaultPlan = plans.find((p) => p.default) ?? plans[0];\n\n const tracked: TrackedService = {\n serviceId: service.id,\n detectedVia: det.sources,\n hasApiKey: false,\n firstDetected: new Date().toISOString(),\n budget: 0,\n };\n\n if (defaultPlan && defaultPlan.type !== \"exclude\") {\n tracked.planName = defaultPlan.name;\n\n if (defaultPlan.type === \"flat\" && defaultPlan.monthlyBase !== undefined) {\n tracked.planCost = defaultPlan.monthlyBase;\n tracked.budget = defaultPlan.monthlyBase;\n } else if (defaultPlan.suggestedBudget !== undefined) {\n tracked.budget = defaultPlan.suggestedBudget;\n }\n\n // Credit-pool services: track unit allowance, not just dollars\n if (defaultPlan.includedUnits !== undefined && defaultPlan.unitName) {\n tracked.allowance = {\n included: defaultPlan.includedUnits,\n unitName: defaultPlan.unitName,\n };\n }\n }\n\n // Check for existing API key in global config or environment\n const existingKey = globalConfig.services[service.id]?.apiKey;\n const envKey = findEnvKey(service);\n let keySource = \"\";\n let apiKey: string | undefined;\n\n if (existingKey) {\n tracked.hasApiKey = true;\n apiKey = existingKey;\n keySource = \" (key: global config)\";\n } else if (envKey) {\n tracked.hasApiKey = true;\n apiKey = envKey;\n if (!globalConfig.services[service.id]) {\n globalConfig.services[service.id] = {};\n }\n globalConfig.services[service.id]!.apiKey = envKey;\n keySource = ` (key: ${service.envPatterns[0]})`;\n }\n\n // If we have a key and a probe, try to auto-detect the plan\n if (apiKey && hasProbe(service.id)) {\n try {\n const probe = await probeService(service.id, apiKey, plans);\n if (probe?.matchedPlan && probe.confidence === \"high\") {\n const mp = probe.matchedPlan;\n tracked.planName = mp.name;\n if (mp.type === \"flat\" && mp.monthlyBase !== undefined) {\n tracked.planCost = mp.monthlyBase;\n tracked.budget = mp.monthlyBase;\n } else if (mp.suggestedBudget !== undefined) {\n tracked.budget = mp.suggestedBudget;\n }\n if (mp.includedUnits !== undefined && mp.unitName) {\n tracked.allowance = { included: mp.includedUnits, unitName: mp.unitName };\n }\n }\n } catch {\n // Probe failed — use defaults\n }\n }\n\n const tierLabel = tracked.hasApiKey\n ? \"LIVE\"\n : tracked.planCost !== undefined\n ? \"CALC\"\n : \"BLIND\";\n const planStr = tracked.planName ? ` ${tracked.planName}` : \"\";\n const trackingStr = tracked.allowance\n ? `$${tracked.budget}/mo | ${formatUnits(tracked.allowance.included)} ${tracked.allowance.unitName}`\n : `$${tracked.budget}/mo`;\n console.log(\n ` ${service.name}:${planStr} | ${tierLabel} | ${trackingStr}${keySource}`,\n );\n\n services[service.id] = tracked;\n }\n console.log(\"\");\n }\n\n // Summary\n const trackedList = Object.values(services);\n const liveCount = trackedList.filter((s) => s.hasApiKey).length;\n const totalBudget = trackedList.reduce((sum, s) => sum + (s.budget ?? 0), 0);\n\n console.log(\" \" + \"-\".repeat(48));\n console.log(` ${trackedList.length} services configured | Total budget: $${totalBudget}/mo`);\n if (liveCount > 0) console.log(` ${liveCount} with real-time billing (LIVE)`);\n console.log(\"\");\n\n // Save discovered keys\n writeGlobalConfig(globalConfig);\n\n return { services };\n}\n\n/**\n * Run the interactive init flow.\n *\n * For each detected service:\n * 1. Ask which plan they're on\n * 2. If LIVE-capable, check for existing key or ask for one\n * 3. Set budget (defaults to plan cost, $0 for free - never skipped)\n */\nexport async function runInteractiveInit(\n detected: DetectionResult[],\n): Promise<InteractiveInitResult> {\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n const services: Record<string, TrackedService> = {};\n const groups = groupByRisk(detected);\n const globalConfig = readGlobalConfig();\n\n console.log(\n `\\n Found ${detected.length} paid service${detected.length !== 1 ? \"s\" : \"\"}. Let's configure each one.\\n`,\n );\n\n for (const category of RISK_ORDER) {\n const group = groups.get(category)!;\n if (group.length === 0) continue;\n\n console.log(`\\n ${RISK_LABELS[category]}`);\n console.log(\" \" + \"-\".repeat(48));\n\n for (const det of group) {\n const service = det.service;\n const plans = service.plans;\n\n console.log(`\\n ${service.name}`);\n console.log(` Detected via: ${det.details.join(\", \")}`);\n\n if (!plans || plans.length === 0) {\n // No plans defined - basic tracking with $0 budget\n services[service.id] = {\n serviceId: service.id,\n detectedVia: det.sources,\n hasApiKey: false,\n firstDetected: new Date().toISOString(),\n budget: 0,\n };\n console.log(\" -> Configured (no plan tiers in registry, budget: $0)\");\n continue;\n }\n\n // --- Step 1: Find API key (env vars, global config, or ask) ---\n let apiKey: string | undefined;\n\n const existingKey = globalConfig.services[service.id]?.apiKey;\n const envKey = findEnvKey(service);\n\n if (existingKey) {\n apiKey = existingKey;\n console.log(` API key: found in global config`);\n } else if (envKey) {\n apiKey = envKey;\n console.log(` API key: found in environment (${service.envPatterns[0]})`);\n if (!globalConfig.services[service.id]) {\n globalConfig.services[service.id] = {};\n }\n globalConfig.services[service.id]!.apiKey = envKey;\n }\n\n // --- Step 2: If we have a key AND a probe exists, try auto-discovery ---\n let chosen: PlanTier | undefined;\n\n if (apiKey && hasProbe(service.id)) {\n console.log(\" Probing API...\");\n const probe = await probeService(service.id, apiKey, plans);\n\n if (probe) {\n console.log(` ${probe.summary}`);\n\n if (probe.confidence === \"high\" && probe.matchedPlan) {\n // Plan detected — confirm with user\n const plan = probe.matchedPlan;\n const costStr = plan.monthlyBase !== undefined ? `$${plan.monthlyBase}/mo` : \"variable\";\n const unitsStr = plan.includedUnits && plan.unitName\n ? `, ${formatUnits(plan.includedUnits)} ${plan.unitName}`\n : \"\";\n const confirm = await ask(\n rl,\n ` Detected: ${plan.name} (${costStr}${unitsStr}). Correct? [Y/n]: `,\n );\n if (confirm === \"\" || confirm.toLowerCase().startsWith(\"y\")) {\n chosen = plan;\n }\n } else if (probe.confidence === \"medium\") {\n // Usage data found but plan not certain — show it, still ask\n if (probe.usage?.spend !== undefined) {\n console.log(` Current spend: $${probe.usage.spend.toFixed(2)}`);\n }\n // Fall through to plan selection with context\n }\n // \"low\" confidence — key works but no useful data, fall through\n }\n }\n\n // --- Step 3: If no auto-detect or user said no, show plan list ---\n if (!chosen) {\n const defaultIndex = plans.findIndex((p) => p.default);\n console.log(\"\");\n for (let i = 0; i < plans.length; i++) {\n const plan = plans[i]!;\n const marker = i === defaultIndex ? \" *\" : \"\";\n const costStr =\n plan.type === \"exclude\"\n ? \"\"\n : plan.monthlyBase !== undefined\n ? ` - $${plan.monthlyBase}/mo`\n : \" - variable\";\n console.log(` ${i + 1}) ${plan.name}${costStr}${marker}`);\n }\n\n const defaultChoice =\n defaultIndex >= 0 ? String(defaultIndex + 1) : \"1\";\n const answer = await ask(\n rl,\n ` Which plan? [${defaultChoice}]: `,\n );\n\n const choiceIndex = (answer === \"\" ? parseInt(defaultChoice) : parseInt(answer)) - 1;\n chosen =\n plans[choiceIndex] ?? plans[defaultIndex >= 0 ? defaultIndex : 0]!;\n }\n\n if (chosen.type === \"exclude\") {\n services[service.id] = {\n serviceId: service.id,\n detectedVia: det.sources,\n hasApiKey: false,\n firstDetected: new Date().toISOString(),\n excluded: true,\n planName: chosen.name,\n };\n console.log(` -> ${service.name}: excluded`);\n continue;\n }\n\n const tracked: TrackedService = {\n serviceId: service.id,\n detectedVia: det.sources,\n hasApiKey: !!apiKey,\n firstDetected: new Date().toISOString(),\n planName: chosen.name,\n };\n\n if (chosen.type === \"flat\" && chosen.monthlyBase !== undefined) {\n tracked.planCost = chosen.monthlyBase;\n }\n\n // Credit-pool services: track unit allowance\n if (chosen.includedUnits !== undefined && chosen.unitName) {\n tracked.allowance = {\n included: chosen.includedUnits,\n unitName: chosen.unitName,\n };\n }\n\n // --- Step 4: If we still don't have a key, offer to provide one ---\n if (!apiKey && hasProbe(service.id)) {\n const hint = API_KEY_HINTS[service.id];\n if (hint) console.log(` ${hint}`);\n const keyAnswer = await ask(\n rl,\n ` API key for real-time tracking (Enter to skip): `,\n );\n if (keyAnswer) {\n tracked.hasApiKey = true;\n apiKey = keyAnswer;\n if (!globalConfig.services[service.id]) {\n globalConfig.services[service.id] = {};\n }\n globalConfig.services[service.id]!.apiKey = keyAnswer;\n\n // Now that we have a key, probe to enrich tracking data\n if (hasProbe(service.id)) {\n const probe = await probeService(service.id, keyAnswer, plans);\n if (probe?.usage) {\n console.log(` ${probe.summary}`);\n }\n }\n }\n }\n\n // --- Step 5: Budget (always set, never skip) ---\n const defaultBudget = chosen.monthlyBase ?? chosen.suggestedBudget ?? 0;\n\n const budgetAnswer = await ask(\n rl,\n ` Monthly budget [$${defaultBudget}]: $`,\n );\n if (budgetAnswer) {\n const parsed = parseFloat(budgetAnswer);\n tracked.budget = !isNaN(parsed) ? parsed : defaultBudget;\n } else {\n tracked.budget = defaultBudget;\n }\n\n services[service.id] = tracked;\n\n const tierLabel = tracked.hasApiKey\n ? \"LIVE\"\n : tracked.planCost !== undefined\n ? \"CALC\"\n : \"BLIND\";\n const allowanceStr = tracked.allowance\n ? ` | ${formatUnits(tracked.allowance.included)} ${tracked.allowance.unitName}`\n : \"\";\n console.log(\n ` -> ${service.name}: ${tracked.planName} | ${tierLabel} | $${tracked.budget}/mo${allowanceStr}`,\n );\n }\n }\n\n // --- Summary ---\n const tracked = Object.values(services).filter((s) => !s.excluded);\n const excluded = Object.values(services).filter((s) => s.excluded);\n const liveCount = tracked.filter((s) => s.hasApiKey).length;\n const totalBudget = tracked.reduce((sum, s) => sum + (s.budget ?? 0), 0);\n\n console.log(\"\\n \" + \"=\".repeat(48));\n console.log(` ${tracked.length} services configured`);\n if (liveCount > 0) console.log(` ${liveCount} with real-time billing (LIVE)`);\n if (tracked.length - liveCount > 0) console.log(` ${tracked.length - liveCount} estimated/calculated`);\n if (excluded.length > 0) console.log(` ${excluded.length} excluded`);\n console.log(` Total monthly budget: $${totalBudget}`);\n console.log(\" \" + \"=\".repeat(48));\n\n // Save any collected API keys\n writeGlobalConfig(globalConfig);\n\n rl.close();\n\n return { services };\n}\n"],"mappings":"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","names":["url","fs","path","fs","path","fs","path","url","url","url","url","totalStr","fs","path","resolve","tracked","config"]}
package/llms.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  # burnwatch
2
2
 
3
- > Passive cost memory for vibe coding. Detects paid services in your project, tracks spend across AI-native tools, and injects budget context into Claude Code sessions so the agent can factor cost into every recommendation.
3
+ > Passive cost memory for AI-assisted development. Detects paid services in your project, tracks spend across AI-native tools, and injects budget context into Claude Code sessions so the agent can factor cost into every recommendation.
4
4
 
5
5
  burnwatch is an open-source CLI and Claude Code hooks integration that solves the problem of unpredictable SaaS costs in agentic development workflows. It passively monitors your project for paid services (via package.json, env vars, import statements, and prompt mentions), tracks spend through billing APIs where available, and injects real-time cost awareness into AI coding sessions.
6
6
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "burnwatch",
3
- "version": "0.8.1",
4
- "description": "Passive cost memory for vibe coding — detects paid services, tracks spend, injects budget context into your AI coding sessions.",
3
+ "version": "0.9.0",
4
+ "description": "Passive cost memory for AI-assisted development — detects paid services, tracks spend, injects budget context into your AI coding sessions.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "burnwatch": "./dist/cli.js"