burnwatch 0.4.0 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,14 @@ All notable changes to burnwatch will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.4.2] - 2026-03-24
9
+
10
+ ### Fixed
11
+
12
+ - **Init is re-runnable**: `burnwatch init` no longer early-returns on already-initialized projects. Re-running init re-detects services and walks through interactive setup again, so users who initialized before v0.4.0 can configure budgets without manually running `burnwatch add` 14 times.
13
+ - **Budget prompt fires for all services**: Budget prompt was gated inside the `requiresKey` block - services without API key requirements never got asked. Now every non-excluded service gets a budget prompt during interactive init.
14
+ - **Untracked message is actionable**: Changed circular "run burnwatch status" message to "run burnwatch init to configure" so users know what to do next.
15
+
8
16
  ## [0.4.0] - 2026-03-24
9
17
 
10
18
  ### Added
@@ -43,5 +51,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
43
51
  - Snapshot system for delta computation across sessions
44
52
  - Claude Code skills: `/spend` (on-demand brief), `/setup-burnwatch` (guided onboarding)
45
53
 
54
+ [0.4.2]: https://github.com/RaleighSF/burnwatch/compare/v0.4.0...v0.4.2
46
55
  [0.4.0]: https://github.com/RaleighSF/burnwatch/compare/v0.1.0...v0.4.0
47
56
  [0.1.0]: https://github.com/RaleighSF/burnwatch/releases/tag/v0.1.0
package/README.md CHANGED
@@ -12,24 +12,24 @@
12
12
 
13
13
  <br>
14
14
 
15
- burnwatch detects every paid service in your project, tracks what you're spending, and injects budget context directly into your AI coding sessions so the agent knows what things cost before it recommends burning more money.
15
+ burnwatch detects every paid service in your project, tracks what you're spending, and injects budget context directly into your AI coding sessions - so the agent knows what things cost before it recommends burning more money.
16
16
 
17
17
  ```
18
- ╔══════════════════════════════════════════════════════════════╗
19
- ║ BURNWATCH hullscore March 2026
20
- ╠══════════════════════════════════════════════════════════════╣
21
- ║ Service Spend Conf Budget Left
22
- ║ ──────────────────────────────────────────────────────────
23
- ║ Anthropic $47.20 ✅ LIVE $100 53%
24
- ║ Vercel $23.00 ✅ LIVE $50 54%
25
- ║ Scrapfly $127.00 ✅ LIVE $50 ⚠️ OVR
26
- ║ Browserbase ~$63.00 🟠 EST $75 16%
27
- ║ Supabase $25.00 ✅ LIVE $100 75%
28
- ║ PostHog ~$49.00 🟡 CALC $49 0%
29
- ╠══════════════════════════════════════════════════════════════╣
30
- ║ TOTAL: ~$334.20 Untracked: 0 ✅ Est margin: ±$20
31
- ║ 🚨 SCRAPFLY 254% OVER BUDGET review before use
32
- ╚══════════════════════════════════════════════════════════════╝
18
+ ╔══════════════════════════════════════════════════════════════
19
+ ║ BURNWATCH - your-app - March 2026
20
+ ╠══════════════════════════════════════════════════════════════
21
+ ║ Service Spend Conf Budget Left
22
+ ║ ──────────────────────────────────────────────────────────
23
+ ║ Anthropic $47.20 ✅ LIVE $100 53%
24
+ ║ Vercel $23.00 ✅ LIVE $50 54%
25
+ ║ Scrapfly $127.00 ✅ LIVE $50 ⚠️ OVR
26
+ ║ Browserbase ~$63.00 🟠 EST $75 16%
27
+ ║ Supabase $25.00 ✅ LIVE $100 75%
28
+ ║ PostHog ~$49.00 🟡 CALC $49 0%
29
+ ╠══════════════════════════════════════════════════════════════
30
+ ║ TOTAL: ~$334.20 Untracked: 0 ✅ Est margin: ±$20
31
+ ║ 🚨 SCRAPFLY 254% OVER BUDGET - review before use
32
+ ╚══════════════════════════════════════════════════════════════
33
33
  ```
34
34
 
