@pellux/goodvibes-tui 0.20.3 → 0.22.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 +50 -0
- package/README.md +23 -2
- package/docs/foundation-artifacts/operator-contract.json +78 -1
- package/package.json +4 -2
- package/src/audio/spoken-turn-controller.ts +31 -1
- package/src/audio/spoken-turn-wiring.ts +26 -4
- package/src/cli/bundle-command.ts +1 -1
- package/src/cli/completions/generate.ts +658 -0
- package/src/cli/config-overrides.ts +68 -0
- package/src/cli/entrypoint.ts +6 -0
- package/src/cli/help.ts +4 -2
- package/src/cli/management-commands.ts +1 -1
- package/src/cli/management.ts +1 -8
- package/src/cli/parser.ts +31 -18
- package/src/cli/service-command.ts +1 -1
- package/src/cli/surface-command.ts +1 -1
- package/src/cli/tui-startup.ts +72 -10
- package/src/cli/types.ts +14 -3
- package/src/cli-flags.ts +1 -0
- package/src/config/atomic-write.ts +70 -0
- package/src/config/goodvibes-home-audit.ts +2 -0
- package/src/config/read-versioned.ts +115 -0
- package/src/core/context-auto-compact.ts +77 -0
- package/src/core/conversation-rendering.ts +49 -15
- package/src/core/conversation.ts +101 -16
- package/src/core/format-user-error.ts +192 -0
- package/src/core/stream-event-wiring.ts +144 -0
- package/src/core/stream-stall-watchdog.ts +103 -0
- package/src/core/system-message-router.ts +5 -1
- package/src/core/turn-event-wiring.ts +124 -0
- package/src/daemon/cli.ts +5 -0
- package/src/export/cost-utils.ts +71 -0
- package/src/export/gist-uploader.ts +136 -0
- package/src/input/command-registry.ts +32 -1
- package/src/input/commands/control-room-runtime.ts +10 -10
- package/src/input/commands/experience-runtime.ts +5 -4
- package/src/input/commands/knowledge.ts +1 -1
- package/src/input/commands/local-auth-runtime.ts +27 -5
- package/src/input/commands/local-setup.ts +4 -6
- package/src/input/commands/memory-product-runtime.ts +8 -6
- package/src/input/commands/operator-panel-runtime.ts +1 -1
- package/src/input/commands/operator-runtime.ts +3 -10
- package/src/input/commands/{integration-runtime.ts → plugin-runtime.ts} +1 -1
- package/src/input/commands/provider.ts +57 -3
- package/src/input/commands/recall-review.ts +26 -2
- package/src/input/commands/services-runtime.ts +2 -2
- package/src/input/commands/session-workflow.ts +8 -16
- package/src/input/commands/session.ts +70 -20
- package/src/input/commands/share-runtime.ts +99 -12
- package/src/input/commands/tts-runtime.ts +30 -4
- package/src/input/commands.ts +2 -4
- package/src/input/delete-key-policy.ts +46 -0
- package/src/input/feed-context-factory.ts +2 -0
- package/src/input/handler-feed.ts +3 -0
- package/src/input/handler-interactions.ts +2 -15
- package/src/input/handler-modal-routes.ts +128 -12
- package/src/input/handler-modal-token-routes.ts +22 -5
- package/src/input/handler-onboarding-cloudflare.ts +1 -1
- package/src/input/handler-onboarding.ts +73 -69
- package/src/input/handler-types.ts +163 -0
- package/src/input/handler.ts +6 -2
- package/src/input/input-history.ts +76 -6
- package/src/input/model-picker-filter.ts +265 -0
- package/src/input/model-picker-items.ts +208 -0
- package/src/input/model-picker.ts +92 -325
- package/src/input/onboarding/handler-onboarding-routes.ts +7 -2
- package/src/input/onboarding/onboarding-verification-helpers.ts +76 -0
- package/src/input/onboarding/onboarding-wizard-apply.ts +14 -4
- package/src/input/onboarding/onboarding-wizard-cloudflare-step.ts +16 -2
- package/src/input/onboarding/onboarding-wizard-cloudflare.ts +8 -8
- package/src/input/onboarding/onboarding-wizard-external-surface-extra-specs.ts +1 -1
- package/src/input/onboarding/onboarding-wizard-external-surfaces.ts +2 -29
- package/src/input/onboarding/onboarding-wizard-rules.ts +28 -28
- package/src/input/onboarding/onboarding-wizard-state.ts +20 -20
- package/src/input/onboarding/onboarding-wizard-steps.ts +24 -25
- package/src/input/onboarding/onboarding-wizard-types.ts +145 -3
- package/src/input/onboarding/onboarding-wizard-validation.ts +77 -0
- package/src/input/onboarding/onboarding-wizard.ts +3 -3
- package/src/input/settings-modal-behavior.ts +5 -0
- package/src/input/settings-modal-data.ts +378 -0
- package/src/input/settings-modal-mutations.ts +157 -0
- package/src/input/settings-modal-reset.ts +154 -0
- package/src/input/settings-modal.ts +236 -232
- package/src/main.ts +93 -85
- package/src/panels/agent-inspector-panel.ts +120 -18
- package/src/panels/agent-inspector-shared.ts +29 -0
- package/src/panels/builtin/agent.ts +4 -1
- package/src/panels/builtin/development.ts +5 -1
- package/src/panels/builtin/knowledge.ts +14 -13
- package/src/panels/builtin/operations.ts +22 -1
- package/src/panels/builtin/shared.ts +7 -0
- package/src/panels/cockpit-panel.ts +123 -3
- package/src/panels/cockpit-read-model.ts +232 -0
- package/src/panels/confirm-state.ts +27 -12
- package/src/panels/cost-tracker-panel.ts +23 -67
- package/src/panels/eval-panel.ts +10 -9
- package/src/panels/index.ts +1 -1
- package/src/panels/knowledge-graph-panel.ts +84 -0
- package/src/panels/local-auth-panel.ts +124 -4
- package/src/panels/memory-panel.ts +370 -40
- package/src/panels/project-planning-panel.ts +42 -4
- package/src/panels/search-focus.ts +11 -5
- package/src/panels/session-maintenance.ts +66 -15
- package/src/panels/subscription-panel.ts +33 -25
- package/src/panels/types.ts +28 -1
- package/src/panels/wrfc-panel.ts +224 -41
- package/src/renderer/agent-detail-modal.ts +118 -13
- package/src/renderer/code-block.ts +10 -2
- package/src/renderer/compositor.ts +18 -4
- package/src/renderer/context-inspector.ts +1 -5
- package/src/renderer/context-status-hint.ts +54 -0
- package/src/renderer/diff.ts +94 -21
- package/src/renderer/markdown.ts +29 -13
- package/src/renderer/settings-modal-helpers.ts +1 -1
- package/src/renderer/settings-modal.ts +90 -10
- package/src/renderer/shell-surface.ts +10 -0
- package/src/renderer/syntax-highlighter.ts +10 -3
- package/src/renderer/term-caps.ts +318 -0
- package/src/renderer/theme.ts +158 -0
- package/src/renderer/tool-call.ts +12 -2
- package/src/renderer/ui-factory.ts +50 -6
- package/src/runtime/bootstrap-command-context.ts +1 -0
- package/src/runtime/bootstrap-command-parts.ts +18 -0
- package/src/runtime/bootstrap-core.ts +145 -13
- package/src/runtime/bootstrap-shell.ts +11 -0
- package/src/runtime/bootstrap.ts +9 -0
- package/src/runtime/onboarding/apply.ts +4 -6
- package/src/runtime/onboarding/index.ts +1 -0
- package/src/runtime/onboarding/markers.ts +42 -49
- package/src/runtime/onboarding/progress.ts +148 -0
- package/src/runtime/onboarding/state.ts +133 -55
- package/src/runtime/onboarding/types.ts +20 -0
- package/src/runtime/services.ts +27 -1
- package/src/runtime/wrfc-persistence.ts +237 -0
- package/src/shell/blocking-input.ts +20 -5
- package/src/tools/wrfc-agent-guard.ts +64 -3
- package/src/utils/format-elapsed.ts +30 -0
- package/src/utils/terminal-width.ts +45 -0
- package/src/version.ts +1 -1
- package/src/work-plans/work-plan-store.ts +4 -6
- package/src/panels/knowledge-panel.ts +0 -345
- package/src/planning/project-planning-coordinator.ts +0 -543
package/src/panels/eval-panel.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import { BasePanel } from './base-panel.ts';
|
|
9
9
|
import type { Line } from '../types/grid.ts';
|
|
10
|
+
import type { KeyName } from './types.ts';
|
|
10
11
|
import { createEmptyLine } from '../types/grid.ts';
|
|
11
12
|
import {
|
|
12
13
|
buildEmptyState,
|
|
@@ -136,21 +137,21 @@ export class EvalPanel extends BasePanel {
|
|
|
136
137
|
this._unsub = null;
|
|
137
138
|
}
|
|
138
139
|
|
|
139
|
-
public handleInput(key:
|
|
140
|
+
public handleInput(key: KeyName): boolean {
|
|
140
141
|
const suites = this._registry.getSuiteResults();
|
|
141
142
|
|
|
142
143
|
if (this._mode === 'list') {
|
|
143
|
-
if (key === '
|
|
144
|
+
if (key === 'up' || key === 'k') {
|
|
144
145
|
this._selectedSuiteIdx = Math.max(0, this._selectedSuiteIdx - 1);
|
|
145
146
|
this.markDirty();
|
|
146
147
|
return true;
|
|
147
148
|
}
|
|
148
|
-
if (key === '
|
|
149
|
+
if (key === 'down' || key === 'j') {
|
|
149
150
|
this._selectedSuiteIdx = Math.min(suites.length - 1, this._selectedSuiteIdx + 1);
|
|
150
151
|
this.markDirty();
|
|
151
152
|
return true;
|
|
152
153
|
}
|
|
153
|
-
if ((key === '
|
|
154
|
+
if ((key === 'enter' || key === 'return' || key === 'l') && suites.length > 0) {
|
|
154
155
|
this._mode = 'detail';
|
|
155
156
|
this._selectedScenarioIdx = 0;
|
|
156
157
|
this._scrollOffset = 0;
|
|
@@ -161,12 +162,12 @@ export class EvalPanel extends BasePanel {
|
|
|
161
162
|
}
|
|
162
163
|
|
|
163
164
|
// detail mode
|
|
164
|
-
if (key === '
|
|
165
|
+
if (key === 'escape' || key === 'q' || key === 'h') {
|
|
165
166
|
this._mode = 'list';
|
|
166
167
|
this.markDirty();
|
|
167
168
|
return true;
|
|
168
169
|
}
|
|
169
|
-
if (key === '
|
|
170
|
+
if (key === 'up' || key === 'k') {
|
|
170
171
|
const suite = suites[this._selectedSuiteIdx];
|
|
171
172
|
if (suite) {
|
|
172
173
|
this._selectedScenarioIdx = Math.max(0, this._selectedScenarioIdx - 1);
|
|
@@ -175,7 +176,7 @@ export class EvalPanel extends BasePanel {
|
|
|
175
176
|
}
|
|
176
177
|
return true;
|
|
177
178
|
}
|
|
178
|
-
if (key === '
|
|
179
|
+
if (key === 'down' || key === 'j') {
|
|
179
180
|
const suite = suites[this._selectedSuiteIdx];
|
|
180
181
|
if (suite) {
|
|
181
182
|
this._selectedScenarioIdx = Math.min(
|
|
@@ -187,12 +188,12 @@ export class EvalPanel extends BasePanel {
|
|
|
187
188
|
}
|
|
188
189
|
return true;
|
|
189
190
|
}
|
|
190
|
-
if (key === '
|
|
191
|
+
if (key === 'pageup') {
|
|
191
192
|
this._scrollOffset = Math.max(0, this._scrollOffset - 5);
|
|
192
193
|
this.markDirty();
|
|
193
194
|
return true;
|
|
194
195
|
}
|
|
195
|
-
if (key === '
|
|
196
|
+
if (key === 'pagedown') {
|
|
196
197
|
this._scrollOffset += 5;
|
|
197
198
|
this.markDirty();
|
|
198
199
|
return true;
|
package/src/panels/index.ts
CHANGED
|
@@ -40,7 +40,7 @@ export { SecurityPanel } from './security-panel.ts';
|
|
|
40
40
|
export { MarketplacePanel } from './marketplace-panel.ts';
|
|
41
41
|
export { SandboxPanel } from './sandbox-panel.ts';
|
|
42
42
|
export { ApprovalPanel } from './approval-panel.ts';
|
|
43
|
-
export {
|
|
43
|
+
export { KnowledgeGraphPanel } from './knowledge-graph-panel.ts';
|
|
44
44
|
export { SystemMessagesPanel } from './system-messages-panel.ts';
|
|
45
45
|
export { PanelListPanel } from './panel-list-panel.ts';
|
|
46
46
|
export type { SystemMessageEntry, SystemMessagePriority } from './system-messages-panel.ts';
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* KnowledgeGraphPanel — SDK knowledge graph front-door.
|
|
3
|
+
*
|
|
4
|
+
* TASK-040: The 'knowledge' panel id is repointed here (the SDK graph), fixing
|
|
5
|
+
* the naming inversion where the former panel named 'Knowledge' was actually
|
|
6
|
+
* rendering memory records.
|
|
7
|
+
*
|
|
8
|
+
* This panel is a thin information surface that explains the graph's capabilities
|
|
9
|
+
* and routes the user to the /knowledge command suite for ingest/RAG operations.
|
|
10
|
+
* The full graph UI is command-driven (/knowledge ask, ingest-url, list, search…).
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { Line } from '../types/grid.ts';
|
|
14
|
+
import { BasePanel } from './base-panel.ts';
|
|
15
|
+
import {
|
|
16
|
+
buildBodyText,
|
|
17
|
+
buildGuidanceLine,
|
|
18
|
+
buildPanelLine,
|
|
19
|
+
buildPanelWorkspace,
|
|
20
|
+
DEFAULT_PANEL_PALETTE,
|
|
21
|
+
} from './polish.ts';
|
|
22
|
+
|
|
23
|
+
const C = {
|
|
24
|
+
...DEFAULT_PANEL_PALETTE,
|
|
25
|
+
header: '#94a3b8',
|
|
26
|
+
headerBg: '#1e293b',
|
|
27
|
+
} as const;
|
|
28
|
+
|
|
29
|
+
export class KnowledgeGraphPanel extends BasePanel {
|
|
30
|
+
constructor() {
|
|
31
|
+
super('knowledge', 'Knowledge', 'K', 'agent');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
handleInput(_key: string): boolean {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
render(width: number, height: number): Line[] {
|
|
39
|
+
const sections = [
|
|
40
|
+
{
|
|
41
|
+
title: 'SDK Knowledge Graph',
|
|
42
|
+
lines: [
|
|
43
|
+
...buildBodyText(
|
|
44
|
+
width,
|
|
45
|
+
'The knowledge graph stores ingested URLs, bookmarks, and structured facts as nodes and edges. ' +
|
|
46
|
+
'Use /knowledge commands to ingest sources, search the graph, and build task-context packets.',
|
|
47
|
+
C,
|
|
48
|
+
C.value,
|
|
49
|
+
),
|
|
50
|
+
buildPanelLine(width, [['', C.dim]]),
|
|
51
|
+
buildGuidanceLine(width, '/knowledge status', 'check the graph status and source counts', C),
|
|
52
|
+
buildGuidanceLine(width, '/knowledge ask <query>', 'ask a question against the ingested knowledge', C),
|
|
53
|
+
buildGuidanceLine(width, '/knowledge ingest-url <url>', 'ingest a URL as a knowledge source', C),
|
|
54
|
+
buildGuidanceLine(width, '/knowledge list', 'list ingested sources or graph nodes', C),
|
|
55
|
+
buildGuidanceLine(width, '/knowledge search <query>', 'search the graph for nodes and sources', C),
|
|
56
|
+
buildGuidanceLine(width, '/knowledge packet <task>', 'build a compact prompt packet for a task', C),
|
|
57
|
+
],
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
title: 'Project Memory',
|
|
61
|
+
lines: [
|
|
62
|
+
...buildBodyText(
|
|
63
|
+
width,
|
|
64
|
+
'For durable decisions, risks, runbooks, incidents, and architecture records, use the Memory panel ' +
|
|
65
|
+
'or the /recall command surface. Durable memory is a sub-namespace of the knowledge graph.',
|
|
66
|
+
C,
|
|
67
|
+
C.dim,
|
|
68
|
+
),
|
|
69
|
+
buildPanelLine(width, [['', C.dim]]),
|
|
70
|
+
buildGuidanceLine(width, '/recall add <class> <summary>', 'capture a new memory record', C),
|
|
71
|
+
buildGuidanceLine(width, '/recall queue', 'show the operator review queue', C),
|
|
72
|
+
buildGuidanceLine(width, '/project-memory (pmem)', 'project-memory alias for /recall front-door', C),
|
|
73
|
+
],
|
|
74
|
+
},
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
return buildPanelWorkspace(width, height, {
|
|
78
|
+
title: 'Knowledge Graph',
|
|
79
|
+
intro: 'Ingested sources, graph nodes, and the durable memory bridge.',
|
|
80
|
+
sections,
|
|
81
|
+
palette: C,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -11,8 +11,11 @@ import {
|
|
|
11
11
|
DEFAULT_PANEL_PALETTE,
|
|
12
12
|
type PanelPalette,
|
|
13
13
|
} from './polish.ts';
|
|
14
|
-
import type { LocalAuthSnapshot } from '@pellux/goodvibes-sdk/platform/security';
|
|
14
|
+
import type { LocalAuthSnapshot, UserAuthManager } from '@pellux/goodvibes-sdk/platform/security';
|
|
15
15
|
import type { LocalAuthInspectionQuery } from '../runtime/ui-service-queries.ts';
|
|
16
|
+
import type { KeyName } from './types.ts';
|
|
17
|
+
import { isTextBackspace } from '../input/delete-key-policy.ts';
|
|
18
|
+
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
16
19
|
|
|
17
20
|
const C = {
|
|
18
21
|
...DEFAULT_PANEL_PALETTE,
|
|
@@ -28,8 +31,19 @@ function formatRoles(roles: readonly string[]): string {
|
|
|
28
31
|
|
|
29
32
|
type LocalAuthUser = LocalAuthSnapshot['users'][number];
|
|
30
33
|
|
|
34
|
+
/** Action kind for the masked password entry mode. */
|
|
35
|
+
export type MaskedEntryKind = 'add-user' | 'rotate-password';
|
|
36
|
+
|
|
37
|
+
interface MaskedEntryState {
|
|
38
|
+
readonly kind: MaskedEntryKind;
|
|
39
|
+
readonly username: string;
|
|
40
|
+
readonly auth: UserAuthManager;
|
|
41
|
+
buffer: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
31
44
|
export class LocalAuthPanel extends ScrollableListPanel<LocalAuthUser> {
|
|
32
45
|
private readonly authManager: LocalAuthInspectionQuery;
|
|
46
|
+
private maskedState: MaskedEntryState | null = null;
|
|
33
47
|
|
|
34
48
|
public constructor(authManager: LocalAuthInspectionQuery) {
|
|
35
49
|
super('local-auth', 'Local Auth', 'U', 'monitoring');
|
|
@@ -37,6 +51,81 @@ export class LocalAuthPanel extends ScrollableListPanel<LocalAuthUser> {
|
|
|
37
51
|
this.authManager = authManager;
|
|
38
52
|
}
|
|
39
53
|
|
|
54
|
+
/**
|
|
55
|
+
* Activate masked password-entry mode for the given operation.
|
|
56
|
+
* The panel's handleInput will capture keystrokes into a private buffer;
|
|
57
|
+
* no plaintext is ever recorded in input history, transcript, or logs.
|
|
58
|
+
*/
|
|
59
|
+
public openMaskedEntry(kind: MaskedEntryKind, username: string, auth: UserAuthManager): void {
|
|
60
|
+
this.maskedState = { kind, username, auth, buffer: '' };
|
|
61
|
+
this.invalidate();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Returns true when the panel is in masked-entry mode. */
|
|
65
|
+
public get isMaskedEntryActive(): boolean {
|
|
66
|
+
return this.maskedState !== null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
public override handleInput(key: KeyName): boolean {
|
|
70
|
+
if (this.maskedState === null) {
|
|
71
|
+
// Delegate scroll/selection to ScrollableListPanel when not in masked mode.
|
|
72
|
+
return super.handleInput?.(key) ?? false;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Masked entry is active — capture all keystrokes.
|
|
76
|
+
const state = this.maskedState;
|
|
77
|
+
|
|
78
|
+
if (key === 'escape') {
|
|
79
|
+
// Cancel: discard buffer, exit masked mode without persisting.
|
|
80
|
+
this.maskedState = null;
|
|
81
|
+
this.invalidate();
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (key === 'enter' || key === 'return') {
|
|
86
|
+
// Submit: call the auth API if a non-empty password was entered.
|
|
87
|
+
if (state.buffer.length === 0) {
|
|
88
|
+
return true; // no-op — require at least one character
|
|
89
|
+
}
|
|
90
|
+
const password = state.buffer;
|
|
91
|
+
const { kind, username, auth } = state;
|
|
92
|
+
// Clear the mutable state *before* the auth call so the secret
|
|
93
|
+
// never lingers in the buffer after an exception.
|
|
94
|
+
this.maskedState = null;
|
|
95
|
+
try {
|
|
96
|
+
if (kind === 'add-user') {
|
|
97
|
+
auth.addUser(username, password, ['admin']);
|
|
98
|
+
} else {
|
|
99
|
+
auth.rotatePassword(username, password);
|
|
100
|
+
}
|
|
101
|
+
} catch (_error) {
|
|
102
|
+
// Surface errors via the panel's error facility rather than logging.
|
|
103
|
+
this.setError(summarizeError(_error));
|
|
104
|
+
}
|
|
105
|
+
this.invalidate();
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (isTextBackspace(key)) {
|
|
110
|
+
// Remove last character (Ink 6.8.0: raw-stdin raw 'backspace' only).
|
|
111
|
+
if (state.buffer.length > 0) {
|
|
112
|
+
state.buffer = state.buffer.slice(0, -1);
|
|
113
|
+
this.invalidate();
|
|
114
|
+
}
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Single printable character: append to buffer.
|
|
119
|
+
if (key.length === 1) {
|
|
120
|
+
state.buffer += key;
|
|
121
|
+
this.invalidate();
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// All other named keys are consumed (ignored) while masked mode is active.
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
|
|
40
129
|
protected override getPalette(): PanelPalette {
|
|
41
130
|
return C;
|
|
42
131
|
}
|
|
@@ -57,6 +146,12 @@ export class LocalAuthPanel extends ScrollableListPanel<LocalAuthUser> {
|
|
|
57
146
|
}
|
|
58
147
|
|
|
59
148
|
public render(width: number, height: number): Line[] {
|
|
149
|
+
// When masked entry is active, render a dedicated prompt instead of the
|
|
150
|
+
// normal panel content. No plaintext password appears anywhere in output.
|
|
151
|
+
if (this.maskedState !== null) {
|
|
152
|
+
return this.renderMaskedPrompt(width, height);
|
|
153
|
+
}
|
|
154
|
+
|
|
60
155
|
const intro = 'Manage local daemon and HTTP-listener auth users, bootstrap state, and active sessions.';
|
|
61
156
|
const snapshot = this.authManager.inspect();
|
|
62
157
|
const users = this.getItems();
|
|
@@ -81,7 +176,7 @@ export class LocalAuthPanel extends ScrollableListPanel<LocalAuthUser> {
|
|
|
81
176
|
...(issueMessages.length > 0
|
|
82
177
|
? issueMessages.map((issue) => buildPanelLine(width, [[` issue: ${issue}`.slice(0, Math.max(0, width)), C.warn]]))
|
|
83
178
|
: [buildPanelLine(width, [[' local auth posture looks healthy.', C.good]])]),
|
|
84
|
-
buildGuidanceLine(width, '/auth local rotate-password <user>
|
|
179
|
+
buildGuidanceLine(width, '/auth local rotate-password <user>', 'open masked password entry for the selected user (no plaintext in history)', C),
|
|
85
180
|
], C),
|
|
86
181
|
];
|
|
87
182
|
|
|
@@ -104,7 +199,7 @@ export class LocalAuthPanel extends ScrollableListPanel<LocalAuthUser> {
|
|
|
104
199
|
footerLines.push(
|
|
105
200
|
...buildDetailBlock(width, 'Selected user', [
|
|
106
201
|
buildPanelLine(width, [[' username ', C.label], [selected.username, C.value], [' roles ', C.label], [formatRoles(selected.roles).slice(0, Math.max(0, width - 23)), C.info]]),
|
|
107
|
-
buildPanelLine(width, [[` next: /auth local rotate-password ${selected.username}
|
|
202
|
+
buildPanelLine(width, [[` next: /auth local rotate-password ${selected.username}`.slice(0, Math.max(0, width)), C.dim]]),
|
|
108
203
|
buildPanelLine(width, [[` next: /auth local delete-user ${selected.username}`.slice(0, Math.max(0, width)), C.dim]]),
|
|
109
204
|
], C),
|
|
110
205
|
);
|
|
@@ -119,7 +214,7 @@ export class LocalAuthPanel extends ScrollableListPanel<LocalAuthUser> {
|
|
|
119
214
|
])),
|
|
120
215
|
);
|
|
121
216
|
}
|
|
122
|
-
footerLines.push(buildPanelLine(width, [[' /auth local review /auth local add-user /auth local rotate-password
|
|
217
|
+
footerLines.push(buildPanelLine(width, [[' /auth local review /auth local add-user <user> /auth local rotate-password <user> (omit password for masked entry) ', C.dim]]));
|
|
123
218
|
|
|
124
219
|
return this.renderList(width, height, {
|
|
125
220
|
title: 'Local Auth Control Room',
|
|
@@ -127,4 +222,29 @@ export class LocalAuthPanel extends ScrollableListPanel<LocalAuthUser> {
|
|
|
127
222
|
footer: footerLines,
|
|
128
223
|
});
|
|
129
224
|
}
|
|
225
|
+
|
|
226
|
+
private renderMaskedPrompt(width: number, height: number): Line[] {
|
|
227
|
+
const state = this.maskedState!;
|
|
228
|
+
const actionLabel = state.kind === 'add-user' ? 'Add user' : 'Rotate password';
|
|
229
|
+
const dots = '•'.repeat(Math.min(32, state.buffer.length));
|
|
230
|
+
const cursor = '█'; // block cursor
|
|
231
|
+
const maskedDisplay = state.buffer.length > 0 ? `${dots}${cursor}` : cursor;
|
|
232
|
+
|
|
233
|
+
const promptLines: Line[] = [
|
|
234
|
+
buildPanelLine(width, [[` ${actionLabel}: ${state.username}`, C.value]]),
|
|
235
|
+
buildPanelLine(width, [['', C.label]]),
|
|
236
|
+
buildPanelLine(width, [[' Password ', C.label], [maskedDisplay.slice(0, Math.max(0, width - 12)), C.info]]),
|
|
237
|
+
buildPanelLine(width, [['', C.label]]),
|
|
238
|
+
buildPanelLine(width, [[' [Enter] Confirm [Esc] Cancel [Backspace] Delete char', C.dim]]),
|
|
239
|
+
];
|
|
240
|
+
|
|
241
|
+
const workspace = buildPanelWorkspace(width, height, {
|
|
242
|
+
title: 'Local Auth — Password Entry',
|
|
243
|
+
intro: `Type a password for ${state.username}. The value is never echoed in plaintext or stored in history.`,
|
|
244
|
+
sections: [{ lines: promptLines }],
|
|
245
|
+
palette: C,
|
|
246
|
+
});
|
|
247
|
+
while (workspace.length < height) workspace.push(createEmptyLine(width));
|
|
248
|
+
return workspace;
|
|
249
|
+
}
|
|
130
250
|
}
|