@pellux/goodvibes-tui 0.19.24 → 0.19.26

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.
Files changed (76) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +5 -5
  3. package/bin/goodvibes +10 -0
  4. package/bin/goodvibes-daemon +10 -0
  5. package/docs/foundation-artifacts/operator-contract.json +1 -1
  6. package/package.json +3 -2
  7. package/src/cli/bundle-command.ts +225 -0
  8. package/src/cli/completion.ts +90 -0
  9. package/src/cli/config-overrides.ts +159 -0
  10. package/src/cli/endpoints.ts +63 -0
  11. package/src/cli/entrypoint.ts +169 -0
  12. package/src/cli/help.ts +301 -0
  13. package/src/cli/index.ts +11 -0
  14. package/src/cli/management-commands.ts +426 -0
  15. package/src/cli/management.ts +719 -0
  16. package/src/cli/network-posture.ts +46 -0
  17. package/src/cli/package-verification.ts +119 -0
  18. package/src/cli/parser.ts +369 -0
  19. package/src/cli/provider-classification.ts +107 -0
  20. package/src/cli/redaction.ts +105 -0
  21. package/src/cli/service-command.ts +45 -0
  22. package/src/cli/service-posture.ts +247 -0
  23. package/src/cli/status.ts +382 -0
  24. package/src/cli/surface-command.ts +248 -0
  25. package/src/cli/tui-startup.ts +32 -0
  26. package/src/cli/types.ts +69 -0
  27. package/src/cli-flags.ts +18 -55
  28. package/src/config/index.ts +1 -1
  29. package/src/config/secrets.ts +44 -0
  30. package/src/daemon/cli.ts +62 -11
  31. package/src/input/command-registry.ts +3 -0
  32. package/src/input/commands/guidance-runtime.ts +9 -4
  33. package/src/input/commands/local-runtime.ts +21 -7
  34. package/src/input/commands/local-setup.ts +31 -38
  35. package/src/input/commands/onboarding-runtime.ts +14 -0
  36. package/src/input/commands/runtime-services.ts +9 -0
  37. package/src/input/commands.ts +2 -0
  38. package/src/input/feed-context-factory.ts +8 -1
  39. package/src/input/handler-feed.ts +13 -8
  40. package/src/input/handler-interactions.ts +266 -0
  41. package/src/input/handler-modal-stack.ts +23 -3
  42. package/src/input/handler-modal-token-routes.ts +23 -1
  43. package/src/input/handler-onboarding.ts +696 -0
  44. package/src/input/handler-picker-routes.ts +15 -7
  45. package/src/input/handler-ui-state.ts +58 -0
  46. package/src/input/handler.ts +120 -246
  47. package/src/input/onboarding/handler-onboarding-routes.ts +105 -0
  48. package/src/input/onboarding/onboarding-wizard-apply.ts +211 -0
  49. package/src/input/onboarding/onboarding-wizard-constants.ts +148 -0
  50. package/src/input/onboarding/onboarding-wizard-external-surfaces.ts +712 -0
  51. package/src/input/onboarding/onboarding-wizard-helpers.ts +218 -0
  52. package/src/input/onboarding/onboarding-wizard-rules.ts +224 -0
  53. package/src/input/onboarding/onboarding-wizard-state.ts +354 -0
  54. package/src/input/onboarding/onboarding-wizard-steps.ts +642 -0
  55. package/src/input/onboarding/onboarding-wizard-types.ts +170 -0
  56. package/src/input/onboarding/onboarding-wizard.ts +594 -0
  57. package/src/main.ts +32 -39
  58. package/src/panels/builtin/operations.ts +0 -10
  59. package/src/panels/index.ts +0 -1
  60. package/src/renderer/conversation-overlays.ts +6 -0
  61. package/src/renderer/help-overlay.ts +1 -1
  62. package/src/renderer/onboarding/onboarding-wizard.ts +533 -0
  63. package/src/runtime/bootstrap-core.ts +1 -0
  64. package/src/runtime/bootstrap.ts +123 -0
  65. package/src/runtime/onboarding/apply.ts +685 -0
  66. package/src/runtime/onboarding/derivation.ts +495 -0
  67. package/src/runtime/onboarding/index.ts +7 -0
  68. package/src/runtime/onboarding/markers.ts +161 -0
  69. package/src/runtime/onboarding/snapshot.ts +400 -0
  70. package/src/runtime/onboarding/state.ts +140 -0
  71. package/src/runtime/onboarding/types.ts +402 -0
  72. package/src/runtime/onboarding/verify.ts +233 -0
  73. package/src/runtime/ui-services.ts +16 -0
  74. package/src/shell/ui-openers.ts +12 -2
  75. package/src/version.ts +1 -1
  76. package/src/panels/welcome-panel.ts +0 -64
