@pellux/goodvibes-tui 1.1.0 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +85 -21
- package/README.md +28 -18
- package/docs/foundation-artifacts/operator-contract.json +4045 -1763
- package/package.json +2 -2
- package/src/audio/spoken-turn-controller.ts +12 -2
- package/src/audio/spoken-turn-wiring.ts +2 -1
- package/src/cli/help.ts +8 -1
- package/src/cli/service-posture.ts +2 -1
- package/src/cli/status.ts +2 -1
- package/src/cli/surface-command.ts +2 -2
- package/src/core/alert-gating.ts +67 -0
- package/src/core/approval-alert.ts +72 -0
- package/src/core/budget-breach-notifier.ts +106 -0
- package/src/core/composer-state.ts +11 -3
- package/src/core/conversation-line-cache.ts +27 -3
- package/src/core/conversation-rendering.ts +90 -14
- package/src/core/conversation.ts +18 -0
- package/src/core/focus-tracker.ts +41 -0
- package/src/core/long-task-notifier.ts +33 -6
- package/src/core/stream-event-wiring.ts +20 -1
- package/src/core/system-message-noise.ts +87 -0
- package/src/core/system-message-router.ts +104 -51
- package/src/core/turn-cancellation.ts +25 -0
- package/src/core/turn-event-wiring.ts +72 -3
- package/src/daemon/cli.ts +29 -2
- package/src/daemon/handlers/register.ts +8 -1
- package/src/daemon/service-commands.ts +329 -0
- package/src/export/cost-utils.ts +36 -0
- package/src/input/autocomplete.ts +27 -1
- package/src/input/command-registry.ts +84 -5
- package/src/input/commands/checkpoint-runtime.ts +280 -0
- package/src/input/commands/codebase-runtime.ts +232 -0
- package/src/input/commands/config.ts +43 -3
- package/src/input/commands/eval.ts +1 -1
- package/src/input/commands/health-runtime.ts +10 -2
- package/src/input/commands/image-runtime.ts +112 -0
- package/src/input/commands/intelligence-runtime.ts +11 -1
- package/src/input/commands/local-auth-runtime.ts +4 -1
- package/src/input/commands/local-runtime.ts +9 -3
- package/src/input/commands/marketplace-runtime.ts +2 -2
- package/src/input/commands/memory.ts +73 -35
- package/src/input/commands/operator-panel-runtime.ts +71 -31
- package/src/input/commands/planning-runtime.ts +116 -4
- package/src/input/commands/platform-sandbox-runtime.ts +1 -6
- package/src/input/commands/plugin-runtime.ts +2 -2
- package/src/input/commands/policy-dispatch.ts +21 -0
- package/src/input/commands/provider-accounts-runtime.ts +1 -1
- package/src/input/commands/qrcode-runtime.ts +26 -6
- package/src/input/commands/recall-review.ts +35 -0
- package/src/input/commands/remote-runtime-setup.ts +5 -3
- package/src/input/commands/remote-runtime.ts +3 -3
- package/src/input/commands/runtime-services.ts +54 -13
- package/src/input/commands/services-runtime.ts +1 -1
- package/src/input/commands/session-content.ts +20 -9
- package/src/input/commands/session-workflow.ts +16 -1
- package/src/input/commands/settings-sync-runtime.ts +16 -4
- package/src/input/commands/shell-core.ts +25 -8
- package/src/input/commands/skills-runtime.ts +2 -8
- package/src/input/commands/subscription-runtime.ts +2 -2
- package/src/input/commands/test-runtime.ts +277 -0
- package/src/input/commands/websearch-runtime.ts +101 -0
- package/src/input/commands/work-plan-runtime.ts +36 -10
- package/src/input/commands/workstream-runtime.ts +488 -0
- package/src/input/commands.ts +12 -0
- package/src/input/config-modal-types.ts +175 -0
- package/src/input/config-modal.ts +592 -0
- package/src/input/feed-context-factory.ts +10 -8
- package/src/input/handler-command-route.ts +37 -20
- package/src/input/handler-content-actions.ts +17 -2
- package/src/input/handler-feed-routes.ts +74 -114
- package/src/input/handler-feed.ts +79 -27
- package/src/input/handler-interactions.ts +1 -3
- package/src/input/handler-modal-routes.ts +136 -6
- package/src/input/handler-modal-stack.ts +14 -7
- package/src/input/handler-modal-token-routes.ts +16 -49
- package/src/input/handler-onboarding-daemon-adopt.ts +149 -0
- package/src/input/handler-onboarding.ts +71 -59
- package/src/input/handler-picker-routes.ts +26 -102
- package/src/input/handler-shortcuts.ts +74 -14
- package/src/input/handler-types.ts +2 -6
- package/src/input/handler-ui-state.ts +10 -22
- package/src/input/handler.ts +12 -29
- package/src/input/keybindings.ts +23 -8
- package/src/input/model-picker-types.ts +24 -6
- package/src/input/model-picker.ts +77 -2
- package/src/input/onboarding/onboarding-runtime-status.ts +10 -1
- package/src/input/onboarding/onboarding-wizard-apply.ts +8 -1
- package/src/input/onboarding/onboarding-wizard-constants.ts +4 -1
- package/src/input/onboarding/onboarding-wizard-network-adopt.ts +136 -0
- package/src/input/onboarding/onboarding-wizard-steps.ts +9 -6
- package/src/input/onboarding/onboarding-wizard-types.ts +3 -1
- package/src/input/panel-integration-actions.ts +9 -170
- package/src/input/panel-mouse-geometry.ts +97 -0
- package/src/input/panel-paste-flood-guard.ts +86 -0
- package/src/input/selection-modal.ts +11 -0
- package/src/input/session-picker-modal.ts +44 -4
- package/src/input/settings-modal-data.ts +146 -2
- package/src/input/settings-modal-mutations.ts +6 -4
- package/src/input/settings-modal-types.ts +4 -2
- package/src/main.ts +49 -51
- package/src/panels/agent-inspector-shared.ts +2 -1
- package/src/panels/base-panel.ts +22 -1
- package/src/panels/builtin/agent.ts +21 -107
- package/src/panels/builtin/development.ts +15 -61
- package/src/panels/builtin/knowledge.ts +8 -32
- package/src/panels/builtin/operations.ts +84 -428
- package/src/panels/builtin/session.ts +21 -112
- package/src/panels/builtin/shared.ts +18 -46
- package/src/panels/builtin-modals.ts +218 -0
- package/src/panels/builtin-panels.ts +5 -0
- package/src/panels/cost-tracker-panel.ts +36 -10
- package/src/panels/diff-panel.ts +12 -43
- package/src/panels/eval-registry.ts +60 -0
- package/src/panels/fleet-deep-link.ts +31 -0
- package/src/panels/fleet-panel-format.ts +62 -0
- package/src/panels/fleet-panel-worktree-detail.ts +48 -0
- package/src/panels/fleet-panel.ts +758 -0
- package/src/panels/fleet-read-model.ts +520 -0
- package/src/panels/fleet-steer.ts +125 -0
- package/src/panels/fleet-stop.ts +153 -0
- package/src/panels/fleet-tabs.ts +230 -0
- package/src/panels/fleet-transcript.ts +356 -0
- package/src/panels/index.ts +7 -31
- package/src/panels/modals/hooks-modal.ts +187 -0
- package/src/panels/modals/keybindings-modal.ts +166 -0
- package/src/panels/modals/knowledge-modal.ts +158 -0
- package/src/panels/modals/local-auth-modal.ts +132 -0
- package/src/panels/modals/marketplace-modal.ts +218 -0
- package/src/panels/modals/memory-modal.ts +180 -0
- package/src/panels/modals/modal-surface-helpers.ts +54 -0
- package/src/panels/modals/modal-theme.ts +42 -0
- package/src/panels/modals/pairing-modal.ts +163 -0
- package/src/panels/modals/planning-modal.ts +304 -0
- package/src/panels/modals/plugins-modal.ts +174 -0
- package/src/panels/modals/policy-modal.ts +256 -0
- package/src/panels/modals/provider-health-modal.ts +136 -0
- package/src/panels/modals/remote-modal.ts +115 -0
- package/src/panels/modals/sandbox-modal.ts +150 -0
- package/src/panels/modals/security-modal.ts +217 -0
- package/src/panels/modals/services-modal.ts +179 -0
- package/src/panels/modals/settings-sync-modal.ts +142 -0
- package/src/panels/modals/skills-modal.ts +189 -0
- package/src/panels/modals/subscription-modal.ts +172 -0
- package/src/panels/modals/work-plan-modal.ts +177 -0
- package/src/panels/panel-confirm-overlay.ts +72 -0
- package/src/panels/panel-manager.ts +123 -9
- package/src/panels/polish-core.ts +38 -25
- package/src/panels/project-planning-answer-actions.ts +8 -11
- package/src/panels/skills-panel.ts +7 -51
- package/src/panels/types.ts +37 -0
- package/src/permissions/hunk-selection.ts +179 -0
- package/src/permissions/prompt.ts +216 -13
- package/src/renderer/autocomplete-overlay.ts +28 -2
- package/src/renderer/compaction-history-modal.ts +19 -3
- package/src/renderer/compaction-preview.ts +13 -2
- package/src/renderer/compaction-quality.ts +100 -0
- package/src/renderer/compositor.ts +2 -3
- package/src/renderer/config-modal.ts +95 -0
- package/src/renderer/conversation-overlays.ts +10 -17
- package/src/renderer/fleet-tab-strip.ts +56 -0
- package/src/renderer/footer-tips.ts +15 -3
- package/src/renderer/fullscreen-primitives.ts +32 -22
- package/src/renderer/git-status.ts +43 -1
- package/src/renderer/help-overlay.ts +2 -2
- package/src/renderer/layout.ts +0 -4
- package/src/renderer/markdown.ts +7 -3
- package/src/renderer/modal-factory.ts +25 -20
- package/src/renderer/model-workspace.ts +62 -4
- package/src/renderer/overlay-box.ts +21 -17
- package/src/renderer/panel-workspace-bar.ts +8 -2
- package/src/renderer/process-indicator.ts +14 -3
- package/src/renderer/selection-modal-overlay.ts +6 -1
- package/src/renderer/session-picker-modal.ts +196 -3
- package/src/renderer/settings-modal-helpers.ts +2 -0
- package/src/renderer/settings-modal.ts +7 -0
- package/src/renderer/shell-surface.ts +8 -1
- package/src/renderer/status-glyphs.ts +14 -15
- package/src/renderer/system-message.ts +15 -3
- package/src/renderer/terminal-bg-probe.ts +339 -0
- package/src/renderer/terminal-escapes.ts +20 -0
- package/src/renderer/theme-mode-config.ts +67 -0
- package/src/renderer/theme.ts +91 -1
- package/src/renderer/thinking.ts +11 -3
- package/src/renderer/tool-call.ts +15 -9
- package/src/renderer/tool-result-summary.ts +148 -0
- package/src/renderer/turn-injection.ts +133 -0
- package/src/renderer/ui-factory.ts +154 -85
- package/src/renderer/ui-primitives.ts +30 -129
- package/src/runtime/bootstrap-command-context.ts +27 -1
- package/src/runtime/bootstrap-command-parts.ts +41 -10
- package/src/runtime/bootstrap-core.ts +47 -15
- package/src/runtime/bootstrap-hook-bridge.ts +7 -0
- package/src/runtime/bootstrap-shell.ts +48 -17
- package/src/runtime/bootstrap.ts +129 -22
- package/src/runtime/code-index-services.ts +135 -0
- package/src/runtime/legacy-daemon-migration.ts +516 -0
- package/src/runtime/memory-fold.ts +26 -0
- package/src/runtime/onboarding/derivation.ts +7 -2
- package/src/runtime/onboarding/snapshot.ts +27 -1
- package/src/runtime/onboarding/types.ts +31 -1
- package/src/runtime/operator-token-cleanup.ts +82 -1
- package/src/runtime/orchestrator-core-services.ts +59 -0
- package/src/runtime/process-lifecycle.ts +3 -1
- package/src/runtime/resume-notice.ts +209 -0
- package/src/runtime/services.ts +98 -46
- package/src/runtime/session-inbound-inputs.ts +252 -0
- package/src/runtime/session-spine-transport.ts +64 -0
- package/src/runtime/terminal-output-guard.ts +15 -8
- package/src/runtime/ui-services.ts +27 -3
- package/src/runtime/workstream-services.ts +327 -0
- package/src/runtime/wrfc-persistence.ts +124 -17
- package/src/shell/blocking-input.ts +68 -4
- package/src/shell/recovery-input-helpers.ts +170 -1
- package/src/shell/ui-openers.ts +171 -26
- package/src/utils/format-duration.ts +8 -18
- package/src/utils/splash-lines.ts +1 -1
- package/src/utils/terminal-width.ts +52 -0
- package/src/version.ts +1 -1
- package/src/panels/agent-inspector-panel.ts +0 -786
- package/src/panels/approval-panel.ts +0 -252
- package/src/panels/automation-control-panel.ts +0 -479
- package/src/panels/cockpit-panel.ts +0 -481
- package/src/panels/cockpit-read-model.ts +0 -238
- package/src/panels/communication-panel.ts +0 -256
- package/src/panels/control-plane-panel.ts +0 -470
- package/src/panels/debug-panel.ts +0 -615
- package/src/panels/docs-panel.ts +0 -384
- package/src/panels/eval-panel.ts +0 -627
- package/src/panels/file-explorer-panel.ts +0 -673
- package/src/panels/file-preview-panel.ts +0 -517
- package/src/panels/hooks-panel.ts +0 -401
- package/src/panels/incident-review-panel.ts +0 -406
- package/src/panels/intelligence-panel.ts +0 -383
- package/src/panels/knowledge-graph-panel.ts +0 -515
- package/src/panels/marketplace-panel.ts +0 -399
- package/src/panels/memory-panel.ts +0 -558
- package/src/panels/ops-control-panel.ts +0 -329
- package/src/panels/ops-strategy-panel.ts +0 -321
- package/src/panels/orchestration-panel.ts +0 -465
- package/src/panels/panel-list-panel.ts +0 -566
- package/src/panels/plan-dashboard-panel.ts +0 -707
- package/src/panels/policy-panel.ts +0 -517
- package/src/panels/project-planning-panel.ts +0 -731
- package/src/panels/provider-health-panel.ts +0 -678
- package/src/panels/provider-health-tracker.ts +0 -310
- package/src/panels/provider-health-views.ts +0 -567
- package/src/panels/qr-panel.ts +0 -280
- package/src/panels/remote-panel.ts +0 -534
- package/src/panels/routes-panel.ts +0 -241
- package/src/panels/sandbox-panel.ts +0 -456
- package/src/panels/security-panel.ts +0 -447
- package/src/panels/services-panel.ts +0 -329
- package/src/panels/session-browser-panel.ts +0 -496
- package/src/panels/settings-sync-panel.ts +0 -398
- package/src/panels/subscription-panel.ts +0 -342
- package/src/panels/symbol-outline-panel.ts +0 -619
- package/src/panels/system-messages-panel.ts +0 -364
- package/src/panels/tasks-panel.ts +0 -608
- package/src/panels/thinking-panel.ts +0 -333
- package/src/panels/tool-inspector-panel.ts +0 -537
- package/src/panels/work-plan-panel.ts +0 -540
- package/src/panels/worktree-panel.ts +0 -360
- package/src/panels/wrfc-panel.ts +0 -790
- package/src/renderer/agent-detail-modal.ts +0 -466
- package/src/renderer/live-tail-modal.ts +0 -156
- package/src/renderer/process-modal.ts +0 -671
- package/src/renderer/qr-renderer.ts +0 -120
|
@@ -1,567 +0,0 @@
|
|
|
1
|
-
// ---------------------------------------------------------------------------
|
|
2
|
-
// provider-health-views.ts
|
|
3
|
-
//
|
|
4
|
-
// Pure Line[] builders for the provider console (WO-112). The panel owns
|
|
5
|
-
// state, subscriptions, and section composition; this module owns turning
|
|
6
|
-
// console data into rows: posture summary, fallback chain, provider metrics
|
|
7
|
-
// table (absorbed from the retired providers/stats panel), per-route auth
|
|
8
|
-
// detail (absorbed from the retired accounts panel), repair domains, and
|
|
9
|
-
// session maintenance. No hardcoded hex — tones arrive from the panel's
|
|
10
|
-
// extendPalette result.
|
|
11
|
-
// ---------------------------------------------------------------------------
|
|
12
|
-
|
|
13
|
-
import type { Line } from '../types/grid.ts';
|
|
14
|
-
import { formatLatencyMs } from '../utils/format-duration.ts';
|
|
15
|
-
import type { ProviderStatus, ProviderHealth } from './provider-health-tracker.ts';
|
|
16
|
-
import type {
|
|
17
|
-
ProviderAccountPosture,
|
|
18
|
-
ProviderPanelAuthFreshness,
|
|
19
|
-
ProviderPanelAuthRoute,
|
|
20
|
-
} from './provider-health-routes.ts';
|
|
21
|
-
import type { FallbackChainData, ProviderHealthEntry } from '../runtime/ui/provider-health/index.ts';
|
|
22
|
-
import type { HealthDomainSummary } from './provider-health-domains.ts';
|
|
23
|
-
import {
|
|
24
|
-
buildAlignedRow,
|
|
25
|
-
buildBodyText,
|
|
26
|
-
buildDetailBlock,
|
|
27
|
-
buildKeyValueLine,
|
|
28
|
-
DEFAULT_PANEL_PALETTE,
|
|
29
|
-
type PanelWorkspaceSection,
|
|
30
|
-
} from './polish.ts';
|
|
31
|
-
|
|
32
|
-
/** Extended palette produced by the panel's extendPalette call. */
|
|
33
|
-
export type ProviderConsoleTones = typeof DEFAULT_PANEL_PALETTE & {
|
|
34
|
-
readonly title: string;
|
|
35
|
-
readonly unknown: string;
|
|
36
|
-
readonly rowSelectBg: string;
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
type Palette = typeof DEFAULT_PANEL_PALETTE;
|
|
40
|
-
|
|
41
|
-
const LATENCY_WARN_MS = 2_000;
|
|
42
|
-
const LATENCY_BAD_MS = 5_000;
|
|
43
|
-
const SPARKLINE_CHARS = '._-:=+*#';
|
|
44
|
-
const SPARKLINE_WIDTH = 12;
|
|
45
|
-
|
|
46
|
-
// ---------------------------------------------------------------------------
|
|
47
|
-
// Formatting helpers
|
|
48
|
-
// ---------------------------------------------------------------------------
|
|
49
|
-
|
|
50
|
-
export function statusDot(tones: ProviderConsoleTones, status: ProviderStatus): { char: string; color: string } {
|
|
51
|
-
switch (status) {
|
|
52
|
-
case 'healthy': return { char: '●', color: tones.good };
|
|
53
|
-
case 'degraded': return { char: '◑', color: tones.warn };
|
|
54
|
-
case 'rate_limited': return { char: '◐', color: tones.warn };
|
|
55
|
-
case 'auth_error': return { char: '✕', color: tones.bad };
|
|
56
|
-
case 'unavailable': return { char: '✕', color: tones.bad };
|
|
57
|
-
default: return { char: '○', color: tones.unknown };
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export function statusLabel(status: ProviderStatus): string {
|
|
62
|
-
switch (status) {
|
|
63
|
-
case 'healthy': return 'online';
|
|
64
|
-
case 'degraded': return 'degraded';
|
|
65
|
-
case 'rate_limited': return 'rate-limited';
|
|
66
|
-
case 'auth_error': return 'auth error';
|
|
67
|
-
case 'unavailable': return 'unavailable';
|
|
68
|
-
default: return 'unknown';
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export function latencyColor(tones: ProviderConsoleTones, ms: number): string {
|
|
73
|
-
if (ms >= LATENCY_BAD_MS) return tones.bad;
|
|
74
|
-
if (ms >= LATENCY_WARN_MS) return tones.warn;
|
|
75
|
-
return tones.good;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export function fmtMs(ms: number): string {
|
|
79
|
-
return formatLatencyMs(ms);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export function fmtAgo(ts: number | undefined): string {
|
|
83
|
-
if (!ts) return 'n/a';
|
|
84
|
-
const sec = Math.floor((Date.now() - ts) / 1000);
|
|
85
|
-
if (sec < 60) return `${sec}s ago`;
|
|
86
|
-
if (sec < 3600) return `${Math.floor(sec / 60)}m ago`;
|
|
87
|
-
return `${Math.floor(sec / 3600)}h ago`;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export function fmtCooldown(expiresAt: number): string {
|
|
91
|
-
const remaining = Math.ceil((expiresAt - Date.now()) / 1000);
|
|
92
|
-
if (remaining <= 0) return 'expiring';
|
|
93
|
-
return `${remaining}s cooldown`;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
export function fmtTokens(count: number): string {
|
|
97
|
-
if (count >= 1_000_000) return `${(count / 1_000_000).toFixed(1)}M`;
|
|
98
|
-
if (count >= 1_000) return `${(count / 1_000).toFixed(1)}k`;
|
|
99
|
-
return String(count);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
export function fmtUsd(value: number): string {
|
|
103
|
-
if (value <= 0) return '$0';
|
|
104
|
-
return value >= 1 ? `$${value.toFixed(2)}` : `$${value.toFixed(3)}`;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
export function buildSparkline(points: readonly { readonly avgLatencyMs: number }[]): string {
|
|
108
|
-
if (points.length === 0) return ' '.repeat(SPARKLINE_WIDTH);
|
|
109
|
-
const values = points.slice(-SPARKLINE_WIDTH).map((point) => point.avgLatencyMs);
|
|
110
|
-
const min = Math.min(...values);
|
|
111
|
-
const max = Math.max(...values);
|
|
112
|
-
const range = max - min || 1;
|
|
113
|
-
const chars = values.map((value) => {
|
|
114
|
-
const idx = Math.min(
|
|
115
|
-
SPARKLINE_CHARS.length - 1,
|
|
116
|
-
Math.floor(((value - min) / range) * (SPARKLINE_CHARS.length - 1)),
|
|
117
|
-
);
|
|
118
|
-
return SPARKLINE_CHARS[idx] ?? '.';
|
|
119
|
-
});
|
|
120
|
-
while (chars.length < SPARKLINE_WIDTH) chars.unshift(' ');
|
|
121
|
-
return chars.join('');
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
export function domainColor(tones: ProviderConsoleTones, level: HealthDomainSummary['level']): string {
|
|
125
|
-
switch (level) {
|
|
126
|
-
case 'good':
|
|
127
|
-
return tones.good;
|
|
128
|
-
case 'warn':
|
|
129
|
-
return tones.warn;
|
|
130
|
-
case 'bad':
|
|
131
|
-
return tones.bad;
|
|
132
|
-
default:
|
|
133
|
-
return tones.value;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
export function routeColor(tones: ProviderConsoleTones, route: ProviderPanelAuthRoute): string {
|
|
138
|
-
switch (route) {
|
|
139
|
-
case 'subscription-oauth':
|
|
140
|
-
return tones.title;
|
|
141
|
-
case 'service-oauth':
|
|
142
|
-
return tones.good;
|
|
143
|
-
case 'api-key':
|
|
144
|
-
return tones.warn;
|
|
145
|
-
case 'secret-ref':
|
|
146
|
-
return tones.value;
|
|
147
|
-
case 'anonymous':
|
|
148
|
-
case 'none':
|
|
149
|
-
return tones.dim;
|
|
150
|
-
default:
|
|
151
|
-
return tones.value;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
export function freshnessColor(tones: ProviderConsoleTones, freshness: ProviderPanelAuthFreshness): string {
|
|
156
|
-
switch (freshness) {
|
|
157
|
-
case 'expired':
|
|
158
|
-
return tones.bad;
|
|
159
|
-
case 'expiring':
|
|
160
|
-
case 'pending':
|
|
161
|
-
return tones.warn;
|
|
162
|
-
case 'healthy':
|
|
163
|
-
return tones.good;
|
|
164
|
-
default:
|
|
165
|
-
return tones.dim;
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// ---------------------------------------------------------------------------
|
|
170
|
-
// Posture summary
|
|
171
|
-
// ---------------------------------------------------------------------------
|
|
172
|
-
|
|
173
|
-
export interface ConsolePostureInput {
|
|
174
|
-
readonly providers: readonly string[];
|
|
175
|
-
readonly entriesById: ReadonlyMap<string, ProviderHealthEntry>;
|
|
176
|
-
readonly accounts: ReadonlyMap<string, ProviderAccountPosture>;
|
|
177
|
-
readonly trackerRecords: readonly ProviderHealth[];
|
|
178
|
-
readonly compositeStatus: string;
|
|
179
|
-
readonly falloverCount: number;
|
|
180
|
-
readonly activeProvider: string | undefined;
|
|
181
|
-
/** Present when domains/maintenance render collapsed (non-domains views). */
|
|
182
|
-
readonly collapsedDomains?: {
|
|
183
|
-
readonly attention: number;
|
|
184
|
-
readonly maintenanceLevel: string;
|
|
185
|
-
};
|
|
186
|
-
readonly unattributedError: string | null;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
export function buildPostureLines(
|
|
190
|
-
width: number,
|
|
191
|
-
tones: ProviderConsoleTones,
|
|
192
|
-
palette: Palette,
|
|
193
|
-
input: ConsolePostureInput,
|
|
194
|
-
): Line[] {
|
|
195
|
-
let online = 0;
|
|
196
|
-
let rateLimited = 0;
|
|
197
|
-
let errored = 0;
|
|
198
|
-
let accountIssues = 0;
|
|
199
|
-
let expiringAuth = 0;
|
|
200
|
-
for (const name of input.providers) {
|
|
201
|
-
const status = input.entriesById.get(name)?.status ?? 'unknown';
|
|
202
|
-
if (status === 'healthy') online++;
|
|
203
|
-
else if (status === 'rate_limited') rateLimited++;
|
|
204
|
-
else if (status === 'degraded' || status === 'auth_error' || status === 'unavailable') errored++;
|
|
205
|
-
const account = input.accounts.get(name);
|
|
206
|
-
if (account) {
|
|
207
|
-
accountIssues += account.issues.length;
|
|
208
|
-
if (account.expiringSoon) expiringAuth++;
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
let totalRequests = 0;
|
|
213
|
-
let totalErrors = 0;
|
|
214
|
-
let totalTokens = 0;
|
|
215
|
-
let totalCost = 0;
|
|
216
|
-
for (const health of input.trackerRecords) {
|
|
217
|
-
totalRequests += health.requests;
|
|
218
|
-
totalErrors += health.errors;
|
|
219
|
-
totalTokens += health.totalTokens;
|
|
220
|
-
totalCost += health.totalCostUsd;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
const lines: Line[] = [
|
|
224
|
-
buildKeyValueLine(width, [
|
|
225
|
-
{ label: 'providers', value: String(input.providers.length), valueColor: tones.value },
|
|
226
|
-
{ label: 'online', value: String(online), valueColor: tones.good },
|
|
227
|
-
{ label: 'rate-limited', value: String(rateLimited), valueColor: rateLimited > 0 ? tones.warn : tones.dim },
|
|
228
|
-
{ label: 'error', value: String(errored), valueColor: errored > 0 ? tones.bad : tones.dim },
|
|
229
|
-
{ label: 'composite', value: input.compositeStatus, valueColor: input.compositeStatus === 'healthy' ? tones.good : input.compositeStatus === 'degraded' ? tones.warn : input.compositeStatus === 'critical' ? tones.bad : tones.dim },
|
|
230
|
-
], palette),
|
|
231
|
-
buildKeyValueLine(width, [
|
|
232
|
-
{ label: 'requests', value: String(totalRequests), valueColor: tones.value },
|
|
233
|
-
{ label: 'errors', value: String(totalErrors), valueColor: totalErrors > 0 ? tones.bad : tones.good },
|
|
234
|
-
{ label: 'tokens', value: fmtTokens(totalTokens), valueColor: tones.value },
|
|
235
|
-
{ label: 'cost', value: fmtUsd(totalCost), valueColor: tones.value },
|
|
236
|
-
{ label: 'fallovers', value: String(input.falloverCount), valueColor: input.falloverCount > 0 ? tones.warn : tones.dim },
|
|
237
|
-
], palette),
|
|
238
|
-
buildKeyValueLine(width, [
|
|
239
|
-
{ label: 'active', value: input.activeProvider ?? 'n/a', valueColor: tones.title },
|
|
240
|
-
{ label: 'auth alerts', value: String(expiringAuth), valueColor: expiringAuth > 0 ? tones.warn : tones.dim },
|
|
241
|
-
{ label: 'account issues', value: String(accountIssues), valueColor: accountIssues > 0 ? tones.bad : tones.dim },
|
|
242
|
-
], palette),
|
|
243
|
-
];
|
|
244
|
-
|
|
245
|
-
if (input.collapsedDomains) {
|
|
246
|
-
const { attention, maintenanceLevel } = input.collapsedDomains;
|
|
247
|
-
lines.push(buildKeyValueLine(width, [
|
|
248
|
-
{ label: 'repair domains', value: attention > 0 ? `${attention} need attention` : 'all clear', valueColor: attention > 0 ? tones.warn : tones.good },
|
|
249
|
-
{ label: 'maintenance', value: maintenanceLevel, valueColor: maintenanceLevel === 'needs-repair' ? tones.bad : maintenanceLevel === 'suggest-compact' || maintenanceLevel === 'watch' ? tones.warn : tones.good },
|
|
250
|
-
{ label: 'detail', value: 'press t for domains view', valueColor: tones.dim },
|
|
251
|
-
], palette));
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
if (input.unattributedError) {
|
|
255
|
-
lines.push(...buildBodyText(width, `Last turn error (no provider registered): ${input.unattributedError}`, palette, tones.bad));
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
return lines;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
// ---------------------------------------------------------------------------
|
|
262
|
-
// Fallback chain
|
|
263
|
-
// ---------------------------------------------------------------------------
|
|
264
|
-
|
|
265
|
-
export function buildChainLines(
|
|
266
|
-
width: number,
|
|
267
|
-
tones: ProviderConsoleTones,
|
|
268
|
-
palette: Palette,
|
|
269
|
-
chain: FallbackChainData,
|
|
270
|
-
): Line[] {
|
|
271
|
-
if (!chain.nodes.some((node) => node.providerId && node.providerId !== 'unknown')) return [];
|
|
272
|
-
const lines: Line[] = [];
|
|
273
|
-
for (const node of chain.nodes) {
|
|
274
|
-
const dot = statusDot(tones, node.providerStatus);
|
|
275
|
-
lines.push(buildAlignedRow(
|
|
276
|
-
width,
|
|
277
|
-
[
|
|
278
|
-
{ text: node.isCurrent ? '▸' : ' ', fg: tones.title },
|
|
279
|
-
{ text: `#${node.position}`, fg: tones.dim },
|
|
280
|
-
{ text: node.displayName, fg: node.isCurrent ? tones.title : tones.value, bold: node.isCurrent },
|
|
281
|
-
{ text: statusLabel(node.providerStatus), fg: dot.color },
|
|
282
|
-
{ text: node.reason ?? (node.position === 0 ? 'primary' : 'fallback'), fg: tones.dim },
|
|
283
|
-
],
|
|
284
|
-
[
|
|
285
|
-
{ width: 2 },
|
|
286
|
-
{ width: 3 },
|
|
287
|
-
{ width: Math.max(18, width - 52) },
|
|
288
|
-
{ width: 13 },
|
|
289
|
-
{ width: 16 },
|
|
290
|
-
],
|
|
291
|
-
));
|
|
292
|
-
}
|
|
293
|
-
if (chain.hasUnhealthyNode) {
|
|
294
|
-
lines.push(...buildBodyText(width, 'At least one chain node is unhealthy — fallback may engage on the next turn.', palette, tones.warn));
|
|
295
|
-
}
|
|
296
|
-
return lines;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
// ---------------------------------------------------------------------------
|
|
300
|
-
// Provider metrics table
|
|
301
|
-
// ---------------------------------------------------------------------------
|
|
302
|
-
|
|
303
|
-
export const PROVIDER_TABLE_COLUMNS = [
|
|
304
|
-
{ width: 16 },
|
|
305
|
-
{ width: 12 },
|
|
306
|
-
{ width: 7, align: 'right' as const },
|
|
307
|
-
{ width: 7, align: 'right' as const },
|
|
308
|
-
{ width: 5, align: 'right' as const },
|
|
309
|
-
{ width: SPARKLINE_WIDTH + 1 },
|
|
310
|
-
{ width: 8, align: 'right' as const },
|
|
311
|
-
{ width: 9, align: 'right' as const },
|
|
312
|
-
{ width: 10 },
|
|
313
|
-
];
|
|
314
|
-
|
|
315
|
-
export function buildProviderColumnHeader(width: number, tones: ProviderConsoleTones): Line {
|
|
316
|
-
return buildAlignedRow(
|
|
317
|
-
width,
|
|
318
|
-
[
|
|
319
|
-
{ text: 'provider', fg: tones.label, bold: true },
|
|
320
|
-
{ text: 'status', fg: tones.label, bold: true },
|
|
321
|
-
{ text: 'avg', fg: tones.label, bold: true },
|
|
322
|
-
{ text: 'p95', fg: tones.label, bold: true },
|
|
323
|
-
{ text: 'err', fg: tones.label, bold: true },
|
|
324
|
-
{ text: 'latency trend', fg: tones.label, bold: true },
|
|
325
|
-
{ text: 'tokens', fg: tones.label, bold: true },
|
|
326
|
-
{ text: 'cost', fg: tones.label, bold: true },
|
|
327
|
-
{ text: 'auth', fg: tones.label, bold: true },
|
|
328
|
-
],
|
|
329
|
-
PROVIDER_TABLE_COLUMNS,
|
|
330
|
-
{ marker: '▸' },
|
|
331
|
-
);
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
export interface ProviderRowInput {
|
|
335
|
-
readonly name: string;
|
|
336
|
-
readonly entry: ProviderHealthEntry | undefined;
|
|
337
|
-
readonly health: ProviderHealth | undefined;
|
|
338
|
-
readonly account: ProviderAccountPosture | undefined;
|
|
339
|
-
readonly selected: boolean;
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
export function buildProviderRow(width: number, tones: ProviderConsoleTones, input: ProviderRowInput): Line {
|
|
343
|
-
const { name, entry, health, account, selected } = input;
|
|
344
|
-
const status = entry?.status ?? 'unknown';
|
|
345
|
-
const dot = statusDot(tones, status);
|
|
346
|
-
const hasCalls = (entry?.totalCalls ?? 0) > 0;
|
|
347
|
-
const avgMs = entry?.avgLatencyMs ?? 0;
|
|
348
|
-
const p95Ms = entry?.p95LatencyMs ?? 0;
|
|
349
|
-
const errPct = hasCalls ? `${Math.round((entry?.errorRate ?? 0) * 100)}%` : '-';
|
|
350
|
-
return buildAlignedRow(
|
|
351
|
-
width,
|
|
352
|
-
[
|
|
353
|
-
{ text: `${dot.char} ${name}`, fg: tones.value, bold: selected },
|
|
354
|
-
{ text: statusLabel(status), fg: dot.color },
|
|
355
|
-
{ text: hasCalls && avgMs > 0 ? fmtMs(avgMs) : 'n/a', fg: hasCalls && avgMs > 0 ? latencyColor(tones, avgMs) : tones.dim },
|
|
356
|
-
{ text: hasCalls && p95Ms > 0 ? fmtMs(p95Ms) : '-', fg: tones.accent },
|
|
357
|
-
{ text: errPct, fg: hasCalls && (entry?.errorRate ?? 0) > 0 ? tones.bad : tones.dim },
|
|
358
|
-
{ text: buildSparkline(entry?.timeline.points ?? []), fg: hasCalls ? latencyColor(tones, avgMs) : tones.dim },
|
|
359
|
-
{ text: health && health.totalTokens > 0 ? fmtTokens(health.totalTokens) : '-', fg: tones.dim },
|
|
360
|
-
{
|
|
361
|
-
// Tokens flowed but the model never resolved to a real price — say so
|
|
362
|
-
// instead of collapsing into the same '-' shown for "no calls yet".
|
|
363
|
-
text: health && health.totalTokens > 0
|
|
364
|
-
? (health.hasUnpricedModel ? 'unpriced' : fmtUsd(health.totalCostUsd))
|
|
365
|
-
: '-',
|
|
366
|
-
fg: health?.hasUnpricedModel ? tones.dim : tones.value,
|
|
367
|
-
},
|
|
368
|
-
{ text: account ? account.authFreshness : '-', fg: account ? freshnessColor(tones, account.authFreshness) : tones.dim },
|
|
369
|
-
],
|
|
370
|
-
PROVIDER_TABLE_COLUMNS,
|
|
371
|
-
{ selected, selectedBg: tones.rowSelectBg, marker: '▸' },
|
|
372
|
-
);
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
// ---------------------------------------------------------------------------
|
|
376
|
-
// Selected provider detail
|
|
377
|
-
// ---------------------------------------------------------------------------
|
|
378
|
-
|
|
379
|
-
export interface SelectedDetailInput {
|
|
380
|
-
readonly selectedName: string;
|
|
381
|
-
readonly entry: ProviderHealthEntry | undefined;
|
|
382
|
-
readonly health: ProviderHealth | undefined;
|
|
383
|
-
readonly account: ProviderAccountPosture | undefined;
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
export function buildSelectedDetailSection(
|
|
387
|
-
width: number,
|
|
388
|
-
tones: ProviderConsoleTones,
|
|
389
|
-
palette: Palette,
|
|
390
|
-
input: SelectedDetailInput,
|
|
391
|
-
): PanelWorkspaceSection {
|
|
392
|
-
const { selectedName, entry, health, account } = input;
|
|
393
|
-
const status = entry?.status ?? 'unknown';
|
|
394
|
-
const lines: Line[] = [
|
|
395
|
-
buildKeyValueLine(width, [
|
|
396
|
-
{ label: 'provider', value: selectedName, valueColor: tones.value },
|
|
397
|
-
{ label: 'status', value: statusLabel(status), valueColor: statusDot(tones, status).color },
|
|
398
|
-
{ label: 'last ok', value: fmtAgo(entry?.lastSuccessAt), valueColor: tones.value },
|
|
399
|
-
{ label: 'model', value: health?.lastModelId ?? 'n/a', valueColor: tones.dim },
|
|
400
|
-
], palette),
|
|
401
|
-
];
|
|
402
|
-
if (entry?.rateLimitResetAt && entry.rateLimitResetAt > Date.now()) {
|
|
403
|
-
lines.push(...buildBodyText(width, `Cooldown: ${fmtCooldown(entry.rateLimitResetAt)}`, palette, tones.warn));
|
|
404
|
-
}
|
|
405
|
-
if (entry?.lastErrorMessage) {
|
|
406
|
-
lines.push(...buildBodyText(width, `Last error: ${entry.lastErrorMessage}`, palette, tones.bad));
|
|
407
|
-
}
|
|
408
|
-
if (entry && (entry.cacheReadTokens ?? 0) + (entry.cacheWriteTokens ?? 0) > 0) {
|
|
409
|
-
lines.push(buildKeyValueLine(width, [
|
|
410
|
-
{ label: 'cache hit', value: entry.cacheHitRate !== undefined ? `${Math.round(entry.cacheHitRate * 100)}%` : 'n/a', valueColor: tones.good },
|
|
411
|
-
{ label: 'cache read', value: fmtTokens(entry.cacheReadTokens ?? 0), valueColor: tones.value },
|
|
412
|
-
{ label: 'cache write', value: fmtTokens(entry.cacheWriteTokens ?? 0), valueColor: tones.value },
|
|
413
|
-
], palette));
|
|
414
|
-
}
|
|
415
|
-
if (account) {
|
|
416
|
-
lines.push(buildKeyValueLine(width, [
|
|
417
|
-
{ label: 'route', value: account.activeRoute, valueColor: routeColor(tones, account.activeRoute) },
|
|
418
|
-
{ label: 'preferred', value: account.preferredRoute, valueColor: tones.dim },
|
|
419
|
-
{ label: 'freshness', value: account.authFreshness, valueColor: freshnessColor(tones, account.authFreshness) },
|
|
420
|
-
{ label: 'models', value: String(account.modelCount), valueColor: tones.value },
|
|
421
|
-
], palette));
|
|
422
|
-
lines.push(...buildBodyText(width, `Auth route: ${account.activeRouteReason}`, palette, tones.dim));
|
|
423
|
-
if (account.issues.length > 0) {
|
|
424
|
-
lines.push(...buildBodyText(width, `Issue: ${account.issues[0]!}`, palette, tones.bad));
|
|
425
|
-
}
|
|
426
|
-
if (account.repairHints.length > 0) {
|
|
427
|
-
lines.push(...buildBodyText(width, `Fix: ${account.repairHints[0]!} (enter runs repair)`, palette, tones.title));
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
return { lines: buildDetailBlock(width, 'Selected provider', lines, palette) };
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
// ---------------------------------------------------------------------------
|
|
434
|
-
// Auth routes view
|
|
435
|
-
// ---------------------------------------------------------------------------
|
|
436
|
-
|
|
437
|
-
export const ROUTE_COLUMNS = [
|
|
438
|
-
{ width: 20 },
|
|
439
|
-
{ width: 8 },
|
|
440
|
-
{ width: 12 },
|
|
441
|
-
{ width: 28 },
|
|
442
|
-
];
|
|
443
|
-
|
|
444
|
-
export function buildRouteColumnHeader(width: number, tones: ProviderConsoleTones): Line {
|
|
445
|
-
return buildAlignedRow(
|
|
446
|
-
width,
|
|
447
|
-
[
|
|
448
|
-
{ text: 'route', fg: tones.label, bold: true },
|
|
449
|
-
{ text: 'usable', fg: tones.label, bold: true },
|
|
450
|
-
{ text: 'freshness', fg: tones.label, bold: true },
|
|
451
|
-
{ text: 'label', fg: tones.label, bold: true },
|
|
452
|
-
],
|
|
453
|
-
ROUTE_COLUMNS,
|
|
454
|
-
{ marker: '▸' },
|
|
455
|
-
);
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
export function buildRouteViewLines(
|
|
459
|
-
width: number,
|
|
460
|
-
tones: ProviderConsoleTones,
|
|
461
|
-
palette: Palette,
|
|
462
|
-
account: ProviderAccountPosture | undefined,
|
|
463
|
-
): Line[] {
|
|
464
|
-
const lines: Line[] = [];
|
|
465
|
-
if (!account) {
|
|
466
|
-
lines.push(...buildBodyText(width, 'No route inspection data yet for this provider. Press r to refresh posture.', palette, tones.dim));
|
|
467
|
-
return lines;
|
|
468
|
-
}
|
|
469
|
-
for (const route of account.routes) {
|
|
470
|
-
const freshness = route.freshness ?? 'unconfigured';
|
|
471
|
-
const usable = route.usable ?? route.configured;
|
|
472
|
-
lines.push(buildAlignedRow(
|
|
473
|
-
width,
|
|
474
|
-
[
|
|
475
|
-
{ text: route.route, fg: routeColor(tones, route.route), bold: route.route === account.activeRoute },
|
|
476
|
-
{ text: usable ? 'usable' : 'blocked', fg: usable ? tones.good : tones.bad },
|
|
477
|
-
{ text: freshness, fg: freshnessColor(tones, freshness) },
|
|
478
|
-
{ text: route.label, fg: tones.value },
|
|
479
|
-
],
|
|
480
|
-
ROUTE_COLUMNS,
|
|
481
|
-
{ marker: '▸' },
|
|
482
|
-
));
|
|
483
|
-
if (route.detail) {
|
|
484
|
-
lines.push(...buildBodyText(width, ` ${route.detail}`, palette, tones.dim));
|
|
485
|
-
}
|
|
486
|
-
if (route.envVars?.length) {
|
|
487
|
-
lines.push(...buildBodyText(width, ` env: ${route.envVars.join(', ')}`, palette, tones.dim));
|
|
488
|
-
}
|
|
489
|
-
for (const hint of route.repairHints ?? []) {
|
|
490
|
-
lines.push(...buildBodyText(width, ` fix: ${hint}`, palette, tones.title));
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
for (const issue of account.issues) {
|
|
494
|
-
lines.push(...buildBodyText(width, `issue: ${issue}`, palette, tones.bad));
|
|
495
|
-
}
|
|
496
|
-
if (account.repairHints.length > 0) {
|
|
497
|
-
lines.push(...buildBodyText(width, `Enter dispatches /accounts repair ${account.providerId}.`, palette, tones.dim));
|
|
498
|
-
}
|
|
499
|
-
return lines;
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
// ---------------------------------------------------------------------------
|
|
503
|
-
// Repair domains + session maintenance view
|
|
504
|
-
// ---------------------------------------------------------------------------
|
|
505
|
-
|
|
506
|
-
export function buildDomainLines(
|
|
507
|
-
width: number,
|
|
508
|
-
tones: ProviderConsoleTones,
|
|
509
|
-
palette: Palette,
|
|
510
|
-
summaries: readonly HealthDomainSummary[],
|
|
511
|
-
): Line[] {
|
|
512
|
-
const lines: Line[] = [];
|
|
513
|
-
for (const domain of summaries) {
|
|
514
|
-
lines.push(buildAlignedRow(
|
|
515
|
-
width,
|
|
516
|
-
[
|
|
517
|
-
{ text: domain.name, fg: tones.value, bold: true },
|
|
518
|
-
{ text: domain.summary, fg: domainColor(tones, domain.level) },
|
|
519
|
-
{ text: domain.next, fg: tones.dim },
|
|
520
|
-
],
|
|
521
|
-
[
|
|
522
|
-
{ width: 14 },
|
|
523
|
-
{ width: Math.max(10, width - 38) },
|
|
524
|
-
{ width: 20 },
|
|
525
|
-
],
|
|
526
|
-
));
|
|
527
|
-
for (const detail of domain.details.slice(0, 2)) {
|
|
528
|
-
lines.push(...buildBodyText(width, ` ${detail}`, palette, tones.dim));
|
|
529
|
-
}
|
|
530
|
-
if (domain.nextSteps.length > 1) {
|
|
531
|
-
lines.push(...buildBodyText(width, ` next: ${domain.nextSteps.join(' | ')}`, palette, tones.title));
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
return lines;
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
export interface MaintenanceSummaryInput {
|
|
538
|
-
readonly level: string;
|
|
539
|
-
readonly guidanceMode: string;
|
|
540
|
-
readonly usagePct: number;
|
|
541
|
-
readonly remainingTokens: number;
|
|
542
|
-
readonly reasons: readonly string[];
|
|
543
|
-
readonly nextSteps: readonly string[];
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
export function buildMaintenanceLines(
|
|
547
|
-
width: number,
|
|
548
|
-
tones: ProviderConsoleTones,
|
|
549
|
-
palette: Palette,
|
|
550
|
-
maintenance: MaintenanceSummaryInput,
|
|
551
|
-
): Line[] {
|
|
552
|
-
const lines: Line[] = [
|
|
553
|
-
buildKeyValueLine(width, [
|
|
554
|
-
{ label: 'level', value: maintenance.level, valueColor: maintenance.level === 'needs-repair' ? tones.bad : maintenance.level === 'suggest-compact' || maintenance.level === 'watch' ? tones.warn : tones.good },
|
|
555
|
-
{ label: 'guidance', value: maintenance.guidanceMode, valueColor: tones.value },
|
|
556
|
-
{ label: 'usage', value: `${maintenance.usagePct}%`, valueColor: maintenance.usagePct >= 80 ? tones.bad : maintenance.usagePct >= 70 ? tones.warn : tones.value },
|
|
557
|
-
{ label: 'remaining', value: maintenance.remainingTokens.toLocaleString(), valueColor: tones.value },
|
|
558
|
-
], palette),
|
|
559
|
-
];
|
|
560
|
-
for (const reason of maintenance.reasons.slice(0, 3)) {
|
|
561
|
-
lines.push(...buildBodyText(width, reason, palette, tones.dim));
|
|
562
|
-
}
|
|
563
|
-
if (maintenance.nextSteps.length > 0) {
|
|
564
|
-
lines.push(...buildBodyText(width, `Next: ${maintenance.nextSteps.join(' | ')}`, palette, tones.title));
|
|
565
|
-
}
|
|
566
|
-
return lines;
|
|
567
|
-
}
|