agent-nuvira 3.5.2 → 3.5.6

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.
Files changed (155) hide show
  1. package/README.md +66 -0
  2. package/dist/cli/chat.d.ts.map +1 -1
  3. package/dist/cli/chat.js +72 -19
  4. package/dist/cli/chat.js.map +1 -1
  5. package/dist/cli/config.d.ts.map +1 -1
  6. package/dist/cli/config.js +17 -3
  7. package/dist/cli/config.js.map +1 -1
  8. package/dist/cli/execute.d.ts.map +1 -1
  9. package/dist/cli/execute.js +63 -22
  10. package/dist/cli/execute.js.map +1 -1
  11. package/dist/cli/failover-runner.js +4 -4
  12. package/dist/cli/failover-runner.js.map +1 -1
  13. package/dist/cli/gateway.js +13 -13
  14. package/dist/cli/gateway.js.map +1 -1
  15. package/dist/cli/loop-executor.d.ts +22 -0
  16. package/dist/cli/loop-executor.d.ts.map +1 -1
  17. package/dist/cli/loop-executor.js +56 -12
  18. package/dist/cli/loop-executor.js.map +1 -1
  19. package/dist/cli/stats.d.ts +62 -4
  20. package/dist/cli/stats.d.ts.map +1 -1
  21. package/dist/cli/stats.js +282 -5
  22. package/dist/cli/stats.js.map +1 -1
  23. package/dist/cli/ui/index.d.ts +2 -2
  24. package/dist/cli/ui/index.d.ts.map +1 -1
  25. package/dist/cli/ui/index.js +1 -1
  26. package/dist/cli/ui/index.js.map +1 -1
  27. package/dist/cli/ui/presenter.d.ts +43 -0
  28. package/dist/cli/ui/presenter.d.ts.map +1 -1
  29. package/dist/cli/ui/presenter.js +88 -1
  30. package/dist/cli/ui/presenter.js.map +1 -1
  31. package/dist/config/manager.d.ts +29 -1
  32. package/dist/config/manager.d.ts.map +1 -1
  33. package/dist/config/manager.js +178 -143
  34. package/dist/config/manager.js.map +1 -1
  35. package/dist/config/paths.d.ts +48 -1
  36. package/dist/config/paths.d.ts.map +1 -1
  37. package/dist/config/paths.js +59 -9
  38. package/dist/config/paths.js.map +1 -1
  39. package/dist/config/service-catalog.js +1 -1
  40. package/dist/config/service-catalog.js.map +1 -1
  41. package/dist/config/service-probe.js +1 -1
  42. package/dist/config/service-probe.js.map +1 -1
  43. package/dist/config/types.d.ts +22 -0
  44. package/dist/config/types.d.ts.map +1 -1
  45. package/dist/gateway/adapters.js +8 -8
  46. package/dist/gateway/adapters.js.map +1 -1
  47. package/dist/gateway/registry.js +1 -1
  48. package/dist/gateway/registry.js.map +1 -1
  49. package/dist/learning/auto-router.d.ts +30 -4
  50. package/dist/learning/auto-router.d.ts.map +1 -1
  51. package/dist/learning/auto-router.js +54 -38
  52. package/dist/learning/auto-router.js.map +1 -1
  53. package/dist/learning/cost-classification.d.ts +188 -0
  54. package/dist/learning/cost-classification.d.ts.map +1 -0
  55. package/dist/learning/cost-classification.js +294 -0
  56. package/dist/learning/cost-classification.js.map +1 -0
  57. package/dist/learning/cost-ledger-maintenance.d.ts +122 -0
  58. package/dist/learning/cost-ledger-maintenance.d.ts.map +1 -0
  59. package/dist/learning/cost-ledger-maintenance.js +289 -0
  60. package/dist/learning/cost-ledger-maintenance.js.map +1 -0
  61. package/dist/learning/cost-reconcile.d.ts +160 -0
  62. package/dist/learning/cost-reconcile.d.ts.map +1 -0
  63. package/dist/learning/cost-reconcile.js +238 -0
  64. package/dist/learning/cost-reconcile.js.map +1 -0
  65. package/dist/learning/cost-tracker.d.ts +134 -13
  66. package/dist/learning/cost-tracker.d.ts.map +1 -1
  67. package/dist/learning/cost-tracker.js +190 -68
  68. package/dist/learning/cost-tracker.js.map +1 -1
  69. package/dist/learning/model-pricing.d.ts +240 -0
  70. package/dist/learning/model-pricing.d.ts.map +1 -0
  71. package/dist/learning/model-pricing.js +309 -0
  72. package/dist/learning/model-pricing.js.map +1 -0
  73. package/dist/learning/model-selection.d.ts +15 -0
  74. package/dist/learning/model-selection.d.ts.map +1 -1
  75. package/dist/learning/model-selection.js +22 -2
  76. package/dist/learning/model-selection.js.map +1 -1
  77. package/dist/learning/model-warmup.d.ts.map +1 -1
  78. package/dist/learning/model-warmup.js +4 -1
  79. package/dist/learning/model-warmup.js.map +1 -1
  80. package/dist/learning/session-store.d.ts +1 -5
  81. package/dist/learning/session-store.d.ts.map +1 -1
  82. package/dist/learning/session-store.js +10 -1
  83. package/dist/learning/session-store.js.map +1 -1
  84. package/dist/learning/step-checkpoint.d.ts +32 -3
  85. package/dist/learning/step-checkpoint.d.ts.map +1 -1
  86. package/dist/learning/step-checkpoint.js +33 -4
  87. package/dist/learning/step-checkpoint.js.map +1 -1
  88. package/dist/mcp/settings.d.ts +15 -3
  89. package/dist/mcp/settings.d.ts.map +1 -1
  90. package/dist/mcp/settings.js +30 -15
  91. package/dist/mcp/settings.js.map +1 -1
  92. package/dist/parity/drivers.d.ts.map +1 -1
  93. package/dist/parity/drivers.js +31 -5
  94. package/dist/parity/drivers.js.map +1 -1
  95. package/dist/tools/child-agent-runtime.d.ts.map +1 -1
  96. package/dist/tools/child-agent-runtime.js +13 -7
  97. package/dist/tools/child-agent-runtime.js.map +1 -1
  98. package/dist/tools/followup-utils.d.ts +36 -0
  99. package/dist/tools/followup-utils.d.ts.map +1 -1
  100. package/dist/tools/followup-utils.js +66 -0
  101. package/dist/tools/followup-utils.js.map +1 -1
  102. package/dist/tools/modality/browser.js +2 -2
  103. package/dist/tools/modality/browser.js.map +1 -1
  104. package/dist/tools/modality/image-gen.d.ts +1 -1
  105. package/dist/tools/modality/image-gen.js +1 -1
  106. package/dist/tools/modality/image-providers.js +1 -1
  107. package/dist/tools/modality/image-providers.js.map +1 -1
  108. package/dist/tools/modality/vision.d.ts +1 -1
  109. package/dist/tools/modality/vision.js +1 -1
  110. package/dist/tools/modality/voice.js +2 -2
  111. package/dist/tools/modality/voice.js.map +1 -1
  112. package/dist/tools/neutts-synth.d.ts +3 -3
  113. package/dist/tools/neutts-synth.js +3 -3
  114. package/dist/tools/neutts-synth.js.map +1 -1
  115. package/dist/tools/process-liveness.d.ts +50 -0
  116. package/dist/tools/process-liveness.d.ts.map +1 -1
  117. package/dist/tools/process-liveness.js +82 -9
  118. package/dist/tools/process-liveness.js.map +1 -1
  119. package/dist/tools/registry.d.ts +1 -1
  120. package/dist/tools/registry.d.ts.map +1 -1
  121. package/dist/tools/registry.js +27 -4
  122. package/dist/tools/registry.js.map +1 -1
  123. package/dist/tools/run-registry.d.ts.map +1 -1
  124. package/dist/tools/run-registry.js +14 -1
  125. package/dist/tools/run-registry.js.map +1 -1
  126. package/dist/tools/run-terminal.d.ts.map +1 -1
  127. package/dist/tools/run-terminal.js +18 -1
  128. package/dist/tools/run-terminal.js.map +1 -1
  129. package/dist/tools/tool-loop.d.ts.map +1 -1
  130. package/dist/tools/tool-loop.js +5 -3
  131. package/dist/tools/tool-loop.js.map +1 -1
  132. package/dist/tools/web-research.d.ts +1 -1
  133. package/dist/tools/web-research.js +3 -3
  134. package/dist/tools/web-research.js.map +1 -1
  135. package/dist/web-dashboard/chat-console.d.ts +56 -0
  136. package/dist/web-dashboard/chat-console.d.ts.map +1 -1
  137. package/dist/web-dashboard/chat-console.js +57 -1
  138. package/dist/web-dashboard/chat-console.js.map +1 -1
  139. package/dist/web-dashboard/server.d.ts.map +1 -1
  140. package/dist/web-dashboard/server.js +122 -2
  141. package/dist/web-dashboard/server.js.map +1 -1
  142. package/dist/web-dashboard/src/types.d.ts +63 -0
  143. package/dist/web-dashboard/src/types.d.ts.map +1 -1
  144. package/dist/web-dashboard/task-runner.d.ts +17 -1
  145. package/dist/web-dashboard/task-runner.d.ts.map +1 -1
  146. package/dist/web-dashboard/task-runner.js +84 -9
  147. package/dist/web-dashboard/task-runner.js.map +1 -1
  148. package/package.json +1 -1
  149. package/src/web-dashboard/public/assets/index-DeSsNU94.css +1 -0
  150. package/src/web-dashboard/public/assets/index-Do_O67Kv.js +236 -0
  151. package/src/web-dashboard/public/assets/index-Do_O67Kv.js.map +1 -0
  152. package/src/web-dashboard/public/index.html +2 -2
  153. package/src/web-dashboard/public/assets/index-DycGBWwy.js +0 -236
  154. package/src/web-dashboard/public/assets/index-DycGBWwy.js.map +0 -1
  155. package/src/web-dashboard/public/assets/index-aO3aG7ex.css +0 -1
