@iowarp/clio-coder 0.5.0 → 0.5.1

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 (132) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +12 -9
  3. package/dist/{acp-KS7ARGV6.js → acp-X3N7JDGS.js} +2 -2
  4. package/dist/{agents-YOGJ2YEJ.js → agents-UYGSMAWR.js} +3 -3
  5. package/dist/assets/codewiki.json +1 -1
  6. package/dist/{chunk-EOJPDNUP.js → chunk-3OODBJ45.js} +1 -1
  7. package/dist/{chunk-R7FGRZNF.js → chunk-4D7QM7S5.js} +2 -2
  8. package/dist/{chunk-X4CEFOSG.js → chunk-6B76U65H.js} +4 -4
  9. package/dist/{chunk-LTWH2ACR.js → chunk-7FHSA7VB.js} +5 -5
  10. package/dist/{chunk-GRHOXDKB.js → chunk-7KI4ZSEB.js} +2 -2
  11. package/dist/{chunk-LYJJ6546.js → chunk-7VNLPEUG.js} +6 -6
  12. package/dist/{chunk-D65E25JG.js → chunk-A7G7OUND.js} +2 -2
  13. package/dist/{chunk-3JLTRPN7.js → chunk-AKQ3N3L4.js} +2 -2
  14. package/dist/{chunk-AVSR2HKI.js → chunk-BIQLF3NZ.js} +3 -3
  15. package/dist/{chunk-D3KWFOUT.js → chunk-DGMY2DWE.js} +2 -2
  16. package/dist/{chunk-IN7DGBVS.js → chunk-FBXTHPNZ.js} +2 -2
  17. package/dist/{chunk-EX57VVWW.js → chunk-GGEEG7CD.js} +2 -2
  18. package/dist/{chunk-V35KZDUZ.js → chunk-GW2KVAVI.js} +2 -2
  19. package/dist/{chunk-SJGS3GDI.js → chunk-KIAUZGKF.js} +2 -2
  20. package/dist/{chunk-CBAQPTDU.js → chunk-LFYA3HYH.js} +2 -2
  21. package/dist/{chunk-4VDHLCF5.js → chunk-M5FPIZQW.js} +2 -2
  22. package/dist/{chunk-R2UUNSNQ.js → chunk-O3ZAF7UY.js} +18 -5
  23. package/dist/{chunk-QNUQ7K7D.js → chunk-OXWRE2QJ.js} +2 -2
  24. package/dist/{chunk-55RMDDFM.js → chunk-PGQG7ZMW.js} +2 -2
  25. package/dist/{chunk-HQVN7G4F.js → chunk-QM7HFPQQ.js} +4 -4
  26. package/dist/{chunk-ZR5YA6ZF.js → chunk-QTYRMBW6.js} +2 -2
  27. package/dist/{chunk-3LT34CAM.js → chunk-SAL2SBP4.js} +8 -8
  28. package/dist/{chunk-OJXOA6YU.js → chunk-TL6LNERH.js} +2 -2
  29. package/dist/{chunk-U6YTUVOX.js → chunk-VMTCZ2GR.js} +2 -2
  30. package/dist/{chunk-OQ2EOAHA.js → chunk-VWKR2NN5.js} +5 -5
  31. package/dist/{chunk-QDUUOZ3S.js → chunk-WAISUJNQ.js} +2 -2
  32. package/dist/{chunk-XDHUDE5K.js → chunk-XOH3SD7H.js} +3 -3
  33. package/dist/{chunk-62UK7AGW.js → chunk-ZNTGRDFA.js} +281 -14
  34. package/dist/cli/index.js +17 -17
  35. package/dist/{clio-K2PBVCEM.js → clio-Z3JTEL2I.js} +2 -2
  36. package/dist/{config-Q5J7UBAB.js → config-QYIRZHI7.js} +8 -8
  37. package/dist/{config-graph-P3555574.js → config-graph-TS3SGGQW.js} +8 -8
  38. package/dist/{configure-KIJ32ZO6.js → configure-OX7KVJTQ.js} +2 -2
  39. package/dist/{context-LFBQOSXT.js → context-2BNF6HTP.js} +12 -12
  40. package/dist/{context-WBH3KSVR.js → context-GTMUGAR4.js} +7 -7
  41. package/dist/{context-clear-YOHMQ6SQ.js → context-clear-FVJQBBL7.js} +7 -7
  42. package/dist/{context-working-set-MRAEVDNV.js → context-working-set-MOE72SD6.js} +2 -2
  43. package/dist/{detail-RHJOTGN4.js → detail-QZM3QAAU.js} +8 -8
  44. package/dist/{dispatch-runner-NC7PYNIO.js → dispatch-runner-2KSQO5FU.js} +8 -8
  45. package/dist/{doctor-S5KZ2GTN.js → doctor-2U6YR5NH.js} +3 -3
  46. package/dist/{eval-3D6M36N7.js → eval-ZKK77JWZ.js} +3 -3
  47. package/dist/{evidence-6IYN52ET.js → evidence-FMP7KZZ2.js} +7 -7
  48. package/dist/{evidence-JVECPI3F.js → evidence-PJTOE7JA.js} +7 -7
  49. package/dist/{evidence-XNVPPLVK.js → evidence-POSEQW4Q.js} +9 -9
  50. package/dist/{evolve-TLPUDJDM.js → evolve-JT6PAMZE.js} +7 -7
  51. package/dist/{fleet-WKVBF5SZ.js → fleet-4XR2ZEOF.js} +16 -16
  52. package/dist/{fleet-6RJWIOEO.js → fleet-6JJKNIMV.js} +8 -8
  53. package/dist/{fleet-inspect-236FZPRB.js → fleet-inspect-SWJMFVDP.js} +9 -9
  54. package/dist/{fleet-preflight-NZQHABWO.js → fleet-preflight-KSIHWQAU.js} +4 -4
  55. package/dist/{fleet-verify-E3RO6A5W.js → fleet-verify-QM6XU2KL.js} +7 -7
  56. package/dist/{fleet-view-KMZQY7YJ.js → fleet-view-RLKFHFCM.js} +8 -8
  57. package/dist/gui/reads-worker.js +4 -4
  58. package/dist/{init-DV5B6HQG.js → init-VBUPHABR.js} +11 -11
  59. package/dist/{interactive-OXGYZYAS.js → interactive-JCVBPVYT.js} +1597 -503
  60. package/dist/{inventory-OEGIL6PY.js → inventory-54G7FSVY.js} +8 -8
  61. package/dist/{mcp-2GSPYQ54.js → mcp-EFCWUPUR.js} +3 -3
  62. package/dist/{memory-J3STUUIK.js → memory-5BPQSSOP.js} +7 -7
  63. package/dist/{models-Y3GMFJ2V.js → models-R6HILX6X.js} +3 -3
  64. package/dist/{monitor-LHNH577J.js → monitor-HVRKSFEQ.js} +10 -10
  65. package/dist/{orchestrator-DTAPXULA.js → orchestrator-OR7V4YWK.js} +26 -26
  66. package/dist/{preload-OJBMAIXF.js → preload-KLXFOX2F.js} +7 -7
  67. package/dist/{run-H64MEVTA.js → run-UIDRUYAY.js} +16 -16
  68. package/dist/{skills-eval-CQ23MYQK.js → skills-eval-JDAXOPWC.js} +7 -7
  69. package/dist/{slash-commands-ROG3KIFJ.js → slash-commands-MNKQ5QP3.js} +4 -4
  70. package/dist/{startup-background-KDWCQMWT.js → startup-background-PJMWTKVO.js} +7 -7
  71. package/dist/{system-ZDW44ZGX.js → system-IVVZRKGI.js} +2 -2
  72. package/dist/{targets-AJWA6E7Z.js → targets-RFRUMCQ5.js} +4 -4
  73. package/dist/{terminal-lease-YFUSDPAD.js → terminal-lease-V2N2BIKQ.js} +2 -2
  74. package/dist/{usage-XSILDHKF.js → usage-LA33SG3R.js} +9 -9
  75. package/dist/{wiki-generate-RP22WP5K.js → wiki-generate-HL73TICA.js} +10 -10
  76. package/dist/worker/entry.js +4 -4
  77. package/docs/architecture/architecture.md +1 -0
  78. package/docs/architecture/context-engine.md +2 -2
  79. package/docs/architecture/observability.md +6 -4
  80. package/docs/architecture/prompt-envelope-and-tools.md +1 -1
  81. package/docs/architecture/session-lifecycle.md +1 -1
  82. package/docs/architecture/tui-design.md +12 -9
  83. package/docs/gui/parity/02-slash-and-surfaces.md +1 -1
  84. package/docs/guide/commands-and-modes.md +91 -5
  85. package/docs/guide/configuration-and-targets.md +2 -2
  86. package/docs/guide/proactive-memory.md +3 -3
  87. package/package.json +1 -1
  88. package/src/cli/usage.ts +2 -2
  89. package/src/domains/gateway/mcp/client.ts +1 -1
  90. package/src/domains/observability/background-memory-usage.ts +1 -1
  91. package/src/domains/observability/cost.ts +3 -3
  92. package/src/domains/observability/extension.ts +1 -1
  93. package/src/domains/observability/metrics.ts +1 -1
  94. package/src/domains/quota/anthropic-max-provider.ts +134 -0
  95. package/src/domains/quota/anthropic-usage.ts +220 -0
  96. package/src/domains/quota/antigravity-provider.ts +339 -0
  97. package/src/domains/quota/cache.ts +87 -0
  98. package/src/domains/quota/claude-code-provider.ts +169 -0
  99. package/src/domains/quota/codex-provider.ts +237 -0
  100. package/src/domains/quota/presentation.ts +240 -0
  101. package/src/domains/quota/registry.ts +23 -0
  102. package/src/domains/quota/service.ts +93 -0
  103. package/src/domains/quota/summary-feed.ts +86 -0
  104. package/src/domains/quota/types.ts +84 -0
  105. package/src/domains/session/usage.ts +2 -2
  106. package/src/engine/acp/commands.ts +1 -1
  107. package/src/entry/orchestrator.ts +2 -2
  108. package/src/interactive/chat-loop-messages.ts +2 -2
  109. package/src/interactive/chat-loop.ts +3 -3
  110. package/src/interactive/dispatch-board.ts +21 -3
  111. package/src/interactive/footer/dashboard.ts +13 -0
  112. package/src/interactive/footer/key-hints.ts +2 -2
  113. package/src/interactive/footer/pages.ts +55 -20
  114. package/src/interactive/footer-panel.ts +1 -1
  115. package/src/interactive/interactive-application.ts +5 -2
  116. package/src/interactive/interactive-input-runtime.ts +2 -2
  117. package/src/interactive/interactive-presentation.ts +15 -0
  118. package/src/interactive/interactive-slash-runtime.ts +2 -2
  119. package/src/interactive/interactive-tickers.ts +7 -1
  120. package/src/interactive/overlay-general-openers.ts +12 -8
  121. package/src/interactive/overlay-key-routing.ts +6 -9
  122. package/src/interactive/overlay-lifecycle.ts +10 -6
  123. package/src/interactive/overlay-session-lifecycle.ts +3 -3
  124. package/src/interactive/quota-view.ts +229 -0
  125. package/src/interactive/session-last-turn.ts +1 -1
  126. package/src/interactive/session-usage-reseed.ts +2 -2
  127. package/src/interactive/side-question.ts +2 -2
  128. package/src/interactive/slash-commands.ts +8 -8
  129. package/src/interactive/turn-context.ts +1 -1
  130. package/src/interactive/turn-prewarm.ts +1 -1
  131. package/src/interactive/{cost-overlay.ts → usage-overlay.ts} +121 -33
  132. package/src/interactive/welcome-dashboard.ts +26 -3
@@ -11,7 +11,7 @@ import {
11
11
  parseEditorBashCommand,
12
12
  unguardPastedEditorOperator,
13
13
  writeInputWedgeDump
14
- } from "./chunk-R2UUNSNQ.js";
14
+ } from "./chunk-O3ZAF7UY.js";
15
15
  import {
16
16
  OperatorExtensionRuntime
17
17
  } from "./chunk-C7AQTPCE.js";
@@ -68,6 +68,7 @@ import {
68
68
  isDispatchBoardRowSteerable,
69
69
  listRecentModels,
70
70
  listViewArtifacts,
71
+ localQuotaSnapshot,
71
72
  loopBlockedAuditReason,
72
73
  loopBlockedStopReason,
73
74
  mergeHandoffDecisions,
@@ -83,6 +84,8 @@ import {
83
84
  previewBudget,
84
85
  previewRows,
85
86
  providerErrorEvidence,
87
+ quotaMeter,
88
+ quotaSummaryLine,
86
89
  readWorkerReceiptFacts,
87
90
  reasoningFromTally,
88
91
  rehydrateChatPanelFromTurns,
@@ -93,13 +96,16 @@ import {
93
96
  renderDispatchActivity,
94
97
  renderEvictedTokensLine,
95
98
  renderHandoffDocument,
99
+ renderQuotaAccounts,
96
100
  renderRetryStatus,
97
101
  renderToolArguments,
98
102
  renderToolAwaitingApproval,
99
103
  renderToolExecution,
100
104
  renderToolPreview,
105
+ renderWorkerUsage,
101
106
  resolveFooterVerb,
102
107
  restartRequiredNotice,
108
+ routeWeeklyQuota,
103
109
  safeWorkerAnswerText,
104
110
  safetyBlockedNotice,
105
111
  sessionQuadrant,
@@ -119,12 +125,12 @@ import {
119
125
  workspaceQuadrant,
120
126
  zipColumnBlocks,
121
127
  zipColumns
122
- } from "./chunk-62UK7AGW.js";
128
+ } from "./chunk-ZNTGRDFA.js";
123
129
  import "./chunk-YQ6XEFVK.js";
124
130
  import {
125
131
  extractText,
126
132
  isSelfExplainingAbort
127
- } from "./chunk-ZR5YA6ZF.js";
133
+ } from "./chunk-QTYRMBW6.js";
128
134
  import "./chunk-ZJPORZTC.js";
129
135
  import {
130
136
  CLIO_APP_KEYBINDINGS,
@@ -199,22 +205,22 @@ import {
199
205
  parseSlashCommand,
200
206
  renderArgsSpec,
201
207
  workerAskedByModel
202
- } from "./chunk-3LT34CAM.js";
208
+ } from "./chunk-SAL2SBP4.js";
203
209
  import "./chunk-DGC6X6XR.js";
204
210
  import {
205
211
  resolveExtensionCommands
206
212
  } from "./chunk-RFCKPS2V.js";
207
213
  import "./chunk-QUIJZSDW.js";
208
214
  import "./chunk-UCINIRHW.js";
209
- import "./chunk-OJXOA6YU.js";
215
+ import "./chunk-TL6LNERH.js";
210
216
  import "./chunk-GGPF6LLE.js";
211
217
  import {
212
218
  formatUserTaskHandoff
213
219
  } from "./chunk-ISHUS7HC.js";
214
- import "./chunk-HQVN7G4F.js";
220
+ import "./chunk-QM7HFPQQ.js";
215
221
  import {
216
222
  appendOutOfTurnUsageRow
217
- } from "./chunk-V35KZDUZ.js";
223
+ } from "./chunk-GW2KVAVI.js";
218
224
  import "./chunk-HOEDA42N.js";
219
225
  import {
220
226
  SETTINGS_DESCRIPTIONS_BY_ID,
@@ -287,7 +293,7 @@ import {
287
293
  loadMemoryRecordsSync,
288
294
  proposeMemoryPromotion,
289
295
  renderCostAggregate
290
- } from "./chunk-LTWH2ACR.js";
296
+ } from "./chunk-7FHSA7VB.js";
291
297
  import "./chunk-PECBKCDK.js";
292
298
  import "./chunk-OV62D6KO.js";
293
299
  import "./chunk-I4ELN5BX.js";
@@ -309,7 +315,7 @@ import {
309
315
  ledgerUsageCalls,
310
316
  responseModelIdObservationCountsLabel,
311
317
  responseModelIdObservationFromRecord
312
- } from "./chunk-EOJPDNUP.js";
318
+ } from "./chunk-3OODBJ45.js";
313
319
  import "./chunk-BOBCCDJ6.js";
