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.
@@ -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, GEMINI_API_KEY, etc.
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
- rovo: { label: 'Rovo Dev CLI', emoji: '๐Ÿฆ˜', flag: '--rovo', color: [148, 163, 184], cliOnly: true },
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
- { emoji: '๐Ÿฆ˜', toolKeys: ['rovo'], color: [148, 163, 184] },
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
- 'rovo',
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 rovo, gemini (which are CLI-only exclusives).
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 (rovo, gemini) are excluded since they have their own exclusive models.
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', 'rovo', 'opencode-zen')
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, --rovo, --hermes, --continue, --cline,
524
- // --xcode, --gemini, --jcode, --copilot, --forgecode,
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
- const hasAnyKey = Object.keys(sources).some(pk => !!getApiKey(config, pk))
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 (!hasAnyKey) {
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
  }
@@ -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
- rovo: null, // ๐Ÿ“– CLI tool โ€” no API key
111
- gemini: null, // ๐Ÿ“– CLI tool โ€” no API key
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 === 'rovo' || providerKey === 'gemini' || providerKey === 'opencode-zen') return 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 (rovo/gemini) but selected model doesn't belong to it
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 Rovo/Gemini mode but selected a model from a different provider
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, CLI-only tools, and Zen models manage auth differently โ€” skip API key warning for them.
491
- if (state.mode !== 'openclaw' && !isModelFromCliOnly && !isModelFromZen) {
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 (isModelFromCliOnly && !isToolInstalled(toolModeForProvider)) {
493
+ if (isActiveModeCliOnly && !isToolInstalled(toolModeForProvider)) {
504
494
  const installPlan = getToolInstallPlan(toolModeForProvider)
505
495
  if (installPlan.supported) {
506
496
  console.log()
@@ -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
- const rateSummary = themeColors.dim(meta.rateLimits || 'No limit info')
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 providerName = chalk.bold.rgb(...providerRgb)((meta.label || src.name || pk).slice(0, 22).padEnd(22))
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 coloredProviderName = chalk.bold.rgb(...selectedProviderRgb)(selectedMeta.label || selectedSource.name || selectedProviderKey)
209
- lines.push(` ${themeColors.textBold('Setup Instructions')} โ€” ${coloredProviderName}`)
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 โ†’ ๐Ÿฆ˜ Rovo โ†’ โ™Š Gemini)')}`)
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
 
@@ -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 (rovo, gemini) and Zen models don't need traditional API keys โ€”
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'