create-oke 0.18.5 → 0.19.0

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 (62) hide show
  1. package/README.md +1 -1
  2. package/package.json +3 -3
  3. package/src/agents-md.ts +6 -4
  4. package/src/ai-setup/apply.test.ts +278 -0
  5. package/src/ai-setup/apply.ts +430 -52
  6. package/src/ai-setup/catalog.ts +250 -1343
  7. package/src/ai-setup/from-pref.ts +3 -71
  8. package/src/ai-setup/prompts.ts +60 -443
  9. package/src/cli.test.ts +21 -15
  10. package/src/cli.ts +20 -11
  11. package/src/create-defaults.test.ts +4 -4
  12. package/src/create-defaults.ts +3 -3
  13. package/src/customize-flow.test.ts +9 -15
  14. package/src/customize-flow.ts +59 -66
  15. package/src/drivers-catalog.ts +18 -23
  16. package/src/transform.test.ts +69 -10
  17. package/src/transform.ts +10 -35
  18. package/templates/advanced/.env.example +28 -11
  19. package/templates/advanced/.github/workflows/ci.yml +1 -1
  20. package/templates/advanced/oke.config.ts +3 -4
  21. package/templates/advanced/package.json +11 -10
  22. package/templates/advanced/src/app.ts +36 -1
  23. package/templates/advanced/src/core.ts +12 -11
  24. package/templates/advanced/src/db/schema.decl.ts +6 -2
  25. package/templates/advanced/src/db/seed/index.ts +4 -4
  26. package/templates/advanced/src/flows/main/route.ts +3 -2
  27. package/templates/advanced/src/flows/notes/[id]/archive.ts +5 -8
  28. package/templates/advanced/src/flows/notes/[id]/attach.ts +1 -4
  29. package/templates/advanced/src/flows/notes/[id]/get.ts +4 -7
  30. package/templates/advanced/src/flows/notes/[id]/summarize.ts +3 -4
  31. package/templates/advanced/src/flows/notes/create.ts +4 -6
  32. package/templates/advanced/src/flows/notes/digest.ts +6 -5
  33. package/templates/advanced/src/flows/notes/list.ts +4 -5
  34. package/templates/advanced/src/flows/notes/shapes.ts +13 -3
  35. package/templates/advanced/src/flows/notes/signals.ts +1 -2
  36. package/templates/advanced/src/vault.ts +138 -0
  37. package/templates/advanced/tests/advanced.test.ts +11 -8
  38. package/templates/advanced/web/src/App.css +7 -0
  39. package/templates/advanced/web/src/App.tsx +101 -1
  40. package/templates/advanced/web/src/client.ts +16 -3
  41. package/templates/advanced/web/vite.config.ts +1 -0
  42. package/templates/standard/.env.example +22 -11
  43. package/templates/standard/.github/workflows/ci.yml +1 -1
  44. package/templates/standard/oke.config.ts +3 -3
  45. package/templates/standard/package.json +11 -10
  46. package/templates/standard/src/app.ts +6 -1
  47. package/templates/standard/src/core.ts +10 -11
  48. package/templates/standard/src/db/schema.decl.ts +6 -2
  49. package/templates/standard/src/db/seed/index.ts +3 -3
  50. package/templates/standard/src/flows/main/route.ts +3 -2
  51. package/templates/standard/src/flows/notes/[id]/archive.ts +5 -8
  52. package/templates/standard/src/flows/notes/[id]/get.ts +4 -7
  53. package/templates/standard/src/flows/notes/create.ts +4 -6
  54. package/templates/standard/src/flows/notes/list.ts +4 -5
  55. package/templates/standard/src/flows/notes/shapes.ts +12 -2
  56. package/templates/standard/src/flows/notes/signals.ts +1 -2
  57. package/templates/standard/src/vault.ts +123 -0
  58. package/templates/standard/tests/standard.test.ts +3 -1
  59. package/templates/standard/web/src/client.ts +2 -2
  60. package/templates/standard/web/vite.config.ts +1 -0
  61. package/src/ai-setup/detect-ollama.ts +0 -228
  62. package/src/ai-setup/recommend.ts +0 -220
@@ -1,623 +1,165 @@
1
1
  /**
2
- * Curated model catalog for `oke ai setup` / create-oke AI wizard.
3
- *
4
- * Tiers map to machine RAM classes (not download size). Manual lists show
5
- * up to 10 entries with modalities.
2
+ * Curated cloud model catalog for `oke ai setup` / create-oke AI wizard.
6
3
  */
7
4
 