314
320
  import {
315
321
  describeLocalCapacity
@@ -334,7 +340,7 @@ import {
334
340
  import {
335
341
  filterEntriesToActivePath
336
342
  } from "./chunk-O5F25NIE.js";
337
- import "./chunk-U6YTUVOX.js";
343
+ import "./chunk-VMTCZ2GR.js";
338
344
  import "./chunk-SD7SHNDW.js";
339
345
  import "./chunk-7RET77VB.js";
340
346
  import "./chunk-YQ3GFEUY.js";
@@ -381,7 +387,7 @@ import {
381
387
  validateFleetGraph
382
388
  } from "./chunk-5LP5FNNR.js";
383
389
  import "./chunk-NVPXXW2H.js";
384
- import "./chunk-R7FGRZNF.js";
390
+ import "./chunk-4D7QM7S5.js";
385
391
  import "./chunk-DOUISWEE.js";
386
392
  import {
387
393
  retiredIntegrityVersionOf
@@ -396,7 +402,7 @@ import {
396
402
  decisionFactsForAnswer,
397
403
  openDetachedBatchViews,
398
404
  runBashCommand
399
- } from "./chunk-OQ2EOAHA.js";
405
+ } from "./chunk-VWKR2NN5.js";
400
406
  import {
401
407
  foldSessionTaskHistory,
402
408
  foldTaskBoard,
@@ -422,7 +428,7 @@ import {
422
428
  requestExecutionRole,
423
429
  withAttemptRole
424
430
  } from "./chunk-BRCOSE7O.js";
425
- import "./chunk-55RMDDFM.js";
431
+ import "./chunk-PGQG7ZMW.js";
426
432
  import "./chunk-L4N3DWNW.js";
427
433
  import "./chunk-TDGYTWM6.js";
428
434
  import "./chunk-CTFPFW3H.js";
@@ -480,6 +486,7 @@ import {
480
486
  resolveProviderReference
481
487
  } from "./chunk-SYF7FVBL.js";
482
488
  import {
489
+ openAuthStorage,
483
490
  targetRequiresAuth
484
491
  } from "./chunk-Z4GLJ6J4.js";
485
492
  import "./chunk-VNBICSS2.js";
@@ -3188,273 +3195,6 @@ function openContextOverlay(tui, getLedger, options) {
3188
3195
  };
3189
3196
  }
3190
3197
 
3191
- // src/interactive/cost-overlay.ts
3192
- init_esm_shims();
3193
- var DEFAULT_CONTENT_WIDTH2 = 80;
3194
- var COST_OVERLAY_WIDTH = DEFAULT_CONTENT_WIDTH2 + 4;
3195
- function formatTokens2(n) {
3196
- return n.toLocaleString("en-US");
3197
- }
3198
- function aggregateCostEntries(entries) {
3199
- const grouped = /* @__PURE__ */ new Map();
3200
- for (const entry of entries) {
3201
- const key = `${entry.providerId}::${entry.attributedModelId}`;
3202
- const existing = grouped.get(key);
3203
- if (existing) {
3204
- existing.row.runs += 1;
3205
- existing.row.tokens += entry.tokens;
3206
- existing.row.input += entry.input;
3207
- existing.row.output += entry.output;
3208
- existing.row.cacheRead += entry.cacheRead;
3209
- existing.row.cacheWrite += entry.cacheWrite;
3210
- existing.row.reasoningTokens += entry.reasoningTokens;
3211
- existing.row.apiCalls += entry.apiCalls ?? 1;
3212
- if (entry.label === "side-question") existing.row.sideQuestions += 1;
3213
- if (entry.label === "handoff") existing.row.handoffs += 1;
3214
- if (entry.label === "prewarm") existing.row.prewarms += 1;
3215
- if (entry.label === "background-memory") existing.row.backgroundMemory += 1;
3216
- if (entry.label === "failed-compaction") existing.row.failedCompaction += 1;
3217
- for (const requestedModelId of entry.requestedModelIds) existing.requestedModelIds.add(requestedModelId);
3218
- addResponseModelIdObservationCounts(
3219
- existing.responseModelIdObservationCounts,
3220
- entry.responseModelIdObservationCounts
3221
- );
3222
- existing.entries.push(entry);
3223
- continue;
3224
- }
3225
- grouped.set(key, {
3226
- row: {
3227
- providerId: entry.providerId,
3228
- attributedModelId: entry.attributedModelId,
3229
- runs: 1,
3230
- tokens: entry.tokens,
3231
- input: entry.input,
3232
- output: entry.output,
3233
- cacheRead: entry.cacheRead,
3234
- cacheWrite: entry.cacheWrite,
3235
- reasoningTokens: entry.reasoningTokens,
3236
- apiCalls: entry.apiCalls ?? 1,
3237
- sideQuestions: entry.label === "side-question" ? 1 : 0,
3238
- handoffs: entry.label === "handoff" ? 1 : 0,
3239
- prewarms: entry.label === "prewarm" ? 1 : 0,
3240
- backgroundMemory: entry.label === "background-memory" ? 1 : 0,
3241
- failedCompaction: entry.label === "failed-compaction" ? 1 : 0
3242
- },
3243
- requestedModelIds: new Set(entry.requestedModelIds),
3244
- responseModelIdObservationCounts: { ...entry.responseModelIdObservationCounts },
3245
- entries: [entry]
3246
- });
3247
- }
3248
- const rows = Array.from(grouped.values(), ({ row: row2, entries: entries2, requestedModelIds, responseModelIdObservationCounts }) => ({
3249
- ...row2,
3250
- requestedModelIds: [...requestedModelIds].sort(),
3251
- responseModelIdObservationCounts,
3252
- cost: aggregateCostAmounts(entries2.map((entry) => ({ usd: entry.usd, provenance: entry.provenance })))
3253
- }));
3254
- rows.sort((a, b) => {
3255
- if (a.providerId !== b.providerId) return a.providerId < b.providerId ? -1 : 1;
3256
- if (a.attributedModelId !== b.attributedModelId) return a.attributedModelId < b.attributedModelId ? -1 : 1;
3257
- return 0;
3258
- });
3259
- return rows;
3260
- }
3261
- function sumRows(rows) {
3262
- return rows.reduce(
3263
- (acc, row2) => ({
3264
- runs: acc.runs + row2.runs,
3265
- tokens: acc.tokens + row2.tokens,
3266
- input: acc.input + row2.input,
3267
- output: acc.output + row2.output,
3268
- cacheRead: acc.cacheRead + row2.cacheRead,
3269
- cacheWrite: acc.cacheWrite + row2.cacheWrite,
3270
- reasoningTokens: acc.reasoningTokens + row2.reasoningTokens,
3271
- apiCalls: acc.apiCalls + row2.apiCalls,
3272
- sideQuestions: acc.sideQuestions + row2.sideQuestions,
3273
- handoffs: acc.handoffs + row2.handoffs,
3274
- prewarms: acc.prewarms + row2.prewarms,
3275
- backgroundMemory: acc.backgroundMemory + row2.backgroundMemory,
3276
- failedCompaction: acc.failedCompaction + row2.failedCompaction
3277
- }),
3278
- {
3279
- runs: 0,
3280
- tokens: 0,
3281
- input: 0,
3282
- output: 0,
3283
- cacheRead: 0,
3284
- cacheWrite: 0,
3285
- reasoningTokens: 0,
3286
- apiCalls: 0,
3287
- sideQuestions: 0,
3288
- handoffs: 0,
3289
- prewarms: 0,
3290
- backgroundMemory: 0,
3291
- failedCompaction: 0
3292
- }
3293
- );
3294
- }
3295
- function cacheReadValue(cacheRead, apiCalls) {
3296
- if (apiCalls > 1 && cacheRead > 0) {
3297
- return [formatTokens2(cacheRead), `(avg/call ${formatTokens2(Math.round(cacheRead / apiCalls))})`];
3298
- }
3299
- return [formatTokens2(cacheRead)];
3300
- }
3301
- function reasoningValue(reasoningTokens) {
3302
- return [formatTokens2(reasoningTokens), "provider-reported only"];
3303
- }
3304
- function kvBlock(entries) {
3305
- const theme2 = clioTheme();
3306
- const keyWidth = entries.reduce((max, [key]) => Math.max(max, key.length), 0);
3307
- const valueWidth = entries.reduce((max, [, value]) => Math.max(max, value.length), 0);
3308
- return entries.map(([key, value, annotation]) => {
3309
- const row2 = `${theme2.fg("dim", key.padEnd(keyWidth))} ${theme2.fg("muted", value.padStart(valueWidth))}`;
3310
- return annotation ? `${row2} ${theme2.fg("dim", annotation)}` : row2;
3311
- });
3312
- }
3313
- function summaryBlock(totalCost, totalTokens, rows, promptCache) {
3314
- const totals = sumRows(rows);
3315
- const resolvedTotal = totalTokens > 0 ? totalTokens : totals.tokens;
3316
- const cost = formatCostAggregate(totalCost);
3317
- const cacheRows = promptCache !== null && hasPromptCacheTelemetry(promptCache) ? [
3318
- [
3319
- "uncached prefill",
3320
- promptCache.uncachedPrefillTokens === null ? "not reported" : formatTokens2(promptCache.uncachedPrefillTokens)
3321
- ],
3322
- [
3323
- "cache verdicts",
3324
- `hot ${promptCache.verdictCounts.hot} \xB7 partial ${promptCache.verdictCounts.partial} \xB7 cold ${promptCache.verdictCounts.cold} \xB7 small ${promptCache.verdictCounts.small} \xB7 unknown ${promptCache.verdictCounts.unknown}`
3325
- ]
3326
- ] : [];
3327
- return kvBlock([
3328
- [
3329
- "turns",
3330
- formatTokens2(
3331
- totals.runs - totals.sideQuestions - totals.handoffs - totals.prewarms - totals.backgroundMemory - totals.failedCompaction
3332
- )
3333
- ],
3334
- ["model calls", formatTokens2(totals.apiCalls)],
3335
- ...totals.sideQuestions > 0 ? [["side questions", formatTokens2(totals.sideQuestions)]] : [],
3336
- ...totals.handoffs > 0 ? [["handoffs", formatTokens2(totals.handoffs)]] : [],
3337
- ...totals.prewarms > 0 ? [["pre-warms", formatTokens2(totals.prewarms)]] : [],
3338
- ...totals.backgroundMemory > 0 ? [["memory steps", formatTokens2(totals.backgroundMemory)]] : [],
3339
- ...totals.failedCompaction > 0 ? [
3340
- ["failed compaction calls", formatTokens2(totals.failedCompaction)],
3341
- ["compaction usage", "known subtotals; missing usage is unknown"]
3342
- ] : [],
3343
- ...cost === null ? [] : [["cost", cost]],
3344
- ["input", formatTokens2(totals.input)],
3345
- ["output", formatTokens2(totals.output)],
3346
- ["reasoning", ...reasoningValue(totals.reasoningTokens)],
3347
- ["cache read", ...cacheReadValue(totals.cacheRead, totals.apiCalls)],
3348
- ["cache write", formatTokens2(totals.cacheWrite)],
3349
- ...cacheRows,
3350
- ["processed", `${formatTokens2(resolvedTotal)} tokens`]
3351
- ]);
3352
- }
3353
- function modelBlock(row2) {
3354
- const cost = formatCostAggregate(row2.cost);
3355
- return kvBlock([
3356
- ["requested model ids", row2.requestedModelIds.join(", ")],
3357
- ["response model id observation", responseModelIdObservationCountsLabel(row2.responseModelIdObservationCounts)],
3358
- [
3359
- "turns",
3360
- formatTokens2(
3361
- row2.runs - row2.sideQuestions - row2.handoffs - row2.prewarms - row2.backgroundMemory - row2.failedCompaction
3362
- )
3363
- ],
3364
- ["model calls", formatTokens2(row2.apiCalls)],
3365
- ...row2.sideQuestions > 0 ? [["side questions", formatTokens2(row2.sideQuestions)]] : [],
3366
- ...row2.handoffs > 0 ? [["handoffs", formatTokens2(row2.handoffs)]] : [],
3367
- ...row2.prewarms > 0 ? [["pre-warms", formatTokens2(row2.prewarms)]] : [],
3368
- ...row2.backgroundMemory > 0 ? [["memory steps", formatTokens2(row2.backgroundMemory)]] : [],
3369
- ...row2.failedCompaction > 0 ? [["failed compaction calls", formatTokens2(row2.failedCompaction)]] : [],
3370
- ...cost === null ? [] : [["cost", cost]],
3371
- ["input", formatTokens2(row2.input)],
3372
- ["output", formatTokens2(row2.output)],
3373
- ["reasoning", ...reasoningValue(row2.reasoningTokens)],
3374
- ["cache read", ...cacheReadValue(row2.cacheRead, row2.apiCalls)],
3375
- ["cache write", formatTokens2(row2.cacheWrite)],
3376
- ["processed", `${formatTokens2(row2.tokens)} tokens`]
3377
- ]);
3378
- }
3379
- function formatCostOverlayBodyLines(totalCost, totalTokens, rows, contentWidth, promptCache = null) {
3380
- const theme2 = clioTheme();
3381
- const lines = [];
3382
- for (const line of summaryBlock(totalCost, totalTokens, rows, promptCache)) {
3383
- lines.push(line);
3384
- }
3385
- lines.push(rule(theme2, contentWidth));
3386
- if (rows.length === 0) {
3387
- lines.push(theme2.fg("muted", "no token usage recorded for this session"));
3388
- } else {
3389
- for (const [index, row2] of rows.entries()) {
3390
- if (index > 0) lines.push("");
3391
- lines.push(theme2.style("accent", `${row2.providerId} \xB7 attributed model ${row2.attributedModelId}`, { bold: true }));
3392
- for (const line of modelBlock(row2)) {
3393
- lines.push(line);
3394
- }
3395
- }
3396
- }
3397
- return lines;
3398
- }
3399
- function buildCostSnapshot(observability, sessionId, snapshot, getSessionEntries) {
3400
- const entries = observability.costEntries();
3401
- const rows = aggregateCostEntries(entries);
3402
- const totalTokens = rows.reduce((sum, r) => sum + r.tokens, 0);
3403
- return {
3404
- sessionId,
3405
- totalCost: snapshot?.session.cost ?? observability.sessionCostSummary(),
3406
- totalTokens,
3407
- rows,
3408
- promptCache: getSessionEntries ? foldPromptCacheTelemetry(getSessionEntries()) : null
3409
- };
3410
- }
3411
- var CostOverlayBody = class {
3412
- constructor(getSnapshot) {
3413
- this.getSnapshot = getSnapshot;
3414
- }
3415
- getSnapshot;
3416
- render(width) {
3417
- const contentWidth = Math.max(1, Math.floor(width));
3418
- const snapshot = this.getSnapshot();
3419
- return formatCostOverlayBodyLines(
3420
- snapshot.totalCost,
3421
- snapshot.totalTokens,
3422
- snapshot.rows,
3423
- contentWidth,
3424
- snapshot.promptCache
3425
- );
3426
- }
3427
- invalidate() {
3428
- }
3429
- };
3430
- function openCostOverlay(tui, observability, options) {
3431
- const sessionId = options?.sessionId ?? null;
3432
- let latest = observability.snapshot();
3433
- const body = new CostOverlayBody(
3434
- () => buildCostSnapshot(observability, sessionId, latest, options?.getSessionEntries)
3435
- );
3436
- const handle = showClioOverlayFrame(tui, body, {
3437
- anchor: "center",
3438
- width: COST_OVERLAY_WIDTH,
3439
- // Not derived from the title: that one carries the session id.
3440
- markerId: "cost",
3441
- title: sessionId && sessionId.length > 0 ? `Session usage (${sessionId})` : "Session usage",
3442
- footerHint: buildHint([])
3443
- });
3444
- const unsubscribe = observability.subscribe((snapshot) => {
3445
- latest = snapshot;
3446
- body.invalidate();
3447
- tui.requestRender();
3448
- });
3449
- return {
3450
- ...handle,
3451
- hide() {
3452
- unsubscribe();
3453
- handle.hide();
3454
- }
3455
- };
3456
- }
3457
-
3458
3198
  // src/interactive/fleet-run-preview.ts
3459
3199
  init_esm_shims();
3460
3200
  function defaultLoad(workspaceRoot, name) {
@@ -4338,9 +4078,9 @@ function openListOverlay(tui, options) {
4338
4078
  }
4339
4079
 
4340
4080
  // src/interactive/memory-overlay.ts
4341
- var DEFAULT_CONTENT_WIDTH3 = 96;
4081
+ var DEFAULT_CONTENT_WIDTH2 = 96;
4342
4082
  var REFRESH_MS = 1e3;
4343
- var MEMORY_OVERLAY_WIDTH = DEFAULT_CONTENT_WIDTH3 + 4;
4083
+ var MEMORY_OVERLAY_WIDTH = DEFAULT_CONTENT_WIDTH2 + 4;
4344
4084
  var EMPTY_MESSAGE = "no approved lessons, task-bank entries, or memory steps captured yet.";
4345
4085
  function formatMemoryStatusLine(status, contentWidth) {
4346
4086
  const theme2 = clioTheme();
@@ -4707,8 +4447,8 @@ function openContextResetOverlay(tui, deps) {
4707
4447
 
4708
4448
  // src/interactive/overlays/decisions.ts
4709
4449
  init_esm_shims();
4710
- var DEFAULT_CONTENT_WIDTH4 = 88;
4711
- var DECISIONS_OVERLAY_WIDTH = DEFAULT_CONTENT_WIDTH4 + 4;
4450
+ var DEFAULT_CONTENT_WIDTH3 = 88;
4451
+ var DECISIONS_OVERLAY_WIDTH = DEFAULT_CONTENT_WIDTH3 + 4;
4712
4452
  function formatDecisionCorrectionTurn(selection, correction) {
4713
4453
  return `Decision "${selection.label}" (previously: ${selection.value}) is superseded by the operator. New direction: ${correction}. Acknowledge and adjust the plan.`;
4714
4454
  }
@@ -4772,7 +4512,7 @@ function decisionLines(decision, selected, expanded, width) {
4772
4512
  }
4773
4513
  return lines;
4774
4514
  }
4775
- function formatDecisionsOverlayBodyLines(interviews, selectedIndex = 0, expandedKey = null, contentWidth = DEFAULT_CONTENT_WIDTH4, now = Date.now()) {
4515
+ function formatDecisionsOverlayBodyLines(interviews, selectedIndex = 0, expandedKey = null, contentWidth = DEFAULT_CONTENT_WIDTH3, now = Date.now()) {
4776
4516
  const width = Math.max(1, Math.floor(contentWidth));
4777
4517
  const theme2 = clioTheme();
4778
4518
  if (interviews.length === 0) {
@@ -5171,9 +4911,9 @@ function openSideQuestionOverlay(tui, options) {
5171
4911
  // src/interactive/tasks-overlay.ts
5172
4912
  init_esm_shims();
5173
4913
  import { isAbsolute, relative as relative3, resolve } from "node:path";
5174
- var DEFAULT_CONTENT_WIDTH5 = 84;
4914
+ var DEFAULT_CONTENT_WIDTH4 = 84;
5175
4915
  var REFRESH_MS2 = 1e3;
5176
- var TASKS_OVERLAY_WIDTH = DEFAULT_CONTENT_WIDTH5 + 4;
4916
+ var TASKS_OVERLAY_WIDTH = DEFAULT_CONTENT_WIDTH4 + 4;
5177
4917
  var STATUS_PRESENTATION = {
5178
4918
  pending: { glyph: GLYPH.queued, token: "dim", label: "pending" },
5179
4919
  active: { glyph: GLYPH.running, token: "accent", label: "active" },
@@ -5243,7 +4983,7 @@ function taskReceiptRows(task, width) {
5243
4983
  }
5244
4984
  return [];
5245
4985
  }
5246
- function formatTasksOverlayBodyLines(board, contentWidth = DEFAULT_CONTENT_WIDTH5, selectedTaskId) {
4986
+ function formatTasksOverlayBodyLines(board, contentWidth = DEFAULT_CONTENT_WIDTH4, selectedTaskId) {
5247
4987
  const theme2 = clioTheme();
5248
4988
  const width = Math.max(1, Math.floor(contentWidth));
5249
4989
  if (!board || board.tasks.length === 0) {
@@ -5326,7 +5066,7 @@ var USER_TASK_PRESENTATION = {
5326
5066
  done: { glyph: GLYPH.ok, token: "success" },
5327
5067
  dropped: { glyph: GLYPH.cancelled, token: "dim" }
5328
5068
  };
5329
- function formatCompositeTasksOverlayBodyLines(state, contentWidth = DEFAULT_CONTENT_WIDTH5) {
5069
+ function formatCompositeTasksOverlayBodyLines(state, contentWidth = DEFAULT_CONTENT_WIDTH4) {
5330
5070
  const width = Math.max(1, Math.floor(contentWidth));
5331
5071
  const theme2 = clioTheme();
5332
5072
  const rows = selectableRows2(state);
@@ -5535,46 +5275,397 @@ var TasksOverlayBody = class {
5535
5275
  } catch (error) {
5536
5276
  this.status = error instanceof Error ? error.message : String(error);
5537
5277
  }
5538
- this.requestRender();
5539
- };
5540
- this.addInput = input;
5541
- this.status = "";
5542
- this.requestRender();
5543
- }
5544
- mutateUserTask(status, id, mutation) {
5545
- if (!mutation) {
5546
- this.status = "Operator task actions are unavailable.";
5547
- this.requestRender();
5548
- return;
5278
+ this.requestRender();
5279
+ };
5280
+ this.addInput = input;
5281
+ this.status = "";
5282
+ this.requestRender();
5283
+ }
5284
+ mutateUserTask(status, id, mutation) {
5285
+ if (!mutation) {
5286
+ this.status = "Operator task actions are unavailable.";
5287
+ this.requestRender();
5288
+ return;
5289
+ }
5290
+ try {
5291
+ mutation(id);
5292
+ this.refreshUserTasks();
5293
+ this.status = `Operator task ${id} ${status}.`;
5294
+ } catch (error) {
5295
+ this.status = error instanceof Error ? error.message : String(error);
5296
+ }
5297
+ this.requestRender();
5298
+ }
5299
+ requestRender() {
5300
+ this.options.requestRender?.();
5301
+ }
5302
+ };
5303
+ function openTasksOverlay(tui, getBoard, options = {}) {
5304
+ const body = new TasksOverlayBody(getBoard, options);
5305
+ const handle = showClioOverlayFrame(tui, body, {
5306
+ anchor: "center",
5307
+ width: TASKS_OVERLAY_WIDTH,
5308
+ markerId: "tasks",
5309
+ title: () => "Tasks",
5310
+ footerHint: () => body.footerHint()
5311
+ });
5312
+ const timer = setInterval(() => tui.requestRender(), REFRESH_MS2);
5313
+ timer.unref?.();
5314
+ return {
5315
+ ...handle,
5316
+ hide() {
5317
+ clearInterval(timer);
5318
+ handle.hide();
5319
+ }
5320
+ };
5321
+ }
5322
+
5323
+ // src/interactive/usage-overlay.ts
5324
+ init_esm_shims();
5325
+ var DEFAULT_CONTENT_WIDTH5 = 104;
5326
+ var USAGE_OVERLAY_WIDTH = DEFAULT_CONTENT_WIDTH5 + 4;
5327
+ function formatTokens2(n) {
5328
+ return n.toLocaleString("en-US");
5329
+ }
5330
+ function aggregateCostEntries(entries) {
5331
+ const grouped = /* @__PURE__ */ new Map();
5332
+ for (const entry of entries) {
5333
+ const key = `${entry.providerId}::${entry.attributedModelId}`;
5334
+ const existing = grouped.get(key);
5335
+ if (existing) {
5336
+ existing.row.runs += 1;
5337
+ existing.row.tokens += entry.tokens;
5338
+ existing.row.input += entry.input;
5339
+ existing.row.output += entry.output;
5340
+ existing.row.cacheRead += entry.cacheRead;
5341
+ existing.row.cacheWrite += entry.cacheWrite;
5342
+ existing.row.reasoningTokens += entry.reasoningTokens;
5343
+ existing.row.apiCalls += entry.apiCalls ?? 1;
5344
+ if (entry.label === "side-question") existing.row.sideQuestions += 1;
5345
+ if (entry.label === "handoff") existing.row.handoffs += 1;
5346
+ if (entry.label === "prewarm") existing.row.prewarms += 1;
5347
+ if (entry.label === "background-memory") existing.row.backgroundMemory += 1;
5348
+ if (entry.label === "failed-compaction") existing.row.failedCompaction += 1;
5349
+ for (const requestedModelId of entry.requestedModelIds) existing.requestedModelIds.add(requestedModelId);
5350
+ addResponseModelIdObservationCounts(
5351
+ existing.responseModelIdObservationCounts,
5352
+ entry.responseModelIdObservationCounts
5353
+ );
5354
+ existing.entries.push(entry);
5355
+ continue;
5356
+ }
5357
+ grouped.set(key, {
5358
+ row: {
5359
+ providerId: entry.providerId,
5360
+ attributedModelId: entry.attributedModelId,
5361
+ runs: 1,
5362
+ tokens: entry.tokens,
5363
+ input: entry.input,
5364
+ output: entry.output,
5365
+ cacheRead: entry.cacheRead,
5366
+ cacheWrite: entry.cacheWrite,
5367
+ reasoningTokens: entry.reasoningTokens,
5368
+ apiCalls: entry.apiCalls ?? 1,
5369
+ sideQuestions: entry.label === "side-question" ? 1 : 0,
5370
+ handoffs: entry.label === "handoff" ? 1 : 0,
5371
+ prewarms: entry.label === "prewarm" ? 1 : 0,
5372
+ backgroundMemory: entry.label === "background-memory" ? 1 : 0,
5373
+ failedCompaction: entry.label === "failed-compaction" ? 1 : 0
5374
+ },
5375
+ requestedModelIds: new Set(entry.requestedModelIds),
5376
+ responseModelIdObservationCounts: { ...entry.responseModelIdObservationCounts },
5377
+ entries: [entry]
5378
+ });
5379
+ }
5380
+ const rows = Array.from(grouped.values(), ({ row: row2, entries: entries2, requestedModelIds, responseModelIdObservationCounts }) => ({
5381
+ ...row2,
5382
+ requestedModelIds: [...requestedModelIds].sort(),
5383
+ responseModelIdObservationCounts,
5384
+ cost: aggregateCostAmounts(entries2.map((entry) => ({ usd: entry.usd, provenance: entry.provenance })))
5385
+ }));
5386
+ rows.sort((a, b) => {
5387
+ if (a.providerId !== b.providerId) return a.providerId < b.providerId ? -1 : 1;
5388
+ if (a.attributedModelId !== b.attributedModelId) return a.attributedModelId < b.attributedModelId ? -1 : 1;
5389
+ return 0;
5390
+ });
5391
+ return rows;
5392
+ }
5393
+ function sumRows(rows) {
5394
+ return rows.reduce(
5395
+ (acc, row2) => ({
5396
+ runs: acc.runs + row2.runs,
5397
+ tokens: acc.tokens + row2.tokens,
5398
+ input: acc.input + row2.input,
5399
+ output: acc.output + row2.output,
5400
+ cacheRead: acc.cacheRead + row2.cacheRead,
5401
+ cacheWrite: acc.cacheWrite + row2.cacheWrite,
5402
+ reasoningTokens: acc.reasoningTokens + row2.reasoningTokens,
5403
+ apiCalls: acc.apiCalls + row2.apiCalls,
5404
+ sideQuestions: acc.sideQuestions + row2.sideQuestions,
5405
+ handoffs: acc.handoffs + row2.handoffs,
5406
+ prewarms: acc.prewarms + row2.prewarms,
5407
+ backgroundMemory: acc.backgroundMemory + row2.backgroundMemory,
5408
+ failedCompaction: acc.failedCompaction + row2.failedCompaction
5409
+ }),
5410
+ {
5411
+ runs: 0,
5412
+ tokens: 0,
5413
+ input: 0,
5414
+ output: 0,
5415
+ cacheRead: 0,
5416
+ cacheWrite: 0,
5417
+ reasoningTokens: 0,
5418
+ apiCalls: 0,
5419
+ sideQuestions: 0,
5420
+ handoffs: 0,
5421
+ prewarms: 0,
5422
+ backgroundMemory: 0,
5423
+ failedCompaction: 0
5424
+ }
5425
+ );
5426
+ }
5427
+ function cacheReadValue(cacheRead, apiCalls) {
5428
+ if (apiCalls > 1 && cacheRead > 0) {
5429
+ return [formatTokens2(cacheRead), `(avg/call ${formatTokens2(Math.round(cacheRead / apiCalls))})`];
5430
+ }
5431
+ return [formatTokens2(cacheRead)];
5432
+ }
5433
+ function reasoningValue(reasoningTokens) {
5434
+ return [formatTokens2(reasoningTokens), "provider-reported only"];
5435
+ }
5436
+ function kvBlock(entries) {
5437
+ const theme2 = clioTheme();
5438
+ const keyWidth = entries.reduce((max, [key]) => Math.max(max, key.length), 0);
5439
+ const valueWidth = entries.reduce((max, [, value]) => Math.max(max, value.length), 0);
5440
+ return entries.map(([key, value, annotation]) => {
5441
+ const row2 = `${theme2.fg("dim", key.padEnd(keyWidth))} ${theme2.fg("muted", value.padStart(valueWidth))}`;
5442
+ return annotation ? `${row2} ${theme2.fg("dim", annotation)}` : row2;
5443
+ });
5444
+ }
5445
+ function summaryBlock(totalCost, totalTokens, rows, promptCache) {
5446
+ const totals = sumRows(rows);
5447
+ const resolvedTotal = totalTokens > 0 ? totalTokens : totals.tokens;
5448
+ const cost = formatCostAggregate(totalCost);
5449
+ const cacheRows = promptCache !== null && hasPromptCacheTelemetry(promptCache) ? [
5450
+ [
5451
+ "uncached prefill",
5452
+ promptCache.uncachedPrefillTokens === null ? "not reported" : formatTokens2(promptCache.uncachedPrefillTokens)
5453
+ ],
5454
+ [
5455
+ "cache verdicts",
5456
+ `hot ${promptCache.verdictCounts.hot} \xB7 partial ${promptCache.verdictCounts.partial} \xB7 cold ${promptCache.verdictCounts.cold} \xB7 small ${promptCache.verdictCounts.small} \xB7 unknown ${promptCache.verdictCounts.unknown}`
5457
+ ]
5458
+ ] : [];
5459
+ return kvBlock([
5460
+ [
5461
+ "turns",
5462
+ formatTokens2(
5463
+ totals.runs - totals.sideQuestions - totals.handoffs - totals.prewarms - totals.backgroundMemory - totals.failedCompaction
5464
+ )
5465
+ ],
5466
+ ["model calls", formatTokens2(totals.apiCalls)],
5467
+ ...totals.sideQuestions > 0 ? [["side questions", formatTokens2(totals.sideQuestions)]] : [],
5468
+ ...totals.handoffs > 0 ? [["handoffs", formatTokens2(totals.handoffs)]] : [],
5469
+ ...totals.prewarms > 0 ? [["pre-warms", formatTokens2(totals.prewarms)]] : [],
5470
+ ...totals.backgroundMemory > 0 ? [["memory steps", formatTokens2(totals.backgroundMemory)]] : [],
5471
+ ...totals.failedCompaction > 0 ? [
5472
+ ["failed compaction calls", formatTokens2(totals.failedCompaction)],
5473
+ ["compaction usage", "known subtotals; missing usage is unknown"]
5474
+ ] : [],
5475
+ ...cost === null ? [] : [["cost", cost]],
5476
+ ["input", formatTokens2(totals.input)],
5477
+ ["output", formatTokens2(totals.output)],
5478
+ ["reasoning", ...reasoningValue(totals.reasoningTokens)],
5479
+ ["cache read", ...cacheReadValue(totals.cacheRead, totals.apiCalls)],
5480
+ ["cache write", formatTokens2(totals.cacheWrite)],
5481
+ ...cacheRows,
5482
+ ["processed", `${formatTokens2(resolvedTotal)} tokens`]
5483
+ ]);
5484
+ }
5485
+ function modelBlock(row2) {
5486
+ const cost = formatCostAggregate(row2.cost);
5487
+ return kvBlock([
5488
+ ["requested model ids", row2.requestedModelIds.join(", ")],
5489
+ ["response model id observation", responseModelIdObservationCountsLabel(row2.responseModelIdObservationCounts)],
5490
+ [
5491
+ "turns",
5492
+ formatTokens2(
5493
+ row2.runs - row2.sideQuestions - row2.handoffs - row2.prewarms - row2.backgroundMemory - row2.failedCompaction
5494
+ )
5495
+ ],
5496
+ ["model calls", formatTokens2(row2.apiCalls)],
5497
+ ...row2.sideQuestions > 0 ? [["side questions", formatTokens2(row2.sideQuestions)]] : [],
5498
+ ...row2.handoffs > 0 ? [["handoffs", formatTokens2(row2.handoffs)]] : [],
5499
+ ...row2.prewarms > 0 ? [["pre-warms", formatTokens2(row2.prewarms)]] : [],
5500
+ ...row2.backgroundMemory > 0 ? [["memory steps", formatTokens2(row2.backgroundMemory)]] : [],
5501
+ ...row2.failedCompaction > 0 ? [["failed compaction calls", formatTokens2(row2.failedCompaction)]] : [],
5502
+ ...cost === null ? [] : [["cost", cost]],
5503
+ ["input", formatTokens2(row2.input)],
5504
+ ["output", formatTokens2(row2.output)],
5505
+ ["reasoning", ...reasoningValue(row2.reasoningTokens)],
5506
+ ["cache read", ...cacheReadValue(row2.cacheRead, row2.apiCalls)],
5507
+ ["cache write", formatTokens2(row2.cacheWrite)],
5508
+ ["processed", `${formatTokens2(row2.tokens)} tokens`]
5509
+ ]);
5510
+ }
5511
+ function modelDetailLines(rows, contentWidth) {
5512
+ const theme2 = clioTheme();
5513
+ const lines = [rule(theme2, contentWidth)];
5514
+ if (rows.length === 0) {
5515
+ lines.push(theme2.fg("muted", "no token usage recorded for this session"));
5516
+ } else {
5517
+ for (const [index, row2] of rows.entries()) {
5518
+ if (index > 0) lines.push("");
5519
+ lines.push(theme2.style("accent", `${row2.providerId} \xB7 attributed model ${row2.attributedModelId}`, { bold: true }));
5520
+ for (const line of modelBlock(row2)) {
5521
+ lines.push(line);
5522
+ }
5549
5523
  }
5550
- try {
5551
- mutation(id);
5552
- this.refreshUserTasks();
5553
- this.status = `Operator task ${id} ${status}.`;
5554
- } catch (error) {
5555
- this.status = error instanceof Error ? error.message : String(error);
5524
+ }
5525
+ return lines;
5526
+ }
5527
+ function buildCostSnapshot(observability, sessionId, snapshot, getSessionEntries) {
5528
+ const entries = observability.costEntries();
5529
+ const rows = aggregateCostEntries(entries);
5530
+ const totalTokens = rows.reduce((sum, r) => sum + r.tokens, 0);
5531
+ return {
5532
+ sessionId,
5533
+ totalCost: snapshot?.session.cost ?? observability.sessionCostSummary(),
5534
+ totalTokens,
5535
+ rows,
5536
+ promptCache: getSessionEntries ? foldPromptCacheTelemetry(getSessionEntries()) : null
5537
+ };
5538
+ }
5539
+ var USAGE_TABS = ["Accounts", "Session", "Models", "Workers"];
5540
+ var UsageOverlayBody = class {
5541
+ constructor(getSnapshot, getQuotaSnapshots, getDispatchRows, requestRender) {
5542
+ this.getSnapshot = getSnapshot;
5543
+ this.getQuotaSnapshots = getQuotaSnapshots;
5544
+ this.getDispatchRows = getDispatchRows;
5545
+ this.requestRender = requestRender;
5546
+ }
5547
+ getSnapshot;
5548
+ getQuotaSnapshots;
5549
+ getDispatchRows;
5550
+ requestRender;
5551
+ tab = 0;
5552
+ offsets = [0, 0, 0, 0];
5553
+ height = 20;
5554
+ pageHeight = 16;
5555
+ lineCount = 0;
5556
+ setHeight(rows) {
5557
+ this.height = Math.max(1, rows - 3);
5558
+ }
5559
+ handleInput(data) {
5560
+ const selected = ["1", "2", "3", "4"].findIndex((key) => matchesKey(data, key));
5561
+ if (selected >= 0) this.tab = selected;
5562
+ else if (matchesKey(data, "tab") || matchesKey(data, "right")) this.tab = (this.tab + 1) % USAGE_TABS.length;
5563
+ else if (matchesKey(data, "shift+tab") || matchesKey(data, "left"))
5564
+ this.tab = (this.tab + USAGE_TABS.length - 1) % USAGE_TABS.length;
5565
+ else {
5566
+ let offset = this.offsets[this.tab] ?? 0;
5567
+ if (matchesKey(data, "up")) offset--;
5568
+ else if (matchesKey(data, "down")) offset++;
5569
+ else if (matchesKey(data, "pageUp")) offset -= this.pageHeight;
5570
+ else if (matchesKey(data, "pageDown")) offset += this.pageHeight;
5571
+ else if (matchesKey(data, "home") || matchesKey(data, "ctrl+home")) offset = 0;
5572
+ else if (matchesKey(data, "end") || matchesKey(data, "ctrl+end")) offset = this.lineCount;
5573
+ else return;
5574
+ this.offsets[this.tab] = Math.max(0, Math.min(offset, this.lineCount - this.pageHeight));
5556
5575
  }
5557
5576
  this.requestRender();
5558
5577
  }
5559
- requestRender() {
5560
- this.options.requestRender?.();
5578
+ render(width) {
5579
+ const contentWidth = Math.max(1, Math.floor(width));
5580
+ const snapshot = this.getSnapshot();
5581
+ const quota = this.getQuotaSnapshots();
5582
+ const theme2 = clioTheme();
5583
+ const tabs = wrapTextWithAnsi(
5584
+ USAGE_TABS.map(
5585
+ (name, index) => index === this.tab ? theme2.style("accent", `${index + 1} ${name}`, { bold: true, underline: true }) : theme2.fg("dim", `${index + 1} ${name}`)
5586
+ ).join(" "),
5587
+ contentWidth
5588
+ );
5589
+ let body;
5590
+ if (this.tab === 0)
5591
+ body = [
5592
+ theme2.fg("dim", "Account-wide limits \xB7 filled = used \xB7 shared across sessions and devices"),
5593
+ "",
5594
+ ...renderQuotaAccounts(quota, contentWidth)
5595
+ ];
5596
+ else if (this.tab === 1)
5597
+ body = [
5598
+ theme2.style("accent", "Session tokens & cost", { bold: true }),
5599
+ theme2.fg("dim", "Recorded calls in this session \xB7 estimates are marked in the cost totals"),
5600
+ "",
5601
+ ...summaryBlock(snapshot.totalCost, snapshot.totalTokens, snapshot.rows, snapshot.promptCache),
5602
+ "",
5603
+ rule(theme2, contentWidth),
5604
+ theme2.fg("dim", "Session tokens cannot be converted to subscription percentages."),
5605
+ ...snapshot.rows.length ? [] : ["no token usage recorded for this session"]
5606
+ ];
5607
+ else if (this.tab === 2) {
5608
+ body = [
5609
+ theme2.style("accent", "Model activity \xB7 this session", { bold: true }),
5610
+ theme2.fg("dim", "Share of recorded processed tokens, including cache traffic; not account quota."),
5611
+ ""
5612
+ ];
5613
+ for (const row2 of [...snapshot.rows].sort((a, b) => b.tokens - a.tokens)) {
5614
+ const share = snapshot.totalTokens > 0 ? row2.tokens / snapshot.totalTokens * 100 : 0;
5615
+ body.push(
5616
+ `${row2.providerId} \xB7 ${row2.attributedModelId}`,
5617
+ `${quotaMeter(share, Math.min(24, contentWidth), "normal")} ${share.toFixed(1)}% \xB7 ${formatTokens2(row2.tokens)} tokens`
5618
+ );
5619
+ }
5620
+ body.push("", ...modelDetailLines(snapshot.rows, contentWidth));
5621
+ } else body = renderWorkerUsage(this.getDispatchRows(), quota, contentWidth);
5622
+ const lines = body.flatMap((line) => wrapTextWithAnsi(line, contentWidth));
5623
+ this.lineCount = lines.length;
5624
+ this.pageHeight = Math.max(1, this.height - tabs.length - 1);
5625
+ const offset = Math.max(0, Math.min(this.offsets[this.tab] ?? 0, lines.length - this.pageHeight));
5626
+ this.offsets[this.tab] = offset;
5627
+ const visible = [...tabs, "", ...lines.slice(offset, offset + this.pageHeight)];
5628
+ if (lines.length > this.pageHeight)
5629
+ visible.push(theme2.fg("dim", `${offset + 1}\u2013${Math.min(lines.length, offset + this.pageHeight)} / ${lines.length}`));
5630
+ return visible;
5631
+ }
5632
+ invalidate() {
5561
5633
  }
5562
5634
  };
5563
- function openTasksOverlay(tui, getBoard, options = {}) {
5564
- const body = new TasksOverlayBody(getBoard, options);
5635
+ function openUsageOverlay(tui, observability, options) {
5636
+ const sessionId = options?.sessionId ?? null;
5637
+ let latest = observability.snapshot();
5638
+ const body = new UsageOverlayBody(
5639
+ () => buildCostSnapshot(observability, sessionId, latest, options?.getSessionEntries),
5640
+ options?.getQuotaSnapshots ?? (() => []),
5641
+ options?.getDispatchRows ?? (() => []),
5642
+ () => tui.requestRender()
5643
+ );
5565
5644
  const handle = showClioOverlayFrame(tui, body, {
5566
5645
  anchor: "center",
5567
- width: TASKS_OVERLAY_WIDTH,
5568
- markerId: "tasks",
5569
- title: () => "Tasks",
5570
- footerHint: () => body.footerHint()
5646
+ width: USAGE_OVERLAY_WIDTH,
5647
+ // Not derived from the title: that one carries the session id.
5648
+ markerId: "usage",
5649
+ title: sessionId && sessionId.length > 0 ? `Usage (${sessionId})` : "Usage",
5650
+ footerHint: buildResponsiveHint([
5651
+ { key: "1\u20134 / Tab", verb: "view", critical: true },
5652
+ { key: "\u2191\u2193", verb: "scroll" },
5653
+ { key: "PgUp/PgDn", verb: "page" }
5654
+ ]),
5655
+ visible: (_width, height) => {
5656
+ body.setHeight(height);
5657
+ return true;
5658
+ }
5659
+ });
5660
+ const unsubscribe = observability.subscribe((snapshot) => {
5661
+ latest = snapshot;
5662
+ body.invalidate();
5663
+ tui.requestRender();
5571
5664
  });
5572
- const timer = setInterval(() => tui.requestRender(), REFRESH_MS2);
5573
- timer.unref?.();
5574
5665
  return {
5575
5666
  ...handle,
5576
5667
  hide() {
5577
- clearInterval(timer);
5668
+ unsubscribe();
5578
5669
  handle.hide();
5579
5670
  }
5580
5671
  };
@@ -5582,7 +5673,7 @@ function openTasksOverlay(tui, getBoard, options = {}) {
5582
5673
 
5583
5674
  // src/interactive/overlay-general-openers.ts
5584
5675
  function createOverlayGeneralOpeners(deps) {
5585
- const openCostOverlayFactory = deps.openCostOverlay ?? openCostOverlay;
5676
+ const openUsageOverlayFactory = deps.openUsageOverlay ?? openUsageOverlay;
5586
5677
  const openContextOverlayFactory = deps.openContextOverlay ?? openContextOverlay;
5587
5678
  const openContextResetOverlayFactory = deps.openContextResetOverlay ?? openContextResetOverlay;
5588
5679
  const openTasksOverlayFactory = deps.openTasksOverlay ?? openTasksOverlay;
@@ -5591,11 +5682,13 @@ function createOverlayGeneralOpeners(deps) {
5591
5682
  const openViewOverlayFactory = deps.openViewOverlay ?? openViewOverlay;
5592
5683
  const showOverlayFrameFactory = deps.showOverlayFrame ?? showClioOverlayFrame;
5593
5684
  const openSideQuestionOverlayFactory = deps.openSideQuestionOverlay ?? openSideQuestionOverlay;
5594
- const openCost = () => {
5685
+ const openUsage = () => {
5595
5686
  if (deps.transitions.state !== "closed") return;
5596
- deps.transitions.state = "cost";
5597
- deps.transitions.handle = openCostOverlayFactory(deps.tui, deps.observability, {
5687
+ deps.transitions.state = "usage";
5688
+ deps.transitions.handle = openUsageOverlayFactory(deps.tui, deps.observability, {
5598
5689
  sessionId: deps.getSessionId?.() ?? null,
5690
+ ...deps.getQuotaSnapshots ? { getQuotaSnapshots: deps.getQuotaSnapshots } : {},
5691
+ ...deps.getDispatchRows ? { getDispatchRows: deps.getDispatchRows } : {},
5599
5692
  ...deps.readSessionEntries ? {
5600
5693
  getSessionEntries: () => deps.readSessionEntries?.() ?? []
5601
5694
  } : {}
@@ -5805,16 +5898,16 @@ function createOverlayGeneralOpeners(deps) {
5805
5898
  });
5806
5899
  deps.transitions.handle = session;
5807
5900
  deps.requestRender();
5808
- void ask(question, { signal: controller.signal, onDelta: (partialText) => session.setAnswer(partialText) }).then((outcome) => {
5809
- if (outcome.status === "answered") {
5810
- session.settle({ kind: "answered", text: outcome.text });
5901
+ void ask(question, { signal: controller.signal, onDelta: (partialText) => session.setAnswer(partialText) }).then((outcome2) => {
5902
+ if (outcome2.status === "answered") {
5903
+ session.settle({ kind: "answered", text: outcome2.text });
5811
5904
  return;
5812
5905
  }
5813
- if (outcome.status === "aborted") {
5814
- session.settle({ kind: "aborted", text: outcome.text });
5906
+ if (outcome2.status === "aborted") {
5907
+ session.settle({ kind: "aborted", text: outcome2.text });
5815
5908
  return;
5816
5909
  }
5817
- session.settle({ kind: "error", reason: outcome.reason });
5910
+ session.settle({ kind: "error", reason: outcome2.reason });
5818
5911
  }).catch((error) => {
5819
5912
  session.settle({ kind: "error", reason: error instanceof Error ? error.message : String(error) });
5820
5913
  });
@@ -5905,13 +5998,13 @@ function createOverlayGeneralOpeners(deps) {
5905
5998
  },
5906
5999
  onNotice: (text) => deps.stderr(`[fleet ${preview.name}] ${text}
5907
6000
  `)
5908
- }).then((outcome) => {
6001
+ }).then((outcome2) => {
5909
6002
  deps.notify(
5910
- outcome.cleanRun ? "success" : "warning",
6003
+ outcome2.cleanRun ? "success" : "warning",
5911
6004
  // `totalCost` rather than the bare `totalCostUsd` sum: a run whose
5912
6005
  // receipts were all unpriced reads as not measured instead of as a
5913
6006
  // measured $0.0000 nobody put a number on.
5914
- `fleet ${preview.name}: ${outcome.succeededStepCount}/${outcome.requiredStepCount} steps succeeded, cost ${renderCostAggregate(outcome.totalCost)}`,
6007
+ `fleet ${preview.name}: ${outcome2.succeededStepCount}/${outcome2.requiredStepCount} steps succeeded, cost ${renderCostAggregate(outcome2.totalCost)}`,
5915
6008
  "fleet-run:settled"
5916
6009
  );
5917
6010
  }).catch((error) => {
@@ -5923,7 +6016,7 @@ function createOverlayGeneralOpeners(deps) {
5923
6016
  });
5924
6017
  };
5925
6018
  return {
5926
- openCost,
6019
+ openUsage,
5927
6020
  openContextView,
5928
6021
  openContextReset,
5929
6022
  toggleFooter,
@@ -7033,13 +7126,13 @@ function editNumberSubmenu(title, id) {
7033
7126
  numberSettingNote(rule2)
7034
7127
  );
7035
7128
  input.onSubmit = (val) => {
7036
- const outcome = parseNumberSetting(val, rule2);
7037
- if ("refusal" in outcome) {
7038
- wrapper.setProblem(outcome.refusal);
7129
+ const outcome2 = parseNumberSetting(val, rule2);
7130
+ if ("refusal" in outcome2) {
7131
+ wrapper.setProblem(outcome2.refusal);
7039
7132
  return;
7040
7133
  }
7041
7134
  wrapper.setProblem(null);
7042
- done(outcome.value === null ? "" : val.trim());
7135
+ done(outcome2.value === null ? "" : val.trim());
7043
7136
  };
7044
7137
  input.onEscape = () => done();
7045
7138
  return wrapper;
@@ -7067,13 +7160,13 @@ function editEnumListSubmenu(title, legal) {
7067
7160
  `Comma-separated, from ${legal.join(", ")}; blank activates none.`
7068
7161
  );
7069
7162
  input.onSubmit = (val) => {
7070
- const outcome = parseEnumListSetting(val, legal);
7071
- if ("refusal" in outcome) {
7072
- wrapper.setProblem(outcome.refusal);
7163
+ const outcome2 = parseEnumListSetting(val, legal);
7164
+ if ("refusal" in outcome2) {
7165
+ wrapper.setProblem(outcome2.refusal);
7073
7166
  return;
7074
7167
  }
7075
7168
  wrapper.setProblem(null);
7076
- done(outcome.values.join(", "));
7169
+ done(outcome2.values.join(", "));
7077
7170
  };
7078
7171
  input.onEscape = () => done();
7079
7172
  return wrapper;
@@ -7289,12 +7382,12 @@ function scopedModelsSubmenu(selectedRefs, providers) {
7289
7382
  "Target entries cycle by target, using its default model when switching targets. Model entries pin one exact target/model reference."
7290
7383
  );
7291
7384
  }
7292
- function chainSubmenus(first, second, join3) {
7385
+ function chainSubmenus(first, second, join6) {
7293
7386
  return (_currentValue, done) => {
7294
7387
  let active = first("", (head) => {
7295
7388
  const trimmed = head?.trim() ?? "";
7296
7389
  if (!trimmed) return done();
7297
- active = second(trimmed)("", (tail) => done(tail?.trim() ? join3(trimmed, tail.trim()) : void 0));
7390
+ active = second(trimmed)("", (tail) => done(tail?.trim() ? join6(trimmed, tail.trim()) : void 0));
7298
7391
  });
7299
7392
  return {
7300
7393
  render: (width) => active.render(width),
@@ -8277,9 +8370,9 @@ function applyNonNegativeInteger(value, set) {
8277
8370
  if (Number.isFinite(parsed) && parsed >= 0) set(Math.floor(parsed));
8278
8371
  }
8279
8372
  function applyNumberSetting(id, value, set) {
8280
- const outcome = parseNumberSetting(value, NUMBER_SETTING_RULES[id]);
8281
- if ("refusal" in outcome) return;
8282
- set(outcome.value);
8373
+ const outcome2 = parseNumberSetting(value, NUMBER_SETTING_RULES[id]);
8374
+ if ("refusal" in outcome2) return;
8375
+ set(outcome2.value);
8283
8376
  }
8284
8377
  function applyDelegationAgentChange(settings, value) {
8285
8378
  if (value.startsWith(DELEGATION_REMOVE_PREFIX)) {
@@ -8567,13 +8660,13 @@ function applySettingChange(settings, id, value) {
8567
8660
  });
8568
8661
  return;
8569
8662
  case "routing.activeRoles": {
8570
- const outcome = parseEnumListSetting(value, ACTIVE_ROUTING_ROLES);
8571
- if ("values" in outcome) settings.fleet.adaptiveRouting.roles = outcome.values;
8663
+ const outcome2 = parseEnumListSetting(value, ACTIVE_ROUTING_ROLES);
8664
+ if ("values" in outcome2) settings.fleet.adaptiveRouting.roles = outcome2.values;
8572
8665
  return;
8573
8666
  }
8574
8667
  case "routing.activePostures": {
8575
- const outcome = parseEnumListSetting(value, ACTIVE_ROUTING_POSTURES);
8576
- if ("values" in outcome) settings.fleet.adaptiveRouting.postures = outcome.values;
8668
+ const outcome2 = parseEnumListSetting(value, ACTIVE_ROUTING_POSTURES);
8669
+ if ("values" in outcome2) settings.fleet.adaptiveRouting.postures = outcome2.values;
8577
8670
  return;
8578
8671
  }
8579
8672
  case "prewarm.enabled":
@@ -8692,12 +8785,12 @@ function applyEntrySettingChange(settings, id, value) {
8692
8785
  }
8693
8786
  function changedLeafPaths(before, after) {
8694
8787
  const out = [];
8695
- const isRecord = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
8788
+ const isRecord6 = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
8696
8789
  const visit = (a, b, path) => {
8697
8790
  if (a === b) return;
8698
8791
  const profileLeaf = path.startsWith("fleet.profiles.") && path.split(".").length === 3;
8699
8792
  const collectionLeaf = path === "interface.keybindings" || path === "fleet.rosters";
8700
- if (!isRecord(a) || !isRecord(b) || profileLeaf || collectionLeaf) {
8793
+ if (!isRecord6(a) || !isRecord6(b) || profileLeaf || collectionLeaf) {
8701
8794
  if (JSON.stringify(a) !== JSON.stringify(b)) out.push(path);
8702
8795
  return;
8703
8796
  }
@@ -11435,28 +11528,28 @@ function evidenceLabel(evidence, refresh) {
11435
11528
  return "resource evidence from before refresh; current session admission is unverified because refresh failed";
11436
11529
  return "resource evidence from before refresh; no session refresh applied";
11437
11530
  }
11438
- function outcomeLines(outcome, width, refresh) {
11531
+ function outcomeLines(outcome2, width, refresh) {
11439
11532
  const theme2 = clioTheme();
11440
- const token = outcome.status === "committed" ? "success" : outcome.status === "failed" ? "error" : "dim";
11533
+ const token = outcome2.status === "committed" ? "success" : outcome2.status === "failed" ? "error" : "dim";
11441
11534
  const rows = wrap(
11442
- theme2.fg(token, `${outcome.status}: ${outcome.operation} ${outcome.identity.ref} (${outcome.identity.scope})`),
11535
+ theme2.fg(token, `${outcome2.status}: ${outcome2.operation} ${outcome2.identity.ref} (${outcome2.identity.scope})`),
11443
11536
  width
11444
11537
  );
11445
- if (outcome.error) {
11446
- rows.push(...wrap(theme2.fg("error", `${outcome.error.code}: ${outcome.error.message}`), width));
11447
- if (outcome.error.changed)
11538
+ if (outcome2.error) {
11539
+ rows.push(...wrap(theme2.fg("error", `${outcome2.error.code}: ${outcome2.error.message}`), width));
11540
+ if (outcome2.error.changed)
11448
11541
  rows.push(
11449
11542
  ...wrap(
11450
11543
  theme2.fg(
11451
11544
  "dim",
11452
- `changed fact ${outcome.error.changed.fact}: reviewed ${JSON.stringify(outcome.error.changed.expected)}, observed ${JSON.stringify(outcome.error.changed.observed)}`
11545
+ `changed fact ${outcome2.error.changed.fact}: reviewed ${JSON.stringify(outcome2.error.changed.expected)}, observed ${JSON.stringify(outcome2.error.changed.observed)}`
11453
11546
  ),
11454
11547
  width
11455
11548
  )
11456
11549
  );
11457
- rows.push(...wrap(theme2.fg("info", outcome.error.next), width));
11550
+ rows.push(...wrap(theme2.fg("info", outcome2.error.next), width));
11458
11551
  }
11459
- const verification = outcome.verification;
11552
+ const verification = outcome2.verification;
11460
11553
  if (verification) {
11461
11554
  rows.push(...wrap(theme2.fg("dim", `disk: tree ${verification.tree}, record ${verification.record}`), width));
11462
11555
  rows.push(
@@ -11480,11 +11573,11 @@ function outcomeLines(outcome, width, refresh) {
11480
11573
  )
11481
11574
  );
11482
11575
  }
11483
- if (outcome.recovery?.packageBackup)
11484
- rows.push(...wrap(theme2.fg("info", `recovered content: ${outcome.recovery.packageBackup}`), width));
11485
- if (outcome.recovery?.stateBackup)
11486
- rows.push(...wrap(theme2.fg("dim", `state backup: ${outcome.recovery.stateBackup}`), width));
11487
- for (const diagnostic of outcome.diagnostics) rows.push(...wrap(theme2.fg("dim", diagnostic), width));
11576
+ if (outcome2.recovery?.packageBackup)
11577
+ rows.push(...wrap(theme2.fg("info", `recovered content: ${outcome2.recovery.packageBackup}`), width));
11578
+ if (outcome2.recovery?.stateBackup)
11579
+ rows.push(...wrap(theme2.fg("dim", `state backup: ${outcome2.recovery.stateBackup}`), width));
11580
+ for (const diagnostic of outcome2.diagnostics) rows.push(...wrap(theme2.fg("dim", diagnostic), width));
11488
11581
  return rows;
11489
11582
  }
11490
11583
  function formatLibraryOutcome(result, width) {
@@ -11497,7 +11590,7 @@ function formatLibraryOutcome(result, width) {
11497
11590
  width
11498
11591
  );
11499
11592
  rows.push(rule(theme2, width));
11500
- for (const outcome of result.outcomes) rows.push(...outcomeLines(outcome, width, result.refresh));
11593
+ for (const outcome2 of result.outcomes) rows.push(...outcomeLines(outcome2, width, result.refresh));
11501
11594
  rows.push(rule(theme2, width));
11502
11595
  rows.push(...wrap(refreshLine(result.refresh), width));
11503
11596
  if (result.unattempted > 0)
@@ -11533,7 +11626,7 @@ function formatLibraryImportReview(plan, width, options = {}) {
11533
11626
  ...wrap(
11534
11627
  theme2.fg(
11535
11628
  "info",
11536
- `projects ${plan.outcomes.length} resource${plan.outcomes.length === 1 ? "" : "s"}${plan.outcomes.length > 0 ? `: ${plan.outcomes.map((outcome) => `${outcome.kind}:${outcome.name} (${outcome.status})`).join(", ")}` : ""}`
11629
+ `projects ${plan.outcomes.length} resource${plan.outcomes.length === 1 ? "" : "s"}${plan.outcomes.length > 0 ? `: ${plan.outcomes.map((outcome2) => `${outcome2.kind}:${outcome2.name} (${outcome2.status})`).join(", ")}` : ""}`
11537
11630
  ),
11538
11631
  width
11539
11632
  )
@@ -11631,8 +11724,8 @@ function openReviewOverlay(tui, spec) {
11631
11724
  if (matchesKey(data, "enter")) {
11632
11725
  if (!spec.applicable) return;
11633
11726
  settled = true;
11634
- const outcome = spec.commit();
11635
- body.outcome = () => outcome(width);
11727
+ const outcome2 = spec.commit();
11728
+ body.outcome = () => outcome2(width);
11636
11729
  body.reset();
11637
11730
  tui.requestRender();
11638
11731
  return;
@@ -11935,15 +12028,15 @@ function openLibraryOverlay(tui, deps) {
11935
12028
  columns: columns(),
11936
12029
  commit: (accepted) => (deps.applyImport ?? applyLibraryImport)(accepted, true),
11937
12030
  outcome: (accepted, result) => {
11938
- const outcome = libraryImportOutcome(accepted, result);
11939
- const committed = outcome.status === "committed";
12031
+ const outcome2 = libraryImportOutcome(accepted, result);
12032
+ const committed = outcome2.status === "committed";
11940
12033
  return {
11941
12034
  planId: accepted.reviewFingerprint,
11942
12035
  dryRun: false,
11943
- outcomes: [outcome],
12036
+ outcomes: [outcome2],
11944
12037
  committed: committed ? 1 : 0,
11945
- failed: outcome.status === "failed" ? 1 : 0,
11946
- unattempted: outcome.status === "unattempted" ? 1 : 0,
12038
+ failed: outcome2.status === "failed" ? 1 : 0,
12039
+ unattempted: outcome2.status === "unattempted" ? 1 : 0,
11947
12040
  refresh: committed ? deps.lifecycle.retryRefresh(cwd) : { status: "not-applicable", reason: "nothing was imported" }
11948
12041
  };
11949
12042
  },
@@ -13969,7 +14062,7 @@ function routeAuthOverlayKey(data, deps) {
13969
14062
  }
13970
14063
  return false;
13971
14064
  }
13972
- function routeCostOverlayKey(data, deps) {
14065
+ function routeReadOnlyOverlayKey(data, deps) {
13973
14066
  if (isEscapeKey(data)) {
13974
14067
  deps.closeOverlay();
13975
14068
  return true;
@@ -13999,16 +14092,13 @@ function routeOverlayKey(data, overlayState, deps, matches) {
13999
14092
  return true;
14000
14093
  }
14001
14094
  if (overlayState === "auth") return routeAuthOverlayKey(data, deps);
14002
- if (overlayState === "cost") {
14003
- routeCostOverlayKey(data, deps);
14004
- return true;
14005
- }
14095
+ if (overlayState === "usage") return routeReadOnlyOverlayKey(data, deps);
14006
14096
  if (overlayState === "context-view") {
14007
- routeCostOverlayKey(data, deps);
14097
+ routeReadOnlyOverlayKey(data, deps);
14008
14098
  return true;
14009
14099
  }
14010
14100
  if (overlayState === "side-question") {
14011
- routeCostOverlayKey(data, deps);
14101
+ routeReadOnlyOverlayKey(data, deps);
14012
14102
  return true;
14013
14103
  }
14014
14104
  if (overlayState === "handoff-review") return false;
@@ -14072,7 +14162,7 @@ function createOverlayLifecycle(deps) {
14072
14162
  openTreeOverlay: openTreeOverlayFactory,
14073
14163
  openMessagePickerOverlay: openMessagePickerOverlayFactory,
14074
14164
  openCwdFallbackOverlay: openCwdFallbackOverlayFactory,
14075
- openCostOverlay: openCostOverlayFactory,
14165
+ openUsageOverlay: openUsageOverlayFactory,
14076
14166
  openContextOverlay: openContextOverlayFactory,
14077
14167
  openContextResetOverlay: openContextResetOverlayFactory,
14078
14168
  openTasksOverlay: openTasksOverlayFactory,
@@ -14264,6 +14354,8 @@ function createOverlayLifecycle(deps) {
14264
14354
  tui,
14265
14355
  transitions: overlayTransitions,
14266
14356
  observability: deps.app.observability,
14357
+ ...deps.getQuotaSnapshots ? { getQuotaSnapshots: deps.getQuotaSnapshots } : {},
14358
+ ...deps.getDispatchRows ? { getDispatchRows: deps.getDispatchRows } : {},
14267
14359
  ...deps.app.getSessionId ? { getSessionId: deps.app.getSessionId } : {},
14268
14360
  getContextLedger: () => deps.app.chat.contextLedger(),
14269
14361
  contextChat: deps.app.chat,
@@ -14292,7 +14384,7 @@ function createOverlayLifecycle(deps) {
14292
14384
  startDispatchBoardTicker: () => interactiveTickers.startDispatchBoardTicker(),
14293
14385
  closeOverlay,
14294
14386
  showOverlayFrame,
14295
- ...openCostOverlayFactory ? { openCostOverlay: openCostOverlayFactory } : {},
14387
+ ...openUsageOverlayFactory ? { openUsageOverlay: openUsageOverlayFactory } : {},
14296
14388
  ...openContextOverlayFactory ? { openContextOverlay: openContextOverlayFactory } : {},
14297
14389
  ...openContextResetOverlayFactory ? { openContextResetOverlay: openContextResetOverlayFactory } : {},
14298
14390
  ...openTasksOverlayFactory ? { openTasksOverlay: openTasksOverlayFactory } : {},
@@ -14308,7 +14400,7 @@ function createOverlayLifecycle(deps) {
14308
14400
  const openResumeOverlayState = overlaySessions.openResume;
14309
14401
  const openTreeOverlayState = overlaySessions.openTree;
14310
14402
  const openMessagePickerOverlayState = overlaySessions.openMessagePicker;
14311
- const openCostOverlayState = overlayGeneralOpeners.openCost;
14403
+ const openUsageOverlayState = overlayGeneralOpeners.openUsage;
14312
14404
  const openContextViewOverlayState = overlayGeneralOpeners.openContextView;
14313
14405
  const openContextResetOverlayState = overlayGeneralOpeners.openContextReset;
14314
14406
  const toggleFooterDashboardState = overlayGeneralOpeners.toggleFooter;
@@ -14329,7 +14421,7 @@ function createOverlayLifecycle(deps) {
14329
14421
  isAskUserWaiting: overlayAskUser.isWaiting,
14330
14422
  resetAskUserCancellation: overlayAskUser.resetCancellation,
14331
14423
  refreshSettingsOverlay: overlayModelSelectors.refreshSettingsOverlay,
14332
- openCostOverlayState,
14424
+ openUsageOverlayState,
14333
14425
  openContextViewOverlayState,
14334
14426
  openContextResetOverlayState,
14335
14427
  toggleFooterDashboardState,
@@ -15553,10 +15645,10 @@ function createInteractiveEventProjection(deps) {
15553
15645
  deps.bus.on(BusChannels.ContextPruned, (payload) => {
15554
15646
  const event = payload;
15555
15647
  if (event && typeof event === "object" && typeof event.tokensBefore === "number" && typeof event.tokensAfter === "number") {
15556
- const outcome = event.tokensAfter < event.tokensBefore ? "Reclaimed context" : "Context updated";
15648
+ const outcome2 = event.tokensAfter < event.tokensBefore ? "Reclaimed context" : "Context updated";
15557
15649
  deps.notify(
15558
15650
  "info",
15559
- `[Compaction] ${outcome}: ${event.tokensBefore} -> ${event.tokensAfter} tokens (${event.stage})`,
15651
+ `[Compaction] ${outcome2}: ${event.tokensBefore} -> ${event.tokensAfter} tokens (${event.stage})`,
15560
15652
  "compaction-notice"
15561
15653
  );
15562
15654
  }
@@ -15960,7 +16052,7 @@ function createInteractiveInputRuntime(deps) {
15960
16052
  },
15961
16053
  cancelFocusedOwner: () => {
15962
16054
  const state = deps.overlay.getState();
15963
- if (["permission-confirm", "cost", "context-view", "side-question", "dispatch-board", "auth"].includes(state))
16055
+ if (["permission-confirm", "usage", "context-view", "side-question", "dispatch-board", "auth"].includes(state))
15964
16056
  deps.overlay.closeOverlay();
15965
16057
  else if (focused()?.handleInput) {
15966
16058
  focused()?.handleInput?.("\x1B");
@@ -16078,89 +16170,1041 @@ function createInteractiveInputRuntime(deps) {
16078
16170
  deps.onShutdownArmedChange?.(armed);
16079
16171
  deps.refreshFooter();
16080
16172
  },
16081
- closeOverlay: () => deps.overlay.closeOverlay(),
16082
- listNotifications: () => deps.notifications.list(),
16083
- dismissNotification: (id) => deps.notifications.dismiss(id),
16084
- dismissAllNotifications: () => deps.notifications.dismissAll(),
16085
- shutdownDisposers: [
16086
- () => deps.shutdown.stopTickers(),
16087
- () => leaderKeys.dispose(),
16088
- () => removeReload?.(),
16089
- () => deps.shutdown.disposeInteractiveTickers(),
16090
- () => deps.shutdown.disposeBeforeStatus(),
16091
- () => deps.shutdown.disposeProjectionPrimary(),
16092
- () => deps.shutdown.disposeStatus(),
16093
- () => deps.shutdown.disposeProjectionRemaining(),
16094
- () => deps.shutdown.disposeOverlay(),
16095
- () => deps.shutdown.stopAgentProgress(),
16096
- () => deps.shutdown.disposeChat(),
16097
- () => deps.shutdown.disposeSubscriptions()
16098
- ],
16099
- stopUi: deps.stopUi,
16100
- ...deps.beforeStopUi ? { beforeStopUi: deps.beforeStopUi } : {},
16101
- cancelParkedCalls: deps.cancelParkedCalls,
16102
- onShutdown: deps.onShutdown,
16103
- registerTerminalTeardown: deps.registerTerminalTeardown ?? ((teardown) => getTerminationCoordinator().onDrain(teardown)),
16104
- // stderr for the same reason src/core/termination.ts uses it for a failed
16105
- // hook: by the time teardown fails there is no UI left to carry a notice.
16106
- reportShutdownFailure: deps.reportShutdownFailure ?? ((step, error) => {
16107
- process.stderr.write(
16108
- `[clio-coder:interactive] ${step} failed: ${error instanceof Error ? error.message : String(error)}
16109
- `
16110
- );
16111
- })
16173
+ closeOverlay: () => deps.overlay.closeOverlay(),
16174
+ listNotifications: () => deps.notifications.list(),
16175
+ dismissNotification: (id) => deps.notifications.dismiss(id),
16176
+ dismissAllNotifications: () => deps.notifications.dismissAll(),
16177
+ shutdownDisposers: [
16178
+ () => deps.shutdown.stopTickers(),
16179
+ () => leaderKeys.dispose(),
16180
+ () => removeReload?.(),
16181
+ () => deps.shutdown.disposeInteractiveTickers(),
16182
+ () => deps.shutdown.disposeBeforeStatus(),
16183
+ () => deps.shutdown.disposeProjectionPrimary(),
16184
+ () => deps.shutdown.disposeStatus(),
16185
+ () => deps.shutdown.disposeProjectionRemaining(),
16186
+ () => deps.shutdown.disposeOverlay(),
16187
+ () => deps.shutdown.stopAgentProgress(),
16188
+ () => deps.shutdown.disposeChat(),
16189
+ () => deps.shutdown.disposeSubscriptions()
16190
+ ],
16191
+ stopUi: deps.stopUi,
16192
+ ...deps.beforeStopUi ? { beforeStopUi: deps.beforeStopUi } : {},
16193
+ cancelParkedCalls: deps.cancelParkedCalls,
16194
+ onShutdown: deps.onShutdown,
16195
+ registerTerminalTeardown: deps.registerTerminalTeardown ?? ((teardown) => getTerminationCoordinator().onDrain(teardown)),
16196
+ // stderr for the same reason src/core/termination.ts uses it for a failed
16197
+ // hook: by the time teardown fails there is no UI left to carry a notice.
16198
+ reportShutdownFailure: deps.reportShutdownFailure ?? ((step, error) => {
16199
+ process.stderr.write(
16200
+ `[clio-coder:interactive] ${step} failed: ${error instanceof Error ? error.message : String(error)}
16201
+ `
16202
+ );
16203
+ })
16204
+ });
16205
+ deps.registerInputListener((data) => {
16206
+ deps.onInputIngress?.(classifyInputAction(data, deps), data);
16207
+ return controller.handleInput(data);
16208
+ });
16209
+ return controller;
16210
+ }
16211
+ function classifyInputAction(data, deps) {
16212
+ if (data.length === 0 || isKeyRelease(data)) return "no-visual-change";
16213
+ if (deps.keybindings.matches(data, "tui.input.submit")) return "submit";
16214
+ const sgrMousePrefix = `${String.fromCharCode(27)}[<`;
16215
+ if (data.startsWith(`${sgrMousePrefix}64;`) || data.startsWith(`${sgrMousePrefix}65;`) || deps.keybindings.matches(data, "tui.altScreen.pageUp") || deps.keybindings.matches(data, "tui.altScreen.pageDown") || deps.keybindings.matches(data, "tui.editor.pageUp") || deps.keybindings.matches(data, "tui.editor.pageDown")) {
16216
+ return "scroll";
16217
+ }
16218
+ if (deps.overlay.getState() !== "closed") return "overlay";
16219
+ return "editor";
16220
+ }
16221
+ function overlayScopeLabel(state) {
16222
+ const names = {
16223
+ closed: "Composer",
16224
+ "permission-confirm": "Permission",
16225
+ "dispatch-board": "Workers",
16226
+ auth: "Authentication",
16227
+ usage: "Usage",
16228
+ "context-view": "Context",
16229
+ "context-reset": "Context reset",
16230
+ tasks: "Tasks",
16231
+ decisions: "Decisions",
16232
+ memory: "Memory",
16233
+ view: "View",
16234
+ model: "Model",
16235
+ "model-scope": "Model cycle set",
16236
+ settings: "Settings",
16237
+ resume: "Resume",
16238
+ tree: "Session tree",
16239
+ "message-picker": "Messages",
16240
+ "cwd-fallback": "Working directory",
16241
+ "ask-user": "Interview",
16242
+ help: "Help",
16243
+ extensions: "Extensions",
16244
+ interop: "Interop",
16245
+ "skills-hub": "Library",
16246
+ "side-question": "Side question",
16247
+ "handoff-review": "Handoff review",
16248
+ "fleet-run-approval": "Run approval"
16249
+ };
16250
+ return names[state];
16251
+ }
16252
+
16253
+ // src/interactive/interactive-presentation.ts
16254
+ init_esm_shims();
16255
+ import { performance as performance3 } from "node:perf_hooks";
16256
+
16257
+ // src/domains/quota/summary-feed.ts
16258
+ init_esm_shims();
16259
+
16260
+ // src/domains/quota/service.ts
16261
+ init_esm_shims();
16262
+
16263
+ // src/domains/quota/cache.ts
16264
+ init_esm_shims();
16265
+ var DEFAULT_QUOTA_CACHE_TTL_MS = 5 * 6e4;
16266
+ function createQuotaCache(options = {}) {
16267
+ const ttlMs = options.ttlMs ?? DEFAULT_QUOTA_CACHE_TTL_MS;
16268
+ const now = options.now ?? Date.now;
16269
+ const entries = /* @__PURE__ */ new Map();
16270
+ const fresh = (entry) => entry !== void 0 && now() - entry.storedAtMs < ttlMs;
16271
+ return {
16272
+ read(providerId) {
16273
+ const entry = entries.get(providerId);
16274
+ return fresh(entry) && entry !== void 0 ? entry.snapshot : null;
16275
+ },
16276
+ readLastGood(providerId) {
16277
+ const entry = entries.get(providerId);
16278
+ if (entry === void 0) return null;
16279
+ return fresh(entry) ? entry.snapshot : { ...entry.snapshot, stale: true };
16280
+ },
16281
+ write(snapshot) {
16282
+ entries.set(snapshot.providerId, { snapshot, storedAtMs: now() });
16283
+ },
16284
+ clear(providerId) {
16285
+ if (providerId === void 0) entries.clear();
16286
+ else entries.delete(providerId);
16287
+ },
16288
+ async resolve(provider) {
16289
+ const cached = entries.get(provider.id);
16290
+ if (fresh(cached) && cached !== void 0) return cached.snapshot;
16291
+ const snapshot = await provider.fetch();
16292
+ if (snapshot.status === "ok") {
16293
+ entries.set(provider.id, { snapshot, storedAtMs: now() });
16294
+ return snapshot;
16295
+ }
16296
+ if (cached === void 0) return snapshot;
16297
+ return {
16298
+ ...cached.snapshot,
16299
+ stale: true,
16300
+ message: snapshot.message ?? cached.snapshot.message ?? null
16301
+ };
16302
+ }
16303
+ };
16304
+ }
16305
+
16306
+ // src/domains/quota/registry.ts
16307
+ init_esm_shims();
16308
+
16309
+ // src/domains/quota/anthropic-max-provider.ts
16310
+ init_esm_shims();
16311
+
16312
+ // src/domains/quota/anthropic-usage.ts
16313
+ init_esm_shims();
16314
+
16315
+ // src/domains/quota/types.ts
16316
+ init_esm_shims();
16317
+ function parseRetryAfterSeconds(value) {
16318
+ if (!value) return null;
16319
+ const seconds = Number.parseFloat(value.trim());
16320
+ if (!Number.isFinite(seconds)) return null;
16321
+ return Math.max(0, seconds);
16322
+ }
16323
+ function formatPlan(value) {
16324
+ if (!value) return null;
16325
+ const words = value.replace(/_/g, " ").trim();
16326
+ if (!words) return null;
16327
+ return words.replace(/\S+/g, (word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase());
16328
+ }
16329
+
16330
+ // src/domains/quota/anthropic-usage.ts
16331
+ var ANTHROPIC_USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
16332
+ var ANTHROPIC_OAUTH_BETA = "oauth-2025-04-20";
16333
+ var WINDOW_ORDER = { session: 0, weekly: 1 };
16334
+ function outcome(status, message, extra = {}) {
16335
+ return { status, message, windows: [], credits: null, retryAfterSeconds: null, ...extra };
16336
+ }
16337
+ async function fetchAnthropicUsage(token, dependencies) {
16338
+ const controller = new AbortController();
16339
+ const timer = setTimeout(() => controller.abort(), dependencies.timeoutMs);
16340
+ let response;
16341
+ try {
16342
+ response = await dependencies.fetch(ANTHROPIC_USAGE_URL, {
16343
+ method: "GET",
16344
+ headers: {
16345
+ Authorization: `Bearer ${token}`,
16346
+ "anthropic-beta": ANTHROPIC_OAUTH_BETA
16347
+ },
16348
+ signal: controller.signal
16349
+ });
16350
+ } catch (error) {
16351
+ return outcome("error", describeError2(error, dependencies.timeoutMs));
16352
+ } finally {
16353
+ clearTimeout(timer);
16354
+ }
16355
+ if (response.status === 401 || response.status === 403) {
16356
+ await discardBody(response);
16357
+ return outcome("expired", null);
16358
+ }
16359
+ if (response.status === 429) {
16360
+ await discardBody(response);
16361
+ return outcome("error", "HTTP 429", {
16362
+ retryAfterSeconds: parseRetryAfterSeconds(response.headers.get("Retry-After"))
16363
+ });
16364
+ }
16365
+ if (response.status !== 200) {
16366
+ await discardBody(response);
16367
+ return outcome("error", `HTTP ${response.status}`);
16368
+ }
16369
+ let body;
16370
+ try {
16371
+ body = await response.json();
16372
+ } catch {
16373
+ return outcome("error", "Unexpected usage response");
16374
+ }
16375
+ if (!isRecord(body)) {
16376
+ return outcome("error", "Unexpected usage response");
16377
+ }
16378
+ return outcome("ok", null, { windows: usageWindows(body), credits: usageCredits(body.spend) });
16379
+ }
16380
+ function usageWindows(body) {
16381
+ const fromLimits = windowsFromLimits(body.limits);
16382
+ const windows = fromLimits.length > 0 ? fromLimits : windowsFromFlat(body);
16383
+ return windows.sort((left, right) => {
16384
+ const order = (WINDOW_ORDER[left.key] ?? 2) - (WINDOW_ORDER[right.key] ?? 2);
16385
+ return order !== 0 ? order : left.label.localeCompare(right.label);
16386
+ });
16387
+ }
16388
+ function windowsFromLimits(raw) {
16389
+ if (!Array.isArray(raw)) return [];
16390
+ const windows = [];
16391
+ for (const entry of raw) {
16392
+ if (!isRecord(entry)) continue;
16393
+ const percent = asNumber(entry.percent);
16394
+ if (percent === null) continue;
16395
+ const common = {
16396
+ usedPct: percent,
16397
+ resetsAt: asIsoString(entry.resets_at)
16398
+ };
16399
+ if (typeof entry.severity === "string") common.severity = entry.severity;
16400
+ if (entry.is_active === true) common.active = true;
16401
+ if (entry.kind === "session") {
16402
+ windows.push({ key: "session", label: "5h", short: "5h", ...common });
16403
+ } else if (entry.kind === "weekly_all") {
16404
+ windows.push({ key: "weekly", label: "Weekly", short: "wk", ...common });
16405
+ } else if (entry.kind === "weekly_scoped") {
16406
+ const name = scopeName(entry.scope);
16407
+ if (name === null) continue;
16408
+ windows.push({ key: `weekly_scoped.${slug(name)}`, label: name, scope: name, ...common });
16409
+ }
16410
+ }
16411
+ return windows;
16412
+ }
16413
+ function windowsFromFlat(body) {
16414
+ const windows = [];
16415
+ const fiveHour = bucketWindow(body.five_hour, "session", "5h", "5h");
16416
+ if (fiveHour !== null) windows.push(fiveHour);
16417
+ const sevenDay = bucketWindow(body.seven_day, "weekly", "Weekly", "wk");
16418
+ if (sevenDay !== null) windows.push(sevenDay);
16419
+ return windows;
16420
+ }
16421
+ function bucketWindow(raw, key, label, short) {
16422
+ if (!isRecord(raw)) return null;
16423
+ const usedPct = asNumber(raw.utilization);
16424
+ if (usedPct === null) return null;
16425
+ return { key, label, short, usedPct, resetsAt: asIsoString(raw.resets_at) };
16426
+ }
16427
+ function scopeName(raw) {
16428
+ if (!isRecord(raw)) return null;
16429
+ const model = raw.model;
16430
+ if (!isRecord(model)) return null;
16431
+ const name = typeof model.display_name === "string" ? model.display_name.trim() : "";
16432
+ if (name.length === 0) return null;
16433
+ return slug(name) === "all-models" || slug(name) === "all" ? null : name;
16434
+ }
16435
+ function usageCredits(raw) {
16436
+ if (!isRecord(raw) || raw.enabled !== true) return null;
16437
+ const percent = asNumber(raw.percent);
16438
+ const limit = majorAmount(raw.limit);
16439
+ if (limit === null) {
16440
+ return percent === null ? null : { display: `${percent.toFixed(0)}% used`, usedPct: percent };
16441
+ }
16442
+ const used = majorAmount(raw.used);
16443
+ if (used === null) return null;
16444
+ const currency = isRecord(raw.limit) && typeof raw.limit.currency === "string" ? raw.limit.currency : "USD";
16445
+ const symbol = currency === "USD" ? "$" : "";
16446
+ const usedPct = percent ?? (limit > 0 ? used / limit * 100 : null);
16447
+ return { display: `${symbol}${used.toFixed(2)} / ${symbol}${limit.toFixed(2)}`, usedPct };
16448
+ }
16449
+ function majorAmount(raw) {
16450
+ if (!isRecord(raw)) return null;
16451
+ const minor = asNumber(raw.amount_minor);
16452
+ if (minor === null) return null;
16453
+ const exponent = asNumber(raw.exponent) ?? 0;
16454
+ return minor / 10 ** exponent;
16455
+ }
16456
+ function isRecord(value) {
16457
+ return typeof value === "object" && value !== null && !Array.isArray(value);
16458
+ }
16459
+ function asNumber(value) {
16460
+ if (typeof value === "number") return Number.isFinite(value) ? value : null;
16461
+ if (typeof value === "string") {
16462
+ const parsed = Number.parseFloat(value.trim());
16463
+ return Number.isFinite(parsed) ? parsed : null;
16464
+ }
16465
+ return null;
16466
+ }
16467
+ function asIsoString(value) {
16468
+ if (typeof value !== "string" || value.trim().length === 0) return null;
16469
+ const parsed = new Date(value);
16470
+ return Number.isNaN(parsed.getTime()) ? null : parsed.toISOString();
16471
+ }
16472
+ function slug(value) {
16473
+ return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
16474
+ }
16475
+ function describeError2(error, timeoutMs) {
16476
+ if (error instanceof Error) {
16477
+ return error.name === "AbortError" ? `timeout after ${timeoutMs}ms` : error.message;
16478
+ }
16479
+ return String(error);
16480
+ }
16481
+ async function discardBody(response) {
16482
+ try {
16483
+ await response.body?.cancel();
16484
+ } catch {
16485
+ return;
16486
+ }
16487
+ }
16488
+
16489
+ // src/domains/quota/anthropic-max-provider.ts
16490
+ var ANTHROPIC_MAX_QUOTA_PROVIDER_ID = "anthropic-max";
16491
+ var AUTH_PROVIDER_ID = "anthropic";
16492
+ var DISPLAY_NAME = "Anthropic Max";
16493
+ var REQUEST_TIMEOUT_MS = 15e3;
16494
+ function parseStoredAnthropicCredential(raw) {
16495
+ if (!isRecord2(raw) || raw.type !== "oauth") return null;
16496
+ const access = raw.access;
16497
+ if (typeof access !== "string" || access.length === 0) return null;
16498
+ const refresh = raw.refresh;
16499
+ return {
16500
+ accessToken: access,
16501
+ expiresAtMs: asNumber2(raw.expires),
16502
+ hasRefreshToken: typeof refresh === "string" && refresh.length > 0
16503
+ };
16504
+ }
16505
+ async function readStoredCredential() {
16506
+ try {
16507
+ return parseStoredAnthropicCredential(openAuthStorage().get(AUTH_PROVIDER_ID));
16508
+ } catch {
16509
+ return null;
16510
+ }
16511
+ }
16512
+ function createAnthropicMaxQuotaProvider(options = {}) {
16513
+ const readCredentials = options.readCredentials ?? readStoredCredential;
16514
+ const doFetch = options.fetch ?? globalThis.fetch;
16515
+ const timeoutMs = options.timeoutMs ?? REQUEST_TIMEOUT_MS;
16516
+ const now = options.now ?? (() => /* @__PURE__ */ new Date());
16517
+ const snapshot = (status, message, extra = {}) => ({
16518
+ providerId: ANTHROPIC_MAX_QUOTA_PROVIDER_ID,
16519
+ displayName: DISPLAY_NAME,
16520
+ status,
16521
+ windows: [],
16522
+ message,
16523
+ fetchedAt: now().toISOString(),
16524
+ ...extra
16525
+ });
16526
+ return {
16527
+ id: ANTHROPIC_MAX_QUOTA_PROVIDER_ID,
16528
+ displayName: DISPLAY_NAME,
16529
+ async detect() {
16530
+ return await readCredentials() !== null;
16531
+ },
16532
+ async fetch() {
16533
+ const credentials = await readCredentials();
16534
+ if (credentials === null) {
16535
+ return snapshot("no_credentials", "Clio holds no Anthropic OAuth credential");
16536
+ }
16537
+ if (credentials.expiresAtMs !== null && credentials.expiresAtMs <= now().getTime()) {
16538
+ return snapshot(
16539
+ "expired",
16540
+ credentials.hasRefreshToken ? "Clio's Anthropic token expired; the next turn refreshes it" : "Run clio-coder auth login anthropic again"
16541
+ );
16542
+ }
16543
+ const usage = await fetchAnthropicUsage(credentials.accessToken, { fetch: doFetch, timeoutMs });
16544
+ if (usage.status !== "ok") {
16545
+ return snapshot(usage.status, usage.message ?? "Run clio-coder auth login anthropic again", {
16546
+ retryAfterSeconds: usage.retryAfterSeconds
16547
+ });
16548
+ }
16549
+ return snapshot("ok", null, { windows: usage.windows, credits: usage.credits });
16550
+ }
16551
+ };
16552
+ }
16553
+ function isRecord2(value) {
16554
+ return typeof value === "object" && value !== null && !Array.isArray(value);
16555
+ }
16556
+ function asNumber2(value) {
16557
+ if (typeof value === "number") return Number.isFinite(value) ? value : null;
16558
+ if (typeof value === "string") {
16559
+ const parsed = Number.parseFloat(value.trim());
16560
+ return Number.isFinite(parsed) ? parsed : null;
16561
+ }
16562
+ return null;
16563
+ }
16564
+
16565
+ // src/domains/quota/antigravity-provider.ts
16566
+ init_esm_shims();
16567
+ import { readFile } from "node:fs/promises";
16568
+ import { homedir as homedir2 } from "node:os";
16569
+ import { join } from "node:path";
16570
+ var ANTIGRAVITY_QUOTA_PROVIDER_ID = "antigravity";
16571
+ var DISPLAY_NAME2 = "Antigravity";
16572
+ var USER_AGENT = "antigravity";
16573
+ var REQUEST_TIMEOUT_MS2 = 2e4;
16574
+ var EXPIRY_SKEW_MS = 6e4;
16575
+ var WINDOW_ORDER2 = { session: 0, weekly: 1 };
16576
+ var MODEL_PREFIXES = ["gemini", "claude", "gpt", "image", "imagen"];
16577
+ var ENDPOINTS = [
16578
+ "https://daily-cloudcode-pa.googleapis.com",
16579
+ "https://daily-cloudcode-pa.sandbox.googleapis.com",
16580
+ "https://cloudcode-pa.googleapis.com"
16581
+ ];
16582
+ function defaultCredentialsPath() {
16583
+ return join(homedir2(), ".gemini", "antigravity-cli", "antigravity-oauth-token");
16584
+ }
16585
+ function parseCredentials(raw) {
16586
+ if (!isRecord3(raw)) return null;
16587
+ const token = raw.token;
16588
+ if (!isRecord3(token)) return null;
16589
+ const accessToken = token.access_token;
16590
+ if (typeof accessToken !== "string" || accessToken.length === 0) return null;
16591
+ const refreshToken = token.refresh_token;
16592
+ return {
16593
+ accessToken,
16594
+ refreshToken: typeof refreshToken === "string" && refreshToken.length > 0 ? refreshToken : null,
16595
+ expiryMs: asEpochMs(token.expiry)
16596
+ };
16597
+ }
16598
+ async function readCredentialsFile(path) {
16599
+ let text;
16600
+ try {
16601
+ text = await readFile(path, "utf8");
16602
+ } catch {
16603
+ return null;
16604
+ }
16605
+ try {
16606
+ return parseCredentials(JSON.parse(text));
16607
+ } catch {
16608
+ return null;
16609
+ }
16610
+ }
16611
+ function createAntigravityQuotaProvider(options = {}) {
16612
+ const credentialsPath = options.credentialsPath ?? defaultCredentialsPath();
16613
+ const readCredentials = options.readCredentials ?? (() => readCredentialsFile(credentialsPath));
16614
+ const doFetch = options.fetch ?? globalThis.fetch;
16615
+ const timeoutMs = options.timeoutMs ?? REQUEST_TIMEOUT_MS2;
16616
+ const now = options.now ?? (() => /* @__PURE__ */ new Date());
16617
+ const snapshot = (status, message, extra = {}) => ({
16618
+ providerId: ANTIGRAVITY_QUOTA_PROVIDER_ID,
16619
+ displayName: DISPLAY_NAME2,
16620
+ status,
16621
+ windows: [],
16622
+ message,
16623
+ fetchedAt: now().toISOString(),
16624
+ ...extra
16625
+ });
16626
+ const post = async (url, token, body) => {
16627
+ const controller = new AbortController();
16628
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
16629
+ let response;
16630
+ try {
16631
+ response = await doFetch(url, {
16632
+ method: "POST",
16633
+ headers: {
16634
+ Authorization: `Bearer ${token}`,
16635
+ "Content-Type": "application/json",
16636
+ "User-Agent": USER_AGENT
16637
+ },
16638
+ body: JSON.stringify(body),
16639
+ signal: controller.signal
16640
+ });
16641
+ } catch (error) {
16642
+ return { kind: "error", message: describeError3(error, timeoutMs) };
16643
+ } finally {
16644
+ clearTimeout(timer);
16645
+ }
16646
+ if (response.status === 401 || response.status === 403) {
16647
+ await discardBody2(response);
16648
+ return { kind: "auth" };
16649
+ }
16650
+ if (response.status !== 200) {
16651
+ await discardBody2(response);
16652
+ return { kind: "error", message: `HTTP ${response.status}` };
16653
+ }
16654
+ let data;
16655
+ try {
16656
+ data = await response.json();
16657
+ } catch {
16658
+ return { kind: "error", message: "Unexpected usage response" };
16659
+ }
16660
+ return isRecord3(data) ? { kind: "ok", data } : { kind: "error", message: "Unexpected usage response" };
16661
+ };
16662
+ const collect = async (base, token) => {
16663
+ const load = await post(`${base}/v1internal:loadCodeAssist`, token, { metadata: { ideType: "ANTIGRAVITY" } });
16664
+ if (load.kind !== "ok") return load;
16665
+ const project = typeof load.data.cloudaicompanionProject === "string" ? load.data.cloudaicompanionProject : null;
16666
+ const plan = tierPlan(load.data.currentTier);
16667
+ if (project !== null) {
16668
+ const summary = await post(`${base}/v1internal:retrieveUserQuotaSummary`, token, { project });
16669
+ if (summary.kind === "auth") return summary;
16670
+ if (summary.kind === "ok") {
16671
+ const windows = windowsFromSummary(summary.data);
16672
+ if (windows.length > 0) return { kind: "ok", windows, plan };
16673
+ }
16674
+ }
16675
+ const models = await post(`${base}/v1internal:fetchAvailableModels`, token, project === null ? {} : { project });
16676
+ if (models.kind !== "ok") return models;
16677
+ return { kind: "ok", windows: modelWindows(models.data), plan };
16678
+ };
16679
+ return {
16680
+ id: ANTIGRAVITY_QUOTA_PROVIDER_ID,
16681
+ displayName: DISPLAY_NAME2,
16682
+ async detect() {
16683
+ return await readCredentials() !== null;
16684
+ },
16685
+ async fetch() {
16686
+ const credentials = await readCredentials();
16687
+ if (credentials === null) {
16688
+ return snapshot("no_credentials", "No Antigravity credentials found");
16689
+ }
16690
+ if (credentials.expiryMs !== null && credentials.expiryMs <= now().getTime() + EXPIRY_SKEW_MS) {
16691
+ return snapshot("expired", "Antigravity's stored token expired; run the agy command once to refresh it");
16692
+ }
16693
+ let sawAuthFailure = false;
16694
+ let lastError = null;
16695
+ for (const base of ENDPOINTS) {
16696
+ const result = await collect(base, credentials.accessToken);
16697
+ if (result.kind === "auth") {
16698
+ sawAuthFailure = true;
16699
+ continue;
16700
+ }
16701
+ if (result.kind === "error") {
16702
+ lastError = result.message;
16703
+ continue;
16704
+ }
16705
+ if (result.windows.length > 0) {
16706
+ return snapshot("ok", null, { windows: result.windows, plan: result.plan });
16707
+ }
16708
+ }
16709
+ if (sawAuthFailure) return snapshot("expired", "Sign in with Antigravity again");
16710
+ if (lastError !== null) return snapshot("error", lastError);
16711
+ return snapshot("error", "Antigravity returned no quota");
16712
+ }
16713
+ };
16714
+ }
16715
+ function tierPlan(raw) {
16716
+ if (!isRecord3(raw)) return null;
16717
+ const plan = formatPlan(typeof raw.name === "string" ? raw.name : null);
16718
+ if (plan === null) return null;
16719
+ return plan.toLowerCase() === DISPLAY_NAME2.toLowerCase() ? null : plan;
16720
+ }
16721
+ function windowsFromSummary(data) {
16722
+ if (!Array.isArray(data.groups)) return [];
16723
+ const groups = data.groups.filter(isRecord3).sort((a, b) => Number(isGeminiGroup(b)) - Number(isGeminiGroup(a)));
16724
+ return groups.flatMap((group, index) => {
16725
+ const scope = typeof group.displayName === "string" && group.displayName.trim() ? group.displayName.trim() : `Model group ${index + 1}`;
16726
+ return windowsFromGroup(group).map((window) => ({
16727
+ ...window,
16728
+ key: index === 0 ? window.key : `group.${index}.${window.key}`,
16729
+ scope
16730
+ }));
16112
16731
  });
16113
- deps.registerInputListener((data) => {
16114
- deps.onInputIngress?.(classifyInputAction(data, deps), data);
16115
- return controller.handleInput(data);
16732
+ }
16733
+ function windowsFromGroup(group) {
16734
+ if (!Array.isArray(group.buckets)) return [];
16735
+ const windows = [];
16736
+ for (const bucket of group.buckets) {
16737
+ if (!isRecord3(bucket)) continue;
16738
+ const window = typeof bucket.window === "string" ? bucket.window.toLowerCase() : null;
16739
+ const usedPct = usedFromRemaining(bucket.remainingFraction);
16740
+ if (usedPct === null) continue;
16741
+ if (window === "5h") {
16742
+ windows.push({ key: "session", label: "5h", short: "5h", usedPct, resetsAt: asIsoString2(bucket.resetTime) });
16743
+ } else if (window === "weekly") {
16744
+ windows.push({ key: "weekly", label: "Weekly", short: "wk", usedPct, resetsAt: asIsoString2(bucket.resetTime) });
16745
+ }
16746
+ }
16747
+ return windows.sort((left, right) => (WINDOW_ORDER2[left.key] ?? 2) - (WINDOW_ORDER2[right.key] ?? 2));
16748
+ }
16749
+ function isGeminiGroup(group) {
16750
+ if (mentionsGemini(group.displayName) || mentionsGemini(group.description)) return true;
16751
+ if (!Array.isArray(group.buckets)) return false;
16752
+ return group.buckets.some((bucket) => {
16753
+ if (!isRecord3(bucket)) return false;
16754
+ const id = typeof bucket.bucketId === "string" ? bucket.bucketId.toLowerCase() : "";
16755
+ return id.startsWith("gemini-") || mentionsGemini(bucket.displayName);
16116
16756
  });
16117
- return controller;
16118
16757
  }
16119
- function classifyInputAction(data, deps) {
16120
- if (data.length === 0 || isKeyRelease(data)) return "no-visual-change";
16121
- if (deps.keybindings.matches(data, "tui.input.submit")) return "submit";
16122
- const sgrMousePrefix = `${String.fromCharCode(27)}[<`;
16123
- if (data.startsWith(`${sgrMousePrefix}64;`) || data.startsWith(`${sgrMousePrefix}65;`) || deps.keybindings.matches(data, "tui.altScreen.pageUp") || deps.keybindings.matches(data, "tui.altScreen.pageDown") || deps.keybindings.matches(data, "tui.editor.pageUp") || deps.keybindings.matches(data, "tui.editor.pageDown")) {
16124
- return "scroll";
16758
+ function mentionsGemini(value) {
16759
+ return typeof value === "string" && value.toLowerCase().includes("gemini");
16760
+ }
16761
+ function modelWindows(data) {
16762
+ if (!isRecord3(data.models)) return [];
16763
+ const windows = [];
16764
+ for (const [name, info] of Object.entries(data.models)) {
16765
+ if (!isRecord3(info) || !isRecord3(info.quotaInfo)) continue;
16766
+ if (!MODEL_PREFIXES.some((prefix) => name.toLowerCase().startsWith(prefix))) continue;
16767
+ const usedPct = usedFromRemaining(info.quotaInfo.remainingFraction);
16768
+ if (usedPct === null) continue;
16769
+ windows.push({
16770
+ key: `model.${name}`,
16771
+ label: "Model quota",
16772
+ short: name,
16773
+ scope: name,
16774
+ usedPct,
16775
+ resetsAt: asIsoString2(info.quotaInfo.resetTime)
16776
+ });
16125
16777
  }
16126
- if (deps.overlay.getState() !== "closed") return "overlay";
16127
- return "editor";
16778
+ return windows;
16128
16779
  }
16129
- function overlayScopeLabel(state) {
16130
- const names = {
16131
- closed: "Composer",
16132
- "permission-confirm": "Permission",
16133
- "dispatch-board": "Workers",
16134
- auth: "Authentication",
16135
- cost: "Usage",
16136
- "context-view": "Context",
16137
- "context-reset": "Context reset",
16138
- tasks: "Tasks",
16139
- decisions: "Decisions",
16140
- memory: "Memory",
16141
- view: "View",
16142
- model: "Model",
16143
- "model-scope": "Model cycle set",
16144
- settings: "Settings",
16145
- resume: "Resume",
16146
- tree: "Session tree",
16147
- "message-picker": "Messages",
16148
- "cwd-fallback": "Working directory",
16149
- "ask-user": "Interview",
16150
- help: "Help",
16151
- extensions: "Extensions",
16152
- interop: "Interop",
16153
- "skills-hub": "Library",
16154
- "side-question": "Side question",
16155
- "handoff-review": "Handoff review",
16156
- "fleet-run-approval": "Run approval"
16780
+ function usedFromRemaining(raw) {
16781
+ const remaining = asNumber3(raw);
16782
+ if (remaining === null) return null;
16783
+ return (1 - Math.min(1, Math.max(0, remaining))) * 100;
16784
+ }
16785
+ function isRecord3(value) {
16786
+ return typeof value === "object" && value !== null && !Array.isArray(value);
16787
+ }
16788
+ function asNumber3(value) {
16789
+ if (typeof value === "number") return Number.isFinite(value) ? value : null;
16790
+ if (typeof value === "string") {
16791
+ const parsed = Number.parseFloat(value.trim());
16792
+ return Number.isFinite(parsed) ? parsed : null;
16793
+ }
16794
+ return null;
16795
+ }
16796
+ function asIsoString2(value) {
16797
+ if (typeof value !== "string" || value.trim().length === 0) return null;
16798
+ const parsed = new Date(value);
16799
+ return Number.isNaN(parsed.getTime()) ? null : parsed.toISOString();
16800
+ }
16801
+ function asEpochMs(value) {
16802
+ if (typeof value !== "string" || value.trim().length === 0) return null;
16803
+ const parsed = Date.parse(value);
16804
+ return Number.isNaN(parsed) ? null : parsed;
16805
+ }
16806
+ function describeError3(error, timeoutMs) {
16807
+ if (error instanceof Error) {
16808
+ return error.name === "AbortError" ? `timeout after ${timeoutMs}ms` : error.message;
16809
+ }
16810
+ return String(error);
16811
+ }
16812
+ async function discardBody2(response) {
16813
+ try {
16814
+ await response.body?.cancel();
16815
+ } catch {
16816
+ return;
16817
+ }
16818
+ }
16819
+
16820
+ // src/domains/quota/claude-code-provider.ts
16821
+ init_esm_shims();
16822
+ import { readFile as readFile2 } from "node:fs/promises";
16823
+ import { homedir as homedir3 } from "node:os";
16824
+ import { join as join2 } from "node:path";
16825
+ var CLAUDE_CODE_QUOTA_PROVIDER_ID = "claude-code";
16826
+ var DISPLAY_NAME3 = "Claude Code";
16827
+ var REQUEST_TIMEOUT_MS3 = 15e3;
16828
+ function defaultClaudeCredentialsPath() {
16829
+ return join2(homedir3(), ".claude", ".credentials.json");
16830
+ }
16831
+ function parseClaudeCredentials(raw) {
16832
+ if (!isRecord4(raw)) return null;
16833
+ const oauth = raw.claudeAiOauth;
16834
+ if (!isRecord4(oauth)) return null;
16835
+ const token = oauth.accessToken;
16836
+ if (typeof token !== "string" || token.length === 0) return null;
16837
+ const subscription = oauth.subscriptionType;
16838
+ const refreshToken = oauth.refreshToken;
16839
+ return {
16840
+ accessToken: token,
16841
+ subscriptionType: typeof subscription === "string" ? subscription : null,
16842
+ expiresAtMs: asNumber4(oauth.expiresAt),
16843
+ refreshTokenExpiresAtMs: asNumber4(oauth.refreshTokenExpiresAt),
16844
+ hasRefreshToken: typeof refreshToken === "string" && refreshToken.length > 0
16157
16845
  };
16158
- return names[state];
16846
+ }
16847
+ function expiryMessage(credentials, nowMs) {
16848
+ const { expiresAtMs, refreshTokenExpiresAtMs, hasRefreshToken } = credentials;
16849
+ if (expiresAtMs === null || expiresAtMs > nowMs) return null;
16850
+ const refreshUsable = hasRefreshToken && (refreshTokenExpiresAtMs === null || refreshTokenExpiresAtMs > nowMs);
16851
+ return refreshUsable ? "Claude Code's stored token expired; run the claude command once to refresh it" : "Sign in with Claude Code again";
16852
+ }
16853
+ async function readCredentialsFile2(path) {
16854
+ let text;
16855
+ try {
16856
+ text = await readFile2(path, "utf8");
16857
+ } catch {
16858
+ return null;
16859
+ }
16860
+ try {
16861
+ return parseClaudeCredentials(JSON.parse(text));
16862
+ } catch {
16863
+ return null;
16864
+ }
16865
+ }
16866
+ function createClaudeCodeQuotaProvider(options = {}) {
16867
+ const credentialsPath = options.credentialsPath ?? defaultClaudeCredentialsPath();
16868
+ const readCredentials = options.readCredentials ?? (() => readCredentialsFile2(credentialsPath));
16869
+ const doFetch = options.fetch ?? globalThis.fetch;
16870
+ const timeoutMs = options.timeoutMs ?? REQUEST_TIMEOUT_MS3;
16871
+ const now = options.now ?? (() => /* @__PURE__ */ new Date());
16872
+ const snapshot = (status, message, extra = {}) => ({
16873
+ providerId: CLAUDE_CODE_QUOTA_PROVIDER_ID,
16874
+ displayName: DISPLAY_NAME3,
16875
+ status,
16876
+ windows: [],
16877
+ message,
16878
+ fetchedAt: now().toISOString(),
16879
+ ...extra
16880
+ });
16881
+ return {
16882
+ id: CLAUDE_CODE_QUOTA_PROVIDER_ID,
16883
+ displayName: DISPLAY_NAME3,
16884
+ async detect() {
16885
+ return await readCredentials() !== null;
16886
+ },
16887
+ async fetch() {
16888
+ const credentials = await readCredentials();
16889
+ if (credentials === null) {
16890
+ return snapshot("no_credentials", "No Claude Code credentials found");
16891
+ }
16892
+ const expired = expiryMessage(credentials, now().getTime());
16893
+ if (expired !== null) {
16894
+ return snapshot("expired", expired);
16895
+ }
16896
+ const usage = await fetchAnthropicUsage(credentials.accessToken, { fetch: doFetch, timeoutMs });
16897
+ if (usage.status !== "ok") {
16898
+ return snapshot(usage.status, usage.message ?? "Sign in with Claude Code again", {
16899
+ retryAfterSeconds: usage.retryAfterSeconds
16900
+ });
16901
+ }
16902
+ return snapshot("ok", null, {
16903
+ windows: usage.windows,
16904
+ credits: usage.credits,
16905
+ plan: formatPlan(credentials.subscriptionType)
16906
+ });
16907
+ }
16908
+ };
16909
+ }
16910
+ function isRecord4(value) {
16911
+ return typeof value === "object" && value !== null && !Array.isArray(value);
16912
+ }
16913
+ function asNumber4(value) {
16914
+ if (typeof value === "number") return Number.isFinite(value) ? value : null;
16915
+ if (typeof value === "string") {
16916
+ const parsed = Number.parseFloat(value.trim());
16917
+ return Number.isFinite(parsed) ? parsed : null;
16918
+ }
16919
+ return null;
16159
16920
  }
16160
16921
 
16161
- // src/interactive/interactive-presentation.ts
16922
+ // src/domains/quota/codex-provider.ts
16162
16923
  init_esm_shims();
16163
- import { performance as performance3 } from "node:perf_hooks";
16924
+ import { readFile as readFile3 } from "node:fs/promises";
16925
+ import { homedir as homedir4 } from "node:os";
16926
+ import { join as join3 } from "node:path";
16927
+ var CODEX_QUOTA_PROVIDER_ID = "codex";
16928
+ var CODEX_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
16929
+ var DISPLAY_NAME4 = "Codex";
16930
+ var REQUEST_TIMEOUT_MS4 = 15e3;
16931
+ var WINDOW_ORDER3 = { session: 0, weekly: 1 };
16932
+ var WEEKLY_THRESHOLD_SECONDS = 86400;
16933
+ function defaultCodexCredentialsPath() {
16934
+ const codexHome = process.env.CODEX_HOME;
16935
+ return codexHome ? join3(codexHome, "auth.json") : join3(homedir4(), ".codex", "auth.json");
16936
+ }
16937
+ function parseCodexCredentials(raw) {
16938
+ if (!isRecord5(raw)) return null;
16939
+ const tokens = raw.tokens;
16940
+ if (!isRecord5(tokens)) return null;
16941
+ const accessToken = tokens.access_token;
16942
+ if (typeof accessToken !== "string" || accessToken.length === 0) return null;
16943
+ const accountId = tokens.account_id;
16944
+ return {
16945
+ accessToken,
16946
+ accountId: typeof accountId === "string" ? accountId : null
16947
+ };
16948
+ }
16949
+ async function readCredentialsFile3(path) {
16950
+ let text;
16951
+ try {
16952
+ text = await readFile3(path, "utf8");
16953
+ } catch {
16954
+ return null;
16955
+ }
16956
+ try {
16957
+ return parseCodexCredentials(JSON.parse(text));
16958
+ } catch {
16959
+ return null;
16960
+ }
16961
+ }
16962
+ function createCodexQuotaProvider(options = {}) {
16963
+ const credentialsPath = options.credentialsPath ?? defaultCodexCredentialsPath();
16964
+ const readCredentials = options.readCredentials ?? (() => readCredentialsFile3(credentialsPath));
16965
+ const doFetch = options.fetch ?? globalThis.fetch;
16966
+ const timeoutMs = options.timeoutMs ?? REQUEST_TIMEOUT_MS4;
16967
+ const now = options.now ?? (() => /* @__PURE__ */ new Date());
16968
+ const snapshot = (status, message, extra = {}) => ({
16969
+ providerId: CODEX_QUOTA_PROVIDER_ID,
16970
+ displayName: DISPLAY_NAME4,
16971
+ status,
16972
+ windows: [],
16973
+ message,
16974
+ fetchedAt: now().toISOString(),
16975
+ ...extra
16976
+ });
16977
+ return {
16978
+ id: CODEX_QUOTA_PROVIDER_ID,
16979
+ displayName: DISPLAY_NAME4,
16980
+ async detect() {
16981
+ return await readCredentials() !== null;
16982
+ },
16983
+ async fetch() {
16984
+ const credentials = await readCredentials();
16985
+ if (credentials === null) {
16986
+ return snapshot("no_credentials", "No Codex credentials found");
16987
+ }
16988
+ const headers = {
16989
+ Authorization: `Bearer ${credentials.accessToken}`,
16990
+ "User-Agent": "codex-cli"
16991
+ };
16992
+ if (credentials.accountId !== null) {
16993
+ headers["ChatGPT-Account-Id"] = credentials.accountId;
16994
+ }
16995
+ const controller = new AbortController();
16996
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
16997
+ let response;
16998
+ try {
16999
+ response = await doFetch(CODEX_USAGE_URL, {
17000
+ method: "GET",
17001
+ headers,
17002
+ signal: controller.signal
17003
+ });
17004
+ } catch (error) {
17005
+ return snapshot("error", describeError4(error, timeoutMs));
17006
+ } finally {
17007
+ clearTimeout(timer);
17008
+ }
17009
+ if (response.status === 401 || response.status === 403) {
17010
+ await discardBody3(response);
17011
+ return snapshot("expired", "Sign in with Codex again");
17012
+ }
17013
+ if (response.status !== 200) {
17014
+ await discardBody3(response);
17015
+ return snapshot("error", `HTTP ${response.status}`);
17016
+ }
17017
+ let body;
17018
+ try {
17019
+ body = await response.json();
17020
+ } catch {
17021
+ return snapshot("error", "Unexpected usage response");
17022
+ }
17023
+ if (!isRecord5(body)) {
17024
+ return snapshot("error", "Unexpected usage response");
17025
+ }
17026
+ const planType = typeof body.plan_type === "string" ? body.plan_type : null;
17027
+ return snapshot("ok", null, {
17028
+ windows: usageWindows2(body.rate_limit),
17029
+ credits: usageCredits2(body.credits),
17030
+ plan: formatPlan(planType)
17031
+ });
17032
+ }
17033
+ };
17034
+ }
17035
+ function usageWindows2(raw) {
17036
+ if (!isRecord5(raw)) return [];
17037
+ const windows = [];
17038
+ const primary = buildWindow(raw.primary_window, "session");
17039
+ if (primary !== null) windows.push(primary);
17040
+ const secondary = buildWindow(raw.secondary_window, "weekly");
17041
+ if (secondary !== null) windows.push(secondary);
17042
+ return windows.sort((left, right) => {
17043
+ const order = (WINDOW_ORDER3[left.key] ?? 2) - (WINDOW_ORDER3[right.key] ?? 2);
17044
+ return order !== 0 ? order : left.label.localeCompare(right.label);
17045
+ });
17046
+ }
17047
+ function buildWindow(raw, defaultKey) {
17048
+ if (!isRecord5(raw)) return null;
17049
+ const usedPct = asNumber5(raw.used_percent);
17050
+ if (usedPct === null) return null;
17051
+ const key = windowKey(raw.limit_window_seconds, defaultKey);
17052
+ const label = key === "weekly" ? "Weekly" : "5h";
17053
+ const short = key === "weekly" ? "wk" : "5h";
17054
+ return { key, label, short, usedPct, resetsAt: epochSecondsToIso(raw.reset_at) };
17055
+ }
17056
+ function windowKey(limitWindowSeconds, defaultKey) {
17057
+ const seconds = asNumber5(limitWindowSeconds);
17058
+ if (seconds === null) return defaultKey;
17059
+ return seconds >= WEEKLY_THRESHOLD_SECONDS ? "weekly" : "session";
17060
+ }
17061
+ function usageCredits2(raw) {
17062
+ if (!isRecord5(raw)) return null;
17063
+ if (raw.unlimited === true) return { display: "Unlimited", usedPct: null };
17064
+ if (raw.has_credits === true && typeof raw.balance === "string" && raw.balance.length > 0) {
17065
+ return { display: raw.balance, usedPct: null };
17066
+ }
17067
+ return null;
17068
+ }
17069
+ function isRecord5(value) {
17070
+ return typeof value === "object" && value !== null && !Array.isArray(value);
17071
+ }
17072
+ function asNumber5(value) {
17073
+ if (typeof value === "number") return Number.isFinite(value) ? value : null;
17074
+ if (typeof value === "string") {
17075
+ const parsed = Number.parseFloat(value.trim());
17076
+ return Number.isFinite(parsed) ? parsed : null;
17077
+ }
17078
+ return null;
17079
+ }
17080
+ function epochSecondsToIso(value) {
17081
+ const seconds = asNumber5(value);
17082
+ if (seconds === null) return null;
17083
+ const date = new Date(seconds * 1e3);
17084
+ return Number.isNaN(date.getTime()) ? null : date.toISOString();
17085
+ }
17086
+ function describeError4(error, timeoutMs) {
17087
+ if (error instanceof Error) {
17088
+ return error.name === "AbortError" ? `timeout after ${timeoutMs}ms` : error.message;
17089
+ }
17090
+ return String(error);
17091
+ }
17092
+ async function discardBody3(response) {
17093
+ try {
17094
+ await response.body?.cancel();
17095
+ } catch {
17096
+ return;
17097
+ }
17098
+ }
17099
+
17100
+ // src/domains/quota/registry.ts
17101
+ function buildQuotaProviders() {
17102
+ return [
17103
+ createAnthropicMaxQuotaProvider(),
17104
+ createClaudeCodeQuotaProvider(),
17105
+ createCodexQuotaProvider(),
17106
+ createAntigravityQuotaProvider()
17107
+ // The copilot adapter attaches here once its credential shape is confirmed.
17108
+ ];
17109
+ }
17110
+
17111
+ // src/domains/quota/service.ts
17112
+ function createQuotaService(options = {}) {
17113
+ const providers = options.providers ?? buildQuotaProviders();
17114
+ const cache = options.cache ?? createQuotaCache({ ttlMs: options.ttlMs ?? DEFAULT_QUOTA_CACHE_TTL_MS });
17115
+ const includeLocal = options.includeLocal ?? true;
17116
+ const withLocal = (snapshots) => {
17117
+ if (!includeLocal) return snapshots;
17118
+ const local = options.localRuntimeLabel ? localQuotaSnapshot({ runtimeLabel: options.localRuntimeLabel }) : localQuotaSnapshot();
17119
+ return [...snapshots, local];
17120
+ };
17121
+ return {
17122
+ peek() {
17123
+ const cached = [];
17124
+ for (const provider of providers) {
17125
+ const snapshot = cache.readLastGood(provider.id);
17126
+ if (snapshot !== null) cached.push(snapshot);
17127
+ }
17128
+ return withLocal(cached);
17129
+ },
17130
+ async read() {
17131
+ const settled = await Promise.all(
17132
+ providers.map(async (provider) => {
17133
+ try {
17134
+ return await cache.resolve(provider);
17135
+ } catch (error) {
17136
+ return {
17137
+ providerId: provider.id,
17138
+ displayName: provider.displayName,
17139
+ status: "error",
17140
+ windows: [],
17141
+ message: error instanceof Error ? error.message : String(error),
17142
+ fetchedAt: null
17143
+ };
17144
+ }
17145
+ })
17146
+ );
17147
+ return withLocal(settled.filter((snapshot) => snapshot.status !== "no_credentials"));
17148
+ },
17149
+ async detected() {
17150
+ const flags = await Promise.all(
17151
+ providers.map(async (provider) => {
17152
+ try {
17153
+ return await provider.detect();
17154
+ } catch {
17155
+ return false;
17156
+ }
17157
+ })
17158
+ );
17159
+ return providers.filter((_, index) => flags[index] === true);
17160
+ }
17161
+ };
17162
+ }
17163
+
17164
+ // src/domains/quota/summary-feed.ts
17165
+ var QUOTA_SUMMARY_TTL_MS = 5 * 6e4;
17166
+ function createQuotaSummaryFeed(options = {}) {
17167
+ const service = options.service ?? createQuotaService();
17168
+ const ttlMs = options.ttlMs ?? QUOTA_SUMMARY_TTL_MS;
17169
+ const now = options.now ?? Date.now;
17170
+ let line = null;
17171
+ let snapshots = [];
17172
+ let readAt = null;
17173
+ let inFlight = false;
17174
+ let disposed = false;
17175
+ const refresh = () => {
17176
+ if (inFlight || disposed) return;
17177
+ inFlight = true;
17178
+ void Promise.resolve().then(() => disposed ? snapshots : service.read()).then((nextSnapshots) => {
17179
+ if (disposed) return;
17180
+ const next = quotaSummaryLine(nextSnapshots);
17181
+ const changed = JSON.stringify(nextSnapshots) !== JSON.stringify(snapshots);
17182
+ snapshots = nextSnapshots;
17183
+ readAt = now();
17184
+ if (changed) {
17185
+ line = next;
17186
+ options.onUpdate?.();
17187
+ }
17188
+ }).catch(() => {
17189
+ readAt = now();
17190
+ }).finally(() => {
17191
+ inFlight = false;
17192
+ });
17193
+ };
17194
+ return {
17195
+ peek() {
17196
+ if (!disposed && (readAt === null || now() - readAt >= ttlMs)) refresh();
17197
+ return line;
17198
+ },
17199
+ peekSnapshots() {
17200
+ if (!disposed && (readAt === null || now() - readAt >= ttlMs)) refresh();
17201
+ return snapshots;
17202
+ },
17203
+ dispose() {
17204
+ disposed = true;
17205
+ }
17206
+ };
17207
+ }
16164
17208
 
16165
17209
  // src/interactive/chat-panel.ts
16166
17210
  init_esm_shims();
@@ -18041,8 +19085,8 @@ function footerKeyHint(now, narrow = false) {
18041
19085
  })
18042
19086
  ];
18043
19087
  const count = narrow ? 1 : 2;
18044
- const page = Math.floor(now / 12e3) % (Math.ceil(entries.length / count) + 1);
18045
- if (page === Math.ceil(entries.length / count)) return null;
19088
+ if (!entries.length) return null;
19089
+ const page = Math.floor(now / 12e3) % Math.ceil(entries.length / count);
18046
19090
  const start = page * count;
18047
19091
  return entries.slice(start, start + count).join(" \xB7 ");
18048
19092
  }
@@ -18051,7 +19095,7 @@ function footerKeyHint(now, narrow = false) {
18051
19095
  var DASHBOARD_PAGES = ["Activity", "Context", "Status"];
18052
19096
  var ACTIVE_AGENT_STATUSES = /* @__PURE__ */ new Set(["running", "enqueued", "cancelling", "retrying", "stale"]);
18053
19097
  var clean = (value) => sanitizeCallTargetText(redactSecretString(value));
18054
- function agentCard(row2, width, compact = false) {
19098
+ function agentCard(row2, width, compact = false, quota = []) {
18055
19099
  const theme2 = clioTheme();
18056
19100
  const presentation = dispatchStatusPresentation(row2.status, { compact: false });
18057
19101
  const name = clean(row2.agentId).replace(
@@ -18066,6 +19110,9 @@ function agentCard(row2, width, compact = false) {
18066
19110
  );
18067
19111
  const field = (label, value) => wrapTextWithAnsi(`${theme2.fg("dim", `${label} `)}${clean(value)}`, width);
18068
19112
  lines.push(...previewRows(field("Route", `${row2.targetId}/${row2.wireModelId}`), 2, width));
19113
+ const weekly = routeWeeklyQuota(row2, quota);
19114
+ if (weekly)
19115
+ lines.push(...previewRows(field("Account", `Shared ${weekly.account} \xB7 ${weekly.label}`), compact ? 1 : 2, width));
18069
19116
  if (row2.taskSummary)
18070
19117
  lines.push(...compact ? previewRows(field("Task", row2.taskSummary), 2, width) : field("Task", row2.taskSummary));
18071
19118
  const activity = renderDispatchActivity(row2, width);
@@ -18136,8 +19183,8 @@ function activityPage(state, width, budget) {
18136
19183
  if (first && second) {
18137
19184
  cards.push(
18138
19185
  ...zipColumns(
18139
- agentCard(first, col, true),
18140
- agentCard(second, width - col - 5, true),
19186
+ agentCard(first, col, true, state.quota),
19187
+ agentCard(second, width - col - 5, true, state.quota),
18141
19188
  col,
18142
19189
  width - col - 5,
18143
19190
  ` ${theme2.fg("frame", "\u2502")} `
@@ -18148,7 +19195,7 @@ function activityPage(state, width, budget) {
18148
19195
  }
18149
19196
  } else
18150
19197
  for (const row2 of active) {
18151
- const card = agentCard(row2, width, active.length > 1 || cardBudget < 18);
19198
+ const card = agentCard(row2, width, active.length > 1 || cardBudget < 18, state.quota);
18152
19199
  if (cards.length + card.length + 2 > cardBudget && shown > 0) break;
18153
19200
  cards.push(...card, "");
18154
19201
  shown++;
@@ -18208,6 +19255,7 @@ function contextPage(state, width) {
18208
19255
  width
18209
19256
  )
18210
19257
  ];
19258
+ out.push(theme2.fg("dim", "Context occupancy is per request; subscription limits are account-wide \xB7 /usage"));
18211
19259
  const gridWidth = Math.max(12, Math.min(64, width >= 76 ? Math.floor(width * 0.38) : width));
18212
19260
  const gridHeight = width >= 76 ? 8 : 3;
18213
19261
  const grid = renderContextMeterGrid(ledger, gridWidth, gridHeight, theme2);
@@ -18281,8 +19329,17 @@ function renderCompactDashboard(state, width) {
18281
19329
  const usage = `${formatFooterTokens(ledger?.usedTokens ?? state.context.used ?? 0)} / ${ledger?.contextWindow ?? state.context.contextWindow ? formatFooterTokens(ledger?.contextWindow ?? state.context.contextWindow ?? 0) : "?"}`;
18282
19330
  const context = `${ledger ? renderContextMeterBar(ledger, w >= 100 ? 14 : 8, theme2) : ""} ${usage}`;
18283
19331
  const rightWidth = Math.min(Math.floor(w * 0.48), visibleWidth(context));
18284
- const identityRoom = Math.max(8, w - rightWidth - visibleWidth(thinking) - visibleWidth(activity) - 10);
18285
- const left = `${activity} \xB7 ${theme2.fg("muted", fitIdentityLabel(identity, identityRoom))} \xB7 ${thinking}`;
19332
+ const weekly = state.quotaRoute ? routeWeeklyQuota(state.quotaRoute, state.quota ?? []) : null;
19333
+ const leftRoom = Math.max(1, w - rightWidth - 3);
19334
+ const activityLabel = fit(activity, Math.min(visibleWidth(activity), Math.max(5, Math.floor(leftRoom / 3))));
19335
+ const badge = weekly && leftRoom - visibleWidth(activityLabel) - visibleWidth(weekly.label) >= 16 ? theme2.fg(
19336
+ weekly.severity === "critical" ? "error" : weekly.severity === "normal" ? "muted" : "warning",
19337
+ weekly.label
19338
+ ) : "";
19339
+ const baseRoom = leftRoom - visibleWidth(activityLabel) - (badge ? visibleWidth(badge) + 3 : 0) - 5;
19340
+ const showThinking = baseRoom - visibleWidth(thinking) - 3 >= 12;
19341
+ const identityRoom = Math.max(1, baseRoom - (showThinking ? visibleWidth(thinking) + 3 : 0));
19342
+ const left = `${activityLabel} \xB7 ${theme2.fg("muted", fitIdentityLabel(identity, identityRoom))}${badge ? ` \xB7 ${badge}` : ""}${showThinking ? ` \xB7 ${thinking}` : ""}`;
18286
19343
  const pair = (l, r, rw) => `${fit(l, w - rw - 3)} ${fit(r, rw)}`;
18287
19344
  const notice = [...state.notices].filter((n) => n.expiresAt === null || n.expiresAt > state.now).sort((a, b) => b.addedAt - a.addedAt)[0];
18288
19345
  const key = getKeybindings().getKeys("clio-coder.status.toggle").join("/") || "Dashboard";
@@ -18290,18 +19347,28 @@ function renderCompactDashboard(state, width) {
18290
19347
  const foot = urgent ? theme2.fg("warning", urgent) : notice ? theme2.fg(
18291
19348
  notice.level === "error" ? "error" : notice.level === "warning" ? "warning" : "muted",
18292
19349
  `\u2022 ${clean(notice.text)}`
18293
- ) : state.demoHint ? `${theme2.fg("accent", "Tip")} ${theme2.fg("muted", clean(state.demoHint))}` : theme2.fg(
19350
+ ) : state.demoHint ? `${theme2.fg("accent", "Tip")} ${theme2.fg("muted", clean(state.demoHint))}` : theme2.fg("dim", footerKeyHint(state.now, w < 120) ?? `${key} Dashboard`);
19351
+ const hintWidth = Math.min(Math.floor(w * 0.48), visibleWidth(foot));
19352
+ const workspaceWidth = Math.max(1, w - hintWidth - 3);
19353
+ const git = `${clean(state.workspace.branch ?? "no Git branch")}${state.workspace.dirty ? " *" : ""}`;
19354
+ const gitWidth = Math.min(visibleWidth(git), Math.max(4, Math.floor(workspaceWidth * 0.45)));
19355
+ const cwdWidth = Math.max(1, workspaceWidth - gitWidth - 3);
19356
+ const workspace = theme2.fg(
18294
19357
  "dim",
18295
- footerKeyHint(state.now, w < 80) ?? `${clean(state.workspace.cwd)} \xB7 ${clean(state.workspace.branch ?? "no Git branch")}${state.workspace.dirty ? " *" : ""}`
19358
+ `${fitIdentityLabel(clean(state.workspace.cwd), cwdWidth)} \xB7 ${fitIdentityLabel(git, gitWidth)}`
18296
19359
  );
18297
- const hint = theme2.fg("muted", `${state.session.throughput ? `${state.session.throughput} \xB7 ` : ""}${key}`);
18298
- return [
18299
- fit(pair(left, context, rightWidth)),
18300
- fit(pair(foot, hint, Math.min(Math.floor(w * 0.4), visibleWidth(hint))))
18301
- ];
19360
+ return [fit(pair(left, context, rightWidth)), fit(pair(workspace, foot, hintWidth))];
18302
19361
  }
18303
19362
  function statusPage(state, width) {
18304
19363
  const theme2 = clioTheme();
19364
+ const quotaRows = [
19365
+ theme2.style(
19366
+ "accent",
19367
+ `SESSION \xB7 ${formatFooterTokens(state.sessionTokens?.totalTokens ?? 0)} recorded tokens \xB7 ${formatCostAggregate(state.sessionCost) ?? "cost not yet priced"}`,
19368
+ { bold: true }
19369
+ ),
19370
+ ...renderQuotaAccounts(state.quota ?? [], width, { compact: true, now: state.now })
19371
+ ];
18305
19372
  const active = state.dispatchRows.filter((row2) => ACTIVE_AGENT_STATUSES.has(row2.status));
18306
19373
  const completed = state.dispatchRows.filter((row2) => row2.status === "completed").length;
18307
19374
  const failed = state.dispatchRows.filter((row2) => ["failed", "dead", "aborted"].includes(row2.status)).length;
@@ -18331,7 +19398,6 @@ function statusPage(state, width) {
18331
19398
  ["Target", state.session.target ?? "not selected"],
18332
19399
  ["Tracked cost", formatCostAggregate(state.sessionCost) ?? "not yet priced"],
18333
19400
  ["Clio ceiling", state.costCeilingUsd === void 0 ? "unknown" : `$${state.costCeilingUsd} \xB7 tracked pricing only`],
18334
- ["Provider quota", "not reported by provider"],
18335
19401
  ["MCP connected", names(state.connections?.mcp)],
18336
19402
  ["Plugins active", names(state.connections?.plugins)],
18337
19403
  [
@@ -18385,6 +19451,7 @@ function statusPage(state, width) {
18385
19451
  ];
18386
19452
  if (width < 76)
18387
19453
  return [
19454
+ ...quotaRows,
18388
19455
  ...section("COST & CONNECTIONS", left, width),
18389
19456
  ...section("LOCAL MACHINE", right, width),
18390
19457
  theme2.fg("accent", "CONTEXT ENGINE"),
@@ -18395,6 +19462,7 @@ function statusPage(state, width) {
18395
19462
  ];
18396
19463
  const col = Math.floor((width - 5) / 2);
18397
19464
  return [
19465
+ ...quotaRows,
18398
19466
  ...zipColumns(
18399
19467
  section("COST & CONNECTIONS", left, col),
18400
19468
  section("LOCAL MACHINE", right, width - col - 5),
@@ -18428,7 +19496,7 @@ function renderDashboardPage(state, page, width, terminalRows, cycleKey) {
18428
19496
  const hint = truncateToWidth(
18429
19497
  theme2.fg(
18430
19498
  "muted",
18431
- `${cycleKey || "Dashboard"} \u2192 ${next} \xB7 ${page === "Status" ? "/cost \xB7 /mcp \xB7 /library" : "composer stays active"}`
19499
+ `${cycleKey || "Dashboard"} \u2192 ${next} \xB7 ${page === "Status" ? "/usage \xB7 /mcp \xB7 /library" : "composer stays active"}`
18432
19500
  ),
18433
19501
  safeWidth,
18434
19502
  "\u2026",
@@ -18441,7 +19509,7 @@ function renderDashboardPage(state, page, width, terminalRows, cycleKey) {
18441
19509
  else content = statusPage(state, safeWidth);
18442
19510
  content = content.flatMap((line) => visibleWidth(line) > safeWidth ? wrapTextWithAnsi(line, safeWidth) : [line]);
18443
19511
  if (content.length > available) {
18444
- const detail = page === "Activity" ? `${getKeybindings().getKeys("clio-coder.dispatchBoard.toggle").join("/") || "Fleet Runs"} \xB7 /view` : page === "Context" ? "/context" : "/cost \xB7 /context";
19512
+ const detail = page === "Activity" ? `${getKeybindings().getKeys("clio-coder.dispatchBoard.toggle").join("/") || "Fleet Runs"} \xB7 /view` : page === "Context" ? "/context" : "/usage \xB7 /context";
18445
19513
  content = [
18446
19514
  ...content.slice(0, available - 1),
18447
19515
  truncateToWidth(theme2.fg("dim", `More detail: ${detail}`), safeWidth, "\u2026", true)
@@ -18453,7 +19521,7 @@ function renderDashboardPage(state, page, width, terminalRows, cycleKey) {
18453
19521
 
18454
19522
  // src/interactive/footer/system-metrics.ts
18455
19523
  init_esm_shims();
18456
- import { readdir, readFile } from "node:fs/promises";
19524
+ import { readdir, readFile as readFile4 } from "node:fs/promises";
18457
19525
  import { cpus, freemem, platform, release, totalmem } from "node:os";
18458
19526
  function counterRate(current, previous, elapsedMs) {
18459
19527
  return elapsedMs > 0 && current >= previous && Number.isFinite(current) && Number.isFinite(previous) ? (current - previous) * 1e3 / elapsedMs : null;
@@ -18492,7 +19560,7 @@ function busiest(current, previous, elapsed) {
18492
19560
  });
18493
19561
  return rates.sort((a, b) => b.first + b.second - a.first - a.second)[0] ?? null;
18494
19562
  }
18495
- var read = (path) => readFile(path, "utf8").catch(() => null);
19563
+ var read = (path) => readFile4(path, "utf8").catch(() => null);
18496
19564
  var numeric = async (path) => {
18497
19565
  const raw = await read(path);
18498
19566
  if (raw === null || raw.trim() === "") return null;
@@ -18795,6 +19863,13 @@ function buildFooterDashboard(deps) {
18795
19863
  const toolProfile = settings?.integrations.externalAgents?.defaults?.toolGovernance ?? "clio-coder-policy";
18796
19864
  return {
18797
19865
  resources: machine.snapshot(),
19866
+ quota: deps.getQuotaSnapshots?.() ?? [],
19867
+ ...current?.runtime && wireModelId ? {
19868
+ quotaRoute: {
19869
+ runtimeId: current.runtime.id,
19870
+ wireModelId
19871
+ }
19872
+ } : {},
18798
19873
  ...deps.getConnections ? { connections: deps.getConnections() } : {},
18799
19874
  ...settings ? { costCeilingUsd: settings.safety.limits.sessionCostUsd } : {},
18800
19875
  workspace: workspaceFacts(deps, branchSlot),
@@ -19005,8 +20080,8 @@ init_esm_shims();
19005
20080
  init_esm_shims();
19006
20081
  import { execFile as execFile2 } from "node:child_process";
19007
20082
  import { open, readdir as readdir2, stat } from "node:fs/promises";
19008
- import { homedir as homedir2 } from "node:os";
19009
- import { basename as basename2, dirname, join, relative as relative4, resolve as resolve3, sep } from "node:path";
20083
+ import { homedir as homedir5 } from "node:os";
20084
+ import { basename as basename2, dirname, join as join4, relative as relative4, resolve as resolve3, sep } from "node:path";
19010
20085
  var MAX_COMPLETION_ROWS = 40;
19011
20086
  var PREVIEW_BYTES = 2048;
19012
20087
  var PREVIEW_CHARS = 120;
@@ -19054,7 +20129,7 @@ function buildWorkspaceSnapshot(basePath, visibleFiles, trackedFiles) {
19054
20129
  const tracked = trackedFiles.has(path);
19055
20130
  files.push({
19056
20131
  path,
19057
- absolutePath: join(basePath, path),
20132
+ absolutePath: join4(basePath, path),
19058
20133
  isDirectory: false,
19059
20134
  tracked,
19060
20135
  descendantFiles: 0,
@@ -19072,7 +20147,7 @@ function buildWorkspaceSnapshot(basePath, visibleFiles, trackedFiles) {
19072
20147
  const directories = [...directoryFacts].map(
19073
20148
  ([path, facts]) => ({
19074
20149
  path,
19075
- absolutePath: join(basePath, path),
20150
+ absolutePath: join4(basePath, path),
19076
20151
  isDirectory: true,
19077
20152
  tracked: facts.tracked,
19078
20153
  descendantFiles: facts.descendantFiles,
@@ -19129,7 +20204,7 @@ function explicitPathQuery(query) {
19129
20204
  return query.startsWith("../") || query.startsWith("~/") || query.startsWith("/");
19130
20205
  }
19131
20206
  function resolveDisplayBase(basePath, displayBase) {
19132
- if (displayBase.startsWith("~/")) return resolve3(homedir2(), displayBase.slice(2));
20207
+ if (displayBase.startsWith("~/")) return resolve3(homedir5(), displayBase.slice(2));
19133
20208
  if (displayBase.startsWith("/")) return resolve3(displayBase);
19134
20209
  return resolve3(basePath, displayBase);
19135
20210
  }
@@ -19147,7 +20222,7 @@ async function explicitCandidates(basePath, query, trackedFiles) {
19147
20222
  const candidates = [];
19148
20223
  for (const entry of await readdir2(directory, { withFileTypes: true })) {
19149
20224
  if (entry.name === ".git" || /[\r\n\0]/u.test(entry.name)) continue;
19150
- const absolutePath = join(directory, entry.name);
20225
+ const absolutePath = join4(directory, entry.name);
19151
20226
  let isDirectory = entry.isDirectory();
19152
20227
  let isFile = entry.isFile();
19153
20228
  if (!isDirectory && !isFile && entry.isSymbolicLink()) {
@@ -19799,6 +20874,12 @@ function createInteractivePresentation(deps) {
19799
20874
  const key = keybindings.getKeys("tui.input.submit")[0];
19800
20875
  return key === void 0 || key.length === 0 ? null : formatKeyLabel(key, "");
19801
20876
  };
20877
+ const quotaSummary = createQuotaSummaryFeed({
20878
+ onUpdate: () => {
20879
+ footer?.refresh();
20880
+ requestRender();
20881
+ }
20882
+ });
19802
20883
  const banner = factories.createBanner({
19803
20884
  providers: deps.providers,
19804
20885
  ...deps.agents ? { getAgentCount: () => deps.agents?.listSpecs().filter(isUserVisibleAgent).length ?? 0 } : {},
@@ -19813,6 +20894,9 @@ function createInteractivePresentation(deps) {
19813
20894
  return key ? formatKeyLabel(key, "") : null;
19814
20895
  },
19815
20896
  onFactsRefreshed: requestRender,
20897
+ // Subscription usage is read lazily off the render path and cached, so the
20898
+ // banner reads a string and never awaits a provider.
20899
+ getQuotaSummary: () => quotaSummary.peek(),
19816
20900
  ...deps.getSettings ? { getSettings: deps.getSettings } : {}
19817
20901
  });
19818
20902
  if (deps.startsResumed === true) banner.collapseToSessionHeader();
@@ -19945,6 +21029,7 @@ function createInteractivePresentation(deps) {
19945
21029
  getSessionTokens: () => observabilitySnapshot.session.tokens,
19946
21030
  getTokenThroughput: () => liveThroughput === null ? observabilitySnapshot.session.latestThroughput : currentLiveThroughput(),
19947
21031
  getSessionCost: () => observabilitySnapshot.session.cost,
21032
+ getQuotaSnapshots: () => quotaSummary.peekSnapshots(),
19948
21033
  getContextUsage: () => deps.chat.contextUsage(),
19949
21034
  getContextLedger: () => deps.chat.contextLedger(),
19950
21035
  getDispatchRows: () => dispatchBoardStore.rows(),
@@ -20145,6 +21230,7 @@ function createInteractivePresentation(deps) {
20145
21230
  const disposeBeforeStatus = () => {
20146
21231
  if (beforeStatusDisposed) return;
20147
21232
  beforeStatusDisposed = true;
21233
+ quotaSummary.dispose();
20148
21234
  banner.dispose();
20149
21235
  footer.dispose();
20150
21236
  unsubscribeObservability();
@@ -20186,6 +21272,7 @@ function createInteractivePresentation(deps) {
20186
21272
  },
20187
21273
  changeOutputStyle: (mutation) => chatRenderer.mutate(() => preserveTranscriptScroll(transcriptView, deps.terminal.columns, mutation), "output-style"),
20188
21274
  getObservabilitySnapshot: () => observabilitySnapshot,
21275
+ getQuotaSnapshots: () => quotaSummary.peekSnapshots(),
20189
21276
  recordChatEvent,
20190
21277
  recordToolStart: (toolCallId, toolName) => {
20191
21278
  footerActiveTools.add(toolCallId);
@@ -20223,7 +21310,7 @@ function createInteractivePresentation(deps) {
20223
21310
  // src/interactive/interactive-slash-runtime.ts
20224
21311
  init_esm_shims();
20225
21312
  import { mkdirSync, writeFileSync } from "node:fs";
20226
- import { join as join2, resolve as resolve4 } from "node:path";
21313
+ import { join as join5, resolve as resolve4 } from "node:path";
20227
21314
 
20228
21315
  // src/interactive/context-recall-command.ts
20229
21316
  init_esm_shims();
@@ -20754,7 +21841,7 @@ function createInteractiveSlashRuntime(deps) {
20754
21841
  deps.requestRender();
20755
21842
  },
20756
21843
  runDoctor: async ({ deep }) => {
20757
- const { collectDoctorFindings, doctorNotice } = await import("./doctor-S5KZ2GTN.js");
21844
+ const { collectDoctorFindings, doctorNotice } = await import("./doctor-2U6YR5NH.js");
20758
21845
  const autonomy = deps.getSettings?.().safety.autonomy;
20759
21846
  const findings = await collectDoctorFindings({
20760
21847
  workspaceRoot: cwd(),
@@ -20792,7 +21879,7 @@ function createInteractiveSlashRuntime(deps) {
20792
21879
  };
20793
21880
  return options.dryRun ? deps.share.planImport(resolve4(archivePath), importOptions) : deps.share.importArchive(resolve4(archivePath), importOptions);
20794
21881
  },
20795
- openCost: deps.openCost,
21882
+ openUsage: deps.openUsage,
20796
21883
  openSideQuestion: deps.openSideQuestion,
20797
21884
  // `/oracle` is refused, never queued, while a turn streams: the digest it
20798
21885
  // packs describes the record as it stands.
@@ -20921,7 +22008,7 @@ function createInteractiveSlashRuntime(deps) {
20921
22008
  const requestedPath = pathArg?.trim() ?? "";
20922
22009
  const markdown = requestedPath.toLowerCase().endsWith(".md");
20923
22010
  const target = resolve4(
20924
- requestedPath.length > 0 ? requestedPath : join2(".clio-coder", "exports", `${sessionId}-${date}.html`)
22011
+ requestedPath.length > 0 ? requestedPath : join5(".clio-coder", "exports", `${sessionId}-${date}.html`)
20925
22012
  );
20926
22013
  mkdirSync(resolve4(target, ".."), { recursive: true });
20927
22014
  if (markdown) {
@@ -20970,7 +22057,7 @@ function createInteractiveSlashRuntime(deps) {
20970
22057
  runContextRecall: (ref) => {
20971
22058
  const session = deps.session;
20972
22059
  if (!session) return;
20973
- const outcome = runOperatorRecall(ref, {
22060
+ const outcome2 = runOperatorRecall(ref, {
20974
22061
  hasSession: () => session.current() !== null,
20975
22062
  readEntries: () => {
20976
22063
  const sessionId = deps.chat.getSessionId();
@@ -20984,12 +22071,12 @@ function createInteractiveSlashRuntime(deps) {
20984
22071
  onRecalled: (payload) => deps.bus.emit(BusChannels.ContextRecalled, payload),
20985
22072
  ...deps.now ? { now: () => (deps.now?.() ?? /* @__PURE__ */ new Date()).getTime() } : {}
20986
22073
  });
20987
- if (!outcome.ok) {
20988
- appendCommandNotice("error", outcome.message);
22074
+ if (!outcome2.ok) {
22075
+ appendCommandNotice("error", outcome2.message);
20989
22076
  return;
20990
22077
  }
20991
- appendCommandNotice("success", outcome.headline);
20992
- deps.io.stdout(`${outcome.body}
22078
+ appendCommandNotice("success", outcome2.headline);
22079
+ deps.io.stdout(`${outcome2.body}
20993
22080
  `);
20994
22081
  deps.requestRender();
20995
22082
  }
@@ -21188,7 +22275,11 @@ function createInteractiveTickers(deps) {
21188
22275
  taskIslandHidden = hidden;
21189
22276
  if (rows.length > 0)
21190
22277
  taskIsland.setText(
21191
- formatTaskIslandLines(rows, Math.max(1, Math.min(4, Math.floor((taskIslandHeight - 8) / 9)))).join("\n")
22278
+ formatTaskIslandLines(
22279
+ rows,
22280
+ Math.max(1, Math.min(4, Math.floor((taskIslandHeight - 8) / 10))),
22281
+ deps.getQuotaSnapshots?.() ?? []
22282
+ ).join("\n")
21192
22283
  );
21193
22284
  else if (board) taskIsland.setText(formatTaskBoardIslandLines(board).join("\n"));
21194
22285
  taskIsland.invalidate();
@@ -21823,7 +22914,7 @@ async function createInteractiveApplication(deps) {
21823
22914
  expandSubmit: (text) => expandInteractiveSubmitAsync(text, deps.resources),
21824
22915
  openAskUser: (questions, options) => openAskUserOverlayState(questions, options),
21825
22916
  openSkillsHub: (request) => openSkillsHubState(request),
21826
- openCost: () => openCostOverlayState(),
22917
+ openUsage: () => openUsageOverlayState(),
21827
22918
  openSideQuestion: (question) => openSideQuestionOverlayState(question),
21828
22919
  startHandoff: (goal) => startHandoffState(goal),
21829
22920
  startFleetRun: (name, vars) => startFleetRunState(name, vars),
@@ -21879,6 +22970,7 @@ async function createInteractiveApplication(deps) {
21879
22970
  getTerminationCoordinator().onDrain(() => editorSubmit.shutdownEditorBash(), { timeoutMs: EDITOR_BASH_SHUTDOWN_MS });
21880
22971
  editor.onSubmit = editorSubmit.submitEditorText;
21881
22972
  const interactiveTickers = createInteractiveTickers({
22973
+ getQuotaSnapshots: presentation.getQuotaSnapshots,
21882
22974
  tui,
21883
22975
  dispatchBoardStore,
21884
22976
  contextActivityStore,
@@ -21897,6 +22989,8 @@ async function createInteractiveApplication(deps) {
21897
22989
  resetTranscript();
21898
22990
  };
21899
22991
  overlayLifecycle = createOverlayLifecycle({
22992
+ getQuotaSnapshots: presentation.getQuotaSnapshots,
22993
+ getDispatchRows: () => dispatchBoardStore.rows(),
21900
22994
  app: deps,
21901
22995
  tui,
21902
22996
  footer,
@@ -21932,7 +23026,7 @@ async function createInteractiveApplication(deps) {
21932
23026
  const {
21933
23027
  closeOverlay,
21934
23028
  openAskUserOverlayState,
21935
- openCostOverlayState,
23029
+ openUsageOverlayState,
21936
23030
  openSideQuestionOverlayState,
21937
23031
  startHandoffState,
21938
23032
  startFleetRunState,
@@ -21999,11 +23093,11 @@ async function createInteractiveApplication(deps) {
21999
23093
  );
22000
23094
  };
22001
23095
  const backgroundActiveDispatch = () => {
22002
- const outcome = deps.onBackgroundDispatch?.() ?? {
23096
+ const outcome2 = deps.onBackgroundDispatch?.() ?? {
22003
23097
  ok: false,
22004
23098
  message: "background: dispatch backgrounding is unavailable in this session"
22005
23099
  };
22006
- notify(outcome.ok ? "success" : "info", outcome.message, "dispatch:background");
23100
+ notify(outcome2.ok ? "success" : "info", outcome2.message, "dispatch:background");
22007
23101
  };
22008
23102
  const mux = deps.mux;
22009
23103
  const muxBridge = mux && mux.mode !== "none" && deps.createMuxBridge ? deps.createMuxBridge({
@@ -22285,7 +23379,7 @@ async function createInteractiveApplication(deps) {
22285
23379
  });
22286
23380
  const settings = deps.getSettings?.();
22287
23381
  if (settings)
22288
- void import("./startup-background-KDWCQMWT.js").then(
23382
+ void import("./startup-background-PJMWTKVO.js").then(
22289
23383
  ({ prepareWorkerTargets }) => prepareWorkerTargets({
22290
23384
  settings,
22291
23385
  providers: deps.providers,
@@ -22378,4 +23472,4 @@ export {
22378
23472
  routeOverlayKey,
22379
23473
  startInteractive
22380
23474
  };
22381
- //# sourceMappingURL=interactive-OXGYZYAS.js.map
23475
+ //# sourceMappingURL=interactive-JCVBPVYT.js.map