@trazum/cli 1.41.0 → 1.43.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/README.md +2 -0
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +113 -1
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/es.d.ts.map +1 -1
- package/dist/i18n/es.js +116 -1
- package/dist/i18n/es.js.map +1 -1
- package/dist/i18n/types.d.ts +56 -2
- package/dist/i18n/types.d.ts.map +1 -1
- package/dist/index.js +372 -34
- package/dist/index.js.map +1 -1
- package/dist/store-fs.d.ts +59 -0
- package/dist/store-fs.d.ts.map +1 -0
- package/dist/store-fs.js +145 -0
- package/dist/store-fs.js.map +1 -0
- package/dist/watch-run.d.ts +69 -0
- package/dist/watch-run.d.ts.map +1 -0
- package/dist/watch-run.js +79 -0
- package/dist/watch-run.js.map +1 -0
- package/package.json +2 -2
- package/src/i18n/en.ts +137 -1
- package/src/i18n/es.ts +140 -1
- package/src/i18n/types.ts +58 -2
- package/src/index.ts +501 -33
- package/src/store-fs.ts +157 -0
- package/src/watch-run.ts +126 -0
package/src/i18n/es.ts
CHANGED
|
@@ -30,6 +30,8 @@ ${bold('USO')}
|
|
|
30
30
|
trazum verify <plan.json> --against <nuevo.jsonl|dir> [opciones]
|
|
31
31
|
trazum history <dir-de-informes-guardados> [opciones]
|
|
32
32
|
trazum connect <anthropic|openai> [opciones]
|
|
33
|
+
trazum store [--prune] [opciones]
|
|
34
|
+
trazum watch [--once | --interval 15m] [opciones]
|
|
33
35
|
trazum diff <antes> <después> [opciones]
|
|
34
36
|
trazum diff --all <dir> <dir> [opciones]
|
|
35
37
|
trazum rank <dir> [opciones]
|
|
@@ -327,6 +329,57 @@ ${bold('OPCIONES DE plan')}
|
|
|
327
329
|
consejo haciéndose pasar por aritmética. El ahorro proyectado y el dinero ya
|
|
328
330
|
gastado son totales separados en todas partes.
|
|
329
331
|
|
|
332
|
+
${bold('OPCIONES DE watch')}
|
|
333
|
+
--once Una vuelta: medir, guardar, evaluar, emitir,
|
|
334
|
+
recordar. Lo que ejecuta una entrada de cron. Es
|
|
335
|
+
lo que se hace por defecto.
|
|
336
|
+
--interval <n>m|h Se queda en primer plano y repite. Mínimo cinco
|
|
337
|
+
minutos: las APIs de uso están limitadas por
|
|
338
|
+
tasa, y un bucle apretado es una forma de que te
|
|
339
|
+
estrangulen tu propia clave.
|
|
340
|
+
--webhook <url> Envía los cruces por POST. Solo https, salvo en
|
|
341
|
+
loopback; una URL con credenciales se rechaza,
|
|
342
|
+
porque las URLs acaban en logs e historiales.
|
|
343
|
+
--payload <fichero> Evalúa un payload de uso que ya tengas, en vez
|
|
344
|
+
del almacén.
|
|
345
|
+
--json La vuelta como datos: cruces, abstenciones, hueco.
|
|
346
|
+
|
|
347
|
+
Evalúa los gates de gasto de tu configuración — maxUsd, maxDayUsd,
|
|
348
|
+
maxCacheLossUsd — contra lo que se ha medido, y te lo dice la tarde en que
|
|
349
|
+
pasa en vez de tres semanas después. Sale con 1 cuando algo cruzó, así que
|
|
350
|
+
cron te lo manda y CI falla.
|
|
351
|
+
|
|
352
|
+
Una alerta salta por un cruce medido y nunca por una proyección: "has
|
|
353
|
+
gastado $412 de un presupuesto de $400" es un hecho y "vas a excederte" es un
|
|
354
|
+
pronóstico, que esta herramienta no hace a ninguna escala de ventana. Un día
|
|
355
|
+
que todavía se está midiendo se reporta como aún no juzgable en vez de
|
|
356
|
+
aprobarse — pero un día que ya se pasó del presupuesto salta a cualquier
|
|
357
|
+
hora, porque no se pasa menos a medianoche.
|
|
358
|
+
|
|
359
|
+
Un reinicio no vuelve a avisar de un cruce ya reportado, y nombra el tramo
|
|
360
|
+
que no estuvo vigilando, porque un vigilante que se reanuda en silencio
|
|
361
|
+
insinúa una cobertura que no tuvo.
|
|
362
|
+
|
|
363
|
+
${bold('OPCIONES DE store')}
|
|
364
|
+
--prune Borra las mediciones más antiguas que la política
|
|
365
|
+
de retención y compacta el log a lo que el
|
|
366
|
+
almacén ya resuelve. Dice qué se fue.
|
|
367
|
+
--keep <n>d Retención para esta ejecución, si la config no
|
|
368
|
+
tiene ninguna.
|
|
369
|
+
--dry-run Con --prune: dice qué se iría y no borra nada.
|
|
370
|
+
--json El inventario como datos.
|
|
371
|
+
|
|
372
|
+
Dice qué guarda el almacén local: cuántas mediciones, sobre qué período, por
|
|
373
|
+
proveedor, y qué se llevaría una poda. El almacén guarda agregados y campos
|
|
374
|
+
de facturación — nunca texto de prompt, nunca texto de respuesta, nunca una
|
|
375
|
+
credencial — así que es un fichero que un equipo puede respaldar sin una
|
|
376
|
+
revisión de privacidad.
|
|
377
|
+
|
|
378
|
+
La poda es la única operación de aquí que destruye algo, así que se niega a
|
|
379
|
+
correr sin política de retención: pon "store": {"keepDays": 90} en la config
|
|
380
|
+
o pasa --keep. Borrar mediciones con una política que nadie escribió no es un
|
|
381
|
+
valor por defecto que nadie deba recibir por accidente.
|
|
382
|
+
|
|
330
383
|
${bold('OPCIONES DE connect')}
|
|
331
384
|
--since <cuándo> La ventana que se descarga. Un día UTC, una marca
|
|
332
385
|
--until <cuándo> ISO, una ventana relativa (7d, 24h) o "now". Por
|
|
@@ -337,6 +390,9 @@ ${bold('OPCIONES DE connect')}
|
|
|
337
390
|
--payload <fichero> Tasa un payload de uso que ya tengas, en vez de
|
|
338
391
|
descargar uno. Sin credencial y sin red — la misma
|
|
339
392
|
aritmética sobre la misma forma.
|
|
393
|
+
--store Guarda lo descargado en el almacén local, para que
|
|
394
|
+
la próxima vez no haya que bajarlo otra vez y
|
|
395
|
+
"trazum history --store" tenga una serie.
|
|
340
396
|
-o, --out <fichero> Guarda el informe tasado como JSON.
|
|
341
397
|
--markdown-out <fichero> Lo escribe además como Markdown, para CI.
|
|
342
398
|
--json El informe como datos.
|
|
@@ -358,6 +414,12 @@ ${bold('OPCIONES DE connect')}
|
|
|
358
414
|
que describe en silencio menos tráfico del que pediste.
|
|
359
415
|
|
|
360
416
|
${bold('OPCIONES DE history')}
|
|
417
|
+
--store Construye la serie desde el almacén local en vez
|
|
418
|
+
de un directorio de informes guardados. Las
|
|
419
|
+
fuentes agregadas no llevan label, así que la
|
|
420
|
+
serie por label está ausente y se dice — las de
|
|
421
|
+
cuota de modelo y de caché son para lo que existe
|
|
422
|
+
una serie, y funcionan enteras.
|
|
361
423
|
--markdown-out <fichero> Escribe además la serie como Markdown, para un
|
|
362
424
|
resumen de CI o un comentario de pull request.
|
|
363
425
|
--json La historia como datos.
|
|
@@ -1501,6 +1563,80 @@ ${bold('EJEMPLOS')}
|
|
|
1501
1563
|
`Plan escrito en ${path}, con fecha. Guárdalo: una predicción que nadie apuntó es una predicción que no se le puede exigir a nadie.`,
|
|
1502
1564
|
},
|
|
1503
1565
|
|
|
1566
|
+
watch: {
|
|
1567
|
+
noThresholds: () =>
|
|
1568
|
+
'Vigilar necesita algo que vigilar. Define spend.maxUsd, spend.maxDayUsd o spend.maxCacheLossUsd en trazum.config.json — un vigilante sin umbral es una luz verde que nadie se ha ganado.',
|
|
1569
|
+
nothingToWatch: (dir) =>
|
|
1570
|
+
`Todavía no se ha medido nada: el almacén de ${dir} está vacío. Llénalo primero con "trazum connect <proveedor> --store" — vigilar la nada reportaría que todo está bien.`,
|
|
1571
|
+
intervalTooTight: () =>
|
|
1572
|
+
'--interval tiene que ser de al menos 5m. Las APIs de uso están limitadas por tasa, y un bucle apretado es una forma de que una herramienta que existe para ahorrarte dinero acabe estrangulando tu propia clave.',
|
|
1573
|
+
badWebhook: (reason) =>
|
|
1574
|
+
reason === 'credentials-in-url'
|
|
1575
|
+
? 'Esa URL de webhook lleva credenciales. Las URLs acaban en logs, historiales de shell y mensajes de error, así que se rechaza — pon el secreto en una cabecera que tu receptor compruebe, o en el propio receptor.'
|
|
1576
|
+
: reason === 'insecure-scheme'
|
|
1577
|
+
? 'Un webhook tiene que ser https, salvo en loopback. Una alerta lleva tus cifras de gasto, y mandarlas en claro por una red es una fuga que no pediste.'
|
|
1578
|
+
: 'Ese webhook no es una URL que esta herramienta pueda parsear.',
|
|
1579
|
+
crossed: (gate, measured, limit, day) => {
|
|
1580
|
+
const what =
|
|
1581
|
+
gate === 'maxUsd'
|
|
1582
|
+
? 'El gasto total'
|
|
1583
|
+
: gate === 'maxDayUsd'
|
|
1584
|
+
? `El gasto del ${day}`
|
|
1585
|
+
: 'El dinero perdido con la caché';
|
|
1586
|
+
return `CRUZADO — ${what} es ${measured} contra un límite de ${limit}. Medido, no proyectado.`;
|
|
1587
|
+
},
|
|
1588
|
+
stillOver: (gate, measured, limit, day) => {
|
|
1589
|
+
const what =
|
|
1590
|
+
gate === 'maxUsd'
|
|
1591
|
+
? 'El gasto total'
|
|
1592
|
+
: gate === 'maxDayUsd'
|
|
1593
|
+
? `El gasto del ${day}`
|
|
1594
|
+
: 'El dinero perdido con la caché';
|
|
1595
|
+
return `SIGUE POR ENCIMA — ${what} es ${measured} contra un límite de ${limit}, y ya se avisó. Callado no es limpio.`;
|
|
1596
|
+
},
|
|
1597
|
+
notJudgeable: (gate, reason, covered) =>
|
|
1598
|
+
reason === 'window-too-short'
|
|
1599
|
+
? `${gate} todavía no se puede juzgar: este período está medido al ${covered ?? 'parcialmente'}, y un umbral sobre parte de un día es un umbral sobre otra cosa. No es un aprobado — se juzgará cuando el día esté completo.`
|
|
1600
|
+
: `${gate} no se puede juzgar en esta fuente, que no sirve aquello sobre lo que está escrito el gate. No es un aprobado: un gate saltado en silencio se lee exactamente igual que un gate que lleva tiempo pasando.`,
|
|
1601
|
+
gap: (from, to) =>
|
|
1602
|
+
`Nada estuvo vigilando entre ${from} y ${to}. Lo que cruzara en ese tramo no se vio, y esta línea existe para que un vigilante reanudado no insinúe una cobertura que no tuvo.`,
|
|
1603
|
+
allWithin: (gates) => `Dentro de todos los umbrales: ${gates} gates evaluados contra gasto medido.`,
|
|
1604
|
+
webhookFailed: (status) =>
|
|
1605
|
+
`El webhook no se entregó (${status}). El cruce sigue en el código de salida y en la salida de arriba — que un receptor esté caído no puede ser el fallo más silencioso de la sala.`,
|
|
1606
|
+
watching: (minutes) => `Vigilando cada ${minutes} minutos. Ctrl-C lo para.`,
|
|
1607
|
+
},
|
|
1608
|
+
|
|
1609
|
+
store: {
|
|
1610
|
+
appended: (count, dir) => `Guardadas ${count} mediciones en ${dir}.`,
|
|
1611
|
+
empty: (dir) =>
|
|
1612
|
+
`El almacén de ${dir} está vacío. Llénalo con "trazum connect <proveedor> --store" — eso es un estado, no un error.`,
|
|
1613
|
+
heading: (records, usd, from, to) =>
|
|
1614
|
+
`El almacén: ${records} mediciones · ${usd} · ${from} → ${to}`,
|
|
1615
|
+
providerRow: (provider, records, span, models) =>
|
|
1616
|
+
`${provider} ${records} mediciones · ${span} · ${models} modelos`,
|
|
1617
|
+
holds: (files) =>
|
|
1618
|
+
`Guardado en ${files} ficheros: recuentos de tokens, dólares facturados y los identificadores de workspace y clave de la propia cuenta. Nunca texto de prompt, nunca texto de respuesta, nunca una credencial — esto es un fichero que puedes respaldar sin una revisión de privacidad.`,
|
|
1619
|
+
possiblyDouble: (count) =>
|
|
1620
|
+
`${count} registros no se pudieron distinguir de otro — una ventana de longitud cero, o un registro que no nombra modelo. Se guardan enteros en vez de fundirse, así que un total construido sobre ellos puede contar el mismo gasto dos veces. Decirlo es mejor que un número más pequeño que nadie puede comprobar.`,
|
|
1621
|
+
unknownVersion: (count) =>
|
|
1622
|
+
`${count} registros vienen de un esquema más nuevo del que esta versión conoce, así que se conservan y quedan fuera de las cifras de arriba en vez de adivinarse. Actualiza para leerlos.`,
|
|
1623
|
+
unreadable: (file, line) =>
|
|
1624
|
+
`${file} línea ${line} no se pudo parsear, así que no está en las cifras de arriba. El resto del fichero sí se leyó — una línea rota no puede costar un mes.`,
|
|
1625
|
+
retention: (days) => `Retención: ${days} días, de "store.keepDays". Ejecuta "trazum store --prune" para aplicarla.`,
|
|
1626
|
+
noRetention: () =>
|
|
1627
|
+
'No hay política de retención configurada, así que nunca se borra nada por su cuenta. Pon "store": {"keepDays": 90} cuando quieras una.',
|
|
1628
|
+
pruneNeedsPolicy: () =>
|
|
1629
|
+
'Podar necesita una política de retención: pon "store": {"keepDays": 90} en trazum.config.json, o pasa --keep 90d para esta ejecución. Borrar mediciones con una política que nadie escribió no es un valor por defecto que debas recibir por accidente.',
|
|
1630
|
+
pruneDryRun: (count, days, span, usd) =>
|
|
1631
|
+
span === null
|
|
1632
|
+
? `Nada es más antiguo que ${days} días, así que una poda no borraría nada.`
|
|
1633
|
+
: `Una poda borraría ${count} mediciones de más de ${days} días, que cubren ${span} y ${usd} de gasto medido. No se borró nada — esto era --dry-run.`,
|
|
1634
|
+
pruned: (count, days, span, usd, kept) =>
|
|
1635
|
+
span === null
|
|
1636
|
+
? `Nada era más antiguo que ${days} días. ${kept} mediciones conservadas, y el log compactado.`
|
|
1637
|
+
: `Borradas ${count} mediciones de más de ${days} días, que cubren ${span} y ${usd} de gasto medido. ${kept} conservadas, y el log compactado a lo que el almacén ya resolvía.`,
|
|
1638
|
+
},
|
|
1639
|
+
|
|
1504
1640
|
connect: {
|
|
1505
1641
|
noTarget: (providers) =>
|
|
1506
1642
|
`Nombra un proveedor del que leer tu factura: trazum connect anthropic. Disponibles: ${providers}. La credencial sale del entorno y nunca se guarda — añade --dry-run para ver exactamente qué se llamaría y de qué variable saldría.`,
|
|
@@ -1538,7 +1674,8 @@ ${bold('EJEMPLOS')}
|
|
|
1538
1674
|
needsThree: (count) =>
|
|
1539
1675
|
`Una serie necesita al menos tres informes con fecha, y este directorio tiene ${count}. Dos informes son una comparación, y "trazum profile --against" ya la hace mejor.`,
|
|
1540
1676
|
heading: (periods, from, to) => `La larga distancia: ${periods} períodos, ${from} → ${to}`,
|
|
1541
|
-
periodRow: (name, usd, calls, days) =>
|
|
1677
|
+
periodRow: (name, usd, calls, days) =>
|
|
1678
|
+
calls === null ? `${name} ${usd} · ${days} días` : `${name} ${usd} · ${calls} llamadas · ${days} días`,
|
|
1542
1679
|
runLabel: (label, periods, sinceName, from, to) =>
|
|
1543
1680
|
`${label} lleva ${periods} períodos consecutivos subiendo desde ${sinceName}: ${from} → ${to}. Una forma, no un pronóstico.`,
|
|
1544
1681
|
runModel: (model, periods, sinceName, from, to) =>
|
|
@@ -1559,6 +1696,8 @@ ${bold('EJEMPLOS')}
|
|
|
1559
1696
|
const span = first !== null && last !== null ? ` (${first} → ${last})` : '';
|
|
1560
1697
|
return `${what} se ha planificado ${appearances} veces${span} y sigue en el plan más reciente — una decisión que nadie está revisando.`;
|
|
1561
1698
|
},
|
|
1699
|
+
storeNoLabels: () =>
|
|
1700
|
+
'Esta serie viene del almacén, y una API de uso agrupa por modelo y workspace, no por carga de trabajo — así que aquí no hay serie por label en absoluto. Ausente, no vacía: nada de lo de arriba dice que una carga se moviera o dejara de moverse.',
|
|
1562
1701
|
undated: (name) => `${name} no lleva período, así que no está en ninguna línea de tiempo de arriba — nombrado, nunca absorbido en silencio.`,
|
|
1563
1702
|
unrecognized: (name) => `${name} no es ni un informe guardado ni un plan guardado, así que no está en ninguna serie de arriba.`,
|
|
1564
1703
|
footer: () =>
|
package/src/i18n/types.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CannotTellReason, EvalVerdict, Locale, PlanActionKind, PlanAssumption, RuleLevel, VerifyOutcome } from '@trazum/core';
|
|
1
|
+
import type { CannotTellReason, EvalVerdict, Locale, NotJudgeable, PlanActionKind, PlanAssumption, RuleLevel, VerifyOutcome, WatchGate } from '@trazum/core';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* The CLI's own message catalogue.
|
|
@@ -1072,6 +1072,59 @@ export interface CliMessages {
|
|
|
1072
1072
|
wrote(path: string): string;
|
|
1073
1073
|
};
|
|
1074
1074
|
|
|
1075
|
+
/**
|
|
1076
|
+
* `trazum watch` — the gates, evaluated as the money moves.
|
|
1077
|
+
*
|
|
1078
|
+
* The copy here carries the rule that makes an alert at 3am trustworthy: a
|
|
1079
|
+
* crossing is measured, never projected, and a period too short to judge is
|
|
1080
|
+
* said to be rather than passed.
|
|
1081
|
+
*/
|
|
1082
|
+
watch: {
|
|
1083
|
+
/** Watching with no threshold configured is a green light nobody earned. */
|
|
1084
|
+
noThresholds(): string;
|
|
1085
|
+
nothingToWatch(dir: string): string;
|
|
1086
|
+
intervalTooTight(): string;
|
|
1087
|
+
badWebhook(reason: 'invalid-url' | 'credentials-in-url' | 'insecure-scheme'): string;
|
|
1088
|
+
/** A measured crossing. `day` names the afternoon when the gate is a day gate. */
|
|
1089
|
+
crossed(gate: WatchGate, measured: string, limit: string, day: string | null): string;
|
|
1090
|
+
/** Still over the limit, and already reported — quiet, but not clean. */
|
|
1091
|
+
stillOver(gate: WatchGate, measured: string, limit: string, day: string | null): string;
|
|
1092
|
+
/** Neither a pass nor a failure: this cannot be judged yet, and why. */
|
|
1093
|
+
notJudgeable(gate: WatchGate, reason: NotJudgeable, covered: string | null): string;
|
|
1094
|
+
/** The stretch nobody was watching, named rather than implied away. */
|
|
1095
|
+
gap(from: string, to: string): string;
|
|
1096
|
+
allWithin(gates: string): string;
|
|
1097
|
+
webhookFailed(status: string): string;
|
|
1098
|
+
watching(minutes: string): string;
|
|
1099
|
+
};
|
|
1100
|
+
|
|
1101
|
+
/**
|
|
1102
|
+
* `trazum store` — the measurements kept on disk.
|
|
1103
|
+
*
|
|
1104
|
+
* The one part of this product that deletes something, so its copy is
|
|
1105
|
+
* written to make that visible: what is held, what a prune would take, and
|
|
1106
|
+
* a refusal when no retention policy exists to prune against.
|
|
1107
|
+
*/
|
|
1108
|
+
store: {
|
|
1109
|
+
/** Records appended by a pull that asked to keep them. */
|
|
1110
|
+
appended(count: string, dir: string): string;
|
|
1111
|
+
empty(dir: string): string;
|
|
1112
|
+
heading(records: string, usd: string, from: string, to: string): string;
|
|
1113
|
+
providerRow(provider: string, records: string, span: string, models: string): string;
|
|
1114
|
+
/** What the store holds — and what it never holds. */
|
|
1115
|
+
holds(files: string): string;
|
|
1116
|
+
/** Records the store could not tell apart, kept whole rather than merged. */
|
|
1117
|
+
possiblyDouble(count: string): string;
|
|
1118
|
+
unknownVersion(count: string): string;
|
|
1119
|
+
unreadable(file: string, line: string): string;
|
|
1120
|
+
retention(days: string): string;
|
|
1121
|
+
noRetention(): string;
|
|
1122
|
+
/** Deleting on a policy nobody wrote down is refused, never defaulted. */
|
|
1123
|
+
pruneNeedsPolicy(): string;
|
|
1124
|
+
pruneDryRun(count: string, days: string, span: string | null, usd: string): string;
|
|
1125
|
+
pruned(count: string, days: string, span: string | null, usd: string, kept: string): string;
|
|
1126
|
+
};
|
|
1127
|
+
|
|
1075
1128
|
/**
|
|
1076
1129
|
* `trazum connect` — the bill, read from the provider.
|
|
1077
1130
|
*
|
|
@@ -1115,12 +1168,15 @@ export interface CliMessages {
|
|
|
1115
1168
|
/** Under three dated reports there is no series — only the comparison --against already does. */
|
|
1116
1169
|
needsThree(count: string): string;
|
|
1117
1170
|
heading(periods: string, from: string, to: string): string;
|
|
1118
|
-
|
|
1171
|
+
/** `calls` is null on a source that serves no request count. */
|
|
1172
|
+
periodRow(name: string, usd: string, calls: string | null, days: string): string;
|
|
1119
1173
|
runLabel(label: string, periods: string, sinceName: string, from: string, to: string): string;
|
|
1120
1174
|
runModel(model: string, periods: string, sinceName: string, from: string, to: string): string;
|
|
1121
1175
|
runCache(periods: string, sinceName: string, from: string, to: string): string;
|
|
1122
1176
|
/** The same action in plan after plan: a decision nobody is executing. */
|
|
1123
1177
|
repeated(kind: PlanActionKind, label: string, model: string, appearances: string, first: string | null, last: string | null): string;
|
|
1178
|
+
/** A store source carries no labels, so the label series is absent, not empty. */
|
|
1179
|
+
storeNoLabels(): string;
|
|
1124
1180
|
undated(name: string): string;
|
|
1125
1181
|
unrecognized(name: string): string;
|
|
1126
1182
|
footer(): string;
|