burnwatch 0.4.1 → 0.4.3

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,22 @@ 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.3] - 2026-03-24
9
+
10
+ ### Fixed
11
+
12
+ - **Non-interactive init auto-applies default plans**: `burnwatch init` (non-interactive or re-run) now picks each service's default plan from the registry. Flat-rate services with a cost (e.g., Scrapfly Scale at $80/mo) get budget auto-set to plan cost. Services with existing API keys in global config are auto-detected as LIVE.
13
+ - **Init output shows tier and budget per service**: Non-interactive init now prints each service with its confidence tier, plan name, and budget (if set), plus a clear list of which services still need budgets with the exact `burnwatch add` commands.
14
+ - **Untracked message is actionable**: Changed circular "run burnwatch status" message to "run burnwatch init to configure".
15
+
16
+ ## [0.4.2] - 2026-03-24
17
+
18
+ ### Fixed
19
+
20
+ - **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.
21
+ - **Budget prompt fires for all services**: Budget prompt was gated inside the `requiresKey` block - now every non-excluded service gets a budget prompt during interactive init.
22
+ - **Untracked message fix**: Same as 0.4.3 (first shipped here).
23
+
8
24
  ## [0.4.0] - 2026-03-24
9
25
 
10
26
  ### Added
@@ -43,5 +59,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
43
59
  - Snapshot system for delta computation across sessions
44
60
  - Claude Code skills: `/spend` (on-demand brief), `/setup-burnwatch` (guided onboarding)
45
61
 
62
+ [0.4.3]: https://github.com/RaleighSF/burnwatch/compare/v0.4.2...v0.4.3
63
+ [0.4.2]: https://github.com/RaleighSF/burnwatch/compare/v0.4.0...v0.4.2
46
64
  [0.4.0]: https://github.com/RaleighSF/burnwatch/compare/v0.1.0...v0.4.0
47
65
  [0.1.0]: https://github.com/RaleighSF/burnwatch/releases/tag/v0.1.0
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
  }
