@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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pellux/goodvibes-tui",
|
|
3
|
-
"version": "0.19.
|
|
3
|
+
"version": "0.19.35",
|
|
4
4
|
"description": "Terminal-native GoodVibes product for coding, operations, automation, knowledge, channels, and daemon-backed control-plane workflows.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/main.ts",
|
|
@@ -91,7 +91,7 @@
|
|
|
91
91
|
"@anthropic-ai/vertex-sdk": "^0.16.0",
|
|
92
92
|
"@ast-grep/napi": "^0.42.0",
|
|
93
93
|
"@aws/bedrock-token-generator": "^1.1.0",
|
|
94
|
-
"@pellux/goodvibes-sdk": "0.25.
|
|
94
|
+
"@pellux/goodvibes-sdk": "0.25.11",
|
|
95
95
|
"bash-language-server": "^5.6.0",
|
|
96
96
|
"fuse.js": "^7.1.0",
|
|
97
97
|
"graphql": "^16.13.2",
|
package/src/cli/management.ts
CHANGED
|
@@ -601,7 +601,7 @@ async function renderAuth(runtime: CliCommandRuntime): Promise<string> {
|
|
|
601
601
|
}
|
|
602
602
|
if (sub === 'revoke-session') {
|
|
603
603
|
const token = rest[0];
|
|
604
|
-
if (!token) return 'Usage: goodvibes auth revoke-session <token>';
|
|
604
|
+
if (!token) return 'Usage: goodvibes auth revoke-session <token-or-fingerprint>';
|
|
605
605
|
return services.localUserAuthManager.revokeSession(token)
|
|
606
606
|
? 'Auth session revoked.'
|
|
607
607
|
: 'No auth session found.';
|
|
@@ -637,7 +637,7 @@ async function renderAuth(runtime: CliCommandRuntime): Promise<string> {
|
|
|
637
637
|
if (sub === 'sessions') {
|
|
638
638
|
return formatJsonOrText(runtime.cli)(snapshot.sessions, [
|
|
639
639
|
`GoodVibes auth sessions (${snapshot.sessions.length})`,
|
|
640
|
-
...snapshot.sessions.map((session) => ` ${session.username} expires=${new Date(session.expiresAt).toISOString()}
|
|
640
|
+
...snapshot.sessions.map((session) => ` ${session.username} expires=${new Date(session.expiresAt).toISOString()} fingerprint=${session.tokenFingerprint}`),
|
|
641
641
|
].join('\n'));
|
|
642
642
|
}
|
|
643
643
|
return formatJsonOrText(runtime.cli)(value, [
|
|
@@ -80,6 +80,7 @@ export interface CommandShellUiOpeners {
|
|
|
80
80
|
openOnboardingWizard?: (modeOrOptions?: OnboardingWizardMode | OpenOnboardingWizardOptions) => void;
|
|
81
81
|
openModelPicker?: () => void;
|
|
82
82
|
openModelPickerWithTarget?: (target: import('./model-picker.ts').ModelPickerTarget) => boolean;
|
|
83
|
+
openProviderModelPickerWithTarget?: (target: import('./model-picker.ts').ModelPickerTarget) => boolean;
|
|
83
84
|
openProviderPicker?: () => void;
|
|
84
85
|
openContextInspector?: () => void;
|
|
85
86
|
openBookmarkModal?: () => void;
|
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CLOUDFLARE_COMPONENT_IDS,
|
|
3
|
+
CLOUDFLARE_COMPONENT_LABELS,
|
|
4
|
+
DEFAULT_CLOUDFLARE_COMPONENT_SELECTION,
|
|
5
|
+
CloudflareDaemonRouteError,
|
|
6
|
+
createCloudflareDaemonClient,
|
|
7
|
+
type CloudflareComponent,
|
|
8
|
+
type CloudflareComponentSelection,
|
|
9
|
+
type CloudflareDaemonClient,
|
|
10
|
+
type CloudflareProvisionStep,
|
|
11
|
+
} from '../../runtime/cloudflare-control-plane.ts';
|
|
12
|
+
import type { CommandContext, CommandRegistry } from '../command-registry.ts';
|
|
13
|
+
import { requireShellPaths } from './runtime-services.ts';
|
|
14
|
+
|
|
15
|
+
interface ParsedCloudflareArgs {
|
|
16
|
+
readonly positional: readonly string[];
|
|
17
|
+
readonly flags: ReadonlyMap<string, readonly string[]>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function registerCloudflareRuntimeCommands(registry: CommandRegistry): void {
|
|
21
|
+
registry.register({
|
|
22
|
+
name: 'cloudflare',
|
|
23
|
+
aliases: ['cf'],
|
|
24
|
+
description: 'Inspect and manage optional Cloudflare batch/control-plane integration through daemon SDK routes',
|
|
25
|
+
usage: '[status|setup|requirements|create-token|discover|validate|provision|verify|disable] [flags]',
|
|
26
|
+
async handler(args, ctx) {
|
|
27
|
+
const subcommand = (args[0] ?? 'status').toLowerCase();
|
|
28
|
+
const parsed = parseCloudflareArgs(args.slice(1));
|
|
29
|
+
if (subcommand === 'setup' || subcommand === 'onboarding') {
|
|
30
|
+
ctx.openOnboardingWizard?.({ mode: 'edit', reset: true });
|
|
31
|
+
ctx.print('Opening onboarding wizard. Select the Cloudflare batch capability to configure Cloudflare.');
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let client: CloudflareDaemonClient;
|
|
36
|
+
try {
|
|
37
|
+
client = createCloudflareClient(ctx);
|
|
38
|
+
} catch (error) {
|
|
39
|
+
ctx.print(`Cloudflare command unavailable: ${formatCloudflareError(error)}`);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
if (subcommand === 'status' || subcommand === 'show') {
|
|
45
|
+
const status = await client.status();
|
|
46
|
+
ctx.print([
|
|
47
|
+
'Cloudflare Status',
|
|
48
|
+
` enabled: ${status.enabled ? 'yes' : 'no'}`,
|
|
49
|
+
` ready: ${status.ready ? 'yes' : 'no'}`,
|
|
50
|
+
` account: ${status.config.accountId || '(not set)'}`,
|
|
51
|
+
` token ref: ${status.config.apiTokenRef || '(CLOUDFLARE_API_TOKEN fallback)'}`,
|
|
52
|
+
` worker: ${status.config.workerName || '(not set)'}`,
|
|
53
|
+
` worker URL: ${status.config.workerBaseUrl || '(not set)'}`,
|
|
54
|
+
` queue: ${status.config.queueName || '(not set)'}`,
|
|
55
|
+
` DLQ: ${status.config.deadLetterQueueName || '(not set)'}`,
|
|
56
|
+
` tunnel: ${status.config.tunnelName || '(not set)'} ${status.config.tunnelId ? `(${status.config.tunnelId})` : ''}`.trimEnd(),
|
|
57
|
+
` access app: ${status.config.accessAppId || '(not set)'}`,
|
|
58
|
+
` batch mode: ${String(ctx.platform.configManager.get('batch.mode') ?? 'off')}`,
|
|
59
|
+
` queue backend: ${String(ctx.platform.configManager.get('batch.queueBackend') ?? 'local')}`,
|
|
60
|
+
...status.warnings.map((warning) => ` warning: ${warning}`),
|
|
61
|
+
].join('\n'));
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (subcommand === 'requirements') {
|
|
66
|
+
const result = await client.tokenRequirements({
|
|
67
|
+
components: componentsFromArgs(parsed),
|
|
68
|
+
includeBootstrap: true,
|
|
69
|
+
});
|
|
70
|
+
ctx.print([
|
|
71
|
+
'Cloudflare Token Requirements',
|
|
72
|
+
` components: ${formatComponents(result.components)}`,
|
|
73
|
+
' permissions:',
|
|
74
|
+
...result.permissions.map((permission) => ` ${permission.scope}: ${permission.permission} (${permission.component}) - ${permission.reason}`),
|
|
75
|
+
...(result.bootstrapToken.instructions.length > 0
|
|
76
|
+
? [' bootstrap token:', ...result.bootstrapToken.instructions.map((line) => ` ${line}`)]
|
|
77
|
+
: []),
|
|
78
|
+
].join('\n'));
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (subcommand === 'create-token') {
|
|
83
|
+
const bootstrapToken = getFlag(parsed, 'bootstrap-token') || readTokenEnv(parsed, 'bootstrap-env');
|
|
84
|
+
if (!bootstrapToken) {
|
|
85
|
+
ctx.print('Usage: /cloudflare create-token --account <account-id> --bootstrap-token <token> or --bootstrap-env <env-name>');
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const result = await client.createOperationalToken({
|
|
89
|
+
components: componentsFromArgs(parsed),
|
|
90
|
+
...optionalString('accountId', getFlag(parsed, 'account') || getFlag(parsed, 'account-id')),
|
|
91
|
+
...optionalString('zoneId', getFlag(parsed, 'zone-id')),
|
|
92
|
+
...optionalString('zoneName', getFlag(parsed, 'zone') || getFlag(parsed, 'zone-name')),
|
|
93
|
+
bootstrapToken,
|
|
94
|
+
storeApiToken: true,
|
|
95
|
+
persistConfig: true,
|
|
96
|
+
});
|
|
97
|
+
ctx.print([
|
|
98
|
+
'Cloudflare Operational Token Created',
|
|
99
|
+
` token: ${result.tokenName}${result.tokenId ? ` (${result.tokenId})` : ''}`,
|
|
100
|
+
` account: ${result.accountId}`,
|
|
101
|
+
` zone: ${result.zoneId || '(none)'}`,
|
|
102
|
+
` stored ref: ${result.apiTokenRef ?? '(not stored)'}`,
|
|
103
|
+
' revoke or expire the temporary bootstrap token after validation.',
|
|
104
|
+
].join('\n'));
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (subcommand === 'discover') {
|
|
109
|
+
const result = await client.discover({
|
|
110
|
+
...cloudflareAuthInput(parsed),
|
|
111
|
+
components: componentsFromArgs(parsed),
|
|
112
|
+
...optionalString('zoneId', getFlag(parsed, 'zone-id')),
|
|
113
|
+
...optionalString('zoneName', getFlag(parsed, 'zone') || getFlag(parsed, 'zone-name')),
|
|
114
|
+
includeResources: !hasFlag(parsed, 'fast'),
|
|
115
|
+
});
|
|
116
|
+
ctx.print([
|
|
117
|
+
'Cloudflare Discovery',
|
|
118
|
+
` token source: ${result.tokenSource}`,
|
|
119
|
+
` accounts: ${result.accounts.length}`,
|
|
120
|
+
...result.accounts.slice(0, 12).map((account) => ` account ${account.id}: ${account.name}`),
|
|
121
|
+
` zones: ${result.zones.length}`,
|
|
122
|
+
...result.zones.slice(0, 12).map((zone) => ` zone ${zone.id}: ${zone.name}${zone.status ? ` (${zone.status})` : ''}`),
|
|
123
|
+
` worker subdomain: ${result.workerSubdomain || '(not detected)'}`,
|
|
124
|
+
` queues: ${result.queues?.length ?? 0}`,
|
|
125
|
+
` KV namespaces: ${result.kvNamespaces?.length ?? 0}`,
|
|
126
|
+
` R2 buckets: ${result.r2Buckets?.length ?? 0}`,
|
|
127
|
+
...result.warnings.map((warning) => ` warning: ${warning}`),
|
|
128
|
+
].join('\n'));
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (subcommand === 'validate') {
|
|
133
|
+
const result = await client.validate(cloudflareAuthInput(parsed));
|
|
134
|
+
ctx.print([
|
|
135
|
+
'Cloudflare Token Validation',
|
|
136
|
+
` ok: ${result.ok ? 'yes' : 'no'}`,
|
|
137
|
+
` token source: ${result.tokenSource}`,
|
|
138
|
+
result.account ? ` account: ${result.account.name} (${result.account.id})` : ' account: not resolved',
|
|
139
|
+
].join('\n'));
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (subcommand === 'provision') {
|
|
144
|
+
const result = await client.provision({
|
|
145
|
+
...cloudflareAuthInput(parsed),
|
|
146
|
+
components: componentsFromArgs(parsed),
|
|
147
|
+
...optionalString('accountId', getFlag(parsed, 'account') || getFlag(parsed, 'account-id')),
|
|
148
|
+
...optionalString('zoneId', getFlag(parsed, 'zone-id')),
|
|
149
|
+
...optionalString('zoneName', getFlag(parsed, 'zone') || getFlag(parsed, 'zone-name')),
|
|
150
|
+
...optionalString('daemonBaseUrl', getFlag(parsed, 'daemon-url')),
|
|
151
|
+
...optionalString('daemonHostname', getFlag(parsed, 'daemon-hostname')),
|
|
152
|
+
...optionalString('workerName', getFlag(parsed, 'worker-name')),
|
|
153
|
+
...optionalString('workerSubdomain', getFlag(parsed, 'worker-subdomain')),
|
|
154
|
+
...optionalString('workerHostname', getFlag(parsed, 'worker-hostname')),
|
|
155
|
+
...optionalString('workerBaseUrl', getFlag(parsed, 'worker-url')),
|
|
156
|
+
...optionalString('queueName', getFlag(parsed, 'queue') || getFlag(parsed, 'queue-name')),
|
|
157
|
+
...optionalString('deadLetterQueueName', getFlag(parsed, 'dlq') || getFlag(parsed, 'dead-letter-queue')),
|
|
158
|
+
...optionalString('tunnelName', getFlag(parsed, 'tunnel-name')),
|
|
159
|
+
...optionalString('tunnelId', getFlag(parsed, 'tunnel-id')),
|
|
160
|
+
...optionalString('tunnelServiceUrl', getFlag(parsed, 'tunnel-service-url')),
|
|
161
|
+
...optionalString('tunnelTokenRef', getFlag(parsed, 'tunnel-token-ref')),
|
|
162
|
+
...optionalString('accessAppId', getFlag(parsed, 'access-app-id')),
|
|
163
|
+
...optionalString('accessServiceTokenId', getFlag(parsed, 'access-service-token-id')),
|
|
164
|
+
...optionalString('accessServiceTokenRef', getFlag(parsed, 'access-service-token-ref')),
|
|
165
|
+
...optionalString('kvNamespaceName', getFlag(parsed, 'kv-namespace-name')),
|
|
166
|
+
...optionalString('kvNamespaceId', getFlag(parsed, 'kv-namespace-id')),
|
|
167
|
+
...optionalString('durableObjectNamespaceName', getFlag(parsed, 'do-namespace-name') || getFlag(parsed, 'durable-object-namespace-name')),
|
|
168
|
+
...optionalString('durableObjectNamespaceId', getFlag(parsed, 'do-namespace-id') || getFlag(parsed, 'durable-object-namespace-id')),
|
|
169
|
+
...optionalString('r2BucketName', getFlag(parsed, 'r2-bucket-name')),
|
|
170
|
+
...optionalString('secretsStoreName', getFlag(parsed, 'secrets-store-name')),
|
|
171
|
+
...optionalString('secretsStoreId', getFlag(parsed, 'secrets-store-id')),
|
|
172
|
+
...optionalString('workerCron', getFlag(parsed, 'worker-cron')),
|
|
173
|
+
...optionalString('operatorToken', getFlag(parsed, 'operator-token')),
|
|
174
|
+
...optionalString('operatorTokenRef', getFlag(parsed, 'operator-token-ref')),
|
|
175
|
+
...optionalString('workerClientToken', getFlag(parsed, 'worker-client-token')),
|
|
176
|
+
...optionalString('workerClientTokenRef', getFlag(parsed, 'worker-client-token-ref')),
|
|
177
|
+
...optionalBatchMode(readBatchMode(parsed)),
|
|
178
|
+
persistConfig: true,
|
|
179
|
+
verify: !hasFlag(parsed, 'no-verify'),
|
|
180
|
+
storeApiToken: !hasFlag(parsed, 'no-store-token'),
|
|
181
|
+
enableWorkersDev: !hasFlag(parsed, 'no-workers-dev'),
|
|
182
|
+
});
|
|
183
|
+
ctx.print([
|
|
184
|
+
'Cloudflare Provisioning',
|
|
185
|
+
` ok: ${result.ok ? 'yes' : 'no'}`,
|
|
186
|
+
...(result.worker ? [` worker: ${result.worker.name}${result.worker.baseUrl ? ` at ${result.worker.baseUrl}` : ''}`] : []),
|
|
187
|
+
...(result.queues ? [` queues: ${result.queues.queueName}; DLQ ${result.queues.deadLetterQueueName}`] : []),
|
|
188
|
+
...(result.tunnel ? [` tunnel: ${result.tunnel.name} (${result.tunnel.id})${result.tunnel.hostname ? ` ${result.tunnel.hostname}` : ''}`] : []),
|
|
189
|
+
...(result.access ? [` access: app=${result.access.appId || '(none)'} serviceToken=${result.access.serviceTokenId || '(none)'}`] : []),
|
|
190
|
+
...(result.dns ? [` dns: ${result.dns.zoneName || result.dns.zoneId} records=${result.dns.records.length}`] : []),
|
|
191
|
+
...(result.kv ? [` kv: ${result.kv.namespaceName} (${result.kv.namespaceId})`] : []),
|
|
192
|
+
...(result.r2 ? [` r2: ${result.r2.bucketName}`] : []),
|
|
193
|
+
...(result.secretsStore ? [` secrets store: ${result.secretsStore.storeName} (${result.secretsStore.storeId})`] : []),
|
|
194
|
+
...formatProvisionSteps(result.steps),
|
|
195
|
+
].join('\n'));
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (subcommand === 'verify') {
|
|
200
|
+
const result = await client.verify({
|
|
201
|
+
...optionalString('workerBaseUrl', getFlag(parsed, 'worker-url')),
|
|
202
|
+
...optionalString('workerClientToken', getFlag(parsed, 'worker-token')),
|
|
203
|
+
...optionalString('workerClientTokenRef', getFlag(parsed, 'worker-token-ref')),
|
|
204
|
+
});
|
|
205
|
+
ctx.print([
|
|
206
|
+
'Cloudflare Verification',
|
|
207
|
+
` ok: ${result.ok ? 'yes' : 'no'}`,
|
|
208
|
+
` worker health: ${result.workerHealth.ok ? 'ok' : 'failed'} (HTTP ${result.workerHealth.status})${result.workerHealth.error ? ` - ${result.workerHealth.error}` : ''}`,
|
|
209
|
+
...(result.daemonBatchProxy ? [` daemon batch proxy: ${result.daemonBatchProxy.ok ? 'ok' : 'failed'} (HTTP ${result.daemonBatchProxy.status})${result.daemonBatchProxy.error ? ` - ${result.daemonBatchProxy.error}` : ''}`] : []),
|
|
210
|
+
].join('\n'));
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (subcommand === 'disable') {
|
|
215
|
+
const result = await client.disable({
|
|
216
|
+
...cloudflareAuthInput(parsed),
|
|
217
|
+
...optionalString('workerName', getFlag(parsed, 'worker-name')),
|
|
218
|
+
disableWorkerSubdomain: hasFlag(parsed, 'disable-worker-subdomain'),
|
|
219
|
+
disableCron: !hasFlag(parsed, 'keep-cron'),
|
|
220
|
+
persistConfig: true,
|
|
221
|
+
});
|
|
222
|
+
ctx.print([
|
|
223
|
+
'Cloudflare Disabled',
|
|
224
|
+
` ok: ${result.ok ? 'yes' : 'no'}`,
|
|
225
|
+
...formatProvisionSteps(result.steps),
|
|
226
|
+
].join('\n'));
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
ctx.print('Usage: /cloudflare [status|setup|requirements|create-token|discover|validate|provision|verify|disable] [flags]');
|
|
231
|
+
} catch (error) {
|
|
232
|
+
ctx.print(`Cloudflare ${subcommand} failed: ${formatCloudflareError(error)}`);
|
|
233
|
+
}
|
|
234
|
+
},
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function createCloudflareClient(ctx: CommandContext): CloudflareDaemonClient {
|
|
239
|
+
const shellPaths = requireShellPaths(ctx);
|
|
240
|
+
return createCloudflareDaemonClient({
|
|
241
|
+
configManager: ctx.platform.configManager,
|
|
242
|
+
homeDirectory: shellPaths.homeDirectory,
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function parseCloudflareArgs(args: readonly string[]): ParsedCloudflareArgs {
|
|
247
|
+
const positional: string[] = [];
|
|
248
|
+
const flags = new Map<string, string[]>();
|
|
249
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
250
|
+
const arg = args[index]!;
|
|
251
|
+
if (!arg.startsWith('--')) {
|
|
252
|
+
positional.push(arg);
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
const raw = arg.slice(2);
|
|
256
|
+
const equalsIndex = raw.indexOf('=');
|
|
257
|
+
if (equalsIndex >= 0) {
|
|
258
|
+
const key = raw.slice(0, equalsIndex);
|
|
259
|
+
const value = raw.slice(equalsIndex + 1);
|
|
260
|
+
flags.set(key, [...(flags.get(key) ?? []), value]);
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
const next = args[index + 1];
|
|
264
|
+
if (next && !next.startsWith('--')) {
|
|
265
|
+
flags.set(raw, [...(flags.get(raw) ?? []), next]);
|
|
266
|
+
index += 1;
|
|
267
|
+
} else {
|
|
268
|
+
flags.set(raw, [...(flags.get(raw) ?? []), 'true']);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return { positional, flags };
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function hasFlag(args: ParsedCloudflareArgs, key: string): boolean {
|
|
275
|
+
return args.flags.has(key);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function getFlag(args: ParsedCloudflareArgs, key: string): string {
|
|
279
|
+
const values = args.flags.get(key);
|
|
280
|
+
const value = values?.[values.length - 1] ?? '';
|
|
281
|
+
return value === 'true' ? '' : value.trim();
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function getFlagValues(args: ParsedCloudflareArgs, key: string): readonly string[] {
|
|
285
|
+
return args.flags.get(key)?.map((value) => value.trim()).filter(Boolean) ?? [];
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function componentsFromArgs(args: ParsedCloudflareArgs): Record<CloudflareComponent, boolean> {
|
|
289
|
+
const components: Record<CloudflareComponent, boolean> = { ...DEFAULT_CLOUDFLARE_COMPONENT_SELECTION };
|
|
290
|
+
if (hasFlag(args, 'all') || hasFlag(args, 'advanced')) {
|
|
291
|
+
for (const component of CLOUDFLARE_COMPONENT_IDS) components[component] = true;
|
|
292
|
+
}
|
|
293
|
+
for (const raw of [...args.positional, ...getFlagValues(args, 'component')]) {
|
|
294
|
+
const normalized = normalizeComponent(raw);
|
|
295
|
+
if (normalized) components[normalized] = true;
|
|
296
|
+
}
|
|
297
|
+
for (const raw of getFlagValues(args, 'no-component')) {
|
|
298
|
+
const normalized = normalizeComponent(raw);
|
|
299
|
+
if (normalized) components[normalized] = false;
|
|
300
|
+
}
|
|
301
|
+
return components;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function normalizeComponent(value: string): CloudflareComponent | null {
|
|
305
|
+
const normalized = value.trim().toLowerCase().replace(/[-_]/g, '');
|
|
306
|
+
for (const component of CLOUDFLARE_COMPONENT_IDS) {
|
|
307
|
+
if (component.toLowerCase() === normalized) return component;
|
|
308
|
+
}
|
|
309
|
+
if (normalized === 'workerscript' || normalized === 'worker') return 'workers';
|
|
310
|
+
if (normalized === 'queue') return 'queues';
|
|
311
|
+
if (normalized === 'tunnel') return 'zeroTrustTunnel';
|
|
312
|
+
if (normalized === 'access') return 'zeroTrustAccess';
|
|
313
|
+
if (normalized === 'domain' || normalized === 'hostname') return 'dns';
|
|
314
|
+
if (normalized === 'do' || normalized === 'durableobject') return 'durableObjects';
|
|
315
|
+
if (normalized === 'secret' || normalized === 'secrets') return 'secretsStore';
|
|
316
|
+
return null;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function formatComponents(components: CloudflareComponentSelection): string {
|
|
320
|
+
const selected = CLOUDFLARE_COMPONENT_IDS
|
|
321
|
+
.filter((component) => components[component] === true)
|
|
322
|
+
.map((component) => CLOUDFLARE_COMPONENT_LABELS[component]);
|
|
323
|
+
return selected.length > 0 ? selected.join(', ') : 'none';
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function cloudflareAuthInput(args: ParsedCloudflareArgs): {
|
|
327
|
+
readonly accountId?: string;
|
|
328
|
+
readonly apiToken?: string;
|
|
329
|
+
readonly apiTokenRef?: string;
|
|
330
|
+
} {
|
|
331
|
+
const token = getFlag(args, 'token') || readTokenEnv(args, 'token-env');
|
|
332
|
+
const tokenRef = getFlag(args, 'token-ref');
|
|
333
|
+
return {
|
|
334
|
+
...optionalString('accountId', getFlag(args, 'account') || getFlag(args, 'account-id')),
|
|
335
|
+
...(token ? { apiToken: token } : tokenRef ? { apiTokenRef: tokenRef } : {}),
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function readTokenEnv(args: ParsedCloudflareArgs, key: string): string {
|
|
340
|
+
const envName = getFlag(args, key);
|
|
341
|
+
if (!envName) return '';
|
|
342
|
+
return process.env[envName] ?? '';
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function readBatchMode(args: ParsedCloudflareArgs): 'off' | 'explicit' | 'eligible-by-default' | undefined {
|
|
346
|
+
const value = getFlag(args, 'batch-mode');
|
|
347
|
+
if (value === 'off' || value === 'explicit' || value === 'eligible-by-default') return value;
|
|
348
|
+
return undefined;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function optionalString<K extends string>(key: K, value: string): Partial<Record<K, string>> {
|
|
352
|
+
return value.trim().length > 0 ? { [key]: value.trim() } as Partial<Record<K, string>> : {};
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function optionalBatchMode(value: ReturnType<typeof readBatchMode>): { readonly batchMode?: 'off' | 'explicit' | 'eligible-by-default' } {
|
|
356
|
+
return value ? { batchMode: value } : {};
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function formatProvisionSteps(steps: readonly CloudflareProvisionStep[]): string[] {
|
|
360
|
+
return steps.length > 0
|
|
361
|
+
? steps.map((step) => ` ${step.status}: ${step.name}${step.message ? ` - ${step.message}` : ''}`)
|
|
362
|
+
: [' no steps returned'];
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function formatCloudflareError(error: unknown): string {
|
|
366
|
+
if (error instanceof CloudflareDaemonRouteError) {
|
|
367
|
+
return `${error.message} (HTTP ${error.status}, ${error.code})`;
|
|
368
|
+
}
|
|
369
|
+
return error instanceof Error ? error.message : String(error);
|
|
370
|
+
}
|
|
@@ -65,10 +65,10 @@ export function handleLocalAuthCommand(args: string[], ctx: CommandContext): voi
|
|
|
65
65
|
if (sub === 'revoke-session') {
|
|
66
66
|
const token = args[1];
|
|
67
67
|
if (!token) {
|
|
68
|
-
ctx.print('Usage: /auth local revoke-session <token>');
|
|
68
|
+
ctx.print('Usage: /auth local revoke-session <token-or-fingerprint>');
|
|
69
69
|
return;
|
|
70
70
|
}
|
|
71
|
-
ctx.print(auth.revokeSession(token) ? `Revoked session ${token.slice(0, 12)}…` : `Unknown session token: ${token}`);
|
|
71
|
+
ctx.print(auth.revokeSession(token) ? `Revoked session ${token.slice(0, 12)}…` : `Unknown session token or fingerprint: ${token}`);
|
|
72
72
|
return;
|
|
73
73
|
}
|
|
74
74
|
|
|
@@ -88,7 +88,7 @@ export function handleLocalAuthCommand(args: string[], ctx: CommandContext): voi
|
|
|
88
88
|
` users: ${snapshot.userCount}`,
|
|
89
89
|
` sessions: ${snapshot.sessionCount}`,
|
|
90
90
|
...snapshot.users.map((user) => ` user: ${user.username} roles=${formatRoles(user.roles)}`),
|
|
91
|
-
...snapshot.sessions.map((session) => ` session: ${session.username} expires=${new Date(session.expiresAt).toISOString()}
|
|
91
|
+
...snapshot.sessions.map((session) => ` session: ${session.username} expires=${new Date(session.expiresAt).toISOString()} fingerprint=${session.tokenFingerprint}`),
|
|
92
92
|
].join('\n'));
|
|
93
93
|
}
|
|
94
94
|
|
|
@@ -97,7 +97,7 @@ export function registerLocalAuthRuntimeCommands(registry: CommandRegistry): voi
|
|
|
97
97
|
name: 'local-auth',
|
|
98
98
|
aliases: ['auth-local'],
|
|
99
99
|
description: 'Inspect and manage local daemon/listener auth users, sessions, and bootstrap credentials',
|
|
100
|
-
usage: '[review|panel|add-user <username> <password> [roles]|delete-user <username>|rotate-password <username> <password>|revoke-session <token>|clear-bootstrap-file]',
|
|
100
|
+
usage: '[review|panel|add-user <username> <password> [roles]|delete-user <username>|rotate-password <username> <password>|revoke-session <token-or-fingerprint>|clear-bootstrap-file]',
|
|
101
101
|
handler(args, ctx) {
|
|
102
102
|
handleLocalAuthCommand(args, ctx);
|
|
103
103
|
},
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ConfigKey } from '@pellux/goodvibes-sdk/platform/config/schema';
|
|
2
|
+
import type { ModelDefinition } from '@pellux/goodvibes-sdk/platform/providers/registry';
|
|
2
3
|
import type { CommandContext, CommandRegistry } from '../command-registry.ts';
|
|
3
4
|
import type { SelectionItem } from '../selection-modal.ts';
|
|
4
5
|
|
|
@@ -60,13 +61,14 @@ export function registerTtsRuntimeCommands(registry: CommandRegistry): void {
|
|
|
60
61
|
ctx.print('TTS LLM override cleared. /tts will use the current chat model.');
|
|
61
62
|
return;
|
|
62
63
|
}
|
|
63
|
-
if (ctx
|
|
64
|
+
if (openTtsLlmPicker(ctx)) return;
|
|
64
65
|
ctx.print('TTS LLM picker is not available in this runtime. Use /config-tts llm-provider <id> and /config-tts llm-model <model>.');
|
|
65
66
|
return;
|
|
66
67
|
}
|
|
67
68
|
if (TTS_CONFIG_KEYS.has(sub)) {
|
|
68
69
|
const value = args.slice(1).join(' ').trim();
|
|
69
70
|
if (!value) {
|
|
71
|
+
if ((sub === 'llm-provider' || sub === 'llm-model') && openTtsLlmPicker(ctx)) return;
|
|
70
72
|
ctx.print(`Usage: /config-tts ${sub} <value|clear>`);
|
|
71
73
|
return;
|
|
72
74
|
}
|
|
@@ -75,6 +77,14 @@ export function registerTtsRuntimeCommands(registry: CommandRegistry): void {
|
|
|
75
77
|
const previousProvider = key === 'tts.provider'
|
|
76
78
|
? String(ctx.platform.configManager.get('tts.provider') ?? '').trim()
|
|
77
79
|
: '';
|
|
80
|
+
if (key === 'tts.llmProvider') {
|
|
81
|
+
setTtsLlmProvider(ctx, nextValue);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (key === 'tts.llmModel') {
|
|
85
|
+
setTtsLlmModel(ctx, nextValue);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
78
88
|
setTtsConfigValue(ctx, key, nextValue);
|
|
79
89
|
if (key === 'tts.provider' && previousProvider && previousProvider !== nextValue) {
|
|
80
90
|
setTtsConfigValue(ctx, 'tts.voice', '');
|
|
@@ -119,7 +129,8 @@ function openTtsConfigModal(ctx: CommandContext): boolean {
|
|
|
119
129
|
const voice = String(cm.get('tts.voice') ?? '').trim() || '(provider default)';
|
|
120
130
|
const llmProvider = String(cm.get('tts.llmProvider') ?? '').trim();
|
|
121
131
|
const llmModel = String(cm.get('tts.llmModel') ?? '').trim();
|
|
122
|
-
const
|
|
132
|
+
const llmProviderLabel = llmProvider || '(current chat provider)';
|
|
133
|
+
const llmModelLabel = llmModel || '(current chat model)';
|
|
123
134
|
const items: SelectionItem[] = [
|
|
124
135
|
{
|
|
125
136
|
id: 'provider',
|
|
@@ -138,12 +149,20 @@ function openTtsConfigModal(ctx: CommandContext): boolean {
|
|
|
138
149
|
actions: '[Enter] choose',
|
|
139
150
|
},
|
|
140
151
|
{
|
|
141
|
-
id: 'llm',
|
|
142
|
-
label: 'TTS
|
|
143
|
-
detail:
|
|
152
|
+
id: 'llm-provider',
|
|
153
|
+
label: 'TTS LLM provider',
|
|
154
|
+
detail: llmProviderLabel,
|
|
155
|
+
category: 'response generation',
|
|
156
|
+
primaryAction: 'select',
|
|
157
|
+
actions: '[Enter] choose provider and model',
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
id: 'llm-model',
|
|
161
|
+
label: 'TTS LLM model',
|
|
162
|
+
detail: llmModelLabel,
|
|
144
163
|
category: 'response generation',
|
|
145
164
|
primaryAction: 'select',
|
|
146
|
-
actions: '[Enter] choose model',
|
|
165
|
+
actions: '[Enter] choose provider and model',
|
|
147
166
|
},
|
|
148
167
|
{
|
|
149
168
|
id: 'clear-voice',
|
|
@@ -171,10 +190,12 @@ function openTtsConfigModal(ctx: CommandContext): boolean {
|
|
|
171
190
|
void openTtsVoicePicker(ctx);
|
|
172
191
|
return;
|
|
173
192
|
}
|
|
174
|
-
if (result.item.id === 'llm') {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
193
|
+
if (result.item.id === 'llm-provider') {
|
|
194
|
+
openTtsLlmPicker(ctx);
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
if (result.item.id === 'llm-model') {
|
|
198
|
+
openTtsLlmPicker(ctx);
|
|
178
199
|
return;
|
|
179
200
|
}
|
|
180
201
|
if (result.item.id === 'clear-voice') {
|
|
@@ -191,6 +212,11 @@ function openTtsConfigModal(ctx: CommandContext): boolean {
|
|
|
191
212
|
return true;
|
|
192
213
|
}
|
|
193
214
|
|
|
215
|
+
function openTtsLlmPicker(ctx: CommandContext): boolean {
|
|
216
|
+
if (ctx.openProviderModelPickerWithTarget?.('tts')) return true;
|
|
217
|
+
return ctx.openModelPickerWithTarget?.('tts') ?? false;
|
|
218
|
+
}
|
|
219
|
+
|
|
194
220
|
function getStreamingTtsProviders(ctx: CommandContext): Array<{ id: string; label: string; capabilities: readonly string[] }> {
|
|
195
221
|
const registry = ctx.platform.voiceProviderRegistry;
|
|
196
222
|
if (!registry) {
|
|
@@ -253,6 +279,63 @@ function printTtsProviders(ctx: CommandContext): void {
|
|
|
253
279
|
].join('\n'));
|
|
254
280
|
}
|
|
255
281
|
|
|
282
|
+
function getSelectableLlmModels(ctx: CommandContext): ModelDefinition[] {
|
|
283
|
+
const registry = ctx.provider.providerRegistry as Partial<Pick<typeof ctx.provider.providerRegistry, 'getSelectableModels' | 'listModels'>>;
|
|
284
|
+
if (typeof registry.getSelectableModels === 'function') return registry.getSelectableModels();
|
|
285
|
+
if (typeof registry.listModels === 'function') return registry.listModels().filter((model) => model.selectable !== false);
|
|
286
|
+
return [];
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function setTtsLlmProvider(ctx: CommandContext, nextValue: string): void {
|
|
290
|
+
if (!nextValue) {
|
|
291
|
+
setTtsConfigValue(ctx, 'tts.llmProvider', '');
|
|
292
|
+
setTtsConfigValue(ctx, 'tts.llmModel', '');
|
|
293
|
+
ctx.print('TTS LLM override cleared. /tts will use the current chat model.');
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
const previousProvider = String(ctx.platform.configManager.get('tts.llmProvider') ?? '').trim();
|
|
297
|
+
setTtsConfigValue(ctx, 'tts.llmProvider', nextValue);
|
|
298
|
+
if (previousProvider && previousProvider !== nextValue) {
|
|
299
|
+
setTtsConfigValue(ctx, 'tts.llmModel', '');
|
|
300
|
+
ctx.print(`TTS LLM provider set to ${nextValue}. TTS LLM model was cleared because models are provider-specific.`);
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
ctx.print(`TTS LLM provider set to ${nextValue}.`);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function setTtsLlmModel(ctx: CommandContext, nextValue: string): void {
|
|
307
|
+
if (!nextValue) {
|
|
308
|
+
setTtsConfigValue(ctx, 'tts.llmModel', '');
|
|
309
|
+
ctx.print('TTS LLM model override cleared. /tts will use the current chat model unless a model is selected.');
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
const preferredProvider = String(ctx.platform.configManager.get('tts.llmProvider') ?? '').trim() || undefined;
|
|
313
|
+
const selected = findSelectableLlmModel(ctx, nextValue, preferredProvider);
|
|
314
|
+
if (selected) {
|
|
315
|
+
setTtsConfigValue(ctx, 'tts.llmProvider', selected.provider);
|
|
316
|
+
setTtsConfigValue(ctx, 'tts.llmModel', getModelRegistryKey(selected));
|
|
317
|
+
ctx.print(`TTS LLM set to ${selected.displayName} (${selected.provider}).`);
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
setTtsConfigValue(ctx, 'tts.llmModel', nextValue);
|
|
321
|
+
ctx.print(`tts.llmModel set to ${nextValue}.`);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function findSelectableLlmModel(ctx: CommandContext, ref: string, preferredProvider?: string): ModelDefinition | undefined {
|
|
325
|
+
const matches = getSelectableLlmModels(ctx).filter((model) =>
|
|
326
|
+
model.registryKey === ref || model.id === ref || model.displayName === ref,
|
|
327
|
+
);
|
|
328
|
+
if (preferredProvider) {
|
|
329
|
+
const providerMatch = matches.find((model) => model.provider === preferredProvider);
|
|
330
|
+
if (providerMatch) return providerMatch;
|
|
331
|
+
}
|
|
332
|
+
return matches[0];
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function getModelRegistryKey(model: ModelDefinition): string {
|
|
336
|
+
return model.registryKey ?? `${model.provider}:${model.id}`;
|
|
337
|
+
}
|
|
338
|
+
|
|
256
339
|
async function openTtsVoicePicker(ctx: CommandContext, providerArg?: string): Promise<boolean> {
|
|
257
340
|
if (!ctx.openSelection) return false;
|
|
258
341
|
const service = ctx.platform.voiceService;
|
package/src/input/commands.ts
CHANGED
|
@@ -55,6 +55,7 @@ import { registerConversationRuntimeCommands } from './commands/conversation-run
|
|
|
55
55
|
import { registerQrcodeRuntimeCommands } from './commands/qrcode-runtime.ts';
|
|
56
56
|
import { registerOnboardingRuntimeCommands } from './commands/onboarding-runtime.ts';
|
|
57
57
|
import { registerTtsRuntimeCommands } from './commands/tts-runtime.ts';
|
|
58
|
+
import { registerCloudflareRuntimeCommands } from './commands/cloudflare-runtime.ts';
|
|
58
59
|
|
|
59
60
|
/**
|
|
60
61
|
* registerBuiltinCommands - Register all built-in slash commands into the registry.
|
|
@@ -104,6 +105,7 @@ export function registerBuiltinCommands(registry: CommandRegistry): void {
|
|
|
104
105
|
registerQrcodeRuntimeCommands(registry);
|
|
105
106
|
registerOnboardingRuntimeCommands(registry);
|
|
106
107
|
registerTtsRuntimeCommands(registry);
|
|
108
|
+
registerCloudflareRuntimeCommands(registry);
|
|
107
109
|
registerLocalRuntimeCommands(registry);
|
|
108
110
|
registerSessionWorkflowCommands(registry);
|
|
109
111
|
registerDiscoveryRuntimeCommands(registry);
|
|
@@ -152,6 +152,7 @@ export interface FeedContextClosures {
|
|
|
152
152
|
cleanupMarkerRegistry: (text: string) => void;
|
|
153
153
|
expandPrompt: (text: string) => string | import('@pellux/goodvibes-sdk/platform/providers/interface').ContentPart[];
|
|
154
154
|
openModelPickerWithTarget: (target: ModelPickerTarget, source?: 'settings' | 'onboarding') => boolean;
|
|
155
|
+
openProviderModelPickerWithTarget: (target: ModelPickerTarget, source?: 'settings' | 'onboarding') => boolean;
|
|
155
156
|
onModelPickerCommit: () => boolean;
|
|
156
157
|
onOnboardingAction: (action: import('./onboarding/onboarding-wizard.ts').OnboardingWizardAction) => void;
|
|
157
158
|
}
|
|
@@ -153,6 +153,7 @@ export interface InputFeedContext {
|
|
|
153
153
|
readonly cleanupMarkerRegistry: (text: string) => void;
|
|
154
154
|
readonly expandPrompt: (text: string) => string | import('@pellux/goodvibes-sdk/platform/providers/interface').ContentPart[];
|
|
155
155
|
readonly openModelPickerWithTarget: (target: ModelPickerTarget, source?: 'settings' | 'onboarding') => boolean;
|
|
156
|
+
readonly openProviderModelPickerWithTarget: (target: ModelPickerTarget, source?: 'settings' | 'onboarding') => boolean;
|
|
156
157
|
readonly onModelPickerCommit: () => boolean;
|
|
157
158
|
readonly onOnboardingAction: (action: OnboardingWizardAction) => void;
|
|
158
159
|
readonly exitApp: () => void;
|
|
@@ -176,6 +177,10 @@ export function feedInputTokens(context: InputFeedContext, tokens: readonly Inpu
|
|
|
176
177
|
searchShortcutMatch: token.type === 'key' && keybindings.matches('search', token),
|
|
177
178
|
selectionModal: context.selectionModal,
|
|
178
179
|
selectionCallback: context.selectionCallback,
|
|
180
|
+
getSelectionCallback: () => context.selectionCallback,
|
|
181
|
+
setSelectionCallback: (callback) => {
|
|
182
|
+
context.selectionCallback = callback;
|
|
183
|
+
},
|
|
179
184
|
bookmarkModal: context.bookmarkModal,
|
|
180
185
|
settingsModal: context.settingsModal,
|
|
181
186
|
sessionPickerModal: context.sessionPickerModal,
|
|
@@ -213,6 +218,7 @@ export function feedInputTokens(context: InputFeedContext, tokens: readonly Inpu
|
|
|
213
218
|
scroll: context.scroll,
|
|
214
219
|
getScrollTop: context.getScrollTop,
|
|
215
220
|
openModelPickerWithTarget: context.openModelPickerWithTarget,
|
|
221
|
+
openProviderModelPickerWithTarget: context.openProviderModelPickerWithTarget,
|
|
216
222
|
onModelPickerCommit: context.onModelPickerCommit,
|
|
217
223
|
onOnboardingAction: context.onOnboardingAction,
|
|
218
224
|
}, token);
|