@trazum/cli 1.10.0 → 1.26.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/types.ts CHANGED
@@ -93,7 +93,49 @@ export interface CliMessages {
93
93
  tokensOnlyAsked(): string;
94
94
  tokensSaved(tokens: string): string;
95
95
  windowUse(before: string, after: string, model: string, window: string): string;
96
+ /**
97
+ * The prompt is a rounding error against the window.
98
+ *
99
+ * Distinct from `windowUnmoved`: both shares rounding to the same string can
100
+ * mean "this prompt is nothing against a million tokens" or "this prompt is
101
+ * 10% of the window and one token did not move it". The first version used
102
+ * one message for both and told a reader holding 10% of the window that they
103
+ * were under a tenth of a percent.
104
+ */
105
+ windowNegligible(tokens: string, model: string, window: string): string;
106
+ /** A material share the change did not move. */
107
+ windowUnmoved(share: string, model: string, window: string): string;
108
+ /**
109
+ * Where the money actually is, said at the front door.
110
+ *
111
+ * `optimize` is the first command anybody runs and it reports the smallest
112
+ * line item on the bill — measured, about 1%. Everything that moves 40% to
113
+ * 80% lives in `profile`, which needs a usage log, which a new reader does
114
+ * not have and has no reason to go looking for. A tool that learned the
115
+ * truth and only tells it in the command you reach last has not told you.
116
+ */
117
+ beyondThisPrompt(): string;
118
+ /**
119
+ * The same pointer where the host bills by subscription.
120
+ *
121
+ * Deliberately does not list the metered levers. `tokens-only.test.js` refuses
122
+ * any mention of the Batch API on a flat plan — "use a cheaper model" is not
123
+ * weaker advice there, it is not advice — and a closing note that recited them
124
+ * would put money advice in front of the reader that guard exists to protect,
125
+ * however carefully it was hedged.
126
+ */
127
+ beyondThisPromptTokensOnly(): string;
96
128
  tokensOnlyCost(): string;
129
+ /**
130
+ * They named a scenario and the host withheld the money anyway.
131
+ *
132
+ * `--cost` stays the one way to ask, because `--calls` is a scenario
133
+ * parameter with a default that several commands take purely to size a
134
+ * finding. But answering somebody who typed `--calls 50000` with "pass
135
+ * --cost if this prompt is bound for a metered API" tells them to do the
136
+ * thing they plainly just tried to do.
137
+ */
138
+ tokensOnlyAskedFor(): string;
97
139
  diffTooLarge(lines: number, max: number): string;
98
140
  reorderHeading(): string;
99
141
  reorderMoved(blocks: number, tokens: string): string;
@@ -354,8 +396,17 @@ export interface CliMessages {
354
396
  profile: {
355
397
  noTarget(): string;
356
398
  heading(): string;
357
- /** Totals line: calls and the bill they came to. */
399
+ /**
400
+ * Totals line: calls and the bill they came to.
401
+ *
402
+ * `calls` arrives already agreeing with its noun — "1 call", "2,400 calls".
403
+ * The count and the word have to be built together or one language gets it
404
+ * right and the next does not, and `1 calls` was reachable on an ordinary
405
+ * one-call log.
406
+ */
358
407
  spent(calls: string, total: string): string;
408
+ /** A count of calls with its noun, agreeing. */
409
+ calls(count: number): string;
359
410
  /** One row of the split, with its share of the bill. */
360
411
  part(name: string, usd: string, pct: string, tokens: string): string;
361
412
  partInput(): string;
@@ -368,6 +419,44 @@ export interface CliMessages {
368
419
  unlabelled(): string;
369
420
  cacheHit(pct: string): string;
370
421
  cacheNever(): string;
422
+ /**
423
+ * Caching added to the bill instead of taking money off it.
424
+ *
425
+ * The finding no other command in this repository can produce, and the only
426
+ * one that can contradict Trazum's own advice: on Anthropic a cache write is
427
+ * billed at 1.25x plain input, or 2x at the 1-hour TTL, so a prefix that
428
+ * changes faster than it is reused costs a premium and returns nothing.
429
+ */
430
+ cacheLost(usd: string, writes: string, reads: string): string;
431
+ cachePaidOff(usd: string): string;
432
+ cacheNoDifference(): string;
433
+ /** Which labels the loss is in, when the total already reports one. */
434
+ cacheLostBy(labels: string): string;
435
+ /**
436
+ * A label bleeding underneath a total that reports no loss.
437
+ *
438
+ * Deliberately says nothing about what the total did. It runs under both
439
+ * `paid-off` and `no-difference`, and the version that opened "Caching pays
440
+ * off overall" printed that claim directly beneath a line saying caching had
441
+ * come out level.
442
+ */
443
+ cacheLostHidden(usd: string, labels: string): string;
444
+ /** Losing labels past the ones named, counted rather than dropped. */
445
+ andMoreLabels(count: number): string;
446
+ /**
447
+ * The log cannot say whether caching paid for itself.
448
+ *
449
+ * An unrecorded cache-write TTL is priced at the cheaper of the two rates, and
450
+ * that moves the verdict rather than only the total: between 0.28 and 1.11
451
+ * reads per write the same calls pay for themselves at 1.25x and lose money at
452
+ * 2x. Reporting the assumed half as an answer takes the flattering side of a
453
+ * question the data does not settle.
454
+ */
455
+ cacheTtlUnsettled(calls: number, asRecorded: string, atLongTtl: string): string;
456
+ /** Same verdict either way, but the figure beside it is a bound. */
457
+ cacheTtlBound(calls: number, atLongTtl: string): string;
458
+ /** Labels that lose money only if their unstated TTL was the long one. */
459
+ cacheTtlUnsettledLabels(labels: string): string;
371
460
  /** The finding the whole command exists to produce. */
372
461
  biggestPart(name: string, pct: string): string;
373
462
  outputDominates(pct: string): string;
@@ -375,8 +464,440 @@ export interface CliMessages {
375
464
  skipped(count: number, lines: string): string;
376
465
  empty(): string;
377
466
  nothingPriced(): string;
467
+ /**
468
+ * The section the whole command builds towards.
469
+ *
470
+ * Trazum's rules recover about 1% of a bill. Which model a call goes to moves
471
+ * 40% to 80%, and the Batch API moves 50% flat. A report that shows where the
472
+ * money went and then stops is a report that leaves the reader with the
473
+ * smallest lever in their hand.
474
+ */
475
+ leversHeading(): string;
476
+ /**
477
+ * One slice, with what everything on it comes to **together**.
478
+ *
479
+ * The headline is the combined figure and not a sum of the options below it:
480
+ * batching a routed call discounts the cheaper model's price, not the one you
481
+ * left. Printing the options as separate rows produced $12.60 and $10.50
482
+ * against a slice that had spent $21.00.
483
+ */
484
+ leverSlice(label: string, model: string, usd: string, pct: string): string;
485
+ leverRoute(candidate: string, usd: string): string;
486
+ /**
487
+ * The command that settles it.
488
+ *
489
+ * It used to name `trazum eval --model <candidate>`, which does not do what
490
+ * that sentence claims: `eval` runs against whatever `TRAZUM_LLM_MODEL` says
491
+ * and `--model` only prices the report. The instruction sent the reader to a
492
+ * measurement that never touched the candidate model.
493
+ */
494
+ leverRouteVerify(candidate: string): string;
495
+ leverBatch(usd: string): string;
496
+ /** How many calls the slice covers, so the reader can judge the effort. */
497
+ leverCalls(calls: string, spent: string): string;
498
+ /**
499
+ * The ceiling on shortening prompts, printed beside the levers on purpose.
500
+ *
501
+ * A 1% win reported without saying 1% of what is not information. This is the
502
+ * comparison that makes the rest of the report honest about its own value.
503
+ */
504
+ leverPromptCeiling(usd: string, pct: string): string;
505
+ /** Nothing cleared the threshold, which is a real answer. */
506
+ leversNone(): string;
507
+ /**
508
+ * The log carries no labels, so every lever describes a mixture.
509
+ *
510
+ * A classifier and a RAG workload with no label between them merge into one
511
+ * slice, and the report then recommends a single route for two workloads that
512
+ * need different answers. The session case already says "add the field"; this
513
+ * one said nothing, and the row is named `unlabelled` as though that were a
514
+ * workload.
515
+ */
516
+ leversUnlabelled(): string;
517
+ /**
518
+ * What re-sending the conversation costs.
519
+ *
520
+ * On an agent bill this is routinely the largest line, and nothing in this
521
+ * tool could see it: a prompt file shows the system prompt and not the
522
+ * history, and a total shows the sum and not the shape.
523
+ */
524
+ historyHeading(): string;
525
+ historyGrowth(label: string, model: string, first: string, last: string, turns: string): string;
526
+ /** A ceiling, and it says so — part of the growth is the user's own messages. */
527
+ historyCeiling(usd: string, pct: string, flat: string, spent: string): string;
528
+ /** No session field in the log, so the question cannot be asked. */
529
+ historyNoSessions(): string;
530
+ /**
531
+ * Output spend that bought answers cut off mid-generation.
532
+ *
533
+ * The one category of a bill that is waste without a counterpart: a
534
+ * truncated answer was paid for in full, is frequently retried — billed
535
+ * again — and the cut attempt bought nothing. Output is the largest line on
536
+ * most bills, and this is the slice of it nobody sees.
537
+ */
538
+ truncatedWaste(calls: string, usd: string, pct: string): string;
539
+ /** The log carries no stop reason, so the question cannot be asked. */
540
+ truncatedNotRecorded(): string;
541
+ /**
542
+ * This bill against the previous one — how spend actually gets out of hand.
543
+ *
544
+ * Nobody adds five thousand a month in one day; bills grow four percent a
545
+ * week while every snapshot looks reasonable. Comparing two logs is the
546
+ * baseline gate the prompts already had, applied to the money itself.
547
+ * **Positive means the bill grew** — the diff convention, not the savings
548
+ * one — and every figure is between exactly these two files: no periods are
549
+ * assumed, so the call counts print beside the money for the reader to
550
+ * judge comparability.
551
+ */
552
+ againstHeading(): string;
553
+ againstTotals(before: string, after: string, delta: string, pct: string, callsBefore: string, callsAfter: string): string;
554
+ /** One label's contribution to the change. */
555
+ againstDriver(delta: string, label: string, before: string, after: string): string;
556
+ againstDriverNew(delta: string, label: string): string;
557
+ againstDriverGone(delta: string, label: string): string;
558
+ /**
559
+ * Lead-in for the change split by model — where the mix moved. A workload
560
+ * that kept its name and switched models reads as "chat grew" in the
561
+ * label rows; only this section can say the reason is the model.
562
+ */
563
+ againstByModel(): string;
564
+ againstNothingPriced(): string;
565
+ /**
566
+ * Why a label's cache is failing, read from its mapped prompt file.
567
+ *
568
+ * `profile` alone can only say *that* caching loses money on a label — the
569
+ * log carries counts, not content. With `labels` in the config it reads the
570
+ * named file and says why. The file is whatever the repository holds today,
571
+ * which may not be what produced the log, and the sentence says so rather
572
+ * than presenting a fresh file as the history's explanation.
573
+ */
574
+ labelPrefixBelowMinimum(file: string, prefix: string, minimum: string, model: string): string;
575
+ labelPrefixMovable(file: string, movable: string, prefix: string): string;
576
+ labelPrefixHealthy(file: string, prefix: string, minimum: string): string;
577
+ labelFileMissing(label: string, file: string): string;
578
+ /**
579
+ * Where the output spend concentrates — the actionable half of "output
580
+ * dominates". A tail worth hunting and a task whose answers are inherently
581
+ * long produce the same total, and only the shape tells them apart.
582
+ */
583
+ outputShapeHeading(): string;
584
+ /** A tail: a small share of calls holds half the spend. */
585
+ outputTail(label: string, model: string, callPct: string, spendPct: string, above: string, usd: string): string;
586
+ outputTailAdvice(): string;
587
+ /** Flat: the spend is where the calls are, and the length is the task. */
588
+ outputFlat(label: string, model: string, callPct: string, spendPct: string, usd: string): string;
589
+ outputFlatAdvice(): string;
590
+ /**
591
+ * Bucket ceilings, exact over the histogram: the number somebody setting
592
+ * max_tokens actually wants, measured on these calls and promised for
593
+ * nothing.
594
+ */
595
+ outputPercentiles(p50: string, p95: string): string;
596
+ /**
597
+ * How big the calls themselves are — the other half of the bill.
598
+ *
599
+ * Both figures are bucket ceilings, so the ratio between them is coarse
600
+ * by construction; the copy says "about". Loud past four times the
601
+ * median, a presentation threshold stated in the sentence.
602
+ */
603
+ inputShapeHeading(): string;
604
+ inputSkewed(
605
+ label: string,
606
+ model: string,
607
+ p50: string,
608
+ p95: string,
609
+ ratio: string,
610
+ usd: string,
611
+ ): string;
612
+ inputSkewedAdvice(): string;
613
+ inputEven(label: string, model: string, p50: string, p95: string, usd: string): string;
614
+ inputEvenAdvice(): string;
615
+ /** Every call above the widest bucket edge, so no ceiling can be named. */
616
+ inputHuge(label: string, model: string, calls: string, usd: string): string;
617
+ inputMostlyCached(share: string): string;
618
+ /**
619
+ * Consecutive calls in one conversation carrying the same input size,
620
+ * seconds apart — a retry or a loop. Hedged on purpose: this reads counts
621
+ * and cannot see content, so the pattern is stated and the conclusion is
622
+ * left to the reader.
623
+ */
624
+ repeatsHeading(): string;
625
+ repeatsFound(
626
+ label: string,
627
+ model: string,
628
+ repeats: string,
629
+ checked: string,
630
+ seconds: string,
631
+ usd: string,
632
+ ): string;
633
+ repeatsAdvice(): string;
634
+ inputFullRate(): string;
378
635
  assumedWriteTtl(calls: number): string;
636
+ /**
637
+ * The period the log covers, when its records carry a clock. Stated and
638
+ * never extrapolated: naming the span makes the reader's own monthly
639
+ * arithmetic valid, while a per-month figure from a partial month would be
640
+ * this tool doing the guessing it exists to end.
641
+ */
642
+ spanLine(from: string, to: string, days: string): string;
643
+ /** Appended when only part of the log carries a timestamp. */
644
+ spanPartial(withTs: string, total: string): string;
645
+ /**
646
+ * Whether the cache TTL fits how fast the turns arrive — the mechanism
647
+ * behind a losing cache, and the only place an overlong TTL is visible.
648
+ * Four verdicts plus "could not be measured": the same three-state
649
+ * discipline as truncation, because "no data" and "fits" are different
650
+ * answers.
651
+ */
652
+ ttlFitExpires(label: string, model: string, gap: string): string;
653
+ ttlFitExpiresBoth(label: string, model: string, gap: string): string;
654
+ ttlFitOverlong(label: string, model: string, gap: string, usd: string): string;
655
+ ttlFitUnsettledGap(label: string, model: string, gap: string): string;
656
+ ttlFitFits(label: string, model: string, gap: string): string;
657
+ ttlFitUnmeasured(): string;
658
+ /**
659
+ * The most expensive day against the median day — the shape of the bill
660
+ * over time, which the total hides. The 2x-median threshold that makes it
661
+ * loud is stated in the sentence, never hidden in code.
662
+ */
663
+ dayPeak(day: string, usd: string, xMedian: string): string;
664
+ dayPeakLabel(label: string, usd: string): string;
665
+ /**
666
+ * The money gates. `check` gates tokens before spending; these gate the
667
+ * spend itself, over exactly the log handed in — no period assumed.
668
+ */
669
+ maxUsdOk(total: string, max: string): string;
670
+ maxUsdFailed(total: string, max: string): string;
671
+ maxGrowthUsdFailed(delta: string, max: string): string;
672
+ maxGrowthNeedsAgainst(): string;
673
+ /**
674
+ * The cache gate reads the worst case on purpose: a gate reading the
675
+ * flattering half of an unsettled verdict would pass exactly the bills
676
+ * it exists to catch. Two failure messages, because a settled loss and a
677
+ * ceiling the missing TTL field could settle are different instructions.
678
+ */
679
+ maxCacheLossOk(worst: string, max: string): string;
680
+ maxCacheLossFailed(delta: string, max: string): string;
681
+ maxCacheLossWorstCase(calls: number, worst: string, max: string): string;
682
+ /**
683
+ * The per-day gate — the one a total cannot arm. A month under budget
684
+ * hides the afternoon a loop burned a quarter of it, and the worst single
685
+ * UTC day is the shape that has. A log with no clock cannot be judged by
686
+ * day and fails: "not measured" is not "under budget".
687
+ */
688
+ maxDayOk(day: string, usd: string, max: string): string;
689
+ maxDayFailed(day: string, usd: string, max: string): string;
690
+ maxDayNoClock(): string;
691
+ maxDayUndated(calls: string): string;
692
+ /**
693
+ * The price table behind every dollar in the report, when it is old
694
+ * enough to matter. Unlike a skipped line, staleness does not name its
695
+ * own size — the error is exactly whatever the provider changed.
696
+ */
697
+ pricesStale(date: string, days: number): string;
698
+ /**
699
+ * The spend-per-day table in the markdown rendering — the series the
700
+ * peak sentence summarises. Truncation is counted out loud: silent
701
+ * truncation reads as "covered everything" when it did not.
702
+ */
703
+ dayTableDay(): string;
704
+ dayTableCalls(): string;
705
+ dayTableTop(): string;
706
+ dayTableEarlier(days: number): string;
707
+ /**
708
+ * A gate can only judge the money it can see. When lines were unreadable,
709
+ * models unpriced, or clockless calls left outside a window, the gated
710
+ * figure is a floor — and passing on a floor silently is the flattering
711
+ * omission this repository refuses.
712
+ */
713
+ gateOnFloor(reasons: string): string;
714
+ floorSkipped(lines: number): string;
715
+ floorUnpriced(calls: number): string;
716
+ floorUndated(calls: number): string;
717
+ /**
718
+ * Two logs whose periods intersect: part of the "growth" is the same
719
+ * money on both sides of the subtraction. Only decidable when both logs
720
+ * carry a clock; unknown stays silent rather than reassuring.
721
+ */
722
+ againstOverlap(from: string, to: string): string;
723
+ /**
724
+ * What one conversation costs — median against p95, never a mean: one
725
+ * runaway loop would drag a mean up and hide the ordinary case, which is
726
+ * the figure a per-seat price is set from.
727
+ */
728
+ sessionCost(
729
+ label: string,
730
+ model: string,
731
+ sessions: string,
732
+ median: string,
733
+ medianTurns: string,
734
+ p95: string,
735
+ max: string,
736
+ ): string;
737
+ /** Said only when the p95 clears ten times the median — a real tail. */
738
+ sessionCostTail(ratio: string): string;
739
+ /**
740
+ * Per-workload budgets from the config. A budgeted label with no calls in
741
+ * the log is "not measured", never a pass: a workload that did not appear
742
+ * is not one that came in under budget.
743
+ */
744
+ labelBudgetOk(label: string, usd: string, max: string): string;
745
+ labelBudgetFailed(label: string, usd: string, max: string): string;
746
+ labelBudgetMissing(label: string): string;
747
+ labelBudgetWindowed(): string;
748
+ /**
749
+ * A directory of rotated logs, read as one. How many files were read is
750
+ * stated: a report over "the logs" that silently skipped one is a total
751
+ * wrong by an unknown amount.
752
+ */
753
+ /**
754
+ * Which workloads pay for truncated answers, and at what rate. The rate
755
+ * is over calls that recorded a stop reason, never over all calls: a
756
+ * workload that logs the field on half its traffic must not be reported
757
+ * as though the unmeasured half completed.
758
+ */
759
+ /**
760
+ * The shape of the UTC day: the fewest hours holding 80% of the spend.
761
+ * Concentrated means somebody is waiting on those calls; flat means
762
+ * background work, which is what the Batch API halves. Names the lever,
763
+ * never claims the saving — whether a workload can wait is a product
764
+ * decision counts cannot make.
765
+ */
766
+ /**
767
+ * What the log cannot answer, and what would fix it. Counts rather than
768
+ * booleans: twelve labelled records out of forty thousand is not a
769
+ * labelled log, and a boolean would call it one. Only missing fields are
770
+ * listed — a paragraph of things that are fine is the paragraph readers
771
+ * learn to skip.
772
+ */
773
+ /**
774
+ * The token budget against what the call actually carried. `budgets`
775
+ * gates a prompt file; the log records the whole call. When the gap is
776
+ * wide the gate is real but tiny, and a green build says nothing about
777
+ * the other 96%.
778
+ */
779
+ /**
780
+ * Lines identical to an earlier one, with a clock to make the claim safe.
781
+ * States the count and the money and stops: whether it is a double export
782
+ * or a busy millisecond is the reader's to know.
783
+ */
784
+ duplicateLines(count: number, usd: string): string;
785
+ budgetVsWire(label: string, file: string, budget: string, perCall: string, share: string): string;
786
+ /** `--csv-shape` naming a table that does not exist. */
787
+ badCsvShape(value: string): string;
788
+ /**
789
+ * `--what-if <model>`: these exact calls at another model's rates.
790
+ *
791
+ * The assumption line prints before the figure on purpose — a dollar
792
+ * amount with the caveat underneath is read as a recommendation with
793
+ * small print, and this comparison knows nothing about whether the
794
+ * cheaper model could do the work.
795
+ */
796
+ whatIfHeading(model: string): string;
797
+ whatIfAssumption(): string;
798
+ whatIfTotal(current: string, target: string, delta: string): string;
799
+ whatIfCheaper(): string;
800
+ whatIfDearer(): string;
801
+ whatIfSlice(label: string, model: string, current: string, target: string): string;
802
+ /** A call the target's context window could not have accepted. */
803
+ whatIfOverContext(label: string, tokens: string, window: string, usd: string): string;
804
+ whatIfAlreadyThere(calls: string, usd: string): string;
805
+ whatIfUnpriced(calls: string, models: string): string;
806
+ whatIfNothingToMove(): string;
807
+ /** `--what-if` naming a model the catalogue does not price. */
808
+ whatIfUnknown(value: string, available: string): string;
809
+ /**
810
+ * A gzipped log that will not decompress. An error naming the file, not
811
+ * a skip: a total quietly missing a day is the failure this refuses.
812
+ */
813
+ badGzip(file: string, detail: string): string;
814
+ coverageHeading(): string;
815
+ needsLabel(seen: string): string;
816
+ needsSession(seen: string): string;
817
+ needsTs(seen: string): string;
818
+ needsStopReason(seen: string): string;
819
+ needsCacheTtl(seen: string): string;
820
+ hoursConcentrated(hours: string, list: string): string;
821
+ hoursFlat(hours: string): string;
822
+ truncatedBy(label: string, calls: string, measured: string, rate: string, usd: string): string;
823
+ /** What the answers that finished actually needed, for setting the cap. */
824
+ truncatedCeiling(p95: string): string;
825
+ readFiles(files: number, directory: string): string;
826
+ noLogsInDirectory(directory: string, extensions: string): string;
827
+ /**
828
+ * The time window — the drill-down in time. Every figure below the line
829
+ * describes a slice, so the line prints before any of them; clockless
830
+ * calls under a window are excluded and *counted out loud*, because
831
+ * dropping them silently would understate the period's bill invisibly.
832
+ */
833
+ windowLine(since: string, until: string): string;
834
+ windowUndated(calls: number): string;
835
+ /** A window over a log with no clock gates nothing, which is an error. */
836
+ /**
837
+ * A relative window is measured against the machine's clock, not the
838
+ * log's — a real difference on an exported log, and one a reader who did
839
+ * not name the dates would otherwise misread.
840
+ */
841
+ windowRelative(): string;
842
+ windowRelativeEmpty(): string;
843
+ windowNeedsClock(): string;
844
+ /** A window matching nothing must not become a passing $0 gate. */
845
+ windowMatchesNothing(from: string, to: string): string;
846
+ sinceAfterUntil(): string;
847
+ badWhen(flag: string, value: string): string;
848
+ /**
849
+ * Cache writes by conversations that ended after one turn. Two sentences
850
+ * for the same tokens: a ceiling when the slice has reads (another
851
+ * conversation sharing the prefix may have read the write — the log
852
+ * cannot see whose write a read hit), a fact when it has none.
853
+ */
854
+ singleTurnCeiling(label: string, model: string, single: string, sessions: string, usd: string): string;
855
+ singleTurnConfirmed(label: string, model: string, single: string, sessions: string, usd: string): string;
379
856
  };
857
+ /**
858
+ * `trazum route` — the loop the levers section could only point at.
859
+ *
860
+ * `profile` prices a route exactly and can say nothing about whether the cheaper
861
+ * model still does the job. This runs that measurement: same prompt, two models,
862
+ * judged against the expensive model's own run-to-run variance. It is what turns
863
+ * "here is $16.80 you might save" into "here is $16.80, measured".
864
+ */
865
+ route: {
866
+ noTarget(): string;
867
+ needsPrompt(): string;
868
+ noRoute(): string;
869
+ /**
870
+ * `--label` named something no call carries — almost always a typo.
871
+ *
872
+ * The generic answer here was "no route clears 1% of the bill: these calls
873
+ * are already on the cheapest model of their family" — false on both counts
874
+ * when the log had a 60% route under a different name. A wrong label gets
875
+ * the labels that exist, not a verdict about ones that do not.
876
+ */
877
+ labelNotFound(label: string, available: string): string;
878
+ /** The slice picked, and what taking it is worth. */
879
+ picked(label: string, model: string, candidate: string, usd: string, pct: string): string;
880
+ /** What the measurement will cost, before a single call is made. */
881
+ willSpend(calls: number, model: string, candidate: string): string;
882
+ dryRun(): string;
883
+ running(cases: number): string;
884
+ /** The verdict, against the original model's own variance. */
885
+ agreement(cross: string, self: string): string;
886
+ holds(usd: string): string;
887
+ diverges(usd: string): string;
888
+ inconclusive(): string;
889
+ /** Never a recommendation on its own — the money is only half the answer. */
890
+ yours(): string;
891
+ /**
892
+ * The slice carries no label, so the figure may cover calls this prompt is not.
893
+ *
894
+ * Measuring one prompt and attributing the verdict to a bucket holding two
895
+ * workloads is a figure describing something other than what was measured —
896
+ * the fault this repository keeps finding in itself, in a new place.
897
+ */
898
+ unlabelledSlice(): string;
899
+ };
900
+
380
901
  baseline: {
381
902
  recorded(path: string, files: string, tokens: string): string;
382
903
  recordedMoney(monthly: string, model: string, calls: string): string;