@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,19 +11,22 @@ import {
11
11
  type ObservabilityContract,
12
12
  type ObservabilitySnapshot,
13
13
  } from "../domains/observability/index.js";
14
+ import type { UsageSnapshot } from "../domains/quota/types.js";
14
15
  import {
15
16
  foldPromptCacheTelemetry,
16
17
  hasPromptCacheTelemetry,
17
18
  type PromptCacheTelemetry,
18
19
  type SessionEntry,
19
20
  } from "../domains/session/index.js";
20
- import type { Component, OverlayHandle, TUI } from "../engine/tui.js";
21
- import { buildHint, showClioOverlayFrame } from "./overlay-frame.js";
21
+ import { type Component, matchesKey, type OverlayHandle, type TUI, wrapTextWithAnsi } from "../engine/tui.js";
22
+ import type { DispatchBoardRow } from "./dispatch-board.js";
23
+ import { buildResponsiveHint, showClioOverlayFrame } from "./overlay-frame.js";
24
+ import { quotaMeter, renderQuotaAccounts, renderWorkerUsage } from "./quota-view.js";
22
25
  import { clioTheme, rule } from "./theme/index.js";
23
26
 
24
- const DEFAULT_CONTENT_WIDTH = 80;
27
+ const DEFAULT_CONTENT_WIDTH = 104;
25
28
 
26
- export const COST_OVERLAY_WIDTH = DEFAULT_CONTENT_WIDTH + 4;
29
+ export const USAGE_OVERLAY_WIDTH = DEFAULT_CONTENT_WIDTH + 4;
27
30
 
28
31
  export interface CostRow {
29
32
  providerId: string;
@@ -295,19 +298,9 @@ function modelBlock(row: CostRow): string[] {
295
298
  ]);
296
299
  }
297
300
 
