burnwatch 0.4.3 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,13 +5,26 @@ 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
8
+ ## [0.5.1] - 2026-03-24
9
9
 
10
10
  ### Fixed
11
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".
12
+ - **Init actually works now**: `process.stdin.isTTY` was `undefined` in Claude Code and many terminal environments, so the interactive interview never ran. Init now has three modes: (1) TTY detected = full interactive interview, (2) no TTY = smart auto-configure with defaults, (3) `--non-interactive` = minimal CI mode. The auto-configure path picks each service's default plan, detects API keys from env vars and global config, sets budget = plan cost for flat plans and $0 for usage-based, and only shows `burnwatch add` commands for the handful of usage-based services that genuinely need a human-set budget.
13
+
14
+ ## [0.5.0] - 2026-03-24
15
+
16
+ ### Changed
17
+
18
+ - **Interactive init rewritten as proper interview**: Each service gets a structured conversation - plan selection, API key collection with hints on where to find them, and budget. Budgets are never skipped - pressing Enter applies the plan cost (or $0 for free tiers) instead of leaving budget undefined. Summary table at the end shows total monthly budget across all services.
19
+ - **Env var auto-detection for API keys**: Init checks `process.env` for known key patterns (e.g., `ANTHROPIC_ADMIN_KEY`, `SCRAPFLY_KEY`) and auto-imports them to global config. No need to re-enter keys that are already in your environment.
20
+ - **API key prompts for all LIVE services**: Previously only fired when the chosen plan had `requiresKey: true`. Now any LIVE-capable service gets the key prompt regardless of plan, with a hint about where to find the key (e.g., "Admin key: console.anthropic.com -> Settings -> Admin API Keys").
21
+ - **Non-interactive init always sets budget**: Every service gets `budget: 0` at minimum (flat-rate services get budget = plan cost). No more undefined budgets showing as "-" in status output.
22
+ - **Non-interactive env var key detection**: Checks environment for API keys matching service patterns, auto-saves to global config for LIVE tracking.
23
+
24
+ ### Fixed
25
+
26
+ - **13/14 services had no budget after init**: Root cause was budget prompt saying "press Enter to skip". Now pressing Enter applies the default budget instead of skipping.
27
+ - **Untracked message was circular**: Changed "run burnwatch status" to "run burnwatch init to configure".
15
28
 
16
29
  ## [0.4.2] - 2026-03-24
17
30
 
@@ -59,6 +72,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
59
72
  - Snapshot system for delta computation across sessions
60
73
  - Claude Code skills: `/spend` (on-demand brief), `/setup-burnwatch` (guided onboarding)
61
74
 
75
+ [0.5.1]: https://github.com/RaleighSF/burnwatch/compare/v0.5.0...v0.5.1
76
+ [0.5.0]: https://github.com/RaleighSF/burnwatch/compare/v0.4.3...v0.5.0
62
77
  [0.4.3]: https://github.com/RaleighSF/burnwatch/compare/v0.4.2...v0.4.3
63
78
  [0.4.2]: https://github.com/RaleighSF/burnwatch/compare/v0.4.0...v0.4.2
64
79
  [0.4.0]: https://github.com/RaleighSF/burnwatch/compare/v0.1.0...v0.4.0
