burnwatch 0.14.0 → 0.14.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/dist/cli.js +68 -16
- package/dist/cli.js.map +1 -1
- package/dist/cost-impact.d.ts +1 -1
- package/dist/{detector-DiBj3WjE.d.ts → detector-CkgIuoqj.d.ts} +1 -1
- package/dist/hooks/on-prompt.js +9 -2
- package/dist/hooks/on-prompt.js.map +1 -1
- package/dist/hooks/on-session-start.js +50 -20
- package/dist/hooks/on-session-start.js.map +1 -1
- package/dist/hooks/on-stop.js +25 -12
- package/dist/hooks/on-stop.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +34 -7
- package/dist/index.js.map +1 -1
- package/dist/interactive-init.d.ts +2 -2
- package/dist/interactive-init.js +25 -0
- package/dist/interactive-init.js.map +1 -1
- package/dist/mcp-server.js +48 -19
- package/dist/mcp-server.js.map +1 -1
- package/dist/{types-CUAiYzmE.d.ts → types-Be52LNVD.d.ts} +6 -0
- package/package.json +1 -1
- package/registry.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -639,9 +639,9 @@ var anthropicConnector = {
|
|
|
639
639
|
async fetchSpend(apiKey) {
|
|
640
640
|
const now = /* @__PURE__ */ new Date();
|
|
641
641
|
const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);
|
|
642
|
-
const
|
|
643
|
-
const
|
|
644
|
-
const url3 = `https://api.anthropic.com/v1/organizations/
|
|
642
|
+
const startingAt = startOfMonth.toISOString();
|
|
643
|
+
const endingAt = now.toISOString();
|
|
644
|
+
const url3 = `https://api.anthropic.com/v1/organizations/cost_report?starting_at=${encodeURIComponent(startingAt)}&ending_at=${encodeURIComponent(endingAt)}&bucket_width=1m`;
|
|
645
645
|
const result = await fetchJson(url3, {
|
|
646
646
|
headers: {
|
|
647
647
|
"x-api-key": apiKey,
|
|
@@ -654,17 +654,18 @@ var anthropicConnector = {
|
|
|
654
654
|
spend: 0,
|
|
655
655
|
isEstimate: true,
|
|
656
656
|
tier: "est",
|
|
657
|
-
error: result.error ?? "Failed to fetch Anthropic
|
|
657
|
+
error: result.error ?? "Failed to fetch Anthropic cost report"
|
|
658
658
|
};
|
|
659
659
|
}
|
|
660
660
|
let totalSpend = 0;
|
|
661
|
-
if (result.data.
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
661
|
+
if (result.data.data) {
|
|
662
|
+
for (const bucket of result.data.data) {
|
|
663
|
+
if (bucket.results) {
|
|
664
|
+
for (const entry of bucket.results) {
|
|
665
|
+
totalSpend += entry.cost_usd ?? 0;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
}
|
|
668
669
|
}
|
|
669
670
|
return {
|
|
670
671
|
serviceId: "anthropic",
|
|
@@ -1094,24 +1095,33 @@ function formatBrief(brief) {
|
|
|
1094
1095
|
lines.push(formatRow("Service", "Spend", "Conf", "Budget", "Left"));
|
|
1095
1096
|
lines.push(`\u2551 ${hrThin}`);
|
|
1096
1097
|
for (const svc of brief.services.filter((s) => s.tier !== "excluded")) {
|
|
1097
|
-
const spendStr =
|
|
1098
|
+
const spendStr = formatSpendValue(svc);
|
|
1098
1099
|
const badge = CONFIDENCE_BADGES[svc.tier];
|
|
1099
1100
|
const budgetStr = svc.budget ? `$${svc.budget}` : "\u2014";
|
|
1100
1101
|
const leftStr = formatLeft(svc);
|
|
1101
1102
|
lines.push(formatRow(svc.serviceId, spendStr, badge, budgetStr, leftStr));
|
|
1102
1103
|
if (svc.allowance) {
|
|
1103
1104
|
const usedStr = formatCompact(svc.allowance.used);
|
|
1104
|
-
const
|
|
1105
|
+
const totalStr = formatCompact(svc.allowance.included);
|
|
1105
1106
|
const pctStr = svc.allowance.percent.toFixed(0);
|
|
1106
1107
|
const warn = svc.allowance.percent >= 75 ? " \u26A0\uFE0F" : "";
|
|
1107
|
-
lines.push(`\u2551 \u21B3 ${usedStr}/${
|
|
1108
|
+
lines.push(`\u2551 \u21B3 ${usedStr}/${totalStr} ${svc.allowance.unitName} (${pctStr}%)${warn}`);
|
|
1108
1109
|
}
|
|
1109
1110
|
}
|
|
1110
1111
|
lines.push(`\u2560${hr}`);
|
|
1111
|
-
const
|
|
1112
|
+
const parts = [];
|
|
1113
|
+
if (brief.liveSpend > 0) {
|
|
1114
|
+
parts.push(`Spend: $${brief.liveSpend.toFixed(2)}`);
|
|
1115
|
+
}
|
|
1116
|
+
if (brief.planCostTotal > 0) {
|
|
1117
|
+
parts.push(`Plans: $${brief.planCostTotal.toFixed(0)}/mo`);
|
|
1118
|
+
}
|
|
1119
|
+
if (parts.length === 0) {
|
|
1120
|
+
parts.push(`$${brief.totalSpend.toFixed(2)}`);
|
|
1121
|
+
}
|
|
1112
1122
|
const marginStr = brief.estimateMargin > 0 ? ` Est margin: \xB1$${brief.estimateMargin.toFixed(0)}` : "";
|
|
1113
1123
|
const untrackedStr = brief.untrackedCount > 0 ? `No billing data: ${brief.untrackedCount} \u26A0\uFE0F` : `All tracked \u2705`;
|
|
1114
|
-
lines.push(`\u2551
|
|
1124
|
+
lines.push(`\u2551 ${parts.join(" | ")} ${untrackedStr}${marginStr}`);
|
|
1115
1125
|
for (const alert of brief.alerts) {
|
|
1116
1126
|
const icon = alert.severity === "critical" ? "\u{1F6A8}" : "\u26A0\uFE0F";
|
|
1117
1127
|
lines.push(`\u2551 ${icon} ${alert.message}`);
|
|
@@ -1126,11 +1136,18 @@ function buildBrief(projectName, snapshots, blindCount) {
|
|
|
1126
1136
|
year: "numeric"
|
|
1127
1137
|
});
|
|
1128
1138
|
let totalSpend = 0;
|
|
1139
|
+
let liveSpend = 0;
|
|
1140
|
+
let planCostTotal = 0;
|
|
1129
1141
|
let hasEstimates = false;
|
|
1130
1142
|
let estimateMargin = 0;
|
|
1131
1143
|
const alerts = [];
|
|
1132
1144
|
for (const snap of snapshots) {
|
|
1133
1145
|
totalSpend += snap.spend;
|
|
1146
|
+
if (snap.isPlanCost) {
|
|
1147
|
+
planCostTotal += snap.spend;
|
|
1148
|
+
} else if (snap.tier === "live") {
|
|
1149
|
+
liveSpend += snap.spend;
|
|
1150
|
+
}
|
|
1134
1151
|
if (snap.isEstimate) {
|
|
1135
1152
|
hasEstimates = true;
|
|
1136
1153
|
estimateMargin += snap.spend * 0.15;
|
|
@@ -1167,6 +1184,8 @@ function buildBrief(projectName, snapshots, blindCount) {
|
|
|
1167
1184
|
period,
|
|
1168
1185
|
services: snapshots,
|
|
1169
1186
|
totalSpend,
|
|
1187
|
+
liveSpend,
|
|
1188
|
+
planCostTotal,
|
|
1170
1189
|
totalIsEstimate: hasEstimates,
|
|
1171
1190
|
estimateMargin,
|
|
1172
1191
|
untrackedCount: blindCount,
|
|
@@ -1176,6 +1195,12 @@ function buildBrief(projectName, snapshots, blindCount) {
|
|
|
1176
1195
|
function formatRow(service, spend, conf, budget, left) {
|
|
1177
1196
|
return `\u2551 ${service.padEnd(14)} ${spend.padEnd(11)} ${conf.padEnd(9)} ${budget.padEnd(7)} ${left}`;
|
|
1178
1197
|
}
|
|
1198
|
+
function formatSpendValue(svc) {
|
|
1199
|
+
if (svc.tier === "blind" && svc.spend === 0) return "\u2014";
|
|
1200
|
+
if (svc.isPlanCost) return `$${svc.spend.toFixed(0)}/mo`;
|
|
1201
|
+
if (svc.isEstimate) return `~$${svc.spend.toFixed(2)}`;
|
|
1202
|
+
return `$${svc.spend.toFixed(2)}`;
|
|
1203
|
+
}
|
|
1179
1204
|
function formatLeft(snap) {
|
|
1180
1205
|
if (!snap.budget) return "\u2014";
|
|
1181
1206
|
if (snap.status === "over") return "\u26A0\uFE0F OVER";
|
|
@@ -1189,6 +1214,7 @@ function buildSnapshot(serviceId, tier, spend, budget, allowanceData, isEstimate
|
|
|
1189
1214
|
if (isNaN(spend) || !isFinite(spend)) spend = 0;
|
|
1190
1215
|
if (budget !== void 0 && (isNaN(budget) || !isFinite(budget))) budget = void 0;
|
|
1191
1216
|
const isEstimate = isEstimateOverride ?? (tier === "est" || tier === "calc");
|
|
1217
|
+
const isPlanCost = tier === "calc" && isFlatPlan === true;
|
|
1192
1218
|
const budgetPercent = budget && budget > 0 ? spend / budget * 100 : void 0;
|
|
1193
1219
|
let status = "unknown";
|
|
1194
1220
|
let statusLabel = tier === "blind" ? "needs API key" : "no budget";
|
|
@@ -1229,6 +1255,7 @@ function buildSnapshot(serviceId, tier, spend, budget, allowanceData, isEstimate
|
|
|
1229
1255
|
serviceId,
|
|
1230
1256
|
spend,
|
|
1231
1257
|
isEstimate,
|
|
1258
|
+
isPlanCost,
|
|
1232
1259
|
tier,
|
|
1233
1260
|
budget,
|
|
1234
1261
|
budgetPercent,
|
|
@@ -1626,6 +1653,31 @@ async function runInteractiveInit(detected) {
|
|
|
1626
1653
|
} else {
|
|
1627
1654
|
tracked2.budget = defaultBudget;
|
|
1628
1655
|
}
|
|
1656
|
+
if (tracked2.budget > 0 && tracked2.budget !== (chosen.monthlyBase ?? 0)) {
|
|
1657
|
+
const betterPlan = plans.find(
|
|
1658
|
+
(p) => p !== chosen && p.type !== "exclude" && p.monthlyBase === tracked2.budget
|
|
1659
|
+
);
|
|
1660
|
+
if (betterPlan) {
|
|
1661
|
+
const switchAnswer = await ask(
|
|
1662
|
+
rl,
|
|
1663
|
+
` Budget matches "${betterPlan.name}" \u2014 switch to that plan? [Y/n]: `
|
|
1664
|
+
);
|
|
1665
|
+
if (!switchAnswer || switchAnswer.toLowerCase() !== "n") {
|
|
1666
|
+
chosen = betterPlan;
|
|
1667
|
+
tracked2.planName = betterPlan.name;
|
|
1668
|
+
if (betterPlan.type === "flat" && betterPlan.monthlyBase !== void 0) {
|
|
1669
|
+
tracked2.planCost = betterPlan.monthlyBase;
|
|
1670
|
+
}
|
|
1671
|
+
if (betterPlan.includedUnits !== void 0 && betterPlan.unitName) {
|
|
1672
|
+
tracked2.allowance = {
|
|
1673
|
+
included: betterPlan.includedUnits,
|
|
1674
|
+
unitName: betterPlan.unitName
|
|
1675
|
+
};
|
|
1676
|
+
}
|
|
1677
|
+
console.log(` -> Switched to ${betterPlan.name}`);
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1629
1681
|
services[service.id] = tracked2;
|
|
1630
1682
|
const tierLabel = tracked2.hasApiKey ? "LIVE" : tracked2.planCost !== void 0 ? "CALC" : "BLIND";
|
|
1631
1683
|
const allowanceStr = tracked2.allowance ? ` | ${formatUnits(tracked2.allowance.included)} ${tracked2.allowance.unitName}` : "";
|