@trazum/cli 1.50.4 → 1.50.5
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 +20 -0
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/es.d.ts.map +1 -1
- package/dist/i18n/es.js +20 -0
- package/dist/i18n/es.js.map +1 -1
- package/dist/i18n/types.d.ts +12 -0
- package/dist/i18n/types.d.ts.map +1 -1
- package/dist/index.js +49 -1
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/i18n/en.ts +24 -0
- package/src/i18n/es.ts +24 -0
- package/src/i18n/types.ts +7 -0
- package/src/index.ts +71 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trazum/cli",
|
|
3
|
-
"version": "1.50.
|
|
3
|
+
"version": "1.50.5",
|
|
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.5"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@types/node": "^26.2.0",
|
package/src/i18n/en.ts
CHANGED
|
@@ -1653,6 +1653,30 @@ ${bold('EXAMPLES')}
|
|
|
1653
1653
|
`${share} of the bill (${usd}) carried no outcome, and is in neither half of the rate above.`,
|
|
1654
1654
|
outcomeUndeclared: (values) =>
|
|
1655
1655
|
`Not declared in "outcomes.values": ${values}. Named rather than counted as failures \u2014 a typo in an exporter should look like a typo, not like a product regression.`,
|
|
1656
|
+
perOutcomeHeading: () => 'What an outcome costs',
|
|
1657
|
+
perOutcomeRow: (key, perCall, perOutcome, coverage) => `${key} ${perCall} ${perOutcome} ${coverage}`,
|
|
1658
|
+
perOutcomeColumns: {
|
|
1659
|
+
workload: 'workload',
|
|
1660
|
+
perCall: 'per call',
|
|
1661
|
+
perOutcome: 'per success',
|
|
1662
|
+
recorded: 'recorded',
|
|
1663
|
+
},
|
|
1664
|
+
perOutcomeWithheld: (why, successes, coverage) =>
|
|
1665
|
+
why === 'too-few-outcomes'
|
|
1666
|
+
? `${successes} so far`
|
|
1667
|
+
: why === 'too-little-coverage'
|
|
1668
|
+
? `${coverage} covered`
|
|
1669
|
+
: why === 'no-successes-recorded'
|
|
1670
|
+
? 'none succeeded'
|
|
1671
|
+
: why === 'nothing-recorded'
|
|
1672
|
+
? 'not recorded'
|
|
1673
|
+
: 'no vocabulary',
|
|
1674
|
+
perOutcomeNumerator: () =>
|
|
1675
|
+
'Per success divides the spend on calls that recorded an outcome, never the whole bill \u2014 dividing everything would charge your uninstrumented traffic to your measured successes and report a figure too high by exactly the uncovered share, silently, in the direction that gets a working feature killed. "recorded" is what share of each workload\u2019s spend the figure covers.',
|
|
1676
|
+
perOutcomeDisagreement: (key, callRank, outcomeRank) =>
|
|
1677
|
+
`${key} is #${callRank} by cost per call and #${outcomeRank} by cost per success.`,
|
|
1678
|
+
perOutcomeBothOrders: () =>
|
|
1679
|
+
'Cheapest per call and cheapest per success are different orders, and both are printed rather than one being picked. A workload can move up one while moving down the other, and somebody optimising on the first number would be moving the wrong one.',
|
|
1656
1680
|
outcomeColumns: { outcome: 'outcome', calls: 'calls', spend: 'spend' },
|
|
1657
1681
|
verdictSuccess: () => 'success',
|
|
1658
1682
|
verdictOther: () => '\u2014',
|
package/src/i18n/es.ts
CHANGED
|
@@ -1686,6 +1686,30 @@ ${bold('EJEMPLOS')}
|
|
|
1686
1686
|
`${share} de la factura (${usd}) no llev\u00f3 resultado, y no est\u00e1 en ninguna de las dos mitades de la tasa de arriba.`,
|
|
1687
1687
|
outcomeUndeclared: (values) =>
|
|
1688
1688
|
`No declarados en "outcomes.values": ${values}. Nombrados en vez de contados como fallos \u2014 una errata en un exportador debe parecer una errata, no una regresi\u00f3n del producto.`,
|
|
1689
|
+
perOutcomeHeading: () => 'Lo que cuesta un resultado',
|
|
1690
|
+
perOutcomeRow: (key, perCall, perOutcome, coverage) => `${key} ${perCall} ${perOutcome} ${coverage}`,
|
|
1691
|
+
perOutcomeColumns: {
|
|
1692
|
+
workload: 'carga',
|
|
1693
|
+
perCall: 'por llamada',
|
|
1694
|
+
perOutcome: 'por acierto',
|
|
1695
|
+
recorded: 'cubierto',
|
|
1696
|
+
},
|
|
1697
|
+
perOutcomeWithheld: (why, successes, coverage) =>
|
|
1698
|
+
why === 'too-few-outcomes'
|
|
1699
|
+
? `${successes} hasta ahora`
|
|
1700
|
+
: why === 'too-little-coverage'
|
|
1701
|
+
? `${coverage} cubierto`
|
|
1702
|
+
: why === 'no-successes-recorded'
|
|
1703
|
+
? 'ninguno acert\u00f3'
|
|
1704
|
+
: why === 'nothing-recorded'
|
|
1705
|
+
? 'sin registrar'
|
|
1706
|
+
: 'sin vocabulario',
|
|
1707
|
+
perOutcomeNumerator: () =>
|
|
1708
|
+
'Por acierto divide el gasto de las llamadas que registraron un resultado, nunca la factura entera \u2014 dividirlo todo cargar\u00eda tu tr\u00e1fico sin instrumentar a tus aciertos medidos y dar\u00eda una cifra alta por exactamente la parte no cubierta, en silencio, en la direcci\u00f3n que mata una funci\u00f3n que s\u00ed funciona. "cubierto" es qu\u00e9 parte del gasto de cada carga cubre la cifra.',
|
|
1709
|
+
perOutcomeDisagreement: (key, callRank, outcomeRank) =>
|
|
1710
|
+
`${key} es el #${callRank} por coste por llamada y el #${outcomeRank} por coste por acierto.`,
|
|
1711
|
+
perOutcomeBothOrders: () =>
|
|
1712
|
+
'M\u00e1s barato por llamada y m\u00e1s barato por acierto son \u00f3rdenes distintos, y se imprimen los dos en vez de elegir uno. Una carga puede subir en uno mientras baja en el otro, y quien optimice por el primer n\u00famero estar\u00eda moviendo el equivocado.',
|
|
1689
1713
|
outcomeColumns: { outcome: 'resultado', calls: 'llamadas', spend: 'gasto' },
|
|
1690
1714
|
verdictSuccess: () => '\u00e9xito',
|
|
1691
1715
|
verdictOther: () => '\u2014',
|
package/src/i18n/types.ts
CHANGED
|
@@ -1128,6 +1128,13 @@ export interface CliMessages {
|
|
|
1128
1128
|
outcomeNoRate(why: string): string;
|
|
1129
1129
|
outcomeUnrecorded(share: string, usd: string): string;
|
|
1130
1130
|
outcomeUndeclared(values: string): string;
|
|
1131
|
+
perOutcomeHeading(): string;
|
|
1132
|
+
perOutcomeRow(key: string, perCall: string, perOutcome: string, coverage: string): string;
|
|
1133
|
+
perOutcomeColumns: { workload: string; perCall: string; perOutcome: string; recorded: string };
|
|
1134
|
+
perOutcomeWithheld(why: string, successes: string, coverage: string): string;
|
|
1135
|
+
perOutcomeNumerator(): string;
|
|
1136
|
+
perOutcomeDisagreement(key: string, callRank: string, outcomeRank: string): string;
|
|
1137
|
+
perOutcomeBothOrders(): string;
|
|
1131
1138
|
outcomeColumns: { outcome: string; calls: string; spend: string };
|
|
1132
1139
|
verdictSuccess(): string;
|
|
1133
1140
|
verdictOther(): string;
|
package/src/index.ts
CHANGED
|
@@ -40,6 +40,7 @@ import {
|
|
|
40
40
|
budgetPositions,
|
|
41
41
|
conform,
|
|
42
42
|
outcomeReport,
|
|
43
|
+
rankPerOutcome,
|
|
43
44
|
FAILURE_POLICIES,
|
|
44
45
|
detectFromSource,
|
|
45
46
|
matchLocale,
|
|
@@ -6497,6 +6498,76 @@ async function commandProfile(
|
|
|
6497
6498
|
)}`,
|
|
6498
6499
|
);
|
|
6499
6500
|
}
|
|
6501
|
+
/**
|
|
6502
|
+
* What an outcome costs, per workload — the finding a total cannot make.
|
|
6503
|
+
*
|
|
6504
|
+
* Printed only when at least one workload has enough recorded outcomes
|
|
6505
|
+
* to say something, and every row that cannot state a figure says which
|
|
6506
|
+
* of the five reasons applies rather than showing a blank.
|
|
6507
|
+
*/
|
|
6508
|
+
const ranking = rankPerOutcome(
|
|
6509
|
+
report.outcomeTallyByLabel.map((entry) => ({
|
|
6510
|
+
key: entry.label,
|
|
6511
|
+
calls: entry.calls,
|
|
6512
|
+
totalUsd: entry.totalUsd,
|
|
6513
|
+
tally: entry.tally,
|
|
6514
|
+
})),
|
|
6515
|
+
config.outcomes ?? null,
|
|
6516
|
+
);
|
|
6517
|
+
if (ranking.byCall.length > 0) {
|
|
6518
|
+
console.log();
|
|
6519
|
+
console.log(c.bold(t.profile.perOutcomeHeading()));
|
|
6520
|
+
const pcol = t.profile.perOutcomeColumns;
|
|
6521
|
+
const prows = ranking.byCall.map((slice) => ({
|
|
6522
|
+
key: slice.key,
|
|
6523
|
+
perCall: formatUsd(slice.usdPerCall),
|
|
6524
|
+
perOutcome:
|
|
6525
|
+
slice.per.usdPerSuccess !== null
|
|
6526
|
+
? formatUsd(slice.per.usdPerSuccess)
|
|
6527
|
+
: t.profile.perOutcomeWithheld(
|
|
6528
|
+
slice.per.withheld ?? 'no-vocabulary',
|
|
6529
|
+
n(slice.per.successes),
|
|
6530
|
+
pct(slice.per.coverage),
|
|
6531
|
+
),
|
|
6532
|
+
recorded: pct(slice.per.coverage),
|
|
6533
|
+
}));
|
|
6534
|
+
const pw = {
|
|
6535
|
+
key: Math.max(...prows.map((r) => r.key.length), pcol.workload.length),
|
|
6536
|
+
perCall: Math.max(...prows.map((r) => r.perCall.length), pcol.perCall.length),
|
|
6537
|
+
perOutcome: Math.max(...prows.map((r) => r.perOutcome.length), pcol.perOutcome.length),
|
|
6538
|
+
recorded: Math.max(...prows.map((r) => r.recorded.length), pcol.recorded.length),
|
|
6539
|
+
};
|
|
6540
|
+
console.log(
|
|
6541
|
+
c.dim(
|
|
6542
|
+
` ${pcol.workload.padEnd(pw.key)} ${pcol.perCall.padStart(pw.perCall)} ` +
|
|
6543
|
+
`${pcol.perOutcome.padStart(pw.perOutcome)} ${pcol.recorded.padStart(pw.recorded)}`,
|
|
6544
|
+
),
|
|
6545
|
+
);
|
|
6546
|
+
for (const row of prows) {
|
|
6547
|
+
console.log(
|
|
6548
|
+
` ${row.key.padEnd(pw.key)} ${row.perCall.padStart(pw.perCall)} ` +
|
|
6549
|
+
`${row.perOutcome.padStart(pw.perOutcome)} ${c.dim(row.recorded.padStart(pw.recorded))}`,
|
|
6550
|
+
);
|
|
6551
|
+
}
|
|
6552
|
+
console.log();
|
|
6553
|
+
console.log(` ${c.dim(wrap(t.profile.perOutcomeNumerator(), 74, ' '))}`);
|
|
6554
|
+
|
|
6555
|
+
// The disagreement between the two orders, which is itself the finding.
|
|
6556
|
+
if (ranking.disagreements.length > 0) {
|
|
6557
|
+
console.log();
|
|
6558
|
+
console.log(` ${c.dim(wrap(t.profile.perOutcomeBothOrders(), 74, ' '))}`);
|
|
6559
|
+
for (const d of ranking.disagreements) {
|
|
6560
|
+
console.log(
|
|
6561
|
+
` ${c.yellow('→')} ${wrap(
|
|
6562
|
+
t.profile.perOutcomeDisagreement(d.slice.key, n(d.callRank + 1), n(d.outcomeRank + 1)),
|
|
6563
|
+
74,
|
|
6564
|
+
' ',
|
|
6565
|
+
)}`,
|
|
6566
|
+
);
|
|
6567
|
+
}
|
|
6568
|
+
}
|
|
6569
|
+
}
|
|
6570
|
+
|
|
6500
6571
|
if (outcomes.undeclared.length > 0) {
|
|
6501
6572
|
console.log(
|
|
6502
6573
|
` ${c.yellow('!')} ${wrap(
|