@trazum/cli 1.50.0 → 1.50.2
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 +26 -1
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/es.d.ts.map +1 -1
- package/dist/i18n/es.js +27 -1
- package/dist/i18n/es.js.map +1 -1
- package/dist/i18n/types.d.ts +18 -0
- package/dist/i18n/types.d.ts.map +1 -1
- package/dist/index.js +126 -2
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/i18n/en.ts +31 -1
- package/src/i18n/es.ts +32 -1
- package/src/i18n/types.ts +19 -0
- package/src/index.ts +134 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trazum/cli",
|
|
3
|
-
"version": "1.50.
|
|
3
|
+
"version": "1.50.2",
|
|
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.50.
|
|
40
|
+
"@trazum/core": "1.50.2"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@types/node": "^26.2.0",
|
package/src/i18n/en.ts
CHANGED
|
@@ -57,6 +57,8 @@ ${bold('USAGE')}
|
|
|
57
57
|
trazum conform <file|-> [--contract <name>]
|
|
58
58
|
trazum models
|
|
59
59
|
trazum rules
|
|
60
|
+
trazum feedback
|
|
61
|
+
trazum --version
|
|
60
62
|
|
|
61
63
|
${bold('OPTIONS FOR init')}
|
|
62
64
|
--dry-run Print the config it would write and write nothing.
|
|
@@ -88,6 +90,17 @@ ${bold('OPTIONS FOR conform')}
|
|
|
88
90
|
The second never gates. Choosing not to log sessions is a decision, not a
|
|
89
91
|
defect. See docs/format.md.
|
|
90
92
|
|
|
93
|
+
${bold('OPTIONS FOR feedback')}
|
|
94
|
+
(none)
|
|
95
|
+
|
|
96
|
+
Prints where to report a wrong optimisation, a bug, a question or a security
|
|
97
|
+
problem — and a blank issue with the version, runtime and platform already
|
|
98
|
+
filled in, printed in full first so nothing travels you have not read.
|
|
99
|
+
|
|
100
|
+
It sends nothing. Trazum has no telemetry: no ping, no install hook, no
|
|
101
|
+
anonymous counter, and a test fails the build if this command ever reaches
|
|
102
|
+
the network.
|
|
103
|
+
|
|
91
104
|
${bold('OPTIONS FOR prune')}
|
|
92
105
|
--cases <file> One input per line, or a JSON array. Required.
|
|
93
106
|
--yes Actually spend the calls. Without it the estimate is
|
|
@@ -907,7 +920,8 @@ ${bold('EXAMPLES')}
|
|
|
907
920
|
findingRoute: (model) => `The same work fits ${model}, which is cheaper per token.`,
|
|
908
921
|
findingBatch: () => 'The Batch API halves both halves of the bill, for work that can wait.',
|
|
909
922
|
findingTotal: (usd, days) => `Together: $${usd} over the same ${days} days.`,
|
|
910
|
-
findingNext: () =>
|
|
923
|
+
findingNext: () =>
|
|
924
|
+
'trazum plan <your log> ranks every action, not just this one. If a figure here looks wrong, trazum feedback says where to tell us — it sends nothing on its own.',
|
|
911
925
|
|
|
912
926
|
wouldOverwrite: (keys) => `This replaces keys you already set: ${keys}. Pass --yes to write anyway.`,
|
|
913
927
|
nothingToWrite: () => 'No config written: nothing above could be justified from what is here.',
|
|
@@ -919,6 +933,22 @@ ${bold('EXAMPLES')}
|
|
|
919
933
|
`${path} exists and could not be parsed, so nothing was written over it. Fix or move it first.`,
|
|
920
934
|
},
|
|
921
935
|
|
|
936
|
+
feedback: {
|
|
937
|
+
heading: () => 'Telling us something',
|
|
938
|
+
sendsNothing: () =>
|
|
939
|
+
'This command sends nothing, and neither does anything else here. Trazum has no telemetry: no ping, no install hook, no anonymous counter. A tool whose whole argument is that it reads your bill without uploading it cannot also be quietly reporting on you, and a test fails the build if this command ever reaches the network.',
|
|
940
|
+
whereHeading: () => 'Where',
|
|
941
|
+
wrongOptimisation: () =>
|
|
942
|
+
'A rule changed what a prompt asks for — the report that matters most, and the failure this product exists to avoid:',
|
|
943
|
+
bug: () => 'Anything else that is wrong:',
|
|
944
|
+
question: () => 'A question, or an idea you are not sure about:',
|
|
945
|
+
security: () => 'A security problem — privately, never a public issue:',
|
|
946
|
+
environmentHeading: () => 'What a maintainer will ask for',
|
|
947
|
+
environmentOnly: () =>
|
|
948
|
+
'That is the whole of it. Nothing about your work is here — not the config, not a prompt, not a label, not a figure. Those are what a good report needs and what only you can decide to share.',
|
|
949
|
+
linkHeading: () => 'A blank issue with the above already filled in',
|
|
950
|
+
},
|
|
951
|
+
|
|
922
952
|
conform: {
|
|
923
953
|
noTarget: () =>
|
|
924
954
|
'Pass a file to check — a usage log, or any document Trazum emits. Use "-" to read from stdin.',
|
package/src/i18n/es.ts
CHANGED
|
@@ -44,6 +44,8 @@ ${bold('USO')}
|
|
|
44
44
|
trazum conform <fichero|-> [--contract <nombre>]
|
|
45
45
|
trazum models
|
|
46
46
|
trazum rules
|
|
47
|
+
trazum feedback
|
|
48
|
+
trazum --version
|
|
47
49
|
|
|
48
50
|
${bold('OPCIONES DE init')}
|
|
49
51
|
--dry-run Imprime la configuración que escribiría y no escribe nada.
|
|
@@ -75,6 +77,18 @@ ${bold('OPCIONES DE conform')}
|
|
|
75
77
|
La segunda nunca hace fallar nada. Decidir no registrar sesiones es una
|
|
76
78
|
decisión, no un defecto. Ver docs/format.md.
|
|
77
79
|
|
|
80
|
+
${bold('OPCIONES DE feedback')}
|
|
81
|
+
(ninguna)
|
|
82
|
+
|
|
83
|
+
Imprime dónde reportar una optimización incorrecta, un fallo, una pregunta o
|
|
84
|
+
un problema de seguridad — y una incidencia en blanco con la versión, el
|
|
85
|
+
runtime y la plataforma ya puestos, mostrados enteros antes para que no viaje
|
|
86
|
+
nada que no hayas leído.
|
|
87
|
+
|
|
88
|
+
No envía nada. Trazum no tiene telemetría: ni ping, ni hook de instalación, ni
|
|
89
|
+
contador anónimo, y una prueba hace fallar la compilación si este comando
|
|
90
|
+
llega a tocar la red.
|
|
91
|
+
|
|
78
92
|
${bold('OPCIONES DE prune')}
|
|
79
93
|
--cases <fichero> Una entrada por línea, o un array JSON. Obligatorio.
|
|
80
94
|
--yes Gasta las llamadas de verdad. Sin él se imprime la
|
|
@@ -940,7 +954,8 @@ ${bold('EJEMPLOS')}
|
|
|
940
954
|
findingRoute: (model) => `El mismo trabajo cabe en ${model}, más barato por token.`,
|
|
941
955
|
findingBatch: () => 'La API de lotes reduce a la mitad ambas mitades de la factura, para trabajo que puede esperar.',
|
|
942
956
|
findingTotal: (usd, days) => `Juntas: $${usd} en esos mismos ${days} días.`,
|
|
943
|
-
findingNext: () =>
|
|
957
|
+
findingNext: () =>
|
|
958
|
+
'trazum plan <tu registro> ordena todas las acciones, no solo esta. Si alguna cifra de aquí parece mal, trazum feedback dice dónde contarlo — no envía nada por su cuenta.',
|
|
944
959
|
|
|
945
960
|
wouldOverwrite: (keys) => `Esto reemplaza claves que ya tenías: ${keys}. Pasa --yes para escribir igualmente.`,
|
|
946
961
|
nothingToWrite: () => 'No se escribió configuración: nada de lo anterior se pudo justificar con lo que hay aquí.',
|
|
@@ -952,6 +967,22 @@ ${bold('EJEMPLOS')}
|
|
|
952
967
|
`${path} existe y no se pudo interpretar, así que no se escribió nada encima. Arréglalo o muévelo primero.`,
|
|
953
968
|
},
|
|
954
969
|
|
|
970
|
+
feedback: {
|
|
971
|
+
heading: () => 'Contarnos algo',
|
|
972
|
+
sendsNothing: () =>
|
|
973
|
+
'Este comando no envía nada, y nada más aquí lo hace tampoco. Trazum no tiene telemetría: ni ping, ni hook de instalación, ni contador anónimo. Una herramienta cuyo argumento entero es que lee tu factura sin subirla no puede a la vez estar informando sobre ti en silencio, y una prueba hace fallar la compilación si este comando llega a tocar la red.',
|
|
974
|
+
whereHeading: () => 'Dónde',
|
|
975
|
+
wrongOptimisation: () =>
|
|
976
|
+
'Una regla cambió lo que pedía un prompt — el informe que más importa, y el fallo que este producto existe para evitar:',
|
|
977
|
+
bug: () => 'Cualquier otra cosa que esté mal:',
|
|
978
|
+
question: () => 'Una pregunta, o una idea de la que no estás seguro:',
|
|
979
|
+
security: () => 'Un problema de seguridad — en privado, nunca en una incidencia pública:',
|
|
980
|
+
environmentHeading: () => 'Lo que te va a preguntar quien mantiene esto',
|
|
981
|
+
environmentOnly: () =>
|
|
982
|
+
'Y eso es todo. Aquí no hay nada de tu trabajo — ni la configuración, ni un prompt, ni una etiqueta, ni una cifra. Eso es lo que necesita un buen informe y lo que solo tú puedes decidir compartir.',
|
|
983
|
+
linkHeading: () => 'Una incidencia en blanco con lo de arriba ya puesto',
|
|
984
|
+
},
|
|
985
|
+
|
|
955
986
|
conform: {
|
|
956
987
|
noTarget: () =>
|
|
957
988
|
'Pasa un archivo para comprobar — un registro de consumo, o cualquier documento que emita Trazum. Usa "-" para leer de la entrada estándar.',
|
package/src/i18n/types.ts
CHANGED
|
@@ -252,6 +252,25 @@ export interface CliMessages {
|
|
|
252
252
|
* Two halves, and the copy keeps them apart everywhere: problems gate, gaps
|
|
253
253
|
* do not. Choosing not to log sessions is a decision, not a defect.
|
|
254
254
|
*/
|
|
255
|
+
/**
|
|
256
|
+
* Where to say something, and the standing fact that this command sends
|
|
257
|
+
* nothing. The second half is not decoration: a tool that offers to help you
|
|
258
|
+
* file a report is exactly the shape of a tool that phones home, and the
|
|
259
|
+
* only way to be believed is to say so where somebody is looking.
|
|
260
|
+
*/
|
|
261
|
+
feedback: {
|
|
262
|
+
heading(): string;
|
|
263
|
+
sendsNothing(): string;
|
|
264
|
+
whereHeading(): string;
|
|
265
|
+
wrongOptimisation(): string;
|
|
266
|
+
bug(): string;
|
|
267
|
+
question(): string;
|
|
268
|
+
security(): string;
|
|
269
|
+
environmentHeading(): string;
|
|
270
|
+
environmentOnly(): string;
|
|
271
|
+
linkHeading(): string;
|
|
272
|
+
};
|
|
273
|
+
|
|
255
274
|
conform: {
|
|
256
275
|
noTarget(): string;
|
|
257
276
|
badContract(given: string, known: string): string;
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
2
3
|
import { open, readdir, readFile, stat, writeFile } from 'node:fs/promises';
|
|
3
|
-
import { join, resolve as resolvePath } from 'node:path';
|
|
4
|
+
import { dirname, join, resolve as resolvePath } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
4
6
|
import { gunzipSync } from 'node:zlib';
|
|
5
7
|
|
|
6
8
|
import {
|
|
@@ -528,7 +530,7 @@ function disabledRules(args: Args, config: TrazumConfig): RuleId[] | undefined {
|
|
|
528
530
|
* a threshold is set — `--max-growh 5` would have been ignored and the build
|
|
529
531
|
* gone green. Silence is the wrong answer for a typo.
|
|
530
532
|
*/
|
|
531
|
-
const GLOBAL_FLAGS = ['help', 'h', 'locale', 'json', 'config', 'pricing', 'pricing-live'];
|
|
533
|
+
const GLOBAL_FLAGS = ['help', 'h', 'version', 'v', 'locale', 'json', 'config', 'pricing', 'pricing-live'];
|
|
532
534
|
const COMMAND_FLAGS: Record<string, string[]> = {
|
|
533
535
|
optimize: [
|
|
534
536
|
'level', 'model', 'calls', 'output-tokens', 'cache-hit-rate', 'batch',
|
|
@@ -554,6 +556,7 @@ const COMMAND_FLAGS: Record<string, string[]> = {
|
|
|
554
556
|
rank: ['level', 'model', 'calls', 'output-tokens', 'batch', 'disable', 'prompt', 'markdown-out'],
|
|
555
557
|
init: ['dry-run', 'yes', 'json', 'pricing', 'pricing-live'],
|
|
556
558
|
conform: ['contract', 'json'],
|
|
559
|
+
feedback: [],
|
|
557
560
|
where: [],
|
|
558
561
|
rules: [],
|
|
559
562
|
blame: ['limit', 'model', 'calls', 'output-tokens', 'batch', 'prompt', 'markdown-out'],
|
|
@@ -1476,6 +1479,44 @@ const INIT_LOG_CANDIDATES = [
|
|
|
1476
1479
|
* nobody has vouched for.
|
|
1477
1480
|
*/
|
|
1478
1481
|
|
|
1482
|
+
/**
|
|
1483
|
+
* Where feedback goes. Compiled in, never configurable.
|
|
1484
|
+
*
|
|
1485
|
+
* A flag or a config key naming this host would let a fork — or anything that
|
|
1486
|
+
* had rewritten a config on disk — point somebody's bug report, and the
|
|
1487
|
+
* prefilled body with it, at a machine they did not choose. It is one string
|
|
1488
|
+
* and it stays one string.
|
|
1489
|
+
*/
|
|
1490
|
+
/**
|
|
1491
|
+
* Which Trazum this is.
|
|
1492
|
+
*
|
|
1493
|
+
* Read from the manifest beside the built entry point rather than baked in by
|
|
1494
|
+
* a generator, so it cannot drift from what npm installed — the one number a
|
|
1495
|
+
* bug report is useless without is the one that must not be a copy.
|
|
1496
|
+
*
|
|
1497
|
+
* `readFileSync` at module load, deliberately: every other read in this file
|
|
1498
|
+
* is async and inside a command, but a version has to be available to
|
|
1499
|
+
* `--version` before any command is chosen, and one small synchronous read at
|
|
1500
|
+
* startup is cheaper than making the whole entry point await.
|
|
1501
|
+
*
|
|
1502
|
+
* A failure falls back to `unknown` rather than throwing. A tool that will not
|
|
1503
|
+
* start because it cannot find its own manifest is worse than one that admits
|
|
1504
|
+
* it does not know — and `unknown` in a bug report is itself a useful fact
|
|
1505
|
+
* about how somebody installed it.
|
|
1506
|
+
*/
|
|
1507
|
+
const VERSION: string = (() => {
|
|
1508
|
+
try {
|
|
1509
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
1510
|
+
const manifest: unknown = JSON.parse(readFileSync(join(here, '..', 'package.json'), 'utf8'));
|
|
1511
|
+
const found = (manifest as { version?: unknown }).version;
|
|
1512
|
+
return typeof found === 'string' ? found : 'unknown';
|
|
1513
|
+
} catch {
|
|
1514
|
+
return 'unknown';
|
|
1515
|
+
}
|
|
1516
|
+
})();
|
|
1517
|
+
|
|
1518
|
+
const FEEDBACK_REPO = 'https://github.com/Davmunrey/Trazum';
|
|
1519
|
+
|
|
1479
1520
|
/** Problems listed before the rest are counted. A wall of them helps nobody. */
|
|
1480
1521
|
const MAX_CONFORM_PROBLEMS = 20;
|
|
1481
1522
|
|
|
@@ -1949,6 +1990,80 @@ async function commandConform(args: Args, t: CliMessages): Promise<void> {
|
|
|
1949
1990
|
console.log();
|
|
1950
1991
|
}
|
|
1951
1992
|
|
|
1993
|
+
/**
|
|
1994
|
+
* `trazum feedback` — where to say it, and what to say.
|
|
1995
|
+
*
|
|
1996
|
+
* **This command sends nothing.** Trazum has no telemetry: the CLI makes no
|
|
1997
|
+
* network call it was not explicitly asked to make, and there is no ping, no
|
|
1998
|
+
* install hook and no anonymous counter anywhere in it. That is not an
|
|
1999
|
+
* omission somebody has been meaning to fix — a tool whose entire argument is
|
|
2000
|
+
* that it reads your bill without uploading it cannot also be quietly
|
|
2001
|
+
* reporting on you, and the security suite fails the build if this command
|
|
2002
|
+
* ever reaches the network.
|
|
2003
|
+
*
|
|
2004
|
+
* So the loop is closed the only honest way: the person decides to send
|
|
2005
|
+
* something, and this makes that as cheap as possible. It prints the four
|
|
2006
|
+
* places worth writing to, and a **prefilled link** carrying the facts a
|
|
2007
|
+
* maintainer always has to ask for — version, runtime, platform — printed in
|
|
2008
|
+
* full first, so nothing travels that the sender has not read.
|
|
2009
|
+
*
|
|
2010
|
+
* Nothing about *their work* is in it. Not the config, not a prompt, not a
|
|
2011
|
+
* label, not a figure. Those are the things a bug report needs and the things
|
|
2012
|
+
* only the reporter can decide to share, and a command that helpfully attached
|
|
2013
|
+
* them would be the leak this product exists not to be.
|
|
2014
|
+
*/
|
|
2015
|
+
function commandFeedback(t: CliMessages): void {
|
|
2016
|
+
const version = VERSION;
|
|
2017
|
+
/**
|
|
2018
|
+
* Facts about the machine, and nothing about the person.
|
|
2019
|
+
*
|
|
2020
|
+
* `process.platform` and the Node version are what every "cannot reproduce"
|
|
2021
|
+
* thread eventually asks for. The locale is here because Trazum ships two
|
|
2022
|
+
* languages and a report reading wrong in one of them is a real bug class.
|
|
2023
|
+
*/
|
|
2024
|
+
const environment = [
|
|
2025
|
+
`Trazum ${version}`,
|
|
2026
|
+
`Node ${process.version}`,
|
|
2027
|
+
`${process.platform} ${process.arch}`,
|
|
2028
|
+
`locale ${t.locale}`,
|
|
2029
|
+
];
|
|
2030
|
+
|
|
2031
|
+
const body = [
|
|
2032
|
+
'<!-- What happened, and what you expected instead. -->',
|
|
2033
|
+
'',
|
|
2034
|
+
'',
|
|
2035
|
+
'---',
|
|
2036
|
+
...environment.map((line) => `- ${line}`),
|
|
2037
|
+
].join('\n');
|
|
2038
|
+
const url =
|
|
2039
|
+
`${FEEDBACK_REPO}/issues/new?body=${encodeURIComponent(body)}`;
|
|
2040
|
+
|
|
2041
|
+
console.log();
|
|
2042
|
+
console.log(c.bold(t.feedback.heading()));
|
|
2043
|
+
console.log(` ${c.dim(wrap(t.feedback.sendsNothing(), 74, ' '))}`);
|
|
2044
|
+
console.log();
|
|
2045
|
+
|
|
2046
|
+
console.log(c.bold(t.feedback.whereHeading()));
|
|
2047
|
+
console.log(` ${t.feedback.wrongOptimisation()}`);
|
|
2048
|
+
console.log(` ${c.dim(`${FEEDBACK_REPO}/issues/new?template=wrong_optimisation.yml`)}`);
|
|
2049
|
+
console.log(` ${t.feedback.bug()}`);
|
|
2050
|
+
console.log(` ${c.dim(`${FEEDBACK_REPO}/issues/new?template=bug_report.yml`)}`);
|
|
2051
|
+
console.log(` ${t.feedback.question()}`);
|
|
2052
|
+
console.log(` ${c.dim(`${FEEDBACK_REPO}/discussions`)}`);
|
|
2053
|
+
console.log(` ${t.feedback.security()}`);
|
|
2054
|
+
console.log(` ${c.dim(`${FEEDBACK_REPO}/security/advisories/new`)}`);
|
|
2055
|
+
console.log();
|
|
2056
|
+
|
|
2057
|
+
console.log(c.bold(t.feedback.environmentHeading()));
|
|
2058
|
+
for (const line of environment) console.log(` ${line}`);
|
|
2059
|
+
console.log(` ${c.dim(wrap(t.feedback.environmentOnly(), 74, ' '))}`);
|
|
2060
|
+
console.log();
|
|
2061
|
+
|
|
2062
|
+
console.log(c.bold(t.feedback.linkHeading()));
|
|
2063
|
+
console.log(` ${url}`);
|
|
2064
|
+
console.log();
|
|
2065
|
+
}
|
|
2066
|
+
|
|
1952
2067
|
function commandModels(t: CliMessages, pricing: PricingCatalogue): void {
|
|
1953
2068
|
const n = (value: number): string => value.toLocaleString(t.numberLocale);
|
|
1954
2069
|
const col = t.models.columns;
|
|
@@ -7835,6 +7950,20 @@ async function main(): Promise<void> {
|
|
|
7835
7950
|
return;
|
|
7836
7951
|
}
|
|
7837
7952
|
|
|
7953
|
+
/**
|
|
7954
|
+
* Before the help branch, and before the config loads.
|
|
7955
|
+
*
|
|
7956
|
+
* `trazum --version` on its own is how somebody answers "which one is
|
|
7957
|
+
* installed", and it has to work when the config is broken — that is
|
|
7958
|
+
* precisely the moment they are being asked. Placed above `!args.command`
|
|
7959
|
+
* for the same reason `--clear-suggestion-cache` is: with nothing else on
|
|
7960
|
+
* the line, the help branch would have swallowed it.
|
|
7961
|
+
*/
|
|
7962
|
+
if (boolFlag(args, 'version') || boolFlag(args, 'v')) {
|
|
7963
|
+
console.log(VERSION);
|
|
7964
|
+
return;
|
|
7965
|
+
}
|
|
7966
|
+
|
|
7838
7967
|
if (boolFlag(args, 'help') || boolFlag(args, 'h') || !args.command) {
|
|
7839
7968
|
console.log(
|
|
7840
7969
|
t.help(
|
|
@@ -7942,6 +8071,9 @@ async function main(): Promise<void> {
|
|
|
7942
8071
|
case 'models':
|
|
7943
8072
|
commandModels(t, pricing);
|
|
7944
8073
|
break;
|
|
8074
|
+
case 'feedback':
|
|
8075
|
+
commandFeedback(t);
|
|
8076
|
+
break;
|
|
7945
8077
|
case 'conform':
|
|
7946
8078
|
await commandConform(args, t);
|
|
7947
8079
|
break;
|