298
- function formatCostOverlayBodyLines(
299
- totalCost: CostAggregate,
300
- totalTokens: number,
301
- rows: ReadonlyArray<CostRow>,
302
- contentWidth: number,
303
- promptCache: PromptCacheTelemetry | null = null,
304
- ): string[] {
301
+ function modelDetailLines(rows: ReadonlyArray<CostRow>, contentWidth: number): string[] {
305
302
  const theme = clioTheme();
306
- const lines: string[] = [];
307
- for (const line of summaryBlock(totalCost, totalTokens, rows, promptCache)) {
308
- lines.push(line);
309
- }
310
- lines.push(rule(theme, contentWidth));
303
+ const lines: string[] = [rule(theme, contentWidth)];
311
304
  if (rows.length === 0) {
312
305
  lines.push(theme.fg("muted", "no token usage recorded for this session"));
313
306
  } else {
@@ -352,32 +345,116 @@ function buildCostSnapshot(
352
345
  };
353
346
  }
354
347
 
355
- export interface OpenCostOverlayOptions {
348
+ export interface OpenUsageOverlayOptions {
349
+ getDispatchRows?: () => ReadonlyArray<DispatchBoardRow>;
350
+ getQuotaSnapshots?: () => ReadonlyArray<UsageSnapshot>;
356
351
  sessionId?: string | null;
357
352
  /** Session ledger, read on every render so newly settled and branched calls appear. */
358
353
  getSessionEntries?: () => ReadonlyArray<SessionEntry>;
359
354
  }
360
355
 
361
- class CostOverlayBody implements Component {
362
- constructor(private readonly getSnapshot: () => CostSnapshot) {}
356
+ const USAGE_TABS = ["Accounts", "Session", "Models", "Workers"] as const;
357
+
358
+ class UsageOverlayBody implements Component {
359
+ private tab = 0;
360
+ private offsets = [0, 0, 0, 0];
361
+ private height = 20;
362
+ private pageHeight = 16;
363
+ private lineCount = 0;
364
+
365
+ constructor(
366
+ private readonly getSnapshot: () => CostSnapshot,
367
+ private readonly getQuotaSnapshots: () => ReadonlyArray<UsageSnapshot>,
368
+ private readonly getDispatchRows: () => ReadonlyArray<DispatchBoardRow>,
369
+ private readonly requestRender: () => void,
370
+ ) {}
371
+
372
+ setHeight(rows: number): void {
373
+ this.height = Math.max(1, rows - 3);
374
+ }
375
+
376
+ handleInput(data: string): void {
377
+ const selected = (["1", "2", "3", "4"] as const).findIndex((key) => matchesKey(data, key));
378
+ if (selected >= 0) this.tab = selected;
379
+ else if (matchesKey(data, "tab") || matchesKey(data, "right")) this.tab = (this.tab + 1) % USAGE_TABS.length;
380
+ else if (matchesKey(data, "shift+tab") || matchesKey(data, "left"))
381
+ this.tab = (this.tab + USAGE_TABS.length - 1) % USAGE_TABS.length;
382
+ else {
383
+ let offset = this.offsets[this.tab] ?? 0;
384
+ if (matchesKey(data, "up")) offset--;
385
+ else if (matchesKey(data, "down")) offset++;
386
+ else if (matchesKey(data, "pageUp")) offset -= this.pageHeight;
387
+ else if (matchesKey(data, "pageDown")) offset += this.pageHeight;
388
+ else if (matchesKey(data, "home") || matchesKey(data, "ctrl+home")) offset = 0;
389
+ else if (matchesKey(data, "end") || matchesKey(data, "ctrl+end")) offset = this.lineCount;
390
+ else return;
391
+ this.offsets[this.tab] = Math.max(0, Math.min(offset, this.lineCount - this.pageHeight));
392
+ }
393
+ this.requestRender();
394
+ }
363
395
 
364
396
  render(width: number): string[] {
365
397
  const contentWidth = Math.max(1, Math.floor(width));
366
398
  const snapshot = this.getSnapshot();
367
- return formatCostOverlayBodyLines(
368
- snapshot.totalCost,
369
- snapshot.totalTokens,
370
- snapshot.rows,
399
+ const quota = this.getQuotaSnapshots();
400
+ const theme = clioTheme();
401
+ const tabs = wrapTextWithAnsi(
402
+ USAGE_TABS.map((name, index) =>
403
+ index === this.tab
404
+ ? theme.style("accent", `${index + 1} ${name}`, { bold: true, underline: true })
405
+ : theme.fg("dim", `${index + 1} ${name}`),
406
+ ).join(" "),
371
407
  contentWidth,
372
- snapshot.promptCache,
373
408
  );
409
+ let body: string[];
410
+ if (this.tab === 0)
411
+ body = [
412
+ theme.fg("dim", "Account-wide limits · filled = used · shared across sessions and devices"),
413
+ "",
414
+ ...renderQuotaAccounts(quota, contentWidth),
415
+ ];
416
+ else if (this.tab === 1)
417
+ body = [
418
+ theme.style("accent", "Session tokens & cost", { bold: true }),
419
+ theme.fg("dim", "Recorded calls in this session · estimates are marked in the cost totals"),
420
+ "",
421
+ ...summaryBlock(snapshot.totalCost, snapshot.totalTokens, snapshot.rows, snapshot.promptCache),
422
+ "",
423
+ rule(theme, contentWidth),
424
+ theme.fg("dim", "Session tokens cannot be converted to subscription percentages."),
425
+ ...(snapshot.rows.length ? [] : ["no token usage recorded for this session"]),
426
+ ];
427
+ else if (this.tab === 2) {
428
+ body = [
429
+ theme.style("accent", "Model activity · this session", { bold: true }),
430
+ theme.fg("dim", "Share of recorded processed tokens, including cache traffic; not account quota."),
431
+ "",
432
+ ];
433
+ for (const row of [...snapshot.rows].sort((a, b) => b.tokens - a.tokens)) {
434
+ const share = snapshot.totalTokens > 0 ? (row.tokens / snapshot.totalTokens) * 100 : 0;
435
+ body.push(
436
+ `${row.providerId} · ${row.attributedModelId}`,
437
+ `${quotaMeter(share, Math.min(24, contentWidth), "normal")} ${share.toFixed(1)}% · ${formatTokens(row.tokens)} tokens`,
438
+ );
439
+ }
440
+ body.push("", ...modelDetailLines(snapshot.rows, contentWidth));
441
+ } else body = renderWorkerUsage(this.getDispatchRows(), quota, contentWidth);
442
+ const lines = body.flatMap((line) => wrapTextWithAnsi(line, contentWidth));
443
+ this.lineCount = lines.length;
444
+ this.pageHeight = Math.max(1, this.height - tabs.length - 1);
445
+ const offset = Math.max(0, Math.min(this.offsets[this.tab] ?? 0, lines.length - this.pageHeight));
446
+ this.offsets[this.tab] = offset;
447
+ const visible = [...tabs, "", ...lines.slice(offset, offset + this.pageHeight)];
448
+ if (lines.length > this.pageHeight)
449
+ visible.push(theme.fg("dim", `${offset + 1}–${Math.min(lines.length, offset + this.pageHeight)} / ${lines.length}`));
450
+ return visible;
374
451
  }
375
452
 
376
453
  invalidate(): void {}
377
454
  }
378
455
 
379
456
  /**
380
- * Mount a read-only session-cost overlay. The running USD total comes from the
457
+ * Mount the read-only usage overlay. Quota comes from the shared presentation feed. The running USD total comes from the
381
458
  * observability projection's `snapshot().session.costUsd`, while the
382
459
  * per-provider/model rows fold `observability.costEntries()`. The overlay is
383
460
  * kept live by `observability.subscribe()`: the projection already folds the
@@ -385,23 +462,34 @@ class CostOverlayBody implements Component {
385
462
  * update, so a single subscription replaces the former DispatchCompleted /
386
463
  * DispatchFailed / chat-turn refresh wiring. `hide()` unsubscribes.
387
464
  */
388
- export function openCostOverlay(
465
+ export function openUsageOverlay(
389
466
  tui: TUI,
390
467
  observability: ObservabilityContract,
391
- options?: OpenCostOverlayOptions,
468
+ options?: OpenUsageOverlayOptions,
392
469
  ): OverlayHandle {
393
470
  const sessionId = options?.sessionId ?? null;
394
471
  let latest: ObservabilitySnapshot = observability.snapshot();
395
- const body = new CostOverlayBody(() =>
396
- buildCostSnapshot(observability, sessionId, latest, options?.getSessionEntries),
472
+ const body = new UsageOverlayBody(
473
+ () => buildCostSnapshot(observability, sessionId, latest, options?.getSessionEntries),
474
+ options?.getQuotaSnapshots ?? (() => []),
475
+ options?.getDispatchRows ?? (() => []),
476
+ () => tui.requestRender(),
397
477
  );
398
478
  const handle = showClioOverlayFrame(tui, body, {
399
479
  anchor: "center",
400
- width: COST_OVERLAY_WIDTH,
480
+ width: USAGE_OVERLAY_WIDTH,
401
481
  // Not derived from the title: that one carries the session id.
402
- markerId: "cost",
403
- title: sessionId && sessionId.length > 0 ? `Session usage (${sessionId})` : "Session usage",
404
- footerHint: buildHint([]),
482
+ markerId: "usage",
483
+ title: sessionId && sessionId.length > 0 ? `Usage (${sessionId})` : "Usage",
484
+ footerHint: buildResponsiveHint([
485
+ { key: "1–4 / Tab", verb: "view", critical: true },
486
+ { key: "↑↓", verb: "scroll" },
487
+ { key: "PgUp/PgDn", verb: "page" },
488
+ ]),
489
+ visible: (_width, height) => {
490
+ body.setHeight(height);
491
+ return true;
492
+ },
405
493
  });
406
494
 
407
495
  // subscribe() fires immediately with the current snapshot, then on each
@@ -4,7 +4,7 @@ import type { ContextState } from "../domains/context/index.js";
4
4
  import type { ProvidersContract, TargetStatus } from "../domains/providers/index.js";
5
5
  import { sanitizeCallTargetText } from "../domains/safety/call-target.js";
6
6
  import type { WorkspaceSnapshot } from "../domains/session/workspace/index.js";
7
- import { type Component, type Keybinding, truncateToWidth, visibleWidth } from "../engine/tui.js";
7
+ import { type Component, type Keybinding, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "../engine/tui.js";
8
8
  import {
9
9
  brandMark,
10
10
  type ClioTheme,
@@ -55,6 +55,12 @@ export interface WelcomeDashboardDeps {
55
55
  getSubmitKeyLabel?: () => string | null;
56
56
  /** Effective shortcut labels; null hides an unbound action. */
57
57
  getKeyLabel?: (action: Keybinding) => string | null;
58
+ /**
59
+ * Connected subscription quota, already summarized, or null when nothing is
60
+ * connected yet. Read synchronously on every frame, so the supplier must
61
+ * serve a cached value and do its own refreshing off the render path.
62
+ */
63
+ getQuotaSummary?: () => string | null;
58
64
  /** Called when an off-render refresh lands, so the frame owner can ask for one. */
59
65
  onFactsRefreshed?: () => void;
60
66
  /**
@@ -82,6 +88,8 @@ export interface WelcomeDashboardStats {
82
88
  autonomy: string;
83
89
  targets: string;
84
90
  fleet: string;
91
+ /** One line of connected subscription usage; null hides the row entirely. */
92
+ quota: string | null;
85
93
  }
86
94
 
87
95
  export type WelcomeDashboardMode = "launchpad" | "session";
@@ -536,13 +544,24 @@ function buildWelcomeDashboardLines(
536
544
  theme.fg("dim", "Ask Clio how to use or extend her."),
537
545
  theme.fg("dim", "Targets"),
538
546
  theme.fg("muted", stats.targets),
547
+ // Keep the subscription field beside the wordmark, between Targets and
548
+ // Fleet. Wrap at the actual detail-column width instead of clipping the
549
+ // last accounts (especially Local) off a single concatenated line.
550
+ ...(stats.quota === null
551
+ ? []
552
+ : wrapTextWithAnsi(
553
+ field("Subscriptions", theme.fg("muted", sanitizeCallTargetText(stats.quota))),
554
+ Math.max(1, detailWidth),
555
+ )),
539
556
  theme.fg("dim", "Fleet"),
540
557
  theme.fg("muted", stats.fleet),
541
558
  ];
559
+
542
560
  return [
543
561
  heading,
544
- ...details.map((detail, index) => {
545
- const art = sideBySide ? `${theme.fg("accent", wordmark[index] ?? "")} ` : "";
562
+ ...Array.from({ length: Math.max(details.length, hints.length, sideBySide ? wordmark.length : 0) }, (_, index) => {
563
+ const detail = details[index] ?? "";
564
+ const art = sideBySide ? `${padAnsi(theme.fg("accent", wordmark[index] ?? ""), artWidth)} ` : "";
546
565
  const content = `${art}${truncateToWidth(detail, detailWidth, GLYPH.ellipsis, false)}`;
547
566
  return row(
548
567
  showHints
@@ -577,6 +596,7 @@ function statsSignature(stats: WelcomeDashboardStats): string {
577
596
  stats.autonomy,
578
597
  stats.targets,
579
598
  stats.fleet,
599
+ stats.quota,
580
600
  ].join("\0");
581
601
  }
582
602
 
@@ -707,6 +727,7 @@ export class WelcomeDashboard implements WelcomeDashboardComponent {
707
727
  projectContext: launchpad ? this.projectContext(cwd) : "checking",
708
728
  submitKeyLabel: launchpad ? (this.deps.getSubmitKeyLabel?.() ?? null) : null,
709
729
  autonomy: settings?.safety?.autonomy ?? "auto-edit",
730
+ quota: launchpad ? (this.deps.getQuotaSummary?.() ?? null) : null,
710
731
  ...inventory(settings, statuses, this.deps.getAgentCount?.()),
711
732
  };
712
733
  }
@@ -804,6 +825,8 @@ export function createBootWelcome(
804
825
  routeReason: null,
805
826
  projectContext: "checking",
806
827
  submitKeyLabel,
828
+ // Boot paints before any provider has been read, so the row stays hidden.
829
+ quota: null,
807
830
  autonomy: settings.safety.autonomy,
808
831
  ...inventory(settings),
809
832
  };