free-coding-models 0.3.52 → 0.3.55

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 CHANGED
@@ -1,8 +1,27 @@
1
- ## [0.3.52] - 2026-04-18
1
+ ## [0.3.55] - 2026-04-18
2
+
3
+ ### Changed
4
+
5
+ - **Direct Launch Attempt** — Tool launchers for OpenCode and Kilo now attempt to spawn the command directly instead of blocking with a pre-installation check. Installation instructions are now provided via the process error handler for a more seamless experience.
6
+ - **Fixed OpenCode NPM Command** — Updated the suggested installation command for OpenCode to `npm install -g opencode-ai`.
7
+
8
+ ## [0.3.54] - 2026-04-18
9
+
10
+ ### Changed
11
+
12
+ - **Improved OpenCode WebUI Launch** — The `--opencode-web` flag now correctly spawns the `opencode web` command, providing a integrated browser-based coding experience with pre-configured model selection.
2
13
 
3
14
  ### Added
4
15
 
5
- - **OpenCode WebUI Support** — Added `--opencode-web` flag to open the OpenCode WebUI dashboard after configuring the selected model. This mirrors the existing `--opencode-desktop` behavior.
16
+ - **Kilo CLI Support** — Added `--kilo` flag to launch the Kilo CLI with the selected model. Kilo is a fork of OpenCode and shares the same configuration structure (stored in `~/.config/kilo/opencode.json`).
17
+
18
+ ## [0.3.53] - 2026-04-18
19
+
20
+ ### Added
21
+
22
+ - **OpenCode WebUI Support** — Added `--opencode-web` flag to open the OpenCode WebUI dashboard after configuring the selected model.
23
+
24
+ ## [0.3.52] - 2026-04-18
6
25
 
7
26
  ## [0.3.51] - 2026-04-11
8
27
 
package/README.md CHANGED
@@ -16,7 +16,7 @@
16
16
 
17
17
  <p align="center">
18
18
  <strong>Find the fastest free coding model in seconds</strong><br>
19
- <sub>Ping 238 models across 25 AI Free providers in real-time </sub><br> <sub> Install Free API endpoints to your favorite AI coding tool: <br>📦 OpenCode, 📦 OpenCode Desktop, 📦 OpenCode WebUI, 🦞 OpenClaw, 💘 Crush, 🪿 Goose, 🛠 Aider, 🐉 Qwen Code, 🤲 OpenHands, ⚡ Amp, 🔮 Hermes, ▶️ Continue, 🧠 Cline, 🛠️ Xcode, π Pi, 🦘 Rovo or ♊ Gemini in one keystroke</sub>
19
+ <sub>Ping 238 models across 25 AI Free providers in real-time </sub><br> <sub> Install Free API endpoints to your favorite AI coding tool: <br>📦 OpenCode, 📦 OpenCode Desktop, 📦 OpenCode WebUI, 🦞 OpenClaw, 💘 Crush, 🪿 Goose, 🛠 Aider, ⚡️ Kilo CLI, 🐉 Qwen Code, 🤲 OpenHands, ⚡ Amp, 🔮 Hermes, ▶️ Continue, 🧠 Cline, 🛠️ Xcode, π Pi, 🦘 Rovo or ♊ Gemini in one keystroke</sub>
20
20
  </p>
21
21
 
22
22
 
