@pellux/goodvibes-tui 0.19.33 → 0.19.35
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 +23 -0
- package/README.md +6 -3
- package/docs/foundation-artifacts/operator-contract.json +284 -112
- package/package.json +2 -2
- package/src/cli/management.ts +2 -2
- package/src/input/command-registry.ts +1 -0
- package/src/input/commands/cloudflare-runtime.ts +370 -0
- package/src/input/commands/local-auth-runtime.ts +4 -4
- package/src/input/commands/tts-runtime.ts +93 -10
- package/src/input/commands.ts +2 -0
- package/src/input/feed-context-factory.ts +1 -0
- package/src/input/handler-feed.ts +6 -0
- package/src/input/handler-modal-routes.ts +23 -10
- package/src/input/handler-modal-token-routes.ts +9 -0
- package/src/input/handler-onboarding-cloudflare.ts +391 -0
- package/src/input/handler-onboarding.ts +33 -0
- package/src/input/handler-picker-routes.ts +1 -1
- package/src/input/handler.ts +4 -1
- package/src/input/model-picker-types.ts +125 -0
- package/src/input/model-picker.ts +144 -135
- package/src/input/onboarding/onboarding-wizard-apply.ts +85 -0
- package/src/input/onboarding/onboarding-wizard-cloudflare-step.ts +494 -0
- package/src/input/onboarding/onboarding-wizard-cloudflare.ts +204 -0
- package/src/input/onboarding/onboarding-wizard-constants.ts +12 -1
- package/src/input/onboarding/onboarding-wizard-external-surface-extra-specs.ts +117 -0
- package/src/input/onboarding/onboarding-wizard-external-surfaces.ts +3 -41
- package/src/input/onboarding/onboarding-wizard-steps.ts +6 -6
- package/src/input/onboarding/onboarding-wizard-types.ts +8 -0
- package/src/input/settings-modal-types.ts +2 -1
- package/src/input/settings-modal.ts +30 -8
- package/src/renderer/buffer.ts +40 -2
- package/src/renderer/compositor.ts +25 -17
- package/src/renderer/model-picker-overlay.ts +70 -0
- package/src/renderer/settings-modal-helpers.ts +9 -0
- package/src/runtime/cloudflare-control-plane.ts +349 -0
- package/src/runtime/onboarding/apply.ts +9 -8
- package/src/runtime/onboarding/derivation.ts +26 -1
- package/src/runtime/onboarding/snapshot.ts +2 -0
- package/src/runtime/onboarding/types.ts +5 -1
- package/src/shell/ui-openers.ts +10 -1
- package/src/version.ts +1 -1
|
@@ -19,6 +19,8 @@ type SelectionRouteState = {
|
|
|
19
19
|
close: () => void;
|
|
20
20
|
};
|
|
21
21
|
selectionCallback: ((result: SelectionResult | null) => void) | null;
|
|
22
|
+
getSelectionCallback?: () => ((result: SelectionResult | null) => void) | null;
|
|
23
|
+
setSelectionCallback?: (callback: ((result: SelectionResult | null) => void) | null) => void;
|
|
22
24
|
modalStack: string[];
|
|
23
25
|
requestRender: () => void;
|
|
24
26
|
handleEscape: () => void;
|
|
@@ -53,11 +55,13 @@ export function handleSelectionModalToken(state: SelectionRouteState, token: Inp
|
|
|
53
55
|
}
|
|
54
56
|
const cb = state.selectionCallback;
|
|
55
57
|
state.selectionCallback = null;
|
|
58
|
+
state.setSelectionCallback?.(null);
|
|
56
59
|
state.selectionModal.close();
|
|
57
60
|
if (state.modalStack.length > 0 && state.modalStack[state.modalStack.length - 1] === 'selection') {
|
|
58
61
|
state.modalStack.pop();
|
|
59
62
|
}
|
|
60
63
|
cb?.({ item: selected, action, step });
|
|
64
|
+
state.selectionCallback = state.getSelectionCallback?.() ?? state.selectionCallback;
|
|
61
65
|
};
|
|
62
66
|
|
|
63
67
|
const getAdjustmentStep = (
|
|
@@ -216,13 +220,30 @@ type SettingsRouteState = {
|
|
|
216
220
|
editBackspace: () => void;
|
|
217
221
|
editChar: (char: string) => void;
|
|
218
222
|
pendingModelPickerTarget: import('./model-picker.ts').ModelPickerTarget | null;
|
|
223
|
+
pendingProviderModelPickerTarget?: import('./model-picker.ts').ModelPickerTarget | null;
|
|
219
224
|
};
|
|
220
225
|
/** Called when the settings modal requests the model picker for a non-main target. */
|
|
221
226
|
openModelPickerWithTarget?: (target: import('./model-picker.ts').ModelPickerTarget) => void;
|
|
227
|
+
/** Called when the settings modal requests provider selection before model selection. */
|
|
228
|
+
openProviderModelPickerWithTarget?: (target: import('./model-picker.ts').ModelPickerTarget) => void;
|
|
222
229
|
requestRender: () => void;
|
|
223
230
|
handleEscape: () => void;
|
|
224
231
|
};
|
|
225
232
|
|
|
233
|
+
function consumeSettingsPickerRequest(state: SettingsRouteState): void {
|
|
234
|
+
const providerModelTarget = state.settingsModal.pendingProviderModelPickerTarget ?? null;
|
|
235
|
+
if (providerModelTarget !== null) {
|
|
236
|
+
state.settingsModal.pendingProviderModelPickerTarget = null;
|
|
237
|
+
state.openProviderModelPickerWithTarget?.(providerModelTarget);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
const pickerTarget = state.settingsModal.pendingModelPickerTarget;
|
|
241
|
+
if (pickerTarget !== null) {
|
|
242
|
+
state.settingsModal.pendingModelPickerTarget = null;
|
|
243
|
+
state.openModelPickerWithTarget?.(pickerTarget);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
226
247
|
export function handleSettingsModalToken(state: SettingsRouteState, token: InputToken): boolean {
|
|
227
248
|
if (!state.settingsModal.active) return false;
|
|
228
249
|
|
|
@@ -236,11 +257,7 @@ export function handleSettingsModalToken(state: SettingsRouteState, token: Input
|
|
|
236
257
|
else if (state.settingsModal.currentCategory === 'flags') state.settingsModal.toggleSelectedFlag();
|
|
237
258
|
else {
|
|
238
259
|
state.settingsModal.activateSelected();
|
|
239
|
-
|
|
240
|
-
if (pickerTarget !== null) {
|
|
241
|
-
state.settingsModal.pendingModelPickerTarget = null;
|
|
242
|
-
state.openModelPickerWithTarget?.(pickerTarget);
|
|
243
|
-
}
|
|
260
|
+
consumeSettingsPickerRequest(state);
|
|
244
261
|
}
|
|
245
262
|
} else if ((token.logicalName === 'left' || token.logicalName === 'right') && !state.settingsModal.editingMode) {
|
|
246
263
|
state.settingsModal.adjustSelected(token.logicalName, token.shift ? 10 : 1);
|
|
@@ -253,11 +270,7 @@ export function handleSettingsModalToken(state: SettingsRouteState, token: Input
|
|
|
253
270
|
if (state.settingsModal.currentCategory === 'flags') state.settingsModal.toggleSelectedFlag();
|
|
254
271
|
else {
|
|
255
272
|
state.settingsModal.activateSelected();
|
|
256
|
-
|
|
257
|
-
if (pickerTarget !== null) {
|
|
258
|
-
state.settingsModal.pendingModelPickerTarget = null;
|
|
259
|
-
state.openModelPickerWithTarget?.(pickerTarget);
|
|
260
|
-
}
|
|
273
|
+
consumeSettingsPickerRequest(state);
|
|
261
274
|
}
|
|
262
275
|
} else if (state.settingsModal.editingMode) {
|
|
263
276
|
state.settingsModal.editChar(token.value);
|
|
@@ -39,6 +39,8 @@ export type ModalTokenRouteState = {
|
|
|
39
39
|
searchShortcutMatch: boolean;
|
|
40
40
|
selectionModal: SelectionModal;
|
|
41
41
|
selectionCallback: ((result: SelectionResult | null) => void) | null;
|
|
42
|
+
getSelectionCallback?: () => ((result: SelectionResult | null) => void) | null;
|
|
43
|
+
setSelectionCallback?: (callback: ((result: SelectionResult | null) => void) | null) => void;
|
|
42
44
|
bookmarkModal: BookmarkModal;
|
|
43
45
|
settingsModal: SettingsModal;
|
|
44
46
|
sessionPickerModal: SessionPickerModal;
|
|
@@ -80,6 +82,10 @@ export type ModalTokenRouteState = {
|
|
|
80
82
|
target: import('./model-picker.ts').ModelPickerTarget,
|
|
81
83
|
source?: 'settings' | 'onboarding',
|
|
82
84
|
) => boolean;
|
|
85
|
+
openProviderModelPickerWithTarget?: (
|
|
86
|
+
target: import('./model-picker.ts').ModelPickerTarget,
|
|
87
|
+
source?: 'settings' | 'onboarding',
|
|
88
|
+
) => boolean;
|
|
83
89
|
clearOnboardingModelPickerCancelState?: () => void;
|
|
84
90
|
restoreOnboardingModelPickerCancelState?: () => void;
|
|
85
91
|
onModelPickerCommit?: () => boolean;
|
|
@@ -110,6 +116,8 @@ export function handleModalTokenRoutes(state: ModalTokenRouteState, token: Input
|
|
|
110
116
|
const selectionState = {
|
|
111
117
|
selectionModal: state.selectionModal,
|
|
112
118
|
selectionCallback: state.selectionCallback,
|
|
119
|
+
getSelectionCallback: state.getSelectionCallback,
|
|
120
|
+
setSelectionCallback: state.setSelectionCallback,
|
|
113
121
|
modalStack: state.modalStack,
|
|
114
122
|
requestRender: state.requestRender,
|
|
115
123
|
handleEscape: state.handleEscape,
|
|
@@ -130,6 +138,7 @@ export function handleModalTokenRoutes(state: ModalTokenRouteState, token: Input
|
|
|
130
138
|
if (handleSettingsModalToken({
|
|
131
139
|
settingsModal: state.settingsModal,
|
|
132
140
|
openModelPickerWithTarget: state.openModelPickerWithTarget,
|
|
141
|
+
openProviderModelPickerWithTarget: state.openProviderModelPickerWithTarget,
|
|
133
142
|
requestRender: state.requestRender,
|
|
134
143
|
handleEscape: state.handleEscape,
|
|
135
144
|
}, token)) {
|
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CloudflareDaemonRouteError,
|
|
3
|
+
createCloudflareDaemonClient,
|
|
4
|
+
type CloudflareComponentSelection,
|
|
5
|
+
type CloudflareDaemonClient,
|
|
6
|
+
type CloudflareDiscoverResult,
|
|
7
|
+
type CloudflareOperationalTokenResult,
|
|
8
|
+
type CloudflareProvisionRequest,
|
|
9
|
+
type CloudflareProvisionResult,
|
|
10
|
+
type CloudflareTokenRequirementsResult,
|
|
11
|
+
type CloudflareValidateResult,
|
|
12
|
+
type CloudflareVerifyResult,
|
|
13
|
+
} from '../runtime/cloudflare-control-plane.ts';
|
|
14
|
+
import type { OnboardingVerificationItem } from '../runtime/onboarding/index.ts';
|
|
15
|
+
import type { InputHandler } from './handler.ts';
|
|
16
|
+
import type { OnboardingWizardAction, OnboardingWizardApplyFeedback } from './onboarding/onboarding-wizard.ts';
|
|
17
|
+
import {
|
|
18
|
+
buildCloudflareApiTokenRef,
|
|
19
|
+
buildCloudflareProvisionRequest,
|
|
20
|
+
getCloudflareBatchMode,
|
|
21
|
+
getCloudflareComponentSelection,
|
|
22
|
+
getCloudflareSetupSource,
|
|
23
|
+
shouldShowCloudflareStep,
|
|
24
|
+
} from './onboarding/onboarding-wizard-cloudflare.ts';
|
|
25
|
+
|
|
26
|
+
type CloudflareOnboardingAction = Extract<OnboardingWizardAction,
|
|
27
|
+
| 'cloudflare-token-requirements'
|
|
28
|
+
| 'cloudflare-create-operational-token'
|
|
29
|
+
| 'cloudflare-discover'
|
|
30
|
+
| 'cloudflare-validate'
|
|
31
|
+
| 'cloudflare-provision'
|
|
32
|
+
| 'cloudflare-verify'
|
|
33
|
+
| 'cloudflare-disable'
|
|
34
|
+
>;
|
|
35
|
+
|
|
36
|
+
function getCloudflareDaemonClientForHandler(handler: InputHandler): CloudflareDaemonClient {
|
|
37
|
+
return createCloudflareDaemonClient({
|
|
38
|
+
configManager: handler.uiServices.platform.configManager,
|
|
39
|
+
homeDirectory: handler.uiServices.environment.homeDirectory,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function normalizeCloudflareActionError(error: unknown): string {
|
|
44
|
+
if (error instanceof CloudflareDaemonRouteError) {
|
|
45
|
+
return `${error.message} (HTTP ${error.status}, ${error.code})`;
|
|
46
|
+
}
|
|
47
|
+
return error instanceof Error ? error.message : String(error);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function setCloudflareWizardStatusForHandler(
|
|
51
|
+
handler: InputHandler,
|
|
52
|
+
title: string,
|
|
53
|
+
lines: readonly string[],
|
|
54
|
+
severity: OnboardingWizardApplyFeedback['severity'] = 'info',
|
|
55
|
+
): void {
|
|
56
|
+
const message = [title, ...lines].filter((line) => line.length > 0).join('\n');
|
|
57
|
+
handler.onboardingWizard.textState.set('cloudflare.action-status', message);
|
|
58
|
+
handler.onboardingWizard.setApplyFeedback({
|
|
59
|
+
severity,
|
|
60
|
+
title,
|
|
61
|
+
summary: lines[0] ?? title,
|
|
62
|
+
messages: lines.length > 0 ? lines : [title],
|
|
63
|
+
});
|
|
64
|
+
const targetIndex = handler.onboardingWizard.steps.findIndex((step) => step.id === 'cloudflare');
|
|
65
|
+
if (targetIndex >= 0) handler.onboardingWizard.setStep(targetIndex);
|
|
66
|
+
handler.commandContext?.print?.(message);
|
|
67
|
+
handler.requestRender();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function formatCloudflareComponents(components: CloudflareComponentSelection): string {
|
|
71
|
+
const enabled = Object.entries(components)
|
|
72
|
+
.filter(([, selected]) => selected === true)
|
|
73
|
+
.map(([component]) => component);
|
|
74
|
+
return enabled.length > 0 ? enabled.join(', ') : 'none';
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function formatCloudflareRequirements(result: CloudflareTokenRequirementsResult): string[] {
|
|
78
|
+
const permissionLines = result.permissions.length > 0
|
|
79
|
+
? result.permissions.map((permission) => ` ${permission.scope}: ${permission.permission} (${permission.component}) - ${permission.reason}`)
|
|
80
|
+
: [' No permissions returned for the selected components.'];
|
|
81
|
+
return [
|
|
82
|
+
`components: ${formatCloudflareComponents(result.components)}`,
|
|
83
|
+
'required permissions:',
|
|
84
|
+
...permissionLines,
|
|
85
|
+
...(result.bootstrapToken.instructions.length > 0
|
|
86
|
+
? ['', 'bootstrap token instructions:', ...result.bootstrapToken.instructions.map((line) => ` ${line}`)]
|
|
87
|
+
: []),
|
|
88
|
+
];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function formatCloudflareValidation(result: CloudflareValidateResult): string[] {
|
|
92
|
+
return [
|
|
93
|
+
`token: ${result.ok ? 'valid' : 'not valid'}`,
|
|
94
|
+
`source: ${result.tokenSource}`,
|
|
95
|
+
result.account
|
|
96
|
+
? `account: ${result.account.name} (${result.account.id})`
|
|
97
|
+
: 'account: not resolved',
|
|
98
|
+
];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function formatCloudflareDiscovery(result: CloudflareDiscoverResult): string[] {
|
|
102
|
+
return [
|
|
103
|
+
`token source: ${result.tokenSource}`,
|
|
104
|
+
`accounts: ${result.accounts.length}`,
|
|
105
|
+
`zones: ${result.zones.length}`,
|
|
106
|
+
`worker subdomain: ${result.workerSubdomain || 'not detected'}`,
|
|
107
|
+
`queues: ${result.queues?.length ?? 0}`,
|
|
108
|
+
`KV namespaces: ${result.kvNamespaces?.length ?? 0}`,
|
|
109
|
+
`R2 buckets: ${result.r2Buckets?.length ?? 0}`,
|
|
110
|
+
...(result.selectedAccount ? [`selected account: ${result.selectedAccount.name} (${result.selectedAccount.id})`] : []),
|
|
111
|
+
...(result.selectedZone ? [`selected zone: ${result.selectedZone.name} (${result.selectedZone.id})`] : []),
|
|
112
|
+
...result.warnings.map((warning) => `warning: ${warning}`),
|
|
113
|
+
];
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function formatCloudflareTokenCreate(result: CloudflareOperationalTokenResult): string[] {
|
|
117
|
+
return [
|
|
118
|
+
`token: ${result.tokenName}${result.tokenId ? ` (${result.tokenId})` : ''}`,
|
|
119
|
+
`account: ${result.accountId}`,
|
|
120
|
+
`stored ref: ${result.apiTokenRef ?? 'not stored'}`,
|
|
121
|
+
`permissions: ${result.permissions.length}`,
|
|
122
|
+
'Delete or expire the temporary bootstrap token in Cloudflare after confirming the operational token works.',
|
|
123
|
+
];
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function formatCloudflareProvision(result: CloudflareProvisionResult): string[] {
|
|
127
|
+
return [
|
|
128
|
+
`result: ${result.ok ? 'ok' : 'needs attention'}`,
|
|
129
|
+
...(result.worker ? [`worker: ${result.worker.name}${result.worker.baseUrl ? ` at ${result.worker.baseUrl}` : ''}`] : []),
|
|
130
|
+
...(result.queues ? [`queue: ${result.queues.queueName}; DLQ: ${result.queues.deadLetterQueueName}`] : []),
|
|
131
|
+
...result.steps.map((step) => `${step.status}: ${step.name}${step.message ? ` - ${step.message}` : ''}`),
|
|
132
|
+
...(result.verification ? formatCloudflareVerify(result.verification).map((line) => `verify ${line}`) : []),
|
|
133
|
+
];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function formatCloudflareVerify(result: CloudflareVerifyResult): string[] {
|
|
137
|
+
return [
|
|
138
|
+
`worker health: ${result.workerHealth.ok ? 'ok' : 'failed'} (HTTP ${result.workerHealth.status})${result.workerHealth.error ? ` - ${result.workerHealth.error}` : ''}`,
|
|
139
|
+
...(result.daemonBatchProxy
|
|
140
|
+
? [`daemon batch proxy: ${result.daemonBatchProxy.ok ? 'ok' : 'failed'} (HTTP ${result.daemonBatchProxy.status})${result.daemonBatchProxy.error ? ` - ${result.daemonBatchProxy.error}` : ''}`]
|
|
141
|
+
: []),
|
|
142
|
+
];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function getCloudflareBootstrapTokenFromWizard(handler: InputHandler): string {
|
|
146
|
+
const wizard = handler.onboardingWizard;
|
|
147
|
+
const setupSource = getCloudflareSetupSource(wizard);
|
|
148
|
+
if (setupSource === 'bootstrap-token') {
|
|
149
|
+
return wizard.getStringFieldValue('cloudflare.bootstrap-token', '');
|
|
150
|
+
}
|
|
151
|
+
if (setupSource === 'bootstrap-env') {
|
|
152
|
+
const envName = wizard.getStringFieldValue('cloudflare.bootstrap-env-name', 'GOODVIBES_CLOUDFLARE_BOOTSTRAP_TOKEN');
|
|
153
|
+
return process.env[envName] ?? '';
|
|
154
|
+
}
|
|
155
|
+
return '';
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function getCloudflareOperationalTokenFromWizard(handler: InputHandler): string {
|
|
159
|
+
const wizard = handler.onboardingWizard;
|
|
160
|
+
return getCloudflareSetupSource(wizard) === 'operational-token'
|
|
161
|
+
? wizard.getStringFieldValue('cloudflare.operational-token', '')
|
|
162
|
+
: '';
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function getCloudflareApiTokenRefFromWizard(handler: InputHandler): string {
|
|
166
|
+
const wizard = handler.onboardingWizard;
|
|
167
|
+
const setupSource = getCloudflareSetupSource(wizard);
|
|
168
|
+
if (setupSource === 'operational-env') {
|
|
169
|
+
return buildCloudflareApiTokenRef(wizard.getStringFieldValue('cloudflare.operational-env-name', 'CLOUDFLARE_API_TOKEN'));
|
|
170
|
+
}
|
|
171
|
+
return wizard.runtimeSnapshot?.config.cloudflare.apiTokenRef ?? '';
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async function createCloudflareOperationalTokenForHandler(handler: InputHandler): Promise<CloudflareOperationalTokenResult> {
|
|
175
|
+
const wizard = handler.onboardingWizard;
|
|
176
|
+
const bootstrapToken = getCloudflareBootstrapTokenFromWizard(handler);
|
|
177
|
+
if (!bootstrapToken) {
|
|
178
|
+
throw new Error('A bootstrap token is required. Paste it in the wizard or select an environment variable that is set in this TUI process.');
|
|
179
|
+
}
|
|
180
|
+
const accountId = wizard.getStringFieldValue('cloudflare.account-id', wizard.runtimeSnapshot?.config.cloudflare.accountId ?? '');
|
|
181
|
+
const zoneId = wizard.getStringFieldValue('cloudflare.zone-id', wizard.runtimeSnapshot?.config.cloudflare.zoneId ?? '');
|
|
182
|
+
const zoneName = wizard.getStringFieldValue('cloudflare.zone-name', wizard.runtimeSnapshot?.config.cloudflare.zoneName ?? '');
|
|
183
|
+
return await getCloudflareDaemonClientForHandler(handler).createOperationalToken({
|
|
184
|
+
components: getCloudflareComponentSelection(wizard),
|
|
185
|
+
bootstrapToken,
|
|
186
|
+
...(accountId ? { accountId } : {}),
|
|
187
|
+
...(zoneId ? { zoneId } : {}),
|
|
188
|
+
...(zoneName ? { zoneName } : {}),
|
|
189
|
+
storeApiToken: true,
|
|
190
|
+
persistConfig: true,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async function buildCloudflareProvisionInputForHandler(handler: InputHandler): Promise<CloudflareProvisionRequest> {
|
|
195
|
+
const input = buildCloudflareProvisionRequest(handler.onboardingWizard, { includeTransientSecrets: true });
|
|
196
|
+
const setupSource = getCloudflareSetupSource(handler.onboardingWizard);
|
|
197
|
+
if (setupSource === 'bootstrap-token' || setupSource === 'bootstrap-env') {
|
|
198
|
+
const tokenResult = await createCloudflareOperationalTokenForHandler(handler);
|
|
199
|
+
if (tokenResult.apiTokenRef) {
|
|
200
|
+
const withoutInlineToken = { ...input };
|
|
201
|
+
delete withoutInlineToken.apiToken;
|
|
202
|
+
return { ...withoutInlineToken, apiTokenRef: tokenResult.apiTokenRef };
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return input;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function buildCloudflareDiscoveryInputForHandler(handler: InputHandler): Parameters<CloudflareDaemonClient['discover']>[0] {
|
|
209
|
+
const wizard = handler.onboardingWizard;
|
|
210
|
+
const accountId = wizard.getStringFieldValue('cloudflare.account-id', wizard.runtimeSnapshot?.config.cloudflare.accountId ?? '');
|
|
211
|
+
const zoneId = wizard.getStringFieldValue('cloudflare.zone-id', wizard.runtimeSnapshot?.config.cloudflare.zoneId ?? '');
|
|
212
|
+
const zoneName = wizard.getStringFieldValue('cloudflare.zone-name', wizard.runtimeSnapshot?.config.cloudflare.zoneName ?? '');
|
|
213
|
+
const bootstrapToken = getCloudflareBootstrapTokenFromWizard(handler);
|
|
214
|
+
const apiToken = getCloudflareOperationalTokenFromWizard(handler) || bootstrapToken;
|
|
215
|
+
const apiTokenRef = getCloudflareApiTokenRefFromWizard(handler);
|
|
216
|
+
return {
|
|
217
|
+
components: getCloudflareComponentSelection(wizard),
|
|
218
|
+
includeResources: true,
|
|
219
|
+
...(accountId ? { accountId } : {}),
|
|
220
|
+
...(zoneId ? { zoneId } : {}),
|
|
221
|
+
...(zoneName ? { zoneName } : {}),
|
|
222
|
+
...(apiToken ? { apiToken } : apiTokenRef ? { apiTokenRef } : {}),
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function buildCloudflareValidateInputForHandler(handler: InputHandler): Parameters<CloudflareDaemonClient['validate']>[0] {
|
|
227
|
+
const wizard = handler.onboardingWizard;
|
|
228
|
+
const accountId = wizard.getStringFieldValue('cloudflare.account-id', wizard.runtimeSnapshot?.config.cloudflare.accountId ?? '');
|
|
229
|
+
const bootstrapToken = getCloudflareBootstrapTokenFromWizard(handler);
|
|
230
|
+
const apiToken = getCloudflareOperationalTokenFromWizard(handler) || bootstrapToken;
|
|
231
|
+
const apiTokenRef = getCloudflareApiTokenRefFromWizard(handler);
|
|
232
|
+
return {
|
|
233
|
+
...(accountId ? { accountId } : {}),
|
|
234
|
+
...(apiToken ? { apiToken } : apiTokenRef ? { apiTokenRef } : {}),
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export async function handleCloudflareOnboardingActionForHandler(
|
|
239
|
+
handler: InputHandler,
|
|
240
|
+
action: CloudflareOnboardingAction,
|
|
241
|
+
): Promise<void> {
|
|
242
|
+
if (handler.onboardingApplyPending) return;
|
|
243
|
+
handler.onboardingApplyPending = true;
|
|
244
|
+
handler.onboardingWizard.clearApplyFeedback();
|
|
245
|
+
handler.requestRender();
|
|
246
|
+
try {
|
|
247
|
+
const client = getCloudflareDaemonClientForHandler(handler);
|
|
248
|
+
if (action === 'cloudflare-token-requirements') {
|
|
249
|
+
const result = await client.tokenRequirements({
|
|
250
|
+
components: getCloudflareComponentSelection(handler.onboardingWizard),
|
|
251
|
+
includeBootstrap: true,
|
|
252
|
+
});
|
|
253
|
+
setCloudflareWizardStatusForHandler(handler, 'Cloudflare token requirements', formatCloudflareRequirements(result));
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (action === 'cloudflare-create-operational-token') {
|
|
258
|
+
const result = await createCloudflareOperationalTokenForHandler(handler);
|
|
259
|
+
setCloudflareWizardStatusForHandler(handler, 'Cloudflare operational token created', formatCloudflareTokenCreate(result));
|
|
260
|
+
await handler.refreshOnboardingHydration({ preserveValues: true, targetStepId: 'cloudflare' });
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (action === 'cloudflare-discover') {
|
|
265
|
+
const result = await client.discover(buildCloudflareDiscoveryInputForHandler(handler));
|
|
266
|
+
if (result.selectedAccount && !handler.onboardingWizard.getStringFieldValue('cloudflare.account-id', '')) {
|
|
267
|
+
handler.onboardingWizard.setFieldValue('cloudflare.account-id', result.selectedAccount.id);
|
|
268
|
+
} else if (result.accounts.length === 1 && !handler.onboardingWizard.getStringFieldValue('cloudflare.account-id', '')) {
|
|
269
|
+
handler.onboardingWizard.setFieldValue('cloudflare.account-id', result.accounts[0]!.id);
|
|
270
|
+
}
|
|
271
|
+
if (result.selectedZone && !handler.onboardingWizard.getStringFieldValue('cloudflare.zone-id', '')) {
|
|
272
|
+
handler.onboardingWizard.setFieldValue('cloudflare.zone-id', result.selectedZone.id);
|
|
273
|
+
handler.onboardingWizard.setFieldValue('cloudflare.zone-name', result.selectedZone.name);
|
|
274
|
+
} else if (result.zones.length === 1 && !handler.onboardingWizard.getStringFieldValue('cloudflare.zone-id', '')) {
|
|
275
|
+
handler.onboardingWizard.setFieldValue('cloudflare.zone-id', result.zones[0]!.id);
|
|
276
|
+
handler.onboardingWizard.setFieldValue('cloudflare.zone-name', result.zones[0]!.name);
|
|
277
|
+
}
|
|
278
|
+
if (result.workerSubdomain && !handler.onboardingWizard.getStringFieldValue('cloudflare.worker-subdomain', '')) {
|
|
279
|
+
handler.onboardingWizard.setFieldValue('cloudflare.worker-subdomain', result.workerSubdomain);
|
|
280
|
+
}
|
|
281
|
+
setCloudflareWizardStatusForHandler(handler, 'Cloudflare discovery completed', formatCloudflareDiscovery(result));
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (action === 'cloudflare-validate') {
|
|
286
|
+
const result = await client.validate(buildCloudflareValidateInputForHandler(handler));
|
|
287
|
+
setCloudflareWizardStatusForHandler(
|
|
288
|
+
handler,
|
|
289
|
+
result.ok ? 'Cloudflare token validated' : 'Cloudflare token validation needs attention',
|
|
290
|
+
formatCloudflareValidation(result),
|
|
291
|
+
result.ok ? 'info' : 'warning',
|
|
292
|
+
);
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (action === 'cloudflare-provision') {
|
|
297
|
+
const input = await buildCloudflareProvisionInputForHandler(handler);
|
|
298
|
+
const result = await client.provision(input);
|
|
299
|
+
setCloudflareWizardStatusForHandler(
|
|
300
|
+
handler,
|
|
301
|
+
result.ok ? 'Cloudflare provisioning completed' : 'Cloudflare provisioning needs attention',
|
|
302
|
+
formatCloudflareProvision(result),
|
|
303
|
+
result.ok ? 'info' : 'warning',
|
|
304
|
+
);
|
|
305
|
+
await handler.refreshOnboardingHydration({ preserveValues: true, targetStepId: 'cloudflare' });
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (action === 'cloudflare-verify') {
|
|
310
|
+
const result = await client.verify({
|
|
311
|
+
workerBaseUrl: handler.onboardingWizard.getStringFieldValue('cloudflare.worker-base-url', handler.onboardingWizard.runtimeSnapshot?.config.cloudflare.workerBaseUrl ?? ''),
|
|
312
|
+
workerClientTokenRef: handler.onboardingWizard.runtimeSnapshot?.config.cloudflare.workerClientTokenRef ?? '',
|
|
313
|
+
});
|
|
314
|
+
setCloudflareWizardStatusForHandler(
|
|
315
|
+
handler,
|
|
316
|
+
result.ok ? 'Cloudflare Worker verified' : 'Cloudflare Worker verification needs attention',
|
|
317
|
+
formatCloudflareVerify(result),
|
|
318
|
+
result.ok ? 'info' : 'warning',
|
|
319
|
+
);
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (action === 'cloudflare-disable') {
|
|
324
|
+
const result = await client.disable({
|
|
325
|
+
accountId: handler.onboardingWizard.getStringFieldValue('cloudflare.account-id', handler.onboardingWizard.runtimeSnapshot?.config.cloudflare.accountId ?? ''),
|
|
326
|
+
apiTokenRef: getCloudflareApiTokenRefFromWizard(handler),
|
|
327
|
+
workerName: handler.onboardingWizard.getStringFieldValue('cloudflare.worker-name', handler.onboardingWizard.runtimeSnapshot?.config.cloudflare.workerName ?? 'goodvibes-batch-worker'),
|
|
328
|
+
persistConfig: true,
|
|
329
|
+
});
|
|
330
|
+
setCloudflareWizardStatusForHandler(
|
|
331
|
+
handler,
|
|
332
|
+
result.ok ? 'Cloudflare integration disabled' : 'Cloudflare disable needs attention',
|
|
333
|
+
result.steps.map((step) => `${step.status}: ${step.name}${step.message ? ` - ${step.message}` : ''}`),
|
|
334
|
+
result.ok ? 'info' : 'warning',
|
|
335
|
+
);
|
|
336
|
+
await handler.refreshOnboardingHydration({ preserveValues: true, targetStepId: 'cloudflare' });
|
|
337
|
+
}
|
|
338
|
+
} catch (error) {
|
|
339
|
+
setCloudflareWizardStatusForHandler(handler, 'Cloudflare action failed', [normalizeCloudflareActionError(error)], 'error');
|
|
340
|
+
} finally {
|
|
341
|
+
handler.onboardingApplyPending = false;
|
|
342
|
+
handler.requestRender();
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
export async function maybeProvisionCloudflareOnFinalApplyForHandler(handler: InputHandler): Promise<readonly OnboardingVerificationItem[]> {
|
|
347
|
+
const wizard = handler.onboardingWizard;
|
|
348
|
+
if (!shouldShowCloudflareStep(wizard)) return [];
|
|
349
|
+
const cloudflareEnabled = wizard.getBooleanFieldValue('cloudflare.enabled', wizard.isCapabilitySelected('cloudflare-batch') || wizard.runtimeSnapshot?.config.cloudflare.enabled === true);
|
|
350
|
+
if (!cloudflareEnabled) {
|
|
351
|
+
return [{
|
|
352
|
+
id: 'cloudflare:disabled',
|
|
353
|
+
status: 'pass',
|
|
354
|
+
message: 'Cloudflare integration is disabled; local daemon behavior remains active.',
|
|
355
|
+
target: 'cloudflare',
|
|
356
|
+
}];
|
|
357
|
+
}
|
|
358
|
+
const provisionOnApply = wizard.getStringFieldValue('cloudflare.provision-on-apply', 'no') === 'yes';
|
|
359
|
+
if (!provisionOnApply) {
|
|
360
|
+
return [{
|
|
361
|
+
id: 'cloudflare:configuration-saved',
|
|
362
|
+
status: 'pass',
|
|
363
|
+
message: `Cloudflare settings were saved. Batch mode is ${getCloudflareBatchMode(wizard)}; provisioning was not requested on final apply.`,
|
|
364
|
+
target: 'cloudflare',
|
|
365
|
+
}];
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
try {
|
|
369
|
+
const client = getCloudflareDaemonClientForHandler(handler);
|
|
370
|
+
const result = await client.provision(await buildCloudflareProvisionInputForHandler(handler));
|
|
371
|
+
handler.onboardingWizard.textState.set('cloudflare.action-status', [
|
|
372
|
+
result.ok ? 'Cloudflare provisioning completed during final apply.' : 'Cloudflare provisioning needs attention after final apply.',
|
|
373
|
+
...formatCloudflareProvision(result),
|
|
374
|
+
].join('\n'));
|
|
375
|
+
return [{
|
|
376
|
+
id: 'cloudflare:provision',
|
|
377
|
+
status: result.ok ? 'pass' : 'warn',
|
|
378
|
+
message: result.ok
|
|
379
|
+
? 'Cloudflare resources were provisioned and verified through the daemon SDK route.'
|
|
380
|
+
: 'Cloudflare provisioning returned warnings or failed verification. Settings were saved; rerun the Cloudflare wizard action after correcting token/resource issues.',
|
|
381
|
+
target: 'cloudflare',
|
|
382
|
+
}];
|
|
383
|
+
} catch (error) {
|
|
384
|
+
return [{
|
|
385
|
+
id: 'cloudflare:provision',
|
|
386
|
+
status: 'warn',
|
|
387
|
+
message: `Cloudflare provisioning did not complete: ${normalizeCloudflareActionError(error)} Settings were saved; retry from the Cloudflare wizard or /cloudflare command.`,
|
|
388
|
+
target: 'cloudflare',
|
|
389
|
+
}];
|
|
390
|
+
}
|
|
391
|
+
}
|
|
@@ -3,6 +3,7 @@ import { beginOpenAICodexLogin, exchangeOpenAICodexCode } from '@pellux/goodvibe
|
|
|
3
3
|
import { openExternalUrl } from '@pellux/goodvibes-sdk/platform/utils/open-external';
|
|
4
4
|
import { buildProviderAccountSnapshot } from '@pellux/goodvibes-sdk/platform/runtime/provider-accounts/registry';
|
|
5
5
|
import { OnboardingWizardController, type OnboardingWizardAction, type OnboardingWizardApplyFeedback } from './onboarding/onboarding-wizard.ts';
|
|
6
|
+
import { handleCloudflareOnboardingActionForHandler, maybeProvisionCloudflareOnFinalApplyForHandler } from './handler-onboarding-cloudflare.ts';
|
|
6
7
|
import { applyOnboardingRequest, collectOnboardingSnapshot, verifyOnboardingRequest } from '../runtime/onboarding/index.ts';
|
|
7
8
|
import type { OnboardingApplyRequest, OnboardingVerificationItem } from '../runtime/onboarding/index.ts';
|
|
8
9
|
import type { ModelPickerTarget } from './model-picker.ts';
|
|
@@ -186,6 +187,24 @@ export function openModelPickerWithTargetForHandler(
|
|
|
186
187
|
return true;
|
|
187
188
|
}
|
|
188
189
|
|
|
190
|
+
export function openProviderModelPickerWithTargetForHandler(
|
|
191
|
+
handler: InputHandler,
|
|
192
|
+
target: ModelPickerTarget,
|
|
193
|
+
source: 'settings' | 'onboarding' = 'settings',
|
|
194
|
+
): boolean {
|
|
195
|
+
const openProviderPicker = handler.commandContext?.openProviderPicker;
|
|
196
|
+
if (!openProviderPicker) return false;
|
|
197
|
+
if (source === 'onboarding' && handler.onboardingWizard.active) {
|
|
198
|
+
handler.onboardingModelPickerCancelSnapshot = captureOnboardingWizardSnapshot(handler.onboardingWizard);
|
|
199
|
+
} else {
|
|
200
|
+
handler.clearOnboardingModelPickerCancelState();
|
|
201
|
+
}
|
|
202
|
+
handler.clearOnboardingPendingModelPickerTarget();
|
|
203
|
+
handler.modelPicker.target = target;
|
|
204
|
+
openProviderPicker();
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
207
|
+
|
|
189
208
|
export function handleModelPickerCommitForHandler(handler: InputHandler): boolean {
|
|
190
209
|
if (handler.onboardingModelPickerCancelSnapshot && handler.onboardingWizard.active) {
|
|
191
210
|
const selected = handler.modelPicker.mode === 'effort'
|
|
@@ -227,6 +246,18 @@ export async function handleOnboardingActionForHandler(handler: InputHandler, ac
|
|
|
227
246
|
continueOnboardingSection(handler);
|
|
228
247
|
return;
|
|
229
248
|
}
|
|
249
|
+
if (action.startsWith('cloudflare-')) {
|
|
250
|
+
await handleCloudflareOnboardingActionForHandler(handler, action as Extract<OnboardingWizardAction,
|
|
251
|
+
| 'cloudflare-token-requirements'
|
|
252
|
+
| 'cloudflare-create-operational-token'
|
|
253
|
+
| 'cloudflare-discover'
|
|
254
|
+
| 'cloudflare-validate'
|
|
255
|
+
| 'cloudflare-provision'
|
|
256
|
+
| 'cloudflare-verify'
|
|
257
|
+
| 'cloudflare-disable'
|
|
258
|
+
>);
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
230
261
|
if (action !== 'apply') return;
|
|
231
262
|
if (handler.onboardingApplyPending) return;
|
|
232
263
|
const blockers = handler.onboardingWizard.getBlockingFieldLabels();
|
|
@@ -272,6 +303,8 @@ export async function handleOnboardingActionForHandler(handler: InputHandler, ac
|
|
|
272
303
|
? { ...item, status: 'warn' }
|
|
273
304
|
: item));
|
|
274
305
|
verificationItems = dedupeOnboardingVerificationItems([...verificationItems, ...runtimeWarnings]);
|
|
306
|
+
const cloudflareItems = await maybeProvisionCloudflareOnFinalApplyForHandler(handler);
|
|
307
|
+
verificationItems = dedupeOnboardingVerificationItems([...verificationItems, ...cloudflareItems]);
|
|
275
308
|
}
|
|
276
309
|
} catch (error) {
|
|
277
310
|
showOnboardingApplyFeedbackForHandler(handler, {
|
|
@@ -55,7 +55,7 @@ export function handleModelPickerToken(state: ModelPickerRouteState, token: Inpu
|
|
|
55
55
|
const selected = state.modelPicker.getSelected();
|
|
56
56
|
if (selected) {
|
|
57
57
|
const currentEffort = state.commandContext?.session.runtime.reasoningEffort ?? 'medium';
|
|
58
|
-
if (selected.reasoningEffort && selected.reasoningEffort.length > 0) {
|
|
58
|
+
if (state.modelPicker.target === 'main' && selected.reasoningEffort && selected.reasoningEffort.length > 0) {
|
|
59
59
|
state.modelPicker.showEffortPicker(selected, currentEffort);
|
|
60
60
|
} else {
|
|
61
61
|
const target = state.modelPicker.target;
|
package/src/input/handler.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { dirname } from 'node:path';
|
|
|
3
3
|
import { InputTokenizer } from '@pellux/goodvibes-sdk/platform/core/tokenizer';
|
|
4
4
|
import { createOAuthLocalListener } from '@pellux/goodvibes-sdk/platform/config/oauth-local-listener';
|
|
5
5
|
import { clearModalStackForHandler, cleanupMarkerRegistryForHandler, executeBlockActionForHandler, expandPromptForHandler, findMarkerAtPosForHandler, getImageAttachmentsForHandler, handleBlockCopyForHandler, handleBlockRerunForHandler, handleBlockSaveForHandler, handleBlockToggleForHandler, handleBookmarkForHandler, handleCopyForHandler, handleCtrlCForHandler, handleDiffApplyForHandler, handleEscapeForHandler, hydrateOnboardingWizardFromRuntimeForHandler, modalOpenedForHandler, openOnboardingWizardForHandler, registerPasteForHandler } from './handler-interactions.ts';
|
|
6
|
-
import { clearOnboardingModelPickerCancelStateForHandler, clearOnboardingPendingModelPickerTargetForHandler, completeOpenAiSubscriptionFromListenerForHandler, getOnboardingConfigValueForHandler, getOnboardingRuntimePostureForHandler, handleModelPickerCommitForHandler, handleOnboardingActionForHandler, handleOpenAiSubscriptionFinishForHandler, handleOpenAiSubscriptionStartForHandler, openModelPickerWithTargetForHandler, refreshOnboardingHydrationForHandler, restartOnboardingExternalServicesIfNeededForHandler, restoreOnboardingModelPickerCancelStateForHandler, syncRuntimeFromOnboardingRequestForHandler, verifyOnboardingRuntimePostureForHandler, type OnboardingRuntimePosture } from './handler-onboarding.ts';
|
|
6
|
+
import { clearOnboardingModelPickerCancelStateForHandler, clearOnboardingPendingModelPickerTargetForHandler, completeOpenAiSubscriptionFromListenerForHandler, getOnboardingConfigValueForHandler, getOnboardingRuntimePostureForHandler, handleModelPickerCommitForHandler, handleOnboardingActionForHandler, handleOpenAiSubscriptionFinishForHandler, handleOpenAiSubscriptionStartForHandler, openModelPickerWithTargetForHandler, openProviderModelPickerWithTargetForHandler, refreshOnboardingHydrationForHandler, restartOnboardingExternalServicesIfNeededForHandler, restoreOnboardingModelPickerCancelStateForHandler, syncRuntimeFromOnboardingRequestForHandler, verifyOnboardingRuntimePostureForHandler, type OnboardingRuntimePosture } from './handler-onboarding.ts';
|
|
7
7
|
import { beginOpenAICodexLogin, exchangeOpenAICodexCode } from '@pellux/goodvibes-sdk/platform/config/openai-codex-auth';
|
|
8
8
|
import { openExternalUrl } from '@pellux/goodvibes-sdk/platform/utils/open-external';
|
|
9
9
|
import { buildProviderAccountSnapshot } from '@pellux/goodvibes-sdk/platform/runtime/provider-accounts/registry';
|
|
@@ -325,6 +325,8 @@ export class InputHandler {
|
|
|
325
325
|
expandPrompt: (text: string) => this.expandPrompt(text),
|
|
326
326
|
openModelPickerWithTarget: (target: ModelPickerTarget, source?: 'settings' | 'onboarding') =>
|
|
327
327
|
this.openModelPickerWithTarget(target, source),
|
|
328
|
+
openProviderModelPickerWithTarget: (target: ModelPickerTarget, source?: 'settings' | 'onboarding') =>
|
|
329
|
+
this.openProviderModelPickerWithTarget(target, source),
|
|
328
330
|
onModelPickerCommit: () => this.handleModelPickerCommit(),
|
|
329
331
|
onOnboardingAction: (action: OnboardingWizardAction) => { void this.handleOnboardingAction(action); },
|
|
330
332
|
},
|
|
@@ -404,6 +406,7 @@ export class InputHandler {
|
|
|
404
406
|
public clearOnboardingModelPickerCancelState(): void { clearOnboardingModelPickerCancelStateForHandler(this); }
|
|
405
407
|
public restoreOnboardingModelPickerCancelState(): void { restoreOnboardingModelPickerCancelStateForHandler(this); }
|
|
406
408
|
public openModelPickerWithTarget(target: ModelPickerTarget, source: 'settings' | 'onboarding' = 'settings'): boolean { return openModelPickerWithTargetForHandler(this, target, source); }
|
|
409
|
+
public openProviderModelPickerWithTarget(target: ModelPickerTarget, source: 'settings' | 'onboarding' = 'settings'): boolean { return openProviderModelPickerWithTargetForHandler(this, target, source); }
|
|
407
410
|
public handleModelPickerCommit(): boolean { return handleModelPickerCommitForHandler(this); }
|
|
408
411
|
public async handleOnboardingAction(action: OnboardingWizardAction): Promise<void> { await handleOnboardingActionForHandler(this, action); }
|
|
409
412
|
public async refreshOnboardingHydration(options: { readonly preserveValues?: boolean; readonly targetStepId?: string } = {}): Promise<void> { await refreshOnboardingHydrationForHandler(this, options); }
|