@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.
- package/CHANGELOG.md +11 -0
- package/README.md +12 -9
- package/dist/{acp-KS7ARGV6.js → acp-X3N7JDGS.js} +2 -2
- package/dist/{agents-YOGJ2YEJ.js → agents-UYGSMAWR.js} +3 -3
- package/dist/assets/codewiki.json +1 -1
- package/dist/{chunk-EOJPDNUP.js → chunk-3OODBJ45.js} +1 -1
- package/dist/{chunk-R7FGRZNF.js → chunk-4D7QM7S5.js} +2 -2
- package/dist/{chunk-X4CEFOSG.js → chunk-6B76U65H.js} +4 -4
- package/dist/{chunk-LTWH2ACR.js → chunk-7FHSA7VB.js} +5 -5
- package/dist/{chunk-GRHOXDKB.js → chunk-7KI4ZSEB.js} +2 -2
- package/dist/{chunk-LYJJ6546.js → chunk-7VNLPEUG.js} +6 -6
- package/dist/{chunk-D65E25JG.js → chunk-A7G7OUND.js} +2 -2
- package/dist/{chunk-3JLTRPN7.js → chunk-AKQ3N3L4.js} +2 -2
- package/dist/{chunk-AVSR2HKI.js → chunk-BIQLF3NZ.js} +3 -3
- package/dist/{chunk-D3KWFOUT.js → chunk-DGMY2DWE.js} +2 -2
- package/dist/{chunk-IN7DGBVS.js → chunk-FBXTHPNZ.js} +2 -2
- package/dist/{chunk-EX57VVWW.js → chunk-GGEEG7CD.js} +2 -2
- package/dist/{chunk-V35KZDUZ.js → chunk-GW2KVAVI.js} +2 -2
- package/dist/{chunk-SJGS3GDI.js → chunk-KIAUZGKF.js} +2 -2
- package/dist/{chunk-CBAQPTDU.js → chunk-LFYA3HYH.js} +2 -2
- package/dist/{chunk-4VDHLCF5.js → chunk-M5FPIZQW.js} +2 -2
- package/dist/{chunk-R2UUNSNQ.js → chunk-O3ZAF7UY.js} +18 -5
- package/dist/{chunk-QNUQ7K7D.js → chunk-OXWRE2QJ.js} +2 -2
- package/dist/{chunk-55RMDDFM.js → chunk-PGQG7ZMW.js} +2 -2
- package/dist/{chunk-HQVN7G4F.js → chunk-QM7HFPQQ.js} +4 -4
- package/dist/{chunk-ZR5YA6ZF.js → chunk-QTYRMBW6.js} +2 -2
- package/dist/{chunk-3LT34CAM.js → chunk-SAL2SBP4.js} +8 -8
- package/dist/{chunk-OJXOA6YU.js → chunk-TL6LNERH.js} +2 -2
- package/dist/{chunk-U6YTUVOX.js → chunk-VMTCZ2GR.js} +2 -2
- package/dist/{chunk-OQ2EOAHA.js → chunk-VWKR2NN5.js} +5 -5
- package/dist/{chunk-QDUUOZ3S.js → chunk-WAISUJNQ.js} +2 -2
- package/dist/{chunk-XDHUDE5K.js → chunk-XOH3SD7H.js} +3 -3
- package/dist/{chunk-62UK7AGW.js → chunk-ZNTGRDFA.js} +281 -14
- package/dist/cli/index.js +17 -17
- package/dist/{clio-K2PBVCEM.js → clio-Z3JTEL2I.js} +2 -2
- package/dist/{config-Q5J7UBAB.js → config-QYIRZHI7.js} +8 -8
- package/dist/{config-graph-P3555574.js → config-graph-TS3SGGQW.js} +8 -8
- package/dist/{configure-KIJ32ZO6.js → configure-OX7KVJTQ.js} +2 -2
- package/dist/{context-LFBQOSXT.js → context-2BNF6HTP.js} +12 -12
- package/dist/{context-WBH3KSVR.js → context-GTMUGAR4.js} +7 -7
- package/dist/{context-clear-YOHMQ6SQ.js → context-clear-FVJQBBL7.js} +7 -7
- package/dist/{context-working-set-MRAEVDNV.js → context-working-set-MOE72SD6.js} +2 -2
- package/dist/{detail-RHJOTGN4.js → detail-QZM3QAAU.js} +8 -8
- package/dist/{dispatch-runner-NC7PYNIO.js → dispatch-runner-2KSQO5FU.js} +8 -8
- package/dist/{doctor-S5KZ2GTN.js → doctor-2U6YR5NH.js} +3 -3
- package/dist/{eval-3D6M36N7.js → eval-ZKK77JWZ.js} +3 -3
- package/dist/{evidence-6IYN52ET.js → evidence-FMP7KZZ2.js} +7 -7
- package/dist/{evidence-JVECPI3F.js → evidence-PJTOE7JA.js} +7 -7
- package/dist/{evidence-XNVPPLVK.js → evidence-POSEQW4Q.js} +9 -9
- package/dist/{evolve-TLPUDJDM.js → evolve-JT6PAMZE.js} +7 -7
- package/dist/{fleet-WKVBF5SZ.js → fleet-4XR2ZEOF.js} +16 -16
- package/dist/{fleet-6RJWIOEO.js → fleet-6JJKNIMV.js} +8 -8
- package/dist/{fleet-inspect-236FZPRB.js → fleet-inspect-SWJMFVDP.js} +9 -9
- package/dist/{fleet-preflight-NZQHABWO.js → fleet-preflight-KSIHWQAU.js} +4 -4
- package/dist/{fleet-verify-E3RO6A5W.js → fleet-verify-QM6XU2KL.js} +7 -7
- package/dist/{fleet-view-KMZQY7YJ.js → fleet-view-RLKFHFCM.js} +8 -8
- package/dist/gui/reads-worker.js +4 -4
- package/dist/{init-DV5B6HQG.js → init-VBUPHABR.js} +11 -11
- package/dist/{interactive-OXGYZYAS.js → interactive-JCVBPVYT.js} +1597 -503
- package/dist/{inventory-OEGIL6PY.js → inventory-54G7FSVY.js} +8 -8
- package/dist/{mcp-2GSPYQ54.js → mcp-EFCWUPUR.js} +3 -3
- package/dist/{memory-J3STUUIK.js → memory-5BPQSSOP.js} +7 -7
- package/dist/{models-Y3GMFJ2V.js → models-R6HILX6X.js} +3 -3
- package/dist/{monitor-LHNH577J.js → monitor-HVRKSFEQ.js} +10 -10
- package/dist/{orchestrator-DTAPXULA.js → orchestrator-OR7V4YWK.js} +26 -26
- package/dist/{preload-OJBMAIXF.js → preload-KLXFOX2F.js} +7 -7
- package/dist/{run-H64MEVTA.js → run-UIDRUYAY.js} +16 -16
- package/dist/{skills-eval-CQ23MYQK.js → skills-eval-JDAXOPWC.js} +7 -7
- package/dist/{slash-commands-ROG3KIFJ.js → slash-commands-MNKQ5QP3.js} +4 -4
- package/dist/{startup-background-KDWCQMWT.js → startup-background-PJMWTKVO.js} +7 -7
- package/dist/{system-ZDW44ZGX.js → system-IVVZRKGI.js} +2 -2
- package/dist/{targets-AJWA6E7Z.js → targets-RFRUMCQ5.js} +4 -4
- package/dist/{terminal-lease-YFUSDPAD.js → terminal-lease-V2N2BIKQ.js} +2 -2
- package/dist/{usage-XSILDHKF.js → usage-LA33SG3R.js} +9 -9
- package/dist/{wiki-generate-RP22WP5K.js → wiki-generate-HL73TICA.js} +10 -10
- package/dist/worker/entry.js +4 -4
- package/docs/architecture/architecture.md +1 -0
- package/docs/architecture/context-engine.md +2 -2
- package/docs/architecture/observability.md +6 -4
- package/docs/architecture/prompt-envelope-and-tools.md +1 -1
- package/docs/architecture/session-lifecycle.md +1 -1
- package/docs/architecture/tui-design.md +12 -9
- package/docs/gui/parity/02-slash-and-surfaces.md +1 -1
- package/docs/guide/commands-and-modes.md +91 -5
- package/docs/guide/configuration-and-targets.md +2 -2
- package/docs/guide/proactive-memory.md +3 -3
- package/package.json +1 -1
- package/src/cli/usage.ts +2 -2
- package/src/domains/gateway/mcp/client.ts +1 -1
- package/src/domains/observability/background-memory-usage.ts +1 -1
- package/src/domains/observability/cost.ts +3 -3
- package/src/domains/observability/extension.ts +1 -1
- package/src/domains/observability/metrics.ts +1 -1
- package/src/domains/quota/anthropic-max-provider.ts +134 -0
- package/src/domains/quota/anthropic-usage.ts +220 -0
- package/src/domains/quota/antigravity-provider.ts +339 -0
- package/src/domains/quota/cache.ts +87 -0
- package/src/domains/quota/claude-code-provider.ts +169 -0
- package/src/domains/quota/codex-provider.ts +237 -0
- package/src/domains/quota/presentation.ts +240 -0
- package/src/domains/quota/registry.ts +23 -0
- package/src/domains/quota/service.ts +93 -0
- package/src/domains/quota/summary-feed.ts +86 -0
- package/src/domains/quota/types.ts +84 -0
- package/src/domains/session/usage.ts +2 -2
- package/src/engine/acp/commands.ts +1 -1
- package/src/entry/orchestrator.ts +2 -2
- package/src/interactive/chat-loop-messages.ts +2 -2
- package/src/interactive/chat-loop.ts +3 -3
- package/src/interactive/dispatch-board.ts +21 -3
- package/src/interactive/footer/dashboard.ts +13 -0
- package/src/interactive/footer/key-hints.ts +2 -2
- package/src/interactive/footer/pages.ts +55 -20
- package/src/interactive/footer-panel.ts +1 -1
- package/src/interactive/interactive-application.ts +5 -2
- package/src/interactive/interactive-input-runtime.ts +2 -2
- package/src/interactive/interactive-presentation.ts +15 -0
- package/src/interactive/interactive-slash-runtime.ts +2 -2
- package/src/interactive/interactive-tickers.ts +7 -1
- package/src/interactive/overlay-general-openers.ts +12 -8
- package/src/interactive/overlay-key-routing.ts +6 -9
- package/src/interactive/overlay-lifecycle.ts +10 -6
- package/src/interactive/overlay-session-lifecycle.ts +3 -3
- package/src/interactive/quota-view.ts +229 -0
- package/src/interactive/session-last-turn.ts +1 -1
- package/src/interactive/session-usage-reseed.ts +2 -2
- package/src/interactive/side-question.ts +2 -2
- package/src/interactive/slash-commands.ts +8 -8
- package/src/interactive/turn-context.ts +1 -1
- package/src/interactive/turn-prewarm.ts +1 -1
- package/src/interactive/{cost-overlay.ts → usage-overlay.ts} +121 -33
- 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
|
|
21
|
-
import {
|
|
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 =
|
|
27
|
+
const DEFAULT_CONTENT_WIDTH = 104;
|
|
25
28
|
|
|
26
|
-
export const
|
|
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
|
|
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
|
|
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
|
-
|
|
362
|
-
|
|
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
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
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
|
|
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
|
|
465
|
+
export function openUsageOverlay(
|
|
389
466
|
tui: TUI,
|
|
390
467
|
observability: ObservabilityContract,
|
|
391
|
-
options?:
|
|
468
|
+
options?: OpenUsageOverlayOptions,
|
|
392
469
|
): OverlayHandle {
|
|
393
470
|
const sessionId = options?.sessionId ?? null;
|
|
394
471
|
let latest: ObservabilitySnapshot = observability.snapshot();
|
|
395
|
-
const body = new
|
|
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:
|
|
480
|
+
width: USAGE_OVERLAY_WIDTH,
|
|
401
481
|
// Not derived from the title: that one carries the session id.
|
|
402
|
-
markerId: "
|
|
403
|
-
title: sessionId && sessionId.length > 0 ? `
|
|
404
|
-
footerHint:
|
|
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
|
-
...
|
|
545
|
-
const
|
|
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
|
};
|