free-coding-models 0.5.25 โ 0.5.27
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/README.md +18 -36
- package/changelog/v0.5.26.md +21 -0
- package/changelog/v0.5.27.md +15 -0
- package/package.json +1 -1
- package/sources.js +100 -29
- package/src/core/benchmark.js +0 -10
- package/src/core/config.js +5 -1
- package/src/core/endpoint-installer.js +1 -2
- package/src/core/legacy-proxy-cleanup.js +2 -2
- package/src/core/provider-metadata.js +72 -18
- package/src/core/quota-capabilities.js +5 -1
- package/src/core/setup.js +5 -4
- package/src/core/tool-bootstrap.js +0 -22
- package/src/core/tool-launchers.js +1 -89
- package/src/core/tool-metadata.js +6 -13
- package/src/core/utils.js +2 -6
- package/src/tui/app.js +5 -6
- package/src/tui/command-palette.js +0 -2
- package/src/tui/key-handler.js +11 -21
- package/src/tui/overlays.js +13 -6
- package/src/tui/theme.js +10 -4
- package/src/tui/tui-filters.js +2 -2
- package/web/dist/assets/{index-BaQs4odm.js โ index-JUPDB-J-.js} +3 -3
- package/web/dist/index.html +1 -1
- package/web/server.js +4 -0
- package/web/src/components/atoms/ProviderLogo.jsx +0 -1
- package/web/src/components/settings/SettingsView.jsx +4 -4
|
@@ -427,29 +427,6 @@ function writeOpenHandsEnv(model, apiKey, baseUrl, paths = getDefaultToolPaths()
|
|
|
427
427
|
return { filePath, backupPath }
|
|
428
428
|
}
|
|
429
429
|
|
|
430
|
-
/**
|
|
431
|
-
* ๐ writeRovoConfig - Configure Rovo Dev CLI model selection
|
|
432
|
-
*
|
|
433
|
-
* Rovo Dev CLI uses ~/.rovodev/config.yml for configuration.
|
|
434
|
-
* We write the model ID to the config file before launching.
|
|
435
|
-
*
|
|
436
|
-
* @param {Object} model - Selected model with modelId
|
|
437
|
-
* @param {string} configPath - Path to Rovo config file
|
|
438
|
-
* @returns {{ filePath: string, backupPath: string | null }}
|
|
439
|
-
*/
|
|
440
|
-
function writeRovoConfig(model, configPath = join(homedir(), '.rovodev', 'config.yml')) {
|
|
441
|
-
const backupPath = backupIfExists(configPath)
|
|
442
|
-
const config = {
|
|
443
|
-
agent: {
|
|
444
|
-
modelId: model.modelId,
|
|
445
|
-
},
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
ensureDir(configPath)
|
|
449
|
-
writeFileSync(configPath, `agent:\n modelId: "${model.modelId}"\n`)
|
|
450
|
-
return { filePath: configPath, backupPath }
|
|
451
|
-
}
|
|
452
|
-
|
|
453
430
|
// ๐ writeContinueConfig โ write ~/.continue/config.yaml with the selected model.
|
|
454
431
|
// ๐ Continue CLI uses YAML config with `provider: openai` for OpenAI-compatible endpoints.
|
|
455
432
|
function writeContinueConfig(model, apiKey, baseUrl, paths = getDefaultToolPaths()) {
|
|
@@ -606,33 +583,6 @@ function restartHermesGateway() {
|
|
|
606
583
|
spawnSync(hermesBin, ['gateway', 'restart'], { stdio: 'ignore', timeout: 10000 })
|
|
607
584
|
}
|
|
608
585
|
|
|
609
|
-
/**
|
|
610
|
-
* ๐ buildGeminiEnv - Build environment variables for Gemini CLI
|
|
611
|
-
*
|
|
612
|
-
* Gemini CLI supports OpenAI-compatible APIs via environment variables:
|
|
613
|
-
* - GEMINI_API_BASE_URL: Custom API endpoint
|
|
614
|
-
* - GEMINI_API_KEY: API key for custom endpoint
|
|
615
|
-
*
|
|
616
|
-
* @param {Object} model - Selected model with providerKey
|
|
617
|
-
* @param {Object} config - Full app config
|
|
618
|
-
* @param {Object} options - Env options
|
|
619
|
-
* @returns {NodeJS.ProcessEnv}
|
|
620
|
-
*/
|
|
621
|
-
function buildGeminiEnv(model, config, options = {}) {
|
|
622
|
-
const providerKey = model.providerKey || 'gemini'
|
|
623
|
-
const apiKey = getApiKey(config, providerKey)
|
|
624
|
-
const baseUrl = getProviderBaseUrl(providerKey)
|
|
625
|
-
|
|
626
|
-
const env = cloneInheritedEnv(process.env, SANITIZED_TOOL_ENV_KEYS)
|
|
627
|
-
|
|
628
|
-
// If we have a custom API key and base URL, configure OpenAI-compatible mode
|
|
629
|
-
if (apiKey && baseUrl && options.includeProviderEnv) {
|
|
630
|
-
env.GEMINI_API_BASE_URL = baseUrl
|
|
631
|
-
env.GEMINI_API_KEY = apiKey
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
return env
|
|
635
|
-
}
|
|
636
586
|
|
|
637
587
|
/**
|
|
638
588
|
* ๐ buildCavemanEnv - Build environment variables for Caveman Code
|
|
@@ -643,7 +593,7 @@ function buildGeminiEnv(model, config, options = {}) {
|
|
|
643
593
|
*
|
|
644
594
|
* Supported env vars (from caveman-code source):
|
|
645
595
|
* - ANTHROPIC_API_KEY, OPENAI_API_KEY, GROQ_API_KEY, CEREBRAS_API_KEY
|
|
646
|
-
* - MISTRAL_API_KEY, GOOGLE_API_KEY,
|
|
596
|
+
* - MISTRAL_API_KEY, GOOGLE_API_KEY, etc.
|
|
647
597
|
*
|
|
648
598
|
* @param {Object} model - Selected model with providerKey
|
|
649
599
|
* @param {Object} config - Full app config
|
|
@@ -894,34 +844,6 @@ export function prepareExternalToolLaunch(mode, model, config, options = {}) {
|
|
|
894
844
|
}
|
|
895
845
|
}
|
|
896
846
|
|
|
897
|
-
if (mode === 'rovo') {
|
|
898
|
-
const result = writeRovoConfig(model, join(homedir(), '.rovodev', 'config.yml'), paths)
|
|
899
|
-
console.log(chalk.dim(` ๐ Rovo Dev CLI configured with model: ${model.modelId}`))
|
|
900
|
-
return {
|
|
901
|
-
command: 'acli',
|
|
902
|
-
args: ['rovodev', 'run'],
|
|
903
|
-
env,
|
|
904
|
-
apiKey: null,
|
|
905
|
-
baseUrl: null,
|
|
906
|
-
meta,
|
|
907
|
-
configArtifacts: [{ path: result.filePath, backupPath: result.backupPath, label: 'config' }],
|
|
908
|
-
}
|
|
909
|
-
}
|
|
910
|
-
|
|
911
|
-
if (mode === 'gemini') {
|
|
912
|
-
const geminiEnv = buildGeminiEnv(model, config, { includeProviderEnv: options.includeProviderEnv })
|
|
913
|
-
console.log(chalk.dim(` ๐ Gemini CLI will use model: ${model.modelId}`))
|
|
914
|
-
return {
|
|
915
|
-
command: 'gemini',
|
|
916
|
-
args: [],
|
|
917
|
-
env: { ...env, ...geminiEnv },
|
|
918
|
-
apiKey: geminiEnv.GEMINI_API_KEY || null,
|
|
919
|
-
baseUrl: geminiEnv.GEMINI_API_BASE_URL || null,
|
|
920
|
-
meta,
|
|
921
|
-
configArtifacts: [],
|
|
922
|
-
}
|
|
923
|
-
}
|
|
924
|
-
|
|
925
847
|
if (mode === 'caveman') {
|
|
926
848
|
const cavemanEnv = buildCavemanEnv(model, config, { includeProviderEnv: options.includeProviderEnv })
|
|
927
849
|
console.log(chalk.dim(` ๐ Caveman Code will use model: ${model.modelId}`))
|
|
@@ -1108,16 +1030,6 @@ export async function startExternalTool(mode, model, config) {
|
|
|
1108
1030
|
return spawnCommand(launchPlan.command, launchPlan.args, launchPlan.env)
|
|
1109
1031
|
}
|
|
1110
1032
|
|
|
1111
|
-
if (mode === 'rovo') {
|
|
1112
|
-
console.log(chalk.dim(` ๐ Launching Rovo Dev CLI in interactive mode...`))
|
|
1113
|
-
return spawnCommand(resolveLaunchCommand(mode, launchPlan.command), launchPlan.args, launchPlan.env)
|
|
1114
|
-
}
|
|
1115
|
-
|
|
1116
|
-
if (mode === 'gemini') {
|
|
1117
|
-
console.log(chalk.dim(` ๐ Launching Gemini CLI...`))
|
|
1118
|
-
return spawnCommand(resolveLaunchCommand(mode, launchPlan.command), launchPlan.args, launchPlan.env)
|
|
1119
|
-
}
|
|
1120
|
-
|
|
1121
1033
|
if (mode === 'caveman') {
|
|
1122
1034
|
console.log(chalk.dim(` ๐ Launching Caveman Code...`))
|
|
1123
1035
|
return spawnCommand(resolveLaunchCommand(mode, launchPlan.command), launchPlan.args, launchPlan.env)
|
|
@@ -41,8 +41,7 @@ export const TOOL_METADATA = {
|
|
|
41
41
|
hermes: { label: 'Hermes', emoji: '๐ฎ', flag: '--hermes', color: [200, 160, 255] },
|
|
42
42
|
'continue': { label: 'Continue CLI', emoji: 'โถ๏ธ', flag: '--continue', color: [255, 100, 100] },
|
|
43
43
|
cline: { label: 'Cline', emoji: '๐ง ', flag: '--cline', color: [100, 220, 180] },
|
|
44
|
-
|
|
45
|
-
gemini: { label: 'Gemini CLI', emoji: 'โ', flag: '--gemini', color: [66, 165, 245], cliOnly: true },
|
|
44
|
+
|
|
46
45
|
caveman: { label: 'Caveman Code', emoji: '๐ชจ', flag: '--caveman', color: [180, 130, 80] },
|
|
47
46
|
jcode: { label: 'jcode', emoji: '๐ชผ', flag: '--jcode', color: [255, 140, 0] },
|
|
48
47
|
xcode: { label: 'Xcode Intelligence',emoji: '๐ ๏ธ', flag: '--xcode', color: [20, 126, 251] },
|
|
@@ -69,8 +68,7 @@ export const COMPAT_COLUMN_SLOTS = [
|
|
|
69
68
|
{ emoji: 'โถ๏ธ', toolKeys: ['continue'], color: [255, 100, 100] },
|
|
70
69
|
{ emoji: '๐ง ', toolKeys: ['cline'], color: [100, 220, 180] },
|
|
71
70
|
{ emoji: '๐งญ', toolKeys: ['fcm_router'], color: [80, 200, 120] },
|
|
72
|
-
|
|
73
|
-
{ emoji: 'โ', toolKeys: ['gemini'], color: [66, 165, 245] },
|
|
71
|
+
|
|
74
72
|
{ emoji: '๐ชจ', toolKeys: ['caveman'], color: [180, 130, 80] },
|
|
75
73
|
{ emoji: '๐ชผ', toolKeys: ['jcode'], color: [255, 140, 0] },
|
|
76
74
|
{ emoji: '๐ ๏ธ', toolKeys: ['xcode'], color: [20, 126, 251] },
|
|
@@ -97,8 +95,7 @@ export const TOOL_MODE_ORDER = [
|
|
|
97
95
|
'cline',
|
|
98
96
|
'xcode',
|
|
99
97
|
'fcm_router',
|
|
100
|
-
|
|
101
|
-
'gemini',
|
|
98
|
+
|
|
102
99
|
'caveman',
|
|
103
100
|
'copilot',
|
|
104
101
|
'forgecode',
|
|
@@ -112,27 +109,23 @@ export function getToolModeOrder() {
|
|
|
112
109
|
return [...TOOL_MODE_ORDER]
|
|
113
110
|
}
|
|
114
111
|
|
|
115
|
-
// ๐ Regular tools: all tools EXCEPT
|
|
112
|
+
// ๐ Regular tools: all tools EXCEPT cliOnly ones (which have exclusive models).
|
|
116
113
|
// ๐ Used as the default compatible set for normal provider models.
|
|
117
114
|
const REGULAR_TOOLS = Object.keys(TOOL_METADATA).filter(k => !TOOL_METADATA[k].cliOnly)
|
|
118
115
|
|
|
119
116
|
// ๐ Zen models use OpenAI-compatible endpoints (https://opencode.ai/zen/v1/chat/completions)
|
|
120
117
|
// ๐ and can be used by ANY tool that supports custom OpenAI-compatible providers.
|
|
121
118
|
// ๐ They are NOT locked to OpenCode โ confirmed by OpenCode's own "no lock-in" design goal.
|
|
122
|
-
// ๐ Only CLI-only tools
|
|
119
|
+
// ๐ Only CLI-only tools are excluded since they have their own exclusive models.
|
|
123
120
|
|
|
124
121
|
/**
|
|
125
122
|
* ๐ Returns the list of tool keys a model is compatible with.
|
|
126
|
-
* - Rovo models โ only 'rovo'
|
|
127
|
-
* - Gemini models โ only 'gemini'
|
|
128
123
|
* - OpenCode Zen models โ all non-cliOnly tools (Zen uses OpenAI-compatible endpoints)
|
|
129
124
|
* - Regular models โ all non-cliOnly tools
|
|
130
|
-
* @param {string} providerKey โ the source key from sources.js (e.g. 'nvidia', '
|
|
125
|
+
* @param {string} providerKey โ the source key from sources.js (e.g. 'nvidia', 'opencode-zen')
|
|
131
126
|
* @returns {string[]} โ array of compatible tool keys
|
|
132
127
|
*/
|
|
133
128
|
export function getCompatibleTools(providerKey) {
|
|
134
|
-
if (providerKey === 'rovo') return ['rovo']
|
|
135
|
-
if (providerKey === 'gemini') return ['gemini']
|
|
136
129
|
// ๐ Zen models use /v1/chat/completions โ compatible with any OpenAI-compatible tool
|
|
137
130
|
if (providerKey === 'opencode-zen') return REGULAR_TOOLS
|
|
138
131
|
return REGULAR_TOOLS
|
package/src/core/utils.js
CHANGED
|
@@ -520,8 +520,8 @@ export function findBestModel(results) {
|
|
|
520
520
|
// - API key: first positional arg that does not look like a CLI flag (e.g., "nvapi-xxx")
|
|
521
521
|
// - Boolean flags: --best, --fiable, --opencode, --opencode-desktop, --opencode-web, --openclaw,
|
|
522
522
|
// --aider, --crush, --goose, --qwen, --kilo,
|
|
523
|
-
// --openhands, --amp, --pi, --
|
|
524
|
-
// --xcode, --
|
|
523
|
+
// --openhands, --amp, --pi, --hermes, --continue, --cline,
|
|
524
|
+
// --xcode, --jcode, --copilot, --forgecode,
|
|
525
525
|
// --daemon, --daemon-bg, --daemon-stop,
|
|
526
526
|
// --daemon-status, --no-telemetry, --json, --help/-h (case-insensitive)
|
|
527
527
|
// --playground / playground subcommand (open the in-TUI chat playground)
|
|
@@ -600,12 +600,10 @@ export function parseArgs(argv) {
|
|
|
600
600
|
const openHandsMode = flags.includes('--openhands')
|
|
601
601
|
const ampMode = flags.includes('--amp')
|
|
602
602
|
const piMode = flags.includes('--pi')
|
|
603
|
-
const rovoMode = flags.includes('--rovo')
|
|
604
603
|
const hermesMode = flags.includes('--hermes')
|
|
605
604
|
const continueMode = flags.includes('--continue')
|
|
606
605
|
const clineMode = flags.includes('--cline')
|
|
607
606
|
const xcodeMode = flags.includes('--xcode')
|
|
608
|
-
const geminiMode = flags.includes('--gemini')
|
|
609
607
|
const cavemanMode = flags.includes('--caveman')
|
|
610
608
|
const jcodeMode = flags.includes('--jcode')
|
|
611
609
|
const copilotMode = flags.includes('--copilot')
|
|
@@ -669,8 +667,6 @@ export function parseArgs(argv) {
|
|
|
669
667
|
continueMode,
|
|
670
668
|
clineMode,
|
|
671
669
|
xcodeMode,
|
|
672
|
-
rovoMode,
|
|
673
|
-
geminiMode,
|
|
674
670
|
cavemanMode,
|
|
675
671
|
jcodeMode,
|
|
676
672
|
copilotMode,
|
package/src/tui/app.js
CHANGED
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
* โ๏ธ Configuration:
|
|
60
60
|
* - API keys stored per-provider in ~/.free-coding-models.json (0600 perms)
|
|
61
61
|
* - Old ~/.free-coding-models plain-text auto-migrated as nvidia key on first run
|
|
62
|
-
* - Env vars override config: NVIDIA_API_KEY, GROQ_API_KEY, CEREBRAS_API_KEY, OPENROUTER_API_KEY, GITHUB_TOKEN, MISTRAL_API_KEY, SCALEWAY_API_KEY, GOOGLE_API_KEY, CLOUDFLARE_API_TOKEN, DASHSCOPE_API_KEY, ZAI_API_KEY, etc.
|
|
62
|
+
* - Env vars override config: NVIDIA_API_KEY, GROQ_API_KEY, CEREBRAS_API_KEY, OPENROUTER_API_KEY, GITHUB_TOKEN, MISTRAL_API_KEY, SCALEWAY_API_KEY, GOOGLE_API_KEY, CLOUDFLARE_API_TOKEN, DASHSCOPE_API_KEY, ZAI_API_KEY, LLM7_API_KEY, ROUTEWAY_API_KEY, NOVITA_API_KEY, OLLAMA_API_KEY, etc.
|
|
63
63
|
* - ZAI (z.ai) uses a non-standard base path; cloudflare needs CLOUDFLARE_ACCOUNT_ID in env.
|
|
64
64
|
* - Cloudflare Workers AI requires both CLOUDFLARE_API_TOKEN (or CLOUDFLARE_API_KEY) and CLOUDFLARE_ACCOUNT_ID
|
|
65
65
|
* - Models loaded from sources.js โ all provider/model definitions are centralized there
|
|
@@ -205,10 +205,11 @@ export async function runApp(cliArgs, config, startupOptions = {}) {
|
|
|
205
205
|
|
|
206
206
|
// ๐ Profile system removed - API keys now persist permanently across all sessions
|
|
207
207
|
|
|
208
|
-
// ๐ Check if any provider has a key โ if not, run the first-time setup wizard
|
|
209
|
-
|
|
208
|
+
// ๐ Check if any provider has a key โ if not, run the first-time setup wizard.
|
|
209
|
+
// ๐ Keyless providers (Kilo/LLM7) can still run immediately, so they also count as usable.
|
|
210
|
+
const hasAnyUsableProvider = Object.keys(sources).some(pk => !!getApiKey(config, pk) || PROVIDER_METADATA[pk]?.noKeyNeeded)
|
|
210
211
|
|
|
211
|
-
if (!
|
|
212
|
+
if (!hasAnyUsableProvider) {
|
|
212
213
|
const result = await promptApiKey(config)
|
|
213
214
|
if (!result) {
|
|
214
215
|
console.log()
|
|
@@ -249,8 +250,6 @@ export async function runApp(cliArgs, config, startupOptions = {}) {
|
|
|
249
250
|
cline: cliArgs.clineMode,
|
|
250
251
|
xcode: cliArgs.xcodeMode,
|
|
251
252
|
pi: cliArgs.piMode,
|
|
252
|
-
rovo: cliArgs.rovoMode,
|
|
253
|
-
gemini: cliArgs.geminiMode,
|
|
254
253
|
caveman: cliArgs.cavemanMode,
|
|
255
254
|
copilot: cliArgs.copilotMode,
|
|
256
255
|
forgecode: cliArgs.forgecodeMode,
|
|
@@ -45,8 +45,6 @@ const TOOL_MODE_DESCRIPTIONS = {
|
|
|
45
45
|
hermes: 'Launch Hermes Agent with the selected model.',
|
|
46
46
|
'continue': 'Launch Continue CLI with the selected model.',
|
|
47
47
|
cline: 'Launch Cline CLI with the selected model.',
|
|
48
|
-
rovo: 'Rovo Dev CLI model (launch with Rovo tool only).',
|
|
49
|
-
gemini: 'Gemini CLI model (launch with Gemini tool only).',
|
|
50
48
|
caveman: 'Caveman Code โ token-efficient coding agent (launch with Caveman tool only).',
|
|
51
49
|
jcode: 'Launch jcode coding agent with the selected model.',
|
|
52
50
|
}
|
package/src/tui/key-handler.js
CHANGED
|
@@ -107,8 +107,11 @@ const PROVIDER_AUTH_ENDPOINTS = {
|
|
|
107
107
|
zai: null, // ๐ ZAI undocumented; use ping only
|
|
108
108
|
googleai: null, // ๐ Google AI Studio has no OpenAI-compatible /models; use ping
|
|
109
109
|
'opencode-zen': null, // ๐ OpenCode Zen uses OpenCode auth only; use ping
|
|
110
|
-
|
|
111
|
-
|
|
110
|
+
kilo: { url: 'https://api.kilo.ai/api/gateway/models', method: 'GET' },
|
|
111
|
+
llm7: { url: 'https://api.llm7.io/v1/models', method: 'GET' },
|
|
112
|
+
routeway: { url: 'https://api.routeway.ai/v1/models', method: 'GET' },
|
|
113
|
+
novita: { url: 'https://api.novita.ai/openai/v1/models', method: 'GET' },
|
|
114
|
+
'ollama-cloud': { url: 'https://ollama.com/v1/models', method: 'GET' },
|
|
112
115
|
}
|
|
113
116
|
|
|
114
117
|
// ๐ Sleep helper kept local to this module so the Settings key test flow can
|
|
@@ -337,7 +340,7 @@ export function createKeyHandler(ctx) {
|
|
|
337
340
|
}
|
|
338
341
|
|
|
339
342
|
function getModelTelemetryFamily(providerKey) {
|
|
340
|
-
if (providerKey === '
|
|
343
|
+
if (providerKey === 'opencode-zen') return providerKey
|
|
341
344
|
return 'standard'
|
|
342
345
|
}
|
|
343
346
|
|
|
@@ -443,15 +446,14 @@ export function createKeyHandler(ctx) {
|
|
|
443
446
|
console.log()
|
|
444
447
|
|
|
445
448
|
// ๐ CLI-only tool compatibility checks:
|
|
446
|
-
// ๐ Case A: Active tool mode is CLI-only
|
|
449
|
+
// ๐ Case A: Active tool mode is CLI-only but selected model doesn't belong to it
|
|
447
450
|
// ๐ Case B: Selected model belongs to a CLI-only provider but active mode is something else
|
|
448
451
|
const activeMeta = getToolMeta(state.mode)
|
|
449
452
|
const isActiveModeCliOnly = activeMeta.cliOnly === true
|
|
450
|
-
const isModelFromCliOnly = selected.providerKey === 'rovo' || selected.providerKey === 'gemini'
|
|
451
453
|
const isModelFromZen = selected.providerKey === 'opencode-zen'
|
|
452
454
|
const modelBelongsToActiveMode = selected.providerKey === state.mode
|
|
453
455
|
|
|
454
|
-
// ๐ Case A: User is in
|
|
456
|
+
// ๐ Case A: User is in a CLI-only tool mode but selected a model from a different provider
|
|
455
457
|
if (isActiveModeCliOnly && !modelBelongsToActiveMode) {
|
|
456
458
|
trackAppUseResult(selected, 'blocked_incompatible_model', {
|
|
457
459
|
blocked_by_tool_mode: state.mode,
|
|
@@ -472,23 +474,11 @@ export function createKeyHandler(ctx) {
|
|
|
472
474
|
process.exit(0)
|
|
473
475
|
}
|
|
474
476
|
|
|
475
|
-
// ๐ Case B: Selected model is from a CLI-only provider but active mode is different
|
|
476
|
-
if (isModelFromCliOnly && !modelBelongsToActiveMode) {
|
|
477
|
-
const modelMeta = getToolMeta(selected.providerKey)
|
|
478
|
-
console.log(chalk.yellow(` โ ${selected.label} is a ${modelMeta.label}-exclusive model.`))
|
|
479
|
-
console.log(chalk.yellow(` Your current tool is: ${activeMeta.label}`))
|
|
480
|
-
console.log()
|
|
481
|
-
console.log(chalk.cyan(` Switching to ${modelMeta.label} and launching...`))
|
|
482
|
-
setToolMode(selected.providerKey)
|
|
483
|
-
console.log(chalk.green(` โ Switched to ${modelMeta.label}`))
|
|
484
|
-
console.log()
|
|
485
|
-
}
|
|
486
|
-
|
|
487
477
|
// ๐ Case C removed: Zen models now work with any OpenAI-compatible tool (Pi, Aider, etc.)
|
|
488
478
|
// ๐ The Zen endpoint (https://opencode.ai/zen/v1/chat/completions) is standard OpenAI-compatible.
|
|
489
479
|
|
|
490
|
-
// ๐ OpenClaw
|
|
491
|
-
if (state.mode !== 'openclaw' && !
|
|
480
|
+
// ๐ OpenClaw and Zen models manage auth differently โ skip API key warning for them.
|
|
481
|
+
if (state.mode !== 'openclaw' && !isModelFromZen) {
|
|
492
482
|
const selectedApiKey = getApiKey(state.config, selected.providerKey)
|
|
493
483
|
if (!selectedApiKey) {
|
|
494
484
|
console.log(chalk.yellow(` Warning: No API key configured for ${selected.providerKey}.`))
|
|
@@ -500,7 +490,7 @@ export function createKeyHandler(ctx) {
|
|
|
500
490
|
|
|
501
491
|
// ๐ CLI-only tool auto-install check โ verify the CLI binary is available before launch.
|
|
502
492
|
const toolModeForProvider = selected.providerKey
|
|
503
|
-
if (
|
|
493
|
+
if (isActiveModeCliOnly && !isToolInstalled(toolModeForProvider)) {
|
|
504
494
|
const installPlan = getToolInstallPlan(toolModeForProvider)
|
|
505
495
|
if (installPlan.supported) {
|
|
506
496
|
console.log()
|
package/src/tui/overlays.js
CHANGED
|
@@ -24,6 +24,7 @@ import { buildCliHelpLines } from './cli-help.js'
|
|
|
24
24
|
import { renderRouterDashboard as renderRouterDashboardOverlay } from '../core/router-dashboard.js'
|
|
25
25
|
import { renderPlayground as renderPlaygroundOverlay } from '../core/playground.js'
|
|
26
26
|
import { themeColors, getThemeStatusLabel, getProviderRgb } from './theme.js'
|
|
27
|
+
import { getProviderBillingNote, getProviderLabelWithBilling } from '../core/provider-metadata.js'
|
|
27
28
|
|
|
28
29
|
export function createOverlayRenderers(state, deps) {
|
|
29
30
|
const {
|
|
@@ -183,13 +184,16 @@ export function createOverlayRenderers(state, deps) {
|
|
|
183
184
|
else if (testResult === 'rate_limited') testBadge = themeColors.warning('[Rate limit โณ]')
|
|
184
185
|
else if (testResult === 'no_callable_model') testBadge = chalk.rgb(...getProviderRgb('openrouter'))('[No model โ ]')
|
|
185
186
|
else if (testResult === 'fail') testBadge = themeColors.error('[Test โ]')
|
|
186
|
-
// ๐ No truncation of rate limits - overlay now uses 100% terminal width
|
|
187
|
-
|
|
187
|
+
// ๐ No truncation of rate limits - overlay now uses 100% terminal width.
|
|
188
|
+
// ๐ Paid/credits-required providers get an explicit money marker + parenthesized detail.
|
|
189
|
+
const billingNote = getProviderBillingNote(pk)
|
|
190
|
+
const rateSummary = themeColors.dim(`${meta.rateLimits || 'No limit info'}${billingNote ? ` ${billingNote}` : ''}`)
|
|
188
191
|
|
|
189
192
|
const enabledBadge = enabled ? themeColors.successBold('โ
') : themeColors.errorBold('โ')
|
|
190
193
|
// ๐ Color provider names the same way as in the main table
|
|
191
194
|
const providerRgb = PROVIDER_COLOR[pk] ?? [105, 190, 245]
|
|
192
|
-
const
|
|
195
|
+
const providerLabel = getProviderLabelWithBilling(pk, src.name || pk)
|
|
196
|
+
const providerName = chalk.bold.rgb(...providerRgb)(providerLabel.slice(0, 24).padEnd(24))
|
|
193
197
|
|
|
194
198
|
const row = `${bullet(isCursor)}[ ${enabledBadge} ] ${providerName} ${padEndDisplay(keyDisplay, 30)} ${testBadge} ${rateSummary}`
|
|
195
199
|
cursorLineByRow[i] = lines.length
|
|
@@ -205,9 +209,12 @@ export function createOverlayRenderers(state, deps) {
|
|
|
205
209
|
const setupStatus = selectedKey ? themeColors.success('API key detected โ
') : themeColors.warning('API key missing โ ')
|
|
206
210
|
// ๐ Color the provider name in the setup instructions header
|
|
207
211
|
const selectedProviderRgb = PROVIDER_COLOR[selectedProviderKey] ?? [105, 190, 245]
|
|
208
|
-
const
|
|
209
|
-
|
|
212
|
+
const selectedProviderLabel = getProviderLabelWithBilling(selectedProviderKey, selectedSource.name || selectedProviderKey)
|
|
213
|
+
const selectedBillingNote = getProviderBillingNote(selectedProviderKey)
|
|
214
|
+
const coloredProviderName = chalk.bold.rgb(...selectedProviderRgb)(selectedProviderLabel)
|
|
215
|
+
lines.push(` ${themeColors.textBold('Setup Instructions')} โ ${coloredProviderName}${selectedBillingNote ? ' ' + themeColors.warning(selectedBillingNote) : ''}`)
|
|
210
216
|
lines.push(themeColors.dim(` 1) Create a ${selectedMeta.label || selectedSource.name} account: ${selectedMeta.signupUrl || 'signup link missing'}`))
|
|
217
|
+
if (selectedBillingNote) lines.push(themeColors.warning(` ๐ฐ Paid provider note: ${selectedBillingNote}`))
|
|
211
218
|
lines.push(themeColors.dim(` 2) ${selectedMeta.signupHint || 'Generate an API key and paste it with Enter on this row'}`))
|
|
212
219
|
lines.push(themeColors.dim(` 3) Press ${themeColors.hotkey('T')} to test your key. Status: ${setupStatus}`))
|
|
213
220
|
if (selectedProviderKey === 'cloudflare') {
|
|
@@ -944,7 +951,7 @@ export function createOverlayRenderers(state, deps) {
|
|
|
944
951
|
lines.push(` ${key('Ctrl+A')} AI Speed Test ${hint('(benchmark selected model โ time + TPS)')}`)
|
|
945
952
|
lines.push(` ${key('Ctrl+U')} Global AI Speed Test ${hint('(benchmark all models; Settings can auto-run it on startup)')}`)
|
|
946
953
|
lines.push(` ${key('E')} Cycle filter mode ${hint('(Normal โ Configured only โ Usable only)')}`)
|
|
947
|
-
lines.push(` ${key('Z')} Cycle tool mode ${hint('(๐ฆ OpenCode โ ฯ Pi โ ๐ชผ jcode โ ๐ฆ Desktop โ ๐ฆ OpenClaw โ ๐ Crush โ ๐ชฟ Goose โ ๐ Aider โ ๐ Qwen โ ๐คฒ OpenHands โ โก Amp
|
|
954
|
+
lines.push(` ${key('Z')} Cycle tool mode ${hint('(๐ฆ OpenCode โ ฯ Pi โ ๐ชผ jcode โ ๐ฆ Desktop โ ๐ฆ OpenClaw โ ๐ Crush โ ๐ชฟ Goose โ ๐ Aider โ ๐ Qwen โ ๐คฒ OpenHands โ โก Amp)')}`)
|
|
948
955
|
lines.push(` ${key('F')} Toggle favorite on selected row ${hint('(1๏ธโฃ2๏ธโฃ3๏ธโฃ = router fallback order, capped at ๐)')}`)
|
|
949
956
|
lines.push(` ${key('โงโ/โงโ')} Reorder selected favorite up/down ${hint('(changes router priority)')}`)
|
|
950
957
|
lines.push(` ${key('Y')} Toggle favorites mode ${hint('(Pinned + always visible โ Normal filter/sort behavior)')}`)
|
package/src/tui/theme.js
CHANGED
|
@@ -187,9 +187,12 @@ const PROVIDER_PALETTES = {
|
|
|
187
187
|
qwen: [255, 213, 128],
|
|
188
188
|
zai: [150, 208, 255],
|
|
189
189
|
iflow: [211, 229, 101],
|
|
190
|
-
rovo: [148, 163, 184],
|
|
191
|
-
gemini: [66, 165, 245],
|
|
192
190
|
'opencode-zen': [185, 146, 255],
|
|
191
|
+
kilo: [120, 255, 190],
|
|
192
|
+
llm7: [180, 255, 140],
|
|
193
|
+
routeway: [130, 210, 255],
|
|
194
|
+
novita: [255, 185, 120],
|
|
195
|
+
'ollama-cloud': [230, 230, 230],
|
|
193
196
|
},
|
|
194
197
|
light: {
|
|
195
198
|
nvidia: [0, 126, 73],
|
|
@@ -214,9 +217,12 @@ const PROVIDER_PALETTES = {
|
|
|
214
217
|
qwen: [132, 89, 0],
|
|
215
218
|
zai: [0, 104, 171],
|
|
216
219
|
iflow: [107, 130, 0],
|
|
217
|
-
rovo: [90, 100, 126],
|
|
218
|
-
gemini: [15, 97, 175],
|
|
219
220
|
'opencode-zen': [108, 58, 183],
|
|
221
|
+
kilo: [0, 130, 82],
|
|
222
|
+
llm7: [73, 130, 0],
|
|
223
|
+
routeway: [0, 105, 180],
|
|
224
|
+
novita: [173, 84, 0],
|
|
225
|
+
'ollama-cloud': [88, 88, 88],
|
|
220
226
|
},
|
|
221
227
|
}
|
|
222
228
|
|
package/src/tui/tui-filters.js
CHANGED
|
@@ -55,10 +55,10 @@ export function createTuiFilters(state, { sources, getApiKey, PROVIDER_METADATA
|
|
|
55
55
|
|
|
56
56
|
state.results.forEach(r => {
|
|
57
57
|
const stickyFavorite = state.favoritesPinnedAndSticky && r.isFavorite
|
|
58
|
-
// ๐ CLI-only tools
|
|
58
|
+
// ๐ CLI-only tools and Zen models don't need traditional API keys โ
|
|
59
59
|
// ๐ they authenticate via their own CLI login flow, so "configured only" should never hide them.
|
|
60
60
|
const providerMeta = PROVIDER_METADATA[r.providerKey]
|
|
61
|
-
const noKeyNeeded = providerMeta?.cliOnly || providerMeta?.zenOnly
|
|
61
|
+
const noKeyNeeded = providerMeta?.cliOnly || providerMeta?.zenOnly || providerMeta?.noKeyNeeded
|
|
62
62
|
// ๐ E toggles "Show only configured & working models":
|
|
63
63
|
// ๐ hide models where provider has no key, or where the health status is noauth/auth_error (but keep timeout and 429)
|
|
64
64
|
const badHealth = r.status === 'noauth' || r.status === 'auth_error'
|