@trazum/cli 1.28.0 → 1.29.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 +8 -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 +16 -0
- package/dist/i18n/types.d.ts.map +1 -1
- package/dist/index.js +43 -1
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/i18n/en.ts +12 -0
- package/src/i18n/es.ts +12 -0
- package/src/i18n/types.ts +16 -0
- package/src/index.ts +48 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trazum/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.29.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.29.0"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@types/node": "^26.2.0",
|
package/src/i18n/en.ts
CHANGED
|
@@ -234,6 +234,10 @@ ${bold('OPTIONS FOR profile')}
|
|
|
234
234
|
afternoon a loop burned a quarter of it. A log
|
|
235
235
|
with no timestamps fails: not measured is not
|
|
236
236
|
under budget.
|
|
237
|
+
--max-session-usd <n> Fail when any single conversation in the log
|
|
238
|
+
cost more than this — the unit an agent product
|
|
239
|
+
blows up in. A log with no sessions fails: not
|
|
240
|
+
measured is not under budget.
|
|
237
241
|
--since <when> Profile only calls at or after this moment. A UTC
|
|
238
242
|
--until <when> day (2026-08-14), a full ISO 8601 timestamp, a
|
|
239
243
|
relative window (7d, 24h) or "now";
|
|
@@ -1081,6 +1085,12 @@ ${bold('EXAMPLES')}
|
|
|
1081
1085
|
`FAILED — ${day} spent ${usd}, over the --max-day-usd limit of ${max}. A total under budget can hide a single runaway day, which is what this gate exists to catch.`,
|
|
1082
1086
|
maxDayNoClock: () =>
|
|
1083
1087
|
'FAILED — --max-day-usd was asked for and no record in this log carries a timestamp, so there are no days to judge. That is not a pass: a bill nobody could measure by day is not a bill that stayed under a daily budget. Add "ts" to the record and the gate arms.',
|
|
1088
|
+
maxSessionOk: (worst, max, sessions) =>
|
|
1089
|
+
`No conversation over budget: the most expensive of ${sessions} cost ${worst}, against --max-session-usd ${max}. A conversation that started before this log is counted only for the turns recorded here, so this is a floor.`,
|
|
1090
|
+
maxSessionFailed: (worst, max, sessions) =>
|
|
1091
|
+
`FAILED — the most expensive of ${sessions} conversations cost ${worst}, over the --max-session-usd limit of ${max}. A month's budget and a day's budget both pass while one conversation loops its way through this; the per-conversation figure is the one that catches it.`,
|
|
1092
|
+
maxSessionNoSessions: () =>
|
|
1093
|
+
'FAILED — --max-session-usd was asked for and no record in this log carries a session, so there are no conversations to judge. That is not a pass. Add "session" (or "conversation_id") to the record and the gate arms; Trazum groups by it and never prints it.',
|
|
1084
1094
|
maxDayUndated: (calls) =>
|
|
1085
1095
|
`${calls} calls carry no timestamp, so they are in the bill and in none of the days above — the worst day is a floor by whatever they held. A failure would stand regardless; this pass is over the part that could be dated.`,
|
|
1086
1096
|
maxCacheLossWorstCase: (calls, worst, max) =>
|
|
@@ -1161,6 +1171,8 @@ ${bold('EXAMPLES')}
|
|
|
1161
1171
|
`No usage logs in "${directory}". Looked for files ending in ${extensions}. A directory with nothing readable in it is an error rather than an empty report, which would read as "you spent nothing".`,
|
|
1162
1172
|
sessionCostTail: (ratio) =>
|
|
1163
1173
|
`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.`,
|
|
1174
|
+
sessionSpendOnly: (sessions, max) =>
|
|
1175
|
+
`${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.`,
|
|
1164
1176
|
againstOverlap: (from, to) =>
|
|
1165
1177
|
`These two logs both cover ${from} → ${to}, so some of the same calls sit on both sides of this subtraction and part of the change is the same money counted twice. Compare periods that do not overlap — or window both logs with --since/--until.`,
|
|
1166
1178
|
windowLine: (since, until) =>
|
package/src/i18n/es.ts
CHANGED
|
@@ -232,6 +232,10 @@ ${bold('OPCIONES DE profile')}
|
|
|
232
232
|
una cuarta parte. Un registro sin marcas de
|
|
233
233
|
tiempo falla: no medido no es dentro de
|
|
234
234
|
presupuesto.
|
|
235
|
+
--max-session-usd <n> Falla cuando una sola conversación del registro
|
|
236
|
+
costó más que esto — la unidad en la que revienta
|
|
237
|
+
un producto de agentes. Un registro sin sesiones
|
|
238
|
+
falla: no medido no es dentro de presupuesto.
|
|
235
239
|
--since <cuándo> Perfila solo llamadas desde/hasta ese momento. Un
|
|
236
240
|
--until <cuándo> día UTC (2026-08-14), una marca ISO 8601 completa,
|
|
237
241
|
una ventana relativa (7d, 24h) o "now";
|
|
@@ -1095,6 +1099,12 @@ ${bold('EJEMPLOS')}
|
|
|
1095
1099
|
`FALLO — ${day} gastó ${usd}, por encima del límite --max-day-usd de ${max}. Un total dentro de presupuesto puede esconder un solo día desbocado, que es justo lo que vigila esta puerta.`,
|
|
1096
1100
|
maxDayNoClock: () =>
|
|
1097
1101
|
'FALLO — se pidió --max-day-usd y ningún registro de este fichero lleva marca de tiempo, así que no hay días que juzgar. Eso no es un aprobado: una factura que nadie pudo medir por días no es una factura que se mantuvo bajo un presupuesto diario. Añade "ts" al registro y la puerta se arma.',
|
|
1102
|
+
maxSessionOk: (worst, max, sessions) =>
|
|
1103
|
+
`Ninguna conversación por encima del presupuesto: la más cara de ${sessions} costó ${worst}, contra --max-session-usd ${max}. Una conversación que empezó antes de este registro solo cuenta los turnos grabados aquí, así que esto es un suelo.`,
|
|
1104
|
+
maxSessionFailed: (worst, max, sessions) =>
|
|
1105
|
+
`FALLO — la más cara de ${sessions} conversaciones costó ${worst}, por encima del límite --max-session-usd de ${max}. El presupuesto del mes y el del día aprueban mientras una conversación en bucle se come esto; la cifra por conversación es la que lo caza.`,
|
|
1106
|
+
maxSessionNoSessions: () =>
|
|
1107
|
+
'FALLO — se pidió --max-session-usd y ningún registro lleva sesión, así que no hay conversaciones que juzgar. Eso no es un aprobado. Añade "session" (o "conversation_id") al registro y la puerta se arma; Trazum agrupa por ella y nunca la imprime.',
|
|
1098
1108
|
maxDayUndated: (calls) =>
|
|
1099
1109
|
`${calls} llamadas no llevan marca de tiempo, así que están en la factura y en ninguno de los días de arriba — el peor día es un suelo por lo que valieran esas llamadas. Un fallo se sostendría igual; este aprobado cubre la parte que se pudo fechar.`,
|
|
1100
1110
|
maxCacheLossWorstCase: (calls, worst, max) =>
|
|
@@ -1176,6 +1186,8 @@ ${bold('EJEMPLOS')}
|
|
|
1176
1186
|
`No hay registros de uso en "${directory}". Se buscaron ficheros terminados en ${extensions}. Un directorio sin nada legible es un error, no un informe vacío, que se leería como "no has gastado nada".`,
|
|
1177
1187
|
sessionCostTail: (ratio) =>
|
|
1178
1188
|
`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.`,
|
|
1189
|
+
sessionSpendOnly: (sessions, max) =>
|
|
1190
|
+
`${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.`,
|
|
1179
1191
|
againstOverlap: (from, to) =>
|
|
1180
1192
|
`Estos dos registros cubren ambos ${from} → ${to}, así que algunas de las mismas llamadas están a los dos lados de esta resta y parte del cambio es el mismo dinero contado dos veces. Compara periodos que no se solapen — o acota ambos registros con --since/--until.`,
|
|
1181
1193
|
windowLine: (since, until) =>
|
package/src/i18n/types.ts
CHANGED
|
@@ -730,6 +730,15 @@ export interface CliMessages {
|
|
|
730
730
|
maxDayFailed(day: string, usd: string, max: string): string;
|
|
731
731
|
maxDayNoClock(): string;
|
|
732
732
|
maxDayUndated(calls: string): string;
|
|
733
|
+
/**
|
|
734
|
+
* The per-conversation gate. The single most expensive conversation is
|
|
735
|
+
* the number a per-conversation policy judges; a log with no sessions
|
|
736
|
+
* fails, and a conversation that started before the log makes the pass
|
|
737
|
+
* a floor — said in the pass message. The session key never appears.
|
|
738
|
+
*/
|
|
739
|
+
maxSessionOk(worst: string, max: string, sessions: string): string;
|
|
740
|
+
maxSessionFailed(worst: string, max: string, sessions: string): string;
|
|
741
|
+
maxSessionNoSessions(): string;
|
|
733
742
|
/**
|
|
734
743
|
* The price table behind every dollar in the report, when it is old
|
|
735
744
|
* enough to matter. Unlike a skipped line, staleness does not name its
|
|
@@ -777,6 +786,13 @@ export interface CliMessages {
|
|
|
777
786
|
): string;
|
|
778
787
|
/** Said only when the p95 clears ten times the median — a real tail. */
|
|
779
788
|
sessionCostTail(ratio: string): string;
|
|
789
|
+
/**
|
|
790
|
+
* The whole log's conversations when the per-slice percentiles refused:
|
|
791
|
+
* count and single worst cost, stated because a maximum is a fact at any
|
|
792
|
+
* count while a percentile over four conversations would be the largest
|
|
793
|
+
* of four wearing a percentile's name.
|
|
794
|
+
*/
|
|
795
|
+
sessionSpendOnly(sessions: string, max: string): string;
|
|
780
796
|
/**
|
|
781
797
|
* Per-workload budgets from the config. A budgeted label with no calls in
|
|
782
798
|
* the log is "not measured", never a pass: a workload that did not appear
|
package/src/index.ts
CHANGED
|
@@ -180,6 +180,7 @@ const VALUE_FLAGS = new Set([
|
|
|
180
180
|
'max-growth-usd',
|
|
181
181
|
'max-cache-loss-usd',
|
|
182
182
|
'max-day-usd',
|
|
183
|
+
'max-session-usd',
|
|
183
184
|
'csv-out',
|
|
184
185
|
'csv-shape',
|
|
185
186
|
'what-if',
|
|
@@ -435,7 +436,7 @@ const COMMAND_FLAGS: Record<string, string[]> = {
|
|
|
435
436
|
],
|
|
436
437
|
check: ['max-tokens', 'level', 'exact-tokens', 'markdown-out', 'baseline'],
|
|
437
438
|
baseline: ['model', 'calls', 'output-tokens', 'cache-hit-rate', 'batch', 'exact-tokens', 'out', 'o'],
|
|
438
|
-
profile: ['json', 'pricing', 'pricing-live', 'against', 'what-if', 'markdown-out', 'csv-out', 'csv-shape', 'max-usd', 'max-growth-usd', 'max-cache-loss-usd', 'max-day-usd', 'label', 'since', 'until'],
|
|
439
|
+
profile: ['json', 'pricing', 'pricing-live', 'against', 'what-if', 'markdown-out', 'csv-out', 'csv-shape', 'max-usd', 'max-growth-usd', 'max-cache-loss-usd', 'max-day-usd', 'max-session-usd', 'label', 'since', 'until'],
|
|
439
440
|
route: ['prompt-file', 'cases', 'label', 'concurrency', 'json', 'yes', 'pricing', 'pricing-live'],
|
|
440
441
|
eval: ['cases', 'level', 'concurrency', 'export', 'out', 'o', 'model'],
|
|
441
442
|
prune: ['cases', 'concurrency', 'json', 'yes'],
|
|
@@ -2262,6 +2263,7 @@ async function commandProfile(args: Args, config: TrazumConfig, pricing: Pricing
|
|
|
2262
2263
|
typeof args.flags.get('max-growth-usd') === 'string' ||
|
|
2263
2264
|
typeof args.flags.get('max-cache-loss-usd') === 'string' ||
|
|
2264
2265
|
typeof args.flags.get('max-day-usd') === 'string' ||
|
|
2266
|
+
typeof args.flags.get('max-session-usd') === 'string' ||
|
|
2265
2267
|
config.spend !== undefined;
|
|
2266
2268
|
if (anyGate) {
|
|
2267
2269
|
const reasons: string[] = [];
|
|
@@ -2412,6 +2414,38 @@ async function commandProfile(args: Args, config: TrazumConfig, pricing: Pricing
|
|
|
2412
2414
|
}
|
|
2413
2415
|
}
|
|
2414
2416
|
}
|
|
2417
|
+
/**
|
|
2418
|
+
* The per-conversation gate — the unit an agent product actually blows
|
|
2419
|
+
* up in. A month's budget and a day's budget both pass while one
|
|
2420
|
+
* conversation loops its way through $400; the single most expensive
|
|
2421
|
+
* conversation is the number a per-conversation policy has to judge,
|
|
2422
|
+
* and the log already carries it.
|
|
2423
|
+
*
|
|
2424
|
+
* The refusals it inherits: a log with **no sessions** fails rather
|
|
2425
|
+
* than passes ("not measured" is not "under budget"), and a
|
|
2426
|
+
* conversation that started before this log is counted only for the
|
|
2427
|
+
* turns recorded here — so a pass is a floor, and the pass message says
|
|
2428
|
+
* so. The session key itself is never printed, here or anywhere.
|
|
2429
|
+
*/
|
|
2430
|
+
if (typeof args.flags.get('max-session-usd') === 'string' || config.spend?.maxSessionUsd !== undefined) {
|
|
2431
|
+
const maxSession =
|
|
2432
|
+
typeof args.flags.get('max-session-usd') === 'string'
|
|
2433
|
+
? numberFlag(args, 'max-session-usd', 0, t)
|
|
2434
|
+
: config.spend!.maxSessionUsd!;
|
|
2435
|
+
if (report.sessionSpend === null) {
|
|
2436
|
+
console.error(c.red(t.profile.maxSessionNoSessions()));
|
|
2437
|
+
process.exitCode = 1;
|
|
2438
|
+
} else if (report.sessionSpend.maxUsd > maxSession) {
|
|
2439
|
+
console.error(
|
|
2440
|
+
c.red(t.profile.maxSessionFailed(formatUsd(report.sessionSpend.maxUsd), formatUsd(maxSession), n(report.sessionSpend.sessions))),
|
|
2441
|
+
);
|
|
2442
|
+
process.exitCode = 1;
|
|
2443
|
+
} else {
|
|
2444
|
+
console.error(
|
|
2445
|
+
c.dim(t.profile.maxSessionOk(formatUsd(report.sessionSpend.maxUsd), formatUsd(maxSession), n(report.sessionSpend.sessions))),
|
|
2446
|
+
);
|
|
2447
|
+
}
|
|
2448
|
+
}
|
|
2415
2449
|
};
|
|
2416
2450
|
|
|
2417
2451
|
/**
|
|
@@ -3070,6 +3104,19 @@ async function commandProfile(args: Args, config: TrazumConfig, pricing: Pricing
|
|
|
3070
3104
|
);
|
|
3071
3105
|
}
|
|
3072
3106
|
}
|
|
3107
|
+
/**
|
|
3108
|
+
* The figure that survives a small log. `sessionCosts` refuses slices too
|
|
3109
|
+
* thin for a percentile, and rightly — but a log of four conversations
|
|
3110
|
+
* still has a most expensive one, and that maximum is a fact at any count.
|
|
3111
|
+
* It is also exactly the number `--max-session-usd` judges, so the report
|
|
3112
|
+
* states it rather than going silent where the gate would speak.
|
|
3113
|
+
*/
|
|
3114
|
+
if (report.sessionCosts.length === 0 && report.sessionSpend !== null) {
|
|
3115
|
+
console.log();
|
|
3116
|
+
console.log(
|
|
3117
|
+
` ${c.dim(wrap(t.profile.sessionSpendOnly(n(report.sessionSpend.sessions), formatUsd(report.sessionSpend.maxUsd)), 74, ' '))}`,
|
|
3118
|
+
);
|
|
3119
|
+
}
|
|
3073
3120
|
|
|
3074
3121
|
/**
|
|
3075
3122
|
* A total that assumed a cache-write rate is a floor, and says so.
|