@pellux/goodvibes-tui 0.19.32 → 0.19.34

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 (39) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +4 -2
  3. package/docs/foundation-artifacts/operator-contract.json +1 -1
  4. package/package.json +2 -2
  5. package/src/audio/spoken-turn-model-routing.ts +117 -0
  6. package/src/input/command-registry.ts +2 -0
  7. package/src/input/commands/cloudflare-runtime.ts +343 -0
  8. package/src/input/commands/tts-runtime.ts +288 -7
  9. package/src/input/commands.ts +2 -0
  10. package/src/input/feed-context-factory.ts +1 -0
  11. package/src/input/handler-feed.ts +6 -0
  12. package/src/input/handler-modal-routes.ts +23 -10
  13. package/src/input/handler-modal-token-routes.ts +9 -0
  14. package/src/input/handler-onboarding-cloudflare.ts +391 -0
  15. package/src/input/handler-onboarding.ts +33 -0
  16. package/src/input/handler-picker-routes.ts +1 -1
  17. package/src/input/handler.ts +4 -1
  18. package/src/input/model-picker-types.ts +125 -0
  19. package/src/input/model-picker.ts +144 -134
  20. package/src/input/onboarding/onboarding-wizard-apply.ts +81 -0
  21. package/src/input/onboarding/onboarding-wizard-cloudflare-step.ts +449 -0
  22. package/src/input/onboarding/onboarding-wizard-cloudflare.ts +199 -0
  23. package/src/input/onboarding/onboarding-wizard-constants.ts +7 -0
  24. package/src/input/onboarding/onboarding-wizard-steps.ts +6 -6
  25. package/src/input/onboarding/onboarding-wizard-types.ts +8 -0
  26. package/src/input/settings-modal-types.ts +2 -1
  27. package/src/input/settings-modal.ts +30 -8
  28. package/src/main.ts +12 -1
  29. package/src/renderer/buffer.ts +40 -2
  30. package/src/renderer/compositor.ts +25 -17
  31. package/src/renderer/model-picker-overlay.ts +70 -0
  32. package/src/renderer/settings-modal-helpers.ts +1 -0
  33. package/src/runtime/bootstrap-command-parts.ts +4 -0
  34. package/src/runtime/cloudflare-control-plane.ts +328 -0
  35. package/src/runtime/onboarding/derivation.ts +25 -0
  36. package/src/runtime/onboarding/snapshot.ts +2 -0
  37. package/src/runtime/onboarding/types.ts +5 -1
  38. package/src/shell/ui-openers.ts +21 -2
  39. package/src/version.ts +1 -1
@@ -4,117 +4,11 @@ import { EFFORT_DESCRIPTIONS } from '@pellux/goodvibes-sdk/platform/providers/ef
4
4
  import { getQualityTier, getQualityTierFromScore, compositeScore, A_TIER_THRESHOLD } from '@pellux/goodvibes-sdk/platform/providers/model-benchmarks';
5
5
  import type { BenchmarkStore } from '@pellux/goodvibes-sdk/platform/providers/model-benchmarks';
6
6
  import type { ProviderRegistry } from '@pellux/goodvibes-sdk/platform/providers/registry';
7
+ import { detectFamily, POPULAR_PROVIDERS, tierToCategoryFilter } from './model-picker-types.ts';
8
+ import type { BenchmarkSort, CapabilityFilter, CategoryFilter, FilteredModelsCache, FilteredProvidersCache, GroupByMode, ModelItemsCache, ModelPickerTarget, PickerItem, PickerMode, ProviderItemsCache } from './model-picker-types.ts';
7
9
 