@@ -2,17 +2,75 @@
2
2
  * Stats command — View usage statistics and cost tracking.
3
3
  *
4
4
  * Usage:
5
- * nuvira stats — Show all stats summary
6
- * nuvira stats cost — Show cost tracking details
7
- * nuvira stats cost --clear — Reset cost tracking data
8
- * nuvira stats history — Show conversation history stats
5
+ * nuvira stats — Show all stats summary
6
+ * nuvira stats cost — Show cost tracking details
7
+ * nuvira stats cost --clear — Reset cost tracking data
8
+ * nuvira stats cost reconcile ... — compare the ledger to a provider invoice
9
+ * nuvira stats cost reprice — re-price legacy rows (dry run; --apply writes)
10
+ * nuvira stats history — Show conversation history stats
11
+ *
12
+ * ─── WHY reconcile / reprice EXIST (read before editing their defaults) ─────
13
+ *
14
+ * `stats cost` prints what the ledger can EVIDENCE, and the ledger cannot see a
15
+ * provider's invoice. That is not a defect to paper over with a nicer estimate:
16
+ * the fix is to let the operator supply the one number that is actual — the
17
+ * provider's own charge for a stated window — and to say honestly how far the
18
+ * ledger is from it, and what the gap consists of. `reconcile` is therefore the
19
+ * only command in this file that reports a figure no local computation could
20
+ * produce, and it never adjusts the ledger to agree.
21
+ *
22
+ * `reprice` is the explicit, dry-run-default repair of the rows already on disk
23
+ * (legacy prices from the retired hardcoded table, test-origin rows). Both are
24
+ * documented in `learning/cost-reconcile.ts` and `learning/cost-ledger-maintenance.ts`.
9
25
  */
10
26
  import { Command } from 'commander';