35
35
  This brief appears automatically at the start of every [Claude Code](https://claude.ai/code) session. You don't open a dashboard. You don't remember to check anything. You just see what you're spending.
@@ -38,7 +38,7 @@ This brief appears automatically at the start of every [Claude Code](https://cla
38
38
 
39
39
  ## Why
40
40
 
41
- Agentic development lets you ship 10x faster. It also lets you burn through $400 in Scrapfly credits, rack up unexpected Browserbase bills, and discover PostHog overages three weeks after the code that caused them was written by an agent, in a session you barely remember.
41
+ Agentic development lets you ship 10x faster. It also lets you burn through $400 in Scrapfly credits, rack up unexpected Browserbase bills, and discover PostHog overages three weeks after the code that caused them was written - by an agent, in a session you barely remember.
42
42
 
43
43
  **78% of IT leaders experienced unexpected charges** tied to consumption-based or AI pricing in the past 12 months ([Zylo 2026 SaaS Management Index](https://zylo.com/research/saas-management-index/)).
44
44
 
@@ -56,7 +56,7 @@ npx burnwatch init
56
56
 
57
57
  That's it. burnwatch scans your project, detects paid services, creates a `.burnwatch/` directory, and registers Claude Code hooks. Next time you start a session, you see your spend.
58
58
 
59
- > **Requirements:** Node.js 18+ &mdash; Zero dependencies &mdash; Works with or without Claude Code
59
+ > **Requirements:** Node.js 18+ · Zero dependencies · Works with or without Claude Code
60
60
 
61
61
  <br>
62
62
 
@@ -91,12 +91,12 @@ npx burnwatch init
91
91
  ### 2. Add API keys and budgets
92
92
 
93
93
  ```bash
94
- # LIVE tracking real billing API data
94
+ # LIVE tracking - real billing API data
95
95
  burnwatch add anthropic --key $ANTHROPIC_ADMIN_KEY --budget 100
96
96
  burnwatch add scrapfly --key $SCRAPFLY_KEY --budget 50
97
97
  burnwatch add vercel --token $VERCEL_TOKEN --budget 50
98
98
 
99
- # CALC tracking flat-rate services
99
+ # CALC tracking - flat-rate services
100
100
  burnwatch add posthog --plan-cost 0 --budget 0
101
101
  burnwatch add inngest --plan-cost 25 --budget 25
102
102
 
@@ -119,13 +119,13 @@ Start a Claude Code session. The spend brief appears automatically. When you men
119
119
  ```
120
120
  You: "Use Scrapfly to scrape the competitor pricing pages"
121
121
 
122
- [BURNWATCH] scrapfly current period
122
+ [BURNWATCH] scrapfly - current period
123
123
  Spend: $127.00 | Budget: $50 | ⚠️ 254% over
124
124
  Confidence: ✅ LIVE
125
125
  ⚠️ 254% of budget consumed
126
126
  ```
127
127
 
128
- Claude factors this into its response it might suggest Cheerio instead, or warn you before proceeding.
128
+ Claude factors this into its response - it might suggest Cheerio instead, or warn you before proceeding.
129
129
 
130
130
  When a new paid service enters your project (new dependency, new env var, new import), burnwatch alerts immediately:
131
131
 
@@ -138,7 +138,7 @@ When a new paid service enters your project (new dependency, new env var, new im
138
138
 
139
139
  ## How It Works
140
140
 
141
- burnwatch runs as [Claude Code hooks](https://docs.anthropic.com/en/docs/claude-code/hooks) background scripts that fire on session lifecycle events. It never proxies your traffic. It never intercepts API calls. It watches the exhaust of your sessions silently, completely, and without interrupting the work.
141
+ burnwatch runs as [Claude Code hooks](https://docs.anthropic.com/en/docs/claude-code/hooks) - background scripts that fire on session lifecycle events. It never proxies your traffic. It never intercepts API calls. It watches the exhaust of your sessions silently, completely, and without interrupting the work.
142
142
 
143
143
  ### Four Detection Surfaces
144
144
 
@@ -172,18 +172,18 @@ If burnwatch can't track a service accurately, it says so. The ledger always sho
172
172
 
173
173
  ### The Ledger
174
174
 
175
- burnwatch writes `.burnwatch/spend-ledger.md` at the end of every session human-readable, git-committable, designed to be read in 10 seconds:
175
+ burnwatch writes `.burnwatch/spend-ledger.md` at the end of every session - human-readable, git-committable, designed to be read in 10 seconds:
176
176
 
177
177
  ```markdown
178
- # Burnwatch Ledger hullscore
178
+ # Burnwatch Ledger - your-app
179
179
  Last updated: 2026-03-24T14:32:11Z
180
180
 
181
181
  ## This Month (March 2026)
182
182
  | Service | Spend | Conf | Budget | Status |
183
183
  |---------|-------|------|--------|--------|
184
- | Anthropic | $47.20 | ✅ LIVE | $100 | 53% healthy |
184
+ | Anthropic | $47.20 | ✅ LIVE | $100 | 53% - healthy |
185
185
  | Scrapfly | $127.00 | ✅ LIVE | $50 | ⚠️ 254% over |
186
- | Vercel | $23.00 | ✅ LIVE | $50 | 54% healthy |
186
+ | Vercel | $23.00 | ✅ LIVE | $50 | 54% - healthy |
187
187
 
188
188
  ## TOTAL: ~$209.70 (±$2 estimated margin)
189
189
  ## Untracked services: 0
@@ -216,7 +216,7 @@ Last updated: 2026-03-24T14:32:11Z
216
216
 
217
217
  ## How the Agent Changes Behavior
218
218
 
219
- The real power isn't showing _you_ what you spent it's telling _the agent_ what everything costs, in context, so cost becomes a factor in every recommendation.
219
+ The real power isn't showing _you_ what you spent - it's telling _the agent_ what everything costs, in context, so cost becomes a factor in every recommendation.
220
220
 
221
221
  When Claude sees `Scrapfly: $127 / $50 budget, 254% over` in its context, it:
222
222
 
@@ -281,7 +281,7 @@ burnwatch doesn't need to run in every session. It takes snapshots when present
281
281
  burnwatch reconcile
282
282
  ```
283
283
 
284
- Re-scans your project for services introduced in sessions where burnwatch wasn't active. For billing APIs that expose cumulative usage (like Scrapfly's credit counter), it computes the delta between snapshots attributing spend across the gap.
284
+ Re-scans your project for services introduced in sessions where burnwatch wasn't active. For billing APIs that expose cumulative usage (like Scrapfly's credit counter), it computes the delta between snapshots - attributing spend across the gap.
285
285
 
286
286
  <br>
287
287
 
@@ -304,7 +304,7 @@ Re-scans your project for services introduced in sessions where burnwatch wasn't
304
304
  }
305
305
  ```
306
306
 
307
- The `gotchas` and `alternatives` fields aren't just metadata the agent reads them and uses them to make better recommendations. Every PR that adds a service makes burnwatch smarter for every user.
307
+ The `gotchas` and `alternatives` fields aren't just metadata - the agent reads them and uses them to make better recommendations. Every PR that adds a service makes burnwatch smarter for every user.
308
308
 
309
309
  <br>
310
310
 
package/dist/cli.js CHANGED
@@ -661,7 +661,7 @@ function buildBrief(projectName, snapshots, blindCount) {
661
661
  alerts.push({
662
662
  serviceId: "_blind",
663
663
  type: "blind_service",
664
- message: `${blindCount} service${blindCount > 1 ? "s" : ""} detected but untracked \u2014 run 'burnwatch status' to see`,
664
+ message: `${blindCount} service${blindCount > 1 ? "s" : ""} detected but untracked - run 'burnwatch init' to configure`,
665
665
  severity: "warning"
666
666
  });
667
667
  }
@@ -897,10 +897,12 @@ ${RISK_LABELS[category]}`);
897
897
  planName: chosen.name
898
898
  };
899
899
  if (chosen.type === "flat" && chosen.monthlyBase !== void 0) {
900
- tracked.budget = chosen.monthlyBase;
901
900
  tracked.planCost = chosen.monthlyBase;
901
+ if (chosen.monthlyBase > 0) {
902
+ tracked.budget = chosen.monthlyBase;
903
+ }
902
904
  }
903
- if (chosen.requiresKey) {
905
+ if (service.apiTier === "live" || chosen.requiresKey) {
904
906
  const existingKey = globalConfig.services[service.id]?.apiKey;
905
907
  if (existingKey) {
906
908
  console.log(` \u{1F510} Using existing API key from global config`);
@@ -913,7 +915,7 @@ ${RISK_LABELS[category]}`);
913
915
  tracked.planName = planName;
914
916
  }
915
917
  }
916
- } else {
918
+ } else if (chosen.requiresKey) {
917
919
  const keyAnswer = await ask(
918
920
  rl,
919
921
  ` Enter API key (or press Enter to skip): `
@@ -934,16 +936,17 @@ ${RISK_LABELS[category]}`);
934
936
  }
935
937
  }
936
938
  }
937
- if (tracked.budget === void 0) {
938
- const budgetAnswer = await ask(
939
- rl,
940
- ` Monthly budget in USD (or press Enter to skip): $`
941
- );
942
- if (budgetAnswer) {
943
- const budget = parseFloat(budgetAnswer);
944
- if (!isNaN(budget)) {
945
- tracked.budget = budget;
946
- }
939
+ }
940
+ if (tracked.budget === void 0 || tracked.budget === 0) {
941
+ const suggestion = chosen.monthlyBase && chosen.monthlyBase > 0 ? ` [${chosen.monthlyBase}]` : "";
942
+ const budgetAnswer = await ask(
943
+ rl,
944
+ ` Monthly budget in USD${suggestion} (or press Enter to skip): $`
945
+ );
946
+ if (budgetAnswer) {
947
+ const budget = parseFloat(budgetAnswer);
948
+ if (!isNaN(budget)) {
949
+ tracked.budget = budget;
947
950
  }
948
951
  }
949
952
  }
@@ -1004,11 +1007,7 @@ async function main() {
1004
1007
  async function cmdInit() {
1005
1008
  const projectRoot = process.cwd();
1006
1009
  const nonInteractive = flags.has("--non-interactive") || flags.has("--ni");
1007
- if (isInitialized(projectRoot)) {
1008
- console.log("\u2705 burnwatch is already initialized in this project.");
1009
- console.log(` Config: ${projectConfigDir(projectRoot)}/config.json`);
1010
- return;
1011
- }
1010
+ const alreadyInitialized = isInitialized(projectRoot);
1012
1011
  let projectName = path5.basename(projectRoot);
1013
1012
  try {
1014
1013
  const pkgPath = path5.join(projectRoot, "package.json");
@@ -1019,10 +1018,11 @@ async function cmdInit() {
1019
1018
  ensureProjectDirs(projectRoot);
1020
1019
  console.log("\u{1F50D} Scanning project for paid services...\n");
1021
1020
  const detected = detectServices(projectRoot);
1021
+ const existingConfig = alreadyInitialized ? readProjectConfig(projectRoot) : null;
1022
1022
  const config = {
1023
- projectName,
1024
- services: {},
1025
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1023
+ projectName: existingConfig?.projectName ?? projectName,
1024
+ services: existingConfig?.services ?? {},
1025
+ createdAt: existingConfig?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
1026
1026
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1027
1027
  };
1028
1028
  if (!nonInteractive && detected.length > 0 && process.stdin.isTTY) {
@@ -1030,13 +1030,15 @@ async function cmdInit() {
1030
1030
  config.services = result.services;
1031
1031
  } else {
1032
1032
  for (const det of detected) {
1033
- const tracked2 = {
1034
- serviceId: det.service.id,
1035
- detectedVia: det.sources,
1036
- hasApiKey: false,
1037
- firstDetected: (/* @__PURE__ */ new Date()).toISOString()
1038
- };
1039
- config.services[det.service.id] = tracked2;
1033
+ if (!config.services[det.service.id]) {
1034
+ const tracked2 = {
1035
+ serviceId: det.service.id,
1036
+ detectedVia: det.sources,
1037
+ hasApiKey: false,
1038
+ firstDetected: (/* @__PURE__ */ new Date()).toISOString()
1039
+ };
1040
+ config.services[det.service.id] = tracked2;
1041
+ }
1040
1042
  }
1041
1043
  if (detected.length === 0) {
1042
1044
  console.log(" No paid services detected yet.");
@@ -1193,11 +1195,11 @@ async function cmdStatus() {
1193
1195
  if (blindCount > 0) {
1194
1196
  console.log(`\u26A0\uFE0F ${blindCount} service${blindCount > 1 ? "s" : ""} untracked:`);
1195
1197
  for (const snap of snapshots.filter((s) => s.tier === "blind")) {
1196
- console.log(
1197
- ` \u2022 ${snap.serviceId} \u2014 run 'burnwatch add ${snap.serviceId} --key YOUR_KEY --budget N'`
1198
- );
1198
+ console.log(` \u2022 ${snap.serviceId}`);
1199
1199
  }
1200
- console.log("");
1200
+ console.log(`
1201
+ Run 'burnwatch init' to configure budgets and API keys.
1202
+ `);
1201
1203
  }
1202
1204
  }
1203
1205
  function cmdServices() {