free-coding-models 0.3.65 → 0.3.66
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 +11 -6
- package/README.md +21 -23
- package/package.json +4 -5
- package/sources.js +1 -10
- package/src/app.js +2 -0
- package/src/endpoint-installer.js +63 -1
- package/src/key-handler.js +1 -1
- package/src/tool-bootstrap.js +22 -0
- package/src/tool-launchers.js +144 -0
- package/src/tool-metadata.js +7 -2
- package/src/utils.js +8 -2
- package/web/dist/assets/{index-DqMmOpV2.js → index-BKwbbLPp.js} +2 -2
- package/web/dist/index.html +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
|
-
## [0.3.
|
|
1
|
+
## [0.3.66] - 2026-05-16
|
|
2
2
|
|
|
3
|
-
###
|
|
4
|
-
|
|
5
|
-
- **
|
|
6
|
-
- **
|
|
3
|
+
### Added
|
|
4
|
+
- **ForgeCode Integration**: You can now seamlessly launch and install provider endpoints directly into ForgeCode's TOML config. Use the `--forgecode` flag to run it.
|
|
5
|
+
- **GitHub Copilot CLI Support**: Added direct integration with GitHub Copilot CLI (`--copilot`), dynamically setting `COPILOT_*` environment variables for seamless Bring Your Own Key (BYOK) execution.
|
|
6
|
+
- **Security Audit Report**: Added comprehensive security audit documentation from Jules.
|
|
7
7
|
|
|
8
8
|
### Changed
|
|
9
|
+
- **NVIDIA NIM Model Catalog Update**: Removed 11 deprecated models and updated GLM to the new `glm5` to keep the catalog fresh and fully operational.
|
|
10
|
+
- **README Optimization**: Refactored README layout and updated image sizes for a cleaner documentation experience.
|
|
11
|
+
- **Testing Architecture Refactoring**: Replaced `agent-tui` with native `tmux` to streamline visual TUI testing.
|
|
9
12
|
|
|
10
|
-
|
|
13
|
+
### Fixed
|
|
14
|
+
- **Repository Maintenance**: Cleaned up obsolete tooling artifacts to maintain a bloat-free codebase.
|
|
15
|
+
- **Dependencies**: Bumped internal UI framework tools (React `19.2.6`, Vite `8.0.13`).
|
package/README.md
CHANGED
|
@@ -1,36 +1,32 @@
|
|
|
1
|
-
<
|
|
2
|
-
<
|
|
3
|
-
|
|
4
|
-
<img src="logo.webp" alt="free-coding-models logo" width="128"><br><br>
|
|
5
|
-
<img src="https://img.shields.io/npm/v/free-coding-models?color=3d6b00&label=npm&logo=npm" alt="npm version" width="200"><br>
|
|
6
|
-
<img src="https://img.shields.io/node/v/free-coding-models?color=3d6b00&logo=node.js" alt="node version" width="200"><br>
|
|
7
|
-
<img src="https://img.shields.io/npm/l/free-coding-models?color=3d6b00" alt="license" width="200"><br>
|
|
8
|
-
<img src="https://img.shields.io/badge/models-170+-3d6b00?logo=nvidia" alt="models count" width="200"><br>
|
|
9
|
-
<img src="https://img.shields.io/badge/providers-16-1a56db" alt="providers count" width="200">
|
|
10
|
-
</td>
|
|
11
|
-
<td style="vertical-align: middle;">
|
|
12
|
-
<h1 style="margin-top: 0;">free-coding-models</h1>
|
|
13
|
-
<strong>Find the fastest free coding model in seconds</strong><br>
|
|
14
|
-
Track ~170 models across ~15 trusted free or free-limited AI providers in real time<br><br>
|
|
15
|
-
<strong>Install Free API endpoints to your favorite AI coding tools:</strong><br>
|
|
16
|
-
OpenCode CLI / Desktop / WebUI, OpenClaw, Crush, Goose, Aider, Kilo CLI, Qwen Code, OpenHands, Amp, Hermes, Continue, Cline, Xcode, Pi, Rovo, Gemini and more...<br><br>
|
|
17
|
-
<strong>Use Kimi K2, DeepSeek V3, GPT-OSS, Qwen3, MiniMax M2, GLM, Llama 4, Gemma 4, Devstral and more — for free</strong>
|
|
18
|
-
</td>
|
|
19
|
-
</tr>
|
|
20
|
-
</table>
|
|
21
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="logo.webp" alt="free-coding-models logo" width="328">
|
|
3
|
+
</p>
|
|
22
4
|
|
|
5
|
+
<h1 align="center">free-coding-models</h1>
|
|
23
6
|
|
|
7
|
+
<p align="center">
|
|
8
|
+
<strong>Find the fastest free coding model in seconds</strong><br>
|
|
9
|
+
Track ~170 models across ~15 trusted free or free-limited AI providers in real time<br><br>
|
|
10
|
+
<strong>Install Free API endpoints to your favorite AI coding tools:</strong><br>
|
|
11
|
+
OpenCode CLI / Desktop / WebUI, OpenClaw, Crush, Goose, Aider, Kilo CLI, Qwen Code, OpenHands, Amp, Hermes, Continue, Cline, Xcode, Pi, Rovo, Gemini and more...<br><br>
|
|
12
|
+
<strong>Use Kimi K2, DeepSeek V3, GPT-OSS, Qwen3, MiniMax M2, GLM, Llama 4, Gemma 4, Devstral and more — for free</strong>
|
|
13
|
+
</p>
|
|
24
14
|
|
|
25
15
|
<p align="center">
|
|
16
|
+
<img src="https://img.shields.io/npm/v/free-coding-models?color=3d6b00&label=npm&logo=npm" alt="npm version" width="200"><br>
|
|
17
|
+
<img src="https://img.shields.io/node/v/free-coding-models?color=3d6b00&logo=node.js" alt="node version" width="200"><br>
|
|
18
|
+
<img src="https://img.shields.io/npm/l/free-coding-models?color=3d6b00" alt="license" width="200"><br>
|
|
19
|
+
<img src="https://img.shields.io/badge/models-170+-3d6b00?logo=nvidia" alt="models count" width="200"><br>
|
|
20
|
+
<img src="https://img.shields.io/badge/providers-16-1a56db" alt="providers count" width="200">
|
|
21
|
+
</p>
|
|
26
22
|
|
|
27
23
|
```bash
|
|
28
24
|
npm install -g free-coding-models
|
|
29
25
|
free-coding-models
|
|
30
26
|
```
|
|
31
27
|
|
|
32
|
-
|
|
33
|
-
|
|
28
|
+
<p align="center">
|
|
29
|
+
create a free account on one of the <a href="#-list-of-free-ai-providers">providers</a>
|
|
34
30
|
</p>
|
|
35
31
|
|
|
36
32
|
<p align="center">
|
|
@@ -253,6 +249,8 @@ Routing behavior:
|
|
|
253
249
|
| `--pi` | π Pi |
|
|
254
250
|
| `--rovo` | 🦘 Rovo Dev CLI |
|
|
255
251
|
| `--gemini` | ♊ Gemini CLI |
|
|
252
|
+
| `--copilot` | 🤖 Copilot CLI |
|
|
253
|
+
| `--forgecode` | 🔥 ForgeCode |
|
|
256
254
|
|
|
257
255
|
Press **`Z`** in the TUI to cycle between tools without restarting.
|
|
258
256
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "free-coding-models",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.66",
|
|
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",
|
|
@@ -68,10 +68,9 @@
|
|
|
68
68
|
},
|
|
69
69
|
"devDependencies": {
|
|
70
70
|
"@vitejs/plugin-react": "^6.0.1",
|
|
71
|
-
"
|
|
72
|
-
"react": "^19.2.
|
|
73
|
-
"
|
|
74
|
-
"vite": "^8.0.5",
|
|
71
|
+
"react": "^19.2.6",
|
|
72
|
+
"react-dom": "^19.2.6",
|
|
73
|
+
"vite": "^8.0.13",
|
|
75
74
|
"vite-plus": "^0.1.16"
|
|
76
75
|
}
|
|
77
76
|
}
|
package/sources.js
CHANGED
|
@@ -46,15 +46,10 @@ export const nvidiaNim = [
|
|
|
46
46
|
['moonshotai/kimi-k2.6', 'Kimi K2.6', 'S+', '76.8%', '256k'],
|
|
47
47
|
['deepseek-ai/deepseek-v4-pro', 'DeepSeek V4 Pro', 'S+', '73.1%', '128k'],
|
|
48
48
|
['deepseek-ai/deepseek-v4-flash', 'DeepSeek V4 Flash', 'S+', '72.0%', '128k'],
|
|
49
|
-
['z-ai/
|
|
50
|
-
['moonshotai/kimi-k2-thinking', 'Kimi K2 Thinking', 'S+', '71.3%', '256k'], // ⚠️ Deprecation pending
|
|
51
|
-
['minimaxai/minimax-m2.5', 'MiniMax M2.5', 'S+', '80.2%', '200k'],
|
|
49
|
+
['z-ai/glm5', 'GLM 5', 'S+', '73.8%', '200k'],
|
|
52
50
|
['stepfun-ai/step-3.5-flash', 'Step 3.5 Flash', 'S+', '74.4%', '256k'],
|
|
53
51
|
['qwen/qwen3-coder-480b-a35b-instruct', 'Qwen3 Coder 480B', 'S+', '70.6%', '256k'],
|
|
54
|
-
['mistralai/devstral-2-123b-instruct-2512', 'Devstral 2 123B', 'S+', '72.2%', '256k'],
|
|
55
52
|
// ── S tier — SWE-bench Verified 60–70% ──
|
|
56
|
-
['moonshotai/kimi-k2-instruct-0905', 'Kimi K2 Instruct 0905', 'S', '65.8%', '256k'],
|
|
57
|
-
['moonshotai/kimi-k2-instruct', 'Kimi K2 Instruct', 'S', '65.8%', '128k'],
|
|
58
53
|
['minimaxai/minimax-m2', 'MiniMax M2', 'S', '69.4%', '128k'],
|
|
59
54
|
['qwen/qwen3-next-80b-a3b-thinking', 'Qwen3 80B Thinking', 'S', '68.0%', '128k'],
|
|
60
55
|
['qwen/qwen3-next-80b-a3b-instruct', 'Qwen3 80B Instruct', 'S', '65.0%', '128k'],
|
|
@@ -70,13 +65,9 @@ export const nvidiaNim = [
|
|
|
70
65
|
['nvidia/nemotron-3-super-120b-a12b', 'Nemotron 3 Super', 'A+', '56.0%', '128k'],
|
|
71
66
|
['nvidia/nemotron-3-nano-omni-30b-a3b-reasoning','Nemotron 3 Omni', 'A+', '52.0%', '128k'],
|
|
72
67
|
// ── A tier — SWE-bench Verified 40–50% ──
|
|
73
|
-
['mistralai/mistral-medium-3-instruct', 'Mistral Medium 3', 'A', '48.0%', '128k'],
|
|
74
|
-
['mistralai/magistral-small-2506', 'Magistral Small', 'A', '45.0%', '32k'],
|
|
75
68
|
['nvidia/llama-3.3-nemotron-super-49b-v1.5', 'Nemotron Super 49B', 'A', '49.0%', '128k'],
|
|
76
69
|
['nvidia/nemotron-3-nano-30b-a3b', 'Nemotron Nano 30B', 'A', '43.0%', '128k'],
|
|
77
70
|
['openai/gpt-oss-20b', 'GPT OSS 20B', 'A', '42.0%', '128k'],
|
|
78
|
-
['qwen/qwen2.5-coder-32b-instruct', 'Qwen2.5 Coder 32B', 'A', '46.0%', '32k'],
|
|
79
|
-
['meta/llama-3.1-405b-instruct', 'Llama 3.1 405B', 'A', '44.0%', '128k'],
|
|
80
71
|
['google/gemma-4-31b-it', 'Gemma 4 31B', 'A', '45.0%', '256k'],
|
|
81
72
|
// ── A- tier — SWE-bench Verified 35–40% ──
|
|
82
73
|
['meta/llama-3.3-70b-instruct', 'Llama 3.3 70B', 'A-', '39.5%', '128k'],
|
package/src/app.js
CHANGED
|
@@ -274,6 +274,8 @@ export async function runApp(cliArgs, config) {
|
|
|
274
274
|
pi: cliArgs.piMode,
|
|
275
275
|
rovo: cliArgs.rovoMode,
|
|
276
276
|
gemini: cliArgs.geminiMode,
|
|
277
|
+
copilot: cliArgs.copilotMode,
|
|
278
|
+
forgecode: cliArgs.forgecodeMode,
|
|
277
279
|
}
|
|
278
280
|
return flagByMode[toolMode] === true
|
|
279
281
|
})
|
|
@@ -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', 'opencode-web', 'openclaw', 'kilo', 'crush', 'goose', 'pi', 'aider', 'qwen', 'openhands', 'amp', 'hermes', 'continue', 'cline', 'fcm_router']
|
|
55
|
+
const INSTALL_TARGET_MODES = ['opencode', 'opencode-desktop', 'opencode-web', 'openclaw', 'kilo', 'crush', 'goose', 'pi', 'aider', 'qwen', 'openhands', 'amp', 'hermes', 'continue', 'cline', 'forgecode', 'fcm_router']
|
|
56
56
|
|
|
57
57
|
function getDefaultPaths() {
|
|
58
58
|
const home = homedir()
|
|
@@ -67,6 +67,7 @@ function getDefaultPaths() {
|
|
|
67
67
|
aiderConfigPath: join(home, '.aider.conf.yml'),
|
|
68
68
|
ampConfigPath: join(home, '.config', 'amp', 'settings.json'),
|
|
69
69
|
qwenConfigPath: join(home, '.qwen', 'settings.json'),
|
|
70
|
+
forgeCodeConfigPath: join(home, '.forge', '.forge.toml'),
|
|
70
71
|
}
|
|
71
72
|
}
|
|
72
73
|
|
|
@@ -526,6 +527,65 @@ function installIntoEnvBasedTool(providerKey, models, apiKey, toolMode) {
|
|
|
526
527
|
return { path: envFilePath, backupPath, providerId, modelCount: models.length }
|
|
527
528
|
}
|
|
528
529
|
|
|
530
|
+
// 📖 installIntoForgeCode: writes a managed [[providers]] block into ~/.forge/.forge.toml.
|
|
531
|
+
// 📖 ForgeCode uses TOML config with [[providers]] entries for custom OpenAI-compatible endpoints.
|
|
532
|
+
// 📖 Each provider gets one [[providers]] entry with the model catalog noted in comments.
|
|
533
|
+
// 📖 The API key is referenced via an env var so ForgeCode picks it up at runtime.
|
|
534
|
+
function installIntoForgeCode(providerKey, models, apiKey, paths) {
|
|
535
|
+
const filePath = paths.forgeCodeConfigPath
|
|
536
|
+
const providerId = getManagedProviderId(providerKey)
|
|
537
|
+
const secretEnvName = `FCM_${providerKey.toUpperCase().replace(/[^A-Z0-9]+/g, '_')}_API_KEY`
|
|
538
|
+
const baseUrl = resolveProviderBaseUrl(providerKey)
|
|
539
|
+
|
|
540
|
+
if (!baseUrl) {
|
|
541
|
+
throw new Error(`Cannot resolve base URL for ${getProviderLabel(providerKey)}`)
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// 📖 Ensure the API key is in env for ForgeCode to use
|
|
545
|
+
process.env[secretEnvName] = apiKey
|
|
546
|
+
|
|
547
|
+
const completionsUrl = baseUrl.endsWith('/chat/completions') ? baseUrl : `${baseUrl}/chat/completions`
|
|
548
|
+
|
|
549
|
+
// 📖 Read existing content
|
|
550
|
+
let content = ''
|
|
551
|
+
if (existsSync(filePath)) {
|
|
552
|
+
content = readFileSync(filePath, 'utf8')
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// 📖 Remove any previous FCM-managed provider block for this provider
|
|
556
|
+
const markerStart = `# >>> FCM managed provider: ${providerId}`
|
|
557
|
+
const markerEnd = `# <<< FCM managed provider: ${providerId}`
|
|
558
|
+
const markerRegex = new RegExp(
|
|
559
|
+
`\\n?${markerStart.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}[\\s\\S]*?${markerEnd.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\n?`,
|
|
560
|
+
'g'
|
|
561
|
+
)
|
|
562
|
+
content = content.replace(markerRegex, '\n')
|
|
563
|
+
|
|
564
|
+
// 📖 Build a fresh [[providers]] TOML block with model catalog comments
|
|
565
|
+
const modelComments = models.map(m => `# 📖 Model: ${m.label} (${m.modelId}) — ${m.tier}`).join('\n')
|
|
566
|
+
const providerBlock = [
|
|
567
|
+
'',
|
|
568
|
+
markerStart,
|
|
569
|
+
`# 📖 Provider: ${getManagedProviderLabel(providerKey)} (${models.length} models)`,
|
|
570
|
+
modelComments,
|
|
571
|
+
'[[providers]]',
|
|
572
|
+
`id = "${providerId}"`,
|
|
573
|
+
`url = "${completionsUrl}"`,
|
|
574
|
+
`api_key_vars = "${secretEnvName}"`,
|
|
575
|
+
'response_type = "OpenAI"',
|
|
576
|
+
'auth_methods = ["api_key"]',
|
|
577
|
+
markerEnd,
|
|
578
|
+
].join('\n')
|
|
579
|
+
|
|
580
|
+
content = content.trimEnd() + '\n' + providerBlock + '\n'
|
|
581
|
+
|
|
582
|
+
ensureDirFor(filePath)
|
|
583
|
+
const backupPath = backupIfExists(filePath)
|
|
584
|
+
writeFileSync(filePath, content)
|
|
585
|
+
|
|
586
|
+
return { path: filePath, backupPath, providerId, modelCount: models.length }
|
|
587
|
+
}
|
|
588
|
+
|
|
529
589
|
// 📖 installIntoFcmRouter: adds provider endpoints to the running FCM Router daemon
|
|
530
590
|
// 📖 via the /sets API so the router can use them for failover routing.
|
|
531
591
|
// 📖 Uses the daemon's expected schema: { provider, model, priority } per model entry.
|
|
@@ -605,6 +665,8 @@ export function installProviderEndpoints(config, providerKey, toolMode, options
|
|
|
605
665
|
installResult = installIntoEnvBasedTool(providerKey, models, apiKey, canonicalToolMode, paths)
|
|
606
666
|
} else if (canonicalToolMode === 'fcm_router') {
|
|
607
667
|
installResult = installIntoFcmRouter(providerKey, models, apiKey)
|
|
668
|
+
} else if (canonicalToolMode === 'forgecode') {
|
|
669
|
+
installResult = installIntoForgeCode(providerKey, models, apiKey, paths)
|
|
608
670
|
} else {
|
|
609
671
|
throw new Error(`Unsupported install target: ${toolMode}`)
|
|
610
672
|
}
|
package/src/key-handler.js
CHANGED
|
@@ -415,7 +415,7 @@ export function createKeyHandler(ctx) {
|
|
|
415
415
|
|
|
416
416
|
async function launchSelectedModel(selected, options = {}) {
|
|
417
417
|
const { uiAlreadyStopped = false } = options
|
|
418
|
-
userSelected = { modelId: selected.modelId, label: selected.label, tier: selected.tier, providerKey: selected.providerKey }
|
|
418
|
+
userSelected = { modelId: selected.modelId, label: selected.label, tier: selected.tier, providerKey: selected.providerKey, ctx: selected.ctx }
|
|
419
419
|
|
|
420
420
|
if (!uiAlreadyStopped) {
|
|
421
421
|
readline.emitKeypressEvents(process.stdin)
|
package/src/tool-bootstrap.js
CHANGED
|
@@ -304,6 +304,28 @@ export const TOOL_BOOTSTRAP_METADATA = {
|
|
|
304
304
|
},
|
|
305
305
|
},
|
|
306
306
|
},
|
|
307
|
+
copilot: {
|
|
308
|
+
binary: 'copilot',
|
|
309
|
+
docsUrl: 'https://github.com/github/copilot',
|
|
310
|
+
install: {
|
|
311
|
+
default: {
|
|
312
|
+
shellCommand: 'npm install -g @github/copilot',
|
|
313
|
+
summary: 'Install GitHub Copilot CLI globally via npm.',
|
|
314
|
+
note: 'After installation, run `copilot` to authenticate with GitHub.',
|
|
315
|
+
},
|
|
316
|
+
},
|
|
317
|
+
},
|
|
318
|
+
forgecode: {
|
|
319
|
+
binary: 'forge',
|
|
320
|
+
docsUrl: 'https://forgecode.dev',
|
|
321
|
+
install: {
|
|
322
|
+
default: {
|
|
323
|
+
shellCommand: 'npm install -g forgecode',
|
|
324
|
+
summary: 'Install ForgeCode globally via npm.',
|
|
325
|
+
note: 'After installation, run `forge` to start. Use `forge provider login` to set up credentials.',
|
|
326
|
+
},
|
|
327
|
+
},
|
|
328
|
+
},
|
|
307
329
|
}
|
|
308
330
|
|
|
309
331
|
export function getToolBootstrapMeta(mode) {
|
package/src/tool-launchers.js
CHANGED
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
* 📖 Hermes: uses `hermes config set` CLI commands + `hermes gateway restart` before launching `hermes chat`
|
|
21
21
|
* 📖 Continue: writes ~/.continue/config.yaml with provider: openai + apiBase
|
|
22
22
|
* 📖 Cline: writes ~/.cline/globalState.json with openai-compatible provider config
|
|
23
|
+
* 📖 ForgeCode: writes [[providers]] TOML block into ~/.forge/.forge.toml + sets [session] defaults
|
|
23
24
|
*
|
|
24
25
|
* @functions
|
|
25
26
|
* → `resolveLauncherModelId` — choose the provider-specific id for a launch
|
|
@@ -64,6 +65,19 @@ function ensureDir(filePath) {
|
|
|
64
65
|
if (!existsSync(dir)) mkdirSync(dir, { recursive: true })
|
|
65
66
|
}
|
|
66
67
|
|
|
68
|
+
// 📖 Parse a context window string (e.g. "128k", "1M", "32k") to token count number.
|
|
69
|
+
function parseCtxToTokens(ctx) {
|
|
70
|
+
if (!ctx || typeof ctx !== 'string') return null
|
|
71
|
+
const match = ctx.match(/^\s*(\d+(?:\.\d+)?)\s*([kKmM]?)\s*$/)
|
|
72
|
+
if (!match) return null
|
|
73
|
+
const num = parseFloat(match[1])
|
|
74
|
+
const suffix = match[2].toLowerCase()
|
|
75
|
+
// 📖 LLM token counts use binary (1024), not decimal (1000).
|
|
76
|
+
if (suffix === 'k') return Math.round(num * 1024)
|
|
77
|
+
if (suffix === 'm') return Math.round(num * 1024 * 1024)
|
|
78
|
+
return Math.round(num)
|
|
79
|
+
}
|
|
80
|
+
|
|
67
81
|
function getDefaultToolPaths(homeDir = homedir()) {
|
|
68
82
|
return {
|
|
69
83
|
aiderConfigPath: join(homeDir, '.aider.conf.yml'),
|
|
@@ -79,6 +93,7 @@ function getDefaultToolPaths(homeDir = homedir()) {
|
|
|
79
93
|
hermesConfigPath: join(homeDir, '.hermes', 'config.yaml'),
|
|
80
94
|
continueConfigPath: join(homeDir, '.continue', 'config.yaml'),
|
|
81
95
|
clineConfigPath: join(homeDir, '.cline', 'globalState.json'),
|
|
96
|
+
forgeCodeConfigPath: join(homeDir, '.forge', '.forge.toml'),
|
|
82
97
|
}
|
|
83
98
|
}
|
|
84
99
|
|
|
@@ -504,6 +519,86 @@ function writeHermesConfig(model, apiKey, baseUrl, paths = getDefaultToolPaths()
|
|
|
504
519
|
return { filePath: configPath, backupPath }
|
|
505
520
|
}
|
|
506
521
|
|
|
522
|
+
// 📖 writeForgeCodeConfig — write a managed [[providers]] block into ~/.forge/.forge.toml.
|
|
523
|
+
// 📖 ForgeCode uses TOML config with [[providers]] entries for custom OpenAI-compatible endpoints.
|
|
524
|
+
// 📖 Strategy:
|
|
525
|
+
// 📖 1. Read the existing .forge.toml (if any)
|
|
526
|
+
// 📖 2. Strip any previous FCM-managed provider block (delimited by comments)
|
|
527
|
+
// 📖 3. Append a fresh [[providers]] block with the selected model's provider details
|
|
528
|
+
// 📖 4. Update or insert [session] defaults to auto-select the model on next `forge` launch
|
|
529
|
+
// 📖 The provider ID uses the `fcm-{providerKey}` namespace to avoid clobbering user-defined providers.
|
|
530
|
+
// 📖 The API key is referenced via an env var (FCM_{PROVIDER}_API_KEY) and also set in the process env.
|
|
531
|
+
function writeForgeCodeConfig(model, apiKey, baseUrl, providerKey, paths = getDefaultToolPaths()) {
|
|
532
|
+
const filePath = paths.forgeCodeConfigPath
|
|
533
|
+
const backupPath = backupIfExists(filePath)
|
|
534
|
+
const providerId = `fcm-${providerKey}`
|
|
535
|
+
const providerLabel = PROVIDER_METADATA[providerKey]?.label || sources[providerKey]?.name || providerKey
|
|
536
|
+
const secretEnvName = `FCM_${providerKey.toUpperCase().replace(/[^A-Z0-9]+/g, '_')}_API_KEY`
|
|
537
|
+
|
|
538
|
+
// 📖 Ensure the API key is available in env for ForgeCode to pick up
|
|
539
|
+
process.env[secretEnvName] = apiKey
|
|
540
|
+
|
|
541
|
+
// 📖 Build the provider's chat completions URL
|
|
542
|
+
const completionsUrl = baseUrl
|
|
543
|
+
? (baseUrl.endsWith('/chat/completions') ? baseUrl : `${baseUrl}/chat/completions`)
|
|
544
|
+
: ''
|
|
545
|
+
|
|
546
|
+
// 📖 Read existing TOML content (if any)
|
|
547
|
+
let content = ''
|
|
548
|
+
if (existsSync(filePath)) {
|
|
549
|
+
content = readFileSync(filePath, 'utf8')
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// 📖 Remove any previous FCM-managed provider block (between marker comments)
|
|
553
|
+
const markerStart = `# >>> FCM managed provider: ${providerId}`
|
|
554
|
+
const markerEnd = `# <<< FCM managed provider: ${providerId}`
|
|
555
|
+
const markerRegex = new RegExp(
|
|
556
|
+
`\\n?${markerStart.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}[\\s\\S]*?${markerEnd.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\n?`,
|
|
557
|
+
'g'
|
|
558
|
+
)
|
|
559
|
+
content = content.replace(markerRegex, '\n')
|
|
560
|
+
|
|
561
|
+
// 📖 Build a fresh [[providers]] TOML block
|
|
562
|
+
const providerBlock = [
|
|
563
|
+
'',
|
|
564
|
+
markerStart,
|
|
565
|
+
'[[providers]]',
|
|
566
|
+
`id = "${providerId}"`,
|
|
567
|
+
`url = "${completionsUrl}"`,
|
|
568
|
+
`api_key_vars = "${secretEnvName}"`,
|
|
569
|
+
'response_type = "OpenAI"',
|
|
570
|
+
'auth_methods = ["api_key"]',
|
|
571
|
+
markerEnd,
|
|
572
|
+
].join('\n')
|
|
573
|
+
|
|
574
|
+
content = content.trimEnd() + '\n' + providerBlock + '\n'
|
|
575
|
+
|
|
576
|
+
// 📖 Update or insert [session] defaults so ForgeCode auto-selects this model
|
|
577
|
+
const sessionProviderLine = `provider_id = "${providerId}"`
|
|
578
|
+
const sessionModelLine = `model_id = "${model.modelId}"`
|
|
579
|
+
|
|
580
|
+
if (/^\[session\]/m.test(content)) {
|
|
581
|
+
// 📖 Replace existing provider_id/model_id under [session]
|
|
582
|
+
if (/^provider_id\s*=/m.test(content)) {
|
|
583
|
+
content = content.replace(/^provider_id\s*=.*$/m, sessionProviderLine)
|
|
584
|
+
} else {
|
|
585
|
+
content = content.replace(/^\[session\]/m, `[session]\n${sessionProviderLine}`)
|
|
586
|
+
}
|
|
587
|
+
if (/^model_id\s*=/m.test(content)) {
|
|
588
|
+
content = content.replace(/^model_id\s*=.*$/m, sessionModelLine)
|
|
589
|
+
} else {
|
|
590
|
+
content = content.replace(/^\[session\]/m, `[session]\n${sessionModelLine}`)
|
|
591
|
+
}
|
|
592
|
+
} else {
|
|
593
|
+
// 📖 No [session] block — append one
|
|
594
|
+
content = content.trimEnd() + '\n\n[session]\n' + sessionProviderLine + '\n' + sessionModelLine + '\n'
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
ensureDir(filePath)
|
|
598
|
+
writeFileSync(filePath, content)
|
|
599
|
+
return { filePath, backupPath }
|
|
600
|
+
}
|
|
601
|
+
|
|
507
602
|
// 📖 restartHermesGateway — restart the Hermes messaging gateway after config changes.
|
|
508
603
|
// 📖 Non-blocking: if gateway is not running, this is a no-op.
|
|
509
604
|
function restartHermesGateway() {
|
|
@@ -833,6 +928,45 @@ export function prepareExternalToolLaunch(mode, model, config, options = {}) {
|
|
|
833
928
|
}
|
|
834
929
|
}
|
|
835
930
|
|
|
931
|
+
if (mode === 'copilot') {
|
|
932
|
+
// 📖 copilot: set BYOK env vars so copilot uses the selected provider/model
|
|
933
|
+
const copilotModelId = resolveLauncherModelId(model)
|
|
934
|
+
env.COPILOT_PROVIDER_BASE_URL = baseUrl
|
|
935
|
+
env.COPILOT_MODEL = copilotModelId
|
|
936
|
+
if (apiKey) env.COPILOT_PROVIDER_API_KEY = apiKey
|
|
937
|
+
|
|
938
|
+
// 📖 Set context window limits from model data
|
|
939
|
+
const promptTokens = parseCtxToTokens(model.ctx)
|
|
940
|
+
if (promptTokens) env.COPILOT_PROVIDER_MAX_PROMPT_TOKENS = String(promptTokens)
|
|
941
|
+
// 📖 16k max output as a safety cap — most S+/S tier coding models
|
|
942
|
+
// 📖 support 16-32k output. copilot falls back to built-in model
|
|
943
|
+
// 📖 catalog defaults when a model ID is recognized.
|
|
944
|
+
env.COPILOT_PROVIDER_MAX_OUTPUT_TOKENS = '16384'
|
|
945
|
+
|
|
946
|
+
return {
|
|
947
|
+
command: 'copilot',
|
|
948
|
+
args: [],
|
|
949
|
+
env,
|
|
950
|
+
apiKey,
|
|
951
|
+
baseUrl,
|
|
952
|
+
meta,
|
|
953
|
+
configArtifacts: [],
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
if (mode === 'forgecode') {
|
|
958
|
+
const result = writeForgeCodeConfig(model, apiKey, baseUrl, model.providerKey, paths)
|
|
959
|
+
return {
|
|
960
|
+
command: 'forge',
|
|
961
|
+
args: [],
|
|
962
|
+
env,
|
|
963
|
+
apiKey,
|
|
964
|
+
baseUrl,
|
|
965
|
+
meta,
|
|
966
|
+
configArtifacts: [{ path: result.filePath, backupPath: result.backupPath, label: 'config' }],
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
|
|
836
970
|
return {
|
|
837
971
|
blocked: true,
|
|
838
972
|
exitCode: 1,
|
|
@@ -934,6 +1068,16 @@ export async function startExternalTool(mode, model, config) {
|
|
|
934
1068
|
return spawnCommand(resolveLaunchCommand(mode, launchPlan.command), launchPlan.args, launchPlan.env)
|
|
935
1069
|
}
|
|
936
1070
|
|
|
1071
|
+
if (mode === 'copilot') {
|
|
1072
|
+
console.log(chalk.dim(` 📖 Copilot CLI configured with model: ${model.modelId}`))
|
|
1073
|
+
return spawnCommand(resolveLaunchCommand(mode, launchPlan.command), launchPlan.args, launchPlan.env)
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
if (mode === 'forgecode') {
|
|
1077
|
+
console.log(chalk.dim(` 📖 ForgeCode configured with model: ${model.modelId}`))
|
|
1078
|
+
return spawnCommand(resolveLaunchCommand(mode, launchPlan.command), launchPlan.args, launchPlan.env)
|
|
1079
|
+
}
|
|
1080
|
+
|
|
937
1081
|
console.log(chalk.red(` X Unsupported external tool mode: ${mode}`))
|
|
938
1082
|
return 1
|
|
939
1083
|
}
|
package/src/tool-metadata.js
CHANGED
|
@@ -46,6 +46,8 @@ export const TOOL_METADATA = {
|
|
|
46
46
|
jcode: { label: 'jcode', emoji: '🪼', flag: '--jcode', color: [255, 140, 0] },
|
|
47
47
|
xcode: { label: 'Xcode Intelligence',emoji: '🛠️', flag: '--xcode', color: [20, 126, 251] },
|
|
48
48
|
fcm_router: { label: 'FCM Router', emoji: '🧭', flag: '--fcm-router', color: [80, 200, 120] },
|
|
49
|
+
copilot: { label: 'Copilot CLI', emoji: '🤖', flag: '--copilot', color: [200, 220, 255] },
|
|
50
|
+
forgecode: { label: 'ForgeCode', emoji: '🔥', flag: '--forgecode', color: [255, 120, 50] },
|
|
49
51
|
}
|
|
50
52
|
|
|
51
53
|
// 📖 Deduplicated emoji order for the "Compatible with" column.
|
|
@@ -64,13 +66,14 @@ export const COMPAT_COLUMN_SLOTS = [
|
|
|
64
66
|
{ emoji: '⚡', toolKeys: ['amp'], color: [255, 232, 98] },
|
|
65
67
|
{ emoji: '🔮', toolKeys: ['hermes'], color: [200, 160, 255] },
|
|
66
68
|
{ emoji: '▶️', toolKeys: ['continue'], color: [255, 100, 100] },
|
|
67
|
-
{ emoji: '🧠', toolKeys: ['cline'],
|
|
69
|
+
{ emoji: '🧠', toolKeys: ['cline'], color: [100, 220, 180] },
|
|
68
70
|
{ emoji: '🧭', toolKeys: ['fcm_router'], color: [80, 200, 120] },
|
|
69
71
|
{ emoji: '🦘', toolKeys: ['rovo'], color: [148, 163, 184] },
|
|
70
72
|
{ emoji: '♊', toolKeys: ['gemini'], color: [66, 165, 245] },
|
|
71
73
|
{ emoji: '🪼', toolKeys: ['jcode'], color: [255, 140, 0] },
|
|
72
74
|
{ emoji: '🛠️', toolKeys: ['xcode'], color: [20, 126, 251] },
|
|
73
|
-
{ emoji: '
|
|
75
|
+
{ emoji: '🤖', toolKeys: ['copilot'], color: [200, 220, 255] },
|
|
76
|
+
{ emoji: '🔥', toolKeys: ['forgecode'], color: [255, 120, 50] },
|
|
74
77
|
]
|
|
75
78
|
|
|
76
79
|
export const TOOL_MODE_ORDER = [
|
|
@@ -94,6 +97,8 @@ export const TOOL_MODE_ORDER = [
|
|
|
94
97
|
'fcm_router',
|
|
95
98
|
'rovo',
|
|
96
99
|
'gemini',
|
|
100
|
+
'copilot',
|
|
101
|
+
'forgecode',
|
|
97
102
|
]
|
|
98
103
|
|
|
99
104
|
export function getToolMeta(mode) {
|
package/src/utils.js
CHANGED
|
@@ -389,14 +389,16 @@ export function findBestModel(results) {
|
|
|
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
391
|
// --aider, --crush, --goose, --qwen, --kilo,
|
|
392
|
-
// --openhands, --amp, --pi, --
|
|
392
|
+
// --openhands, --amp, --pi, --rovo, --hermes, --continue, --cline,
|
|
393
|
+
// --xcode, --gemini, --jcode, --copilot, --forgecode,
|
|
394
|
+
// --daemon, --daemon-bg, --daemon-stop,
|
|
393
395
|
// --daemon-status, --no-telemetry, --json, --help/-h (case-insensitive)
|
|
394
396
|
// - Value flag: --tier <letter> (the next non-flag arg is the tier value)
|
|
395
397
|
//
|
|
396
398
|
// Returns:
|
|
397
399
|
// { apiKey, bestMode, fiableMode, openCodeMode, openCodeDesktopMode, openCodeWebMode, openClawMode,
|
|
398
400
|
// aiderMode, crushMode, gooseMode, qwenMode, openHandsMode, ampMode,
|
|
399
|
-
// piMode, jcodeMode, noTelemetry, jsonMode, helpMode, tierFilter }
|
|
401
|
+
// piMode, jcodeMode, copilotMode, forgecodeMode, noTelemetry, jsonMode, helpMode, tierFilter }
|
|
400
402
|
//
|
|
401
403
|
// 📖 Note: apiKey may be null here — the main CLI falls back to env vars and saved config.
|
|
402
404
|
export function parseArgs(argv) {
|
|
@@ -471,6 +473,8 @@ export function parseArgs(argv) {
|
|
|
471
473
|
const xcodeMode = flags.includes('--xcode')
|
|
472
474
|
const geminiMode = flags.includes('--gemini')
|
|
473
475
|
const jcodeMode = flags.includes('--jcode')
|
|
476
|
+
const copilotMode = flags.includes('--copilot')
|
|
477
|
+
const forgecodeMode = flags.includes('--forgecode')
|
|
474
478
|
const noTelemetry = flags.includes('--no-telemetry')
|
|
475
479
|
const devMode = flags.includes('--dev')
|
|
476
480
|
const jsonMode = flags.includes('--json')
|
|
@@ -528,6 +532,8 @@ export function parseArgs(argv) {
|
|
|
528
532
|
rovoMode,
|
|
529
533
|
geminiMode,
|
|
530
534
|
jcodeMode,
|
|
535
|
+
copilotMode,
|
|
536
|
+
forgecodeMode,
|
|
531
537
|
noTelemetry,
|
|
532
538
|
jsonMode,
|
|
533
539
|
helpMode,
|