8
- export type PickerMode = 'model' | 'provider' | 'effort' | 'contextCap';
9
-
10
- /**
11
- * Which config keys the model picker writes to on commit.
12
- * 'main' → provider.provider + provider.model (default)
13
- * 'helper' → helper.globalProvider + helper.globalModel (+ helper.enabled: true)
14
- * 'tool' → tools.llmProvider + tools.llmModel (+ tools.llmEnabled: true)
15
- */
16
- export type ModelPickerTarget = 'main' | 'helper' | 'tool';
17
-
18
- /**
19
- * Pricing tier filter.
20
- * 'paid' matches ModelDefinition tiers 'standard' and 'premium' for forward-compat
21
- * with future CatalogModel tiers ('free' | 'paid' | 'subscription').
22
- */
23
- export type CategoryFilter = 'all' | 'free' | 'paid' | 'subscription';
24
-
25
- /** Model family grouping names. */
26
- export type ModelFamily =
27
- | 'GPT'
28
- | 'Claude'
29
- | 'Gemini'
30
- | 'Llama'
31
- | 'Qwen'
32
- | 'GLM'
33
- | 'MiniMax'
34
- | 'DeepSeek'
35
- | 'Mistral'
36
- | 'Command'
37
- | 'Grok'
38
- | 'Kimi'
39
- | 'Other';
40
-
41
- /** Capability filter — subset of ModelDefinition capabilities. */
42
- export type CapabilityFilter = 'reasoning' | 'toolUse' | 'multimodal' | 'none';
43
-
44
- /** Benchmark score sort order. */
45
- export type BenchmarkSort = 'none' | 'composite' | 'swe' | 'gpqa';
46
-
47
- /** Group-by cycling order. */
48
- export type GroupByMode = 'provider' | 'family' | 'pricingTier' | 'qualityTier';
49
-
50
- // ── Family detection helpers ──────────────────────────────────────────────────
51
-
52
- /** Patterns for detecting model family from id/displayName. */
53
- const FAMILY_PATTERNS: Array<{ pattern: RegExp; family: ModelFamily }> = [
54
- { pattern: /claude/i, family: 'Claude' },
55
- { pattern: /gpt|\bo1\b|\bo3\b|\bo4\b/i, family: 'GPT' },
56
- { pattern: /gemini/i, family: 'Gemini' },
57
- { pattern: /llama/i, family: 'Llama' },
58
- { pattern: /qwen/i, family: 'Qwen' },
59
- { pattern: /glm|chatglm/i, family: 'GLM' },
60
- { pattern: /minimax|abab/i, family: 'MiniMax' },
61
- { pattern: /deepseek/i, family: 'DeepSeek' },
62
- { pattern: /mistral|mixtral/i, family: 'Mistral' },
63
- { pattern: /command|cohere/i, family: 'Command' },
64
- { pattern: /grok/i, family: 'Grok' },
65
- { pattern: /kimi|moonshot/i, family: 'Kimi' },
66
- ];
67
-
68
- /** Detect the model family from id and displayName. */
69
- export function detectFamily(model: ModelDefinition): ModelFamily {
70
- const haystack = `${model.id} ${model.displayName}`;
71
- for (const { pattern, family } of FAMILY_PATTERNS) {
72
- if (pattern.test(haystack)) return family;
73
- }
74
- return 'Other';
75
- }
76
-
77
- /**
78
- * Map ModelDefinition tier to CategoryFilter bucket.
79
- * 'standard' and 'premium' both map to 'paid' for forward-compat.
80
- */
81
- export function tierToCategoryFilter(tier: string | undefined): CategoryFilter {
82
- if (tier === 'free') return 'free';
83
- if (tier === 'subscription') return 'subscription';
84
- return 'paid';
85
- }
86
-
87
- /** A generic selectable item for non-model modes. */
88
- export interface PickerItem {
89
- id: string;
90
- label: string;
91
- detail?: string;
92
- /** If true, this item is a group header (not selectable). */
93
- isGroupHeader?: boolean;
94
- /** Quality tier badge for model items: S/A/B/C. */
95
- qualityTier?: string;
96
- /** Whether this model is pinned/favorited. */
97
- isPinned?: boolean;
98
- /** True when model tier is free. */
99
- isFree?: boolean;
100
- /** True when this provider item has a configured API key. */
101
- isConfigured?: boolean;
102
- /** How the provider is configured — shown as a badge in provider mode. */
103
- configuredVia?: 'env' | 'secrets' | 'subscription' | 'anonymous';
104
- }
105
-
106
- /** Provider IDs treated as "Popular" in the provider picker. */
107
- export const POPULAR_PROVIDERS: ReadonlySet<string> = new Set([
108
- 'anthropic',
109
- 'google',
110
- 'groq',
111
- 'mistral',
112
- 'nvidia',
113
- 'ollama',
114
- 'openai',
115
- 'openrouter',
116
- 'synthetic',
117
- ]);
10
+ export { detectFamily, POPULAR_PROVIDERS, tierToCategoryFilter } from './model-picker-types.ts';
11
+ export type { BenchmarkSort, CapabilityFilter, CategoryFilter, GroupByMode, ModelFamily, ModelPickerTarget, PickerItem, PickerMode } from './model-picker-types.ts';
118
12
 