@@ -1007,11 +1007,7 @@ async function main() {
1007
1007
  async function cmdInit() {
1008
1008
  const projectRoot = process.cwd();
1009
1009
  const nonInteractive = flags.has("--non-interactive") || flags.has("--ni");
1010
- if (isInitialized(projectRoot)) {
1011
- console.log("\u2705 burnwatch is already initialized in this project.");
1012
- console.log(` Config: ${projectConfigDir(projectRoot)}/config.json`);
1013
- return;
1014
- }
1010
+ const alreadyInitialized = isInitialized(projectRoot);
1015
1011
  let projectName = path5.basename(projectRoot);
1016
1012
  try {
1017
1013
  const pkgPath = path5.join(projectRoot, "package.json");
@@ -1022,24 +1018,44 @@ async function cmdInit() {
1022
1018
  ensureProjectDirs(projectRoot);
1023
1019
  console.log("\u{1F50D} Scanning project for paid services...\n");
1024
1020
  const detected = detectServices(projectRoot);
1021
+ const existingConfig = alreadyInitialized ? readProjectConfig(projectRoot) : null;
1025
1022
  const config = {
1026
- projectName,
1027
- services: {},
1028
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1023
+ projectName: existingConfig?.projectName ?? projectName,
1024
+ services: existingConfig?.services ?? {},
1025
+ createdAt: existingConfig?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
1029
1026
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1030
1027
  };
1031
1028
  if (!nonInteractive && detected.length > 0 && process.stdin.isTTY) {
1032
1029
  const result = await runInteractiveInit(detected);
1033
1030
  config.services = result.services;
1034
1031
  } else {
1032
+ const globalConfig = readGlobalConfig();
1035
1033
  for (const det of detected) {
1034
+ const existing = config.services[det.service.id];
1035
+ const service = det.service;
1036
+ const plans = service.plans ?? [];
1037
+ const defaultPlan = plans.find((p) => p.default) ?? plans[0];
1038
+ if (existing?.budget !== void 0 && existing.budget > 0) continue;
1036
1039
  const tracked2 = {
1037
- serviceId: det.service.id,
1038
- detectedVia: det.sources,
1039
- hasApiKey: false,
1040
- firstDetected: (/* @__PURE__ */ new Date()).toISOString()
1040
+ serviceId: service.id,
1041
+ detectedVia: existing?.detectedVia ?? det.sources,
1042
+ hasApiKey: existing?.hasApiKey ?? false,
1043
+ firstDetected: existing?.firstDetected ?? (/* @__PURE__ */ new Date()).toISOString()
1041
1044
  };
1042
- config.services[det.service.id] = tracked2;
1045
+ if (defaultPlan && defaultPlan.type !== "exclude") {
1046
+ tracked2.planName = defaultPlan.name;
1047
+ if (defaultPlan.type === "flat" && defaultPlan.monthlyBase !== void 0) {
1048
+ tracked2.planCost = defaultPlan.monthlyBase;
1049
+ if (defaultPlan.monthlyBase > 0) {
1050
+ tracked2.budget = defaultPlan.monthlyBase;
1051
+ }
1052
+ }
1053
+ }
1054
+ const existingKey = globalConfig.services[service.id]?.apiKey;
1055
+ if (existingKey) {
1056
+ tracked2.hasApiKey = true;
1057
+ }
1058
+ config.services[service.id] = tracked2;
1043
1059
  }
1044
1060
  if (detected.length === 0) {
1045
1061
  console.log(" No paid services detected yet.");
@@ -1047,12 +1063,25 @@ async function cmdInit() {
1047
1063
  } else {
1048
1064
  console.log(` Found ${detected.length} paid service${detected.length > 1 ? "s" : ""}:
1049
1065
  `);
1066
+ const needBudget = [];
1050
1067
  for (const det of detected) {
1051
- const tierBadge = det.service.apiTier === "live" ? "\u2705 LIVE API available" : det.service.apiTier === "calc" ? "\u{1F7E1} Flat-rate tracking" : det.service.apiTier === "est" ? "\u{1F7E0} Estimate tracking" : "\u{1F534} Detection only";
1052
- console.log(` \u2022 ${det.service.name} (${tierBadge})`);
1053
- console.log(` Detected via: ${det.details.join(", ")}`);
1068
+ const svc = config.services[det.service.id];
1069
+ const tierBadge = det.service.apiTier === "live" ? svc?.hasApiKey ? "\u2705 LIVE" : "\u{1F534} BLIND" : det.service.apiTier === "calc" ? "\u{1F7E1} CALC" : det.service.apiTier === "est" ? "\u{1F7E0} EST" : "\u{1F534} BLIND";
1070
+ const planStr = svc?.planName ? ` (${svc.planName})` : "";
1071
+ const budgetStr = svc?.budget ? ` - $${svc.budget}/mo budget` : "";
1072
+ console.log(` \u2022 ${det.service.name}${planStr} ${tierBadge}${budgetStr}`);
1073
+ if (!svc?.budget) {
1074
+ needBudget.push(det.service.id);
1075
+ }
1054
1076
  }
1055
1077
  console.log("");
1078
+ if (needBudget.length > 0) {
1079
+ console.log(` ${needBudget.length} service${needBudget.length > 1 ? "s" : ""} need budgets. Set them with:`);
1080
+ for (const id of needBudget) {
1081
+ console.log(` burnwatch add ${id} --budget <AMOUNT>`);
1082
+ }
1083
+ console.log("");
1084
+ }
1056
1085
  }
1057
1086
  }
1058
1087
  writeProjectConfig(config, projectRoot);
@@ -1196,11 +1225,11 @@ async function cmdStatus() {
1196
1225
  if (blindCount > 0) {
1197
1226
  console.log(`\u26A0\uFE0F ${blindCount} service${blindCount > 1 ? "s" : ""} untracked:`);
1198
1227
  for (const snap of snapshots.filter((s) => s.tier === "blind")) {
1199
- console.log(
1200
- ` \u2022 ${snap.serviceId} \u2014 run 'burnwatch add ${snap.serviceId} --key YOUR_KEY --budget N'`
1201
- );
1228
+ console.log(` \u2022 ${snap.serviceId}`);
1202
1229
  }
1203
- console.log("");
1230
+ console.log(`
1231
+ Run 'burnwatch init' to configure budgets and API keys.
1232
+ `);
1204
1233
  }
1205
1234
  }
1206
1235
  function cmdServices() {