@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,342 +0,0 @@
|
|
|
1
|
-
import type { Line } from '../types/grid.ts';
|
|
2
|
-
import { createEmptyLine } from '../types/grid.ts';
|
|
3
|
-
import { fitDisplay } from '../utils/terminal-width.ts';
|
|
4
|
-
import { formatElapsed } from '../utils/format-elapsed.ts';
|
|
5
|
-
import { ScrollableListPanel } from './scrollable-list-panel.ts';
|
|
6
|
-
import type { KeyName, PanelIntegrationContext } from './types.ts';
|
|
7
|
-
import type { ProviderSubscription, PendingSubscriptionLogin } from '@pellux/goodvibes-sdk/platform/config';
|
|
8
|
-
import { listBuiltinSubscriptionProviders } from '@pellux/goodvibes-sdk/platform/config';
|
|
9
|
-
import { type ConfirmState, handleConfirmInput } from './confirm-state.ts';
|
|
10
|
-
import type { ServiceInspectionQuery, SubscriptionAccessQuery } from '../runtime/ui-service-queries.ts';
|
|
11
|
-
import {
|
|
12
|
-
buildEmptyState,
|
|
13
|
-
buildKeyboardHints,
|
|
14
|
-
buildKeyValueLine,
|
|
15
|
-
buildPanelListRow,
|
|
16
|
-
buildPanelLine,
|
|
17
|
-
buildSummaryBlock,
|
|
18
|
-
buildPanelWorkspace,
|
|
19
|
-
DEFAULT_PANEL_PALETTE,
|
|
20
|
-
} from './polish.ts';
|
|
21
|
-
|
|
22
|
-
// Base chrome only — title band, state colors, and text tokens all come
|
|
23
|
-
// straight from DEFAULT_PANEL_PALETTE (WO-002).
|
|
24
|
-
const C = DEFAULT_PANEL_PALETTE;
|
|
25
|
-
|
|
26
|
-
interface SubscriptionRow {
|
|
27
|
-
readonly provider: string;
|
|
28
|
-
readonly hasOAuthConfig: boolean;
|
|
29
|
-
readonly subscription: ProviderSubscription | null;
|
|
30
|
-
readonly pending: PendingSubscriptionLogin | null;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function statusOf(row: SubscriptionRow): 'active' | 'pending' | 'available' | 'unconfigured' {
|
|
34
|
-
if (row.subscription) return 'active';
|
|
35
|
-
if (row.pending) return 'pending';
|
|
36
|
-
return row.hasOAuthConfig ? 'available' : 'unconfigured';
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function statusColor(status: ReturnType<typeof statusOf>): string {
|
|
40
|
-
switch (status) {
|
|
41
|
-
case 'active':
|
|
42
|
-
return C.good;
|
|
43
|
-
case 'pending':
|
|
44
|
-
return C.warn;
|
|
45
|
-
case 'available':
|
|
46
|
-
return C.info;
|
|
47
|
-
case 'unconfigured':
|
|
48
|
-
return C.dim;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Highlight window for "expiring soon" — mirrors the provider-console
|
|
53
|
-
// convention of flagging tokens inside their last day of validity so the
|
|
54
|
-
// operator can refresh/re-login before the subscription actually lapses.
|
|
55
|
-
const EXPIRING_SOON_MS = 24 * 60 * 60 * 1000;
|
|
56
|
-
|
|
57
|
-
function isExpiringSoon(subscription: ProviderSubscription | null): boolean {
|
|
58
|
-
if (!subscription?.expiresAt) return false;
|
|
59
|
-
const remaining = subscription.expiresAt - Date.now();
|
|
60
|
-
return remaining > 0 && remaining <= EXPIRING_SOON_MS;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export class SubscriptionPanel extends ScrollableListPanel<SubscriptionRow> {
|
|
64
|
-
private readonly serviceRegistry: Pick<ServiceInspectionQuery, 'getAll'>;
|
|
65
|
-
private readonly subscriptionManager: SubscriptionAccessQuery;
|
|
66
|
-
private rows: SubscriptionRow[] = [];
|
|
67
|
-
/** Pending logout confirmation — uses project-standard ConfirmState contract. */
|
|
68
|
-
private confirm: ConfirmState<string> | null = null;
|
|
69
|
-
// Set by handleInput (Enter on an 'available' row) and consumed on the very
|
|
70
|
-
// next handlePanelIntegrationAction dispatch of that same key — handleInput
|
|
71
|
-
// has no ctx.executeCommand, so the actual login-start dispatch happens via
|
|
72
|
-
// the bridge (remote-panel.ts / token-budget-panel.ts pattern).
|
|
73
|
-
private pendingCommand: { name: string; args: string[] } | null = null;
|
|
74
|
-
// Background posture refresh while the panel is visible, so a login/logout
|
|
75
|
-
// that completes elsewhere (e.g. the browser OAuth handshake finishing)
|
|
76
|
-
// shows up without requiring an explicit 'r'.
|
|
77
|
-
private refreshTimer: ReturnType<typeof setInterval> | null = null;
|
|
78
|
-
|
|
79
|
-
public constructor(
|
|
80
|
-
serviceRegistry: Pick<ServiceInspectionQuery, 'getAll'>,
|
|
81
|
-
subscriptionManager: SubscriptionAccessQuery,
|
|
82
|
-
) {
|
|
83
|
-
super('subscription', 'Subscriptions', 'B', 'providers');
|
|
84
|
-
this.showSelectionGutter = true; // I5: non-color selection affordance
|
|
85
|
-
this.serviceRegistry = serviceRegistry;
|
|
86
|
-
this.subscriptionManager = subscriptionManager;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
public override onActivate(): void {
|
|
90
|
-
super.onActivate();
|
|
91
|
-
this.refresh();
|
|
92
|
-
if (this.refreshTimer === null) {
|
|
93
|
-
this.refreshTimer = this.registerTimer(setInterval(() => {
|
|
94
|
-
this.refresh();
|
|
95
|
-
this.markDirty();
|
|
96
|
-
}, 5_000));
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
public override onDeactivate(): void {
|
|
101
|
-
if (this.refreshTimer !== null) {
|
|
102
|
-
this.clearTimer(this.refreshTimer);
|
|
103
|
-
this.refreshTimer = null;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/** Consumes the login-start command queued by handleInput's Enter-on-'available' branch. */
|
|
108
|
-
public handlePanelIntegrationAction(_key: string, ctx: PanelIntegrationContext): boolean {
|
|
109
|
-
if (!this.pendingCommand) return false;
|
|
110
|
-
const { name, args } = this.pendingCommand;
|
|
111
|
-
this.pendingCommand = null;
|
|
112
|
-
void ctx.executeCommand?.(name, args);
|
|
113
|
-
return true;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
protected override getPalette() { return C; }
|
|
117
|
-
protected override getEmptyStateMessage() { return ' No provider subscriptions are active yet.'; }
|
|
118
|
-
protected override getEmptyStateActions() {
|
|
119
|
-
return [
|
|
120
|
-
{ command: '/subscription login openai start', summary: 'start the first-class OpenAI subscription flow' },
|
|
121
|
-
{ command: '/login provider <name> start', summary: 'use the front-door auth surface for supported providers' },
|
|
122
|
-
{ command: '/services auth-review', summary: 'inspect configured service auth posture and stored secrets' },
|
|
123
|
-
];
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
protected getItems(): readonly SubscriptionRow[] {
|
|
127
|
-
return this.rows;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
protected renderItem(row: SubscriptionRow, index: number, selected: boolean, width: number): Line {
|
|
131
|
-
const status = statusOf(row);
|
|
132
|
-
// Expiring-soon highlight: the STATUS text stays 'ACTIVE' (the row is
|
|
133
|
-
// still active), but it's colored as a warning and gets a trailing
|
|
134
|
-
// marker so an about-to-lapse token stands out from the list.
|
|
135
|
-
const expiringSoon = isExpiringSoon(row.subscription);
|
|
136
|
-
return buildPanelListRow(width, [
|
|
137
|
-
{ text: fitDisplay(row.provider, 16), fg: C.value },
|
|
138
|
-
{ text: ` ${status.toUpperCase().padEnd(12)}`, fg: expiringSoon ? C.warn : statusColor(status) },
|
|
139
|
-
{ text: ` oauth=${row.hasOAuthConfig ? 'yes' : 'no'} `, fg: row.hasOAuthConfig ? C.info : C.dim },
|
|
140
|
-
{ text: ` override=${row.subscription ? 'active' : 'off'}`, fg: row.subscription ? C.good : C.dim },
|
|
141
|
-
...(expiringSoon ? [{ text: ' expiring soon', fg: C.warn }] : []),
|
|
142
|
-
], C, { selected, selectedBg: C.selectBg });
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
public handleInput(key: KeyName): boolean {
|
|
146
|
-
// Project-standard confirm contract: Enter/y confirm; n/Esc cancel; other absorbed.
|
|
147
|
-
const confirmResult = handleConfirmInput(this.confirm, key);
|
|
148
|
-
if (confirmResult === 'confirmed') {
|
|
149
|
-
const provider = this.confirm!.subject;
|
|
150
|
-
this.confirm = null;
|
|
151
|
-
this.subscriptionManager.logout(provider);
|
|
152
|
-
this.refresh();
|
|
153
|
-
this.markDirty();
|
|
154
|
-
return true;
|
|
155
|
-
}
|
|
156
|
-
if (confirmResult === 'cancelled') {
|
|
157
|
-
this.confirm = null;
|
|
158
|
-
this.markDirty();
|
|
159
|
-
return true;
|
|
160
|
-
}
|
|
161
|
-
if (confirmResult === 'absorbed') return true;
|
|
162
|
-
|
|
163
|
-
if (this.rows.length === 0) return false;
|
|
164
|
-
const selected = this.getSelectedItem() ?? null;
|
|
165
|
-
if (key === 'up' || key === 'k') {
|
|
166
|
-
this.selectedIndex = Math.max(0, this.selectedIndex - 1);
|
|
167
|
-
this.confirm = null;
|
|
168
|
-
this.markDirty();
|
|
169
|
-
return true;
|
|
170
|
-
}
|
|
171
|
-
if (key === 'down' || key === 'j') {
|
|
172
|
-
this.selectedIndex = Math.min(this.rows.length - 1, this.selectedIndex + 1);
|
|
173
|
-
this.confirm = null;
|
|
174
|
-
this.markDirty();
|
|
175
|
-
return true;
|
|
176
|
-
}
|
|
177
|
-
if (key === 'enter' || key === 'return') {
|
|
178
|
-
if (selected?.subscription) {
|
|
179
|
-
this.confirm = { subject: selected.provider, label: selected.provider };
|
|
180
|
-
this.markDirty();
|
|
181
|
-
return true;
|
|
182
|
-
}
|
|
183
|
-
// Enter on an 'available' row starts the real login flow via the
|
|
184
|
-
// bridge instead of leaving it as a printed signpost.
|
|
185
|
-
if (selected && statusOf(selected) === 'available') {
|
|
186
|
-
this.pendingCommand = { name: 'subscription', args: ['login', selected.provider, 'start'] };
|
|
187
|
-
return true;
|
|
188
|
-
}
|
|
189
|
-
return false;
|
|
190
|
-
}
|
|
191
|
-
if (key === 'r') {
|
|
192
|
-
this.refresh();
|
|
193
|
-
this.confirm = null;
|
|
194
|
-
this.markDirty();
|
|
195
|
-
return true;
|
|
196
|
-
}
|
|
197
|
-
return false;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
private refresh(): void {
|
|
201
|
-
const manager = this.subscriptionManager;
|
|
202
|
-
const services = this.serviceRegistry.getAll();
|
|
203
|
-
const builtinProviders = listBuiltinSubscriptionProviders();
|
|
204
|
-
const providers = new Set<string>([
|
|
205
|
-
...builtinProviders.map((provider) => provider.provider),
|
|
206
|
-
...Object.values(services)
|
|
207
|
-
.filter((service) => service.authType === 'oauth' && service.oauth)
|
|
208
|
-
.map((service) => service.providerId ?? service.name),
|
|
209
|
-
...manager.list().map((entry) => entry.provider),
|
|
210
|
-
...manager.listPending().map((entry) => entry.provider),
|
|
211
|
-
]);
|
|
212
|
-
this.rows = [...providers]
|
|
213
|
-
.sort((a, b) => a.localeCompare(b))
|
|
214
|
-
.map((provider) => {
|
|
215
|
-
const service = Object.values(services).find((entry) => (
|
|
216
|
-
(entry.providerId ?? entry.name) === provider && entry.authType === 'oauth' && entry.oauth
|
|
217
|
-
));
|
|
218
|
-
const builtin = builtinProviders.find((entry) => entry.provider === provider);
|
|
219
|
-
return {
|
|
220
|
-
provider,
|
|
221
|
-
hasOAuthConfig: Boolean(service?.oauth) || Boolean(builtin),
|
|
222
|
-
subscription: manager.get(provider),
|
|
223
|
-
pending: manager.getPending(provider),
|
|
224
|
-
} satisfies SubscriptionRow;
|
|
225
|
-
});
|
|
226
|
-
this.selectedIndex = Math.min(this.selectedIndex, Math.max(0, this.rows.length - 1));
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
/** Footer hints that reflect the current view: confirm-pending vs browsing. */
|
|
230
|
-
private buildFooterHint(width: number): Line {
|
|
231
|
-
if (this.confirm) {
|
|
232
|
-
return buildKeyboardHints(width, [
|
|
233
|
-
{ keys: 'y/Enter', label: 'confirm sign out' },
|
|
234
|
-
{ keys: 'n/Esc', label: 'cancel' },
|
|
235
|
-
], C);
|
|
236
|
-
}
|
|
237
|
-
const selected = this.getSelectedItem();
|
|
238
|
-
const hints: Array<{ keys: string; label: string }> = [{ keys: 'Up/Down', label: 'select' }];
|
|
239
|
-
if (selected?.subscription) hints.push({ keys: 'Enter', label: 'sign out' });
|
|
240
|
-
else if (selected?.hasOAuthConfig) hints.push({ keys: 'Enter', label: 'sign in' });
|
|
241
|
-
hints.push({ keys: 'r', label: 'refresh' });
|
|
242
|
-
return buildKeyboardHints(width, hints, C);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// WO-139: refresh() moved out of render() (hooks-panel.ts:109 pattern) — it
|
|
246
|
-
// now runs on onActivate(), on 'r', and on the 5s background timer above,
|
|
247
|
-
// instead of rebuilding the row set (service registry scan + builtin
|
|
248
|
-
// provider list + subscription-manager reads) on every single frame.
|
|
249
|
-
public render(width: number, height: number): Line[] {
|
|
250
|
-
this.clampSelection();
|
|
251
|
-
const selected = this.getSelectedItem();
|
|
252
|
-
const intro = 'Review provider login state, subscription-backed routing, and pending browser auth handshakes.';
|
|
253
|
-
|
|
254
|
-
const activeCount = this.rows.filter((row) => row.subscription).length;
|
|
255
|
-
const pendingCount = this.rows.filter((row) => row.pending).length;
|
|
256
|
-
const postureLines = [
|
|
257
|
-
buildKeyValueLine(width, [
|
|
258
|
-
{ label: 'configured', value: String(this.rows.filter((row) => row.hasOAuthConfig).length), valueColor: C.info },
|
|
259
|
-
{ label: 'active', value: String(activeCount), valueColor: activeCount > 0 ? C.good : C.dim },
|
|
260
|
-
{ label: 'pending', value: String(pendingCount), valueColor: pendingCount > 0 ? C.warn : C.dim },
|
|
261
|
-
{ label: 'providers', value: String(this.rows.length), valueColor: C.value },
|
|
262
|
-
], C),
|
|
263
|
-
buildKeyValueLine(width, [
|
|
264
|
-
{ label: 'selected', value: (selected?.provider ?? 'none'), valueColor: selected ? C.value : C.dim },
|
|
265
|
-
{ label: 'status', value: selected ? statusOf(selected) : 'n/a', valueColor: selected ? statusColor(statusOf(selected)) : C.dim },
|
|
266
|
-
], C),
|
|
267
|
-
];
|
|
268
|
-
|
|
269
|
-
// Empty state: render posture + base empty state
|
|
270
|
-
if (this.rows.length === 0) {
|
|
271
|
-
const summaryLines = buildSummaryBlock(width, 'Subscription posture', postureLines, C);
|
|
272
|
-
const emptyLines = buildEmptyState(
|
|
273
|
-
width,
|
|
274
|
-
this.getEmptyStateMessage(),
|
|
275
|
-
'Built-in OAuth-capable providers and configured service providers will appear here once available for browser login or session import.',
|
|
276
|
-
this.getEmptyStateActions(),
|
|
277
|
-
C,
|
|
278
|
-
);
|
|
279
|
-
const workspace = buildPanelWorkspace(width, height, {
|
|
280
|
-
title: 'Provider Subscriptions',
|
|
281
|
-
intro,
|
|
282
|
-
sections: [{ lines: [...summaryLines, ...emptyLines] }],
|
|
283
|
-
footerLines: [
|
|
284
|
-
this.buildFooterHint(width),
|
|
285
|
-
],
|
|
286
|
-
palette: C,
|
|
287
|
-
});
|
|
288
|
-
while (workspace.length < height) workspace.push(createEmptyLine(width));
|
|
289
|
-
return workspace.slice(0, height);
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
const selectedRow = selected;
|
|
293
|
-
const detailRows: Line[] = [];
|
|
294
|
-
if (selectedRow) {
|
|
295
|
-
detailRows.push(buildKeyValueLine(width, [
|
|
296
|
-
{ label: 'provider', value: selectedRow.provider, valueColor: C.value },
|
|
297
|
-
{ label: 'status', value: statusOf(selectedRow), valueColor: statusColor(statusOf(selectedRow)) },
|
|
298
|
-
{ label: 'oauth config', value: selectedRow.hasOAuthConfig ? 'present' : 'missing', valueColor: selectedRow.hasOAuthConfig ? C.good : C.bad },
|
|
299
|
-
], C));
|
|
300
|
-
if (selectedRow.subscription) {
|
|
301
|
-
const expiringSoon = isExpiringSoon(selectedRow.subscription);
|
|
302
|
-
const expires = selectedRow.subscription.expiresAt
|
|
303
|
-
? new Date(selectedRow.subscription.expiresAt).toISOString()
|
|
304
|
-
: 'n/a';
|
|
305
|
-
detailRows.push(buildKeyValueLine(width, [
|
|
306
|
-
{ label: 'token type', value: selectedRow.subscription.tokenType, valueColor: C.info },
|
|
307
|
-
{ label: 'expires', value: expiringSoon ? `${expires} (expiring soon)` : expires, valueColor: expiringSoon ? C.warn : C.dim },
|
|
308
|
-
], C));
|
|
309
|
-
detailRows.push(buildPanelLine(width, [[
|
|
310
|
-
` ${selectedRow.subscription.overrideAmbientApiKeys
|
|
311
|
-
? 'Provider subscription overrides ambient API-key resolution for this provider.'
|
|
312
|
-
: 'Stored for subscription-backed flows. Ambient API-key resolution remains unchanged.'}`,
|
|
313
|
-
C.dim,
|
|
314
|
-
]]));
|
|
315
|
-
if (expiringSoon) {
|
|
316
|
-
detailRows.push(buildPanelLine(width, [[' Token expires within 24h — sign out and re-login (Enter) to refresh it before it lapses.', C.warn]]));
|
|
317
|
-
}
|
|
318
|
-
if (this.confirm?.subject === selectedRow.provider) {
|
|
319
|
-
detailRows.push(buildPanelLine(width, [[` Sign out ${selectedRow.provider}? Press y or Enter to confirm, n or Esc to cancel.`, C.warn]]));
|
|
320
|
-
}
|
|
321
|
-
} else if (selectedRow.pending) {
|
|
322
|
-
const age = formatElapsed(Date.now() - selectedRow.pending.createdAt);
|
|
323
|
-
detailRows.push(buildPanelLine(width, [[` Login is pending (started ${age} ago). Finish with /subscription login <provider> finish <code>.`, C.warn]]));
|
|
324
|
-
} else if (selectedRow.hasOAuthConfig) {
|
|
325
|
-
detailRows.push(buildPanelLine(width, [[' Ready for login. Press Enter to start it here.', C.dim]]));
|
|
326
|
-
} else {
|
|
327
|
-
detailRows.push(buildPanelLine(width, [[' Add a provider-specific OAuth config or enable a built-in subscription provider to use subscription login.', C.bad]]));
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
const headerLines: Line[] = buildSummaryBlock(width, 'Subscription posture', postureLines, C);
|
|
332
|
-
|
|
333
|
-
return this.renderList(width, height, {
|
|
334
|
-
title: 'Provider Subscriptions',
|
|
335
|
-
header: headerLines,
|
|
336
|
-
footer: [
|
|
337
|
-
...detailRows,
|
|
338
|
-
this.buildFooterHint(width),
|
|
339
|
-
],
|
|
340
|
-
});
|
|
341
|
-
}
|
|
342
|
-
}
|