@@ -0,0 +1,211 @@
1
+ import type { OnboardingAcknowledgementTarget, OnboardingApplyOperation, OnboardingApplyRequest } from '../../runtime/onboarding/index.ts';
2
+ import { EXTERNAL_SURFACE_SPECS } from './onboarding-wizard-external-surfaces.ts';
3
+ import { buildGoodVibesSecretKey, buildGoodVibesSecretRef, isLoopbackAddress, isSecretReferenceValue } from './onboarding-wizard-helpers.ts';
4
+ import type { OnboardingWizardController } from './onboarding-wizard.ts';
5
+
6
+ export function buildOnboardingApplyRequest(controller: OnboardingWizardController): OnboardingApplyRequest {
7
+ const operations: OnboardingApplyOperation[] = [];
8
+ const hasServers = controller.hasServerCapabilitiesSelected();
9
+ const browserAccess = controller.shouldEnableBrowserSurface();
10
+ const httpListener = controller.shouldEnableHttpListener();
11
+ const controlPlaneRemote = controller.shouldExposeControlPlaneNetwork();
12
+ const networkMode = controller.getStringFieldValue('network.mode', controller.runtimeDerived.step1_5NetworkMode);
13
+ const customNetwork = hasServers && networkMode === 'custom';
14
+
15
+ const setConfig = (
16
+ key: Extract<OnboardingApplyOperation, { kind: 'set-config' }>['key'],
17
+ value: unknown,
18
+ ): void => {
19
+ operations.push({ kind: 'set-config', key, value });
20
+ };
21
+ const acknowledge = (target: OnboardingAcknowledgementTarget, fieldId: string): void => {
22
+ operations.push({
23
+ kind: 'acknowledge',
24
+ target,
25
+ acknowledged: controller.getBooleanFieldValue(fieldId, false),
26
+ });
27
+ };
28
+ const setSecret = (key: string, value: string): void => {
29
+ if (value.length === 0) return;
30
+ const medium = controller.getSelectedSecretMedium();
31
+ operations.push({
32
+ kind: 'set-secret',
33
+ key,
34
+ value,
35
+ scope: 'project',
36
+ medium,
37
+ });
38
+ };
39
+ const setMaskedConfig = (
40
+ key: Extract<OnboardingApplyOperation, { kind: 'set-config' }>['key'],
41
+ value: string,
42
+ ): void => {
43
+ if (value.length === 0 || isSecretReferenceValue(value)) {
44
+ setConfig(key, value);
45
+ return;
46
+ }
47
+
48
+ const secretKey = buildGoodVibesSecretKey(key);
49
+ setSecret(secretKey, value);
50
+ setConfig(key, buildGoodVibesSecretRef(secretKey));
51
+ };
52
+
53
+ if (controller.requiresAuthBootstrap()) {
54
+ operations.push({
55
+ kind: 'ensure-auth-user',
56
+ username: controller.getStringFieldValue('accounts.admin-username', controller.getDefaultAdminUsername()),
57
+ password: controller.getStringFieldValue('accounts.admin-password', ''),
58
+ roles: ['admin'],
59
+ createSession: true,
60
+ retireBootstrapCredential: controller.hasBootstrapCredentialPresent(),
61
+ });
62
+ }
63
+
64
+ setConfig('service.enabled', hasServers);
65
+ setConfig('service.autostart', hasServers);
66
+ setConfig('service.restartOnFailure', true);
67
+ setConfig('danger.daemon', hasServers);
68
+ setConfig('controlPlane.enabled', hasServers);
69
+ setConfig('danger.httpListener', httpListener);
70
+ setConfig('web.enabled', browserAccess);
71
+
72
+ if (hasServers) {
73
+ addNetworkOperations(controller, operations, customNetwork, {
74
+ controlPlane: hasServers,
75
+ controlPlaneRemote,
76
+ httpListener,
77
+ web: browserAccess,
78
+ });
79
+ } else {
80
+ setConfig('controlPlane.hostMode', 'local');
81
+ setConfig('controlPlane.host', controller.runtimeSnapshot?.bindSettings.controlPlane.host || '127.0.0.1');
82
+ setConfig('controlPlane.allowRemote', false);
83
+ setConfig('httpListener.hostMode', 'local');
84
+ setConfig('httpListener.host', controller.runtimeSnapshot?.bindSettings.httpListener.host || '127.0.0.1');
85
+ setConfig('web.hostMode', 'local');
86
+ setConfig('web.host', controller.runtimeSnapshot?.bindSettings.web.host || '127.0.0.1');
87
+ }
88
+
89
+ const defaultModel = controller.modelSelectionState.get('main');
90
+ if (defaultModel && defaultModel.enabled !== false && defaultModel.providerId.length > 0 && defaultModel.modelId.length > 0) {
91
+ setConfig('provider.provider', defaultModel.providerId);
92
+ setConfig('provider.model', defaultModel.modelId);
93
+ }
94
+ setConfig('provider.reasoningEffort', controller.getStringFieldValue('default-model.reasoning', controller.runtimeSnapshot?.providerRouting.primaryReasoningEffort ?? 'medium'));
95
+ setConfig('behavior.hitlMode', controller.getStringFieldValue('experience.hitl', controller.runtimeSnapshot?.runtimeDefaults.behavior.hitlMode ?? 'balanced'));
96
+ setConfig('behavior.guidanceMode', controller.getStringFieldValue('experience.guidance', controller.runtimeSnapshot?.runtimeDefaults.behavior.guidanceMode ?? 'minimal'));
97
+ setConfig('permissions.mode', controller.getStringFieldValue('experience.permissions', controller.runtimeSnapshot?.runtimeDefaults.permissionsMode ?? 'prompt'));
98
+ setConfig('storage.secretPolicy', controller.getStringFieldValue('external-services.secret-policy', controller.runtimeSnapshot?.runtimeDefaults.secretStoragePolicy ?? 'preferred_secure'));
99
+
100
+ setSecret('OPENAI_API_KEY', controller.getStringFieldValue('providers.openai-api-key', ''));
101
+
102
+ const externalIntegrations = controller.isCapabilitySelected('external-integrations');
103
+ for (const surface of EXTERNAL_SURFACE_SPECS) {
104
+ const enabled = externalIntegrations
105
+ && controller.getBooleanFieldValue(surface.enabledFieldId, surface.defaultEnabled(controller.runtimeSnapshot));
106
+ setConfig(surface.enabledConfigKey, enabled);
107
+ if (!enabled) continue;
108
+
109
+ for (const setupField of surface.fields) {
110
+ const fallback = setupField.defaultValue(controller.runtimeSnapshot);
111
+ if (setupField.valueType === 'number') {
112
+ setConfig(
113
+ setupField.configKey,
114
+ controller.getNumberFieldValue(
115
+ setupField.id,
116
+ setupField.defaultNumber ?? (Number.parseInt(fallback, 10) || 0),
117
+ setupField.min,
118
+ setupField.max,
119
+ ),
120
+ );
121
+ continue;
122
+ }
123
+
124
+ const value = controller.getStringFieldValue(setupField.id, fallback);
125
+ if (setupField.kind === 'masked') setMaskedConfig(setupField.configKey, value);
126
+ else setConfig(setupField.configKey, value);
127
+ }
128
+ }
129
+
130
+ acknowledge('providers', 'providers.reviewed');
131
+ acknowledge('subscriptions', 'accounts.subscriptions');
132
+ acknowledge('auth', 'accounts.auth');
133
+
134
+ if (controller.getBooleanFieldValue('review.project-marker', true)) {
135
+ operations.push({
136
+ kind: 'set-completion-marker',
137
+ scope: 'project',
138
+ completed: true,
139
+ payload: { source: 'wizard', mode: controller.mode },
140
+ });
141
+ }
142
+ if (controller.getBooleanFieldValue('review.user-marker', controller.defaultReviewUserMarker())) {
143
+ operations.push({
144
+ kind: 'set-completion-marker',
145
+ scope: 'user',
146
+ completed: true,
147
+ payload: { source: 'wizard', mode: controller.mode },
148
+ });
149
+ }
150
+
151
+ return {
152
+ mode: controller.mode,
153
+ source: 'wizard',
154
+ operations,
155
+ };
156
+ }
157
+
158
+ export function addNetworkOperations(
159
+ controller: OnboardingWizardController,
160
+ operations: OnboardingApplyOperation[],
161
+ customNetwork: boolean,
162
+ enabled: {
163
+ readonly controlPlane: boolean;
164
+ readonly controlPlaneRemote: boolean;
165
+ readonly httpListener: boolean;
166
+ readonly web: boolean;
167
+ },
168
+ ): void {
169
+ const setConfig = (
170
+ key: Extract<OnboardingApplyOperation, { kind: 'set-config' }>['key'],
171
+ value: unknown,
172
+ ): void => {
173
+ operations.push({ kind: 'set-config', key, value });
174
+ };
175
+ const networkFacingEnabled = {
176
+ controlPlane: enabled.controlPlaneRemote,
177
+ httpListener: enabled.httpListener,
178
+ web: enabled.web,
179
+ };
180
+ const sharedIpDefault = controller.getSharedIpDefault(networkFacingEnabled);
181
+ const sharedIp = controller.getBooleanFieldValue('network.shared-ip', sharedIpDefault);
182
+ const sharedHost = controller.getStringFieldValue('network.shared-ip-address', controller.getSharedIpHostDefault(networkFacingEnabled)) || '0.0.0.0';
183
+ const controlPlaneHost = sharedIp
184
+ ? sharedHost
185
+ : controller.getStringFieldValue('network.service-ip', controller.runtimeSnapshot?.bindSettings.controlPlane.host ?? '0.0.0.0');
186
+ const httpListenerHost = sharedIp
187
+ ? sharedHost
188
+ : controller.getStringFieldValue('network.webhook-ip', controller.runtimeSnapshot?.bindSettings.httpListener.host ?? '0.0.0.0');
189
+ const webHost = sharedIp
190
+ ? sharedHost
191
+ : controller.getStringFieldValue('network.browser-ip', controller.runtimeSnapshot?.bindSettings.web.host ?? '0.0.0.0');
192
+
193
+ if (enabled.controlPlane) {
194
+ setConfig('controlPlane.hostMode', enabled.controlPlaneRemote ? (customNetwork ? 'custom' : 'network') : 'local');
195
+ setConfig('controlPlane.host', enabled.controlPlaneRemote ? (customNetwork ? controlPlaneHost : '0.0.0.0') : '127.0.0.1');
196
+ setConfig('controlPlane.port', controller.getPortFieldValue('network.service-port', controller.runtimeSnapshot?.bindSettings.controlPlane.port ?? 3421));
197
+ setConfig('controlPlane.allowRemote', enabled.controlPlaneRemote && (customNetwork ? !isLoopbackAddress(controlPlaneHost) : true));
198
+ }
199
+
200
+ if (enabled.httpListener) {
201
+ setConfig('httpListener.hostMode', customNetwork ? 'custom' : 'network');
202
+ setConfig('httpListener.host', customNetwork ? httpListenerHost : '0.0.0.0');
203
+ setConfig('httpListener.port', controller.getPortFieldValue('network.webhook-port', controller.runtimeSnapshot?.bindSettings.httpListener.port ?? 3422));
204
+ }
205
+
206
+ if (enabled.web) {
207
+ setConfig('web.hostMode', customNetwork ? 'custom' : 'network');
208
+ setConfig('web.host', customNetwork ? webHost : '0.0.0.0');
209
+ setConfig('web.port', controller.getPortFieldValue('network.browser-port', controller.runtimeSnapshot?.bindSettings.web.port ?? 3423));
210
+ }
211
+ }
@@ -0,0 +1,148 @@
1
+ import type { OnboardingStep1CapabilityItem } from '../../runtime/onboarding/index.ts';
2
+ import type { OnboardingWizardRadioOption, OnboardingWizardStepId } from './onboarding-wizard-types.ts';
3
+
4
+ export const STEP_ORDER: readonly OnboardingWizardStepId[] = [
5
+ 'capabilities',
6
+ 'network',
7
+ 'access',
8
+ 'external-services',
9
+ 'provider-access',
10
+ 'default-model',
11
+ 'experience',
12
+ 'review',
13
+ ];
14
+
15
+ export const DEFAULT_CAPABILITIES: readonly OnboardingStep1CapabilityItem[] = [
16
+ {
17
+ id: 'local-tui-only',
18
+ label: 'Local TUI Only (No Servers)',
19
+ selected: true,
20
+ detail: 'Keep GoodVibes in this terminal and disable browser access, background services, network listeners, and external surfaces.',
21
+ },
22
+ {
23
+ id: 'browser-access',
24
+ label: 'Open GoodVibes in a Browser',
25
+ selected: false,
26
+ detail: 'Enable the background service and web UI, reachable on the local network by default unless customized.',
27
+ },
28
+ {
29
+ id: 'network-access',
30
+ label: 'Let other devices use GoodVibes',
31
+ selected: false,
32
+ detail: 'Expose enabled GoodVibes services on your LAN so other devices can reach them. Local auth is required.',
33
+ },
34
+ {
35
+ id: 'webhook-events',
36
+ label: 'Receive webhooks or events from other tools',
37
+ selected: false,
38
+ detail: 'Turn on the HTTP listener for incoming webhooks, callbacks, and automation events.',
39
+ },
40
+ {
41
+ id: 'external-integrations',
42
+ label: 'Connect GoodVibes to external apps and services',
43
+ selected: false,
44
+ detail: 'Show Slack, Discord, Telegram, Teams, Matrix, and other app surfaces so they can be enabled and configured here.',
45
+ },
46
+ ];
47
+
48
+ export const REASONING_OPTIONS: readonly OnboardingWizardRadioOption[] = [
49
+ { id: 'instant', label: 'Instant', hint: 'Lowest-latency routing.' },
50
+ { id: 'low', label: 'Low', hint: 'Compact reasoning for quick work.' },
51
+ { id: 'medium', label: 'Medium', hint: 'Balanced latency and quality.' },
52
+ { id: 'high', label: 'High', hint: 'Deeper reasoning for complex tasks.' },
53
+ ];
54
+
55
+ export const NETWORK_MODE_OPTIONS: readonly OnboardingWizardRadioOption[] = [
56
+ { id: 'local-network-default', label: 'Local Network (Default)', hint: 'Use the default LAN-facing setup for enabled browser, service, and event features.' },
57
+ { id: 'custom', label: 'Custom', hint: 'Choose IP addresses and ports for each enabled service.' },
58
+ ];
59
+
60
+ export const HITL_MODE_OPTIONS: readonly OnboardingWizardRadioOption[] = [
61
+ { id: 'quiet', label: 'Quiet', hint: 'Only interrupt for important attention requests.' },
62
+ { id: 'balanced', label: 'Balanced', hint: 'Show important activity without turning the TUI into a log stream.' },
63
+ { id: 'operator', label: 'Operator', hint: 'Keep operational activity visible for hands-on supervision.' },
64
+ ];
65
+
66
+ export const GUIDANCE_MODE_OPTIONS: readonly OnboardingWizardRadioOption[] = [
67
+ { id: 'minimal', label: 'Minimal', hint: 'Use compact guidance and repair suggestions.' },
68
+ { id: 'guided', label: 'Guided', hint: 'Show more explanation while working.' },
69
+ { id: 'off', label: 'Off', hint: 'Disable proactive guidance.' },
70
+ ];
71
+
72
+ export const PERMISSION_MODE_OPTIONS: readonly OnboardingWizardRadioOption[] = [
73
+ { id: 'prompt', label: 'Ask before powerful actions', hint: 'Prompt before write, edit, network, and execution tools.' },
74
+ { id: 'allow-all', label: 'Allow everything', hint: 'Allow tools without approval prompts.' },
75
+ { id: 'custom', label: 'Custom advanced rules', hint: 'Use tool-specific permission rules.' },
76
+ ];
77
+
78
+ export const SECRET_POLICY_OPTIONS: readonly OnboardingWizardRadioOption[] = [
79
+ { id: 'preferred_secure', label: 'Use secure storage when available', hint: 'Prefer the secure secret backend and fall back only when needed.' },
80
+ { id: 'require_secure', label: 'Require secure storage', hint: 'Refuse plaintext secret persistence.' },
81
+ { id: 'plaintext_allowed', label: 'Allow plaintext storage', hint: 'Permit local plaintext secret storage.' },
82
+ ];
83
+
84
+ export const TELEGRAM_MODE_OPTIONS: readonly OnboardingWizardRadioOption[] = [
85
+ { id: 'webhook', label: 'Webhook', hint: 'Receive Telegram updates through a webhook.' },
86
+ { id: 'polling', label: 'Polling', hint: 'Poll Telegram for updates from the background service.' },
87
+ ];
88
+
89
+ export const WHATSAPP_PROVIDER_OPTIONS: readonly OnboardingWizardRadioOption[] = [
90
+ { id: 'meta-cloud', label: 'Meta Cloud', hint: 'Use the Meta Cloud API.' },
91
+ { id: 'bridge', label: 'Bridge', hint: 'Use a configured bridge service.' },
92
+ ];
93
+
94
+ export const INBOUND_EXTERNAL_SURFACE_IDS = new Set<string>([
95
+ 'bluebubbles',
96
+ 'discord',
97
+ 'googleChat',
98
+ 'imessage',
99
+ 'mattermost',
100
+ 'matrix',
101
+ 'msteams',
102
+ 'ntfy',
103
+ 'signal',
104
+ 'slack',
105
+ 'telegram',
106
+ 'webhook',
107
+ 'whatsapp',
108
+ ]);
109
+
110
+ export const REQUIRED_EXTERNAL_SETUP_FIELD_IDS = new Set<string>([
111
+ 'external-services.slack.bot-token',
112
+ 'external-services.slack.signing-secret',
113
+ 'external-services.discord.bot-token',
114
+ 'external-services.discord.application-id',
115
+ 'external-services.discord.public-key',
116
+ 'external-services.telegram.bot-token',
117
+ 'external-services.telegram.webhook-secret',
118
+ 'external-services.ntfy.topic',
119
+ 'external-services.webhook.default-target',
120
+ 'external-services.google-chat.webhook-url',
121
+ 'external-services.google-chat.verification-token',
122
+ 'external-services.signal.bridge-url',
123
+ 'external-services.signal.account',
124
+ 'external-services.signal.token',
125
+ 'external-services.whatsapp.access-token',
126
+ 'external-services.whatsapp.verify-token',
127
+ 'external-services.whatsapp.phone-number-id',
128
+ 'external-services.imessage.bridge-url',
129
+ 'external-services.imessage.account',
130
+ 'external-services.imessage.token',
131
+ 'external-services.msteams.app-id',
132
+ 'external-services.msteams.app-password',
133
+ 'external-services.msteams.tenant-id',
134
+ 'external-services.bluebubbles.server-url',
135
+ 'external-services.bluebubbles.password',
136
+ 'external-services.mattermost.base-url',
137
+ 'external-services.mattermost.bot-token',
138
+ 'external-services.matrix.homeserver-url',
139
+ 'external-services.matrix.access-token',
140
+ 'external-services.matrix.user-id',
141
+ ]);
142
+
143
+ export const NETWORK_HOST_FIELD_IDS = new Set<string>([
144
+ 'network.shared-ip-address',
145
+ 'network.service-ip',
146
+ 'network.browser-ip',
147
+ 'network.webhook-ip',
148
+ ]);