free-coding-models 0.2.15 → 0.3.0

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.
@@ -13,7 +13,7 @@
13
13
  * - Emoji-aware padding via padEndDisplay for aligned verdict/status cells
14
14
  * - Viewport clipping with above/below indicators
15
15
  * - Smart badges (mode, tier filter, origin filter, profile)
16
- * - Proxy status line integrated in footer
16
+ * - Footer J badge: green "Proxy On" / red "Proxy Off" indicator with direct overlay access
17
17
  * - Install-endpoints shortcut surfaced directly in the footer hints
18
18
  * - Distinct auth-failure vs missing-key health labels so configured providers stay honest
19
19
  *
@@ -40,7 +40,7 @@ import { TIER_COLOR } from './tier-colors.js'
40
40
  import { getAvg, getVerdict, getUptime, getStabilityScore, getVersionStatusInfo } from './utils.js'
41
41
  import { usagePlaceholderForProvider } from './ping.js'
42
42
  import { formatTokenTotalCompact } from './token-usage-reader.js'
43
- import { calculateViewport, sortResultsWithPinnedFavorites, renderProxyStatusLine, padEndDisplay } from './render-helpers.js'
43
+ import { calculateViewport, sortResultsWithPinnedFavorites, padEndDisplay } from './render-helpers.js'
44
44
  import { getToolMeta } from './tool-metadata.js'
45
45
 