11
27
  import { BaseCommand } from './commands.js';
12
28
  export declare class StatsCommand extends BaseCommand {
13
29
  create(): Command;
14
30
  private showCost;
31
+ /**
32
+ * `stats cost reconcile` — the provider's own figure vs this ledger.
33
+ *
34
+ * Two rules the defaults encode:
35
+ * 1. A WINDOW IS MANDATORY. Comparing an all-time ledger to a monthly invoice
36
+ * is a meaningless subtraction that would print a delta as if it meant
37
+ * something. The default is the current calendar month, and the window is
38
+ * always printed so a partial (still-running) period can be judged as such.
39
+ * 2. THE PROVIDER IS VALIDATED. An unknown id would otherwise read as "no
40
+ * rows", i.e. a silent zero that looks like "you used nothing" rather than
41
+ * "you typed the wrong id".
42
+ */
43
+ private reconcileCost;
44
+ /**
45
+ * Turn the CLI flags into an invoice claim, or explain why they cannot be.
46
+ * Returns null after printing the reason — the command exits without a report
47
+ * rather than reconciling against a window we had to guess.
48
+ */
49
+ private buildClaim;
50
+ /**
51
+ * Record the billing fact, but ONLY one the invoice does not contradict.
52
+ *
53
+ * This is the whole point of the exercise: `billingTier` moves the headline
54
+ * from "unverified" to "billed", so letting a user declare `free` while holding
55
+ * an invoice that charged $18 would reintroduce exactly the fabricated picture
56
+ * this change removed. A refusal is the useful answer.
57
+ */
58
+ private applyTierDeclaration;
59
+ /**
60
+ * `stats cost reprice` — the explicit repair of the rows already on disk.
61
+ *
62
+ * Dry run by default: the ledger is the user's billing history, and a reprice
63
+ * changes every legacy figure in it. `--apply` writes once, after taking a
64
+ * backup, and the report says exactly what changed either way.
65
+ */
66
+ private repriceLedger;
15
67
  private showHistoryStats;
16
68
  private showAllStats;
17
69
  }
70
+ /**
71
+ * Parse a currency amount the way a user copies it off a dashboard: `$12.34`,
72
+ * `1,234.56`, ` 12.34 `. Returns null when it is not a non-negative finite
73
+ * number — a reconciled figure is money, and "NaN" must never reach a report.
74
+ */
75
+ export declare function parseAmount(raw: string | undefined): number | null;
18
76
  //# sourceMappingURL=stats.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"stats.d.ts","sourceRoot":"","sources":["../../src/cli/stats.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAK5C,qBAAa,YAAa,SAAQ,WAAW;IAC3C,MAAM,IAAI,OAAO;YA+BH,QAAQ;IAWtB,OAAO,CAAC,gBAAgB;IAsBxB,OAAO,CAAC,YAAY;CAYrB"}
