@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/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) => `${name} ${usd} · ${calls} llamadas · ${days} días`,
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
- periodRow(name: string, usd: string, calls: string, days: string): string;
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;