46
46
  const ACTIVE_FILTER_BG_BY_TIER = {
@@ -92,7 +92,7 @@ export function setActiveProxy(proxyInstance) {
92
92
  }
93
93
 
94
94
  // ─── renderTable: mode param controls footer hint text (opencode vs openclaw) ─────────
95
- 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, activeProfile = null, profileSaveMode = false, profileSaveBuffer = '', proxyStartupStatus = null, pingMode = 'normal', pingModeSource = 'auto', hideUnconfiguredModels = false, widthWarningStartedAt = null, widthWarningDismissed = false, settingsUpdateState = 'idle', settingsUpdateLatestVersion = null, proxyEnabled = false, isOutdated = false, latestVersion = null) {
95
+ 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, activeProfile = null, profileSaveMode = false, profileSaveBuffer = '', proxyStartupStatus = null, pingMode = 'normal', pingModeSource = 'auto', hideUnconfiguredModels = false, widthWarningStartedAt = null, widthWarningDismissed = false, widthWarningShowCount = 0, settingsUpdateState = 'idle', settingsUpdateLatestVersion = null, proxyEnabled = false, isOutdated = false, latestVersion = null) {
96
96
  // 📖 Filter out hidden models for display
97
97
  const visibleResults = results.filter(r => !r.hidden)
98
98
 
@@ -193,23 +193,25 @@ export function renderTable(results, pendingPings, frame, cursor = null, sortCol
193
193
  const W_TOKENS = 7
194
194
  const W_USAGE = 7
195
195
  const MIN_TABLE_WIDTH = 166
196
- const warningDurationMs = 5_000
196
+ const warningDurationMs = 4_000
197
197
  const elapsed = widthWarningStartedAt ? Math.max(0, Date.now() - widthWarningStartedAt) : warningDurationMs
198
198
  const remainingMs = Math.max(0, warningDurationMs - elapsed)
199
- const showWidthWarning = terminalCols > 0 && terminalCols < MIN_TABLE_WIDTH && !widthWarningDismissed && remainingMs > 0
199
+ const showWidthWarning = terminalCols > 0 && terminalCols < MIN_TABLE_WIDTH && !widthWarningDismissed && widthWarningShowCount < 2 && remainingMs > 0
200
200
 
201
201
  if (showWidthWarning) {
202
202
  const lines = []
203
- const blankLines = Math.max(0, Math.floor(((terminalRows || 24) - 5) / 2))
204
- const warning = 'Please maximize your terminal for optimal use.'
205
- const warning2 = 'The current terminal is too small.'
206
- const warning3 = 'Reduce font size or maximize width of terminal.'
203
+ const blankLines = Math.max(0, Math.floor(((terminalRows || 24) - 7) / 2))
204
+ const warning = '🖥️ Please maximize your terminal for optimal use.'
205
+ const warning2 = '⚠️ The current terminal is too small.'
206
+ const warning3 = '📏 Reduce font size or maximize width of terminal.'
207
207
  const padLeft = Math.max(0, Math.floor((terminalCols - warning.length) / 2))
208
208
  const padLeft2 = Math.max(0, Math.floor((terminalCols - warning2.length) / 2))
209
209
  const padLeft3 = Math.max(0, Math.floor((terminalCols - warning3.length) / 2))
210
210
  for (let i = 0; i < blankLines; i++) lines.push('')
211
211
  lines.push(' '.repeat(padLeft) + chalk.red.bold(warning))
212
+ lines.push('')
212
213
  lines.push(' '.repeat(padLeft2) + chalk.red(warning2))
214
+ lines.push('')
213
215
  lines.push(' '.repeat(padLeft3) + chalk.red(warning3))
214
216
  lines.push('')
215
217
  lines.push(' '.repeat(Math.max(0, Math.floor((terminalCols - 34) / 2))) + chalk.yellow(`this message will hide in ${(remainingMs / 1000).toFixed(1)}s`))
@@ -620,7 +622,8 @@ export function renderTable(results, pendingPings, frame, cursor = null, sortCol
620
622
  const activeHotkey = (keyLabel, text, bg = [57, 255, 20], fg = [0, 0, 0]) => chalk.bgRgb(...bg).rgb(...fg)(` ${keyLabel}${text} `)
621
623
  // 📖 Line 1: core navigation + filtering shortcuts
622
624
  lines.push(
623
- chalk.dim(` ↑↓ Navigate • `) +
625
+ (proxyEnabled ? activeHotkey('J', ' 📡 FCM Proxy V2 On') : activeHotkey('J', ' 📡 FCM Proxy V2 Off', [180, 30, 30], [255, 255, 255])) +
626
+ chalk.dim(` • `) +
624
627
  hotkey('F', ' Toggle Favorite') +
625
628
  chalk.dim(` • `) +
626
629
  (tierFilterMode > 0
@@ -631,7 +634,7 @@ export function renderTable(results, pendingPings, frame, cursor = null, sortCol
631
634
  ? activeHotkey('D', ` Provider (${activeOriginLabel})`, [0, 0, 0], PROVIDER_COLOR[[null, ...Object.keys(sources)][originFilterMode]] || [255, 255, 255])
632
635
  : hotkey('D', ' Provider')) +
633
636
  chalk.dim(` • `) +
634
- (hideUnconfiguredModels ? activeHotkey('E', ' Configured Only') : hotkey('E', ' Configured Only')) +
637
+ (hideUnconfiguredModels ? activeHotkey('E', ' Configured Models Only') : hotkey('E', ' Configured Models Only')) +
635
638
  chalk.dim(` • `) +
636
639
  hotkey('X', ' Token Logs') +
637
640
  chalk.dim(` • `) +
@@ -639,10 +642,16 @@ export function renderTable(results, pendingPings, frame, cursor = null, sortCol
639
642
  chalk.dim(` • `) +
640
643
  hotkey('K', ' Help')
641
644
  )
642
- // 📖 Line 2: profiles, install flow, recommend, feature request, bug report, and extended hints.
643
- lines.push(chalk.dim(` `) + hotkey('⇧P', ' Cycle profile') + chalk.dim(` • `) + hotkey('⇧S', ' Save profile') + chalk.dim(` • `) + hotkey('Y', ' Install endpoints') + chalk.dim(` • `) + hotkey('Q', ' Smart Recommend') + chalk.dim(` • `) + hotkey('J', ' Request feature') + chalk.dim(` • `) + hotkey('I', ' Report bug'))
644
- // 📖 Proxy status line — always rendered with explicit state (starting/running/failed/stopped)
645
- lines.push(renderProxyStatusLine(proxyStartupStatus, activeProxyRef, proxyEnabled))
645
+ // 📖 Line 2: profiles, install flow, recommend, proxy shortcut, feedback, and extended hints.
646
+ lines.push(
647
+ chalk.dim(` `) +
648
+ hotkey('⇧P', ' Cycle profile') + chalk.dim(` • `) +
649
+ hotkey('⇧S', ' Save profile') + chalk.dim(` • `) +
650
+ hotkey('Y', ' Install endpoints') + chalk.dim(` • `) +
651
+ hotkey('Q', ' Smart Recommend') + chalk.dim(` • `) +
652
+ hotkey('I', ' Feedback, bugs & requests')
653
+ )
654
+ // 📖 Proxy status is now shown via the J badge in line 2 above — no need for a dedicated line
646
655
  if (versionStatus.isOutdated) {
647
656
  const outdatedBadge = chalk.bgRed.bold.yellow(' This version is outdated . ')
648
657
  const latestLabel = chalk.redBright(` local v${LOCAL_VERSION} · latest v${versionStatus.latestVersion}`)
@@ -15,8 +15,16 @@
15
15
  * For those, we prefer a transparent warning over pretending the integration is
16
16
  * fully official. The user still gets a reproducible env/config handoff.
17
17
  *
18
+ * 📖 Goose: writes custom provider JSON + secrets.yaml + updates config.yaml (GOOSE_PROVIDER/GOOSE_MODEL)
19
+ * 📖 Crush: writes crush.json with provider config + models.large/small defaults
20
+ * 📖 Pi: uses --provider/--model CLI flags for guaranteed auto-selection
21
+ * 📖 Aider: writes ~/.aider.conf.yml + passes --model flag
22
+ * 📖 Claude Code: uses ANTHROPIC_BASE_URL env + --model flag (proxy translates Anthropic ↔ OpenAI)
23
+ *
18
24
  * @functions
19
25
  * → `resolveLauncherModelId` — choose the provider-specific id or proxy slug for a launch
26
+ * → `writeGooseConfig` — install provider + set GOOSE_PROVIDER/GOOSE_MODEL in config.yaml
27
+ * → `writeCrushConfig` — write provider + models.large/small to crush.json
20
28
  * → `startExternalTool` — configure and launch the selected external tool mode
21
29
  *
22
30
  * @exports resolveLauncherModelId, startExternalTool
@@ -37,6 +45,7 @@ import { getApiKey, getProxySettings } from './config.js'
37
45
  import { ENV_VAR_NAMES, isWindows } from './provider-metadata.js'
38
46
  import { getToolMeta } from './tool-metadata.js'
39
47
  import { ensureProxyRunning, resolveProxyModelId } from './opencode.js'
48
+ import { PROVIDER_METADATA } from './provider-metadata.js'
40
49
 
41
50
  function ensureDir(filePath) {
42
51
  const dir = dirname(filePath)
@@ -173,8 +182,10 @@ function writeCrushConfig(model, apiKey, baseUrl, providerId) {
173
182
  const filePath = join(homedir(), '.config', 'crush', 'crush.json')
174
183
  const backupPath = backupIfExists(filePath)
175
184
  const config = readJson(filePath, { $schema: 'https://charm.land/crush.json' })
176
- if (!config.options || typeof config.options !== 'object') config.options = {}
177
- config.options.disable_default_providers = true
185
+ // 📖 Remove legacy disable_default_providers it can prevent Crush from auto-selecting models
186
+ if (config.options && config.options.disable_default_providers) {
187
+ delete config.options.disable_default_providers
188
+ }
178
189
  if (!config.providers || typeof config.providers !== 'object') config.providers = {}
179
190
  config.providers[providerId] = {
180
191
  name: 'Free Coding Models',
@@ -189,10 +200,11 @@ function writeCrushConfig(model, apiKey, baseUrl, providerId) {
189
200
  ],
190
201
  }
191
202
  // 📖 Crush expects structured selected models at config.models.{large,small}.
192
- // 📖 Root `crush` reads these defaults in interactive mode, unlike `crush run --model`.
203
+ // 📖 Setting both large AND small ensures Crush auto-selects the model in interactive mode.
193
204
  config.models = {
194
205
  ...(config.models && typeof config.models === 'object' ? config.models : {}),
195
206
  large: { model: model.modelId, provider: providerId },
207
+ small: { model: model.modelId, provider: providerId },
196
208
  }
197
209
  writeJson(filePath, config)
198
210
  return { filePath, backupPath }
@@ -252,6 +264,73 @@ function writePiConfig(model, apiKey, baseUrl) {
252
264
  return { filePath: modelsFilePath, backupPath: modelsBackupPath, settingsFilePath, settingsBackupPath }
253
265
  }
254
266
 
267
+ // 📖 writeGooseConfig: Install/update the provider in Goose's custom_providers/, set the
268
+ // 📖 API key in secrets.yaml, and update config.yaml with GOOSE_PROVIDER + GOOSE_MODEL
269
+ // 📖 so Goose auto-selects the model on launch.
270
+ function writeGooseConfig(model, apiKey, baseUrl, providerKey) {
271
+ const home = homedir()
272
+ const providerId = `fcm-${providerKey}`
273
+ const providerLabel = PROVIDER_METADATA[providerKey]?.label || sources[providerKey]?.name || providerKey
274
+ const secretEnvName = `FCM_${providerKey.toUpperCase().replace(/[^A-Z0-9]+/g, '_')}_API_KEY`
275
+
276
+ // 📖 Step 1: Write custom provider JSON (same format as endpoint-installer)
277
+ const providerDir = join(home, '.config', 'goose', 'custom_providers')
278
+ const providerFilePath = join(providerDir, `${providerId}.json`)
279
+ ensureDir(providerFilePath)
280
+ const providerConfig = {
281
+ name: providerId,
282
+ engine: 'openai',
283
+ display_name: `FCM ${providerLabel}`,
284
+ description: `Managed by free-coding-models for ${providerLabel}`,
285
+ api_key_env: secretEnvName,
286
+ base_url: baseUrl?.endsWith('/chat/completions') ? baseUrl : (baseUrl || ''),
287
+ models: [{ name: model.modelId, context_limit: 128000 }],
288
+ supports_streaming: true,
289
+ requires_auth: true,
290
+ }
291
+ writeFileSync(providerFilePath, JSON.stringify(providerConfig, null, 2) + '\n')
292
+
293
+ // 📖 Step 2: Write API key to secrets.yaml (simple key: value format)
294
+ const secretsPath = join(home, '.config', 'goose', 'secrets.yaml')
295
+ let secretsContent = ''
296
+ if (existsSync(secretsPath)) {
297
+ secretsContent = readFileSync(secretsPath, 'utf8')
298
+ }
299
+ // 📖 Replace existing secret or append new one
300
+ const secretLine = `${secretEnvName}: ${JSON.stringify(apiKey)}`
301
+ const secretRegex = new RegExp(`^${secretEnvName}:.*$`, 'm')
302
+ if (secretRegex.test(secretsContent)) {
303
+ secretsContent = secretsContent.replace(secretRegex, secretLine)
304
+ } else {
305
+ secretsContent = secretsContent.trimEnd() + '\n' + secretLine + '\n'
306
+ }
307
+ ensureDir(secretsPath)
308
+ writeFileSync(secretsPath, secretsContent)
309
+
310
+ // 📖 Step 3: Update config.yaml — set GOOSE_PROVIDER and GOOSE_MODEL at top level
311
+ const configPath = join(home, '.config', 'goose', 'config.yaml')
312
+ let configContent = ''
313
+ if (existsSync(configPath)) {
314
+ configContent = readFileSync(configPath, 'utf8')
315
+ }
316
+ // 📖 Replace or add GOOSE_PROVIDER line
317
+ if (/^GOOSE_PROVIDER:.*/m.test(configContent)) {
318
+ configContent = configContent.replace(/^GOOSE_PROVIDER:.*/m, `GOOSE_PROVIDER: ${providerId}`)
319
+ } else {
320
+ configContent = `GOOSE_PROVIDER: ${providerId}\n` + configContent
321
+ }
322
+ // 📖 Replace or add GOOSE_MODEL line
323
+ if (/^GOOSE_MODEL:.*/m.test(configContent)) {
324
+ configContent = configContent.replace(/^GOOSE_MODEL:.*/m, `GOOSE_MODEL: ${model.modelId}`)
325
+ } else {
326
+ // 📖 Insert after GOOSE_PROVIDER line
327
+ configContent = configContent.replace(/^(GOOSE_PROVIDER:.*)/m, `$1\nGOOSE_MODEL: ${model.modelId}`)
328
+ }
329
+ writeFileSync(configPath, configContent)
330
+
331
+ return { providerFilePath, secretsPath, configPath }
332
+ }
333
+
255
334
  function writeAmpConfig(model, baseUrl) {
256
335
  const filePath = join(homedir(), '.config', 'amp', 'settings.json')
257
336
  const backupPath = backupIfExists(filePath)
@@ -315,40 +394,80 @@ export async function startExternalTool(mode, model, config) {
315
394
  }
316
395
 
317
396
  if (mode === 'goose') {
318
- let gooseBaseUrl = baseUrl
397
+ let gooseBaseUrl = sources[model.providerKey]?.url || baseUrl || ''
319
398
  let gooseApiKey = apiKey
320
399
  let gooseModelId = resolveLauncherModelId(model, false)
400
+ let gooseProviderKey = model.providerKey
321
401
 
322
402
  if (proxySettings.enabled) {
323
403
  const started = await ensureProxyRunning(config)
324
404
  gooseApiKey = started.proxyToken
325
- gooseBaseUrl = `http://127.0.0.1:${started.port}/v1`
405
+ gooseBaseUrl = `http://127.0.0.1:${started.port}/v1/chat/completions`
326
406
  gooseModelId = resolveLauncherModelId(model, true)
327
- applyOpenAiCompatEnv(env, gooseApiKey, gooseBaseUrl, gooseModelId)
407
+ gooseProviderKey = 'proxy'
328
408
  console.log(chalk.dim(` 📖 Goose will use the local FCM proxy on :${started.port} for this launch.`))
329
409
  }
330
410
 
331
- env.OPENAI_HOST = gooseBaseUrl
332
- env.OPENAI_BASE_PATH = 'v1/chat/completions'
333
- env.OPENAI_MODEL = gooseModelId
334
- console.log(chalk.dim(` 📖 Goose uses env-based OpenAI-compatible configuration for ${proxySettings.enabled ? 'the proxy' : 'this provider'} launch.`))
411
+ // 📖 Write Goose config: custom provider JSON + secrets.yaml + config.yaml (GOOSE_PROVIDER/GOOSE_MODEL)
412
+ const gooseResult = writeGooseConfig({ ...model, modelId: gooseModelId }, gooseApiKey, gooseBaseUrl, gooseProviderKey)
413
+ console.log(chalk.dim(` 📄 Goose config updated: ${gooseResult.configPath}`))
414
+ console.log(chalk.dim(` 📄 Provider installed: ${gooseResult.providerFilePath}`))
415
+
416
+ // 📖 Also set env vars as belt-and-suspenders
417
+ env.GOOSE_PROVIDER = `fcm-${gooseProviderKey}`
418
+ env.GOOSE_MODEL = gooseModelId
419
+ applyOpenAiCompatEnv(env, gooseApiKey, gooseBaseUrl.replace(/\/chat\/completions$/, ''), gooseModelId)
335
420
  return spawnCommand('goose', [], env)
336
421
  }
337
422
 
423
+ // 📖 Claude Code, Codex, and Gemini require the FCM Proxy V2 background service.
424
+ // 📖 Without it, these tools cannot connect to the free providers (protocol mismatch / no direct support).
425
+ if (mode === 'claude-code' || mode === 'codex' || mode === 'gemini') {
426
+ if (!proxySettings.enabled) {
427
+ console.log()
428
+ console.log(chalk.red(` ✖ ${meta.label} requires FCM Proxy V2 to work with free providers.`))
429
+ console.log()
430
+ console.log(chalk.yellow(' The proxy translates between provider protocols and handles key rotation,'))
431
+ console.log(chalk.yellow(' which is required for this tool to connect.'))
432
+ console.log()
433
+ console.log(chalk.white(' To enable it:'))
434
+ console.log(chalk.dim(' 1. Press ') + chalk.bold.white('J') + chalk.dim(' to open FCM Proxy V2 settings'))
435
+ console.log(chalk.dim(' 2. Enable ') + chalk.bold.white('Proxy mode') + chalk.dim(' and install the ') + chalk.bold.white('background service'))
436
+ console.log(chalk.dim(' 3. Come back and select your model again'))
437
+ console.log()
438
+ return 1
439
+ }
440
+ }
441
+
338
442
  if (mode === 'claude-code') {
339
- console.log(chalk.yellow(' ⚠ Claude Code expects an Anthropic/Bedrock/Vertex-compatible gateway.'))
340
- console.log(chalk.dim(' This launch passes proxy env vars, but your endpoint must support Claude Code wire semantics.'))
341
- return spawnCommand('claude', ['--model', model.modelId], env)
443
+ // 📖 Claude Code needs Anthropic-compatible wire format (POST /v1/messages).
444
+ // 📖 The FCM proxy natively translates Anthropic OpenAI.
445
+ const started = await ensureProxyRunning(config)
446
+ const proxyBase = `http://127.0.0.1:${started.port}`
447
+ env.ANTHROPIC_BASE_URL = proxyBase
448
+ env.ANTHROPIC_API_KEY = started.proxyToken
449
+ const launchModelId = resolveLauncherModelId(model, true)
450
+ console.log(chalk.dim(` 📖 Claude Code routed through FCM proxy on :${started.port} (Anthropic translation enabled)`))
451
+ return spawnCommand('claude', ['--model', launchModelId], env)
342
452
  }
343
453
 
344
454
  if (mode === 'codex') {
345
- console.log(chalk.dim(' 📖 Codex CLI is launched with proxy env vars for this session.'))
346
- return spawnCommand('codex', ['--model', model.modelId], env)
455
+ const started = await ensureProxyRunning(config)
456
+ env.OPENAI_API_KEY = started.proxyToken
457
+ env.OPENAI_BASE_URL = `http://127.0.0.1:${started.port}/v1`
458
+ const launchModelId = resolveLauncherModelId(model, true)
459
+ console.log(chalk.dim(` 📖 Codex routed through FCM proxy on :${started.port}`))
460
+ return spawnCommand('codex', ['--model', launchModelId], env)
347
461
  }
348
462
 
349
463
  if (mode === 'gemini') {
350
- printConfigResult(meta.label, writeGeminiConfig(model))
351
- return spawnCommand('gemini', ['--model', model.modelId], env)
464
+ const started = await ensureProxyRunning(config)
465
+ env.OPENAI_API_KEY = started.proxyToken
466
+ env.OPENAI_BASE_URL = `http://127.0.0.1:${started.port}/v1`
467
+ const launchModelId = resolveLauncherModelId(model, true)
468
+ printConfigResult(meta.label, writeGeminiConfig({ ...model, modelId: launchModelId }))
469
+ console.log(chalk.dim(` 📖 Gemini routed through FCM proxy on :${started.port}`))
470
+ return spawnCommand('gemini', ['--model', launchModelId], env)
352
471
  }
353
472
 
354
473
  if (mode === 'qwen') {
@@ -375,7 +494,8 @@ export async function startExternalTool(mode, model, config) {
375
494
  const piResult = writePiConfig(model, apiKey, baseUrl)
376
495
  printConfigResult(meta.label, { filePath: piResult.filePath, backupPath: piResult.backupPath })
377
496
  printConfigResult(meta.label, { filePath: piResult.settingsFilePath, backupPath: piResult.settingsBackupPath })
378
- return spawnCommand('pi', [], env)
497
+ // 📖 Pi supports --provider and --model flags for guaranteed auto-selection
498
+ return spawnCommand('pi', ['--provider', 'freeCodingModels', '--model', model.modelId, '--api-key', apiKey], env)
379
499
  }
380
500
 
381
501
  console.log(chalk.red(` X Unsupported external tool mode: ${mode}`))
@@ -29,13 +29,13 @@ export const TOOL_METADATA = {
29
29
  crush: { label: 'Crush', emoji: '💘', flag: '--crush' },
30
30
  goose: { label: 'Goose', emoji: '🪿', flag: '--goose' },
31
31
  pi: { label: 'Pi', emoji: 'π', flag: '--pi' },
32
- // aider: { label: 'Aider', emoji: '🛠', flag: '--aider' },
33
- // 'claude-code': { label: 'Claude Code', emoji: '🧠', flag: '--claude-code' },
34
- // codex: { label: 'Codex CLI', emoji: '⌘', flag: '--codex' },
35
- // gemini: { label: 'Gemini CLI', emoji: '✦', flag: '--gemini' },
36
- // qwen: { label: 'Qwen Code', emoji: '🌊', flag: '--qwen' },
37
- // openhands: { label: 'OpenHands', emoji: '🤲', flag: '--openhands' },
38
- // amp: { label: 'Amp', emoji: '⚡', flag: '--amp' },
32
+ aider: { label: 'Aider', emoji: '🛠', flag: '--aider' },
33
+ 'claude-code': { label: 'Claude Code', emoji: '🧠', flag: '--claude-code' },
34
+ codex: { label: 'Codex CLI', emoji: '⌘', flag: '--codex' },
35
+ gemini: { label: 'Gemini CLI', emoji: '✦', flag: '--gemini' },
36
+ qwen: { label: 'Qwen Code', emoji: '🌊', flag: '--qwen' },
37
+ openhands: { label: 'OpenHands', emoji: '🤲', flag: '--openhands' },
38
+ amp: { label: 'Amp', emoji: '⚡', flag: '--amp' },
39
39
  }
40
40
 
41
41
  export const TOOL_MODE_ORDER = [
@@ -45,13 +45,13 @@ export const TOOL_MODE_ORDER = [
45
45
  'crush',
46
46
  'goose',
47
47
  'pi',
48
- // 'aider',
49
- // 'claude-code',
50
- // 'codex',
51
- // 'gemini',
52
- // 'qwen',
53
- // 'openhands',
54
- // 'amp',
48
+ 'aider',
49
+ 'claude-code',
50
+ 'codex',
51
+ 'gemini',
52
+ 'qwen',
53
+ 'openhands',
54
+ 'amp',
55
55
  ]
56
56
 
57
57
  export function getToolMeta(mode) {