119
13
  /**
120
14
  * ModelPickerModal - Multi-step interactive picker for model, provider, and effort.
@@ -179,12 +73,18 @@ export class ModelPickerModal {
179
73
  /** Current group-by mode. */
180
74
  public groupBy: GroupByMode = 'provider';
181
75
 
76
+ private filteredModelsCache: FilteredModelsCache | null = null;
77
+ private filteredProvidersCache: FilteredProvidersCache | null = null;
78
+ private modelItemsCache: ModelItemsCache | null = null;
79
+ private providerItemsCache: ProviderItemsCache | null = null;
80
+
182
81
  // ── Category filter cycling ───────────────────────────────────────────────
183
82
  private static readonly CATEGORY_CYCLE: CategoryFilter[] = ['all', 'free', 'paid', 'subscription'];
184
83
  /** Cycle to next pricing tier filter. */
185
84
  cycleCategory(): void {
186
85
  const idx = ModelPickerModal.CATEGORY_CYCLE.indexOf(this.categoryFilter);
187
86
  this.categoryFilter = ModelPickerModal.CATEGORY_CYCLE[(idx + 1) % ModelPickerModal.CATEGORY_CYCLE.length];
87
+ this.clearFilteredCaches();
188
88
  this._clampSelection();
189
89
  }
190
90
 