@@ -267,6 +267,7 @@ When launching the web dashboard, `free-coding-models` prefers `http://localhost
267
267
  | `--crush` | 💘 Crush |
268
268
  | `--goose` | 🪿 Goose |
269
269
  | `--aider` | 🛠 Aider |
270
+ | `--kilo` | ⚡️ Kilo CLI |
270
271
  | `--qwen` | 🐉 Qwen Code |
271
272
  | `--openhands` | 🤲 OpenHands |
272
273
  | `--amp` | ⚡ Amp |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "free-coding-models",
3
- "version": "0.3.52",
3
+ "version": "0.3.55",
4
4
  "description": "Find the fastest coding LLM models in seconds — ping free models from multiple providers, pick the best one for OpenCode, Cursor, or any AI coding assistant.",
5
5
  "keywords": [
6
6
  "nvidia",
package/src/app.js CHANGED
@@ -118,6 +118,7 @@ import { syncShellEnv, ensureShellRcSource, promptShellEnvMigration, removeShell
118
118
  import { stripAnsi, maskApiKey, displayWidth, padEndDisplay, tintOverlayLines, keepOverlayTargetVisible, sliceOverlayLines, calculateViewport, sortResultsWithPinnedFavorites, adjustScrollOffset } from '../src/render-helpers.js'
119
119
  import { renderTable, PROVIDER_COLOR } from '../src/render-table.js'
120
120
  import { setOpenCodeModelData, startOpenCode, startOpenCodeDesktop, startOpenCodeWeb } from '../src/opencode.js'
121
+ import { startKilo } from '../src/kilo.js'
121
122
  import { startOpenClaw } from '../src/openclaw.js'
122
123
  import { createOverlayRenderers } from '../src/overlays.js'
123
124
  import { createKeyHandler, createMouseEventHandler } from '../src/key-handler.js'
@@ -257,6 +258,7 @@ export async function runApp(cliArgs, config) {
257
258
  aider: cliArgs.aiderMode,
258
259
  crush: cliArgs.crushMode,
259
260
  goose: cliArgs.gooseMode,
261
+ kilo: cliArgs.kiloMode,
260
262
  qwen: cliArgs.qwenMode,
261
263
  openhands: cliArgs.openHandsMode,
262
264
  amp: cliArgs.ampMode,
@@ -874,6 +876,7 @@ export async function runApp(cliArgs, config) {
874
876
  startOpenClaw,
875
877
  startOpenCodeDesktop,
876
878
  startOpenCodeWeb,
879
+ startKilo,
877
880
  startOpenCode,
878
881
  startExternalTool,
879
882
  getToolModeOrder,
@@ -26,6 +26,7 @@ const TOOL_MODE_DESCRIPTIONS = {
26
26
  goose: 'Launch Goose and preselect the active model.',
27
27
  pi: 'Launch Pi with model/provider flags.',
28
28
  aider: 'Launch Aider configured on the selected model.',
29
+ kilo: 'Set model in shared config, then launch Kilo CLI.',
29
30
  qwen: 'Launch Qwen Code using the selected provider model.',
30
31
  openhands: 'Launch OpenHands with the selected model endpoint.',
31
32
  amp: 'Launch Amp with this model as active target.',
@@ -52,7 +52,7 @@ import { getToolMeta } from './tool-metadata.js'
52
52
  const DIRECT_INSTALL_UNSUPPORTED_PROVIDERS = new Set(['replicate', 'zai', 'rovo', 'gemini', 'opencode-zen'])
53
53
  // 📖 Install Endpoints only lists tools whose persisted config shape is actually supported here.
54
54
  // 📖 Claude Code, Codex, and Gemini stay out while their dedicated bridges are being rebuilt.
55
- const INSTALL_TARGET_MODES = ['opencode', 'opencode-desktop', 'openclaw', 'crush', 'goose', 'pi', 'aider', 'qwen', 'openhands', 'amp', 'hermes', 'continue', 'cline']
55
+ const INSTALL_TARGET_MODES = ['opencode', 'opencode-desktop', 'opencode-web', 'openclaw', 'kilo', 'crush', 'goose', 'pi', 'aider', 'qwen', 'openhands', 'amp', 'hermes', 'continue', 'cline']
56
56
 
57
57
  function getDefaultPaths() {
58
58
  const home = homedir()
@@ -14,6 +14,7 @@
14
14
  * - Goose (~/.config/goose/config.yaml + custom_providers/*.json)
15
15
  * - Crush (~/.config/crush/crush.json)
16
16
  * - Aider (~/.aider.conf.yml)
17
+ * - Kilo (~/.config/kilo/opencode.json)
17
18
  * - Qwen (~/.qwen/settings.json)
18
19
  * - Pi (~/.pi/agent/models.json + settings.json)
19
20
  * - OpenHands (~/.fcm-openhands-env)
@@ -29,6 +30,7 @@
29
30
  * → parseGooseConfig — Parse Goose YAML config
30
31
  * → parseCrushConfig — Parse Crush JSON config
31
32
  * → parseAiderConfig — Parse Aider YAML config
33
+ * → parseKiloConfig — Parse Kilo JSON config
32
34
  * → parseQwenConfig — Parse Qwen JSON config
33
35
  * → parsePiConfig — Parse Pi JSON configs
34
36
  * → parseOpenHandsConfig — Parse OpenHands env file
@@ -58,6 +60,7 @@ function getToolConfigPaths(homeDir = homedir()) {
58
60
  goose: join(homeDir, '.config', 'goose', 'config.yaml'),
59
61
  crush: join(homeDir, '.config', 'crush', 'crush.json'),
60
62
  aider: join(homeDir, '.aider.conf.yml'),
63
+ kilo: join(homeDir, '.config', 'kilo', 'opencode.json'),
61
64
  qwen: join(homeDir, '.qwen', 'settings.json'),
62
65
  piModels: join(homeDir, '.pi', 'agent', 'models.json'),
63
66
  piSettings: join(homeDir, '.pi', 'agent', 'settings.json'),
@@ -270,6 +273,43 @@ function parseAiderConfig(paths = getToolConfigPaths()) {
270
273
  }
271
274
  }
272
275
 
276
+ /**
277
+ * 📖 Parse Kilo config for model
278
+ */
279
+ function parseKiloConfig(paths = getToolConfigPaths()) {
280
+ const configPath = paths.kilo
281
+ if (!existsSync(configPath)) {
282
+ return { isValid: false, models: [], configPath }
283
+ }
284
+
285
+ try {
286
+ const content = readFileSync(configPath, 'utf8')
287
+ const config = JSON.parse(content)
288
+
289
+ const models = []
290
+ if (config.model) {
291
+ models.push({
292
+ modelId: config.model,
293
+ label: config.model,
294
+ tier: '-',
295
+ sweScore: '-',
296
+ providerKey: 'external',
297
+ isExternal: true,
298
+ canLaunch: true,
299
+ })
300
+ }
301
+
302
+ return {
303
+ isValid: true,
304
+ hasManagedMarker: true, // Kilo CLI integration always uses modelRef format
305
+ models,
306
+ configPath,
307
+ }
308
+ } catch (err) {
309
+ return { isValid: false, models: [], configPath }
310
+ }
311
+ }
312
+
273
313
  /**
274
314
  * 📖 Parse Qwen config for model
275
315
  */
@@ -459,6 +499,8 @@ export function parseToolConfig(toolMode, paths = getToolConfigPaths()) {
459
499
  return parseCrushConfig(paths)
460
500
  case 'aider':
461
501
  return parseAiderConfig(paths)
502
+ case 'kilo':
503
+ return parseKiloConfig(paths)
462
504
  case 'qwen':
463
505
  return parseQwenConfig(paths)
464
506
  case 'pi':
@@ -476,7 +518,7 @@ export function parseToolConfig(toolMode, paths = getToolConfigPaths()) {
476
518
  * 📖 Scan all tool configs and return structured results
477
519
  */
478
520
  export function scanAllToolConfigs(paths = getToolConfigPaths()) {
479
- const toolModes = ['goose', 'crush', 'aider', 'qwen', 'pi', 'openhands', 'amp']
521
+ const toolModes = ['goose', 'crush', 'aider', 'kilo', 'qwen', 'pi', 'openhands', 'amp']
480
522
 
481
523
  return toolModes.map((toolMode) => {
482
524
  const result = parseToolConfig(toolMode, paths)
@@ -499,6 +541,7 @@ function getToolEmoji(toolMode) {
499
541
  goose: '🪿',
500
542
  crush: '💘',
501
543
  aider: '🛠',
544
+ kilo: '⚡️',
502
545
  qwen: '🐉',
503
546
  pi: 'π',
504
547
  openhands: '🤲',
@@ -576,6 +619,15 @@ export function softDeleteModel(toolMode, modelId, paths = getToolConfigPaths())
576
619
  }
577
620
  break
578
621
 
622
+ case 'kilo':
623
+ const kiloConfig = JSON.parse(originalContent)
624
+ if (kiloConfig.model === modelId) {
625
+ delete kiloConfig.model
626
+ newContent = JSON.stringify(kiloConfig, null, 2)
627
+ modified = true
628
+ }
629
+ break
630
+
579
631
  case 'qwen':
580
632
  const qwenConfig = JSON.parse(originalContent)
581
633
  if (qwenConfig.model === modelId) {
@@ -265,6 +265,7 @@ export function createKeyHandler(ctx) {
265
265
  startOpenClaw,
266
266
  startOpenCodeDesktop,
267
267
  startOpenCodeWeb,
268
+ startKilo,
268
269
  startOpenCode,
269
270
  startExternalTool,
270
271
  getToolModeOrder,
@@ -306,7 +307,10 @@ export function createKeyHandler(ctx) {
306
307
  }
307
308
 
308
309
  function shouldCheckMissingTool(mode) {
309
- return mode !== 'opencode-desktop'
310
+ // 📖 opencode-desktop doesn't have a binary check (it uses 'open -a').
311
+ // 📖 opencode-web, opencode, and kilo manage their own ENOENT errors in spawn handlers.
312
+ // 📖 xcode uses 'open -a Xcode' which doesn't need a binary path resolution.
313
+ return !['opencode-desktop', 'opencode-web', 'opencode', 'kilo', 'xcode'].includes(mode)
310
314
  }
311
315
 
312
316
  function getModelTelemetryFamily(providerKey) {
@@ -498,6 +502,8 @@ export function createKeyHandler(ctx) {
498
502
  exitCode = await startOpenCodeDesktop(userSelected, state.config)
499
503
  } else if (state.mode === 'opencode-web') {
500
504
  exitCode = await startOpenCodeWeb(userSelected, state.config)
505
+ } else if (state.mode === 'kilo') {
506
+ exitCode = await startKilo(userSelected, state.config)
501
507
  } else if (state.mode === 'opencode') {
502
508
  exitCode = await startOpenCode(userSelected, state.config)
503
509
  } else {
@@ -0,0 +1,43 @@
1
+ /**
2
+ * @file src/kilo-config.js
3
+ * @description Small filesystem helpers for the shared Kilo config file (OpenCode fork).
4
+ *
5
+ * @details
6
+ * 📖 Kilo is a fork of OpenCode and uses the same config structure,
7
+ * 📖 but stored in a different directory: ~/.config/kilo/opencode.json
8
+ *
9
+ * @functions
10
+ * → `loadKiloConfig` — read `~/.config/kilo/opencode.json` safely
11
+ * → `saveKiloConfig` — write `opencode.json` with a simple backup
12
+ *
13
+ * @exports loadKiloConfig, saveKiloConfig
14
+ */
15
+
16
+ import { readFileSync, writeFileSync, copyFileSync, existsSync, mkdirSync } from 'node:fs'
17
+ import { join } from 'node:path'
18
+ import { homedir } from 'node:os'
19
+
20
+ const KILO_CONFIG_DIR = join(homedir(), '.config', 'kilo')
21
+ const KILO_CONFIG_PATH = join(KILO_CONFIG_DIR, 'opencode.json')
22
+ const KILO_BACKUP_PATH = join(KILO_CONFIG_DIR, 'opencode.json.bak')
23
+
24
+ export function loadKiloConfig() {
25
+ try {
26
+ if (existsSync(KILO_CONFIG_PATH)) {
27
+ return JSON.parse(readFileSync(KILO_CONFIG_PATH, 'utf8'))
28
+ }
29
+ } catch {}
30
+ return {}
31
+ }
32
+
33
+ export function saveKiloConfig(config) {
34
+ mkdirSync(KILO_CONFIG_DIR, { recursive: true })
35
+ if (existsSync(KILO_CONFIG_PATH)) {
36
+ copyFileSync(KILO_CONFIG_PATH, KILO_BACKUP_PATH)
37
+ }
38
+ writeFileSync(KILO_CONFIG_PATH, JSON.stringify(config, null, 2) + '\n')
39
+ }
40
+
41
+ export function getKiloConfigPath() {
42
+ return KILO_CONFIG_PATH
43
+ }
package/src/kilo.js ADDED
@@ -0,0 +1,200 @@
1
+ /**
2
+ * @file kilo.js
3
+ * @description Kilo CLI integration helpers for direct launches (OpenCode fork).
4
+ */
5
+
6
+ import chalk from 'chalk'
7
+ import { PROVIDER_COLOR } from './render-table.js'
8
+ import { loadKiloConfig, saveKiloConfig, getKiloConfigPath } from './kilo-config.js'
9
+ import { getApiKey } from './config.js'
10
+ import { ENV_VAR_NAMES, OPENCODE_MODEL_MAP } from './provider-metadata.js'
11
+ import { resolveToolBinaryPath } from './tool-bootstrap.js'
12
+
13
+ // 📖 Map source model IDs to Kilo built-in IDs (same as OpenCode).
14
+ function getKiloModelId(providerKey, modelId) {
15
+ if (providerKey === 'nvidia') return modelId.replace(/^nvidia\//, '')
16
+ if (providerKey === 'zai') return modelId.replace(/^zai\//, '')
17
+ return OPENCODE_MODEL_MAP[providerKey]?.[modelId] || modelId
18
+ }
19
+
20
+ // 📖 spawnKilo: Resolve API keys + spawn kilo CLI with correct env.
21
+ async function spawnKilo(args, providerKey, fcmConfig) {
22
+ const envVarName = ENV_VAR_NAMES[providerKey]
23
+ const resolvedKey = getApiKey(fcmConfig, providerKey)
24
+ const childEnv = { ...process.env }
25
+ childEnv.NODE_NO_WARNINGS = '1'
26
+ const finalArgs = [...args]
27
+
28
+ if (envVarName && resolvedKey) childEnv[envVarName] = resolvedKey
29
+
30
+ const { spawn } = await import('child_process')
31
+ const child = spawn(resolveToolBinaryPath('kilo') || 'kilo', finalArgs, {
32
+ stdio: 'inherit',
33
+ shell: true,
34
+ detached: false,
35
+ env: childEnv
36
+ })
37
+
38
+ return new Promise((resolve, reject) => {
39
+ child.on('exit', (code) => resolve(code))
40
+ child.on('error', (err) => {
41
+ if (err.code === 'ENOENT') {
42
+ console.error(chalk.red('\n X Could not find "kilo" -- is it installed and in your PATH?'))
43
+ console.error(chalk.dim(' Install: npm i -g @kilocode/cli or see https://kilo.ai'))
44
+ resolve(1)
45
+ } else {
46
+ reject(err)
47
+ }
48
+ })
49
+ })
50
+ }
51
+
52
+ // ─── Start Kilo CLI ──────────────────────────────────────────────────────────
53
+
54
+ export async function startKilo(model, fcmConfig) {
55
+ const providerKey = model.providerKey ?? 'nvidia'
56
+ const ocModelId = getKiloModelId(providerKey, model.modelId)
57
+ const modelRef = `${providerKey}/${ocModelId}`
58
+
59
+ console.log(chalk.green(` Setting ${chalk.bold(model.label)} as default...`))
60
+ console.log(chalk.dim(` Model: ${modelRef}`))
61
+ console.log()
62
+
63
+ const config = loadKiloConfig()
64
+
65
+ if (!config.provider) config.provider = {}
66
+ if (!config.provider[providerKey]) {
67
+ // 📖 Auto-configure provider if missing (same as OpenCode logic)
68
+ if (providerKey === 'nvidia') {
69
+ config.provider.nvidia = {
70
+ npm: '@ai-sdk/openai-compatible',
71
+ name: 'NVIDIA NIM',
72
+ options: { baseURL: 'https://integrate.api.nvidia.com/v1', apiKey: '{env:NVIDIA_API_KEY}' },
73
+ models: {}
74
+ }
75
+ } else if (providerKey === 'groq') {
76
+ config.provider.groq = { options: { apiKey: '{env:GROQ_API_KEY}' }, models: {} }
77
+ } else if (providerKey === 'cerebras') {
78
+ config.provider.cerebras = {
79
+ npm: '@ai-sdk/openai-compatible',
80
+ name: 'Cerebras',
81
+ options: { baseURL: 'https://api.cerebras.ai/v1', apiKey: '{env:CEREBRAS_API_KEY}' },
82
+ models: {}
83
+ }
84
+ } else if (providerKey === 'sambanova') {
85
+ config.provider.sambanova = {
86
+ npm: '@ai-sdk/openai-compatible',
87
+ name: 'SambaNova',
88
+ options: { baseURL: 'https://api.sambanova.ai/v1', apiKey: '{env:SAMBANOVA_API_KEY}' },
89
+ models: {}
90
+ }
91
+ } else if (providerKey === 'openrouter') {
92
+ config.provider.openrouter = {
93
+ npm: '@ai-sdk/openai-compatible',
94
+ name: 'OpenRouter',
95
+ options: { baseURL: 'https://openrouter.ai/api/v1', apiKey: '{env:OPENROUTER_API_KEY}' },
96
+ models: {}
97
+ }
98
+ } else if (providerKey === 'huggingface') {
99
+ config.provider.huggingface = {
100
+ npm: '@ai-sdk/openai-compatible',
101
+ name: 'Hugging Face Inference',
102
+ options: { baseURL: 'https://router.huggingface.co/v1', apiKey: '{env:HUGGINGFACE_API_KEY}' },
103
+ models: {}
104
+ }
105
+ } else if (providerKey === 'deepinfra') {
106
+ config.provider.deepinfra = {
107
+ npm: '@ai-sdk/openai-compatible',
108
+ name: 'DeepInfra',
109
+ options: { baseURL: 'https://api.deepinfra.com/v1/openai', apiKey: '{env:DEEPINFRA_API_KEY}' },
110
+ models: {}
111
+ }
112
+ } else if (providerKey === 'fireworks') {
113
+ config.provider.fireworks = {
114
+ npm: '@ai-sdk/openai-compatible',
115
+ name: 'Fireworks AI',
116
+ options: { baseURL: 'https://api.fireworks.ai/inference/v1', apiKey: '{env:FIREWORKS_API_KEY}' },
117
+ models: {}
118
+ }
119
+ } else if (providerKey === 'codestral') {
120
+ config.provider.codestral = {
121
+ npm: '@ai-sdk/openai-compatible',
122
+ name: 'Mistral Codestral',
123
+ options: { baseURL: 'https://api.mistral.ai/v1', apiKey: '{env:CODESTRAL_API_KEY}' },
124
+ models: {}
125
+ }
126
+ } else if (providerKey === 'hyperbolic') {
127
+ config.provider.hyperbolic = {
128
+ npm: '@ai-sdk/openai-compatible',
129
+ name: 'Hyperbolic',
130
+ options: { baseURL: 'https://api.hyperbolic.xyz/v1', apiKey: '{env:HYPERBOLIC_API_KEY}' },
131
+ models: {}
132
+ }
133
+ } else if (providerKey === 'scaleway') {
134
+ config.provider.scaleway = {
135
+ npm: '@ai-sdk/openai-compatible',
136
+ name: 'Scaleway',
137
+ options: { baseURL: 'https://api.scaleway.ai/v1', apiKey: '{env:SCALEWAY_API_KEY}' },
138
+ models: {}
139
+ }
140
+ } else if (providerKey === 'googleai') {
141
+ config.provider.googleai = {
142
+ npm: '@ai-sdk/openai-compatible',
143
+ name: 'Google AI Studio',
144
+ options: { baseURL: 'https://generativelanguage.googleapis.com/v1beta/openai', apiKey: '{env:GOOGLE_API_KEY}' },
145
+ models: {}
146
+ }
147
+ } else if (providerKey === 'siliconflow') {
148
+ config.provider.siliconflow = {
149
+ npm: '@ai-sdk/openai-compatible',
150
+ name: 'SiliconFlow',
151
+ options: { baseURL: 'https://api.siliconflow.com/v1', apiKey: '{env:SILICONFLOW_API_KEY}' },
152
+ models: {}
153
+ }
154
+ } else if (providerKey === 'together') {
155
+ config.provider.together = {
156
+ npm: '@ai-sdk/openai-compatible',
157
+ name: 'Together AI',
158
+ options: { baseURL: 'https://api.together.xyz/v1', apiKey: '{env:TOGETHER_API_KEY}' },
159
+ models: {}
160
+ }
161
+ } else if (providerKey === 'perplexity') {
162
+ config.provider.perplexity = {
163
+ npm: '@ai-sdk/openai-compatible',
164
+ name: 'Perplexity API',
165
+ options: { baseURL: 'https://api.perplexity.ai', apiKey: '{env:PERPLEXITY_API_KEY}' },
166
+ models: {}
167
+ }
168
+ } else if (providerKey === 'chutes') {
169
+ config.provider.chutes = {
170
+ npm: '@ai-sdk/openai-compatible',
171
+ name: 'Chutes AI',
172
+ options: { baseURL: 'https://chutes.ai/v1', apiKey: '{env:CHUTES_API_KEY}' },
173
+ models: {}
174
+ }
175
+ } else if (providerKey === 'ovhcloud') {
176
+ config.provider.ovhcloud = {
177
+ npm: '@ai-sdk/openai-compatible',
178
+ name: 'OVHcloud AI',
179
+ options: { baseURL: 'https://oai.endpoints.kepler.ai.cloud.ovh.net/v1', apiKey: '{env:OVH_AI_ENDPOINTS_ACCESS_TOKEN}' },
180
+ models: {}
181
+ }
182
+ }
183
+ }
184
+
185
+ const isBuiltinMapped = OPENCODE_MODEL_MAP[providerKey]?.[model.modelId]
186
+ if (!isBuiltinMapped && config.provider[providerKey]) {
187
+ if (!config.provider[providerKey].models) config.provider[providerKey].models = {}
188
+ config.provider[providerKey].models[ocModelId] = { name: model.label }
189
+ }
190
+
191
+ config.model = modelRef
192
+ saveKiloConfig(config)
193
+
194
+ console.log(chalk.dim(` Config saved to: ${getKiloConfigPath()}`))
195
+ console.log()
196
+ console.log(chalk.dim(' Starting Kilo...'))
197
+ console.log()
198
+
199
+ await spawnKilo(['--model', modelRef], providerKey, fcmConfig)
200
+ }
package/src/opencode.js CHANGED
@@ -203,7 +203,7 @@ async function spawnOpenCode(args, providerKey, fcmConfig, existingZaiProxy = nu
203
203
  if (zaiProxy) zaiProxy.close()
204
204
  if (err.code === 'ENOENT') {
205
205
  console.error(chalk.red('\n X Could not find "opencode" -- is it installed and in your PATH?'))
206
- console.error(chalk.dim(' Install: npm i -g opencode or see https://opencode.ai'))
206
+ console.error(chalk.dim(' Install: npm i -g opencode-ai or see https://opencode.ai'))
207
207
  resolve(1)
208
208
  } else {
209
209
  reject(err)
@@ -550,26 +550,10 @@ export async function startOpenCodeWeb(model, fcmConfig) {
550
550
  const ocModelId = getOpenCodeModelId(providerKey, model.modelId)
551
551
  const modelRef = `${providerKey}/${ocModelId}`
552
552
 
553
- const launchWeb = async () => {
554
- const { exec } = await import('child_process')
555
- const url = 'https://opencode.ai'
556
- let command
557
- if (isMac) {
558
- command = `open "${url}"`
559
- } else if (isWindows) {
560
- command = `start "${url}"`
561
- } else {
562
- command = `xdg-open "${url}"`
563
- }
564
- exec(command, (err) => {
565
- if (err) {
566
- console.error(chalk.red(' Could not open OpenCode WebUI'))
567
- console.error(chalk.dim(` Please visit ${url} manually`))
568
- }
569
- })
570
- }
553
+ console.log(chalk.green(` Setting ${chalk.bold(model.label)} as default...`))
554
+ console.log(chalk.dim(` Model: ${modelRef}`))
555
+ console.log()
571
556
 
572
- // 📖 Mirror OpenCode Desktop behavior: set the model in opencode.json
573
557
  const config = loadOpenCodeConfig()
574
558
  const backupPath = `${getOpenCodeConfigPath()}.backup-${Date.now()}`
575
559
 
@@ -599,8 +583,6 @@ export async function startOpenCodeWeb(model, fcmConfig) {
599
583
  }
600
584
  }
601
585
  // ... other providers are handled as they are selected
602
-
603
- console.log(chalk.green(` Setting ${chalk.bold(model.label)} as default (mirroring Desktop)...`))
604
586
 
605
587
  if (providerKey !== 'opencode-zen' && config.provider[providerKey]) {
606
588
  if (!config.provider[providerKey].models) config.provider[providerKey].models = {}
@@ -611,10 +593,10 @@ export async function startOpenCodeWeb(model, fcmConfig) {
611
593
  saveOpenCodeConfig(config)
612
594
 
613
595
  console.log(chalk.dim(` Config saved to: ${getOpenCodeConfigPath()}`))
614
- console.log(chalk.dim(' Opening OpenCode WebUI...'))
596
+ console.log(chalk.dim(' Starting OpenCode Web...'))
615
597
  console.log()
616
598
 
617
- await launchWeb()
599
+ await spawnOpenCode(['web', '--model', modelRef], providerKey, fcmConfig)
618
600
  }
619
601
 
620
602
  // ─── Start OpenCode Desktop ───────────────────────────────────────────────────
@@ -173,6 +173,16 @@ export const TOOL_BOOTSTRAP_METADATA = {
173
173
  },
174
174
  },
175
175
  },
176
+ kilo: {
177
+ binary: 'kilo',
178
+ docsUrl: 'https://kilo.ai/docs/cli',
179
+ install: {
180
+ default: {
181
+ shellCommand: 'npm install -g @kilocode/cli',
182
+ summary: 'Install Kilo CLI globally via npm.',
183
+ },
184
+ },
185
+ },
176
186
  qwen: {
177
187
  binary: 'qwen',
178
188
  docsUrl: 'https://qwenlm.github.io/qwen-code-docs/en/users/quickstart/',
@@ -34,6 +34,7 @@ export const TOOL_METADATA = {
34
34
  goose: { label: 'Goose', emoji: '🪿', flag: '--goose', color: [132, 235, 168] },
35
35
  pi: { label: 'Pi', emoji: 'π', flag: '--pi', color: [173, 216, 230] },
36
36
  aider: { label: 'Aider', emoji: '🛠', flag: '--aider', color: [255, 208, 102] },
37
+ kilo: { label: 'Kilo CLI', emoji: '⚡️', flag: '--kilo', color: [255, 107, 107] },
37
38
  qwen: { label: 'Qwen Code', emoji: '🐉', flag: '--qwen', color: [255, 213, 128] },
38
39
  openhands: { label: 'OpenHands', emoji: '🤲', flag: '--openhands', color: [228, 191, 239] },
39
40
  amp: { label: 'Amp', emoji: '⚡', flag: '--amp', color: [255, 232, 98] },
@@ -56,6 +57,7 @@ export const COMPAT_COLUMN_SLOTS = [
56
57
  { emoji: '🪿', toolKeys: ['goose'], color: [132, 235, 168] },
57
58
  { emoji: 'π', toolKeys: ['pi'], color: [173, 216, 230] },
58
59
  { emoji: '🛠', toolKeys: ['aider'], color: [255, 208, 102] },
60
+ { emoji: '⚡️', toolKeys: ['kilo'], color: [255, 107, 107] },
59
61
  { emoji: '🐉', toolKeys: ['qwen'], color: [255, 213, 128] },
60
62
  { emoji: '🤲', toolKeys: ['openhands'], color: [228, 191, 239] },
61
63
  { emoji: '⚡', toolKeys: ['amp'], color: [255, 232, 98] },
@@ -78,6 +80,7 @@ export const TOOL_MODE_ORDER = [
78
80
  'crush',
79
81
  'goose',
80
82
  'aider',
83
+ 'kilo',
81
84
  'qwen',
82
85
  'openhands',
83
86
  'amp',
package/src/utils.js CHANGED
@@ -388,7 +388,7 @@ export function findBestModel(results) {
388
388
  // 📖 Argument types:
389
389
  // - API key: first positional arg that does not look like a CLI flag (e.g., "nvapi-xxx")
390
390
  // - Boolean flags: --best, --fiable, --opencode, --opencode-desktop, --opencode-web, --openclaw,
391
- // --aider, --crush, --goose, --qwen,
391
+ // --aider, --crush, --goose, --qwen, --kilo,
392
392
  // --openhands, --amp, --pi, --no-telemetry, --json, --help/-h (case-insensitive)
393
393
  // - Value flag: --tier <letter> (the next non-flag arg is the tier value)
394
394
  //
@@ -452,6 +452,7 @@ export function parseArgs(argv) {
452
452
  const crushMode = flags.includes('--crush')
453
453
  const gooseMode = flags.includes('--goose')
454
454
  const qwenMode = flags.includes('--qwen')
455
+ const kiloMode = flags.includes('--kilo')
455
456
  const openHandsMode = flags.includes('--openhands')
456
457
  const ampMode = flags.includes('--amp')
457
458
  const piMode = flags.includes('--pi')
@@ -499,6 +500,7 @@ export function parseArgs(argv) {
499
500
  crushMode,
500
501
  gooseMode,
501
502
  qwenMode,
503
+ kiloMode,
502
504
  openHandsMode,
503
505
  ampMode,
504
506
  piMode,