package/dist/cli.js CHANGED
@@ -789,10 +789,18 @@ function saveSnapshot(brief, projectRoot) {
789
789
  import * as readline from "readline";
790
790
  var RISK_ORDER = ["llm", "usage", "infra", "flat"];
791
791
  var RISK_LABELS = {
792
- llm: "\u{1F916} LLM / AI Services (highest variable cost)",
793
- usage: "\u{1F4CA} Usage-Based Services",
794
- infra: "\u{1F3D7}\uFE0F Infrastructure & Compute",
795
- flat: "\u{1F4E6} Flat-Rate / Free Tier Services"
792
+ llm: "LLM / AI Services (highest variable cost)",
793
+ usage: "Usage-Based Services",
794
+ infra: "Infrastructure & Compute",
795
+ flat: "Flat-Rate / Free Tier Services"
796
+ };
797
+ var API_KEY_HINTS = {
798
+ anthropic: "Admin key: console.anthropic.com -> Settings -> Admin API Keys",
799
+ openai: "Org key: platform.openai.com -> Settings -> API Keys",
800
+ vercel: "Token: vercel.com/account/tokens",
801
+ supabase: "Service role key: supabase.com/dashboard -> Settings -> API",
802
+ stripe: "Secret key: dashboard.stripe.com -> Developers -> API Keys",
803
+ scrapfly: "API key: scrapfly.io/dashboard"
796
804
  };
797
805
  function classifyRisk(service) {
798
806
  if (service.billingModel === "token_usage") return "llm";
@@ -829,6 +837,88 @@ async function autoDetectScrapflyPlan(apiKey) {
829
837
  }
830
838
  return null;
831
839
  }
840
+ function findEnvKey(service) {
841
+ for (const pattern of service.envPatterns) {
842
+ const val = process.env[pattern];
843
+ if (val && val.length > 0) return val;
844
+ }
845
+ return void 0;
846
+ }
847
+ function autoConfigureServices(detected) {
848
+ const services = {};
849
+ const groups = groupByRisk(detected);
850
+ const globalConfig = readGlobalConfig();
851
+ console.log(
852
+ `
853
+ Found ${detected.length} paid service${detected.length !== 1 ? "s" : ""}. Auto-configuring with defaults.
854
+ `
855
+ );
856
+ console.log(" Run 'burnwatch init' from your terminal for interactive setup.\n");
857
+ for (const category of RISK_ORDER) {
858
+ const group = groups.get(category);
859
+ if (group.length === 0) continue;
860
+ console.log(` ${RISK_LABELS[category]}`);
861
+ for (const det of group) {
862
+ const service = det.service;
863
+ const plans = service.plans ?? [];
864
+ const defaultPlan = plans.find((p) => p.default) ?? plans[0];
865
+ const tracked = {
866
+ serviceId: service.id,
867
+ detectedVia: det.sources,
868
+ hasApiKey: false,
869
+ firstDetected: (/* @__PURE__ */ new Date()).toISOString(),
870
+ budget: 0
871
+ };
872
+ if (defaultPlan && defaultPlan.type !== "exclude") {
873
+ tracked.planName = defaultPlan.name;
874
+ if (defaultPlan.type === "flat" && defaultPlan.monthlyBase !== void 0) {
875
+ tracked.planCost = defaultPlan.monthlyBase;
876
+ tracked.budget = defaultPlan.monthlyBase;
877
+ }
878
+ }
879
+ const existingKey = globalConfig.services[service.id]?.apiKey;
880
+ const envKey = findEnvKey(service);
881
+ let keySource = "";
882
+ if (existingKey) {
883
+ tracked.hasApiKey = true;
884
+ keySource = " (key: global config)";
885
+ } else if (envKey) {
886
+ tracked.hasApiKey = true;
887
+ if (!globalConfig.services[service.id]) {
888
+ globalConfig.services[service.id] = {};
889
+ }
890
+ globalConfig.services[service.id].apiKey = envKey;
891
+ keySource = ` (key: ${service.envPatterns[0]})`;
892
+ }
893
+ const tierLabel = tracked.hasApiKey ? "LIVE" : tracked.planCost !== void 0 ? "CALC" : "BLIND";
894
+ const planStr = tracked.planName ? ` ${tracked.planName}` : "";
895
+ console.log(
896
+ ` ${service.name}:${planStr} | ${tierLabel} | $${tracked.budget}/mo${keySource}`
897
+ );
898
+ services[service.id] = tracked;
899
+ }
900
+ console.log("");
901
+ }
902
+ const trackedList = Object.values(services);
903
+ const liveCount = trackedList.filter((s) => s.hasApiKey).length;
904
+ const totalBudget = trackedList.reduce((sum, s) => sum + (s.budget ?? 0), 0);
905
+ console.log(" " + "-".repeat(48));
906
+ console.log(` ${trackedList.length} services configured | Total budget: $${totalBudget}/mo`);
907
+ if (liveCount > 0) console.log(` ${liveCount} with real-time billing (LIVE)`);
908
+ const needBudget = trackedList.filter(
909
+ (s) => s.budget === 0 && s.planCost === void 0
910
+ );
911
+ if (needBudget.length > 0) {
912
+ console.log(`
913
+ ${needBudget.length} usage-based service${needBudget.length > 1 ? "s" : ""} need budgets:`);
914
+ for (const svc of needBudget) {
915
+ console.log(` burnwatch add ${svc.serviceId} --budget <AMOUNT>`);
916
+ }
917
+ }
918
+ console.log("");
919
+ writeGlobalConfig(globalConfig);
920
+ return { services };
921
+ }
832
922
  async function runInteractiveInit(detected) {
833
923
  const rl = readline.createInterface({
834
924
  input: process.stdin,
@@ -838,14 +928,16 @@ async function runInteractiveInit(detected) {
838
928
  const groups = groupByRisk(detected);
839
929
  const globalConfig = readGlobalConfig();
840
930
  console.log(
841
- "\n\u{1F4CB} Let's configure each detected service. Services are grouped by cost risk.\n"
931
+ `
932
+ Found ${detected.length} paid service${detected.length !== 1 ? "s" : ""}. Let's configure each one.
933
+ `
842
934
  );
843
935
  for (const category of RISK_ORDER) {
844
936
  const group = groups.get(category);
845
937
  if (group.length === 0) continue;
846
938
  console.log(`
847
- ${RISK_LABELS[category]}`);
848
- console.log("\u2500".repeat(50));
939
+ ${RISK_LABELS[category]}`);
940
+ console.log(" " + "-".repeat(48));
849
941
  for (const det of group) {
850
942
  const service = det.service;
851
943
  const plans = service.plans;
@@ -857,23 +949,24 @@ ${RISK_LABELS[category]}`);
857
949
  serviceId: service.id,
858
950
  detectedVia: det.sources,
859
951
  hasApiKey: false,
860
- firstDetected: (/* @__PURE__ */ new Date()).toISOString()
952
+ firstDetected: (/* @__PURE__ */ new Date()).toISOString(),
953
+ budget: 0
861
954
  };
862
- console.log(" \u2192 Auto-configured (no plan tiers available)");
955
+ console.log(" -> Configured (no plan tiers in registry, budget: $0)");
863
956
  continue;
864
957
  }
865
958
  const defaultIndex = plans.findIndex((p) => p.default);
866
959
  console.log("");
867
960
  for (let i = 0; i < plans.length; i++) {
868
961
  const plan = plans[i];
869
- const marker = i === defaultIndex ? " (recommended)" : "";
870
- const costStr = plan.type === "exclude" ? "" : plan.monthlyBase !== void 0 ? ` \u2014 $${plan.monthlyBase}/mo` : " \u2014 variable";
962
+ const marker = i === defaultIndex ? " *" : "";
963
+ const costStr = plan.type === "exclude" ? "" : plan.monthlyBase !== void 0 ? ` - $${plan.monthlyBase}/mo` : " - variable";
871
964
  console.log(` ${i + 1}) ${plan.name}${costStr}${marker}`);
872
965
  }
873
966
  const defaultChoice = defaultIndex >= 0 ? String(defaultIndex + 1) : "1";
874
967
  const answer = await ask(
875
968
  rl,
876
- ` Choose [${defaultChoice}]: `
969
+ ` Which plan? [${defaultChoice}]: `
877
970
  );
878
971
  const choiceIndex = (answer === "" ? parseInt(defaultChoice) : parseInt(answer)) - 1;
879
972
  const chosen = plans[choiceIndex] ?? plans[defaultIndex >= 0 ? defaultIndex : 0];
@@ -886,10 +979,10 @@ ${RISK_LABELS[category]}`);
886
979
  excluded: true,
887
980
  planName: chosen.name
888
981
  };
889
- console.log(` \u2192 ${service.name}: excluded from tracking`);
982
+ console.log(` -> ${service.name}: excluded`);
890
983
  continue;
891
984
  }
892
- const tracked = {
985
+ const tracked2 = {
893
986
  serviceId: service.id,
894
987
  detectedVia: det.sources,
895
988
  hasApiKey: false,
@@ -897,67 +990,86 @@ ${RISK_LABELS[category]}`);
897
990
  planName: chosen.name
898
991
  };
899
992
  if (chosen.type === "flat" && chosen.monthlyBase !== void 0) {
900
- tracked.planCost = chosen.monthlyBase;
901
- if (chosen.monthlyBase > 0) {
902
- tracked.budget = chosen.monthlyBase;
903
- }
993
+ tracked2.planCost = chosen.monthlyBase;
904
994
  }
905
- if (service.apiTier === "live" || chosen.requiresKey) {
995
+ if (service.apiTier === "live") {
906
996
  const existingKey = globalConfig.services[service.id]?.apiKey;
997
+ const envKey = findEnvKey(service);
907
998
  if (existingKey) {
908
- console.log(` \u{1F510} Using existing API key from global config`);
909
- tracked.hasApiKey = true;
910
- if (service.autoDetectPlan && service.id === "scrapfly") {
911
- console.log(" \u{1F50D} Auto-detecting plan from API...");
912
- const planName = await autoDetectScrapflyPlan(existingKey);
913
- if (planName) {
914
- console.log(` \u2192 Detected plan: ${planName}`);
915
- tracked.planName = planName;
916
- }
999
+ console.log(` API key: found in global config`);
1000
+ tracked2.hasApiKey = true;
1001
+ } else if (envKey) {
1002
+ console.log(` API key: found in environment (${service.envPatterns[0]})`);
1003
+ tracked2.hasApiKey = true;
1004
+ if (!globalConfig.services[service.id]) {
1005
+ globalConfig.services[service.id] = {};
917
1006
  }
918
- } else if (chosen.requiresKey) {
1007
+ globalConfig.services[service.id].apiKey = envKey;
1008
+ } else {
1009
+ const hint = API_KEY_HINTS[service.id];
1010
+ if (hint) console.log(` ${hint}`);
919
1011
  const keyAnswer = await ask(
920
1012
  rl,
921
- ` Enter API key (or press Enter to skip): `
1013
+ ` API key for real-time tracking (Enter to skip): `
922
1014
  );
923
1015
  if (keyAnswer) {
924
- tracked.hasApiKey = true;
1016
+ tracked2.hasApiKey = true;
925
1017
  if (!globalConfig.services[service.id]) {
926
1018
  globalConfig.services[service.id] = {};
927
1019
  }
928
1020
  globalConfig.services[service.id].apiKey = keyAnswer;
929
- if (service.autoDetectPlan && service.id === "scrapfly") {
930
- console.log(" \u{1F50D} Auto-detecting plan from API...");
931
- const planName = await autoDetectScrapflyPlan(keyAnswer);
932
- if (planName) {
933
- console.log(` \u2192 Detected plan: ${planName}`);
934
- tracked.planName = planName;
935
- }
1021
+ }
1022
+ }
1023
+ if (service.autoDetectPlan && service.id === "scrapfly" && tracked2.hasApiKey) {
1024
+ const key = globalConfig.services[service.id]?.apiKey;
1025
+ if (key) {
1026
+ console.log(" Detecting plan from API...");
1027
+ const planName = await autoDetectScrapflyPlan(key);
1028
+ if (planName) {
1029
+ console.log(` -> Detected plan: ${planName}`);
1030
+ tracked2.planName = planName;
936
1031
  }
937
1032
  }
938
1033
  }
939
1034
  }
940
- if (tracked.budget === void 0 || tracked.budget === 0) {
941
- const suggestion = chosen.monthlyBase && chosen.monthlyBase > 0 ? ` [${chosen.monthlyBase}]` : "";
1035
+ const planCost = chosen.monthlyBase ?? 0;
1036
+ if (chosen.type === "usage" && planCost === 0) {
1037
+ const budgetAnswer = await ask(
1038
+ rl,
1039
+ ` Monthly budget: $`
1040
+ );
1041
+ const parsed = parseFloat(budgetAnswer);
1042
+ tracked2.budget = !isNaN(parsed) ? parsed : 0;
1043
+ } else {
942
1044
  const budgetAnswer = await ask(
943
1045
  rl,
944
- ` Monthly budget in USD${suggestion} (or press Enter to skip): $`
1046
+ ` Monthly budget [$${planCost}]: $`
945
1047
  );
946
1048
  if (budgetAnswer) {
947
- const budget = parseFloat(budgetAnswer);
948
- if (!isNaN(budget)) {
949
- tracked.budget = budget;
950
- }
1049
+ const parsed = parseFloat(budgetAnswer);
1050
+ tracked2.budget = !isNaN(parsed) ? parsed : planCost;
1051
+ } else {
1052
+ tracked2.budget = planCost;
951
1053
  }
952
1054
  }
953
- services[service.id] = tracked;
954
- const tierLabel = tracked.hasApiKey ? "\u2705 LIVE" : tracked.planCost !== void 0 ? "\u{1F7E1} CALC" : "\u{1F534} BLIND";
955
- const budgetStr = tracked.budget !== void 0 ? ` | Budget: $${tracked.budget}/mo` : "";
1055
+ services[service.id] = tracked2;
1056
+ const tierLabel = tracked2.hasApiKey ? "LIVE" : tracked2.planCost !== void 0 ? "CALC" : "BLIND";
956
1057
  console.log(
957
- ` \u2192 ${service.name}: ${chosen.name} (${tierLabel}${budgetStr})`
1058
+ ` -> ${service.name}: ${tracked2.planName} | ${tierLabel} | $${tracked2.budget}/mo`
958
1059
  );
959
1060
  }
960
1061
  }
1062
+ const tracked = Object.values(services).filter((s) => !s.excluded);
1063
+ const excluded = Object.values(services).filter((s) => s.excluded);
1064
+ const liveCount = tracked.filter((s) => s.hasApiKey).length;
1065
+ const totalBudget = tracked.reduce((sum, s) => sum + (s.budget ?? 0), 0);
1066
+ console.log("\n " + "=".repeat(48));
1067
+ console.log(` ${tracked.length} services configured`);
1068
+ if (liveCount > 0) console.log(` ${liveCount} with real-time billing (LIVE)`);
1069
+ if (tracked.length - liveCount > 0) console.log(` ${tracked.length - liveCount} estimated/calculated`);
1070
+ if (excluded.length > 0) console.log(` ${excluded.length} excluded`);
1071
+ console.log(` Total monthly budget: $${totalBudget}`);
1072
+ console.log(" " + "=".repeat(48));
961
1073
  writeGlobalConfig(globalConfig);
962
1074
  rl.close();
963
1075
  return { services };
@@ -1025,64 +1137,29 @@ async function cmdInit() {
1025
1137
  createdAt: existingConfig?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
1026
1138
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1027
1139
  };
1028
- if (!nonInteractive && detected.length > 0 && process.stdin.isTTY) {
1029
- const result = await runInteractiveInit(detected);
1030
- config.services = result.services;
1031
- } else {
1032
- const globalConfig = readGlobalConfig();
1140
+ if (detected.length === 0) {
1141
+ console.log(" No paid services detected yet.");
1142
+ console.log(" Services will be detected as they enter your project.\n");
1143
+ } else if (nonInteractive) {
1033
1144
  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;
1039
- const tracked2 = {
1040
- serviceId: service.id,
1041
- detectedVia: existing?.detectedVia ?? det.sources,
1042
- hasApiKey: existing?.hasApiKey ?? false,
1043
- firstDetected: existing?.firstDetected ?? (/* @__PURE__ */ new Date()).toISOString()
1044
- };
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;
1145
+ if (!config.services[det.service.id]) {
1146
+ config.services[det.service.id] = {
1147
+ serviceId: det.service.id,
1148
+ detectedVia: det.sources,
1149
+ hasApiKey: false,
1150
+ firstDetected: (/* @__PURE__ */ new Date()).toISOString(),
1151
+ budget: 0
1152
+ };
1057
1153
  }
1058
- config.services[service.id] = tracked2;
1059
1154
  }
1060
- if (detected.length === 0) {
1061
- console.log(" No paid services detected yet.");
1062
- console.log(" Services will be detected as they enter your project.\n");
1063
- } else {
1064
- console.log(` Found ${detected.length} paid service${detected.length > 1 ? "s" : ""}:
1155
+ console.log(` Registered ${detected.length} services (non-interactive).
1065
1156
  `);
1066
- const needBudget = [];
1067
- for (const det of detected) {
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
- }
1076
- }
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
- }
1085
- }
1157
+ } else if (process.stdin.isTTY) {
1158
+ const result = await runInteractiveInit(detected);
1159
+ config.services = result.services;
1160
+ } else {
1161
+ const result = autoConfigureServices(detected);
1162
+ config.services = result.services;
1086
1163
  }
1087
1164
  writeProjectConfig(config, projectRoot);
1088
1165
  const gitignorePath = path5.join(projectConfigDir(projectRoot), ".gitignore");
@@ -1099,27 +1176,11 @@ async function cmdInit() {
1099
1176
  );
1100
1177
  console.log("\n\u{1F517} Registering Claude Code hooks...\n");
1101
1178
  registerHooks(projectRoot);
1102
- const excluded = Object.values(config.services).filter((s) => s.excluded);
1103
- const tracked = Object.values(config.services).filter((s) => !s.excluded);
1104
- console.log("\u2705 burnwatch initialized!\n");
1105
- if (tracked.length > 0) {
1106
- console.log(` Tracking ${tracked.length} service${tracked.length > 1 ? "s" : ""}`);
1107
- for (const svc of tracked) {
1108
- const planStr = svc.planName ? ` (${svc.planName})` : "";
1109
- const budgetStr = svc.budget !== void 0 ? ` \u2014 $${svc.budget}/mo budget` : "";
1110
- console.log(` \u2022 ${svc.serviceId}${planStr}${budgetStr}`);
1111
- }
1112
- }
1113
- if (excluded.length > 0) {
1114
- console.log(`
1115
- Excluded ${excluded.length} service${excluded.length > 1 ? "s" : ""}:`);
1116
- for (const svc of excluded) {
1117
- console.log(` \u2022 ${svc.serviceId}`);
1118
- }
1119
- }
1120
- console.log("\nNext steps:");
1121
- console.log(" burnwatch status \u2014 Check your spend");
1122
- console.log(" burnwatch add <svc> \u2014 Configure additional services\n");
1179
+ console.log("\nburnwatch initialized.\n");
1180
+ console.log("Next steps:");
1181
+ console.log(" burnwatch status Show current spend");
1182
+ console.log(" burnwatch add <svc> Update a service's budget or API key");
1183
+ console.log(" burnwatch init Re-run this setup anytime\n");
1123
1184
  }
1124
1185
  async function cmdAdd() {
1125
1186
  const projectRoot = process.cwd();