@trazum/cli 1.33.0 → 1.34.0
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/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +7 -0
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/es.d.ts.map +1 -1
- package/dist/i18n/es.js +8 -0
- package/dist/i18n/es.js.map +1 -1
- package/dist/i18n/types.d.ts +7 -0
- package/dist/i18n/types.d.ts.map +1 -1
- package/dist/index.js +52 -7
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/i18n/en.ts +9 -0
- package/src/i18n/es.ts +10 -0
- package/src/i18n/types.ts +7 -0
- package/src/index.ts +50 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trazum/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.34.0",
|
|
4
4
|
"description": "Trazum CLI: find where your LLM bill goes, price every finding per month, and enforce token budgets in CI.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "David Mu\u00f1oz Rey",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"prepublishOnly": "npm run build && npm test"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@trazum/core": "1.
|
|
40
|
+
"@trazum/core": "1.34.0"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@types/node": "^26.2.0",
|
package/src/i18n/en.ts
CHANGED
|
@@ -345,6 +345,11 @@ ${bold('CONFIG FILE')}
|
|
|
345
345
|
spend { "maxUsd": 200, "byLabel": { "chat": 40 } } — money budgets for
|
|
346
346
|
"trazum profile", in dollars. A budgeted label with no calls in
|
|
347
347
|
the log is reported as not measured, never as a pass
|
|
348
|
+
waive [{ "gate": "maxUsd", "reason": "August migration", "until":
|
|
349
|
+
"2026-09-15" }] — a gate failure decided about, on the record.
|
|
350
|
+
All three fields required: a waiver with no end date is a
|
|
351
|
+
finding deleted with extra steps. Waived failures still print,
|
|
352
|
+
and the day the waiver expires the gate fails again
|
|
348
353
|
|
|
349
354
|
Flags beat the config; the config beats the defaults. Budgets resolve to the
|
|
350
355
|
most specific matching pattern — most literal characters wins. A boolean the
|
|
@@ -1199,6 +1204,10 @@ ${bold('EXAMPLES')}
|
|
|
1199
1204
|
`The 95th percentile is ${ratio}x the median there: most conversations are cheap and a few are not, which is a tail a quota can catch. Where median and p95 sit close together the workload is simply expensive and there is no tail to hunt.`,
|
|
1200
1205
|
sessionSpendOnly: (sessions, max) =>
|
|
1201
1206
|
`${sessions} ${sessions === '1' ? 'conversation' : 'conversations'} in this log; the most expensive cost ${max}. Too few per workload for a percentile — a maximum is a fact at any count, and it is the figure --max-session-usd judges.`,
|
|
1207
|
+
waiveActive: (gate, reason, until, daysLeft) =>
|
|
1208
|
+
`WAIVED — the ${gate} failure above is on the record and silenced until ${until} (${daysLeft} days left): "${reason}". The bill still counts it; only the exit code is quiet, and the day the waiver expires this gate fails again.`,
|
|
1209
|
+
waiveExpired: (gate, until, reason) =>
|
|
1210
|
+
`The waiver on ${gate} expired on ${until} and no longer silences anything. It was written for: "${reason}". Renew it with a new date and a current reason, or fix what it was covering — an expired waiver left in place is a finding deleted with extra steps.`,
|
|
1202
1211
|
gateLargest: (label, model, usd, share) =>
|
|
1203
1212
|
`Most of it is ${label} on ${model}: ${usd}, ${share} of the bill. That is where the money is, not necessarily where the fix is.`,
|
|
1204
1213
|
gateLever: (label, action, saving, overage, covers) =>
|
package/src/i18n/es.ts
CHANGED
|
@@ -348,6 +348,12 @@ ${bold('FICHERO DE CONFIGURACIÓN')}
|
|
|
348
348
|
spend { "maxUsd": 200, "byLabel": { "chat": 40 } } — presupuestos en
|
|
349
349
|
dólares para "trazum profile". Una etiqueta con presupuesto y sin
|
|
350
350
|
llamadas se informa como no medida, nunca como aprobada
|
|
351
|
+
waive [{ "gate": "maxUsd", "reason": "migración de agosto", "until":
|
|
352
|
+
"2026-09-15" }] — un fallo de gate sobre el que se ha decidido,
|
|
353
|
+
registrado. Los tres campos son obligatorios: un waiver sin
|
|
354
|
+
fecha de fin es un hallazgo borrado con pasos extra. Los fallos
|
|
355
|
+
waived se siguen imprimiendo, y el día que caduca el gate
|
|
356
|
+
vuelve a fallar
|
|
351
357
|
|
|
352
358
|
Las opciones ganan al config; el config gana a los valores por defecto. Los
|
|
353
359
|
presupuestos se resuelven con el patrón más específico que encaje — gana el
|
|
@@ -1214,6 +1220,10 @@ ${bold('EJEMPLOS')}
|
|
|
1214
1220
|
`El percentil 95 es ${ratio}x la mediana ahí: casi todas las conversaciones son baratas y unas pocas no, y esa es una cola que una cuota puede cazar. Cuando mediana y p95 quedan cerca, la carga es cara sin más y no hay cola que perseguir.`,
|
|
1215
1221
|
sessionSpendOnly: (sessions, max) =>
|
|
1216
1222
|
`${sessions} ${sessions === '1' ? 'conversación' : 'conversaciones'} en este registro; la más cara costó ${max}. Demasiado pocas por carga para un percentil — un máximo es un hecho con cualquier recuento, y es la cifra que juzga --max-session-usd.`,
|
|
1223
|
+
waiveActive: (gate, reason, until, daysLeft) =>
|
|
1224
|
+
`WAIVED — el fallo de ${gate} de arriba queda registrado y silenciado hasta ${until} (${daysLeft} días restantes): "${reason}". La factura lo sigue contando; solo el código de salida calla, y el día que caduque el waiver este gate vuelve a fallar.`,
|
|
1225
|
+
waiveExpired: (gate, until, reason) =>
|
|
1226
|
+
`El waiver de ${gate} caducó el ${until} y ya no silencia nada. Se escribió por: "${reason}". Renuévalo con fecha nueva y razón vigente, o arregla lo que cubría — un waiver caducado dejado ahí es un hallazgo borrado con pasos extra.`,
|
|
1217
1227
|
gateLargest: (label, model, usd, share) =>
|
|
1218
1228
|
`La mayor parte es ${label} en ${model}: ${usd}, el ${share} de la factura. Ahí está el dinero, no necesariamente el arreglo.`,
|
|
1219
1229
|
gateLever: (label, action, saving, overage, covers) =>
|
package/src/i18n/types.ts
CHANGED
|
@@ -784,6 +784,13 @@ export interface CliMessages {
|
|
|
784
784
|
* computed. Nothing here recommends: whether the cheaper model can do the
|
|
785
785
|
* work is the reader's to judge, and the copy says so.
|
|
786
786
|
*/
|
|
787
|
+
/**
|
|
788
|
+
* Findings as policy. An active waiver silences one gate's exit code, on
|
|
789
|
+
* the record, with the reason and the days left; an expired one silences
|
|
790
|
+
* nothing and says so beside the failure it used to cover.
|
|
791
|
+
*/
|
|
792
|
+
waiveActive(gate: string, reason: string, until: string, daysLeft: string): string;
|
|
793
|
+
waiveExpired(gate: string, until: string, reason: string): string;
|
|
787
794
|
gateLargest(label: string, model: string, usd: string, share: string): string;
|
|
788
795
|
gateLever(label: string, action: string, saving: string, overage: string, covers: boolean): string;
|
|
789
796
|
/**
|
package/src/index.ts
CHANGED
|
@@ -2312,6 +2312,46 @@ async function commandProfile(args: Args, config: TrazumConfig, pricing: Pricing
|
|
|
2312
2312
|
*/
|
|
2313
2313
|
const gateVerdicts: string[] = [];
|
|
2314
2314
|
let gateFailed = false;
|
|
2315
|
+
/**
|
|
2316
|
+
* Findings as policy. A waiver silences one gate's exit code for a bounded
|
|
2317
|
+
* time, on the record: the failure still prints (waived is shown as
|
|
2318
|
+
* waived, never hidden — the bill still counts it), the reason and the
|
|
2319
|
+
* days left print beside it, and the day the waiver expires the gate
|
|
2320
|
+
* fails again louder, naming the date and the reason somebody wrote.
|
|
2321
|
+
* That expiry is the entire mechanism by which a waiver stays a decision
|
|
2322
|
+
* instead of becoming a habit.
|
|
2323
|
+
*/
|
|
2324
|
+
const waiverFor = (gate: string): { entry: { gate: string; reason: string; until: string }; expired: boolean } | null => {
|
|
2325
|
+
const entry = (config.waive ?? []).find((w) => w.gate === gate);
|
|
2326
|
+
if (entry === undefined) return null;
|
|
2327
|
+
// The waiver covers its named day whole: expiry begins the next UTC day.
|
|
2328
|
+
const expiresMs = Date.parse(`${entry.until}T00:00:00Z`) + 86_400_000;
|
|
2329
|
+
return { entry, expired: Date.now() >= expiresMs };
|
|
2330
|
+
};
|
|
2331
|
+
/**
|
|
2332
|
+
* Applies a waiver to one failing gate. Returns true when the failure is
|
|
2333
|
+
* silenced — the caller skips its exitCode — and prints the record either
|
|
2334
|
+
* way, because a waived failure that vanished from the output would be a
|
|
2335
|
+
* finding deleted with extra steps.
|
|
2336
|
+
*/
|
|
2337
|
+
const waived = (gate: string): boolean => {
|
|
2338
|
+
const found = waiverFor(gate);
|
|
2339
|
+
if (found === null) return false;
|
|
2340
|
+
if (found.expired) {
|
|
2341
|
+
console.error(
|
|
2342
|
+
c.red(t.profile.waiveExpired(gate, found.entry.until, found.entry.reason)),
|
|
2343
|
+
);
|
|
2344
|
+
return false;
|
|
2345
|
+
}
|
|
2346
|
+
const daysLeft = Math.max(
|
|
2347
|
+
0,
|
|
2348
|
+
Math.ceil((Date.parse(`${found.entry.until}T00:00:00Z`) + 86_400_000 - Date.now()) / 86_400_000),
|
|
2349
|
+
);
|
|
2350
|
+
console.error(
|
|
2351
|
+
c.yellow(t.profile.waiveActive(gate, found.entry.reason, found.entry.until, String(daysLeft))),
|
|
2352
|
+
);
|
|
2353
|
+
return true;
|
|
2354
|
+
};
|
|
2315
2355
|
const applyGates = (): void => {
|
|
2316
2356
|
/**
|
|
2317
2357
|
* Before any verdict: whether the gated figure is the whole bill. A gate
|
|
@@ -2358,7 +2398,7 @@ async function commandProfile(args: Args, config: TrazumConfig, pricing: Pricing
|
|
|
2358
2398
|
}
|
|
2359
2399
|
if (usd > limit) {
|
|
2360
2400
|
console.error(c.red(t.profile.labelBudgetFailed(label, formatUsd(usd), formatUsd(limit))));
|
|
2361
|
-
process.exitCode = 1;
|
|
2401
|
+
if (!waived(`byLabel:${label}`)) process.exitCode = 1;
|
|
2362
2402
|
} else {
|
|
2363
2403
|
console.error(c.dim(t.profile.labelBudgetOk(label, formatUsd(usd), formatUsd(limit))));
|
|
2364
2404
|
}
|
|
@@ -2431,7 +2471,7 @@ async function commandProfile(args: Args, config: TrazumConfig, pricing: Pricing
|
|
|
2431
2471
|
* copy says so.
|
|
2432
2472
|
*/
|
|
2433
2473
|
explainFailure(report.total.totalUsd - maxUsd);
|
|
2434
|
-
process.exitCode = 1;
|
|
2474
|
+
if (!waived('maxUsd')) process.exitCode = 1;
|
|
2435
2475
|
} else {
|
|
2436
2476
|
console.error(c.dim(t.profile.maxUsdOk(formatUsd(report.total.totalUsd), formatUsd(maxUsd))));
|
|
2437
2477
|
explainMargin(report.total.totalUsd, maxUsd);
|
|
@@ -2465,10 +2505,13 @@ async function commandProfile(args: Args, config: TrazumConfig, pricing: Pricing
|
|
|
2465
2505
|
),
|
|
2466
2506
|
),
|
|
2467
2507
|
);
|
|
2508
|
+
// Deliberately unwaivable: this failure is "the comparison cannot
|
|
2509
|
+
// be made", and a waiver on unmeasurability would be a decision to
|
|
2510
|
+
// stop measuring — not a budget decision with an end date.
|
|
2468
2511
|
process.exitCode = 1;
|
|
2469
2512
|
} else if (againstDelta > maxGrowth) {
|
|
2470
2513
|
console.error(c.red(t.profile.maxGrowthUsdFailed(formatSignedUsd(againstDelta), formatUsd(maxGrowth))));
|
|
2471
|
-
process.exitCode = 1;
|
|
2514
|
+
if (!waived('maxGrowthUsd')) process.exitCode = 1;
|
|
2472
2515
|
}
|
|
2473
2516
|
}
|
|
2474
2517
|
/**
|
|
@@ -2486,7 +2529,7 @@ async function commandProfile(args: Args, config: TrazumConfig, pricing: Pricing
|
|
|
2486
2529
|
console.error(
|
|
2487
2530
|
c.red(t.profile.maxCacheLossFailed(formatUsd(gateCache.deltaUsd), formatUsd(maxLoss))),
|
|
2488
2531
|
);
|
|
2489
|
-
process.exitCode = 1;
|
|
2532
|
+
if (!waived('maxCacheLossUsd')) process.exitCode = 1;
|
|
2490
2533
|
} else if (gateCache.worstCaseDeltaUsd > maxLoss) {
|
|
2491
2534
|
console.error(
|
|
2492
2535
|
c.red(
|
|
@@ -2497,7 +2540,7 @@ async function commandProfile(args: Args, config: TrazumConfig, pricing: Pricing
|
|
|
2497
2540
|
),
|
|
2498
2541
|
),
|
|
2499
2542
|
);
|
|
2500
|
-
process.exitCode = 1;
|
|
2543
|
+
if (!waived('maxCacheLossUsd')) process.exitCode = 1;
|
|
2501
2544
|
} else {
|
|
2502
2545
|
console.error(
|
|
2503
2546
|
c.dim(t.profile.maxCacheLossOk(formatUsd(Math.max(0, gateCache.worstCaseDeltaUsd)), formatUsd(maxLoss))),
|
|
@@ -2546,7 +2589,7 @@ async function commandProfile(args: Args, config: TrazumConfig, pricing: Pricing
|
|
|
2546
2589
|
c.red(`${t.profile.maxDayFailed(worst.day, formatUsd(worst.usd), formatUsd(maxDay))}${suspect}`),
|
|
2547
2590
|
);
|
|
2548
2591
|
explainFailure(worst.usd - maxDay, { namesLargest: true });
|
|
2549
|
-
process.exitCode = 1;
|
|
2592
|
+
if (!waived('maxDayUsd')) process.exitCode = 1;
|
|
2550
2593
|
} else {
|
|
2551
2594
|
console.error(c.dim(t.profile.maxDayOk(worst.day, formatUsd(worst.usd), formatUsd(maxDay))));
|
|
2552
2595
|
explainMargin(worst.usd, maxDay);
|
|
@@ -2588,7 +2631,7 @@ async function commandProfile(args: Args, config: TrazumConfig, pricing: Pricing
|
|
|
2588
2631
|
c.red(t.profile.maxSessionFailed(formatUsd(report.sessionSpend.maxUsd), formatUsd(maxSession), n(report.sessionSpend.sessions))),
|
|
2589
2632
|
);
|
|
2590
2633
|
explainFailure(report.sessionSpend.maxUsd - maxSession);
|
|
2591
|
-
process.exitCode = 1;
|
|
2634
|
+
if (!waived('maxSessionUsd')) process.exitCode = 1;
|
|
2592
2635
|
} else {
|
|
2593
2636
|
console.error(
|
|
2594
2637
|
c.dim(t.profile.maxSessionOk(formatUsd(report.sessionSpend.maxUsd), formatUsd(maxSession), n(report.sessionSpend.sessions))),
|