free-coding-models 0.3.43 → 0.3.45
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 +6 -7
- package/README.md +79 -0
- package/package.json +1 -1
- package/src/app.js +3 -2
- package/src/command-palette.js +1 -0
- package/src/config.js +3 -1
- package/src/key-handler.js +11 -1
- package/src/openclaw.js +25 -3
- package/src/opencode.js +99 -0
- package/src/render-table.js +32 -24
- package/src/shell-env.js +4 -0
- package/src/tool-bootstrap.js +14 -0
- package/src/tool-launchers.js +36 -3
- package/src/tool-metadata.js +4 -1
- package/src/utils.js +4 -2
- package/web/dist/assets/{index-Bt6SVfZ4.js → index-CR4H9Vxe.js} +1 -1
- package/web/dist/index.html +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
## [0.3.
|
|
1
|
+
## [0.3.45] - 2026-04-11
|
|
2
2
|
|
|
3
3
|
### Added
|
|
4
|
-
- Added new
|
|
5
|
-
- Added
|
|
6
|
-
- Added launch outcome tracking with `app_use_result`, so the dashboard can distinguish successful launches from blocked flows like incompatible model/tool combinations or missing CLI installs.
|
|
4
|
+
- Added **jcode** external tool support, integrating a new CLI tool across the codebase with command-palette description, tool metadata (label, emoji, flag, color), bootstrap metadata (binary, docs URL, install commands), and full launch wiring via `prepareExternalToolLaunch` and `startExternalTool`. CLI flag parsing for `--jcode` (jcodeMode) is also included.
|
|
5
|
+
- Added **Alt+W** footer toggle — collapses the footer to a single-line hint showing only the toggle hotkey and exit command, giving more vertical space for the model table.
|
|
7
6
|
|
|
8
7
|
### Changed
|
|
9
|
-
-
|
|
10
|
-
-
|
|
8
|
+
- Provider API keys are now synced into tool launches — `openclaw` imports `getApiKey` and `syncShellEnv`, accepts an env override in `spawnOpenClawCli`, and populates the child process env with the provider API key when launching the CLI. Tool launchers now use `model.providerKey` instead of hardcoded provider IDs, and `prepareExternalToolLaunch` includes provider and API key in launch args. This enables multi-provider support and ensures launched tools can authenticate using the configured key.
|
|
9
|
+
- Command Palette label updated to remove "NEW !" badge — the feature is no longer new.
|
|
11
10
|
|
|
12
11
|
### Fixed
|
|
13
|
-
-
|
|
12
|
+
- README now includes a "Bonus Free Stuff" section with curated resources: community awesome-lists, AI-powered IDEs with free tiers, API providers with permanent free tiers, trial credit providers, and education/developer program freebies — accessible via a new navigation link.
|
package/README.md
CHANGED
|
@@ -34,6 +34,7 @@ create a free account on one of the [providers](#-list-of-free-ai-providers)
|
|
|
34
34
|
<a href="#-why-this-tool">💡 Why</a> •
|
|
35
35
|
<a href="#-quick-start">⚡ Quick Start</a> •
|
|
36
36
|
<a href="#-list-of-free-ai-providers">🟢 Providers</a> •
|
|
37
|
+
<a href="#-bonus-free-stuff">🎁 Bonus Free Stuff</a> •
|
|
37
38
|
<a href="#-usage">🚀 Usage</a> •
|
|
38
39
|
<a href="#-tui-keys">⌨️ TUI Keys</a> •
|
|
39
40
|
<a href="#-features">✨ Features</a> •
|
|
@@ -103,6 +104,84 @@ Create a free account on one provider below to get started:
|
|
|
103
104
|
|
|
104
105
|
> 💡 One key is enough. Add more at any time with **`P`** inside the TUI.
|
|
105
106
|
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## 🎁 Bonus Free Stuff
|
|
110
|
+
|
|
111
|
+
**Everything free that isn't in the CLI** — IDE extensions, coding agents, GitHub lists, trial credits, and more.
|
|
112
|
+
|
|
113
|
+
### 📚 Awesome Lists (curated by the community)
|
|
114
|
+
|
|
115
|
+
| Resource | What it is |
|
|
116
|
+
|----------|------------|
|
|
117
|
+
| [cheahjs/free-llm-api-resources](https://github.com/cheahjs/free-llm-api-resources) (18.4k ⭐) | Comprehensive list of free LLM API providers with rate limits |
|
|
118
|
+
| [mnfst/awesome-free-llm-apis](https://github.com/mnfst/awesome-free-llm-apis) (2.1k ⭐) | Permanent free LLM API tiers organized by provider |
|
|
119
|
+
| [inmve/free-ai-coding](https://github.com/inmve/free-ai-coding) (648 ⭐) | Pro-grade AI coding tools side-by-side — limits, models, CC requirements |
|
|
120
|
+
| [amardeeplakshkar/awesome-free-llm-apis](https://github.com/amardeeplakshkar/awesome-free-llm-apis) | Additional free LLM API resources |
|
|
121
|
+
|
|
122
|
+
### 🖥️ AI-Powered IDEs with Free Tiers
|
|
123
|
+
|
|
124
|
+
| IDE | Free tier | Credit card |
|
|
125
|
+
|-----|-----------|-------------|
|
|
126
|
+
| [Qwen Code](https://github.com/QwenLM/qwen-code) | 2,000 requests/day | No |
|
|
127
|
+
| [Rovo Dev CLI](https://www.atlassian.com/rovo) | 5M tokens/day (beta) | No |
|
|
128
|
+
| [Gemini CLI](https://github.com/google-gemini/gemini-cli) | 100–250 requests/day | No |
|
|
129
|
+
| [Jules](https://jules.google/) | 15 tasks/day | No |
|
|
130
|
+
| [AWS Kiro](https://kiro.dev/) | 50 credits/month | No |
|
|
131
|
+
| [Trae](https://trae.ai/) | 10 fast + 50 slow requests/month | No |
|
|
132
|
+
| [Codeium](https://codeium.com/) | Unlimited forever, basic models | No |
|
|
133
|
+
| [JetBrains AI Assistant](https://www.jetbrains.com/ai/) | Unlimited completions + local models | No |
|
|
134
|
+
| [Continue.dev](https://www.continue.dev/) | Free VS Code/JetBrains extension, local models via Ollama | No |
|
|
135
|
+
| [Warp](https://warp.dev/) | 150 credits/month (first 2 months), then 75/month | No |
|
|
136
|
+
| [Amazon Q Developer](https://aws.amazon.com/q/developer/) | 50 agentic requests/month | Required |
|
|
137
|
+
| [Windsurf](https://windsurf.com/) | 25 prompt credits/month | Required |
|
|
138
|
+
| [Kilo Code](https://kilocode.ai/) | Up to $25 signup credits (one-time) | Required |
|
|
139
|
+
| [Tabnine](https://www.tabnine.com/) | Basic completions + chat (limited) | Required |
|
|
140
|
+
| [SuperMaven](https://supermaven.com/) | Basic suggestions, 1M token context | Required |
|
|
141
|
+
|
|
142
|
+
### 🔑 API Providers with Permanent Free Tiers
|
|
143
|
+
|
|
144
|
+
| Provider | Free limits | Notable models |
|
|
145
|
+
|----------|-------------|----------------|
|
|
146
|
+
| [OpenRouter](https://openrouter.ai/keys) | 50 req/day, 1K/day with $10 purchase | Qwen3-Coder, Llama 3.3 70B, Gemma 3 |
|
|
147
|
+
| [Google AI Studio](https://aistudio.google.com/apikey) | 5–500 req/day (varies by model) | Gemini 2.5 Flash, Gemma 3 |
|
|
148
|
+
| [NVIDIA NIM](https://build.nvidia.com) | 40 RPM | Llama 3.3 70B, Mistral Large, Qwen3 235B |
|
|
149
|
+
| [Groq](https://console.groq.com/keys) | 1K–14.4K req/day (model-dependent) | Llama 3.3 70B, Llama 4 Scout, Kimi K2 |
|
|
150
|
+
| [Cerebras](https://cloud.cerebras.ai/) | 30 RPM, 1M tokens/day | Qwen3-235B, Llama 3.1 70B, GPT-OSS 120B |
|
|
151
|
+
| [Cohere](https://cohere.com/) | 20 RPM, 1K/month | Command R+, Aya Expanse 32B |
|
|
152
|
+
| [Mistral La Plateforme](https://console.mistral.ai/) | 1 req/s, 1B tokens/month | Mistral Large 3, Small 3.1 |
|
|
153
|
+
| [Cloudflare Workers AI](https://dash.cloudflare.com) | 10K neurons/day | Llama 3.3 70B, QwQ 32B, 47+ models |
|
|
154
|
+
| [GitHub Models](https://github.com/marketplace/models) | Depends on Copilot tier | GPT-4o, DeepSeek-R1, Llama 3.3 |
|
|
155
|
+
| [SiliconFlow](https://cloud.siliconflow.cn/account/ak) | 1K RPM, 50K TPM | Qwen3-8B, DeepSeek-R1, GLM-4.1V |
|
|
156
|
+
| [HuggingFace](https://huggingface.com/settings/tokens) | ~$0.10/month credits | Llama 3.3 70B, Qwen2.5 72B |
|
|
157
|
+
|
|
158
|
+
### 💰 Providers with Trial Credits
|
|
159
|
+
|
|
160
|
+
| Provider | Credits | Duration |
|
|
161
|
+
|----------|---------|----------|
|
|
162
|
+
| [Hyperbolic](https://app.hyperbolic.ai/) | $1 free | Permanent |
|
|
163
|
+
| [Fireworks](https://fireworks.ai/) | $1 | Permanent |
|
|
164
|
+
| [Nebius](https://tokenfactory.nebius.com/) | $1 | Permanent |
|
|
165
|
+
| [SambaNova Cloud](https://cloud.sambanova.ai/) | $5 | 3 months |
|
|
166
|
+
| [AI21](https://studio.ai21.com/) | $10 | 3 months |
|
|
167
|
+
| [Upstage](https://console.upstage.ai/) | $10 | 3 months |
|
|
168
|
+
| [NLP Cloud](https://nlpcloud.com/home) | $15 | Permanent |
|
|
169
|
+
| [Alibaba DashScope](https://bailian.console.alibabacloud.com/) | 1M tokens/model | 90 days |
|
|
170
|
+
| [Scaleway](https://console.scaleway.com/generative-api/models) | 1M tokens | Permanent |
|
|
171
|
+
| [Modal](https://modal.com) | $5/month | Monthly |
|
|
172
|
+
| [Inference.net](https://inference.net) | $1 (+ $25 on survey) | Permanent |
|
|
173
|
+
| [Novita](https://novita.ai/) | $0.5 | 1 year |
|
|
174
|
+
|
|
175
|
+
### 🎓 Free with Education/Developer Programs
|
|
176
|
+
|
|
177
|
+
| Program | What you get |
|
|
178
|
+
|---------|--------------|
|
|
179
|
+
| [GitHub Student Pack](https://education.github.com/pack) | Free Copilot Pro for students (verify with .edu email) |
|
|
180
|
+
| [GitHub Copilot Free](https://code.visualstudio.com/blogs/2024/12/18/free-github-copilot) | 50 chat + 2,000 completions/month in VS Code |
|
|
181
|
+
| [Copilot Pro for teachers/maintainers](https://docs.github.com/en/copilot/how-tos/manage-your-account/get-free-access-to-copilot-pro) | Free Copilot Pro for open source maintainers & educators |
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
106
185
|
### Tier scale
|
|
107
186
|
|
|
108
187
|
| Tier | SWE-bench | Best for |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "free-coding-models",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.45",
|
|
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
|
@@ -240,7 +240,7 @@ export async function runApp(cliArgs, config) {
|
|
|
240
240
|
config.settings.shellEnvEnabled = false
|
|
241
241
|
saveConfig(config)
|
|
242
242
|
}
|
|
243
|
-
// 📖 'skip'
|
|
243
|
+
// 📖 'skip' (Ctrl+C) now also sets shellEnvEnabled = false — prompt won't reappear
|
|
244
244
|
}
|
|
245
245
|
|
|
246
246
|
// 📖 Default mode: use the last persisted launcher choice when valid,
|
|
@@ -425,6 +425,7 @@ export async function runApp(cliArgs, config) {
|
|
|
425
425
|
originFilterMode: 0, // 📖 Index into ORIGIN_CYCLE (0=All, then providers)
|
|
426
426
|
hideUnconfiguredModels: config.settings?.hideUnconfiguredModels === true, // 📖 Hide providers with no configured API key when true.
|
|
427
427
|
favoritesPinnedAndSticky: config.settings?.favoritesPinnedAndSticky === true, // 📖 false by default: favorites follow normal sort/filter rules until Y enables pinned+sticky mode.
|
|
428
|
+
footerHidden: config.settings?.footerHidden === true, // 📖 true = footer is collapsed to a single toggle hint
|
|
428
429
|
scrollOffset: 0, // 📖 First visible model index in viewport
|
|
429
430
|
terminalRows: process.stdout.rows || 24, // 📖 Current terminal height
|
|
430
431
|
terminalCols: process.stdout.columns || 80, // 📖 Current terminal width
|
|
@@ -1120,7 +1121,7 @@ export async function runApp(cliArgs, config) {
|
|
|
1120
1121
|
pinFavorites: state.favoritesPinnedAndSticky,
|
|
1121
1122
|
})
|
|
1122
1123
|
|
|
1123
|
-
process.stdout.write(ALT_HOME + renderTable(state.results, state.pendingPings, state.frame, state.cursor, state.sortColumn, state.sortDirection, state.pingInterval, state.lastPingTime, state.mode, state.tierFilterMode, state.scrollOffset, state.terminalRows, state.terminalCols, state.originFilterMode, null, state.pingMode, state.pingModeSource, state.hideUnconfiguredModels, state.widthWarningStartedAt, state.widthWarningDismissed, state.widthWarningShowCount, state.settingsUpdateState, state.settingsUpdateLatestVersion, false, state.startupLatestVersion, state.versionAlertsEnabled, state.favoritesPinnedAndSticky, state.customTextFilter, state.lastReleaseDate))
|
|
1124
|
+
process.stdout.write(ALT_HOME + renderTable(state.results, state.pendingPings, state.frame, state.cursor, state.sortColumn, state.sortDirection, state.pingInterval, state.lastPingTime, state.mode, state.tierFilterMode, state.scrollOffset, state.terminalRows, state.terminalCols, state.originFilterMode, null, state.pingMode, state.pingModeSource, state.hideUnconfiguredModels, state.widthWarningStartedAt, state.widthWarningDismissed, state.widthWarningShowCount, state.settingsUpdateState, state.settingsUpdateLatestVersion, false, state.startupLatestVersion, state.versionAlertsEnabled, state.favoritesPinnedAndSticky, state.customTextFilter, state.lastReleaseDate, state.footerHidden))
|
|
1124
1125
|
if (process.stdout.isTTY) {
|
|
1125
1126
|
process.stdout.flush && process.stdout.flush()
|
|
1126
1127
|
}
|
package/src/command-palette.js
CHANGED
|
@@ -33,6 +33,7 @@ const TOOL_MODE_DESCRIPTIONS = {
|
|
|
33
33
|
cline: 'Launch Cline CLI with the selected model.',
|
|
34
34
|
rovo: 'Rovo Dev CLI model (launch with Rovo tool only).',
|
|
35
35
|
gemini: 'Gemini CLI model (launch with Gemini tool only).',
|
|
36
|
+
jcode: 'Launch jcode coding agent with the selected model.',
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
const TOOL_MODE_COMMANDS = TOOL_MODE_ORDER.map((toolMode) => {
|
package/src/config.js
CHANGED
|
@@ -212,6 +212,7 @@ function normalizeSettingsSection(settings) {
|
|
|
212
212
|
hideUnconfiguredModels: typeof safeSettings.hideUnconfiguredModels === 'boolean' ? safeSettings.hideUnconfiguredModels : true,
|
|
213
213
|
favoritesPinnedAndSticky: typeof safeSettings.favoritesPinnedAndSticky === 'boolean' ? safeSettings.favoritesPinnedAndSticky : false,
|
|
214
214
|
theme: ['dark', 'light', 'auto'].includes(safeSettings.theme) ? safeSettings.theme : 'auto',
|
|
215
|
+
footerHidden: typeof safeSettings.footerHidden === 'boolean' ? safeSettings.footerHidden : false,
|
|
215
216
|
}
|
|
216
217
|
}
|
|
217
218
|
|
|
@@ -839,7 +840,7 @@ export function isProviderEnabled(config, providerKey) {
|
|
|
839
840
|
/**
|
|
840
841
|
* 📖 _emptyProfileSettings: Default TUI settings.
|
|
841
842
|
*
|
|
842
|
-
* @returns {{ tierFilter: string|null, sortColumn: string, sortAsc: boolean, pingInterval: number, hideUnconfiguredModels: boolean, favoritesPinnedAndSticky: boolean, preferredToolMode: string }}
|
|
843
|
+
* @returns {{ tierFilter: string|null, sortColumn: string, sortAsc: boolean, pingInterval: number, hideUnconfiguredModels: boolean, favoritesPinnedAndSticky: boolean, preferredToolMode: string, theme: string, footerHidden: boolean }}
|
|
843
844
|
*/
|
|
844
845
|
export function _emptyProfileSettings() {
|
|
845
846
|
return {
|
|
@@ -851,6 +852,7 @@ export function _emptyProfileSettings() {
|
|
|
851
852
|
favoritesPinnedAndSticky: false, // 📖 default mode keeps favorites as normal starred rows; press Y to pin+stick them.
|
|
852
853
|
preferredToolMode: 'opencode', // 📖 remember the last Z-selected launcher across app restarts
|
|
853
854
|
theme: 'auto', // 📖 'auto' follows the terminal/OS theme, override with 'dark' or 'light' if needed
|
|
855
|
+
footerHidden: false, // 📖 false = full footer shown; true = collapsed to a single "(W) Toggle Footer" hint
|
|
854
856
|
}
|
|
855
857
|
}
|
|
856
858
|
|
package/src/key-handler.js
CHANGED
|
@@ -2285,10 +2285,20 @@ export function createKeyHandler(ctx) {
|
|
|
2285
2285
|
// 📖 W cycles the supported ping modes:
|
|
2286
2286
|
// 📖 speed (2s) → normal (10s) → slow (30s) → forced (4s) → speed.
|
|
2287
2287
|
// 📖 forced ignores auto speed/slow transitions until the user leaves it manually.
|
|
2288
|
-
if (key.name === 'w') {
|
|
2288
|
+
if (key.name === 'w' && !key.alt && !key.ctrl && !key.meta) {
|
|
2289
2289
|
const currentIdx = PING_MODE_CYCLE.indexOf(state.pingMode)
|
|
2290
2290
|
const nextIdx = currentIdx >= 0 ? (currentIdx + 1) % PING_MODE_CYCLE.length : 0
|
|
2291
2291
|
setPingMode(PING_MODE_CYCLE[nextIdx], 'manual')
|
|
2292
|
+
return
|
|
2293
|
+
}
|
|
2294
|
+
|
|
2295
|
+
// 📖 Alt+W: toggle footer visibility (collapse to single hint when hidden)
|
|
2296
|
+
if (key.name === 'w' && key.alt && !key.ctrl && !key.meta) {
|
|
2297
|
+
state.footerHidden = !state.footerHidden
|
|
2298
|
+
if (!state.config.settings || typeof state.config.settings !== 'object') state.config.settings = {}
|
|
2299
|
+
state.config.settings.footerHidden = state.footerHidden
|
|
2300
|
+
saveConfig(state.config)
|
|
2301
|
+
return
|
|
2292
2302
|
}
|
|
2293
2303
|
|
|
2294
2304
|
// 📖 E toggles hiding models whose provider has no configured API key.
|
package/src/openclaw.js
CHANGED
|
@@ -33,6 +33,8 @@ import { installProviderEndpoints } from './endpoint-installer.js'
|
|
|
33
33
|
import { ENV_VAR_NAMES } from './provider-metadata.js'
|
|
34
34
|
import { PROVIDER_COLOR } from './render-table.js'
|
|
35
35
|
import { resolveToolBinaryPath } from './tool-bootstrap.js'
|
|
36
|
+
import { getApiKey } from './config.js'
|
|
37
|
+
import { syncShellEnv } from './shell-env.js'
|
|
36
38
|
|
|
37
39
|
const OPENCLAW_CONFIG = join(homedir(), '.openclaw', 'openclaw.json')
|
|
38
40
|
|
|
@@ -56,7 +58,14 @@ export function saveOpenClawConfig(config, options = {}) {
|
|
|
56
58
|
writeFileSync(filePath, JSON.stringify(config, null, 2))
|
|
57
59
|
}
|
|
58
60
|
|
|
59
|
-
|
|
61
|
+
/**
|
|
62
|
+
* 📖 Spawn OpenClaw CLI with proper environment including API key.
|
|
63
|
+
* OpenClaw reads API keys from environment variables at runtime, not just config file.
|
|
64
|
+
*
|
|
65
|
+
* @param {NodeJS.ProcessEnv} env - Environment variables including API key
|
|
66
|
+
* @returns {Promise<number>} Exit code
|
|
67
|
+
*/
|
|
68
|
+
function spawnOpenClawCli(env = process.env) {
|
|
60
69
|
return new Promise(async (resolve, reject) => {
|
|
61
70
|
const { spawn } = await import('child_process')
|
|
62
71
|
const command = resolveToolBinaryPath('openclaw') || 'openclaw'
|
|
@@ -64,7 +73,7 @@ function spawnOpenClawCli() {
|
|
|
64
73
|
stdio: 'inherit',
|
|
65
74
|
shell: false,
|
|
66
75
|
detached: false,
|
|
67
|
-
env
|
|
76
|
+
env,
|
|
68
77
|
})
|
|
69
78
|
|
|
70
79
|
child.on('exit', (code) => resolve(typeof code === 'number' ? code : 0))
|
|
@@ -107,16 +116,29 @@ export async function startOpenClaw(model, config, options = {}) {
|
|
|
107
116
|
})
|
|
108
117
|
|
|
109
118
|
const providerEnvName = ENV_VAR_NAMES[model.providerKey]
|
|
119
|
+
const apiKey = getApiKey(config, model.providerKey)
|
|
110
120
|
console.log(chalk.rgb(255, 140, 0)(` ✓ Default model set to: ${result.primaryModelRef || `${result.providerId}/${model.modelId}`}`))
|
|
111
121
|
console.log()
|
|
112
122
|
console.log(chalk.dim(` 📄 Config updated: ${result.path}`))
|
|
113
123
|
if (result.backupPath) console.log(chalk.dim(` 💾 Backup: ${result.backupPath}`))
|
|
114
124
|
if (providerEnvName) console.log(chalk.dim(` 🔑 API key synced under config env.${providerEnvName}`))
|
|
115
125
|
console.log()
|
|
126
|
+
|
|
116
127
|
if (options.launchCli) {
|
|
128
|
+
// 📖 Sync shell env so API key is available as environment variable
|
|
129
|
+
if (config.settings?.shellEnvEnabled !== false) {
|
|
130
|
+
syncShellEnv(config)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// 📖 Build env with API key so OpenClaw can authenticate
|
|
134
|
+
const launchEnv = { ...process.env }
|
|
135
|
+
if (apiKey && providerEnvName) {
|
|
136
|
+
launchEnv[providerEnvName] = apiKey
|
|
137
|
+
}
|
|
138
|
+
|
|
117
139
|
console.log(chalk.dim(' Starting OpenClaw...'))
|
|
118
140
|
console.log()
|
|
119
|
-
await spawnOpenClawCli()
|
|
141
|
+
await spawnOpenClawCli(launchEnv)
|
|
120
142
|
} else {
|
|
121
143
|
console.log(chalk.dim(' 💡 OpenClaw will reload config automatically when it notices the file change.'))
|
|
122
144
|
console.log(chalk.dim(` To apply manually: openclaw models set ${result.primaryModelRef || `${result.providerId}/${model.modelId}`}`))
|
package/src/opencode.js
CHANGED
|
@@ -337,6 +337,41 @@ export async function startOpenCode(model, fcmConfig) {
|
|
|
337
337
|
return
|
|
338
338
|
}
|
|
339
339
|
|
|
340
|
+
// 📖 Zen models are built-in to OpenCode — they use the native `opencode` provider prefix
|
|
341
|
+
// 📖 and don't need a custom provider entry in opencode.json.
|
|
342
|
+
if (providerKey === 'opencode-zen') {
|
|
343
|
+
const zenModelRef = `opencode/${ocModelId}`
|
|
344
|
+
console.log(chalk.green(` Setting ${chalk.bold(model.label)} as default (Zen built-in)...`))
|
|
345
|
+
console.log(chalk.dim(` Model: ${zenModelRef}`))
|
|
346
|
+
console.log()
|
|
347
|
+
|
|
348
|
+
const config = loadOpenCodeConfig()
|
|
349
|
+
const backupPath = `${getOpenCodeConfigPath()}.backup-${Date.now()}`
|
|
350
|
+
|
|
351
|
+
if (existsSync(getOpenCodeConfigPath())) {
|
|
352
|
+
copyFileSync(getOpenCodeConfigPath(), backupPath)
|
|
353
|
+
console.log(chalk.dim(` Backup: ${backupPath}`))
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
config.model = zenModelRef
|
|
357
|
+
saveOpenCodeConfig(config)
|
|
358
|
+
|
|
359
|
+
const savedConfig = loadOpenCodeConfig()
|
|
360
|
+
console.log(chalk.dim(` Config saved to: ${getOpenCodeConfigPath()}`))
|
|
361
|
+
console.log(chalk.dim(` Default model in config: ${savedConfig.model || 'NOT SET'}`))
|
|
362
|
+
console.log()
|
|
363
|
+
|
|
364
|
+
if (savedConfig.model === config.model) {
|
|
365
|
+
console.log(chalk.green(` Default model set to: ${zenModelRef}`))
|
|
366
|
+
} else {
|
|
367
|
+
console.log(chalk.yellow(` Config might not have been saved correctly`))
|
|
368
|
+
}
|
|
369
|
+
console.log()
|
|
370
|
+
|
|
371
|
+
await spawnOpenCode(['--model', zenModelRef], providerKey, fcmConfig)
|
|
372
|
+
return
|
|
373
|
+
}
|
|
374
|
+
|
|
340
375
|
console.log(chalk.green(` Setting ${chalk.bold(model.label)} as default...`))
|
|
341
376
|
console.log(chalk.dim(` Model: ${modelRef}`))
|
|
342
377
|
console.log()
|
|
@@ -465,6 +500,20 @@ export async function startOpenCode(model, fcmConfig) {
|
|
|
465
500
|
options: { baseURL: 'https://apis.iflow.cn/v1', apiKey: '{env:IFLOW_API_KEY}' },
|
|
466
501
|
models: {}
|
|
467
502
|
}
|
|
503
|
+
} else if (providerKey === 'chutes') {
|
|
504
|
+
config.provider.chutes = {
|
|
505
|
+
npm: '@ai-sdk/openai-compatible',
|
|
506
|
+
name: 'Chutes AI',
|
|
507
|
+
options: { baseURL: 'https://chutes.ai/v1', apiKey: '{env:CHUTES_API_KEY}' },
|
|
508
|
+
models: {}
|
|
509
|
+
}
|
|
510
|
+
} else if (providerKey === 'ovhcloud') {
|
|
511
|
+
config.provider.ovhcloud = {
|
|
512
|
+
npm: '@ai-sdk/openai-compatible',
|
|
513
|
+
name: 'OVHcloud AI',
|
|
514
|
+
options: { baseURL: 'https://oai.endpoints.kepler.ai.cloud.ovh.net/v1', apiKey: '{env:OVH_AI_ENDPOINTS_ACCESS_TOKEN}' },
|
|
515
|
+
models: {}
|
|
516
|
+
}
|
|
468
517
|
}
|
|
469
518
|
}
|
|
470
519
|
|
|
@@ -596,6 +645,42 @@ export async function startOpenCodeDesktop(model, fcmConfig) {
|
|
|
596
645
|
return
|
|
597
646
|
}
|
|
598
647
|
|
|
648
|
+
// 📖 Zen models are built-in to OpenCode — remap to `opencode/<model-id>` and skip provider config.
|
|
649
|
+
if (providerKey === 'opencode-zen') {
|
|
650
|
+
const zenModelRef = `opencode/${ocModelId}`
|
|
651
|
+
console.log(chalk.green(` Setting ${chalk.bold(model.label)} as default for OpenCode Desktop (Zen built-in)...`))
|
|
652
|
+
console.log(chalk.dim(` Model: ${zenModelRef}`))
|
|
653
|
+
console.log()
|
|
654
|
+
|
|
655
|
+
const config = loadOpenCodeConfig()
|
|
656
|
+
const backupPath = `${getOpenCodeConfigPath()}.backup-${Date.now()}`
|
|
657
|
+
|
|
658
|
+
if (existsSync(getOpenCodeConfigPath())) {
|
|
659
|
+
copyFileSync(getOpenCodeConfigPath(), backupPath)
|
|
660
|
+
console.log(chalk.dim(` Backup: ${backupPath}`))
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
config.model = zenModelRef
|
|
664
|
+
saveOpenCodeConfig(config)
|
|
665
|
+
|
|
666
|
+
const savedConfig = loadOpenCodeConfig()
|
|
667
|
+
console.log(chalk.dim(` Config saved to: ${getOpenCodeConfigPath()}`))
|
|
668
|
+
console.log(chalk.dim(` Default model in config: ${savedConfig.model || 'NOT SET'}`))
|
|
669
|
+
console.log()
|
|
670
|
+
|
|
671
|
+
if (savedConfig.model === config.model) {
|
|
672
|
+
console.log(chalk.green(` Default model set to: ${zenModelRef}`))
|
|
673
|
+
} else {
|
|
674
|
+
console.log(chalk.yellow(` Config might not have been saved correctly`))
|
|
675
|
+
}
|
|
676
|
+
console.log()
|
|
677
|
+
console.log(chalk.dim(' Opening OpenCode Desktop...'))
|
|
678
|
+
console.log()
|
|
679
|
+
|
|
680
|
+
await launchDesktop()
|
|
681
|
+
return
|
|
682
|
+
}
|
|
683
|
+
|
|
599
684
|
console.log(chalk.green(` Setting ${chalk.bold(model.label)} as default for OpenCode Desktop...`))
|
|
600
685
|
console.log(chalk.dim(` Model: ${modelRef}`))
|
|
601
686
|
console.log()
|
|
@@ -724,6 +809,20 @@ export async function startOpenCodeDesktop(model, fcmConfig) {
|
|
|
724
809
|
options: { baseURL: 'https://apis.iflow.cn/v1', apiKey: '{env:IFLOW_API_KEY}' },
|
|
725
810
|
models: {}
|
|
726
811
|
}
|
|
812
|
+
} else if (providerKey === 'chutes') {
|
|
813
|
+
config.provider.chutes = {
|
|
814
|
+
npm: '@ai-sdk/openai-compatible',
|
|
815
|
+
name: 'Chutes AI',
|
|
816
|
+
options: { baseURL: 'https://chutes.ai/v1', apiKey: '{env:CHUTES_API_KEY}' },
|
|
817
|
+
models: {}
|
|
818
|
+
}
|
|
819
|
+
} else if (providerKey === 'ovhcloud') {
|
|
820
|
+
config.provider.ovhcloud = {
|
|
821
|
+
npm: '@ai-sdk/openai-compatible',
|
|
822
|
+
name: 'OVHcloud AI',
|
|
823
|
+
options: { baseURL: 'https://oai.endpoints.kepler.ai.cloud.ovh.net/v1', apiKey: '{env:OVH_AI_ENDPOINTS_ACCESS_TOKEN}' },
|
|
824
|
+
models: {}
|
|
825
|
+
}
|
|
727
826
|
}
|
|
728
827
|
}
|
|
729
828
|
|
package/src/render-table.js
CHANGED
|
@@ -104,7 +104,7 @@ export const PROVIDER_COLOR = new Proxy({}, {
|
|
|
104
104
|
})
|
|
105
105
|
|
|
106
106
|
// ─── renderTable: mode param controls footer hint text (opencode vs openclaw) ─────────
|
|
107
|
-
export function renderTable(results, pendingPings, frame, cursor = null, sortColumn = 'avg', sortDirection = 'asc', pingInterval = PING_INTERVAL, lastPingTime = Date.now(), mode = 'opencode', tierFilterMode = 0, scrollOffset = 0, terminalRows = 0, terminalCols = 0, originFilterMode = 0, legacyStatus = null, pingMode = 'normal', pingModeSource = 'auto', hideUnconfiguredModels = false, widthWarningStartedAt = null, widthWarningDismissed = false, widthWarningShowCount = 0, settingsUpdateState = 'idle', settingsUpdateLatestVersion = null, legacyFlag = false, startupLatestVersion = null, versionAlertsEnabled = true, favoritesPinnedAndSticky = false, customTextFilter = null, lastReleaseDate = null) {
|
|
107
|
+
export function renderTable(results, pendingPings, frame, cursor = null, sortColumn = 'avg', sortDirection = 'asc', pingInterval = PING_INTERVAL, lastPingTime = Date.now(), mode = 'opencode', tierFilterMode = 0, scrollOffset = 0, terminalRows = 0, terminalCols = 0, originFilterMode = 0, legacyStatus = null, pingMode = 'normal', pingModeSource = 'auto', hideUnconfiguredModels = false, widthWarningStartedAt = null, widthWarningDismissed = false, widthWarningShowCount = 0, settingsUpdateState = 'idle', settingsUpdateLatestVersion = null, legacyFlag = false, startupLatestVersion = null, versionAlertsEnabled = true, favoritesPinnedAndSticky = false, customTextFilter = null, lastReleaseDate = null, footerHidden = false) {
|
|
108
108
|
// 📖 Filter out hidden models for display
|
|
109
109
|
const visibleResults = results.filter(r => !r.hidden)
|
|
110
110
|
|
|
@@ -780,7 +780,7 @@ export function renderTable(results, pendingPings, frame, cursor = null, sortCol
|
|
|
780
780
|
|
|
781
781
|
// 📖 Line 2: command palette, recommend, feedback, theme
|
|
782
782
|
{
|
|
783
|
-
const cpText = '
|
|
783
|
+
const cpText = ' CTRL+P ⚡️ Command Palette '
|
|
784
784
|
const parts = [
|
|
785
785
|
{ text: ' ', key: null },
|
|
786
786
|
{ text: cpText, key: 'ctrl+p' },
|
|
@@ -802,7 +802,7 @@ export function renderTable(results, pendingPings, frame, cursor = null, sortCol
|
|
|
802
802
|
|
|
803
803
|
// 📖 Line 2: command palette (highlighted as new), recommend, feedback, and extended hints.
|
|
804
804
|
// 📖 CTRL+P ⚡️ Command Palette uses neon-green-on-dark-green background to highlight the feature.
|
|
805
|
-
const paletteLabel = chalk.bgRgb(0, 60, 0).rgb(57, 255, 20).bold('
|
|
805
|
+
const paletteLabel = chalk.bgRgb(0, 60, 0).rgb(57, 255, 20).bold(' CTRL+P ⚡️ Command Palette ')
|
|
806
806
|
lines.push(
|
|
807
807
|
' ' + paletteLabel + themeColors.dim(` • `) +
|
|
808
808
|
hotkey('Q', ' Smart Recommend') + themeColors.dim(` • `) +
|
|
@@ -878,27 +878,35 @@ export function renderTable(results, pendingPings, frame, cursor = null, sortCol
|
|
|
878
878
|
|
|
879
879
|
_lastLayout.footerHotkeys = footerHotkeys
|
|
880
880
|
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
:
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
)
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
881
|
+
if (footerHidden) {
|
|
882
|
+
// 📖 Collapsed footer: single line with toggle hint
|
|
883
|
+
lines.push(
|
|
884
|
+
' ' + themeColors.hotkey('Alt+W') + themeColors.dim(' Toggle Footer') +
|
|
885
|
+
themeColors.dim(' • Ctrl+C Exit')
|
|
886
|
+
)
|
|
887
|
+
} else {
|
|
888
|
+
const releaseLabel = lastReleaseDate
|
|
889
|
+
? chalk.rgb(255, 182, 193)(`Last release: ${lastReleaseDate}`)
|
|
890
|
+
: ''
|
|
891
|
+
|
|
892
|
+
lines.push(
|
|
893
|
+
' ' + themeColors.hotkey('N') + themeColors.dim(' Changelog') +
|
|
894
|
+
(filterBadge
|
|
895
|
+
? themeColors.dim(' • ') + filterBadge
|
|
896
|
+
: '') +
|
|
897
|
+
themeColors.dim(' • ') +
|
|
898
|
+
themeColors.dim('Ctrl+C Exit') +
|
|
899
|
+
(releaseLabel ? themeColors.dim(' • ') + releaseLabel : '')
|
|
900
|
+
)
|
|
901
|
+
|
|
902
|
+
// 📖 Discord link at the very bottom of the TUI
|
|
903
|
+
lines.push(
|
|
904
|
+
' 💬 ' +
|
|
905
|
+
themeColors.footerDiscord('\x1b]8;;https://discord.gg/ZTNFHvvCkU\x1b\\Join the Discord\x1b]8;;\x1b\\') +
|
|
906
|
+
themeColors.dim(' → ') +
|
|
907
|
+
themeColors.footerDiscord('https://discord.gg/ZTNFHvvCkU')
|
|
908
|
+
)
|
|
909
|
+
}
|
|
902
910
|
|
|
903
911
|
// 📖 Append \x1b[K (erase to EOL) to each line so leftover chars from previous
|
|
904
912
|
// 📖 frames are cleared. Then pad with blank cleared lines to fill the terminal,
|
package/src/shell-env.js
CHANGED
|
@@ -368,6 +368,10 @@ export async function promptShellEnvMigration(config) {
|
|
|
368
368
|
if (key.ctrl && key.name === 'c') {
|
|
369
369
|
if (process.stdin.isTTY) process.stdin.setRawMode(false)
|
|
370
370
|
process.stdin.removeListener('keypress', onKey)
|
|
371
|
+
// 📖 User chose to skip — record this so the prompt never shows again on restart
|
|
372
|
+
if (!config.settings) config.settings = {}
|
|
373
|
+
config.settings.shellEnvEnabled = false
|
|
374
|
+
saveConfig(config)
|
|
371
375
|
resolve('skip')
|
|
372
376
|
return
|
|
373
377
|
}
|
package/src/tool-bootstrap.js
CHANGED
|
@@ -280,6 +280,20 @@ export const TOOL_BOOTSTRAP_METADATA = {
|
|
|
280
280
|
},
|
|
281
281
|
},
|
|
282
282
|
},
|
|
283
|
+
jcode: {
|
|
284
|
+
binary: 'jcode',
|
|
285
|
+
docsUrl: 'https://github.com/1jehuang/jcode',
|
|
286
|
+
install: {
|
|
287
|
+
default: {
|
|
288
|
+
shellCommand: 'curl -fsSL https://raw.githubusercontent.com/1jehuang/jcode/master/scripts/install.sh | bash',
|
|
289
|
+
summary: 'Install jcode via the official installer script.',
|
|
290
|
+
},
|
|
291
|
+
win32: {
|
|
292
|
+
shellCommand: 'irm https://raw.githubusercontent.com/1jehuang/jcode/master/scripts/install.ps1 | iex',
|
|
293
|
+
summary: 'Install jcode via the official PowerShell installer.',
|
|
294
|
+
},
|
|
295
|
+
},
|
|
296
|
+
},
|
|
283
297
|
}
|
|
284
298
|
|
|
285
299
|
export function getToolBootstrapMeta(mode) {
|
package/src/tool-launchers.js
CHANGED
|
@@ -272,7 +272,7 @@ function writePiConfig(model, apiKey, baseUrl, paths = getDefaultToolPaths()) {
|
|
|
272
272
|
const modelsBackupPath = backupIfExists(modelsFilePath)
|
|
273
273
|
const modelsConfig = readJson(modelsFilePath, { providers: {} })
|
|
274
274
|
if (!modelsConfig.providers || typeof modelsConfig.providers !== 'object') modelsConfig.providers = {}
|
|
275
|
-
modelsConfig.providers.
|
|
275
|
+
modelsConfig.providers[model.providerKey] = {
|
|
276
276
|
baseUrl,
|
|
277
277
|
api: 'openai-completions',
|
|
278
278
|
apiKey,
|
|
@@ -284,7 +284,7 @@ function writePiConfig(model, apiKey, baseUrl, paths = getDefaultToolPaths()) {
|
|
|
284
284
|
const settingsFilePath = paths.piSettingsPath
|
|
285
285
|
const settingsBackupPath = backupIfExists(settingsFilePath)
|
|
286
286
|
const settingsConfig = readJson(settingsFilePath, {})
|
|
287
|
-
settingsConfig.defaultProvider =
|
|
287
|
+
settingsConfig.defaultProvider = model.providerKey
|
|
288
288
|
settingsConfig.defaultModel = model.modelId
|
|
289
289
|
writeJson(settingsFilePath, settingsConfig)
|
|
290
290
|
|
|
@@ -667,7 +667,7 @@ export function prepareExternalToolLaunch(mode, model, config, options = {}) {
|
|
|
667
667
|
const result = writePiConfig(model, apiKey, baseUrl, paths)
|
|
668
668
|
return {
|
|
669
669
|
command: 'pi',
|
|
670
|
-
args: ['--provider',
|
|
670
|
+
args: ['--provider', model.providerKey, '--model', model.modelId, '--api-key', apiKey],
|
|
671
671
|
env,
|
|
672
672
|
apiKey,
|
|
673
673
|
baseUrl,
|
|
@@ -758,6 +758,34 @@ export function prepareExternalToolLaunch(mode, model, config, options = {}) {
|
|
|
758
758
|
}
|
|
759
759
|
}
|
|
760
760
|
|
|
761
|
+
if (mode === 'jcode') {
|
|
762
|
+
if (model.providerKey === 'nvidia') {
|
|
763
|
+
const jcodeModelId = model.modelId.replace(/^nvidia\//, '').replace(/^openai\//, '')
|
|
764
|
+
const nvidiaBaseUrl = 'https://integrate.api.nvidia.com/v1'
|
|
765
|
+
const nvidiaEnv = { ...env, OPENAI_BASE_URL: nvidiaBaseUrl, OPENAI_API_BASE: nvidiaBaseUrl }
|
|
766
|
+
console.log(chalk.dim(` 📖 jcode will use provider: openai-compatible / model: ${jcodeModelId}`))
|
|
767
|
+
return {
|
|
768
|
+
command: 'jcode',
|
|
769
|
+
args: ['repl', '--provider', 'openai-compatible', '--model', jcodeModelId],
|
|
770
|
+
env: nvidiaEnv,
|
|
771
|
+
apiKey,
|
|
772
|
+
baseUrl: nvidiaBaseUrl,
|
|
773
|
+
meta,
|
|
774
|
+
configArtifacts: [],
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
console.log(chalk.dim(` 📖 jcode will use provider: ${model.providerKey} / model: ${model.modelId}`))
|
|
778
|
+
return {
|
|
779
|
+
command: 'jcode',
|
|
780
|
+
args: ['repl', '--provider', model.providerKey, '--model', model.modelId],
|
|
781
|
+
env,
|
|
782
|
+
apiKey,
|
|
783
|
+
baseUrl,
|
|
784
|
+
meta,
|
|
785
|
+
configArtifacts: [],
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
|
|
761
789
|
return {
|
|
762
790
|
blocked: true,
|
|
763
791
|
exitCode: 1,
|
|
@@ -854,6 +882,11 @@ export async function startExternalTool(mode, model, config) {
|
|
|
854
882
|
return spawnCommand(resolveLaunchCommand(mode, launchPlan.command), launchPlan.args, launchPlan.env)
|
|
855
883
|
}
|
|
856
884
|
|
|
885
|
+
if (mode === 'jcode') {
|
|
886
|
+
console.log(chalk.dim(` 📖 Launching jcode...`))
|
|
887
|
+
return spawnCommand(resolveLaunchCommand(mode, launchPlan.command), launchPlan.args, launchPlan.env)
|
|
888
|
+
}
|
|
889
|
+
|
|
857
890
|
console.log(chalk.red(` X Unsupported external tool mode: ${mode}`))
|
|
858
891
|
return 1
|
|
859
892
|
}
|
package/src/tool-metadata.js
CHANGED
|
@@ -41,6 +41,7 @@ export const TOOL_METADATA = {
|
|
|
41
41
|
cline: { label: 'Cline', emoji: '🧠', flag: '--cline', color: [100, 220, 180] },
|
|
42
42
|
rovo: { label: 'Rovo Dev CLI', emoji: '🦘', flag: '--rovo', color: [148, 163, 184], cliOnly: true },
|
|
43
43
|
gemini: { label: 'Gemini CLI', emoji: '♊', flag: '--gemini', color: [66, 165, 245], cliOnly: true },
|
|
44
|
+
jcode: { label: 'jcode', emoji: '🪼', flag: '--jcode', color: [255, 140, 0] },
|
|
44
45
|
xcode: { label: 'Xcode Intelligence',emoji: '🛠️', flag: '--xcode', color: [20, 126, 251] },
|
|
45
46
|
}
|
|
46
47
|
|
|
@@ -62,16 +63,17 @@ export const COMPAT_COLUMN_SLOTS = [
|
|
|
62
63
|
{ emoji: '🧠', toolKeys: ['cline'], color: [100, 220, 180] },
|
|
63
64
|
{ emoji: '🦘', toolKeys: ['rovo'], color: [148, 163, 184] },
|
|
64
65
|
{ emoji: '♊', toolKeys: ['gemini'], color: [66, 165, 245] },
|
|
66
|
+
{ emoji: '🪼', toolKeys: ['jcode'], color: [255, 140, 0] },
|
|
65
67
|
{ emoji: '🛠️', toolKeys: ['xcode'], color: [20, 126, 251] },
|
|
66
68
|
]
|
|
67
69
|
|
|
68
70
|
export const TOOL_MODE_ORDER = [
|
|
69
71
|
'opencode',
|
|
72
|
+
'pi',
|
|
70
73
|
'opencode-desktop',
|
|
71
74
|
'openclaw',
|
|
72
75
|
'crush',
|
|
73
76
|
'goose',
|
|
74
|
-
'pi',
|
|
75
77
|
'aider',
|
|
76
78
|
'qwen',
|
|
77
79
|
'openhands',
|
|
@@ -82,6 +84,7 @@ export const TOOL_MODE_ORDER = [
|
|
|
82
84
|
'xcode',
|
|
83
85
|
'rovo',
|
|
84
86
|
'gemini',
|
|
87
|
+
'jcode',
|
|
85
88
|
]
|
|
86
89
|
|
|
87
90
|
export function getToolMeta(mode) {
|