burnwatch 0.5.0 → 0.5.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 +15 -0
- package/dist/cli.js +94 -83
- package/dist/cli.js.map +1 -1
- package/dist/cost-impact.d.ts +1 -1
- package/dist/{detector-C4LnLT-O.d.ts → detector-C6owsMy7.d.ts} +1 -1
- package/dist/hooks/on-prompt.js.map +1 -1
- package/dist/hooks/on-session-start.js.map +1 -1
- package/dist/hooks/on-stop.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js.map +1 -1
- package/dist/interactive-init.d.ts +11 -3
- package/dist/interactive-init.js +76 -17
- package/dist/interactive-init.js.map +1 -1
- package/dist/mcp-server.js.map +1 -1
- package/dist/{types-fDMu4rOd.d.ts → types-CO6v71UM.d.ts} +2 -0
- package/package.json +1 -1
- package/registry.json +8 -8
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,19 @@ 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.5.2] - 2026-03-24
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- **Zero manual commands after init**: Every detected service is now fully configured during init - plan, tier, and budget. No wall of `burnwatch add` commands. Registry now includes `suggestedBudget` for all usage-based plans (Anthropic $100, OpenAI $100, Stripe $50, Google Gemini $50, Voyage AI $20, AWS $50). Flat-rate services get budget = plan cost. Free tiers get $0.
|
|
13
|
+
- **Interactive interview uses suggested budgets as defaults**: Usage plans show `Monthly budget [$100]: $` instead of a blank prompt. Press Enter to accept.
|
|
14
|
+
|
|
15
|
+
## [0.5.1] - 2026-03-24
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- **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.
|
|
20
|
+
|
|
8
21
|
## [0.5.0] - 2026-03-24
|
|
9
22
|
|
|
10
23
|
### Changed
|
|
@@ -66,6 +79,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
66
79
|
- Snapshot system for delta computation across sessions
|
|
67
80
|
- Claude Code skills: `/spend` (on-demand brief), `/setup-burnwatch` (guided onboarding)
|
|
68
81
|
|
|
82
|
+
[0.5.2]: https://github.com/RaleighSF/burnwatch/compare/v0.5.1...v0.5.2
|
|
83
|
+
[0.5.1]: https://github.com/RaleighSF/burnwatch/compare/v0.5.0...v0.5.1
|
|
69
84
|
[0.5.0]: https://github.com/RaleighSF/burnwatch/compare/v0.4.3...v0.5.0
|
|
70
85
|
[0.4.3]: https://github.com/RaleighSF/burnwatch/compare/v0.4.2...v0.4.3
|
|
71
86
|
[0.4.2]: https://github.com/RaleighSF/burnwatch/compare/v0.4.0...v0.4.2
|
package/dist/cli.js
CHANGED
|
@@ -844,6 +844,73 @@ function findEnvKey(service) {
|
|
|
844
844
|
}
|
|
845
845
|
return void 0;
|
|
846
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
|
+
} else if (defaultPlan.suggestedBudget !== void 0) {
|
|
878
|
+
tracked.budget = defaultPlan.suggestedBudget;
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
const existingKey = globalConfig.services[service.id]?.apiKey;
|
|
882
|
+
const envKey = findEnvKey(service);
|
|
883
|
+
let keySource = "";
|
|
884
|
+
if (existingKey) {
|
|
885
|
+
tracked.hasApiKey = true;
|
|
886
|
+
keySource = " (key: global config)";
|
|
887
|
+
} else if (envKey) {
|
|
888
|
+
tracked.hasApiKey = true;
|
|
889
|
+
if (!globalConfig.services[service.id]) {
|
|
890
|
+
globalConfig.services[service.id] = {};
|
|
891
|
+
}
|
|
892
|
+
globalConfig.services[service.id].apiKey = envKey;
|
|
893
|
+
keySource = ` (key: ${service.envPatterns[0]})`;
|
|
894
|
+
}
|
|
895
|
+
const tierLabel = tracked.hasApiKey ? "LIVE" : tracked.planCost !== void 0 ? "CALC" : "BLIND";
|
|
896
|
+
const planStr = tracked.planName ? ` ${tracked.planName}` : "";
|
|
897
|
+
console.log(
|
|
898
|
+
` ${service.name}:${planStr} | ${tierLabel} | $${tracked.budget}/mo${keySource}`
|
|
899
|
+
);
|
|
900
|
+
services[service.id] = tracked;
|
|
901
|
+
}
|
|
902
|
+
console.log("");
|
|
903
|
+
}
|
|
904
|
+
const trackedList = Object.values(services);
|
|
905
|
+
const liveCount = trackedList.filter((s) => s.hasApiKey).length;
|
|
906
|
+
const totalBudget = trackedList.reduce((sum, s) => sum + (s.budget ?? 0), 0);
|
|
907
|
+
console.log(" " + "-".repeat(48));
|
|
908
|
+
console.log(` ${trackedList.length} services configured | Total budget: $${totalBudget}/mo`);
|
|
909
|
+
if (liveCount > 0) console.log(` ${liveCount} with real-time billing (LIVE)`);
|
|
910
|
+
console.log("");
|
|
911
|
+
writeGlobalConfig(globalConfig);
|
|
912
|
+
return { services };
|
|
913
|
+
}
|
|
847
914
|
async function runInteractiveInit(detected) {
|
|
848
915
|
const rl = readline.createInterface({
|
|
849
916
|
input: process.stdin,
|
|
@@ -957,25 +1024,16 @@ async function runInteractiveInit(detected) {
|
|
|
957
1024
|
}
|
|
958
1025
|
}
|
|
959
1026
|
}
|
|
960
|
-
const
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
1027
|
+
const defaultBudget = chosen.monthlyBase ?? chosen.suggestedBudget ?? 0;
|
|
1028
|
+
const budgetAnswer = await ask(
|
|
1029
|
+
rl,
|
|
1030
|
+
` Monthly budget [$${defaultBudget}]: $`
|
|
1031
|
+
);
|
|
1032
|
+
if (budgetAnswer) {
|
|
966
1033
|
const parsed = parseFloat(budgetAnswer);
|
|
967
|
-
tracked2.budget = !isNaN(parsed) ? parsed :
|
|
1034
|
+
tracked2.budget = !isNaN(parsed) ? parsed : defaultBudget;
|
|
968
1035
|
} else {
|
|
969
|
-
|
|
970
|
-
rl,
|
|
971
|
-
` Monthly budget [$${planCost}]: $`
|
|
972
|
-
);
|
|
973
|
-
if (budgetAnswer) {
|
|
974
|
-
const parsed = parseFloat(budgetAnswer);
|
|
975
|
-
tracked2.budget = !isNaN(parsed) ? parsed : planCost;
|
|
976
|
-
} else {
|
|
977
|
-
tracked2.budget = planCost;
|
|
978
|
-
}
|
|
1036
|
+
tracked2.budget = defaultBudget;
|
|
979
1037
|
}
|
|
980
1038
|
services[service.id] = tracked2;
|
|
981
1039
|
const tierLabel = tracked2.hasApiKey ? "LIVE" : tracked2.planCost !== void 0 ? "CALC" : "BLIND";
|
|
@@ -1062,76 +1120,29 @@ async function cmdInit() {
|
|
|
1062
1120
|
createdAt: existingConfig?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
1063
1121
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1064
1122
|
};
|
|
1065
|
-
if (
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
} else {
|
|
1069
|
-
const globalConfig = readGlobalConfig();
|
|
1123
|
+
if (detected.length === 0) {
|
|
1124
|
+
console.log(" No paid services detected yet.");
|
|
1125
|
+
console.log(" Services will be detected as they enter your project.\n");
|
|
1126
|
+
} else if (nonInteractive) {
|
|
1070
1127
|
for (const det of detected) {
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
hasApiKey: existing?.hasApiKey ?? false,
|
|
1080
|
-
firstDetected: existing?.firstDetected ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
1081
|
-
budget: 0
|
|
1082
|
-
// Always set — $0 is intentional, not missing
|
|
1083
|
-
};
|
|
1084
|
-
if (defaultPlan && defaultPlan.type !== "exclude") {
|
|
1085
|
-
tracked.planName = defaultPlan.name;
|
|
1086
|
-
if (defaultPlan.type === "flat" && defaultPlan.monthlyBase !== void 0) {
|
|
1087
|
-
tracked.planCost = defaultPlan.monthlyBase;
|
|
1088
|
-
tracked.budget = defaultPlan.monthlyBase;
|
|
1089
|
-
}
|
|
1090
|
-
}
|
|
1091
|
-
const existingKey = globalConfig.services[service.id]?.apiKey;
|
|
1092
|
-
if (existingKey) {
|
|
1093
|
-
tracked.hasApiKey = true;
|
|
1094
|
-
} else {
|
|
1095
|
-
for (const pattern of service.envPatterns) {
|
|
1096
|
-
if (process.env[pattern]) {
|
|
1097
|
-
tracked.hasApiKey = true;
|
|
1098
|
-
if (!globalConfig.services[service.id]) {
|
|
1099
|
-
globalConfig.services[service.id] = {};
|
|
1100
|
-
}
|
|
1101
|
-
globalConfig.services[service.id].apiKey = process.env[pattern];
|
|
1102
|
-
break;
|
|
1103
|
-
}
|
|
1104
|
-
}
|
|
1128
|
+
if (!config.services[det.service.id]) {
|
|
1129
|
+
config.services[det.service.id] = {
|
|
1130
|
+
serviceId: det.service.id,
|
|
1131
|
+
detectedVia: det.sources,
|
|
1132
|
+
hasApiKey: false,
|
|
1133
|
+
firstDetected: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1134
|
+
budget: 0
|
|
1135
|
+
};
|
|
1105
1136
|
}
|
|
1106
|
-
config.services[service.id] = tracked;
|
|
1107
1137
|
}
|
|
1108
|
-
|
|
1109
|
-
if (detected.length === 0) {
|
|
1110
|
-
console.log(" No paid services detected yet.");
|
|
1111
|
-
console.log(" Services will be detected as they enter your project.\n");
|
|
1112
|
-
} else {
|
|
1113
|
-
console.log(` Found ${detected.length} paid service${detected.length > 1 ? "s" : ""}:
|
|
1138
|
+
console.log(` Registered ${detected.length} services (non-interactive).
|
|
1114
1139
|
`);
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
console.log(` ${det.service.name}${planStr} [${tierBadge}]${budgetStr}`);
|
|
1122
|
-
if (svc?.budget === 0 && det.service.apiTier !== "calc") {
|
|
1123
|
-
needBudget.push(det.service.id);
|
|
1124
|
-
}
|
|
1125
|
-
}
|
|
1126
|
-
console.log("");
|
|
1127
|
-
if (needBudget.length > 0) {
|
|
1128
|
-
console.log(` ${needBudget.length} service${needBudget.length > 1 ? "s" : ""} need budgets. Set them with:`);
|
|
1129
|
-
for (const id of needBudget) {
|
|
1130
|
-
console.log(` burnwatch add ${id} --budget <AMOUNT>`);
|
|
1131
|
-
}
|
|
1132
|
-
console.log("");
|
|
1133
|
-
}
|
|
1134
|
-
}
|
|
1140
|
+
} else if (process.stdin.isTTY) {
|
|
1141
|
+
const result = await runInteractiveInit(detected);
|
|
1142
|
+
config.services = result.services;
|
|
1143
|
+
} else {
|
|
1144
|
+
const result = autoConfigureServices(detected);
|
|
1145
|
+
config.services = result.services;
|
|
1135
1146
|
}
|
|
1136
1147
|
writeProjectConfig(config, projectRoot);
|
|
1137
1148
|
const gitignorePath = path5.join(projectConfigDir(projectRoot), ".gitignore");
|