8
- /** Role a model can fill in the wizard. */
9
- export type CatalogRole = "chat" | "vision" | "embed";
10
-
11
- /** Speed / quality tier for the Select model step. */
12
- export type ModelTier = "ultra-fast" | "fast" | "balanced" | "smart";
13
-
14
- /** Capability tags shown in manual model lists. */
15
- export type ModelModality = "text" | "vision" | "code" | "reasoning";
16
-
17
- /** One curated local model entry (Ollama tag or Docker Hub `ai/` id). */
18
- export type CatalogModel = {
19
- readonly id: string;
5
+ /** Thin cloud / host-side provider menu. */
6
+ export type CloudProviderMenuEntry = {
7
+ readonly value: string;
20
8
  readonly label: string;
21
- readonly hint: string;
22
- readonly role: CatalogRole;
23
- readonly recommended?: boolean;
24
- /** Approximate RAM (GB) needed to run comfortably (machine tier). */
25
- readonly ramGb: number;
26
- readonly tier: ModelTier;
27
- readonly modalities: readonly ModelModality[];
9
+ readonly hint?: string;
10
+ readonly driver: "openai-compatible" | "anthropic";
11
+ /** Registry / declare provider name (defaults to `value`). */
12
+ readonly provider?: string;
13
+ readonly baseUrl?: string;
14
+ readonly apiKeyEnv?: string;
15
+ /** When true, wizard asks for base URL even if a default exists. */
16
+ readonly promptBaseUrl?: boolean;
28
17
  };
29
18
 
30
- /** Tier menu rows (prompt labels). */
31
- export const MODEL_TIERS: readonly {
32
- readonly value: ModelTier;
33
- readonly label: string;
34
- readonly hint: string;
35
- readonly minRamGb: number;
36
- readonly maxRamGb: number;
37
- }[] = [
38
- {
39
- value: "ultra-fast",
40
- label: "Ultra Fast",
41
- hint: "~1GB–~2GB RAM",
42
- minRamGb: 1,
43
- maxRamGb: 2,
44
- },
45
- {
46
- value: "fast",
47
- label: "Fast",
48
- hint: "~4GB–~8GB RAM",
49
- minRamGb: 4,
50
- maxRamGb: 8,
51
- },
52
- {
53
- value: "balanced",
54
- label: "Balanced",
55
- hint: "~8GB–~16GB RAM",
56
- minRamGb: 8,
57
- maxRamGb: 16,
58
- },
59
- {
60
- value: "smart",
61
- label: "Smart",
62
- hint: "~24GB–~32GB RAM",
63
- minRamGb: 24,
64
- maxRamGb: 32,
65
- },
66
- ];
67
-
68
- /** Chat / multimodal models (curated; manual pick shows ≤10 per tier). */
69
- export const CHAT_MODELS: readonly CatalogModel[] = [
70
- // Ultra Fast (~1–2GB)
71
- {
72
- id: "smollm2:135m",
73
- label: "SmolLM2 135M",
74
- hint: "Tiny · on-device",
75
- role: "chat",
76
- ramGb: 1,
77
- tier: "ultra-fast",
78
- modalities: ["text"],
79
- },
80
- {
81
- id: "qwen2.5:0.5b",
82
- label: "Qwen2.5 0.5B",
83
- hint: "Smallest Qwen",
84
- role: "chat",
85
- ramGb: 1,
86
- tier: "ultra-fast",
87
- modalities: ["text", "code"],
88
- },
89
- {
90
- id: "llama3.2:1b",
91
- label: "Llama 3.2 1B",
92
- hint: "Meta tiny",
93
- role: "chat",
94
- ramGb: 2,
95
- tier: "ultra-fast",
96
- modalities: ["text"],
97
- },
98
- {
99
- id: "gemma3:1b",
100
- label: "Gemma 3 1B",
101
- hint: "Google tiny",
102
- role: "chat",
103
- ramGb: 2,
104
- tier: "ultra-fast",
105
- modalities: ["text"],
106
- },
107
- {
108
- id: "qwen2.5:1.5b",
109
- label: "Qwen2.5 1.5B",
110
- hint: "Small coding",
111
- role: "chat",
112
- recommended: true,
113
- ramGb: 2,
114
- tier: "ultra-fast",
115
- modalities: ["text", "code"],
116
- },
117
- {
118
- id: "deepseek-r1:1.5b",
119
- label: "DeepSeek R1 1.5B",
120
- hint: "Tiny reasoning",
121
- role: "chat",
122
- ramGb: 2,
123
- tier: "ultra-fast",
124
- modalities: ["text", "reasoning"],
125
- },
126
- {
127
- id: "moondream",
128
- label: "Moondream",
129
- hint: "Tiny vision",
130
- role: "chat",
131
- ramGb: 2,
132
- tier: "ultra-fast",
133
- modalities: ["text", "vision"],
134
- },
135
- {
136
- id: "tinydolphin",
137
- label: "TinyDolphin",
138
- hint: "Ultra-compact chat",
139
- role: "chat",
140
- ramGb: 1,
141
- tier: "ultra-fast",
142
- modalities: ["text"],
143
- },
144
- {
145
- id: "phi3:mini",
146
- label: "Phi-3 Mini",
147
- hint: "Microsoft compact",
148
- role: "chat",
149
- ramGb: 2,
150
- tier: "ultra-fast",
151
- modalities: ["text", "code"],
152
- },
153
- {
154
- id: "stable-code:3b",
155
- label: "Stable Code 3B",
156
- hint: "Compact coder",
157
- role: "chat",
158
- ramGb: 2,
159
- tier: "ultra-fast",
160
- modalities: ["text", "code"],
161
- },
162
-
163
- // Fast (~4–8GB)
164
- {
165
- id: "llama3.2:3b",
166
- label: "Llama 3.2 3B",
167
- hint: "Small general",
168
- role: "chat",
169
- ramGb: 4,
170
- tier: "fast",
171
- modalities: ["text"],
172
- },
173
- {
174
- id: "phi4-mini",
175
- label: "Phi-4 Mini",
176
- hint: "Strong small",
177
- role: "chat",
178
- ramGb: 4,
179
- tier: "fast",
180
- modalities: ["text", "code"],
181
- },
182
- {
183
- id: "qwen2.5:3b",
184
- label: "Qwen2.5 3B",
185
- hint: "Coding · small",
186
- role: "chat",
187
- ramGb: 4,
188
- tier: "fast",
189
- modalities: ["text", "code"],
190
- },
191
- {
192
- id: "gemma4:e4b",
193
- label: "Gemma 4 4B",
194
- hint: "Fast default",
195
- role: "chat",
196
- recommended: true,
197
- ramGb: 8,
198
- tier: "fast",
199
- modalities: ["text"],
200
- },
201
- {
202
- id: "qwen2.5:7b",
203
- label: "Qwen2.5 7B",
204
- hint: "Coding workhorse",
205
- role: "chat",
206
- ramGb: 8,
207
- tier: "fast",
208
- modalities: ["text", "code"],
209
- },
210
- {
211
- id: "mistral:7b",
212
- label: "Mistral 7B",
213
- hint: "General purpose",
214
- role: "chat",
215
- ramGb: 8,
216
- tier: "fast",
217
- modalities: ["text"],
218
- },
219
- {
220
- id: "deepseek-r1:7b",
221
- label: "DeepSeek R1 7B",
222
- hint: "Reasoning",
223
- role: "chat",
224
- ramGb: 8,
225
- tier: "fast",
226
- modalities: ["text", "reasoning"],
227
- },
228
- {
229
- id: "llava:7b",
230
- label: "LLaVA 7B",
231
- hint: "Vision-language",
232
- role: "chat",
233
- ramGb: 8,
234
- tier: "fast",
235
- modalities: ["text", "vision"],
236
- },
237
- {
238
- id: "qwen3-vl:4b",
239
- label: "Qwen3-VL 4B",
240
- hint: "Vision-language",
241
- role: "chat",
242
- ramGb: 8,
243
- tier: "fast",
244
- modalities: ["text", "vision"],
245
- },
246
- {
247
- id: "granite3.3:8b",
248
- label: "Granite 3.3 8B",
249
- hint: "IBM enterprise",
250
- role: "chat",
251
- ramGb: 8,
252
- tier: "fast",
253
- modalities: ["text", "code"],
254
- },
255
-
256
- // Balanced (~8–16GB)
257
- {
258
- id: "deepseek-r1:8b",
259
- label: "DeepSeek R1 8B",
260
- hint: "Strong reasoning",
261
- role: "chat",
262
- ramGb: 10,
263
- tier: "balanced",
264
- modalities: ["text", "reasoning"],
265
- },
266
- {
267
- id: "llama3.1:8b",
268
- label: "Llama 3.1 8B",
269
- hint: "General purpose",
270
- role: "chat",
271
- ramGb: 10,
272
- tier: "balanced",
273
- modalities: ["text"],
274
- },
275
- {
276
- id: "qwen3.5:9b",
277
- label: "Qwen3.5 9B",
278
- hint: "Better coding",
279
- role: "chat",
280
- recommended: true,
281
- ramGb: 16,
282
- tier: "balanced",
283
- modalities: ["text", "code"],
284
- },
285
- {
286
- id: "llama4:scout",
287
- label: "Llama 4 Scout",
288
- hint: "General purpose",
289
- role: "chat",
290
- ramGb: 16,
291
- tier: "balanced",
292
- modalities: ["text"],
293
- },
294
- {
295
- id: "qwen2.5:14b",
296
- label: "Qwen2.5 14B",
297
- hint: "Larger Qwen",
298
- role: "chat",
299
- ramGb: 16,
300
- tier: "balanced",
301
- modalities: ["text", "code"],
302
- },
303
- {
304
- id: "qwen2.5-coder:14b",
305
- label: "Qwen2.5 Coder 14B",
306
- hint: "Coding specialist",
307
- role: "chat",
308
- ramGb: 16,
309
- tier: "balanced",
310
- modalities: ["text", "code"],
311
- },
312
- {
313
- id: "mistral-nemo",
314
- label: "Mistral Nemo",
315
- hint: "12B-class general",
316
- role: "chat",
317
- ramGb: 12,
318
- tier: "balanced",
319
- modalities: ["text"],
320
- },
321
- {
322
- id: "llava:13b",
323
- label: "LLaVA 13B",
324
- hint: "Vision-language",
325
- role: "chat",
326
- ramGb: 16,
327
- tier: "balanced",
328
- modalities: ["text", "vision"],
329
- },
330
- {
331
- id: "gemma2:9b",
332
- label: "Gemma 2 9B",
333
- hint: "Google mid",
334
- role: "chat",
335
- ramGb: 12,
336
- tier: "balanced",
337
- modalities: ["text"],
338
- },
339
- {
340
- id: "command-r",
341
- label: "Command R",
342
- hint: "Cohere RAG-friendly",
343
- role: "chat",
344
- ramGb: 16,
345
- tier: "balanced",
346
- modalities: ["text"],
347
- },
348
-
349
- // Smart (~24–32GB)
350
- {
351
- id: "gemma2:27b",
352
- label: "Gemma 2 27B",
353
- hint: "Large Gemma",
354
- role: "chat",
355
- ramGb: 24,
356
- tier: "smart",
357
- modalities: ["text"],
358
- },
359
- {
360
- id: "qwen3.5:27b",
361
- label: "Qwen3.5 27B",
362
- hint: "Best local quality",
363
- role: "chat",
364
- recommended: true,
365
- ramGb: 32,
366
- tier: "smart",
367
- modalities: ["text", "code"],
368
- },
19
+ /**
20
+ * Cloud / openai-compatible menu — URLs from the verified provider registry
21
+ * where known; Anthropic stays native.
22
+ */
23
+ export const CLOUD_PROVIDERS: readonly CloudProviderMenuEntry[] = [
369
24
  {
370
- id: "qwen2.5:32b",
371
- label: "Qwen2.5 32B",
372
- hint: "Large Qwen",
373
- role: "chat",
374
- ramGb: 32,
375
- tier: "smart",
376
- modalities: ["text", "code"],
25
+ value: "openrouter",
26
+ label: "OpenRouter",
27
+ hint: "recommended · zero Docker · openrouter/free",
28
+ driver: "openai-compatible",
29
+ baseUrl: "https://openrouter.ai/api/v1",
30
+ apiKeyEnv: "OPENROUTER_API_KEY",
377
31
  },
378
32
  {
379
- id: "qwen2.5-coder:32b",
380
- label: "Qwen2.5 Coder 32B",
381
- hint: "Large coder",
382
- role: "chat",
383
- ramGb: 32,
384
- tier: "smart",
385
- modalities: ["text", "code"],
33
+ value: "openai",
34
+ label: "OpenAI",
35
+ driver: "openai-compatible",
36
+ baseUrl: "https://api.openai.com/v1",
37
+ apiKeyEnv: "OPENAI_API_KEY",
386
38
  },
387
39
  {
388
- id: "deepseek-r1:32b",
389
- label: "DeepSeek R1 32B",
390
- hint: "Large reasoning",
391
- role: "chat",
392
- ramGb: 32,
393
- tier: "smart",
394
- modalities: ["text", "reasoning"],
40
+ value: "anthropic",
41
+ label: "Anthropic",
42
+ hint: "native Messages API",
43
+ driver: "anthropic",
44
+ provider: "anthropic",
45
+ baseUrl: undefined,
46
+ apiKeyEnv: "ANTHROPIC_API_KEY",
395
47
  },
396
48
  {
397
- id: "mixtral:8x7b",
398
- label: "Mixtral 8x7B",
399
- hint: "MoE general",
400
- role: "chat",
401
- ramGb: 26,
402
- tier: "smart",
403
- modalities: ["text"],
49
+ value: "groq",
50
+ label: "Groq",
51
+ driver: "openai-compatible",
52
+ baseUrl: "https://api.groq.com/openai/v1",
53
+ apiKeyEnv: "GROQ_API_KEY",
404
54
  },
405
55
  {
406
- id: "command-r-plus",
407
- label: "Command R+",
408
- hint: "Cohere large",
409
- role: "chat",
410
- ramGb: 32,
411
- tier: "smart",
412
- modalities: ["text"],
56
+ value: "together",
57
+ label: "Together AI",
58
+ driver: "openai-compatible",
59
+ baseUrl: "https://api.together.ai/v1",
60
+ apiKeyEnv: "TOGETHER_API_KEY",
413
61
  },
414
62
  {
415
- id: "mistral-small:24b",
416
- label: "Mistral Small 24B",
417
- hint: "Mistral mid-large",
418
- role: "chat",
419
- ramGb: 24,
420
- tier: "smart",
421
- modalities: ["text"],
63
+ value: "deepseek",
64
+ label: "DeepSeek",
65
+ driver: "openai-compatible",
66
+ baseUrl: "https://api.deepseek.com",
67
+ apiKeyEnv: "DEEPSEEK_API_KEY",
422
68
  },
423
69
  {
424
- id: "llama3.1:70b",
425
- label: "Llama 3.1 70B",
426
- hint: "Needs ~40GB+; listed for reference",
427
- role: "chat",
428
- ramGb: 40,
429
- tier: "smart",
430
- modalities: ["text"],
70
+ value: "mistral",
71
+ label: "Mistral",
72
+ driver: "openai-compatible",
73
+ baseUrl: "https://api.mistral.ai/v1",
74
+ apiKeyEnv: "MISTRAL_API_KEY",
431
75
  },
432
76
  {
433
- id: "qwen2.5:72b",
434
- label: "Qwen2.5 72B",
435
- hint: "Needs ~48GB+; listed for reference",
436
- role: "chat",
437
- ramGb: 48,
438
- tier: "smart",
439
- modalities: ["text", "code"],
77
+ value: "xai",
78
+ label: "xAI (Grok)",
79
+ driver: "openai-compatible",
80
+ baseUrl: "https://api.x.ai/v1",
81
+ apiKeyEnv: "XAI_API_KEY",
440
82
  },
441
- ];
442
-
443
- /** Vision-only short list (legacy / optional). */
444
- export const VISION_MODELS: readonly CatalogModel[] = [
445
83
  {
446
- id: "qwen3-vl:4b",
447
- label: "Qwen3-VL 4B",
448
- hint: "Vision-language",
449
- role: "vision",
450
- recommended: true,
451
- ramGb: 8,
452
- tier: "fast",
453
- modalities: ["text", "vision"],
84
+ value: "deepinfra",
85
+ label: "DeepInfra",
86
+ driver: "openai-compatible",
87
+ baseUrl: "https://api.deepinfra.com/v1/openai",
88
+ apiKeyEnv: "DEEPINFRA_API_KEY",
454
89
  },
455
90
  {
456
- id: "llava:7b",
457
- label: "LLaVA 7B",
458
- hint: "Vision-language",
459
- role: "vision",
460
- ramGb: 8,
461
- tier: "fast",
462
- modalities: ["text", "vision"],
91
+ value: "vercel",
92
+ label: "Vercel AI Gateway",
93
+ driver: "openai-compatible",
94
+ baseUrl: "https://ai-gateway.vercel.sh/v1",
95
+ apiKeyEnv: "AI_GATEWAY_API_KEY",
463
96
  },
464
- ];
465
-
466
- /** Embedding models (applied automatically — no download prompt). */
467
- export const EMBED_MODELS: readonly CatalogModel[] = [
468
97
  {
469
- id: "nomic-embed-text",
470
- label: "nomic-embed-text",
471
- hint: "Default local RAG embedder",
472
- role: "embed",
473
- recommended: true,
474
- ramGb: 1,
475
- tier: "ultra-fast",
476
- modalities: ["text"],
98
+ value: "gemini",
99
+ label: "Google Gemini",
100
+ hint: "Limited OpenAI-compat tool schemas constrained",
101
+ driver: "openai-compatible",
102
+ provider: "gemini",
103
+ baseUrl: "https://generativelanguage.googleapis.com/v1beta/openai",
104
+ apiKeyEnv: "GEMINI_API_KEY",
477
105
  },
478
106
  {
479
- id: "bge-m3",
480
- label: "bge-m3",
481
- hint: "Multilingual + long docs",
482
- role: "embed",
483
- ramGb: 2,
484
- tier: "ultra-fast",
485
- modalities: ["text"],
107
+ value: "lmstudio",
108
+ label: "LM Studio",
109
+ driver: "openai-compatible",
110
+ provider: "openai-compatible",
111
+ baseUrl: "http://127.0.0.1:1234/v1",
112
+ apiKeyEnv: undefined,
486
113
  },
487
114
  {
488
- id: "jina-embeddings-v4",
489
- label: "jina-embeddings-v4",
490
- hint: "High-quality embedder",
491
- role: "embed",
492
- ramGb: 2,
493
- tier: "ultra-fast",
494
- modalities: ["text"],
115
+ value: "custom",
116
+ label: "Custom OpenAI Compatible",
117
+ hint: "requires base URL",
118
+ driver: "openai-compatible",
119
+ provider: "openai-compatible",
120
+ baseUrl: undefined,
121
+ apiKeyEnv: "OPENAI_API_KEY",
122
+ promptBaseUrl: true,
495
123
  },
496
124
  ];
497
125
 
498
- /** All curated ids (for detect / not-installed). */
499
- export const ALL_CURATED: readonly CatalogModel[] = [
500
- ...CHAT_MODELS,
501
- ...VISION_MODELS.filter((m) => !CHAT_MODELS.some((c) => c.id === m.id)),
502
- ...EMBED_MODELS,
503
- ];
504
-
505
- /**
506
- * Sort catalog rows by RAM ascending, then label (stable pick lists).
507
- *
508
- * @param models - Catalog entries
509
- */
510
- function byRamThenLabel(models: readonly CatalogModel[]): CatalogModel[] {
511
- return [...models].sort((a, b) => a.ramGb - b.ramGb || a.label.localeCompare(b.label));
512
- }
513
-
514
- /**
515
- * Chat models in a tier (≤10 for manual pick), ordered by RAM.
516
- *
517
- * @param tier - Speed / quality tier
518
- */
519
- export function modelsForTier(tier: ModelTier): readonly CatalogModel[] {
520
- return byRamThenLabel(CHAT_MODELS.filter((m) => m.tier === tier)).slice(0, 10);
521
- }
522
-
523
- /**
524
- * Recommended chat model for a tier (falls back to first in tier).
525
- *
526
- * @param tier - Selected tier
527
- * @param totalRamGb - Prefer models that fit when RAM is known
528
- */
529
- export function recommendForTier(tier: ModelTier, totalRamGb: number | null = null): CatalogModel {
530
- const list = modelsForTier(tier);
531
- const headroom = 4;
532
- const fits =
533
- totalRamGb !== null && Number.isFinite(totalRamGb)
534
- ? list.filter((m) => m.ramGb + headroom <= totalRamGb || m.ramGb <= totalRamGb)
535
- : list;
536
- const pool = fits.length > 0 ? fits : list;
537
- return pool.find((m) => m.recommended) ?? pool[pool.length - 1] ?? list[0]!;
538
- }
126
+ /** One row in the AI Provider select. */
127
+ export type AiProviderSelectOption = {
128
+ readonly value: string;
129
+ readonly label: string;
130
+ readonly hint?: string;
131
+ };
539
132
 
540
133
  /**
541
- * Recommend a chat model for the host RAM (comfortable headroom).
134
+ * AI Provider menu OpenRouter first (recommended), then other cloud,
135
+ * then optional mock.
542
136
  *
543
- * @param totalRamGb - Detected total system RAM in GB (or null)
137
+ * @param options - Include Mock (create-oke Customize)
544
138
  */
545
- export function recommendChatModel(totalRamGb: number | null): CatalogModel {
546
- const HEADROOM = 4;
547
- const sorted = [...CHAT_MODELS].sort((a, b) => a.ramGb - b.ramGb);
548
- const smallest = sorted[0]!;
549
- if (totalRamGb === null || !Number.isFinite(totalRamGb)) {
550
- return CHAT_MODELS.find((m) => m.recommended && m.tier === "fast") ?? smallest;
551
- }
552
- const comfortable = sorted.filter(
553
- (m) => (m.id === smallest.id && m.ramGb <= totalRamGb) || m.ramGb + HEADROOM <= totalRamGb,
554
- );
555
- if (comfortable.length > 0) return comfortable[comfortable.length - 1]!;
556
- const tierOk = sorted.filter((m) => m.ramGb <= totalRamGb);
557
- return tierOk.length > 0 ? tierOk[tierOk.length - 1]! : smallest;
139
+ export function aiProviderSelectOptions(
140
+ options: { readonly includeMock?: boolean } = {},
141
+ ): readonly AiProviderSelectOption[] {
142
+ const cloud = CLOUD_PROVIDERS.map((p) => ({
143
+ value: p.value,
144
+ label: p.label,
145
+ ...(p.hint !== undefined ? { hint: p.hint } : {}),
146
+ }));
147
+ const mock: readonly AiProviderSelectOption[] = options.includeMock
148
+ ? [{ value: "mock", label: "Mock (dev only)", hint: "no network" }]
149
+ : [];
150
+ return [...cloud, ...mock];
558
151
  }
559
152
 
560
153
  /**
561
- * Recommend vision / embed defaults.
154
+ * Protocol driver for an AI Provider menu id.
562
155
  *
563
- * @param role - vision or embed
156
+ * @param provider - Menu value
564
157
  */
565
- export function recommendForRole(role: "vision" | "embed"): CatalogModel {
566
- const list = role === "vision" ? VISION_MODELS : EMBED_MODELS;
567
- return list.find((m) => m.recommended) ?? list[0]!;
158
+ export function aiDriverForMenuProvider(provider: string): string {
159
+ if (provider === "mock") return "mock";
160
+ return CLOUD_PROVIDERS.find((p) => p.value === provider)?.driver ?? "openai-compatible";
568
161
  }
569
162
 
570
- /** Format modalities for a list row. */
571
- export function formatModalities(modalities: readonly ModelModality[]): string {
572
- return modalities.join(" · ");
573
- }
574
-
575
- /** Thin cloud provider menu (non-Ollama). */
576
- export const CLOUD_PROVIDERS = [
577
- {
578
- value: "openai",
579
- label: "OpenAI",
580
- driver: "openai-compatible" as const,
581
- baseUrl: "https://api.openai.com/v1",
582
- apiKeyEnv: "OPENAI_API_KEY",
583
- },
584
- {
585
- value: "anthropic",
586
- label: "Anthropic",
587
- driver: "anthropic" as const,
588
- baseUrl: undefined,
589
- apiKeyEnv: "ANTHROPIC_API_KEY",
590
- },
591
- {
592
- value: "gemini",
593
- label: "Gemini",
594
- driver: "openai-compatible" as const,
595
- baseUrl: undefined,
596
- apiKeyEnv: "OPENAI_API_KEY",
597
- },
598
- {
599
- value: "lmstudio",
600
- label: "LM Studio",
601
- driver: "openai-compatible" as const,
602
- baseUrl: "http://127.0.0.1:1234/v1",
603
- apiKeyEnv: undefined,
604
- },
605
- {
606
- value: "openrouter",
607
- label: "OpenRouter",
608
- driver: "openai-compatible" as const,
609
- baseUrl: "https://openrouter.ai/api/v1",
610
- apiKeyEnv: "OPENAI_API_KEY",
611
- },
612
- {
613
- value: "custom",
614
- label: "Custom OpenAI Compatible",
615
- driver: "openai-compatible" as const,
616
- baseUrl: undefined,
617
- apiKeyEnv: "OPENAI_API_KEY",
618
- },
619
- ] as const;
620
-
621
163
  /** Cloud chat model entry (no RAM tier). */
622
164
  export type CloudModel = {
623
165
  readonly id: string;
@@ -716,21 +258,91 @@ export const CLOUD_CHAT_MODELS: Readonly<Record<string, readonly CloudModel[]>>
716
258
  ],
717
259
  openrouter: [
718
260
  {
719
- id: "openai/gpt-4o-mini",
720
- label: "GPT-4o mini",
721
- hint: "via OpenRouter",
261
+ id: "openrouter/free",
262
+ label: "openrouter/free",
263
+ hint: "Free router · zero cost",
722
264
  recommended: true,
723
265
  },
266
+ { id: "openrouter/auto", label: "openrouter/auto", hint: "Market pick by task + cost" },
267
+ {
268
+ id: "openrouter/pareto-code",
269
+ label: "openrouter/pareto-code",
270
+ hint: "Strong coding router",
271
+ },
272
+ {
273
+ id: "openrouter/fusion",
274
+ label: "openrouter/fusion",
275
+ hint: "Multi-model panel",
276
+ },
277
+ { id: "openai/gpt-4o-mini", label: "GPT-4o mini", hint: "via OpenRouter" },
724
278
  { id: "openai/gpt-4.1", label: "GPT-4.1", hint: "via OpenRouter" },
725
- { id: "openai/o4-mini", label: "o4-mini", hint: "via OpenRouter" },
726
279
  { id: "anthropic/claude-sonnet-4", label: "Claude Sonnet 4", hint: "via OpenRouter" },
727
- { id: "anthropic/claude-opus-4", label: "Claude Opus 4", hint: "via OpenRouter" },
728
- { id: "anthropic/claude-haiku-4", label: "Claude Haiku 4", hint: "via OpenRouter" },
729
280
  { id: "google/gemini-2.5-flash", label: "Gemini 2.5 Flash", hint: "via OpenRouter" },
730
- { id: "google/gemini-2.5-pro", label: "Gemini 2.5 Pro", hint: "via OpenRouter" },
731
281
  { id: "meta-llama/llama-4-scout", label: "Llama 4 Scout", hint: "via OpenRouter" },
732
282
  { id: "deepseek/deepseek-r1", label: "DeepSeek R1", hint: "via OpenRouter" },
733
283
  ],
284
+ groq: [
285
+ {
286
+ id: "llama-3.1-8b-instant",
287
+ label: "Llama 3.1 8B Instant",
288
+ hint: "Fast default",
289
+ recommended: true,
290
+ },
291
+ { id: "llama-3.3-70b-versatile", label: "Llama 3.3 70B", hint: "Higher quality" },
292
+ { id: "mixtral-8x7b-32768", label: "Mixtral 8x7B", hint: "Long context" },
293
+ ],
294
+ together: [
295
+ {
296
+ id: "meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo",
297
+ label: "Llama 3.1 8B Turbo",
298
+ hint: "Fast",
299
+ recommended: true,
300
+ },
301
+ {
302
+ id: "meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo",
303
+ label: "Llama 3.1 70B Turbo",
304
+ hint: "Higher quality",
305
+ },
306
+ ],
307
+ deepseek: [
308
+ {
309
+ id: "deepseek-v4-flash",
310
+ label: "DeepSeek V4 Flash",
311
+ hint: "Fast default",
312
+ recommended: true,
313
+ },
314
+ { id: "deepseek-v4-pro", label: "DeepSeek V4 Pro", hint: "Higher quality" },
315
+ ],
316
+ mistral: [
317
+ {
318
+ id: "mistral-small-latest",
319
+ label: "Mistral Small",
320
+ hint: "Fast default",
321
+ recommended: true,
322
+ },
323
+ { id: "mistral-large-latest", label: "Mistral Large", hint: "Higher quality" },
324
+ ],
325
+ xai: [
326
+ { id: "grok-4.6", label: "Grok 4.6", hint: "Current default", recommended: true },
327
+ { id: "grok-3-mini", label: "Grok 3 Mini", hint: "Faster / cheaper" },
328
+ ],
329
+ deepinfra: [
330
+ {
331
+ id: "meta-llama/Meta-Llama-3.1-8B-Instruct",
332
+ label: "Llama 3.1 8B",
333
+ hint: "Fast",
334
+ recommended: true,
335
+ },
336
+ ],
337
+ vercel: [
338
+ {
339
+ id: "openai/gpt-4o-mini",
340
+ label: "GPT-4o mini",
341
+ hint: "via AI Gateway",
342
+ recommended: true,
343
+ },
344
+ { id: "anthropic/claude-sonnet-4", label: "Claude Sonnet 4", hint: "via AI Gateway" },
345
+ ],
734
346
  lmstudio: [
735
347
  {
736
348
  id: "local-model",
@@ -778,767 +390,62 @@ export function recommendCloudChat(provider: string): string {
778
390
  }
779
391
 
780
392
  /**
781
- * Curated Docker Hub `ai/` chat models for llama.cpp (`LLAMA_ARG_DOCKER_REPO`).
782
- * Ids omit the `ai/` org prefix (llama.cpp default). ≥10 per tier; manual pick
783
- * shows up to 20. HF-origin weights are only listed when published under
784
- * Docker Hub [`ai/`](https://hub.docker.com/u/ai) (recipe cannot load raw HF ids).
785
- */
786
- export const LLAMA_CPP_CHAT_MODELS: readonly CatalogModel[] = [
787
- // Ultra Fast (~1–2GB)
788
- {
789
- id: "smollm2",
790
- label: "SmolLM2",
791
- hint: "Lightest",
792
- role: "chat",
793
- ramGb: 1,
794
- tier: "ultra-fast",
795
- modalities: ["text"],
796
- },
797
- {
798
- id: "smollm2:135M-Q4_K_M",
799
- label: "SmolLM2 135M",
800
- hint: "Tiny smoke test",
801
- role: "chat",
802
- ramGb: 1,
803
- tier: "ultra-fast",
804
- modalities: ["text"],
805
- },
806
- {
807
- id: "functiongemma",
808
- label: "FunctionGemma 270M",
809
- hint: "Tool calling",
810
- role: "chat",
811
- ramGb: 1,
812
- tier: "ultra-fast",
813
- modalities: ["text", "code"],
814
- },
815
- {
816
- id: "gemma3:270m",
817
- label: "Gemma 3 270M",
818
- hint: "Google tiny",
819
- role: "chat",
820
- ramGb: 1,
821
- tier: "ultra-fast",
822
- modalities: ["text"],
823
- },
824
- {
825
- id: "granite-4.0-h-nano:350M-Q8_0",
826
- label: "Granite 4.0 H Nano 350M",
827
- hint: "IBM tiny",
828
- role: "chat",
829
- ramGb: 1,
830
- tier: "ultra-fast",
831
- modalities: ["text"],
832
- },
833
- {
834
- id: "qwen3:0.6B-Q4_K_M",
835
- label: "Qwen3 0.6B",
836
- hint: "Small coding",
837
- role: "chat",
838
- ramGb: 2,
839
- tier: "ultra-fast",
840
- modalities: ["text", "code"],
841
- },
842
- {
843
- id: "smolvlm:500M-Q8_0",
844
- label: "SmolVLM 500M",
845
- hint: "Tiny vision",
846
- role: "chat",
847
- ramGb: 2,
848
- tier: "ultra-fast",
849
- modalities: ["text", "vision"],
850
- },
851
- {
852
- id: "llama3.2:1B-Q4_0",
853
- label: "Llama 3.2 1B",
854
- hint: "Meta tiny",
855
- role: "chat",
856
- ramGb: 2,
857
- tier: "ultra-fast",
858
- modalities: ["text"],
859
- },
860
- {
861
- id: "granite-4.0-h-nano",
862
- label: "Granite 4.0 H Nano 1B",
863
- hint: "IBM nano",
864
- role: "chat",
865
- ramGb: 2,
866
- tier: "ultra-fast",
867
- modalities: ["text"],
868
- },
869
- {
870
- id: "qwen2.5:0.5B-F16",
871
- label: "Qwen2.5 0.5B",
872
- hint: "Small coding",
873
- role: "chat",
874
- ramGb: 2,
875
- tier: "ultra-fast",
876
- modalities: ["text", "code"],
877
- },
878
- {
879
- id: "smollm2:360M-Q4_K_M",
880
- label: "SmolLM2 360M",
881
- hint: "Small instruct",
882
- role: "chat",
883
- ramGb: 1,
884
- tier: "ultra-fast",
885
- modalities: ["text"],
886
- },
887
- {
888
- id: "smollm2:135M-Q4_0",
889
- label: "SmolLM2 135M Q4_0",
890
- hint: "Tiny Q4_0",
891
- role: "chat",
892
- ramGb: 1,
893
- tier: "ultra-fast",
894
- modalities: ["text"],
895
- },
896
- {
897
- id: "gemma3:270m-q4_K_M",
898
- label: "Gemma 3 270M Q4",
899
- hint: "Google tiny Q4",
900
- role: "chat",
901
- ramGb: 1,
902
- tier: "ultra-fast",
903
- modalities: ["text"],
904
- },
905
- {
906
- id: "functiongemma:q4_K_M",
907
- label: "FunctionGemma Q4",
908
- hint: "Tool calling Q4",
909
- role: "chat",
910
- ramGb: 1,
911
- tier: "ultra-fast",
912
- modalities: ["text", "code"],
913
- },
914
- {
915
- id: "qwen3:0.6B-Q4_0",
916
- label: "Qwen3 0.6B Q4_0",
917
- hint: "Small coding",
918
- role: "chat",
919
- ramGb: 2,
920
- tier: "ultra-fast",
921
- modalities: ["text", "code"],
922
- },
923
- {
924
- id: "llama3.2:1B-Q8_0",
925
- label: "Llama 3.2 1B Q8",
926
- hint: "Meta tiny Q8",
927
- role: "chat",
928
- ramGb: 2,
929
- tier: "ultra-fast",
930
- modalities: ["text"],
931
- },
932
- {
933
- id: "granite-4.0-nano:350M-BF16",
934
- label: "Granite 4.0 Nano 350M",
935
- hint: "IBM nano",
936
- role: "chat",
937
- ramGb: 2,
938
- tier: "ultra-fast",
939
- modalities: ["text"],
940
- },
941
- {
942
- id: "smolvlm",
943
- label: "SmolVLM",
944
- hint: "Tiny vision default",
945
- role: "chat",
946
- ramGb: 2,
947
- tier: "ultra-fast",
948
- modalities: ["text", "vision"],
949
- },
950
- {
951
- id: "granite3.3:2b",
952
- label: "Granite 3.3 2B",
953
- hint: "Default · IBM 2B",
954
- role: "chat",
955
- recommended: true,
956
- ramGb: 2,
957
- tier: "ultra-fast",
958
- modalities: ["text", "code"],
959
- },
960
- {
961
- id: "gemma3:270m-q8_0",
962
- label: "Gemma 3 270M Q8",
963
- hint: "Google tiny Q8",
964
- role: "chat",
965
- ramGb: 1,
966
- tier: "ultra-fast",
967
- modalities: ["text"],
968
- },
969
- // Fast (~4–8GB) — Gemma 4 · Qwen near the top
970
- {
971
- id: "llama3.2",
972
- label: "Llama 3.2 3B",
973
- hint: "Balanced local chat",
974
- role: "chat",
975
- recommended: true,
976
- ramGb: 4,
977
- tier: "fast",
978
- modalities: ["text"],
979
- },
980
- {
981
- id: "gemma4:e2b-q4_K_M",
982
- label: "Gemma 4 E2B",
983
- hint: "Popular · multimodal",
984
- role: "chat",
985
- ramGb: 8,
986
- tier: "fast",
987
- modalities: ["text", "vision"],
988
- },
989
- {
990
- id: "qwen3:4B-UD-Q4_K_XL",
991
- label: "Qwen3 4B",
992
- hint: "Popular · coding · agents",
993
- role: "chat",
994
- ramGb: 6,
995
- tier: "fast",
996
- modalities: ["text", "code"],
997
- },
998
- {
999
- id: "qwen2.5:3B-Q4_K_M",
1000
- label: "Qwen2.5 3B",
1001
- hint: "Coding",
1002
- role: "chat",
1003
- ramGb: 4,
1004
- tier: "fast",
1005
- modalities: ["text", "code"],
1006
- },
1007
- {
1008
- id: "gemma3:4b",
1009
- label: "Gemma 3 4B",
1010
- hint: "Reasoning",
1011
- role: "chat",
1012
- ramGb: 8,
1013
- tier: "fast",
1014
- modalities: ["text", "reasoning"],
1015
- },
1016
- {
1017
- id: "gemma3n:e2b",
1018
- label: "Gemma 3n E2B",
1019
- hint: "On-device multimodal",
1020
- role: "chat",
1021
- ramGb: 8,
1022
- tier: "fast",
1023
- modalities: ["text", "vision"],
1024
- },
1025
- {
1026
- id: "qwen3-vl:2B-UD-Q4_K_XL",
1027
- label: "Qwen3-VL 2B",
1028
- hint: "Vision",
1029
- role: "chat",
1030
- ramGb: 6,
1031
- tier: "fast",
1032
- modalities: ["text", "vision"],
1033
- },
1034
- {
1035
- id: "smollm3",
1036
- label: "SmolLM3",
1037
- hint: "On-device chat",
1038
- role: "chat",
1039
- ramGb: 4,
1040
- tier: "fast",
1041
- modalities: ["text"],
1042
- },
1043
- {
1044
- id: "ministral3:3B-Q4_K_M",
1045
- label: "Ministral 3B",
1046
- hint: "Mistral small",
1047
- role: "chat",
1048
- ramGb: 4,
1049
- tier: "fast",
1050
- modalities: ["text", "code"],
1051
- },
1052
- {
1053
- id: "nemotron-3-nano:4b",
1054
- label: "Nemotron 3 Nano 4B",
1055
- hint: "NVIDIA nano",
1056
- role: "chat",
1057
- ramGb: 6,
1058
- tier: "fast",
1059
- modalities: ["text", "code"],
1060
- },
1061
- {
1062
- id: "granite-4.0-micro",
1063
- label: "Granite 4.0 Micro 3B",
1064
- hint: "IBM micro",
1065
- role: "chat",
1066
- ramGb: 4,
1067
- tier: "fast",
1068
- modalities: ["text", "code"],
1069
- },
1070
- {
1071
- id: "granite4:micro",
1072
- label: "Granite 4 Micro",
1073
- hint: "IBM micro Q4",
1074
- role: "chat",
1075
- ramGb: 4,
1076
- tier: "fast",
1077
- modalities: ["text"],
1078
- },
1079
- {
1080
- id: "moondream2",
1081
- label: "Moondream2 1.5B",
1082
- hint: "Vision",
1083
- role: "chat",
1084
- ramGb: 6,
1085
- tier: "fast",
1086
- modalities: ["text", "vision"],
1087
- },
1088
- {
1089
- id: "medgemma:4b",
1090
- label: "MedGemma 4B",
1091
- hint: "Medical",
1092
- role: "chat",
1093
- ramGb: 8,
1094
- tier: "fast",
1095
- modalities: ["text", "vision"],
1096
- },
1097
- {
1098
- id: "granite4.1:3b",
1099
- label: "Granite 4.1 3B",
1100
- hint: "IBM 3B",
1101
- role: "chat",
1102
- ramGb: 4,
1103
- tier: "fast",
1104
- modalities: ["text", "code"],
1105
- },
1106
- {
1107
- id: "llama3.2:3B-Q4_K_M",
1108
- label: "Llama 3.2 3B Q4",
1109
- hint: "Meta 3B Q4",
1110
- role: "chat",
1111
- ramGb: 4,
1112
- tier: "fast",
1113
- modalities: ["text"],
1114
- },
1115
- {
1116
- id: "gemma4:e2b",
1117
- label: "Gemma 4 E2B default",
1118
- hint: "Popular · multimodal",
1119
- role: "chat",
1120
- ramGb: 8,
1121
- tier: "fast",
1122
- modalities: ["text", "vision"],
1123
- },
1124
- {
1125
- id: "gemma3:4b-q4_K_M",
1126
- label: "Gemma 3 4B Q4",
1127
- hint: "Reasoning Q4",
1128
- role: "chat",
1129
- ramGb: 8,
1130
- tier: "fast",
1131
- modalities: ["text", "reasoning"],
1132
- },
1133
- {
1134
- id: "smollm3:Q4_K_M",
1135
- label: "SmolLM3 Q4",
1136
- hint: "On-device Q4",
1137
- role: "chat",
1138
- ramGb: 4,
1139
- tier: "fast",
1140
- modalities: ["text"],
1141
- },
1142
- {
1143
- id: "granite3.3:2b-q4_K_M",
1144
- label: "Granite 3.3 2B Q4",
1145
- hint: "IBM 2B Q4",
1146
- role: "chat",
1147
- ramGb: 4,
1148
- tier: "fast",
1149
- modalities: ["text", "code"],
1150
- },
1151
- // Balanced (~8–16GB) — Gemma 4 E4B · Qwen near the top
1152
- {
1153
- id: "qwen3:8B-Q4_K_M",
1154
- label: "Qwen3 8B",
1155
- hint: "Popular · coding · agents",
1156
- role: "chat",
1157
- recommended: true,
1158
- ramGb: 12,
1159
- tier: "balanced",
1160
- modalities: ["text", "code"],
1161
- },
1162
- {
1163
- id: "gemma4:e4b",
1164
- label: "Gemma 4 E4B",
1165
- hint: "Popular · multimodal",
1166
- role: "chat",
1167
- ramGb: 16,
1168
- tier: "balanced",
1169
- modalities: ["text", "vision", "reasoning"],
1170
- },
1171
- {
1172
- id: "qwen2.5:7B-Q4_K_M",
1173
- label: "Qwen2.5 7B",
1174
- hint: "Coding",
1175
- role: "chat",
1176
- ramGb: 12,
1177
- tier: "balanced",
1178
- modalities: ["text", "code"],
1179
- },
1180
- {
1181
- id: "qwen3-vl:8B",
1182
- label: "Qwen3-VL 8B",
1183
- hint: "Vision",
1184
- role: "chat",
1185
- ramGb: 16,
1186
- tier: "balanced",
1187
- modalities: ["text", "vision", "code"],
1188
- },
1189
- {
1190
- id: "llama3.1:8B-Q4_K_M",
1191
- label: "Llama 3.1 8B",
1192
- hint: "Meta general",
1193
- role: "chat",
1194
- ramGb: 12,
1195
- tier: "balanced",
1196
- modalities: ["text"],
1197
- },
1198
- {
1199
- id: "gemma3n:e4b",
1200
- label: "Gemma 3n E4B",
1201
- hint: "On-device multimodal",
1202
- role: "chat",
1203
- ramGb: 16,
1204
- tier: "balanced",
1205
- modalities: ["text", "vision"],
1206
- },
1207
- {
1208
- id: "mistral",
1209
- label: "Mistral 7B",
1210
- hint: "General + code",
1211
- role: "chat",
1212
- ramGb: 12,
1213
- tier: "balanced",
1214
- modalities: ["text", "code"],
1215
- },
1216
- {
1217
- id: "deepseek-r1-distill-llama:8B-Q4_K_M",
1218
- label: "DeepSeek R1 8B",
1219
- hint: "Reasoning distill",
1220
- role: "chat",
1221
- ramGb: 12,
1222
- tier: "balanced",
1223
- modalities: ["text", "reasoning"],
1224
- },
1225
- {
1226
- id: "mistral-nemo",
1227
- label: "Mistral Nemo 12B",
1228
- hint: "General",
1229
- role: "chat",
1230
- ramGb: 16,
1231
- tier: "balanced",
1232
- modalities: ["text", "code"],
1233
- },
1234
- {
1235
- id: "ministral3:8B-Q4_K_M",
1236
- label: "Ministral 8B",
1237
- hint: "Mistral instruct",
1238
- role: "chat",
1239
- ramGb: 12,
1240
- tier: "balanced",
1241
- modalities: ["text", "code"],
1242
- },
1243
- {
1244
- id: "granite-4.0-h-tiny",
1245
- label: "Granite 4.0 H Tiny 7B",
1246
- hint: "IBM tiny",
1247
- role: "chat",
1248
- ramGb: 12,
1249
- tier: "balanced",
1250
- modalities: ["text", "code"],
1251
- },
1252
- {
1253
- id: "ministral-3:8b-instruct",
1254
- label: "Ministral 3 8B Instruct",
1255
- hint: "Instruct",
1256
- role: "chat",
1257
- ramGb: 12,
1258
- tier: "balanced",
1259
- modalities: ["text", "code"],
1260
- },
1261
- {
1262
- id: "granite3.3:8b",
1263
- label: "Granite 3.3 8B",
1264
- hint: "IBM 8B",
1265
- role: "chat",
1266
- ramGb: 12,
1267
- tier: "balanced",
1268
- modalities: ["text", "code"],
1269
- },
1270
- {
1271
- id: "granite4.1:8b",
1272
- label: "Granite 4.1 8B",
1273
- hint: "IBM 8B",
1274
- role: "chat",
1275
- ramGb: 12,
1276
- tier: "balanced",
1277
- modalities: ["text", "code"],
1278
- },
1279
- {
1280
- id: "qwen3:8B-Q4_0",
1281
- label: "Qwen3 8B Q4_0",
1282
- hint: "Coding Q4_0",
1283
- role: "chat",
1284
- ramGb: 12,
1285
- tier: "balanced",
1286
- modalities: ["text", "code"],
1287
- },
1288
- {
1289
- id: "gemma4:e4b-q4_K_M",
1290
- label: "Gemma 4 E4B Q4",
1291
- hint: "Popular · multimodal Q4",
1292
- role: "chat",
1293
- ramGb: 16,
1294
- tier: "balanced",
1295
- modalities: ["text", "vision", "reasoning"],
1296
- },
1297
- {
1298
- id: "ministral-3:8b-reasoning",
1299
- label: "Ministral 3 8B Reasoning",
1300
- hint: "Reasoning",
1301
- role: "chat",
1302
- ramGb: 12,
1303
- tier: "balanced",
1304
- modalities: ["text", "reasoning", "code"],
1305
- },
1306
- {
1307
- id: "llama3.1",
1308
- label: "Llama 3.1 8B",
1309
- hint: "Meta default",
1310
- role: "chat",
1311
- ramGb: 12,
1312
- tier: "balanced",
1313
- modalities: ["text"],
1314
- },
1315
- {
1316
- id: "qwen2.5",
1317
- label: "Qwen2.5 7B",
1318
- hint: "Coding default",
1319
- role: "chat",
1320
- ramGb: 12,
1321
- tier: "balanced",
1322
- modalities: ["text", "code"],
1323
- },
1324
- {
1325
- id: "gemma3n",
1326
- label: "Gemma 3n",
1327
- hint: "On-device default",
1328
- role: "chat",
1329
- ramGb: 16,
1330
- tier: "balanced",
1331
- modalities: ["text", "vision"],
1332
- },
1333
- // Smart (~24–32GB) — Gemma 4 31B · Qwen family near the top
1334
- {
1335
- id: "phi4",
1336
- label: "Phi-4 14B",
1337
- hint: "Reasoning",
1338
- role: "chat",
1339
- recommended: true,
1340
- ramGb: 24,
1341
- tier: "smart",
1342
- modalities: ["text", "reasoning", "code"],
1343
- },
1344
- {
1345
- id: "gemma4:31b",
1346
- label: "Gemma 4 31B",
1347
- hint: "Popular · large multimodal",
1348
- role: "chat",
1349
- ramGb: 32,
1350
- tier: "smart",
1351
- modalities: ["text", "vision", "reasoning"],
1352
- },
1353
- {
1354
- id: "qwen3.5:27b",
1355
- label: "Qwen3.5 27B",
1356
- hint: "Popular · general · coding",
1357
- role: "chat",
1358
- ramGb: 32,
1359
- tier: "smart",
1360
- modalities: ["text", "code"],
1361
- },
1362
- {
1363
- id: "qwen3:30B-A3B-Q4_K_M",
1364
- label: "Qwen3 30B-A3B",
1365
- hint: "MoE · coding",
1366
- role: "chat",
1367
- ramGb: 24,
1368
- tier: "smart",
1369
- modalities: ["text", "code"],
1370
- },
1371
- {
1372
- id: "qwen3-coder:30B",
1373
- label: "Qwen3-Coder 30B",
1374
- hint: "Coding agent",
1375
- role: "chat",
1376
- ramGb: 24,
1377
- tier: "smart",
1378
- modalities: ["text", "code"],
1379
- },
1380
- {
1381
- id: "qwen3-vl:32B-UD-Q4_K_XL",
1382
- label: "Qwen3-VL 32B",
1383
- hint: "Vision",
1384
- role: "chat",
1385
- ramGb: 32,
1386
- tier: "smart",
1387
- modalities: ["text", "vision", "code"],
1388
- },
1389
- {
1390
- id: "gemma3:27b",
1391
- label: "Gemma 3 27B",
1392
- hint: "Large reasoning",
1393
- role: "chat",
1394
- ramGb: 32,
1395
- tier: "smart",
1396
- modalities: ["text", "reasoning"],
1397
- },
1398
- {
1399
- id: "qwq:32B-Q4_K_M",
1400
- label: "QwQ 32B",
1401
- hint: "Reasoning",
1402
- role: "chat",
1403
- ramGb: 32,
1404
- tier: "smart",
1405
- modalities: ["text", "reasoning"],
1406
- },
1407
- {
1408
- id: "gpt-oss:20b",
1409
- label: "GPT-OSS 20B",
1410
- hint: "Open weights",
1411
- role: "chat",
1412
- ramGb: 24,
1413
- tier: "smart",
1414
- modalities: ["text", "reasoning"],
1415
- },
1416
- {
1417
- id: "magistral-small-3.2",
1418
- label: "Magistral Small 24B",
1419
- hint: "Reasoning",
1420
- role: "chat",
1421
- ramGb: 24,
1422
- tier: "smart",
1423
- modalities: ["text", "reasoning"],
1424
- },
1425
- {
1426
- id: "ministral3:14B",
1427
- label: "Ministral 14B",
1428
- hint: "Mistral mid",
1429
- role: "chat",
1430
- ramGb: 24,
1431
- tier: "smart",
1432
- modalities: ["text", "code"],
1433
- },
1434
- {
1435
- id: "nemotron-3-nano:30b-a3b",
1436
- label: "Nemotron 3 Nano 30B-A3B",
1437
- hint: "NVIDIA MoE",
1438
- role: "chat",
1439
- ramGb: 24,
1440
- tier: "smart",
1441
- modalities: ["text", "code"],
1442
- },
1443
- {
1444
- id: "devstral-small-2",
1445
- label: "Devstral Small 2",
1446
- hint: "Coding",
1447
- role: "chat",
1448
- ramGb: 24,
1449
- tier: "smart",
1450
- modalities: ["text", "code"],
1451
- },
1452
- {
1453
- id: "medgemma:27b-text",
1454
- label: "MedGemma 27B Text",
1455
- hint: "Medical",
1456
- role: "chat",
1457
- ramGb: 32,
1458
- tier: "smart",
1459
- modalities: ["text"],
1460
- },
1461
- {
1462
- id: "gemma4:26b-a4b-q4_K_M",
1463
- label: "Gemma 4 26B-A4B",
1464
- hint: "Popular · MoE multimodal",
1465
- role: "chat",
1466
- ramGb: 32,
1467
- tier: "smart",
1468
- modalities: ["text", "vision", "reasoning"],
1469
- },
1470
- {
1471
- id: "glm-4.7-flash",
1472
- label: "GLM-4.7 Flash",
1473
- hint: "MoE flash",
1474
- role: "chat",
1475
- ramGb: 24,
1476
- tier: "smart",
1477
- modalities: ["text", "code"],
1478
- },
1479
- {
1480
- id: "granite4.1:30b",
1481
- label: "Granite 4.1 30B",
1482
- hint: "IBM 30B",
1483
- role: "chat",
1484
- ramGb: 32,
1485
- tier: "smart",
1486
- modalities: ["text", "code"],
1487
- },
1488
- {
1489
- id: "deepcoder-preview",
1490
- label: "DeepCoder Preview 14B",
1491
- hint: "Coding",
1492
- role: "chat",
1493
- ramGb: 24,
1494
- tier: "smart",
1495
- modalities: ["text", "code"],
1496
- },
1497
- {
1498
- id: "qwen3:14B-Q6_K",
1499
- label: "Qwen3 14B",
1500
- hint: "Coding mid",
1501
- role: "chat",
1502
- ramGb: 24,
1503
- tier: "smart",
1504
- modalities: ["text", "code"],
1505
- },
1506
- {
1507
- id: "nemotron3",
1508
- label: "Nemotron 3 30B",
1509
- hint: "NVIDIA",
1510
- role: "chat",
1511
- ramGb: 32,
1512
- tier: "smart",
1513
- modalities: ["text", "code"],
1514
- },
1515
- ];
1516
-
1517
- /**
1518
- * llama.cpp chat models in a tier (manual pick shows up to 20), ordered by RAM.
1519
- *
1520
- * @param tier - Speed / quality tier
1521
- */
1522
- export function llamaCppModelsForTier(tier: ModelTier): readonly CatalogModel[] {
1523
- return byRamThenLabel(LLAMA_CPP_CHAT_MODELS.filter((m) => m.tier === tier)).slice(0, 20);
1524
- }
1525
-
1526
- /**
1527
- * Recommended Docker Hub `ai/` model for a tier (RAM-aware).
393
+ * Resolve apply fields for a cloud menu id (interactive + `--yes`).
1528
394
  *
1529
- * @param tier - Selected tier
1530
- * @param totalRamGb - Prefer models that fit when RAM is known
395
+ * @param menuValue - CLOUD_PROVIDERS value
396
+ * @param overrides - Chat model / API key / base URL overrides
1531
397
  */
1532
- export function recommendLlamaCppForTier(
1533
- tier: ModelTier,
1534
- totalRamGb: number | null = null,
1535
- ): CatalogModel {
1536
- const list = llamaCppModelsForTier(tier);
1537
- const headroom = 4;
1538
- const fits =
1539
- totalRamGb !== null && Number.isFinite(totalRamGb)
1540
- ? list.filter((m) => m.ramGb + headroom <= totalRamGb || m.ramGb <= totalRamGb)
1541
- : list;
1542
- const pool = fits.length > 0 ? fits : list;
1543
- return pool.find((m) => m.recommended) ?? pool[pool.length - 1] ?? list[0]!;
398
+ export function cloudApplyDefaults(
399
+ menuValue: string,
400
+ overrides: {
401
+ readonly chatModel?: string;
402
+ readonly apiKey?: string;
403
+ readonly baseUrl?: string;
404
+ } = {},
405
+ ): {
406
+ readonly driver: "openai-compatible" | "anthropic";
407
+ readonly provider: string;
408
+ readonly baseUrl?: string;
409
+ readonly chatModel: string;
410
+ readonly visionModel: null;
411
+ readonly embedModel: null;
412
+ readonly apiKeyEnv?: string;
413
+ readonly apiKey?: string;
414
+ } {
415
+ const meta = CLOUD_PROVIDERS.find((p) => p.value === menuValue);
416
+ if (!meta) {
417
+ throw new Error(`oke ai setup: unknown cloud provider "${menuValue}"`);
418
+ }
419
+ const provider = meta.provider ?? meta.value;
420
+ const registryKnown = new Set([
421
+ "openai",
422
+ "openrouter",
423
+ "groq",
424
+ "together",
425
+ "deepinfra",
426
+ "xai",
427
+ "mistral",
428
+ "deepseek",
429
+ "vercel",
430
+ "google",
431
+ "gemini",
432
+ "anthropic",
433
+ ]);
434
+ // Registry openai-compat: omit baseUrl so ai.model auto-resolves.
435
+ const omitBase = registryKnown.has(provider) && meta.driver === "openai-compatible";
436
+ const baseUrl = omitBase ? undefined : (overrides.baseUrl ?? meta.baseUrl);
437
+ return {
438
+ driver: meta.driver,
439
+ provider,
440
+ ...(baseUrl !== undefined ? { baseUrl } : {}),
441
+ chatModel: overrides.chatModel ?? recommendCloudChat(menuValue),
442
+ visionModel: null,
443
+ embedModel: null,
444
+ ...(meta.apiKeyEnv
445
+ ? {
446
+ apiKeyEnv: meta.apiKeyEnv,
447
+ ...(overrides.apiKey ? { apiKey: overrides.apiKey } : {}),
448
+ }
449
+ : {}),
450
+ };
1544
451
  }