@pellux/goodvibes-agent 0.1.9 → 0.1.11
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 +41 -0
- package/README.md +1 -1
- package/docs/getting-started.md +1 -1
- package/docs/release-and-publishing.md +2 -2
- package/package.json +4 -1
- package/src/cli/agent-knowledge-command.ts +46 -20
- package/src/cli/help.ts +15 -2
- package/src/cli/management-commands.ts +3 -3
- package/src/cli/management.ts +7 -1
- package/src/cli/parser.ts +3 -0
- package/src/cli/service-posture.ts +6 -6
- package/src/cli/status.ts +9 -9
- package/src/cli/surface-command.ts +3 -3
- package/src/cli/types.ts +2 -0
- package/src/input/commands/cloudflare-runtime.ts +20 -5
- package/src/input/commands/confirmation.ts +24 -0
- package/src/input/commands/discovery-runtime.ts +16 -7
- package/src/input/commands/eval.ts +27 -14
- package/src/input/commands/experience-runtime.ts +66 -27
- package/src/input/commands/health-runtime.ts +1 -1
- package/src/input/commands/hooks-runtime.ts +79 -20
- package/src/input/commands/incident-runtime.ts +17 -6
- package/src/input/commands/integration-runtime.ts +93 -50
- package/src/input/commands/knowledge.ts +38 -12
- package/src/input/commands/local-auth-runtime.ts +36 -13
- package/src/input/commands/local-provider-runtime.ts +22 -11
- package/src/input/commands/local-runtime.ts +21 -11
- package/src/input/commands/local-setup.ts +35 -16
- package/src/input/commands/managed-runtime.ts +51 -20
- package/src/input/commands/marketplace-runtime.ts +31 -16
- package/src/input/commands/mcp-runtime.ts +65 -34
- package/src/input/commands/memory-product-runtime.ts +72 -35
- package/src/input/commands/memory.ts +9 -9
- package/src/input/commands/notify-runtime.ts +27 -8
- package/src/input/commands/operator-runtime.ts +85 -17
- package/src/input/commands/planning-runtime.ts +14 -2
- package/src/input/commands/platform-access-runtime.ts +88 -45
- package/src/input/commands/platform-services-runtime.ts +51 -25
- package/src/input/commands/product-runtime.ts +54 -27
- package/src/input/commands/profile-sync-runtime.ts +17 -6
- package/src/input/commands/recall-bundle.ts +38 -17
- package/src/input/commands/recall-query.ts +15 -4
- package/src/input/commands/recall-review.ts +9 -3
- package/src/input/commands/remote-runtime-setup.ts +45 -18
- package/src/input/commands/remote-runtime.ts +25 -9
- package/src/input/commands/replay-runtime.ts +9 -2
- package/src/input/commands/services-runtime.ts +21 -10
- package/src/input/commands/session-content.ts +53 -51
- package/src/input/commands/session-workflow.ts +10 -4
- package/src/input/commands/session.ts +1 -1
- package/src/input/commands/settings-sync-runtime.ts +40 -17
- package/src/input/commands/share-runtime.ts +12 -4
- package/src/input/commands/shell-core.ts +3 -3
- package/src/input/commands/subscription-runtime.ts +35 -20
- package/src/input/commands/teleport-runtime.ts +16 -5
- package/src/input/commands/work-plan-runtime.ts +23 -12
- package/src/input/handler-content-actions.ts +11 -62
- package/src/input/handler-interactions.ts +1 -1
- package/src/input/handler-onboarding-cloudflare.ts +48 -117
- package/src/input/handler.ts +1 -0
- package/src/input/keybindings.ts +1 -1
- package/src/input/mcp-workspace.ts +25 -49
- package/src/input/onboarding/onboarding-runtime-status.ts +8 -8
- package/src/input/onboarding/onboarding-wizard-apply.ts +13 -53
- package/src/input/onboarding/onboarding-wizard-cloudflare-step.ts +12 -12
- package/src/input/onboarding/onboarding-wizard-cloudflare.ts +2 -7
- package/src/input/onboarding/onboarding-wizard-constants.ts +7 -7
- package/src/input/onboarding/onboarding-wizard-external-surface-extra-specs.ts +4 -4
- package/src/input/onboarding/onboarding-wizard-steps.ts +13 -13
- package/src/input/profile-picker-modal.ts +13 -31
- package/src/input/session-picker-modal.ts +4 -30
- package/src/input/settings-modal-agent-policy.ts +18 -0
- package/src/input/settings-modal-subscriptions.ts +3 -3
- package/src/input/settings-modal-types.ts +17 -0
- package/src/input/settings-modal.ts +30 -29
- package/src/main.ts +3 -26
- package/src/panels/incident-review-panel.ts +1 -1
- package/src/panels/local-auth-panel.ts +4 -4
- package/src/panels/provider-account-snapshot.ts +1 -1
- package/src/panels/provider-health-domains.ts +2 -2
- package/src/panels/settings-sync-panel.ts +2 -2
- package/src/panels/subscription-panel.ts +7 -7
- package/src/renderer/block-actions.ts +1 -1
- package/src/renderer/help-overlay.ts +2 -2
- package/src/renderer/mcp-workspace.ts +12 -12
- package/src/renderer/process-modal.ts +17 -8
- package/src/renderer/profile-picker-modal.ts +3 -11
- package/src/renderer/session-picker-modal.ts +2 -10
- package/src/renderer/settings-modal.ts +12 -8
- package/src/renderer/ui-factory.ts +4 -32
- package/src/runtime/bootstrap-shell.ts +0 -13
- package/src/runtime/bootstrap.ts +0 -10
- package/src/runtime/onboarding/derivation.ts +6 -6
- package/src/verification/live-verifier.ts +148 -13
- package/src/version.ts +10 -3
- package/src/input/commands/quit-shared.ts +0 -162
- package/src/renderer/git-status.ts +0 -89
|
@@ -30,13 +30,13 @@ export const DEFAULT_CAPABILITIES: readonly OnboardingStep1CapabilityItem[] = [
|
|
|
30
30
|
id: 'network-access',
|
|
31
31
|
label: 'Let other devices use GoodVibes',
|
|
32
32
|
selected: false,
|
|
33
|
-
detail: '
|
|
33
|
+
detail: 'Review external daemon surfaces that are reachable from other devices on your LAN. Local authentication is required.',
|
|
34
34
|
},
|
|
35
35
|
{
|
|
36
36
|
id: 'webhook-events',
|
|
37
37
|
label: 'Receive webhooks or events from other tools',
|
|
38
38
|
selected: false,
|
|
39
|
-
detail: '
|
|
39
|
+
detail: 'Review the external HTTP listener required for incoming webhooks, callbacks, and automation events.',
|
|
40
40
|
},
|
|
41
41
|
{
|
|
42
42
|
id: 'external-integrations',
|
|
@@ -48,7 +48,7 @@ export const DEFAULT_CAPABILITIES: readonly OnboardingStep1CapabilityItem[] = [
|
|
|
48
48
|
id: 'cloudflare-batch',
|
|
49
49
|
label: 'Use Cloudflare for batch or remote daemon work',
|
|
50
50
|
selected: false,
|
|
51
|
-
detail: 'Optionally configure Cloudflare Workers and Queues for explicit or eligible background batch jobs.
|
|
51
|
+
detail: 'Optionally configure Cloudflare Workers and Queues for explicit or eligible background batch jobs. The external daemon still owns execution.',
|
|
52
52
|
},
|
|
53
53
|
];
|
|
54
54
|
|
|
@@ -60,13 +60,13 @@ export const REASONING_OPTIONS: readonly OnboardingWizardRadioOption[] = [
|
|
|
60
60
|
];
|
|
61
61
|
|
|
62
62
|
export const NETWORK_MODE_OPTIONS: readonly OnboardingWizardRadioOption[] = [
|
|
63
|
-
{ id: 'local-network-default', label: 'Local Network (Default)', hint: '
|
|
64
|
-
{ id: 'custom', label: 'Custom', hint: '
|
|
63
|
+
{ id: 'local-network-default', label: 'Local Network (Default)', hint: 'Review the default LAN-facing external daemon setup for browser, control-plane, and event features.' },
|
|
64
|
+
{ id: 'custom', label: 'Custom', hint: 'Review IP addresses and ports for each external daemon surface.' },
|
|
65
65
|
];
|
|
66
66
|
|
|
67
67
|
export const HITL_MODE_OPTIONS: readonly OnboardingWizardRadioOption[] = [
|
|
68
68
|
{ id: 'quiet', label: 'Quiet', hint: 'Only interrupt for important attention requests.' },
|
|
69
|
-
{ id: 'balanced', label: 'Balanced', hint: 'Show important activity without turning
|
|
69
|
+
{ id: 'balanced', label: 'Balanced', hint: 'Show important activity without turning Agent into a log stream.' },
|
|
70
70
|
{ id: 'operator', label: 'Operator', hint: 'Keep operational activity visible for hands-on supervision.' },
|
|
71
71
|
];
|
|
72
72
|
|
|
@@ -90,7 +90,7 @@ export const SECRET_POLICY_OPTIONS: readonly OnboardingWizardRadioOption[] = [
|
|
|
90
90
|
|
|
91
91
|
export const TELEGRAM_MODE_OPTIONS: readonly OnboardingWizardRadioOption[] = [
|
|
92
92
|
{ id: 'webhook', label: 'Webhook', hint: 'Receive Telegram updates through a webhook.' },
|
|
93
|
-
{ id: 'polling', label: 'Polling', hint: '
|
|
93
|
+
{ id: 'polling', label: 'Polling', hint: 'The external daemon polls Telegram for updates.' },
|
|
94
94
|
];
|
|
95
95
|
|
|
96
96
|
export const WHATSAPP_PROVIDER_OPTIONS: readonly OnboardingWizardRadioOption[] = [
|
|
@@ -105,8 +105,8 @@ export const HOME_ASSISTANT_SURFACE_SPEC: ExternalSurfaceSpec = {
|
|
|
105
105
|
kind: 'text',
|
|
106
106
|
label: 'Home Assistant device ID',
|
|
107
107
|
hint: 'Stable device identifier exposed by the GoodVibes daemon.',
|
|
108
|
-
placeholder: 'goodvibes-
|
|
109
|
-
defaultValue: (snapshot) => snapshot?.config.surfaces.homeassistant.deviceId ?? 'goodvibes-
|
|
108
|
+
placeholder: 'goodvibes-agent',
|
|
109
|
+
defaultValue: (snapshot) => snapshot?.config.surfaces.homeassistant.deviceId ?? 'goodvibes-agent',
|
|
110
110
|
},
|
|
111
111
|
{
|
|
112
112
|
id: 'external-services.homeassistant.device-name',
|
|
@@ -114,8 +114,8 @@ export const HOME_ASSISTANT_SURFACE_SPEC: ExternalSurfaceSpec = {
|
|
|
114
114
|
kind: 'text',
|
|
115
115
|
label: 'Home Assistant device name',
|
|
116
116
|
hint: 'Display name for the GoodVibes daemon device in Home Assistant.',
|
|
117
|
-
placeholder: 'GoodVibes
|
|
118
|
-
defaultValue: (snapshot) => snapshot?.config.surfaces.homeassistant.deviceName ?? 'GoodVibes
|
|
117
|
+
placeholder: 'GoodVibes Agent',
|
|
118
|
+
defaultValue: (snapshot) => snapshot?.config.surfaces.homeassistant.deviceName ?? 'GoodVibes Agent',
|
|
119
119
|
},
|
|
120
120
|
{
|
|
121
121
|
id: 'external-services.homeassistant.event-type',
|
|
@@ -436,7 +436,7 @@ function buildExternalSurfaceStep(
|
|
|
436
436
|
id: `external-surface:${surface.id}` as OnboardingWizardExternalSurfaceStepId,
|
|
437
437
|
title,
|
|
438
438
|
shortLabel: surface.label.replace(/ surface$/i, ''),
|
|
439
|
-
description: `Configure ${surface.label}. Settings are saved either way; Agent does not start or own the
|
|
439
|
+
description: `Configure ${surface.label}. Settings are saved either way; Agent does not start or own the external daemon service.`,
|
|
440
440
|
summaryTitle: `${surface.label} setup`,
|
|
441
441
|
summaryLines: [
|
|
442
442
|
`External activation requested: ${autoStartValue === 'yes' ? 'yes' : 'no'}`,
|
|
@@ -540,8 +540,8 @@ export function buildNetworkStep(controller: OnboardingWizardController): Onboar
|
|
|
540
540
|
const sharedIpField: OnboardingWizardChecklistFieldDefinition = {
|
|
541
541
|
kind: 'checklist',
|
|
542
542
|
id: 'network.shared-ip',
|
|
543
|
-
label: 'Use the same IP address for all
|
|
544
|
-
hint: 'When included, browser,
|
|
543
|
+
label: 'Use the same IP address for all external daemon surfaces',
|
|
544
|
+
hint: 'When included, browser, external daemon control plane, and webhook listener network bindings share one IP address in the daemon host configuration.',
|
|
545
545
|
defaultValue: controller.getSharedIpDefault(networkEnabled),
|
|
546
546
|
};
|
|
547
547
|
const sharedIp = controller.getBooleanFieldValue(sharedIpField.id, sharedIpField.defaultValue);
|
|
@@ -561,8 +561,8 @@ export function buildNetworkStep(controller: OnboardingWizardController): Onboar
|
|
|
561
561
|
fields.push({
|
|
562
562
|
kind: 'text',
|
|
563
563
|
id: 'network.service-port',
|
|
564
|
-
label: '
|
|
565
|
-
hint: 'Port
|
|
564
|
+
label: 'External daemon control-plane port',
|
|
565
|
+
hint: 'Port exposed by the external daemon control plane.',
|
|
566
566
|
placeholder: '3421',
|
|
567
567
|
defaultValue: String(bindSettings?.controlPlane.port ?? 3421),
|
|
568
568
|
});
|
|
@@ -570,8 +570,8 @@ export function buildNetworkStep(controller: OnboardingWizardController): Onboar
|
|
|
570
570
|
fields.push({
|
|
571
571
|
kind: 'text',
|
|
572
572
|
id: 'network.service-ip',
|
|
573
|
-
label: '
|
|
574
|
-
hint: 'IP address
|
|
573
|
+
label: 'External daemon control-plane IP address',
|
|
574
|
+
hint: 'IP address exposed by the external daemon control plane.',
|
|
575
575
|
placeholder: '0.0.0.0',
|
|
576
576
|
defaultValue: normalizeText(bindSettings?.controlPlane.host) || '0.0.0.0',
|
|
577
577
|
});
|
|
@@ -625,7 +625,7 @@ export function buildNetworkStep(controller: OnboardingWizardController): Onboar
|
|
|
625
625
|
id: 'network',
|
|
626
626
|
title: 'Network setup',
|
|
627
627
|
shortLabel: 'Network',
|
|
628
|
-
description: '
|
|
628
|
+
description: 'Review LAN defaults or IP addresses and ports for the external daemon browser, control-plane, and listener surfaces. Agent does not apply daemon bind changes.',
|
|
629
629
|
summaryTitle: 'Bind posture',
|
|
630
630
|
summaryLines: [
|
|
631
631
|
`Mode: ${custom ? 'custom' : 'local network default'}`,
|
|
@@ -653,7 +653,7 @@ export function buildAccountsStep(controller: OnboardingWizardController): Onboa
|
|
|
653
653
|
id: 'accounts.admin-username',
|
|
654
654
|
label: 'Local auth admin username',
|
|
655
655
|
hint: needsAuthBootstrap
|
|
656
|
-
? 'Required before
|
|
656
|
+
? 'Required before the external daemon exposes browser, control-plane, or listener surfaces.'
|
|
657
657
|
: 'Optional. Enter an existing admin username to rotate its password, or a new username to create another admin.',
|
|
658
658
|
placeholder: defaultAdminUsername,
|
|
659
659
|
defaultValue: defaultAdminUsername,
|
|
@@ -666,7 +666,7 @@ export function buildAccountsStep(controller: OnboardingWizardController): Onboa
|
|
|
666
666
|
hint: needsAuthBootstrap
|
|
667
667
|
? controller.hasBootstrapCredentialPresent()
|
|
668
668
|
? 'Creates or updates the named local admin, removes the bootstrap credential file, and retires the bootstrap admin when it is a different user.'
|
|
669
|
-
: 'Creates the first local admin user and an initial session before
|
|
669
|
+
: 'Creates the first local admin user and an initial session before external daemon network settings are used by the daemon owner.'
|
|
670
670
|
: 'Optional. Leave blank to keep existing local auth unchanged; enter a password to create or rotate the named admin user.',
|
|
671
671
|
placeholder: needsAuthBootstrap ? 'password required' : 'leave blank to keep unchanged',
|
|
672
672
|
defaultValue: '',
|
|
@@ -724,7 +724,7 @@ export function buildAccountsStep(controller: OnboardingWizardController): Onboa
|
|
|
724
724
|
title: 'Subscriptions and auth review',
|
|
725
725
|
shortLabel: 'Accounts',
|
|
726
726
|
description: needsAuthBootstrap
|
|
727
|
-
? 'Create
|
|
727
|
+
? 'Create local auth state before external daemon LAN, browser, service, or listener settings are applied by the daemon owner.'
|
|
728
728
|
: 'Review existing subscription and local auth state. Existing local auth is kept unless you change it elsewhere.',
|
|
729
729
|
summaryTitle: 'Stored account state',
|
|
730
730
|
summaryLines: [
|
|
@@ -732,8 +732,8 @@ export function buildAccountsStep(controller: OnboardingWizardController): Onboa
|
|
|
732
732
|
`Auth: ${auth?.userCount ?? 0} users / ${auth?.sessionCount ?? 0} sessions`,
|
|
733
733
|
needsAuthBootstrap
|
|
734
734
|
? controller.hasBootstrapCredentialPresent()
|
|
735
|
-
? 'Bootstrap credentials will be replaced before network settings are
|
|
736
|
-
: 'Local admin will be created before network settings are
|
|
735
|
+
? 'Bootstrap credentials will be replaced before external network settings are used'
|
|
736
|
+
: 'Local admin will be created before external network settings are used'
|
|
737
737
|
: controller.hasLocalAuthUser() ? 'Existing local auth will be kept' : 'Local auth is not required for this setup',
|
|
738
738
|
],
|
|
739
739
|
fields,
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* ProfilePickerModal — state management for the /profiles picker modal.
|
|
3
3
|
*
|
|
4
4
|
* Lists profiles from ProfileManager.list(), tracks selected index,
|
|
5
|
-
* and handles load
|
|
5
|
+
* and handles load actions.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import type { ProfileInfo, ProfileData, ProfileManager } from '@pellux/goodvibes-sdk/platform/profiles';
|
|
@@ -141,39 +141,12 @@ export class ProfilePickerModal {
|
|
|
141
141
|
}
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
-
/**
|
|
145
|
-
* Delete the selected profile from disk.
|
|
146
|
-
* Refreshes the list after deletion.
|
|
147
|
-
*/
|
|
148
144
|
deleteSelected(): boolean {
|
|
149
145
|
const profile = this.getSelected();
|
|
150
146
|
if (!profile) return false;
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
return false;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
try {
|
|
158
|
-
const deleted = this.profileManager.delete(profile.name);
|
|
159
|
-
if (!deleted) {
|
|
160
|
-
this.statusMessage = `Profile not found: ${profile.name}`;
|
|
161
|
-
this.deleteConfirmationTarget = null;
|
|
162
|
-
return false;
|
|
163
|
-
}
|
|
164
|
-
this.profiles = this.profileManager.list();
|
|
165
|
-
if (this.selectedIndex >= this.profiles.length) {
|
|
166
|
-
this.selectedIndex = Math.max(0, this.profiles.length - 1);
|
|
167
|
-
}
|
|
168
|
-
this._clampScroll();
|
|
169
|
-
this.deleteConfirmationTarget = null;
|
|
170
|
-
this.statusMessage = `Deleted: ${profile.name}`;
|
|
171
|
-
return true;
|
|
172
|
-
} catch (e) {
|
|
173
|
-
this.deleteConfirmationTarget = null;
|
|
174
|
-
this.statusMessage = `Error: ${summarizeError(e)}`;
|
|
175
|
-
return false;
|
|
176
|
-
}
|
|
147
|
+
this.deleteConfirmationTarget = null;
|
|
148
|
+
this.statusMessage = `Deletion requires an explicit command: /profiles delete ${profile.name} --yes`;
|
|
149
|
+
return false;
|
|
177
150
|
}
|
|
178
151
|
|
|
179
152
|
/**
|
|
@@ -184,7 +157,16 @@ export class ProfilePickerModal {
|
|
|
184
157
|
this.statusMessage = 'Profile name cannot be empty';
|
|
185
158
|
return false;
|
|
186
159
|
}
|
|
160
|
+
void configManager;
|
|
161
|
+
this.statusMessage = `Saving requires an explicit command: /profiles save ${name} --yes`;
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
187
164
|
|
|
165
|
+
public saveCurrentAsConfirmed(name: string, configManager: ConfigManager): boolean {
|
|
166
|
+
if (!name || !name.trim()) {
|
|
167
|
+
this.statusMessage = 'Profile name cannot be empty';
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
188
170
|
try {
|
|
189
171
|
const all = configManager.getAll();
|
|
190
172
|
const data: ProfileData = {
|
|
@@ -2,10 +2,9 @@
|
|
|
2
2
|
* SessionPickerModal — state management for the /sessions picker modal.
|
|
3
3
|
*
|
|
4
4
|
* Lists sessions from SessionManager.list(), tracks selected index,
|
|
5
|
-
* and handles load
|
|
5
|
+
* and handles load actions.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { unlinkSync } from 'node:fs';
|
|
9
8
|
import type { SessionInfo, SessionManager } from '@pellux/goodvibes-sdk/platform/sessions';
|
|
10
9
|
import type { ConversationManager } from '../core/conversation';
|
|
11
10
|
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
@@ -90,37 +89,12 @@ export class SessionPickerModal {
|
|
|
90
89
|
}
|
|
91
90
|
}
|
|
92
91
|
|
|
93
|
-
/**
|
|
94
|
-
* Delete the currently selected session from disk.
|
|
95
|
-
* Refreshes the list after deletion.
|
|
96
|
-
*/
|
|
97
92
|
deleteSelected(): boolean {
|
|
98
93
|
const session = this.getSelected();
|
|
99
94
|
if (!session) return false;
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
return false;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
try {
|
|
107
|
-
// Delete directly via filePath so it works with any session directory
|
|
108
|
-
unlinkSync(session.filePath);
|
|
109
|
-
// Reload list from the global session manager (removes the deleted entry)
|
|
110
|
-
this.sessions = this.sessionManager.list();
|
|
111
|
-
// Adjust selection
|
|
112
|
-
if (this.selectedIndex >= this.sessions.length) {
|
|
113
|
-
this.selectedIndex = Math.max(0, this.sessions.length - 1);
|
|
114
|
-
}
|
|
115
|
-
this._clampScroll();
|
|
116
|
-
this.deleteConfirmationTarget = null;
|
|
117
|
-
this.statusMessage = `Deleted: ${session.name}`;
|
|
118
|
-
return true;
|
|
119
|
-
} catch (e) {
|
|
120
|
-
this.deleteConfirmationTarget = null;
|
|
121
|
-
this.statusMessage = `Error: ${summarizeError(e)}`;
|
|
122
|
-
return false;
|
|
123
|
-
}
|
|
95
|
+
this.deleteConfirmationTarget = null;
|
|
96
|
+
this.statusMessage = `Deletion requires an explicit command: /session delete ${session.name} --yes`;
|
|
97
|
+
return false;
|
|
124
98
|
}
|
|
125
99
|
|
|
126
100
|
private _clampScroll(): void {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export const AGENT_EXTERNAL_DAEMON_SETTING_LOCK_REASON = 'GoodVibes Agent connects to an external daemon. Change this from GoodVibes TUI or the daemon host; Agent settings are read-only for daemon lifecycle and bind posture.';
|
|
2
|
+
|
|
3
|
+
const EXTERNAL_DAEMON_SETTING_PREFIXES = [
|
|
4
|
+
'service.',
|
|
5
|
+
'controlPlane.',
|
|
6
|
+
'httpListener.',
|
|
7
|
+
'web.',
|
|
8
|
+
] as const;
|
|
9
|
+
|
|
10
|
+
const EXTERNAL_DAEMON_SETTING_KEYS = new Set<string>([
|
|
11
|
+
'danger.daemon',
|
|
12
|
+
'danger.httpListener',
|
|
13
|
+
]);
|
|
14
|
+
|
|
15
|
+
export function isExternalDaemonOwnedSettingKey(key: string): boolean {
|
|
16
|
+
return EXTERNAL_DAEMON_SETTING_KEYS.has(key)
|
|
17
|
+
|| EXTERNAL_DAEMON_SETTING_PREFIXES.some((prefix) => key.startsWith(prefix));
|
|
18
|
+
}
|
|
@@ -23,7 +23,7 @@ export function buildSubscriptionEntries(
|
|
|
23
23
|
activeRoute: 'unconfigured',
|
|
24
24
|
authFreshness: 'unconfigured',
|
|
25
25
|
routeReason: 'Built-in subscription adapter is available, but no active subscription session is stored yet.',
|
|
26
|
-
nextActions: [`Use /subscription login ${provider} start to begin browser sign-in.`],
|
|
26
|
+
nextActions: [`Use /subscription login ${provider} start --yes to begin browser sign-in.`],
|
|
27
27
|
});
|
|
28
28
|
}
|
|
29
29
|
|
|
@@ -38,7 +38,7 @@ export function buildSubscriptionEntries(
|
|
|
38
38
|
activeRoute: providers.get(provider)?.activeRoute ?? 'unconfigured',
|
|
39
39
|
authFreshness: providers.get(provider)?.authFreshness ?? 'unconfigured',
|
|
40
40
|
routeReason: providers.get(provider)?.routeReason ?? 'OAuth metadata is configured for this provider.',
|
|
41
|
-
nextActions: providers.get(provider)?.nextActions ?? [`Use /subscription login ${provider} start to begin browser sign-in.`],
|
|
41
|
+
nextActions: providers.get(provider)?.nextActions ?? [`Use /subscription login ${provider} start --yes to begin browser sign-in.`],
|
|
42
42
|
});
|
|
43
43
|
}
|
|
44
44
|
|
|
@@ -51,7 +51,7 @@ export function buildSubscriptionEntries(
|
|
|
51
51
|
activeRoute: 'unconfigured',
|
|
52
52
|
authFreshness: 'pending',
|
|
53
53
|
routeReason: 'OAuth login is pending completion for this provider.',
|
|
54
|
-
nextActions: [`Finish /subscription login ${pending.provider} finish <code> to activate this session.`],
|
|
54
|
+
nextActions: [`Finish /subscription login ${pending.provider} finish <code> --yes to activate this session.`],
|
|
55
55
|
});
|
|
56
56
|
}
|
|
57
57
|
|
|
@@ -1,7 +1,24 @@
|
|
|
1
1
|
import type { ConfigSetting } from '@pellux/goodvibes-sdk/platform/config';
|
|
2
|
+
import type { ConfigKey } from '@pellux/goodvibes-sdk/platform/config';
|
|
2
3
|
import type { ProviderAuthFreshness, ProviderAuthRoute } from '@/runtime/index.ts';
|
|
3
4
|
import type { FeatureFlag, FlagState } from '@/runtime/index.ts';
|
|
4
5
|
|
|
6
|
+
export interface SettingsModalChange {
|
|
7
|
+
readonly key: ConfigKey;
|
|
8
|
+
readonly previousValue: unknown;
|
|
9
|
+
readonly value: unknown;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface SettingsModalChangeResult {
|
|
13
|
+
readonly message?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type SettingsModalChangeHandler = (change: SettingsModalChange) => SettingsModalChangeResult | void;
|
|
17
|
+
|
|
18
|
+
export interface SettingsModalOpenOptions {
|
|
19
|
+
readonly onSettingApplied?: SettingsModalChangeHandler;
|
|
20
|
+
}
|
|
21
|
+
|
|
5
22
|
export type SettingsCategory =
|
|
6
23
|
| 'display'
|
|
7
24
|
| 'ui'
|
|
@@ -1,14 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SettingsModal — state management for the /settings and /config fullscreen workspace.
|
|
3
|
-
*
|
|
4
|
-
* Loads CONFIG_SCHEMA, groups settings by category, and tracks UI state:
|
|
5
|
-
* - Active category (Tab to cycle)
|
|
6
|
-
* - Selected setting index within category (↑↓)
|
|
7
|
-
* - Editing mode for inline string/number input
|
|
8
|
-
* - Feature flags tab with runtime toggle support
|
|
9
|
-
*
|
|
10
|
-
* Saves changes via configManager.set(key, value) or featureFlagManager methods.
|
|
11
|
-
*/
|
|
1
|
+
/** SettingsModal state for the /settings and /config fullscreen workspace. */
|
|
12
2
|
|
|
13
3
|
import { CONFIG_SCHEMA, type ConfigKey, type PersistedFlagState } from '@pellux/goodvibes-sdk/platform/config';
|
|
14
4
|
import type { ModelPickerTarget } from './model-picker.ts';
|
|
@@ -40,24 +30,11 @@ import {
|
|
|
40
30
|
type SettingEntry,
|
|
41
31
|
type SettingsCategory,
|
|
42
32
|
type SettingsFocusPane,
|
|
33
|
+
type SettingsModalChangeHandler,
|
|
34
|
+
type SettingsModalOpenOptions,
|
|
43
35
|
type SubscriptionEntry,
|
|
44
36
|
} from './settings-modal-types.ts';
|
|
45
|
-
|
|
46
|
-
export interface SettingsModalChange {
|
|
47
|
-
readonly key: ConfigKey;
|
|
48
|
-
readonly previousValue: unknown;
|
|
49
|
-
readonly value: unknown;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export interface SettingsModalChangeResult {
|
|
53
|
-
readonly message?: string;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export type SettingsModalChangeHandler = (change: SettingsModalChange) => SettingsModalChangeResult | void;
|
|
57
|
-
|
|
58
|
-
export interface SettingsModalOpenOptions {
|
|
59
|
-
readonly onSettingApplied?: SettingsModalChangeHandler;
|
|
60
|
-
}
|
|
37
|
+
import { AGENT_EXTERNAL_DAEMON_SETTING_LOCK_REASON, isExternalDaemonOwnedSettingKey } from './settings-modal-agent-policy.ts';
|
|
61
38
|
|
|
62
39
|
export {
|
|
63
40
|
SETTINGS_CATEGORIES,
|
|
@@ -67,8 +44,13 @@ export {
|
|
|
67
44
|
type SettingEntry,
|
|
68
45
|
type SettingsCategory,
|
|
69
46
|
type SettingsFocusPane,
|
|
47
|
+
type SettingsModalChange,
|
|
48
|
+
type SettingsModalChangeHandler,
|
|
49
|
+
type SettingsModalChangeResult,
|
|
50
|
+
type SettingsModalOpenOptions,
|
|
70
51
|
type SubscriptionEntry,
|
|
71
52
|
} from './settings-modal-types.ts';
|
|
53
|
+
export { AGENT_EXTERNAL_DAEMON_SETTING_LOCK_REASON, isExternalDaemonOwnedSettingKey } from './settings-modal-agent-policy.ts';
|
|
72
54
|
|
|
73
55
|
// ---------------------------------------------------------------------------
|
|
74
56
|
// SettingsModal
|
|
@@ -369,6 +351,7 @@ export class SettingsModal {
|
|
|
369
351
|
|
|
370
352
|
const entry = this.getSelected();
|
|
371
353
|
if (!entry || !this.configManager) return;
|
|
354
|
+
if (this._blockExternalDaemonOwnedSetting(entry)) return;
|
|
372
355
|
|
|
373
356
|
const { setting } = entry;
|
|
374
357
|
|
|
@@ -433,6 +416,7 @@ export class SettingsModal {
|
|
|
433
416
|
|
|
434
417
|
const entry = this.getSelected();
|
|
435
418
|
if (!entry || !this.configManager) return;
|
|
419
|
+
if (this._blockExternalDaemonOwnedSetting(entry)) return;
|
|
436
420
|
const { setting } = entry;
|
|
437
421
|
|
|
438
422
|
if (setting.type === 'boolean') {
|
|
@@ -554,6 +538,11 @@ export class SettingsModal {
|
|
|
554
538
|
|
|
555
539
|
const entry = this.getSelected();
|
|
556
540
|
if (!entry || !this.configManager) return false;
|
|
541
|
+
if (this._blockExternalDaemonOwnedSetting(entry)) {
|
|
542
|
+
this.editingMode = false;
|
|
543
|
+
this.editBuffer = '';
|
|
544
|
+
return false;
|
|
545
|
+
}
|
|
557
546
|
|
|
558
547
|
const { setting } = entry;
|
|
559
548
|
let parsed: unknown = this.editBuffer;
|
|
@@ -600,6 +589,7 @@ export class SettingsModal {
|
|
|
600
589
|
if (this.editingMode || !this.configManager) return null;
|
|
601
590
|
const entry = this.getSelected();
|
|
602
591
|
if (!entry) return null;
|
|
592
|
+
if (this._blockExternalDaemonOwnedSetting(entry)) return null;
|
|
603
593
|
const key = entry.setting.key as ConfigKey;
|
|
604
594
|
this._setValue(key, entry.setting.default);
|
|
605
595
|
if (isSecretConfigKey(key) && this.secretsManager) {
|
|
@@ -635,15 +625,16 @@ export class SettingsModal {
|
|
|
635
625
|
const cat = rawCat as SettingsCategory;
|
|
636
626
|
const currentValue = configManager.get(setting.key as ConfigKey);
|
|
637
627
|
const resolved = getResolvedSettingLookup(configManager, setting.key as ConfigKey)?.entry;
|
|
628
|
+
const daemonOwned = isExternalDaemonOwnedSettingKey(setting.key);
|
|
638
629
|
const entry: SettingEntry = {
|
|
639
630
|
setting,
|
|
640
631
|
currentValue,
|
|
641
632
|
isDefault: currentValue === setting.default,
|
|
642
633
|
effectiveSource: resolved?.effectiveSource,
|
|
643
|
-
locked: resolved?.locked,
|
|
634
|
+
locked: daemonOwned || resolved?.locked,
|
|
644
635
|
conflict: resolved?.conflict,
|
|
645
636
|
sourceLabel: resolved?.sourceLabel,
|
|
646
|
-
lockReason: resolved?.lockReason,
|
|
637
|
+
lockReason: daemonOwned ? AGENT_EXTERNAL_DAEMON_SETTING_LOCK_REASON : resolved?.lockReason,
|
|
647
638
|
};
|
|
648
639
|
if (this.groups.has(cat)) this.groups.get(cat)!.push(entry);
|
|
649
640
|
if ((rawCat === 'controlPlane' || rawCat === 'httpListener' || rawCat === 'web') && this.groups.has('network')) {
|
|
@@ -752,6 +743,10 @@ export class SettingsModal {
|
|
|
752
743
|
|
|
753
744
|
private _setValue(key: ConfigKey, value: unknown): void {
|
|
754
745
|
if (!this.configManager) return;
|
|
746
|
+
if (isExternalDaemonOwnedSettingKey(key)) {
|
|
747
|
+
this.lastSettingEffectMessage = AGENT_EXTERNAL_DAEMON_SETTING_LOCK_REASON;
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
755
750
|
// Diff previous value before writing — avoids false restart notices on no-op saves
|
|
756
751
|
const previousValue = this.configManager.get(key);
|
|
757
752
|
const isRestartKey = ['host', 'port', 'hostMode', 'enabled'].includes(key.split('.')[1] ?? '');
|
|
@@ -790,4 +785,10 @@ export class SettingsModal {
|
|
|
790
785
|
}
|
|
791
786
|
}
|
|
792
787
|
|
|
788
|
+
private _blockExternalDaemonOwnedSetting(entry: SettingEntry): boolean {
|
|
789
|
+
if (!isExternalDaemonOwnedSettingKey(entry.setting.key)) return false;
|
|
790
|
+
this.lastSettingEffectMessage = entry.lockReason ?? AGENT_EXTERNAL_DAEMON_SETTING_LOCK_REASON;
|
|
791
|
+
return true;
|
|
792
|
+
}
|
|
793
|
+
|
|
793
794
|
}
|
package/src/main.ts
CHANGED
|
@@ -20,8 +20,6 @@ import { registerBuiltinCommands } from './input/commands.ts';
|
|
|
20
20
|
import { ScheduleManager } from '@pellux/goodvibes-sdk/platform/tools';
|
|
21
21
|
import { InputHistory } from './input/input-history.ts';
|
|
22
22
|
import { getTierPromptSupplement, getTierForContextWindow } from '@pellux/goodvibes-sdk/platform/providers';
|
|
23
|
-
import { GitStatusProvider } from './renderer/git-status.ts';
|
|
24
|
-
import type { GitHeaderInfo } from './renderer/git-status.ts';
|
|
25
23
|
import { createShellLayout } from './renderer/layout-engine.ts';
|
|
26
24
|
import { buildShellFooter, estimateShellFooterHeight } from './renderer/shell-surface.ts';
|
|
27
25
|
import { buildConversationViewport } from './renderer/conversation-layout.ts';
|
|
@@ -53,7 +51,6 @@ import { attachSpokenTurnModelRouting, createSpokenTurnInputOptions } from './au
|
|
|
53
51
|
import { allowTerminalWrite, installTuiTerminalOutputGuard } from './runtime/terminal-output-guard.ts';
|
|
54
52
|
import { ProjectPlanningCoordinator } from './planning/project-planning-coordinator.ts';
|
|
55
53
|
import { buildCommandArgsHint } from './input/command-args-hint.ts';
|
|
56
|
-
import { summarizeRunningAgents } from './renderer/process-summary.ts';
|
|
57
54
|
import { GOODVIBES_AGENT_PAIRING_SURFACE } from './config/surface.ts';
|
|
58
55
|
|
|
59
56
|
const ALT_SCREEN_ENTER = '\x1b[?1049h';
|
|
@@ -93,8 +90,6 @@ async function main() {
|
|
|
93
90
|
commandRegistry,
|
|
94
91
|
inputHistory,
|
|
95
92
|
hookDispatcher,
|
|
96
|
-
gitStatusProvider,
|
|
97
|
-
lastGitInfoRef,
|
|
98
93
|
bootstrapUnsubs,
|
|
99
94
|
agentStatusIntervalRef,
|
|
100
95
|
orchestratorRefs,
|
|
@@ -133,15 +128,12 @@ async function main() {
|
|
|
133
128
|
const sessionSnapshot = uiServices.readModels.session.getSnapshot();
|
|
134
129
|
const tasksSnapshot = uiServices.readModels.tasks.getSnapshot();
|
|
135
130
|
const remoteSnapshot = uiServices.readModels.remote.getSnapshot();
|
|
136
|
-
const worktreeSnapshot = uiServices.readModels.worktrees.getSnapshot();
|
|
137
131
|
return {
|
|
138
132
|
pendingApprovals: sessionSnapshot.pendingApproval ? 1 : 0,
|
|
139
133
|
activeTasks: tasksSnapshot.tasks.filter((task) => task.status === 'running' || task.status === 'queued').length,
|
|
140
134
|
blockedTasks: tasksSnapshot.tasks.filter((task) => task.status === 'blocked').length,
|
|
141
135
|
remoteContracts: remoteSnapshot.contracts.length,
|
|
142
136
|
remoteRunners: remoteSnapshot.contracts.slice(0, 4).map((contract) => contract.runnerId),
|
|
143
|
-
worktreeCount: worktreeSnapshot.records.length,
|
|
144
|
-
worktreePaths: worktreeSnapshot.records.slice(0, 3).map((record) => record.path),
|
|
145
137
|
openPanels: panelManager.getAllOpen().map((panel) => panel.id),
|
|
146
138
|
};
|
|
147
139
|
};
|
|
@@ -478,15 +470,9 @@ async function main() {
|
|
|
478
470
|
// Cache the current model for consistent values across the entire render frame
|
|
479
471
|
const currentModel = providerRegistry.getCurrentModel();
|
|
480
472
|
const sessionSnapshot = uiServices.readModels.session.getSnapshot();
|
|
481
|
-
const agentSnapshot = uiServices.readModels.agents.getSnapshot();
|
|
482
473
|
|
|
483
|
-
const headerLines = UIFactory.createHeader(width, currentModel.id, currentModel.provider, conversation.title || undefined
|
|
484
|
-
const
|
|
485
|
-
(a) => a.status === 'running' || a.status === 'pending',
|
|
486
|
-
);
|
|
487
|
-
const runtimeAgents = agentSnapshot.active;
|
|
488
|
-
const runningAgentSummary = summarizeRunningAgents(managerAgents, runtimeAgents, ctx.services.wrfcController.listChains());
|
|
489
|
-
const runningAgentCount = runningAgentSummary.count;
|
|
474
|
+
const headerLines = UIFactory.createHeader(width, currentModel.id, currentModel.provider, conversation.title || undefined);
|
|
475
|
+
const runningAgentCount = 0;
|
|
490
476
|
const runningProcessCount = processManager.list().filter((p) => !p.status.startsWith('done')).length;
|
|
491
477
|
const cw = getPromptContentWidth();
|
|
492
478
|
const promptInfo = input.getWrappedPromptInfo(cw);
|
|
@@ -533,7 +519,7 @@ async function main() {
|
|
|
533
519
|
runningAgentCount,
|
|
534
520
|
runningProcessCount,
|
|
535
521
|
indicatorFocused: input.indicatorFocused,
|
|
536
|
-
runningAgentProgress:
|
|
522
|
+
runningAgentProgress: undefined,
|
|
537
523
|
composerMode: composerState.modeLabel,
|
|
538
524
|
composerStatus: composerState.statusLabel,
|
|
539
525
|
composerFlags: composerState.flags,
|
|
@@ -684,8 +670,6 @@ async function main() {
|
|
|
684
670
|
});
|
|
685
671
|
|
|
686
672
|
// --- Streaming speed + tool preview wiring ---
|
|
687
|
-
const refreshGit = () => gitStatusProvider.refresh().then((info) => { lastGitInfoRef.value = info; render(); }).catch(() => { /* non-fatal */ });
|
|
688
|
-
// Refresh git status after each turn completes or after tool results arrive
|
|
689
673
|
unsubs.push(uiServices.events.turns.on('TURN_COMPLETED', () => {
|
|
690
674
|
// Auto-save after every LLM turn so kills don't lose the session
|
|
691
675
|
try {
|
|
@@ -701,13 +685,6 @@ async function main() {
|
|
|
701
685
|
);
|
|
702
686
|
hookDispatcher.fire({ path: 'Lifecycle:session:save' as HookEventPath, phase: 'Lifecycle' as HookPhase, category: 'session' as HookCategory, specific: 'save', sessionId: runtime.sessionId, timestamp: Date.now(), payload: { sessionId: runtime.sessionId } }).catch((err: unknown) => logger.debug('hook fire error', { error: summarizeError(err) }));
|
|
703
687
|
} catch (e) { logger.debug('auto-save on turn:complete failed', { error: summarizeError(e) }); }
|
|
704
|
-
refreshGit();
|
|
705
|
-
}));
|
|
706
|
-
unsubs.push(uiServices.events.tools.on('TOOL_SUCCEEDED', () => {
|
|
707
|
-
refreshGit();
|
|
708
|
-
}));
|
|
709
|
-
unsubs.push(uiServices.events.tools.on('TOOL_FAILED', () => {
|
|
710
|
-
refreshGit();
|
|
711
688
|
}));
|
|
712
689
|
|
|
713
690
|
unsubs.push(uiServices.events.turns.on('STREAM_START', () => {
|
|
@@ -185,7 +185,7 @@ export class IncidentReviewPanel extends ScrollableListPanel<FailureReport> {
|
|
|
185
185
|
}
|
|
186
186
|
}
|
|
187
187
|
footerLines.push(buildPanelLine(width, [[' Action Rail', C.label]]));
|
|
188
|
-
footerLines.push(buildPanelLine(width, [[` /incident latest /incident export ${selected.id} /
|
|
188
|
+
footerLines.push(buildPanelLine(width, [[` /incident latest /incident export ${selected.id} <path> --yes /incident capture ${selected.id} --yes`, C.info]]));
|
|
189
189
|
footerLines.push(buildGuidanceLine(width, '/security', 'open the broader trust and incident posture control room', C));
|
|
190
190
|
|
|
191
191
|
return this.renderList(width, height, {
|
|
@@ -81,7 +81,7 @@ export class LocalAuthPanel extends ScrollableListPanel<LocalAuthUser> {
|
|
|
81
81
|
...(issueMessages.length > 0
|
|
82
82
|
? issueMessages.map((issue) => buildPanelLine(width, [[` issue: ${issue}`.slice(0, Math.max(0, width)), C.warn]]))
|
|
83
83
|
: [buildPanelLine(width, [[' local auth posture looks healthy.', C.good]])]),
|
|
84
|
-
buildGuidanceLine(width, '/auth local rotate-password <user> <password>', 'rotate bootstrap/default credentials and revoke older sessions as needed', C),
|
|
84
|
+
buildGuidanceLine(width, '/auth local rotate-password <user> <password> --yes', 'rotate bootstrap/default credentials and revoke older sessions as needed', C),
|
|
85
85
|
], C),
|
|
86
86
|
];
|
|
87
87
|
|
|
@@ -104,8 +104,8 @@ export class LocalAuthPanel extends ScrollableListPanel<LocalAuthUser> {
|
|
|
104
104
|
footerLines.push(
|
|
105
105
|
...buildDetailBlock(width, 'Selected user', [
|
|
106
106
|
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} <password
|
|
108
|
-
buildPanelLine(width, [[` next: /auth local delete-user ${selected.username}`.slice(0, Math.max(0, width)), C.dim]]),
|
|
107
|
+
buildPanelLine(width, [[` next: /auth local rotate-password ${selected.username} <password> --yes`.slice(0, Math.max(0, width)), C.dim]]),
|
|
108
|
+
buildPanelLine(width, [[` next: /auth local delete-user ${selected.username} --yes`.slice(0, Math.max(0, width)), C.dim]]),
|
|
109
109
|
], C),
|
|
110
110
|
);
|
|
111
111
|
}
|
|
@@ -119,7 +119,7 @@ export class LocalAuthPanel extends ScrollableListPanel<LocalAuthUser> {
|
|
|
119
119
|
])),
|
|
120
120
|
);
|
|
121
121
|
}
|
|
122
|
-
footerLines.push(buildPanelLine(width, [[' /auth local review
|
|
122
|
+
footerLines.push(buildPanelLine(width, [[' /auth local review mutations require --yes: add-user rotate-password revoke-session ', C.dim]]));
|
|
123
123
|
|
|
124
124
|
return this.renderList(width, height, {
|
|
125
125
|
title: 'Local Auth Control Room',
|
|
@@ -209,7 +209,7 @@ export async function buildProviderAccountSnapshot(
|
|
|
209
209
|
}
|
|
210
210
|
if (pending) {
|
|
211
211
|
issues.push('Provider has a pending OAuth login that has not been completed yet.');
|
|
212
|
-
recommendedActions.push(`Finish /subscription login ${providerId} finish <code> or clear the pending login.`);
|
|
212
|
+
recommendedActions.push(`Finish /subscription login ${providerId} finish <code> --yes or clear the pending login.`);
|
|
213
213
|
}
|
|
214
214
|
if (hasSubscription && hasApiKey) {
|
|
215
215
|
issues.push('Provider has both subscription and API-key auth paths; routing must remain explicit.');
|
|
@@ -54,13 +54,13 @@ export function buildProviderHealthDomainSummaries(
|
|
|
54
54
|
summary: auth.bootstrapCredentialPresent
|
|
55
55
|
? 'bootstrap credential file still present'
|
|
56
56
|
: `${auth.userCount} users / ${auth.sessionCount} sessions`,
|
|
57
|
-
next: auth.bootstrapCredentialPresent ? '/auth local clear-bootstrap-file' : '/auth local review',
|
|
57
|
+
next: auth.bootstrapCredentialPresent ? '/auth local clear-bootstrap-file --yes' : '/auth local review',
|
|
58
58
|
details: [
|
|
59
59
|
auth.bootstrapCredentialPresent ? 'bootstrap credential file should be cleared after rotation' : `${auth.userCount} local auth users configured`,
|
|
60
60
|
auth.userCount <= 1 ? 'only one local auth user configured' : `${auth.sessionCount} active local auth sessions`,
|
|
61
61
|
].filter(Boolean),
|
|
62
62
|
nextSteps: auth.bootstrapCredentialPresent
|
|
63
|
-
? ['/auth local review', '/auth local rotate-password <user> <password>', '/auth local clear-bootstrap-file']
|
|
63
|
+
? ['/auth local review', '/auth local rotate-password <user> <password> --yes', '/auth local clear-bootstrap-file --yes']
|
|
64
64
|
: ['/auth local review'],
|
|
65
65
|
});
|
|
66
66
|
|