@trazum/cli 1.41.0 → 1.42.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 +1 -0
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +50 -1
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/es.d.ts.map +1 -1
- package/dist/i18n/es.js +51 -1
- package/dist/i18n/es.js.map +1 -1
- package/dist/i18n/types.d.ts +30 -1
- package/dist/i18n/types.d.ts.map +1 -1
- package/dist/index.js +211 -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/package.json +2 -2
- package/src/i18n/en.ts +64 -1
- package/src/i18n/es.ts +65 -1
- package/src/i18n/types.ts +31 -1
- package/src/index.ts +279 -33
- package/src/store-fs.ts +157 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where the store actually lives.
|
|
3
|
+
*
|
|
4
|
+
* The core decides what a record is and when two are the same; this decides
|
|
5
|
+
* where the bytes go. Split that way for the reason every module here is:
|
|
6
|
+
* `@trazum/core` stays browser-safe, and the CLI keeps its monopoly on I/O.
|
|
7
|
+
*
|
|
8
|
+
* **Append-only, one buffer per write.** A pull appends a single block and
|
|
9
|
+
* never rewrites what is already there. Two consequences worth stating: a
|
|
10
|
+
* crash during a write loses the tail of one block rather than a year of
|
|
11
|
+
* measurements, and two runs writing at once interleave whole blocks rather
|
|
12
|
+
* than half-lines. Compaction is a separate, explicit errand — `store
|
|
13
|
+
* --prune` — because collapsing a log is the one operation that destroys
|
|
14
|
+
* something, and it should never happen as a side effect of a pull.
|
|
15
|
+
*
|
|
16
|
+
* **A line that will not parse is kept, counted and skipped.** The store is a
|
|
17
|
+
* file a human may open, a backup may truncate and a merge may mangle. Losing
|
|
18
|
+
* the whole month because one line is broken would be the worst possible
|
|
19
|
+
* response; so would silently pretending the month is complete.
|
|
20
|
+
*/
|
|
21
|
+
import type { ResolvedStore, StoreRecord } from '@trazum/core';
|
|
22
|
+
/** The directory name, relative to wherever the caller roots the store. */
|
|
23
|
+
export declare const STORE_DIR = ".trazum/store";
|
|
24
|
+
export interface StoreReadResult {
|
|
25
|
+
resolved: ResolvedStore;
|
|
26
|
+
/** Lines that would not parse: counted and named by file, never dropped quietly. */
|
|
27
|
+
unreadable: {
|
|
28
|
+
file: string;
|
|
29
|
+
line: number;
|
|
30
|
+
}[];
|
|
31
|
+
/** Files read, so an empty store can be told from an unread one. */
|
|
32
|
+
files: string[];
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Reads every record in the store.
|
|
36
|
+
*
|
|
37
|
+
* Returns an empty result rather than throwing when the store does not exist:
|
|
38
|
+
* "you have not stored anything yet" is a state, not an error, and the caller
|
|
39
|
+
* says so in a sentence that names `trazum connect`.
|
|
40
|
+
*/
|
|
41
|
+
export declare function readStore(root: string): Promise<StoreReadResult>;
|
|
42
|
+
/**
|
|
43
|
+
* Appends records, grouped into one write per month file.
|
|
44
|
+
*
|
|
45
|
+
* Nothing already on disk is read, rewritten or resolved here: convergence
|
|
46
|
+
* happens when the store is *read*, which is what keeps a write cheap enough
|
|
47
|
+
* to run on a schedule and impossible to corrupt by racing.
|
|
48
|
+
*/
|
|
49
|
+
export declare function appendRecords(root: string, records: readonly StoreRecord[]): Promise<number>;
|
|
50
|
+
/**
|
|
51
|
+
* Rewrites the store with exactly the records given.
|
|
52
|
+
*
|
|
53
|
+
* The one operation that destroys something, so it is only ever reached from
|
|
54
|
+
* an explicit `--prune`. Each month file is written whole, and a month left
|
|
55
|
+
* with nothing is written empty rather than removed — a missing file and an
|
|
56
|
+
* empty one say different things to whoever looks next.
|
|
57
|
+
*/
|
|
58
|
+
export declare function rewriteStore(root: string, records: readonly StoreRecord[]): Promise<void>;
|
|
59
|
+
//# sourceMappingURL=store-fs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store-fs.d.ts","sourceRoot":"","sources":["../src/store-fs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAKH,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAE/D,2EAA2E;AAC3E,eAAO,MAAM,SAAS,kBAAkB,CAAC;AAOzC,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,aAAa,CAAC;IACxB,oFAAoF;IACpF,UAAU,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC7C,oEAAoE;IACpE,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED;;;;;;GAMG;AACH,wBAAsB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CA2CtE;AAED;;;;;;GAMG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,WAAW,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAiBlG;AAED;;;;;;;GAOG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,WAAW,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CA4B/F"}
|
package/dist/store-fs.js
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where the store actually lives.
|
|
3
|
+
*
|
|
4
|
+
* The core decides what a record is and when two are the same; this decides
|
|
5
|
+
* where the bytes go. Split that way for the reason every module here is:
|
|
6
|
+
* `@trazum/core` stays browser-safe, and the CLI keeps its monopoly on I/O.
|
|
7
|
+
*
|
|
8
|
+
* **Append-only, one buffer per write.** A pull appends a single block and
|
|
9
|
+
* never rewrites what is already there. Two consequences worth stating: a
|
|
10
|
+
* crash during a write loses the tail of one block rather than a year of
|
|
11
|
+
* measurements, and two runs writing at once interleave whole blocks rather
|
|
12
|
+
* than half-lines. Compaction is a separate, explicit errand — `store
|
|
13
|
+
* --prune` — because collapsing a log is the one operation that destroys
|
|
14
|
+
* something, and it should never happen as a side effect of a pull.
|
|
15
|
+
*
|
|
16
|
+
* **A line that will not parse is kept, counted and skipped.** The store is a
|
|
17
|
+
* file a human may open, a backup may truncate and a merge may mangle. Losing
|
|
18
|
+
* the whole month because one line is broken would be the worst possible
|
|
19
|
+
* response; so would silently pretending the month is complete.
|
|
20
|
+
*/
|
|
21
|
+
import { mkdir, readFile, readdir, writeFile } from 'node:fs/promises';
|
|
22
|
+
import { join } from 'node:path';
|
|
23
|
+
import { resolveStore } from '@trazum/core';
|
|
24
|
+
/** The directory name, relative to wherever the caller roots the store. */
|
|
25
|
+
export const STORE_DIR = '.trazum/store';
|
|
26
|
+
/** Records are filed by the UTC month their window starts in. */
|
|
27
|
+
function monthOf(record) {
|
|
28
|
+
return new Date(record.fromMs).toISOString().slice(0, 7);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Reads every record in the store.
|
|
32
|
+
*
|
|
33
|
+
* Returns an empty result rather than throwing when the store does not exist:
|
|
34
|
+
* "you have not stored anything yet" is a state, not an error, and the caller
|
|
35
|
+
* says so in a sentence that names `trazum connect`.
|
|
36
|
+
*/
|
|
37
|
+
export async function readStore(root) {
|
|
38
|
+
const dir = join(root, STORE_DIR);
|
|
39
|
+
const records = [];
|
|
40
|
+
const unreadable = [];
|
|
41
|
+
const files = [];
|
|
42
|
+
let providers;
|
|
43
|
+
try {
|
|
44
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
45
|
+
providers = entries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return { resolved: resolveStore([]), unreadable, files };
|
|
49
|
+
}
|
|
50
|
+
for (const provider of providers.sort()) {
|
|
51
|
+
const providerDir = join(dir, provider);
|
|
52
|
+
let months;
|
|
53
|
+
try {
|
|
54
|
+
months = (await readdir(providerDir)).filter((name) => name.endsWith('.jsonl')).sort();
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
for (const month of months) {
|
|
60
|
+
const path = join(providerDir, month);
|
|
61
|
+
files.push(join(STORE_DIR, provider, month));
|
|
62
|
+
const text = await readFile(path, 'utf8');
|
|
63
|
+
for (const [index, line] of text.split('\n').entries()) {
|
|
64
|
+
if (line.trim() === '')
|
|
65
|
+
continue;
|
|
66
|
+
try {
|
|
67
|
+
const parsed = JSON.parse(line);
|
|
68
|
+
if (typeof parsed?.provider === 'string' && typeof parsed?.fromMs === 'number') {
|
|
69
|
+
records.push(parsed);
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
unreadable.push({ file: join(STORE_DIR, provider, month), line: index + 1 });
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
unreadable.push({ file: join(STORE_DIR, provider, month), line: index + 1 });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return { resolved: resolveStore(records), unreadable, files };
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Appends records, grouped into one write per month file.
|
|
85
|
+
*
|
|
86
|
+
* Nothing already on disk is read, rewritten or resolved here: convergence
|
|
87
|
+
* happens when the store is *read*, which is what keeps a write cheap enough
|
|
88
|
+
* to run on a schedule and impossible to corrupt by racing.
|
|
89
|
+
*/
|
|
90
|
+
export async function appendRecords(root, records) {
|
|
91
|
+
if (records.length === 0)
|
|
92
|
+
return 0;
|
|
93
|
+
const byFile = new Map();
|
|
94
|
+
for (const record of records) {
|
|
95
|
+
const key = join(record.provider, `${monthOf(record)}.jsonl`);
|
|
96
|
+
const list = byFile.get(key) ?? [];
|
|
97
|
+
list.push(record);
|
|
98
|
+
byFile.set(key, list);
|
|
99
|
+
}
|
|
100
|
+
for (const [relative, list] of byFile) {
|
|
101
|
+
const path = join(root, STORE_DIR, relative);
|
|
102
|
+
await mkdir(join(path, '..'), { recursive: true });
|
|
103
|
+
const block = `${list.map((record) => JSON.stringify(record)).join('\n')}\n`;
|
|
104
|
+
await writeFile(path, block, { flag: 'a', mode: 0o600 });
|
|
105
|
+
}
|
|
106
|
+
return records.length;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Rewrites the store with exactly the records given.
|
|
110
|
+
*
|
|
111
|
+
* The one operation that destroys something, so it is only ever reached from
|
|
112
|
+
* an explicit `--prune`. Each month file is written whole, and a month left
|
|
113
|
+
* with nothing is written empty rather than removed — a missing file and an
|
|
114
|
+
* empty one say different things to whoever looks next.
|
|
115
|
+
*/
|
|
116
|
+
export async function rewriteStore(root, records) {
|
|
117
|
+
const dir = join(root, STORE_DIR);
|
|
118
|
+
const existing = new Set();
|
|
119
|
+
try {
|
|
120
|
+
for (const provider of await readdir(dir)) {
|
|
121
|
+
for (const month of await readdir(join(dir, provider)).catch(() => [])) {
|
|
122
|
+
if (month.endsWith('.jsonl'))
|
|
123
|
+
existing.add(join(provider, month));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
// Nothing stored yet: the writes below create what is needed.
|
|
129
|
+
}
|
|
130
|
+
const byFile = new Map();
|
|
131
|
+
for (const record of records) {
|
|
132
|
+
const key = join(record.provider, `${monthOf(record)}.jsonl`);
|
|
133
|
+
const list = byFile.get(key) ?? [];
|
|
134
|
+
list.push(record);
|
|
135
|
+
byFile.set(key, list);
|
|
136
|
+
}
|
|
137
|
+
for (const relative of new Set([...existing, ...byFile.keys()])) {
|
|
138
|
+
const list = byFile.get(relative) ?? [];
|
|
139
|
+
const path = join(dir, relative);
|
|
140
|
+
await mkdir(join(path, '..'), { recursive: true });
|
|
141
|
+
const block = list.length === 0 ? '' : `${list.map((r) => JSON.stringify(r)).join('\n')}\n`;
|
|
142
|
+
await writeFile(path, block, { mode: 0o600 });
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
//# sourceMappingURL=store-fs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store-fs.js","sourceRoot":"","sources":["../src/store-fs.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trazum/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.42.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.42.0"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@types/node": "^26.2.0",
|
package/src/i18n/en.ts
CHANGED
|
@@ -43,6 +43,7 @@ ${bold('USAGE')}
|
|
|
43
43
|
trazum verify <plan.json> --against <newer.jsonl|dir> [options]
|
|
44
44
|
trazum history <dir-of-stored-reports> [options]
|
|
45
45
|
trazum connect <anthropic|openai> [options]
|
|
46
|
+
trazum store [--prune] [options]
|
|
46
47
|
trazum diff <before> <after> [options]
|
|
47
48
|
trazum diff --all <dir> <dir> [options]
|
|
48
49
|
trazum rank <dir> [options]
|
|
@@ -320,6 +321,25 @@ ${bold('OPTIONS FOR plan')}
|
|
|
320
321
|
a plan that hides its assumptions is advice pretending to be arithmetic.
|
|
321
322
|
Projected savings and money already spent are separate totals throughout.
|
|
322
323
|
|
|
324
|
+
${bold('OPTIONS FOR store')}
|
|
325
|
+
--prune Drop measurements older than the retention
|
|
326
|
+
policy, and compact the append log to what the
|
|
327
|
+
store already resolves to. Says what went.
|
|
328
|
+
--keep <n>d Retention for this run, when the config has none.
|
|
329
|
+
--dry-run With --prune: say what would go, and delete
|
|
330
|
+
nothing.
|
|
331
|
+
--json The inventory as data.
|
|
332
|
+
|
|
333
|
+
Says what the local store holds: how many measurements, over what span, per
|
|
334
|
+
provider, and what a prune would take. The store keeps aggregates and
|
|
335
|
+
billing fields — never prompt text, never completion text, never a
|
|
336
|
+
credential — so it is a file a team can back up without a privacy review.
|
|
337
|
+
|
|
338
|
+
Pruning is the one operation here that destroys something, so it refuses to
|
|
339
|
+
run without a retention policy: set "store": {"keepDays": 90} in the config
|
|
340
|
+
or pass --keep. Deleting measurements on a policy nobody wrote down is not a
|
|
341
|
+
default anybody should get by accident.
|
|
342
|
+
|
|
323
343
|
${bold('OPTIONS FOR connect')}
|
|
324
344
|
--since <when> The window to pull. A UTC day, an ISO timestamp,
|
|
325
345
|
--until <when> a relative window (7d, 24h) or "now". Defaults to
|
|
@@ -330,6 +350,9 @@ ${bold('OPTIONS FOR connect')}
|
|
|
330
350
|
--payload <file> Price a usage payload you already have, instead of
|
|
331
351
|
pulling one. No credential, no network — the same
|
|
332
352
|
arithmetic on the same shape.
|
|
353
|
+
--store Keep what was pulled in the local store, so the
|
|
354
|
+
next run does not download it again and "trazum
|
|
355
|
+
history --store" has a series.
|
|
333
356
|
-o, --out <file> Save the priced report as JSON.
|
|
334
357
|
--markdown-out <file> Also write it as Markdown, for a CI job summary.
|
|
335
358
|
--json The report as data.
|
|
@@ -350,6 +373,12 @@ ${bold('OPTIONS FOR connect')}
|
|
|
350
373
|
that quietly describes less traffic than you asked about.
|
|
351
374
|
|
|
352
375
|
${bold('OPTIONS FOR history')}
|
|
376
|
+
--store Build the series from the local store instead of
|
|
377
|
+
a directory of stored reports. Bucketed sources
|
|
378
|
+
carry no label, so the label series is absent and
|
|
379
|
+
said to be — the model-share and cache-share
|
|
380
|
+
series are what a series exists for, and both
|
|
381
|
+
work.
|
|
353
382
|
--markdown-out <file> Also write the series as Markdown, for a CI job
|
|
354
383
|
summary or a pull request comment.
|
|
355
384
|
--json The history as data.
|
|
@@ -1477,6 +1506,37 @@ ${bold('EXAMPLES')}
|
|
|
1477
1506
|
`Plan written to ${path}, dated. Keep it: a prediction nobody wrote down is a prediction nobody can be held to.`,
|
|
1478
1507
|
},
|
|
1479
1508
|
|
|
1509
|
+
store: {
|
|
1510
|
+
appended: (count, dir) => `Kept ${count} measurements in ${dir}.`,
|
|
1511
|
+
empty: (dir) =>
|
|
1512
|
+
`The store at ${dir} is empty. Fill it with "trazum connect <provider> --store" — that is a state, not an error.`,
|
|
1513
|
+
heading: (records, usd, from, to) =>
|
|
1514
|
+
`The store: ${records} measurements · ${usd} · ${from} → ${to}`,
|
|
1515
|
+
providerRow: (provider, records, span, models) =>
|
|
1516
|
+
`${provider} ${records} measurements · ${span} · ${models} models`,
|
|
1517
|
+
holds: (files) =>
|
|
1518
|
+
`Held in ${files} files: token counts, billed dollars and the account's own workspace and key identifiers. Never prompt text, never completion text, never a credential — this is a file you can back up without a privacy review.`,
|
|
1519
|
+
possiblyDouble: (count) =>
|
|
1520
|
+
`${count} records could not be told apart from another — a window of no length, or a record naming no model. They are kept whole rather than merged, so a total built on them may count the same spend twice. Saying so beats a smaller number nobody can check.`,
|
|
1521
|
+
unknownVersion: (count) =>
|
|
1522
|
+
`${count} records come from a newer schema than this version knows, so they are kept and left out of the figures above rather than guessed at. Upgrade to read them.`,
|
|
1523
|
+
unreadable: (file, line) =>
|
|
1524
|
+
`${file} line ${line} would not parse, so it is not in the figures above. The rest of the file was read — one broken line must not lose a month.`,
|
|
1525
|
+
retention: (days) => `Retention: ${days} days, from "store.keepDays". Run "trazum store --prune" to apply it.`,
|
|
1526
|
+
noRetention: () =>
|
|
1527
|
+
'No retention policy is configured, so nothing is ever deleted on its own. Set "store": {"keepDays": 90} when you want one.',
|
|
1528
|
+
pruneNeedsPolicy: () =>
|
|
1529
|
+
'Pruning needs a retention policy: set "store": {"keepDays": 90} in trazum.config.json, or pass --keep 90d for this run. Deleting measurements on a policy nobody wrote down is not a default you should get by accident.',
|
|
1530
|
+
pruneDryRun: (count, days, span, usd) =>
|
|
1531
|
+
span === null
|
|
1532
|
+
? `Nothing is older than ${days} days, so a prune would delete nothing.`
|
|
1533
|
+
: `A prune would delete ${count} measurements older than ${days} days, covering ${span} and ${usd} of measured spend. Nothing was deleted — this was --dry-run.`,
|
|
1534
|
+
pruned: (count, days, span, usd, kept) =>
|
|
1535
|
+
span === null
|
|
1536
|
+
? `Nothing was older than ${days} days. ${kept} measurements kept, and the append log compacted.`
|
|
1537
|
+
: `Deleted ${count} measurements older than ${days} days, covering ${span} and ${usd} of measured spend. ${kept} kept, and the append log compacted to what the store already resolved to.`,
|
|
1538
|
+
},
|
|
1539
|
+
|
|
1480
1540
|
connect: {
|
|
1481
1541
|
noTarget: (providers) =>
|
|
1482
1542
|
`Name a provider to read your bill from: trazum connect anthropic. Available: ${providers}. The credential comes from the environment and is never stored — add --dry-run to see exactly what would be called and which variable it would be read from.`,
|
|
@@ -1514,7 +1574,8 @@ ${bold('EXAMPLES')}
|
|
|
1514
1574
|
needsThree: (count) =>
|
|
1515
1575
|
`A series needs at least three dated reports, and this directory has ${count}. Two reports is a comparison, and "trazum profile --against" already does that better.`,
|
|
1516
1576
|
heading: (periods, from, to) => `The long run: ${periods} periods, ${from} → ${to}`,
|
|
1517
|
-
periodRow: (name, usd, calls, days) =>
|
|
1577
|
+
periodRow: (name, usd, calls, days) =>
|
|
1578
|
+
calls === null ? `${name} ${usd} · ${days} days` : `${name} ${usd} · ${calls} calls · ${days} days`,
|
|
1518
1579
|
runLabel: (label, periods, sinceName, from, to) =>
|
|
1519
1580
|
`${label} has climbed for ${periods} consecutive periods since ${sinceName}: ${from} → ${to}. A shape, not a forecast.`,
|
|
1520
1581
|
runModel: (model, periods, sinceName, from, to) =>
|
|
@@ -1535,6 +1596,8 @@ ${bold('EXAMPLES')}
|
|
|
1535
1596
|
const span = first !== null && last !== null ? ` (${first} → ${last})` : '';
|
|
1536
1597
|
return `${what} has been planned ${appearances} times${span} and is still in the newest plan — a decision nobody is revisiting.`;
|
|
1537
1598
|
},
|
|
1599
|
+
storeNoLabels: () =>
|
|
1600
|
+
'This series comes from the store, and a usage API groups by model and workspace rather than by workload — so there is no label series here at all. Absent, not empty: nothing above says a workload did or did not move.',
|
|
1538
1601
|
undated: (name) => `${name} carries no span, so it is on no timeline above — named, never silently absorbed.`,
|
|
1539
1602
|
unrecognized: (name) => `${name} is neither a stored report nor a saved plan, so it is in no series above.`,
|
|
1540
1603
|
footer: () =>
|
package/src/i18n/es.ts
CHANGED
|
@@ -30,6 +30,7 @@ ${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]
|
|
33
34
|
trazum diff <antes> <después> [opciones]
|
|
34
35
|
trazum diff --all <dir> <dir> [opciones]
|
|
35
36
|
trazum rank <dir> [opciones]
|
|
@@ -327,6 +328,26 @@ ${bold('OPCIONES DE plan')}
|
|
|
327
328
|
consejo haciéndose pasar por aritmética. El ahorro proyectado y el dinero ya
|
|
328
329
|
gastado son totales separados en todas partes.
|
|
329
330
|
|
|
331
|
+
${bold('OPCIONES DE store')}
|
|
332
|
+
--prune Borra las mediciones más antiguas que la política
|
|
333
|
+
de retención y compacta el log a lo que el
|
|
334
|
+
almacén ya resuelve. Dice qué se fue.
|
|
335
|
+
--keep <n>d Retención para esta ejecución, si la config no
|
|
336
|
+
tiene ninguna.
|
|
337
|
+
--dry-run Con --prune: dice qué se iría y no borra nada.
|
|
338
|
+
--json El inventario como datos.
|
|
339
|
+
|
|
340
|
+
Dice qué guarda el almacén local: cuántas mediciones, sobre qué período, por
|
|
341
|
+
proveedor, y qué se llevaría una poda. El almacén guarda agregados y campos
|
|
342
|
+
de facturación — nunca texto de prompt, nunca texto de respuesta, nunca una
|
|
343
|
+
credencial — así que es un fichero que un equipo puede respaldar sin una
|
|
344
|
+
revisión de privacidad.
|
|
345
|
+
|
|
346
|
+
La poda es la única operación de aquí que destruye algo, así que se niega a
|
|
347
|
+
correr sin política de retención: pon "store": {"keepDays": 90} en la config
|
|
348
|
+
o pasa --keep. Borrar mediciones con una política que nadie escribió no es un
|
|
349
|
+
valor por defecto que nadie deba recibir por accidente.
|
|
350
|
+
|
|
330
351
|
${bold('OPCIONES DE connect')}
|
|
331
352
|
--since <cuándo> La ventana que se descarga. Un día UTC, una marca
|
|
332
353
|
--until <cuándo> ISO, una ventana relativa (7d, 24h) o "now". Por
|
|
@@ -337,6 +358,9 @@ ${bold('OPCIONES DE connect')}
|
|
|
337
358
|
--payload <fichero> Tasa un payload de uso que ya tengas, en vez de
|
|
338
359
|
descargar uno. Sin credencial y sin red — la misma
|
|
339
360
|
aritmética sobre la misma forma.
|
|
361
|
+
--store Guarda lo descargado en el almacén local, para que
|
|
362
|
+
la próxima vez no haya que bajarlo otra vez y
|
|
363
|
+
"trazum history --store" tenga una serie.
|
|
340
364
|
-o, --out <fichero> Guarda el informe tasado como JSON.
|
|
341
365
|
--markdown-out <fichero> Lo escribe además como Markdown, para CI.
|
|
342
366
|
--json El informe como datos.
|
|
@@ -358,6 +382,12 @@ ${bold('OPCIONES DE connect')}
|
|
|
358
382
|
que describe en silencio menos tráfico del que pediste.
|
|
359
383
|
|
|
360
384
|
${bold('OPCIONES DE history')}
|
|
385
|
+
--store Construye la serie desde el almacén local en vez
|
|
386
|
+
de un directorio de informes guardados. Las
|
|
387
|
+
fuentes agregadas no llevan label, así que la
|
|
388
|
+
serie por label está ausente y se dice — las de
|
|
389
|
+
cuota de modelo y de caché son para lo que existe
|
|
390
|
+
una serie, y funcionan enteras.
|
|
361
391
|
--markdown-out <fichero> Escribe además la serie como Markdown, para un
|
|
362
392
|
resumen de CI o un comentario de pull request.
|
|
363
393
|
--json La historia como datos.
|
|
@@ -1501,6 +1531,37 @@ ${bold('EJEMPLOS')}
|
|
|
1501
1531
|
`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
1532
|
},
|
|
1503
1533
|
|
|
1534
|
+
store: {
|
|
1535
|
+
appended: (count, dir) => `Guardadas ${count} mediciones en ${dir}.`,
|
|
1536
|
+
empty: (dir) =>
|
|
1537
|
+
`El almacén de ${dir} está vacío. Llénalo con "trazum connect <proveedor> --store" — eso es un estado, no un error.`,
|
|
1538
|
+
heading: (records, usd, from, to) =>
|
|
1539
|
+
`El almacén: ${records} mediciones · ${usd} · ${from} → ${to}`,
|
|
1540
|
+
providerRow: (provider, records, span, models) =>
|
|
1541
|
+
`${provider} ${records} mediciones · ${span} · ${models} modelos`,
|
|
1542
|
+
holds: (files) =>
|
|
1543
|
+
`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.`,
|
|
1544
|
+
possiblyDouble: (count) =>
|
|
1545
|
+
`${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.`,
|
|
1546
|
+
unknownVersion: (count) =>
|
|
1547
|
+
`${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.`,
|
|
1548
|
+
unreadable: (file, line) =>
|
|
1549
|
+
`${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.`,
|
|
1550
|
+
retention: (days) => `Retención: ${days} días, de "store.keepDays". Ejecuta "trazum store --prune" para aplicarla.`,
|
|
1551
|
+
noRetention: () =>
|
|
1552
|
+
'No hay política de retención configurada, así que nunca se borra nada por su cuenta. Pon "store": {"keepDays": 90} cuando quieras una.',
|
|
1553
|
+
pruneNeedsPolicy: () =>
|
|
1554
|
+
'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.',
|
|
1555
|
+
pruneDryRun: (count, days, span, usd) =>
|
|
1556
|
+
span === null
|
|
1557
|
+
? `Nada es más antiguo que ${days} días, así que una poda no borraría nada.`
|
|
1558
|
+
: `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.`,
|
|
1559
|
+
pruned: (count, days, span, usd, kept) =>
|
|
1560
|
+
span === null
|
|
1561
|
+
? `Nada era más antiguo que ${days} días. ${kept} mediciones conservadas, y el log compactado.`
|
|
1562
|
+
: `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.`,
|
|
1563
|
+
},
|
|
1564
|
+
|
|
1504
1565
|
connect: {
|
|
1505
1566
|
noTarget: (providers) =>
|
|
1506
1567
|
`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 +1599,8 @@ ${bold('EJEMPLOS')}
|
|
|
1538
1599
|
needsThree: (count) =>
|
|
1539
1600
|
`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
1601
|
heading: (periods, from, to) => `La larga distancia: ${periods} períodos, ${from} → ${to}`,
|
|
1541
|
-
periodRow: (name, usd, calls, days) =>
|
|
1602
|
+
periodRow: (name, usd, calls, days) =>
|
|
1603
|
+
calls === null ? `${name} ${usd} · ${days} días` : `${name} ${usd} · ${calls} llamadas · ${days} días`,
|
|
1542
1604
|
runLabel: (label, periods, sinceName, from, to) =>
|
|
1543
1605
|
`${label} lleva ${periods} períodos consecutivos subiendo desde ${sinceName}: ${from} → ${to}. Una forma, no un pronóstico.`,
|
|
1544
1606
|
runModel: (model, periods, sinceName, from, to) =>
|
|
@@ -1559,6 +1621,8 @@ ${bold('EJEMPLOS')}
|
|
|
1559
1621
|
const span = first !== null && last !== null ? ` (${first} → ${last})` : '';
|
|
1560
1622
|
return `${what} se ha planificado ${appearances} veces${span} y sigue en el plan más reciente — una decisión que nadie está revisando.`;
|
|
1561
1623
|
},
|
|
1624
|
+
storeNoLabels: () =>
|
|
1625
|
+
'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
1626
|
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
1627
|
unrecognized: (name) => `${name} no es ni un informe guardado ni un plan guardado, así que no está en ninguna serie de arriba.`,
|
|
1564
1628
|
footer: () =>
|
package/src/i18n/types.ts
CHANGED
|
@@ -1072,6 +1072,33 @@ export interface CliMessages {
|
|
|
1072
1072
|
wrote(path: string): string;
|
|
1073
1073
|
};
|
|
1074
1074
|
|
|
1075
|
+
/**
|
|
1076
|
+
* `trazum store` — the measurements kept on disk.
|
|
1077
|
+
*
|
|
1078
|
+
* The one part of this product that deletes something, so its copy is
|
|
1079
|
+
* written to make that visible: what is held, what a prune would take, and
|
|
1080
|
+
* a refusal when no retention policy exists to prune against.
|
|
1081
|
+
*/
|
|
1082
|
+
store: {
|
|
1083
|
+
/** Records appended by a pull that asked to keep them. */
|
|
1084
|
+
appended(count: string, dir: string): string;
|
|
1085
|
+
empty(dir: string): string;
|
|
1086
|
+
heading(records: string, usd: string, from: string, to: string): string;
|
|
1087
|
+
providerRow(provider: string, records: string, span: string, models: string): string;
|
|
1088
|
+
/** What the store holds — and what it never holds. */
|
|
1089
|
+
holds(files: string): string;
|
|
1090
|
+
/** Records the store could not tell apart, kept whole rather than merged. */
|
|
1091
|
+
possiblyDouble(count: string): string;
|
|
1092
|
+
unknownVersion(count: string): string;
|
|
1093
|
+
unreadable(file: string, line: string): string;
|
|
1094
|
+
retention(days: string): string;
|
|
1095
|
+
noRetention(): string;
|
|
1096
|
+
/** Deleting on a policy nobody wrote down is refused, never defaulted. */
|
|
1097
|
+
pruneNeedsPolicy(): string;
|
|
1098
|
+
pruneDryRun(count: string, days: string, span: string | null, usd: string): string;
|
|
1099
|
+
pruned(count: string, days: string, span: string | null, usd: string, kept: string): string;
|
|
1100
|
+
};
|
|
1101
|
+
|
|
1075
1102
|
/**
|
|
1076
1103
|
* `trazum connect` — the bill, read from the provider.
|
|
1077
1104
|
*
|
|
@@ -1115,12 +1142,15 @@ export interface CliMessages {
|
|
|
1115
1142
|
/** Under three dated reports there is no series — only the comparison --against already does. */
|
|
1116
1143
|
needsThree(count: string): string;
|
|
1117
1144
|
heading(periods: string, from: string, to: string): string;
|
|
1118
|
-
|
|
1145
|
+
/** `calls` is null on a source that serves no request count. */
|
|
1146
|
+
periodRow(name: string, usd: string, calls: string | null, days: string): string;
|
|
1119
1147
|
runLabel(label: string, periods: string, sinceName: string, from: string, to: string): string;
|
|
1120
1148
|
runModel(model: string, periods: string, sinceName: string, from: string, to: string): string;
|
|
1121
1149
|
runCache(periods: string, sinceName: string, from: string, to: string): string;
|
|
1122
1150
|
/** The same action in plan after plan: a decision nobody is executing. */
|
|
1123
1151
|
repeated(kind: PlanActionKind, label: string, model: string, appearances: string, first: string | null, last: string | null): string;
|
|
1152
|
+
/** A store source carries no labels, so the label series is absent, not empty. */
|
|
1153
|
+
storeNoLabels(): string;
|
|
1124
1154
|
undated(name: string): string;
|
|
1125
1155
|
unrecognized(name: string): string;
|
|
1126
1156
|
footer(): string;
|