@@ -194,6 +94,7 @@ export class ModelPickerModal {
194
94
  cycleGroupBy(): void {
195
95
  const idx = ModelPickerModal.GROUP_BY_CYCLE.indexOf(this.groupBy);
196
96
  this.groupBy = ModelPickerModal.GROUP_BY_CYCLE[(idx + 1) % ModelPickerModal.GROUP_BY_CYCLE.length];
97
+ this.clearFilteredCaches();
197
98
  this._clampSelection();
198
99
  }
199
100
 
@@ -203,6 +104,7 @@ export class ModelPickerModal {
203
104
  cycleBenchmarkSort(): void {
204
105
  const idx = ModelPickerModal.BENCHMARK_SORT_CYCLE.indexOf(this.benchmarkSort);
205
106
  this.benchmarkSort = ModelPickerModal.BENCHMARK_SORT_CYCLE[(idx + 1) % ModelPickerModal.BENCHMARK_SORT_CYCLE.length];
107
+ this.clearFilteredCaches();
206
108
  this._clampSelection();
207
109
  }
208
110
 
@@ -265,8 +167,9 @@ export class ModelPickerModal {
265
167
  this.query = '';
266
168
  this.categoryFilter = 'all';
267
169
  this.capabilityFilter = 'none';
268
- // Start selection at the top of the Popular providers group.
269
- this.selectedIndex = 0;
170
+ const filtered = this.getFilteredProviders();
171
+ const currentIndex = filtered.findIndex((provider) => provider === currentProvider);
172
+ this.selectedIndex = currentIndex >= 0 ? currentIndex : 0;
270
173
  this.scrollOffset = 0;
271
174
  }
272
175
 
@@ -314,6 +217,7 @@ export class ModelPickerModal {
314
217
  this.query = '';
315
218
  this.categoryFilter = 'all';
316
219
  this.capabilityFilter = 'none';
220
+ this.clearCaches();
317
221
  }
318
222
 
319
223
  // ── Search helpers ─────────────────────────────────────────────────────────
@@ -321,6 +225,7 @@ export class ModelPickerModal {
321
225
  /** Append a character to the search query and clamp selectedIndex. */
322
226
  appendChar(ch: string): void {
323
227
  this.query += ch;
228
+ this.clearFilteredCaches();
324
229
  this._clampSelection();
325
230
  }
326
231
 
@@ -328,6 +233,7 @@ export class ModelPickerModal {
328
233
  deleteChar(): void {
329
234
  if (this.query.length > 0) {
330
235
  this.query = this.query.slice(0, -1);
236
+ this.clearFilteredCaches();
331
237
  this._clampSelection();
332
238
  }
333
239
  }
@@ -335,6 +241,7 @@ export class ModelPickerModal {
335
241
  /** Clear the search query and clamp selectedIndex. */
336
242
  clearQuery(): void {
337
243
  this.query = '';
244
+ this.clearFilteredCaches();
338
245
  this._clampSelection();
339
246
  }
340
247
 
@@ -353,18 +260,21 @@ export class ModelPickerModal {
353
260
  /** Set category filter and clamp selectedIndex. */
354
261
  setCategoryFilter(filter: CategoryFilter): void {
355
262
  this.categoryFilter = filter;
263
+ this.clearFilteredCaches();
356
264
  this._clampSelection();
357
265
  }
358
266
 
359
267
  /** Set capability filter and clamp selectedIndex. */
360
268
  setCapabilityFilter(filter: CapabilityFilter): void {
361
269
  this.capabilityFilter = filter;
270
+ this.clearFilteredCaches();
362
271
  this._clampSelection();
363
272
  }
364
273
 
365
274
  /** Toggle the available-only filter. */
366
275
  toggleAvailableOnly(): void {
367
276
  this.availableOnly = !this.availableOnly;
277
+ this.clearFilteredCaches();
368
278
  this._clampSelection();
369
279
  }
370
280
 
@@ -395,15 +305,50 @@ export class ModelPickerModal {
395
305
 
396
306
  /** Return providers matching the current query (case-insensitive substring), in grouped order. */
397
307
  getFilteredProviders(): string[] {
308
+ const cached = this.filteredProvidersCache;
309
+ if (
310
+ cached !== null
311
+ && cached.providersRef === this.providers
312
+ && cached.query === this.query
313
+ ) {
314
+ return cached.result;
315
+ }
316
+
398
317
  const { popular, all } = this.getGroupedProviders();
399
318
  const ordered = [...popular, ...all];
400
- if (this.query.trim().length === 0) return ordered;
401
- const q = this.query.toLowerCase();
402
- return ordered.filter(p => p.toLowerCase().includes(q));
319
+ const result = this.query.trim().length === 0
320
+ ? ordered
321
+ : ordered.filter(p => p.toLowerCase().includes(this.query.toLowerCase()));
322
+ this.filteredProvidersCache = {
323
+ providersRef: this.providers,
324
+ query: this.query,
325
+ result,
326
+ };
327
+ return result;
403
328
  }
404
329
 
405
330
  /** Return models matching all current filters, sorted per benchmarkSort. */
406
331
  getFilteredModels(): ModelDefinition[] {
332
+ const configuredProvidersKey = setKey(this.configuredProviders);
333
+ const pinnedIdsKey = setKey(this.pinnedIds);
334
+ const recentIdsKey = orderedListKey(this.recentIds);
335
+ const cached = this.filteredModelsCache;
336
+ if (
337
+ cached !== null
338
+ && cached.modelsRef === this.models
339
+ && cached.configuredProvidersKey === configuredProvidersKey
340
+ && cached.pinnedIdsKey === pinnedIdsKey
341
+ && cached.recentIdsKey === recentIdsKey
342
+ && cached.query === this.query
343
+ && cached.categoryFilter === this.categoryFilter
344
+ && cached.capabilityFilter === this.capabilityFilter
345
+ && cached.availableOnly === this.availableOnly
346
+ && cached.benchmarkSort === this.benchmarkSort
347
+ && cached.groupBy === this.groupBy
348
+ ) {
349
+ return cached.result;
350
+ }
351
+
407
352
  let result = this.models;
408
353
 
409
354
  // Available-only filter
@@ -519,6 +464,20 @@ export class ModelPickerModal {
519
464
  result = [...recent, ...rest];
520
465
  }
521
466
 
467
+ this.filteredModelsCache = {
468
+ modelsRef: this.models,
469
+ configuredProvidersKey,
470
+ pinnedIdsKey,
471
+ recentIdsKey,
472
+ query: this.query,
473
+ categoryFilter: this.categoryFilter,
474
+ capabilityFilter: this.capabilityFilter,
475
+ availableOnly: this.availableOnly,
476
+ benchmarkSort: this.benchmarkSort,
477
+ groupBy: this.groupBy,
478
+ result,
479
+ };
480
+ this.modelItemsCache = null;
522
481
  return result;
523
482
  }
524
483
 
@@ -528,6 +487,7 @@ export class ModelPickerModal {
528
487
  */
529
488
  async loadRecentModels(n = 10): Promise<void> {
530
489
  this.recentIds = await this.favoritesStore.getRecentModels(n);
490
+ this.clearFilteredCaches();
531
491
  }
532
492
 
533
493
  /**
@@ -573,6 +533,16 @@ export class ModelPickerModal {
573
533
  getItems(): PickerItem[] {
574
534
  if (this.mode === 'model') {
575
535
  const filtered = this.getFilteredModels();
536
+ const pinnedIdsKey = setKey(this.pinnedIds);
537
+ const cached = this.modelItemsCache;
538
+ if (
539
+ cached !== null
540
+ && cached.filteredModelsRef === filtered
541
+ && cached.pinnedIdsKey === pinnedIdsKey
542
+ && cached.groupBy === this.groupBy
543
+ ) {
544
+ return cached.result;
545
+ }
576
546
 
577
547
  // Separate pinned and unpinned
578
548
  const pinned = filtered.filter(m => this.pinnedIds.has(m.id));
@@ -599,33 +569,45 @@ export class ModelPickerModal {
599
569
  items.push(this._modelToItem(m, false));
600
570
  }
601
571
 
572
+ this.modelItemsCache = {
573
+ filteredModelsRef: filtered,
574
+ pinnedIdsKey,
575
+ groupBy: this.groupBy,
576
+ result: items,
577
+ };
602
578
  return items;
603
579
  }
604
580
  if (this.mode === 'provider') {
605
- const q = this.query.trim().toLowerCase();
606
- const { popular, all } = this.getGroupedProviders();
607
-
608
- const filterGroup = (group: string[]) =>
609
- q.length === 0 ? group : group.filter(p => p.toLowerCase().includes(q));
610
-
611
- const filteredPopular = filterGroup(popular);
612
- const filteredAll = filterGroup(all);
581
+ const filteredProviders = this.getFilteredProviders();
582
+ const configuredProvidersKey = setKey(this.configuredProviders);
583
+ const configuredViaKey = mapKey(this.configuredViaMap);
584
+ const cached = this.providerItemsCache;
585
+ if (
586
+ cached !== null
587
+ && cached.filteredProvidersRef === filteredProviders
588
+ && cached.configuredProvidersKey === configuredProvidersKey
589
+ && cached.configuredViaKey === configuredViaKey
590
+ ) {
591
+ return cached.result;
592
+ }
613
593
 
614
594
  const providerItems: PickerItem[] = [];
615
-
616
- if (filteredPopular.length > 0) {
617
- providerItems.push({ id: '__header__popular', label: 'Popular', isGroupHeader: true });
618
- for (const p of filteredPopular) {
619
- providerItems.push({ id: p, label: p, isConfigured: this.configuredProviders.has(p), configuredVia: this.configuredViaMap.get(p) });
620
- }
621
- }
622
- if (filteredAll.length > 0) {
623
- providerItems.push({ id: '__header__all', label: 'All Providers', isGroupHeader: true });
624
- for (const p of filteredAll) {
625
- providerItems.push({ id: p, label: p, isConfigured: this.configuredProviders.has(p), configuredVia: this.configuredViaMap.get(p) });
595
+ let currentGroup: 'Popular' | 'All Providers' | null = null;
596
+ for (const p of filteredProviders) {
597
+ const group: 'Popular' | 'All Providers' = POPULAR_PROVIDERS.has(p.toLowerCase()) ? 'Popular' : 'All Providers';
598
+ if (group !== currentGroup) {
599
+ providerItems.push({ id: `__header__${group}`, label: group, isGroupHeader: true });
600
+ currentGroup = group;
626
601
  }
602
+ providerItems.push({ id: p, label: p, isConfigured: this.configuredProviders.has(p), configuredVia: this.configuredViaMap.get(p) });
627
603
  }
628
604
 
605
+ this.providerItemsCache = {
606
+ filteredProvidersRef: filteredProviders,
607
+ configuredProvidersKey,
608
+ configuredViaKey,
609
+ result: providerItems,
610
+ };
629
611
  return providerItems;
630
612
  }
631
613
  // effort mode
@@ -742,4 +724,32 @@ export class ModelPickerModal {
742
724
  getBenchmarkEntry(model: ModelDefinition) {
743
725
  return this.benchmarkStore.getBenchmarks(model.id) ?? this.benchmarkStore.getBenchmarks(model.displayName);
744
726
  }
727
+
728
+ private clearFilteredCaches(): void {
729
+ this.filteredModelsCache = null;
730
+ this.filteredProvidersCache = null;
731
+ this.modelItemsCache = null;
732
+ this.providerItemsCache = null;
733
+ }
734
+
735
+ private clearCaches(): void {
736
+ this.clearFilteredCaches();
737
+ }
738
+ }
739
+
740
+ function setKey(values: ReadonlySet<string>): string {
741
+ if (values.size === 0) return '';
742
+ return [...values].sort().join('\u001f');
743
+ }
744
+
745
+ function orderedListKey(values: readonly string[]): string {
746
+ return values.length === 0 ? '' : values.join('\u001f');
747
+ }
748
+
749
+ function mapKey(values: ReadonlyMap<string, string | undefined>): string {
750
+ if (values.size === 0) return '';
751
+ return [...values.entries()]
752
+ .sort(([left], [right]) => left.localeCompare(right))
753
+ .map(([key, value]) => `${key}\u001e${value ?? ''}`)
754
+ .join('\u001f');
745
755
  }
@@ -1,5 +1,13 @@
1
1
  import type { OnboardingAcknowledgementTarget, OnboardingApplyOperation, OnboardingApplyRequest } from '../../runtime/onboarding/index.ts';
2
2
  import { getServerSurfaceFeatureFlags } from '../../runtime/surface-feature-flags.ts';
3
+ import {
4
+ buildCloudflareApiTokenRef,
5
+ buildCloudflareOperationalTokenRef,
6
+ getCloudflareBatchMode,
7
+ getCloudflareComponentSelection,
8
+ getCloudflareSetupSource,
9
+ shouldShowCloudflareStep,
10
+ } from './onboarding-wizard-cloudflare.ts';
3
11
  import {
4
12
  EXTERNAL_SURFACE_SPECS,
5
13
  getExternalSurfaceAutoStartDefaultValue,
@@ -111,6 +119,10 @@ export function buildOnboardingApplyRequest(controller: OnboardingWizardControll
111
119
 
112
120
  setSecret('OPENAI_API_KEY', controller.getStringFieldValue('providers.openai-api-key', ''));
113
121
 
122
+ if (shouldShowCloudflareStep(controller)) {
123
+ addCloudflareOperations(controller, operations, setSecret);
124
+ }
125
+
114
126
  const externalIntegrations = controller.isCapabilitySelected('external-integrations');
115
127
  const enabledExternalSurfaceIds: string[] = [];
116
128
  for (const surface of EXTERNAL_SURFACE_SPECS) {
@@ -165,6 +177,75 @@ export function buildOnboardingApplyRequest(controller: OnboardingWizardControll
165
177
  };
166
178
  }
167
179
 
180
+ function addCloudflareOperations(
181
+ controller: OnboardingWizardController,
182
+ operations: OnboardingApplyOperation[],
183
+ setSecret: (key: string, value: string) => void,
184
+ ): void {
185
+ const setConfig = (
186
+ key: Extract<OnboardingApplyOperation, { kind: 'set-config' }>['key'],
187
+ value: unknown,
188
+ ): void => {
189
+ operations.push({ kind: 'set-config', key, value });
190
+ };
191
+
192
+ const config = controller.runtimeSnapshot?.config.cloudflare;
193
+ const enabledDefault = controller.isCapabilitySelected('cloudflare-batch') || config?.enabled === true;
194
+ const enabled = controller.getBooleanFieldValue('cloudflare.enabled', enabledDefault);
195
+ const components = getCloudflareComponentSelection(controller);
196
+ const batchMode = enabled ? getCloudflareBatchMode(controller) : 'off';
197
+ const setupSource = getCloudflareSetupSource(controller);
198
+ const existingApiTokenRef = config?.apiTokenRef ?? '';
199
+ let apiTokenRef = existingApiTokenRef;
200
+
201
+ if (!enabled) {
202
+ setConfig('cloudflare.enabled', false);
203
+ setConfig('batch.mode', 'off');
204
+ setConfig('batch.queueBackend', 'local');
205
+ return;
206
+ }
207
+
208
+ if (setupSource === 'operational-env') {
209
+ apiTokenRef = buildCloudflareApiTokenRef(
210
+ controller.getStringFieldValue('cloudflare.operational-env-name', 'CLOUDFLARE_API_TOKEN'),
211
+ );
212
+ } else if (setupSource === 'operational-token') {
213
+ const token = controller.getStringFieldValue('cloudflare.operational-token', '');
214
+ if (token.length > 0) {
215
+ setSecret('CLOUDFLARE_API_TOKEN', token);
216
+ apiTokenRef = buildCloudflareOperationalTokenRef();
217
+ }
218
+ }
219
+
220
+ setConfig('cloudflare.enabled', true);
221
+ setConfig('cloudflare.freeTierMode', controller.getStringFieldValue('cloudflare.free-tier-mode', config?.freeTierMode === false ? 'no' : 'yes') === 'yes');
222
+ setConfig('cloudflare.accountId', controller.getStringFieldValue('cloudflare.account-id', config?.accountId ?? ''));
223
+ setConfig('cloudflare.apiTokenRef', apiTokenRef);
224
+ setConfig('cloudflare.zoneId', controller.getStringFieldValue('cloudflare.zone-id', config?.zoneId ?? ''));
225
+ setConfig('cloudflare.zoneName', controller.getStringFieldValue('cloudflare.zone-name', config?.zoneName ?? ''));
226
+ setConfig('cloudflare.workerName', controller.getStringFieldValue('cloudflare.worker-name', config?.workerName ?? 'goodvibes-batch-worker'));
227
+ setConfig('cloudflare.workerSubdomain', controller.getStringFieldValue('cloudflare.worker-subdomain', config?.workerSubdomain ?? ''));
228
+ setConfig('cloudflare.workerHostname', controller.getStringFieldValue('cloudflare.worker-hostname', config?.workerHostname ?? ''));
229
+ setConfig('cloudflare.workerBaseUrl', controller.getStringFieldValue('cloudflare.worker-base-url', config?.workerBaseUrl ?? ''));
230
+ setConfig('cloudflare.daemonBaseUrl', controller.getStringFieldValue('cloudflare.daemon-base-url', config?.daemonBaseUrl ?? ''));
231
+ setConfig('cloudflare.daemonHostname', controller.getStringFieldValue('cloudflare.daemon-hostname', config?.daemonHostname ?? ''));
232
+ setConfig('cloudflare.workerCron', controller.getStringFieldValue('cloudflare.worker-cron', config?.workerCron ?? '*/5 * * * *'));
233
+ setConfig('cloudflare.queueName', controller.getStringFieldValue('cloudflare.queue-name', config?.queueName ?? 'goodvibes-batch'));
234
+ setConfig('cloudflare.deadLetterQueueName', controller.getStringFieldValue('cloudflare.dead-letter-queue-name', config?.deadLetterQueueName ?? 'goodvibes-batch-dlq'));
235
+ setConfig('cloudflare.tunnelName', controller.getStringFieldValue('cloudflare.tunnel-name', config?.tunnelName ?? 'goodvibes-daemon'));
236
+ setConfig('cloudflare.tunnelId', controller.getStringFieldValue('cloudflare.tunnel-id', config?.tunnelId ?? ''));
237
+ setConfig('cloudflare.kvNamespaceName', controller.getStringFieldValue('cloudflare.kv-namespace-name', config?.kvNamespaceName ?? 'goodvibes-runtime'));
238
+ setConfig('cloudflare.kvNamespaceId', controller.getStringFieldValue('cloudflare.kv-namespace-id', config?.kvNamespaceId ?? ''));
239
+ setConfig('cloudflare.durableObjectNamespaceName', controller.getStringFieldValue('cloudflare.do-namespace-name', config?.durableObjectNamespaceName ?? 'GoodVibesCoordinator'));
240
+ setConfig('cloudflare.durableObjectNamespaceId', controller.getStringFieldValue('cloudflare.do-namespace-id', config?.durableObjectNamespaceId ?? ''));
241
+ setConfig('cloudflare.r2BucketName', controller.getStringFieldValue('cloudflare.r2-bucket-name', config?.r2BucketName ?? 'goodvibes-artifacts'));
242
+ setConfig('cloudflare.secretsStoreName', controller.getStringFieldValue('cloudflare.secrets-store-name', config?.secretsStoreName ?? 'goodvibes'));
243
+ setConfig('cloudflare.secretsStoreId', controller.getStringFieldValue('cloudflare.secrets-store-id', config?.secretsStoreId ?? ''));
244
+ setConfig('cloudflare.maxQueueOpsPerDay', controller.getNumberFieldValue('cloudflare.max-queue-ops-per-day', config?.maxQueueOpsPerDay ?? 10000, 1));
245
+ setConfig('batch.mode', batchMode);
246
+ setConfig('batch.queueBackend', batchMode !== 'off' && components.queues ? 'cloudflare' : 'local');
247
+ }
248
+
168
249
  export function addNetworkOperations(
169
250
  controller: OnboardingWizardController,
170
251
  operations: OnboardingApplyOperation[],