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 +21 -2
- package/README.md +2 -1
- package/package.json +1 -1
- package/src/app.js +3 -0
- package/src/command-palette.js +1 -0
- package/src/endpoint-installer.js +1 -1
- package/src/installed-models-manager.js +53 -1
- package/src/key-handler.js +7 -1
- package/src/kilo-config.js +43 -0
- package/src/kilo.js +200 -0
- package/src/opencode.js +6 -24
- package/src/tool-bootstrap.js +10 -0
- package/src/tool-metadata.js +3 -0
- package/src/utils.js +3 -1
- package/web/dist/assets/{index-D49esfAN.js → index-C03JjCgA.js} +1 -1
- package/web/dist/index.html +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,8 +1,27 @@
|
|
|
1
|
-
## [0.3.
|
|
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
|
-
- **
|
|
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.
|
|
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,
|
package/src/command-palette.js
CHANGED
|
@@ -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) {
|
package/src/key-handler.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
554
|
-
|
|
555
|
-
|
|
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('
|
|
596
|
+
console.log(chalk.dim(' Starting OpenCode Web...'))
|
|
615
597
|
console.log()
|
|
616
598
|
|
|
617
|
-
await
|
|
599
|
+
await spawnOpenCode(['web', '--model', modelRef], providerKey, fcmConfig)
|
|
618
600
|
}
|
|
619
601
|
|
|
620
602
|
// ─── Start OpenCode Desktop ───────────────────────────────────────────────────
|
package/src/tool-bootstrap.js
CHANGED
|
@@ -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/',
|
package/src/tool-metadata.js
CHANGED
|
@@ -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,
|