@trazum/cli 1.32.0 → 1.33.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 +12 -0
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/es.d.ts.map +1 -1
- package/dist/i18n/es.js +12 -0
- package/dist/i18n/es.js.map +1 -1
- package/dist/i18n/types.d.ts +17 -0
- package/dist/i18n/types.d.ts.map +1 -1
- package/dist/index.js +48 -1
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/i18n/en.ts +22 -0
- package/src/i18n/es.ts +22 -0
- package/src/i18n/types.ts +17 -0
- package/src/index.ts +47 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trazum/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.33.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.33.0"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@types/node": "^26.2.0",
|
package/src/i18n/en.ts
CHANGED
|
@@ -1150,6 +1150,28 @@ ${bold('EXAMPLES')}
|
|
|
1150
1150
|
`--what-if does not know "${value}". Priced models: ${available}. Add it with --pricing if you have its rates.`,
|
|
1151
1151
|
badGzip: (file, detail) =>
|
|
1152
1152
|
`${file} is gzipped and would not decompress: ${detail}. Reading the rest and saying nothing would report a bill missing whatever that file held, so this stops instead. Check the file, or move it out of the directory.`,
|
|
1153
|
+
dryRunHeading: () => 'What this log can answer — no bill was produced',
|
|
1154
|
+
dryRunParsed: (parsed, skipped) =>
|
|
1155
|
+
`${parsed} records parse; ${skipped} lines could not be read. A dry run prices nothing: the point is what the gates you are about to wire would stand on.`,
|
|
1156
|
+
dryRunUnpriced: (models) =>
|
|
1157
|
+
`Models the price table does not know: ${models}. Their tokens parse; their dollars need a --pricing overlay.`,
|
|
1158
|
+
dryRunTotals: () => 'The bill itself: totals, per-model split, cache economics, the levers.',
|
|
1159
|
+
dryRunLabels: (share) =>
|
|
1160
|
+
`Per-workload findings and label budgets — "label" on ${share} of records.`,
|
|
1161
|
+
dryRunClock: (share) =>
|
|
1162
|
+
`The period, per-day and per-hour shape, --max-day-usd, --since/--until — a timestamp on ${share} of records.`,
|
|
1163
|
+
dryRunSessions: (share) =>
|
|
1164
|
+
`Conversation growth, per-conversation cost, --max-session-usd — a session on ${share} of records. Grouped by, never printed.`,
|
|
1165
|
+
dryRunStopReason: (share) =>
|
|
1166
|
+
`Truncated answers and their retry bill — a stop reason on ${share} of records.`,
|
|
1167
|
+
dryRunCacheTtl: (ttl, writes) =>
|
|
1168
|
+
`Settled cache verdicts — the "cache_creation" split on ${ttl} of ${writes} cache-writing records.`,
|
|
1169
|
+
dryRunNoCacheTraffic: () =>
|
|
1170
|
+
'Cache verdicts: nothing wrote to the cache in this log, so there is nothing to settle — not a missing field.',
|
|
1171
|
+
dryRunFooter: () =>
|
|
1172
|
+
'A ✗ is not a defect in the log; it is a finding this log cannot support yet. The README\'s recording recipe carries every field above.',
|
|
1173
|
+
dryRunNoGates: () =>
|
|
1174
|
+
'--dry-run produces no bill, so a gate beside it would exit green having judged nothing. Run the gates without --dry-run.',
|
|
1153
1175
|
coverageHeading: () => 'What this log cannot answer yet',
|
|
1154
1176
|
needsLabel: (seen) =>
|
|
1155
1177
|
`"label" on ${seen} records: without it every workload is one row, so no per-workload spend, no drill-down, and the levers describe a mixture rather than a decision.`,
|
package/src/i18n/es.ts
CHANGED
|
@@ -1165,6 +1165,28 @@ ${bold('EJEMPLOS')}
|
|
|
1165
1165
|
`--what-if no conoce "${value}". Modelos con precio: ${available}. Añádelo con --pricing si tienes sus tarifas.`,
|
|
1166
1166
|
badGzip: (file, detail) =>
|
|
1167
1167
|
`${file} está comprimido con gzip y no se ha podido descomprimir: ${detail}. Leer el resto y no decir nada daría una factura a la que le falta lo que hubiera en ese fichero, así que se para aquí. Revisa el fichero, o sácalo del directorio.`,
|
|
1168
|
+
dryRunHeading: () => 'Qué puede responder este registro — no se ha producido factura',
|
|
1169
|
+
dryRunParsed: (parsed, skipped) =>
|
|
1170
|
+
`${parsed} registros se leen; ${skipped} líneas no se pudieron leer. Un dry run no valora nada: el punto es sobre qué se apoyarían los gates que vas a cablear.`,
|
|
1171
|
+
dryRunUnpriced: (models) =>
|
|
1172
|
+
`Modelos que la tabla de precios no conoce: ${models}. Sus tokens se leen; sus dólares necesitan un overlay --pricing.`,
|
|
1173
|
+
dryRunTotals: () => 'La factura en sí: totales, desglose por modelo, economía de caché, las palancas.',
|
|
1174
|
+
dryRunLabels: (share) =>
|
|
1175
|
+
`Hallazgos por carga y presupuestos por etiqueta — "label" en el ${share} de los registros.`,
|
|
1176
|
+
dryRunClock: (share) =>
|
|
1177
|
+
`El periodo, la forma por día y por hora, --max-day-usd, --since/--until — marca de tiempo en el ${share} de los registros.`,
|
|
1178
|
+
dryRunSessions: (share) =>
|
|
1179
|
+
`Crecimiento de conversación, coste por conversación, --max-session-usd — sesión en el ${share} de los registros. Se agrupa por ella, nunca se imprime.`,
|
|
1180
|
+
dryRunStopReason: (share) =>
|
|
1181
|
+
`Respuestas truncadas y su factura de reintentos — razón de parada en el ${share} de los registros.`,
|
|
1182
|
+
dryRunCacheTtl: (ttl, writes) =>
|
|
1183
|
+
`Veredictos de caché resueltos — el desglose "cache_creation" en ${ttl} de ${writes} registros que escriben caché.`,
|
|
1184
|
+
dryRunNoCacheTraffic: () =>
|
|
1185
|
+
'Veredictos de caché: nada escribió en la caché en este registro, así que no hay nada que resolver — no es un campo ausente.',
|
|
1186
|
+
dryRunFooter: () =>
|
|
1187
|
+
'Una ✗ no es un defecto del registro; es un hallazgo que este registro aún no puede sostener. La receta de registro del README lleva todos los campos de arriba.',
|
|
1188
|
+
dryRunNoGates: () =>
|
|
1189
|
+
'--dry-run no produce factura, así que un gate a su lado saldría en verde sin haber juzgado nada. Corre los gates sin --dry-run.',
|
|
1168
1190
|
coverageHeading: () => 'Lo que este registro todavía no puede responder',
|
|
1169
1191
|
needsLabel: (seen) =>
|
|
1170
1192
|
`"label" en ${seen} registros: sin él todas las cargas son una sola fila, así que no hay gasto por carga, ni zoom, y las palancas describen una mezcla en vez de una decisión.`,
|
package/src/i18n/types.ts
CHANGED
|
@@ -916,6 +916,23 @@ export interface CliMessages {
|
|
|
916
916
|
* a skip: a total quietly missing a day is the failure this refuses.
|
|
917
917
|
*/
|
|
918
918
|
badGzip(file: string, detail: string): string;
|
|
919
|
+
/**
|
|
920
|
+
* `--dry-run`: readiness per capability, no bill. The question somebody
|
|
921
|
+
* has before wiring CI, answered without making them read a report to
|
|
922
|
+
* discover a missing field.
|
|
923
|
+
*/
|
|
924
|
+
dryRunHeading(): string;
|
|
925
|
+
dryRunParsed(parsed: string, skipped: string): string;
|
|
926
|
+
dryRunUnpriced(models: string): string;
|
|
927
|
+
dryRunTotals(): string;
|
|
928
|
+
dryRunLabels(share: string): string;
|
|
929
|
+
dryRunClock(share: string): string;
|
|
930
|
+
dryRunSessions(share: string): string;
|
|
931
|
+
dryRunStopReason(share: string): string;
|
|
932
|
+
dryRunCacheTtl(ttl: string, writes: string): string;
|
|
933
|
+
dryRunNoCacheTraffic(): string;
|
|
934
|
+
dryRunFooter(): string;
|
|
935
|
+
dryRunNoGates(): string;
|
|
919
936
|
coverageHeading(): string;
|
|
920
937
|
needsLabel(seen: string): string;
|
|
921
938
|
needsSession(seen: string): string;
|
package/src/index.ts
CHANGED
|
@@ -440,7 +440,7 @@ const COMMAND_FLAGS: Record<string, string[]> = {
|
|
|
440
440
|
],
|
|
441
441
|
check: ['max-tokens', 'level', 'exact-tokens', 'markdown-out', 'baseline'],
|
|
442
442
|
baseline: ['model', 'calls', 'output-tokens', 'cache-hit-rate', 'batch', 'exact-tokens', 'out', 'o'],
|
|
443
|
-
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'],
|
|
443
|
+
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', 'dry-run'],
|
|
444
444
|
route: ['prompt-file', 'cases', 'label', 'concurrency', 'json', 'yes', 'pricing', 'pricing-live'],
|
|
445
445
|
eval: ['cases', 'level', 'concurrency', 'export', 'out', 'o', 'model'],
|
|
446
446
|
prune: ['cases', 'concurrency', 'json', 'yes'],
|
|
@@ -2146,6 +2146,52 @@ async function commandProfile(args: Args, config: TrazumConfig, pricing: Pricing
|
|
|
2146
2146
|
const n = (value: number): string => value.toLocaleString(t.numberLocale);
|
|
2147
2147
|
const pct = (share: number): string => `${(share * 100).toFixed(1)}%`;
|
|
2148
2148
|
|
|
2149
|
+
/**
|
|
2150
|
+
* `--dry-run`: what this log could and could not answer, and no bill.
|
|
2151
|
+
*
|
|
2152
|
+
* The question somebody has *before* wiring Trazum into CI is not "what did
|
|
2153
|
+
* we spend" but "will this log support the gates I want" — and answering it
|
|
2154
|
+
* with a full report makes them read a bill to find out a field is missing.
|
|
2155
|
+
* This path states readiness per capability and produces no dollar figure
|
|
2156
|
+
* at all, so nothing here can be mistaken for spend. It also refuses to
|
|
2157
|
+
* coexist with the gates: a gate over a report that was never produced
|
|
2158
|
+
* would exit green having judged nothing.
|
|
2159
|
+
*/
|
|
2160
|
+
if (boolFlag(args, 'dry-run')) {
|
|
2161
|
+
const gateFlags = ['max-usd', 'max-growth-usd', 'max-cache-loss-usd', 'max-day-usd', 'max-session-usd'];
|
|
2162
|
+
if (gateFlags.some((flag) => typeof args.flags.get(flag) === 'string')) {
|
|
2163
|
+
throw new Error(t.profile.dryRunNoGates());
|
|
2164
|
+
}
|
|
2165
|
+
const cov = report.fieldCoverage;
|
|
2166
|
+
const parsed = cov.parsed;
|
|
2167
|
+
console.log(c.bold(t.profile.dryRunHeading()));
|
|
2168
|
+
console.log(` ${wrap(t.profile.dryRunParsed(n(parsed), n(report.skippedLines.length)), 74, ' ')}`);
|
|
2169
|
+
if (report.unpricedModels.length > 0) {
|
|
2170
|
+
console.log(` ${c.yellow('!')} ${wrap(t.profile.dryRunUnpriced(report.unpricedModels.join(', ')), 74, ' ')}`);
|
|
2171
|
+
}
|
|
2172
|
+
console.log();
|
|
2173
|
+
const can = (ok: boolean, line: string): void => {
|
|
2174
|
+
console.log(` ${ok ? c.green('✓') : c.yellow('✗')} ${wrap(line, 74, ' ')}`);
|
|
2175
|
+
};
|
|
2176
|
+
const share = (count: number): string => (parsed > 0 ? pct(count / parsed) : '0%');
|
|
2177
|
+
can(parsed > 0, t.profile.dryRunTotals());
|
|
2178
|
+
can(cov.label > 0, t.profile.dryRunLabels(share(cov.label)));
|
|
2179
|
+
can(cov.ts > 0, t.profile.dryRunClock(share(cov.ts)));
|
|
2180
|
+
can(cov.session > 0, t.profile.dryRunSessions(share(cov.session)));
|
|
2181
|
+
can(cov.stopReason > 0, t.profile.dryRunStopReason(share(cov.stopReason)));
|
|
2182
|
+
// "No cache traffic" is not a missing field: the split can only exist on
|
|
2183
|
+
// records that wrote, and a log that never wrote has nothing to record.
|
|
2184
|
+
if (cov.cacheWrites > 0) {
|
|
2185
|
+
can(cov.cacheTtl > 0, t.profile.dryRunCacheTtl(n(cov.cacheTtl), n(cov.cacheWrites)));
|
|
2186
|
+
} else {
|
|
2187
|
+
console.log(` ${c.dim('·')} ${wrap(t.profile.dryRunNoCacheTraffic(), 74, ' ')}`);
|
|
2188
|
+
}
|
|
2189
|
+
console.log();
|
|
2190
|
+
console.log(` ${c.dim(wrap(t.profile.dryRunFooter(), 74, ' '))}`);
|
|
2191
|
+
if (parsed === 0) process.exitCode = 1;
|
|
2192
|
+
return;
|
|
2193
|
+
}
|
|
2194
|
+
|
|
2149
2195
|
/**
|
|
2150
2196
|
* The previous log, loaded before the output paths split so the growth gate
|
|
2151
2197
|
* exists under `--json` too — a CI step reads the JSON and trusts the exit
|