1
+ {"version":3,"file":"stats.d.ts","sourceRoot":"","sources":["../../src/cli/stats.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAuC5C,qBAAa,YAAa,SAAQ,WAAW;IAC3C,MAAM,IAAI,OAAO;YAgEH,QAAQ;IAWtB;;;;;;;;;;;OAWG;YACW,aAAa;IA8B3B;;;;OAIG;IACH,OAAO,CAAC,UAAU;IAkHlB;;;;;;;OAOG;IACH,OAAO,CAAC,oBAAoB;IAwD5B;;;;;;OAMG;IACH,OAAO,CAAC,aAAa;IAwBrB,OAAO,CAAC,gBAAgB;IAsBxB,OAAO,CAAC,YAAY;CAYrB;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAOlE"}
package/dist/cli/stats.js CHANGED
@@ -2,16 +2,38 @@
2
2
  * Stats command — View usage statistics and cost tracking.
3
3
  *
4
4
  * Usage:
5
- * nuvira stats — Show all stats summary
6
- * nuvira stats cost — Show cost tracking details
7
- * nuvira stats cost --clear — Reset cost tracking data
8
- * nuvira stats history — Show conversation history stats
5
+ * nuvira stats — Show all stats summary
6
+ * nuvira stats cost — Show cost tracking details
7
+ * nuvira stats cost --clear — Reset cost tracking data
8
+ * nuvira stats cost reconcile ... — compare the ledger to a provider invoice
9
+ * nuvira stats cost reprice — re-price legacy rows (dry run; --apply writes)
10
+ * nuvira stats history — Show conversation history stats
11
+ *
12
+ * ─── WHY reconcile / reprice EXIST (read before editing their defaults) ─────
13
+ *
14
+ * `stats cost` prints what the ledger can EVIDENCE, and the ledger cannot see a
15
+ * provider's invoice. That is not a defect to paper over with a nicer estimate:
16
+ * the fix is to let the operator supply the one number that is actual — the
17
+ * provider's own charge for a stated window — and to say honestly how far the
18
+ * ledger is from it, and what the gap consists of. `reconcile` is therefore the
19
+ * only command in this file that reports a figure no local computation could
20
+ * produce, and it never adjusts the ledger to agree.
21
+ *
22
+ * `reprice` is the explicit, dry-run-default repair of the rows already on disk
23
+ * (legacy prices from the retired hardcoded table, test-origin rows). Both are
24
+ * documented in `learning/cost-reconcile.ts` and `learning/cost-ledger-maintenance.ts`.
9
25
  */
10
26
  import { Command } from 'commander';
11
27
  import { BaseCommand } from './commands.js';
12
- import { getCostTracker } from '../learning/cost-tracker.js';
28
+ import { getCostTracker, resetPricingContextCache, } from '../learning/cost-tracker.js';
29
+ import { asBillingTier, BILLING_TIERS } from '../learning/model-pricing.js';
30
+ import { formatReconciliation, reconcileEntries, recordReconciliation, reconciliationLedgerPath, } from '../learning/cost-reconcile.js';
31
+ import { formatMaintenanceReport, maintainCostLedger } from '../learning/cost-ledger-maintenance.js';
32
+ import { CATALOG_PROVIDER_IDS } from '../inference/provider-catalog.js';
13
33
  import { getChatHistory } from '../context/history.js';
14
34
  import { logger } from '../utils/logger.js';
35
+ /** One day in ms — the unit the CLI's window flags speak in. */
36
+ const DAY_MS = 24 * 60 * 60 * 1000;
15
37
  export class StatsCommand extends BaseCommand {
16
38
  create() {
17
39
  const command = new Command('stats')
@@ -23,6 +45,30 @@ export class StatsCommand extends BaseCommand {
23
45
  .action(async (options) => {
24
46
  await this.showCost(options);
25
47
  });
48
+ // ── cost reconcile ───────────────────────────────────────────────────
49
+ costCmd.addCommand(new Command('reconcile')
50
+ .description("Compare this ledger to a provider's OWN invoice for a window (the only 'actual' figure)")
51
+ .requiredOption('-p, --provider <id>', 'provider id the invoice line belongs to (e.g. deepseek)')
52
+ .requiredOption('-a, --amount <usd>', "the provider's own charge for the window, in USD (e.g. 12.34)")
53
+ .option('--days <n>', `window: the last N days, ending now (default: the current calendar month)`)
54
+ .option('--since <iso|epochMs>', 'window start (overrides --days)')
55
+ .option('--until <iso|epochMs>', 'window end, exclusive (default: now)')
56
+ .option('--note <text>', 'where the figure came from, printed with the result (e.g. "dashboard, Oct 2026")')
57
+ .option('--declare-tier [tier]', 'record the billing fact this invoice EVIDENCES (paid|free|subscription); no value = the tier the invoice proves')
58
+ .option('--json', 'machine-readable JSON report')
59
+ .action(async (options) => {
60
+ await this.reconcileCost(options);
61
+ }));
62
+ // ── cost reprice ─────────────────────────────────────────────────────
63
+ costCmd.addCommand(new Command('reprice')
64
+ .description('Re-price legacy ledger rows with the ONE price source; drop test rows (dry run by default)')
65
+ .option('--apply', 'write the repaired ledger (a .bak is kept beside it)')
66
+ .option('--keep-test-rows', 're-price only: do NOT drop test-origin / fixture-id rows')
67
+ .option('--file <path>', 'a specific cost ledger file (default: the resolved profile ledger)')
68
+ .option('--json', 'machine-readable JSON report')
69
+ .action((options) => {
70
+ this.repriceLedger(options);
71
+ }));
26
72
  command.addCommand(costCmd);
27
73
  // ── history ──────────────────────────────────────────────────────────
28
74
  const historyCmd = new Command('history')
@@ -46,6 +92,221 @@ export class StatsCommand extends BaseCommand {
46
92
  const tracker = getCostTracker();
47
93
  console.log(`\n${tracker.formatSummary()}`);
48
94
  }
95
+ /**
96
+ * `stats cost reconcile` — the provider's own figure vs this ledger.
97
+ *
98
+ * Two rules the defaults encode:
99
+ * 1. A WINDOW IS MANDATORY. Comparing an all-time ledger to a monthly invoice
100
+ * is a meaningless subtraction that would print a delta as if it meant
101
+ * something. The default is the current calendar month, and the window is
102
+ * always printed so a partial (still-running) period can be judged as such.
103
+ * 2. THE PROVIDER IS VALIDATED. An unknown id would otherwise read as "no
104
+ * rows", i.e. a silent zero that looks like "you used nothing" rather than
105
+ * "you typed the wrong id".
106
+ */
107
+ async reconcileCost(options) {
108
+ const claim = this.buildClaim(options);
109
+ if (!claim)
110
+ return;
111
+ const report = reconcileEntries(claim, getCostTracker().getAllEntries());
112
+ // The record is the audit trail: "this was checked against the October
113
+ // invoice, and the gap was $0.03". A later check appends; nothing is erased.
114
+ recordReconciliation(report);
115
+ if (options.json) {
116
+ console.log(JSON.stringify(report, null, 2));
117
+ return;
118
+ }
119
+ console.log(`\n${formatReconciliation(report)}`);
120
+ console.log(` Recorded in ${reconciliationLedgerPath()}\n`);
121
+ if (options.declareTier !== undefined) {
122
+ this.applyTierDeclaration(claim.provider, options.declareTier, report);
123
+ }
124
+ else if (report.tierEvidence) {
125
+ // Evidence was found but recording it is the operator's call: writing a
126
+ // config field on the strength of a run they may not have intended as a
127
+ // declaration would be its own kind of assumption.
128
+ logger.info(` This invoice EVIDENCES billingTier=${report.tierEvidence.tier} for ${claim.provider}. Record it with:\n` +
129
+ ` nuvira stats cost reconcile --provider ${claim.provider} --amount ${claim.invoiceUsd} --declare-tier`);
130
+ }
131
+ }
132
+ /**
133
+ * Turn the CLI flags into an invoice claim, or explain why they cannot be.
134
+ * Returns null after printing the reason — the command exits without a report
135
+ * rather than reconciling against a window we had to guess.
136
+ */
137
+ buildClaim(options) {
138
+ const provider = (options.provider || '').trim();
139
+ const known = new Set([
140
+ ...CATALOG_PROVIDER_IDS,
141
+ ...Object.keys(this.configManager.getAll().providers || {}),
142
+ // Ids that appear in the ledger are real even when they are not in the
143
+ // catalog (a plugin provider, a sidecar gateway) — never reject those.
144
+ ...getCostTracker()
145
+ .getAllEntries()
146
+ .map((e) => e.provider),
147
+ ]);
148
+ if (!known.has(provider)) {
149
+ logger.error(`Unknown provider "${provider}". Known ids: ${[...known].sort().join(', ')}`);
150
+ logger.info(' Use the id the ledger records (the `By Provider` table of `nuvira stats cost`) — reconciling against a typo would report zero usage.');
151
+ return null;
152
+ }
153
+ const invoiceUsd = parseAmount(options.amount);
154
+ if (invoiceUsd === null) {
155
+ logger.error(`Invalid --amount "${options.amount}". Pass the provider's charge in USD, e.g. --amount 12.34 (0 is valid: nothing was charged).`);
156
+ return null;
157
+ }
158
+ const now = Date.now();
159
+ let since;
160
+ let until;
161
+ const parseWhen = (raw, label) => {
162
+ const asNumber = Number(raw);
163
+ if (Number.isFinite(asNumber) && asNumber > 0)
164
+ return asNumber;
165
+ const parsed = Date.parse(raw);
166
+ if (Number.isFinite(parsed))
167
+ return parsed;
168
+ logger.error(`Invalid ${label} "${raw}". Use an ISO date (2026-10-01) or epoch milliseconds.`);
169
+ return null;
170
+ };
171
+ if (options.since !== undefined) {
172
+ if (options.days !== undefined) {
173
+ logger.error('Use either --since or --days, not both — the window must be unambiguous.');
174
+ return null;
175
+ }
176
+ const parsed = parseWhen(options.since, '--since');
177
+ if (parsed === null)
178
+ return null;
179
+ since = parsed;
180
+ }
181
+ else if (options.days !== undefined) {
182
+ const days = Number(options.days);
183
+ if (!Number.isFinite(days) || days <= 0) {
184
+ logger.error(`Invalid --days "${options.days}". Pass a positive number of days.`);
185
+ return null;
186
+ }
187
+ since = now - days * DAY_MS;
188
+ }
189
+ else {
190
+ // The current calendar month, local time — what most providers bill in.
191
+ const start = new Date(now);
192
+ start.setDate(1);
193
+ start.setHours(0, 0, 0, 0);
194
+ since = start.getTime();
195
+ if (since > now) {
196
+ // A clock at the very start of the month: fall back to the last 24h so
197
+ // the window is never empty-by-construction.
198
+ since = now - DAY_MS;
199
+ }
200
+ }
201
+ if (options.until !== undefined) {
202
+ const parsed = parseWhen(options.until, '--until');
203
+ if (parsed === null)
204
+ return null;
205
+ until = parsed;
206
+ }
207
+ else {
208
+ // HALF-OPEN WINDOW, INCLUSIVE-AT-THE-END DEFAULT. A ledger row's timestamp
209
+ // is `Date.now()` at record time and so is this one, in the SAME
210
+ // millisecond resolution — so a default end of exactly `now` drops a row
211
+ // recorded a moment earlier. MEASURED while writing these tests: the row
212
+ // landed ON `until` and the report said "no ledger rows in this window".
213
+ // A boundary the operator did not state has no business excluding
214
+ // anything, so the default end is one millisecond past now. An EXPLICIT
215
+ // `--until` keeps the half-open meaning, so reconciling September and then
216
+ // October cannot count a midnight-boundary row twice.
217
+ until = now + 1;
218
+ }
219
+ if (until <= since) {
220
+ logger.error('The window end must be after its start.');
221
+ return null;
222
+ }
223
+ const startOfMonth = new Date(since);
224
+ if (until > now - 60_000 && startOfMonth.getDate() === 1) {
225
+ logger.info(' Note: the window runs up to now, so if this billing period has not closed the invoice may still be partial.');
226
+ }
227
+ return {
228
+ provider,
229
+ invoiceUsd,
230
+ since,
231
+ until,
232
+ ...(options.note ? { note: options.note } : {}),
233
+ };
234
+ }
235
+ /**
236
+ * Record the billing fact, but ONLY one the invoice does not contradict.
237
+ *
238
+ * This is the whole point of the exercise: `billingTier` moves the headline
239
+ * from "unverified" to "billed", so letting a user declare `free` while holding
240
+ * an invoice that charged $18 would reintroduce exactly the fabricated picture
241
+ * this change removed. A refusal is the useful answer.
242
+ */
243
+ applyTierDeclaration(provider, requested, report) {
244
+ let tier;
245
+ if (requested === true) {
246
+ if (!report.tierEvidence) {
247
+ logger.error('This reconciliation does not prove a billing tier — the CLI will not guess one. Pass it explicitly if you know it: --declare-tier paid|free|subscription');
248
+ return;
249
+ }
250
+ tier = report.tierEvidence.tier;
251
+ }
252
+ else {
253
+ const typed = asBillingTier(String(requested).trim().toLowerCase());
254
+ if (!BILLING_TIERS.includes(String(requested).trim().toLowerCase())) {
255
+ logger.error(`Invalid tier "${String(requested)}". Valid values: ${BILLING_TIERS.join(' | ')}.`);
256
+ return;
257
+ }
258
+ tier = typed;
259
+ }
260
+ // The invoice is evidence against these two claims; refuse rather than write
261
+ // a fact the provider's own bill contradicts.
262
+ if ((tier === 'free' || tier === 'subscription') && report.invoiceUsd > 0) {
263
+ logger.error(`Refusing to declare "${tier}": the invoice you entered charged $${report.invoiceUsd.toFixed(6)} for this window, so this key IS billed per call. Declare \`paid\` (metered), or \`subscription\` only if a flat plan covers these calls and the charge you entered is that plan's fee.`);
264
+ return;
265
+ }
266
+ if (tier === 'paid' && report.invoiceUsd === 0 && report.requests > 0) {
267
+ logger.error(`Refusing to declare "paid": the invoice you entered is $0 across ${report.requests} call(s), so nothing was charged. Declare \`free\`, or re-run with the correct invoice amount.`);
268
+ return;
269
+ }
270
+ this.configManager.save({ providers: { [provider]: { billingTier: tier } } });
271
+ // The classification context caches a config read for the process; drop it so
272
+ // any later read in this process (tests, a wrapper) sees the new fact.
273
+ resetPricingContextCache();
274
+ logger.success(`Recorded providers.${provider}.billingTier = ${tier} — evidenced by the invoice, not assumed.`);
275
+ if (tier === 'paid') {
276
+ logger.info(' List-priced calls for this provider now count as an estimated bill (the invoice remains the actual figure).');
277
+ }
278
+ else {
279
+ logger.info(' Calls for this provider are now $0 billed by evidence; their list price is reported as value, never spend.');
280
+ }
281
+ }
282
+ /**
283
+ * `stats cost reprice` — the explicit repair of the rows already on disk.
284
+ *
285
+ * Dry run by default: the ledger is the user's billing history, and a reprice
286
+ * changes every legacy figure in it. `--apply` writes once, after taking a
287
+ * backup, and the report says exactly what changed either way.
288
+ */
289
+ repriceLedger(options) {
290
+ const report = maintainCostLedger({
291
+ ...(options.file ? { file: options.file } : {}),
292
+ apply: options.apply === true,
293
+ pruneTestRows: options.keepTestRows !== true,
294
+ });
295
+ if (options.json) {
296
+ console.log(JSON.stringify(report, null, 2));
297
+ return;
298
+ }
299
+ console.log(`\n${formatMaintenanceReport(report)}`);
300
+ if (report.applied) {
301
+ logger.success(`Ledger re-priced: ${report.rows.repriced} row(s) repaired, ${report.rows.testOriginRemoved + report.rows.fixtureRemoved} test row(s) removed.`);
302
+ // A reprice rewrites the stored figures; anything cached for classification
303
+ // in this process is now stale by definition.
304
+ resetPricingContextCache();
305
+ }
306
+ else if (report.rows.repriced + report.rows.testOriginRemoved + report.rows.fixtureRemoved > 0) {
307
+ logger.info(' Re-run with --apply to write (a .bak is kept beside the ledger).');
308
+ }
309
+ }
49
310
  showHistoryStats() {
50
311
  const history = getChatHistory();
51
312
  const total = history.count();
@@ -75,4 +336,20 @@ export class StatsCommand extends BaseCommand {
75
336
  }
76
337
  }
77
338
  }
339
+ /**
340
+ * Parse a currency amount the way a user copies it off a dashboard: `$12.34`,
341
+ * `1,234.56`, ` 12.34 `. Returns null when it is not a non-negative finite
342
+ * number — a reconciled figure is money, and "NaN" must never reach a report.
343
+ */
344
+ export function parseAmount(raw) {
345
+ if (raw === undefined)
346
+ return null;
347
+ const cleaned = String(raw).replace(/[$,\s]/g, '');
348
+ if (cleaned === '')
349
+ return null;
350
+ const value = Number(cleaned);
351
+ if (!Number.isFinite(value) || value < 0)
352
+ return null;
353
+ return Math.round(value * 1e6) / 1e6;
354
+ }
78
355
  //# sourceMappingURL=stats.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"stats.js","sourceRoot":"","sources":["../../src/cli/stats.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAE5C,MAAM,OAAO,YAAa,SAAQ,WAAW;IAC3C,MAAM;QACJ,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,OAAO,CAAC;aACjC,WAAW,CAAC,yCAAyC,CAAC,CAAC;QAE1D,wEAAwE;QACxE,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,MAAM,CAAC;aAChC,WAAW,CAAC,gCAAgC,CAAC;aAC7C,MAAM,CAAC,SAAS,EAAE,8BAA8B,CAAC;aACjD,MAAM,CAAC,KAAK,EAAE,OAA6B,EAAE,EAAE;YAC9C,MAAM,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QAC/B,CAAC,CAAC,CAAC;QAEL,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;QAE5B,wEAAwE;QACxE,MAAM,UAAU,GAAG,IAAI,OAAO,CAAC,SAAS,CAAC;aACtC,WAAW,CAAC,sCAAsC,CAAC;aACnD,MAAM,CAAC,GAAG,EAAE;YACX,IAAI,CAAC,gBAAgB,EAAE,CAAC;QAC1B,CAAC,CAAC,CAAC;QAEL,OAAO,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC;QAE/B,0BAA0B;QAC1B,OAAO,CAAC,MAAM,CAAC,GAAG,EAAE;YAClB,IAAI,CAAC,YAAY,EAAE,CAAC;QACtB,CAAC,CAAC,CAAC;QAEH,OAAO,OAAO,CAAC;IACjB,CAAC;IAEO,KAAK,CAAC,QAAQ,CAAC,OAA6B;QAClD,IAAI,OAAO,EAAE,KAAK,EAAE,CAAC;YACnB,cAAc,EAAE,CAAC,KAAK,EAAE,CAAC;YACzB,MAAM,CAAC,OAAO,CAAC,6BAA6B,CAAC,CAAC;YAC9C,OAAO;QACT,CAAC;QAED,MAAM,OAAO,GAAG,cAAc,EAAE,CAAC;QACjC,OAAO,CAAC,GAAG,CAAC,KAAK,OAAO,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC;IAC9C,CAAC;IAEO,gBAAgB;QACtB,MAAM,OAAO,GAAG,cAAc,EAAE,CAAC;QACjC,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC;QAC9B,MAAM,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC;QAE5C,MAAM,CAAC,SAAS,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;QACtC,MAAM,CAAC,SAAS,CAAC,4BAA4B,CAAC,CAAC;QAC/C,MAAM,CAAC,SAAS,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;QAEtC,OAAO,CAAC,GAAG,CAAC,uBAAuB,KAAK,EAAE,CAAC,CAAC;QAC5C,OAAO,CAAC,GAAG,CAAC,sBAAsB,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;QAEnD,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACtB,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAC,CAAC;YACpC,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC;gBAC1C,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,oBAAoB,CAAC,OAAO,CAAC,CAAC,CAAC;YACrD,CAAC;QACH,CAAC;QAED,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAClB,CAAC;IAEO,YAAY;QAClB,IAAI,CAAC,gBAAgB,EAAE,CAAC;QAExB,MAAM,OAAO,GAAG,cAAc,EAAE,CAAC;QACjC,MAAM,OAAO,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;QAErC,IAAI,OAAO,CAAC,aAAa,GAAG,CAAC,EAAE,CAAC;YAC9B,OAAO,CAAC,GAAG,CAAC,KAAK,OAAO,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC;QAC9C,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,IAAI,CAAC,yDAAyD,CAAC,CAAC;QACzE,CAAC;IACH,CAAC;CACF"}
1
+ {"version":3,"file":"stats.js","sourceRoot":"","sources":["../../src/cli/stats.ts"],"names":[],"mappings":"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"}
@@ -16,6 +16,6 @@ export { attachShellCards, diffLineRole, formatDuration, gitDiffCard, shellCard,
16
16
  export type { ShellEndPayload, ToolCallCardInfo } from './cards.js';
17
17
  export { LiveAnswer } from './answer-view.js';
18
18
  export type { LiveAnswerProps } from './answer-view.js';
19
- export { AnswerStream, createCardSink, presentAnswer, presentCard, renderAnswer, shouldUseRichUi, } from './presenter.js';
20
- export type { CardSinkOverrides, PresenterOptions, StreamMeta } from './presenter.js';
19
+ export { AnswerStream, createCardSink, createRunStream, presentAnswer, presentCard, renderAnswer, shouldUseRichUi, THINKING_NOTE, } from './presenter.js';
20
+ export type { CardSinkOverrides, PresenterOptions, RunStream, RunStreamOverrides, StreamMeta, } from './presenter.js';
21
21
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/cli/ui/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AACnE,YAAY,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE9D,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC9D,YAAY,EAAE,KAAK,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAEtE,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAC1F,YAAY,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEpE,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACrF,YAAY,EAAE,aAAa,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAExE,OAAO,EACL,gBAAgB,EAChB,YAAY,EACZ,cAAc,EACd,WAAW,EACX,SAAS,EACT,YAAY,GACb,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEpE,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,YAAY,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAExD,OAAO,EACL,YAAY,EACZ,cAAc,EACd,aAAa,EACb,WAAW,EACX,YAAY,EACZ,eAAe,GAChB,MAAM,gBAAgB,CAAC;AACxB,YAAY,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/cli/ui/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AACnE,YAAY,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE9D,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC9D,YAAY,EAAE,KAAK,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAEtE,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAC1F,YAAY,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEpE,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACrF,YAAY,EAAE,aAAa,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAExE,OAAO,EACL,gBAAgB,EAChB,YAAY,EACZ,cAAc,EACd,WAAW,EACX,SAAS,EACT,YAAY,GACb,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEpE,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,YAAY,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAExD,OAAO,EACL,YAAY,EACZ,cAAc,EACd,eAAe,EACf,aAAa,EACb,WAAW,EACX,YAAY,EACZ,eAAe,EACf,aAAa,GACd,MAAM,gBAAgB,CAAC;AACxB,YAAY,EACV,iBAAiB,EACjB,gBAAgB,EAChB,SAAS,EACT,kBAAkB,EAClB,UAAU,GACX,MAAM,gBAAgB,CAAC"}
@@ -10,5 +10,5 @@ export { parseMarkdown, parseInline, inlineText, blocksToPlainText } from './mar
10
10
  export { renderMarkdown, renderBlocks, renderCard, visibleWidth } from './render.js';
11
11
  export { attachShellCards, diffLineRole, formatDuration, gitDiffCard, shellCard, toolCallCard, } from './cards.js';
12
12
  export { LiveAnswer } from './answer-view.js';
13
- export { AnswerStream, createCardSink, presentAnswer, presentCard, renderAnswer, shouldUseRichUi, } from './presenter.js';
13
+ export { AnswerStream, createCardSink, createRunStream, presentAnswer, presentCard, renderAnswer, shouldUseRichUi, THINKING_NOTE, } from './presenter.js';
14
14
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/cli/ui/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAGnE,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAG9D,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAG1F,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAGrF,OAAO,EACL,gBAAgB,EAChB,YAAY,EACZ,cAAc,EACd,WAAW,EACX,SAAS,EACT,YAAY,GACb,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAG9C,OAAO,EACL,YAAY,EACZ,cAAc,EACd,aAAa,EACb,WAAW,EACX,YAAY,EACZ,eAAe,GAChB,MAAM,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/cli/ui/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAGnE,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAG9D,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAG1F,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAGrF,OAAO,EACL,gBAAgB,EAChB,YAAY,EACZ,cAAc,EACd,WAAW,EACX,SAAS,EACT,YAAY,GACb,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAG9C,OAAO,EACL,YAAY,EACZ,cAAc,EACd,eAAe,EACf,aAAa,EACb,WAAW,EACX,YAAY,EACZ,eAAe,EACf,aAAa,GACd,MAAM,gBAAgB,CAAC"}
@@ -73,6 +73,49 @@ export interface StreamMeta {
73
73
  headerColor?: ThemeRole;
74
74
  note?: string;
75
75
  }
76
+ /** The live seams a RUNNING turn exposes to a view (all optional). */
77
+ export interface RunStreamOverrides extends CardSinkOverrides {
78
+ /** Verbatim model tokens: the answer as it is written. */
79
+ onToken?: (token: string) => void;
80
+ /** The step the run is on — the executor's own progress line. */
81
+ onProgress?: (line: string) => void;
82
+ }
83
+ /**
84
+ * The live EXECUTION view — what the turn is doing, while it does it.
85
+ *
86
+ * Wraps {@link AnswerStream} with the seams a whole turn has (not just the final
87
+ * answer):
88
+ *
89
+ * - `start()` mounts the frame IMMEDIATELY with a `thinking…` note, so the
90
+ * seconds spent waiting for the first token are visible instead of looking
91
+ * like a hang;
92
+ * - `onToken` streams the answer text as it is written (`begin()` on the first
93
+ * token, exactly like the chat surfaces' live preview);
94
+ * - `onToolCall` / `onGitDiff` / the shell bus archive a CARD per completed
95
+ * call, and put the RUNNING call in the note (`⚙ edit_file …`) so the
96
+ * execution half is visible too, not only the prose;
97
+ * - `onProgress` shows the executor's own progress line in the same note slot;
98
+ * - `commit(content)` unmounts the transient frame and prints the FINAL answer
99
+ * once. The live frame is a preview of a stream that may be discarded by the
100
+ * engine (a rejected step, a repair); the authoritative text is what the
101
+ * caller passes here, so it can never be printed twice.
102
+ *
103
+ * Off a real terminal (`pipes`, CI, `--json-events`, `NUVIRA_UI=plain`) this is
104
+ * INERT: no overrides, no frame, and `commit` falls through to `presentAnswer` —
105
+ * the exact bytes those consumers saw before this existed.
106
+ */
107
+ export interface RunStream {
108
+ overrides: RunStreamOverrides;
109
+ /** Mount the live view now, with a note (rich output only). */
110
+ start: (note?: string) => void;
111
+ /** Unmount the live view and print the FINAL answer exactly once. */
112
+ commit: (content: string) => void;
113
+ /** Detach the shell subscription and leave the terminal clean. */
114
+ stop: () => void;
115
+ }
116
+ /** The note shown while the model is working and nothing has arrived yet. */
117
+ export declare const THINKING_NOTE = "thinking\u2026";
118
+ export declare function createRunStream(meta?: StreamMeta, opts?: PresenterOptions): RunStream;
76
119
  /**
77
120
  * Live-streaming answer view.
78
121
  *
@@ -1 +1 @@
1
- {"version":3,"file":"presenter.d.ts","sourceRoot":"","sources":["../../../src/cli/ui/presenter.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"presenter.d.ts","sourceRoot":"","sources":["../../../src/cli/ui/presenter.ts"],"names":[],"mappings":"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"}
@@ -36,7 +36,13 @@ function resolve(opts) {
36
36
  const env = opts.env ?? process.env;
37
37
  const stream = opts.stream ?? process.stdout;
38
38
  const { name, theme } = resolveTheme(env);
39
- const width = opts.width ?? stream.columns ?? 80;
39
+ // A terminal that does not KNOW its size reports `columns` as 0 (or undefined),
40
+ // and `?? 80` only catches the undefined half: a 0 flowed all the way into the
41
+ // renderer, where `Painter` floors the width at 20 — measured on a PTY with no
42
+ // window size, a code box came out 14 columns wide with its lines cut mid-token
43
+ // ("const cap = 20 / 0;"). A width that cannot be read is 80, not 20.
44
+ const columns = opts.width ?? stream.columns;
45
+ const width = typeof columns === 'number' && columns > 0 ? columns : 80;
40
46
  const rich = shouldUseRichUi(opts);
41
47
  const color = opts.color ?? (name !== 'none' && !('NO_COLOR' in env));
42
48
  return { rich, theme: opts.theme ?? theme, themeName: name, width, color, stream };
@@ -115,6 +121,87 @@ export function createCardSink(opts = {}) {
115
121
  stop: attachShellCards(write),
116
122
  };
117
123
  }
124
+ /** The note shown while the model is working and nothing has arrived yet. */
125
+ export const THINKING_NOTE = 'thinking…';
126
+ export function createRunStream(meta = {}, opts = {}) {
127
+ if (!shouldUseRichUi(opts)) {
128
+ return {
129
+ overrides: {},
130
+ start: () => { },
131
+ // Byte-identical to the pre-layer path: the plain answer, written the way
132
+ // this CLI has always written it.
133
+ commit: (content) => {
134
+ presentAnswer(content, opts);
135
+ },
136
+ stop: () => { },
137
+ };
138
+ }
139
+ const stream = new AnswerStream(meta, opts);
140
+ let started = false;
141
+ let stopped = false;
142
+ const detachShell = attachShellCards((card) => stream.card(card));
143
+ /** Show a note without losing the frame (mounting it if it is not up yet). */
144
+ const noteFor = (note) => {
145
+ if (!started) {
146
+ started = true;
147
+ stream.begin({ ...meta, ...(note === undefined ? {} : { note }) });
148
+ return;
149
+ }
150
+ stream.setNote(note);
151
+ };
152
+ return {
153
+ overrides: {
154
+ onToken: (token) => {
155
+ if (!started) {
156
+ started = true;
157
+ stream.begin(meta);
158
+ }
159
+ // The text is the signal now — a stale `thinking…` beside streaming
160
+ // prose reads as a hang that is not happening.
161
+ stream.setNote(undefined);
162
+ stream.append(token);
163
+ },
164
+ onProgress: (line) => noteFor(line),
165
+ onToolCall: (phase, info) => {
166
+ if (phase === 'started') {
167
+ // The step's preamble is not the answer: clear it so the preview shows
168
+ // the call that is actually running (same rule as the chat REPL).
169
+ if (started)
170
+ stream.reset();
171
+ noteFor(`⚙ ${info.tool} …`);
172
+ return;
173
+ }
174
+ if (started)
175
+ stream.setNote(undefined);
176
+ stream.card(toolCallCard({
177
+ tool: info.tool,
178
+ ok: info.ok,
179
+ error: info.error,
180
+ result: info.result,
181
+ durationMs: info.durationMs,
182
+ args: info.args,
183
+ }));
184
+ },
185
+ onGitDiff: (payload) => stream.card(gitDiffCard(payload)),
186
+ },
187
+ start: (note) => noteFor(note ?? THINKING_NOTE),
188
+ commit: (content) => {
189
+ // Unmount WITHOUT archiving: the preview is not authoritative, and the
190
+ // caller's sanitized answer is written right below.
191
+ stream.finish({ archive: false });
192
+ presentAnswer(content, opts);
193
+ },
194
+ stop: () => {
195
+ // Idempotent on purpose: a caller stops on the failure path AND in a
196
+ // `finally`, and detaching the shell bus twice must not be a problem.
197
+ if (stopped)
198
+ return;
199
+ stopped = true;
200
+ detachShell();
201
+ stream.finish({ archive: false });
202
+ },
203
+ };
204
+ }
118
205
  /**
119
206
  * Live-streaming answer view.
120
207
  *