free-coding-models 0.2.15 → 0.2.17

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 CHANGED
@@ -2,6 +2,31 @@
2
2
 
3
3
  ---
4
4
 
5
+ ## 0.2.17
6
+
7
+ ### Added
8
+ - **All coding tools re-enabled in Z-cycle**: Aider, Claude Code, Codex CLI, Gemini CLI, Qwen Code, OpenHands, and Amp are now back in the public tool mode cycle alongside OpenCode, OpenClaw, Crush, Goose, and Pi — 13 tools total.
9
+ - **All coding tools available in Install Endpoints (Y key)**: The endpoint installer now supports all 13 tools as install targets, not just the original 5. Each tool gets its proper config format (JSON, YAML, or env file).
10
+ - **Connection mode choice in Install flow**: When installing endpoints (Y key), users now choose between **Direct Provider** (pure API connection) or **FCM Proxy** (local proxy with key rotation and usage tracking) — new Step 3 in the 5-step flow.
11
+ - **Install support for new tools**: Pi (models.json + settings.json), Aider (.aider.conf.yml), Amp (settings.json), Gemini (settings.json), Qwen (modelProviders config), Claude Code/Codex/OpenHands (sourceable env files at ~/.fcm-*-env).
12
+
13
+ ### Changed
14
+ - **Install Endpoints flow is now 5 steps**: Provider → Tool → Connection Mode → Scope → Models (was 4 steps without connection mode choice).
15
+ - **Tool labels in install overlay use metadata**: Tool names and emojis in the Y overlay now come from `tool-metadata.js` instead of hard-coded ternary chains — easier to maintain and always in sync.
16
+ - **Help overlay updated**: Z-cycle hint and CLI flag examples now list all 13 tools.
17
+
18
+ ---
19
+
20
+ ## 0.2.16
21
+
22
+ ### Fixed
23
+ - **Changelog index viewport scrolling**: Cursor in changelog version list (N key) now scrolls the viewport to follow selection — previously scrolling past the last visible row would move the cursor off-screen into empty space.
24
+
25
+ ### Added
26
+ - **Changelog version summaries**: Each version in the changelog index now shows a short summary (first change title, ~15 words) after the change count, displayed in dim for readability.
27
+
28
+ ---
29
+
5
30
  ## 0.2.15
6
31
 
7
32
  ### Changed
package/README.md CHANGED
@@ -87,7 +87,7 @@ By Vanessa Depraute
87
87
  - **💻 OpenCode integration** — Auto-detects NIM setup, sets model as default, launches OpenCode
88
88
  - **🦞 OpenClaw integration** — Sets selected model as default provider in `~/.openclaw/openclaw.json`
89
89
  - **🧰 Public tool launchers** — `Enter` auto-configures and launches 10+ tools: `OpenCode CLI`, `OpenCode Desktop`, `OpenClaw`, `Crush`, `Goose`, `Aider`, `Claude Code`, `Codex`, `Gemini`, `Qwen`, `OpenHands`, `Amp`, and `Pi`. All tools auto-select the chosen model on launch.
90
- - **🔌 Install Endpoints flow** — Press `Y` to install one configured provider directly into `OpenCode CLI`, `OpenCode Desktop`, `OpenClaw`, `Crush`, `Goose`, `Aider`, or `Gemini`, either with the full provider catalog or a curated subset of models
90
+ - **🔌 Install Endpoints flow** — Press `Y` to install one configured provider into any of the 13 supported tools, with a choice between **Direct Provider** (pure API) or **FCM Proxy** (key rotation + usage tracking), then pick all models or a curated subset
91
91
  - **📝 Feature Request (J key)** — Send anonymous feedback directly to the project team
92
92
  - **🐛 Bug Report (I key)** — Send anonymous bug reports directly to the project team
93
93
  - **🎨 Clean output** — Zero scrollback pollution, interface stays open until Ctrl+C
@@ -237,8 +237,8 @@ free-coding-models --tier S --json
237
237
 
238
238
  Running `free-coding-models` with no launcher flag starts in **OpenCode CLI** mode.
239
239
 
240
- - Press **`Z`** in the TUI to cycle the public launch targets: `OpenCode CLI` → `OpenCode Desktop` → `OpenClaw` → `Crush` → `Goose`
241
- - Or start directly in the target mode with a CLI flag such as `--opencode-desktop`, `--openclaw`, `--crush`, or `--goose`
240
+ - Press **`Z`** in the TUI to cycle the public launch targets: `OpenCode CLI` → `OpenCode Desktop` → `OpenClaw` → `Crush` → `Goose` → `Pi` → `Aider` → `Claude Code` → `Codex` → `Gemini` → `Qwen` → `OpenHands` → `Amp`
241
+ - Or start directly in the target mode with a CLI flag such as `--opencode-desktop`, `--openclaw`, `--crush`, `--goose`, `--pi`, `--aider`, `--claude-code`, `--codex`, `--gemini`, `--qwen`, `--openhands`, or `--amp`
242
242
  - The active target is always visible in the header badge before you press `Enter`
243
243
 
244
244
  **How it works:**
@@ -615,7 +615,9 @@ You can use `free-coding-models` with 12+ AI coding tools. When you select a mod
615
615
  | **Amp** | `--amp` | ~/.config/amp/settings.json |
616
616
  | **Pi** | `--pi` | ~/.pi/agent/settings.json |
617
617
 
618
- Press **Z** to cycle through different tool modes in the TUI, or use flags to start in your preferred mode.
618
+ Press **Z** to cycle through all 13 tool modes in the TUI, or use flags to start in your preferred mode.
619
+
620
+ All tools are also available as install targets in the **Install Endpoints** flow (`Y` key) — install an entire provider catalog into any tool with one flow, choosing between Direct Provider or FCM Proxy connection.
619
621
 
620
622
  ---
621
623
 
@@ -924,11 +926,11 @@ This script:
924
926
  - **T** — Cycle tier filter (All → S+ → S → A+ → A → A- → B+ → B → C → All)
925
927
  - **D** — Cycle provider filter (All → NIM → Groq → ...)
926
928
  - **E** — Toggle configured-only mode (on by default, persisted across sessions and profiles)
927
- - **Z** — Cycle target tool (OpenCode CLI → OpenCode Desktop → OpenClaw → Crush → Goose)
929
+ - **Z** — Cycle target tool (OpenCode CLI → Desktop → OpenClaw → Crush → Goose → Pi → Aider → Claude Code → Codex → Gemini → Qwen → OpenHands → Amp)
928
930
  - **X** — Toggle request logs (recent proxied request/token usage logs, up to 500 entries)
929
931
  - **A (in logs)** — Toggle between showing 500 entries or ALL logs
930
932
  - **P** — Open Settings (manage API keys, toggles, updates, profiles)
931
- - **Y** — Open Install Endpoints (`provider → tool → all models` or `selected models only`, no proxy)
933
+ - **Y** — Open Install Endpoints (`provider → tool → connection mode scope models`, Direct or FCM Proxy)
932
934
  - **Shift+P** — Cycle through saved profiles (switches live TUI settings)
933
935
  - **Shift+S** — Save current TUI settings as a named profile (inline prompt)
934
936
  - **Q** — Open Smart Recommend overlay (find the best model for your task)
@@ -942,18 +944,25 @@ Pressing **K** now shows a full in-app reference: main hotkeys, settings hotkeys
942
944
 
943
945
  ### 🔌 Install Endpoints (`Y`)
944
946
 
945
- `Y` opens a dedicated install flow for configured providers. The flow is:
947
+ `Y` opens a dedicated install flow for configured providers. The 5-step flow is:
946
948
 
947
- 1. Pick one provider that already has an API key in Settings
948
- 2. Pick the target tool: `OpenCode CLI`, `OpenCode Desktop`, `OpenClaw`, `Crush`, or `Goose`
949
- 3. Choose either `Install all models` or `Install selected models only`
949
+ 1. **Provider** — Pick one provider that already has an API key in Settings
950
+ 2. **Tool** — Pick the target tool from all 13 supported tools:
951
+ - Config-based: `OpenCode CLI`, `OpenCode Desktop`, `OpenClaw`, `Crush`, `Goose`, `Pi`, `Aider`, `Amp`, `Gemini`, `Qwen`
952
+ - Env-file based: `Claude Code`, `Codex CLI`, `OpenHands` (writes `~/.fcm-{tool}-env` — source it before launching)
953
+ 3. **Connection Mode** — Choose how the tool connects to the provider:
954
+ - **⚡ Direct Provider** — pure API connection, no proxy involved
955
+ - **🔄 FCM Proxy** — route through the local FCM proxy with key rotation and usage tracking
956
+ 4. **Scope** — Choose `Install all models` or `Install selected models only`
957
+ 5. **Models** (if scope = selected) — Multi-select individual models from the provider catalog
950
958
 
951
959
  Important behavior:
952
960
 
953
- - Installs are written directly into the target tool config as FCM-managed entries, without going through `fcm-proxy`
961
+ - Installs are written into the target tool config as FCM-managed entries (namespaced under `fcm-*`)
954
962
  - `Install all models` is the recommended path because FCM can refresh that catalog automatically on later launches when the provider model list changes
955
963
  - `Install selected models only` is useful when you want a smaller curated picker inside the target tool
956
964
  - `OpenCode CLI` and `OpenCode Desktop` share the same `opencode.json`, so the managed provider appears in both
965
+ - For env-based tools (Claude Code, Codex, OpenHands), FCM writes a sourceable file at `~/.fcm-{tool}-env` — run `source ~/.fcm-claude-code-env` before launching
957
966
 
958
967
  **Keyboard shortcuts (Settings screen — `P` key):**
959
968
  - **↑↓** — Navigate providers, maintenance row, and profile rows
@@ -118,9 +118,9 @@ import { setOpenCodeModelData, startOpenCode, startOpenCodeDesktop, startProxyAn
118
118
  import { startOpenClaw } from '../src/openclaw.js'
119
119
  import { createOverlayRenderers } from '../src/overlays.js'
120
120
  import { createKeyHandler } from '../src/key-handler.js'
121
- import { getToolModeOrder } from '../src/tool-metadata.js'
121
+ import { getToolModeOrder, getToolMeta } from '../src/tool-metadata.js'
122
122
  import { startExternalTool } from '../src/tool-launchers.js'
123
- import { getConfiguredInstallableProviders, installProviderEndpoints, refreshInstalledEndpoints, getInstallTargetModes, getProviderCatalogModels } from '../src/endpoint-installer.js'
123
+ import { getConfiguredInstallableProviders, installProviderEndpoints, refreshInstalledEndpoints, getInstallTargetModes, getProviderCatalogModels, CONNECTION_MODES } from '../src/endpoint-installer.js'
124
124
  import { loadCache, saveCache, clearCache, getCacheAge } from '../src/cache.js'
125
125
  import { checkConfigSecurity } from '../src/security.js'
126
126
 
@@ -403,11 +403,12 @@ async function main() {
403
403
  helpScrollOffset: 0, // 📖 Vertical scroll offset for Help overlay viewport
404
404
  // 📖 Install Endpoints overlay state (Y key opens it)
405
405
  installEndpointsOpen: false, // 📖 Whether the install-endpoints overlay is active
406
- installEndpointsPhase: 'providers', // 📖 providers | tools | scope | models | result
406
+ installEndpointsPhase: 'providers', // 📖 providers | tools | connection | scope | models | result
407
407
  installEndpointsCursor: 0, // 📖 Selected row within the current install phase
408
408
  installEndpointsScrollOffset: 0, // 📖 Vertical scroll offset for the install overlay viewport
409
409
  installEndpointsProviderKey: null, // 📖 Selected provider for endpoint installation
410
410
  installEndpointsToolMode: null, // 📖 Selected target tool mode
411
+ installEndpointsConnectionMode: null, // 📖 'direct' | 'proxy' — how the tool connects to the provider
411
412
  installEndpointsScope: null, // 📖 all | selected
412
413
  installEndpointsSelectedModelIds: new Set(), // 📖 Multi-select buffer for the selected-models phase
413
414
  installEndpointsErrorMsg: null, // 📖 Temporary validation/error message inside the install flow
@@ -687,6 +688,8 @@ async function main() {
687
688
  getConfiguredInstallableProviders,
688
689
  getInstallTargetModes,
689
690
  getProviderCatalogModels,
691
+ CONNECTION_MODES,
692
+ getToolMeta,
690
693
  })
691
694
 
692
695
  onKeyPress = createKeyHandler({
@@ -711,6 +714,7 @@ async function main() {
711
714
  getInstallTargetModes,
712
715
  getProviderCatalogModels,
713
716
  installProviderEndpoints,
717
+ CONNECTION_MODES,
714
718
  syncFavoriteFlags,
715
719
  toggleFavoriteModel,
716
720
  sortResultsWithPinnedFavorites,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "free-coding-models",
3
- "version": "0.2.15",
3
+ "version": "0.2.17",
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",
@@ -13,11 +13,21 @@
13
13
  * - it merges into existing config files instead of replacing them
14
14
  * - it records successful installs in `~/.free-coding-models.json` so catalogs can be refreshed automatically later
15
15
  *
16
+ * 📖 Connection modes:
17
+ * - `direct` — connect the tool straight to the provider API endpoint (no proxy)
18
+ * - `proxy` — route through the local FCM proxy (key rotation + usage tracking)
19
+ *
16
20
  * 📖 Tool-specific notes:
17
21
  * - OpenCode CLI and OpenCode Desktop share the same `opencode.json`
18
22
  * - Crush gets a managed provider block in `crush.json`
19
23
  * - Goose gets a declarative custom provider JSON + a matching secret in `secrets.yaml`
20
24
  * - OpenClaw gets a managed `models.providers` entry plus matching allowlist rows
25
+ * - Pi gets models.json + settings.json under ~/.pi/agent/
26
+ * - Aider gets ~/.aider.conf.yml with OpenAI-compatible config
27
+ * - Amp gets ~/.config/amp/settings.json
28
+ * - Gemini gets ~/.gemini/settings.json
29
+ * - Qwen gets ~/.qwen/settings.json with modelProviders
30
+ * - Claude Code, Codex, OpenHands get a sourceable env file (~/.fcm-{tool}-env)
21
31
  *
22
32
  * @functions
23
33
  * → `getConfiguredInstallableProviders` — list configured providers that support direct endpoint installs
@@ -44,7 +54,14 @@ import { ENV_VAR_NAMES, PROVIDER_METADATA } from './provider-metadata.js'
44
54
  import { getToolMeta } from './tool-metadata.js'
45
55
 
46
56
  const DIRECT_INSTALL_UNSUPPORTED_PROVIDERS = new Set(['replicate', 'zai'])
47
- const INSTALL_TARGET_MODES = ['opencode', 'opencode-desktop', 'openclaw', 'crush', 'goose']
57
+ // 📖 All supported install targets matches TOOL_MODE_ORDER in tool-metadata.js
58
+ const INSTALL_TARGET_MODES = ['opencode', 'opencode-desktop', 'openclaw', 'crush', 'goose', 'pi', 'aider', 'claude-code', 'codex', 'gemini', 'qwen', 'openhands', 'amp']
59
+
60
+ // 📖 Connection modes: direct (pure provider) vs FCM proxy (rotates keys)
61
+ export const CONNECTION_MODES = [
62
+ { key: 'direct', label: 'Direct Provider', hint: 'Connect the tool straight to the provider API — no proxy involved.' },
63
+ { key: 'proxy', label: 'FCM Proxy', hint: 'Route through the local FCM proxy with key rotation and usage tracking.' },
64
+ ]
48
65
 
49
66
  function getDefaultPaths() {
50
67
  const home = homedir()
@@ -54,6 +71,12 @@ function getDefaultPaths() {
54
71
  crushConfigPath: join(home, '.config', 'crush', 'crush.json'),
55
72
  gooseProvidersDir: join(home, '.config', 'goose', 'custom_providers'),
56
73
  gooseSecretsPath: join(home, '.config', 'goose', 'secrets.yaml'),
74
+ piModelsPath: join(home, '.pi', 'agent', 'models.json'),
75
+ piSettingsPath: join(home, '.pi', 'agent', 'settings.json'),
76
+ aiderConfigPath: join(home, '.aider.conf.yml'),
77
+ ampConfigPath: join(home, '.config', 'amp', 'settings.json'),
78
+ geminiConfigPath: join(home, '.gemini', 'settings.json'),
79
+ qwenConfigPath: join(home, '.qwen', 'settings.json'),
57
80
  }
58
81
  }
59
82
 
@@ -374,8 +397,131 @@ function installIntoOpenClaw(providerKey, models, apiKey, paths) {
374
397
  return { path: filePath, backupPath, providerId, modelCount: models.length }
375
398
  }
376
399
 
400
+ // 📖 installIntoPi writes models.json + settings.json under ~/.pi/agent/
401
+ function installIntoPi(providerKey, models, apiKey, paths) {
402
+ const providerId = getManagedProviderId(providerKey)
403
+ const baseUrl = resolveProviderBaseUrl(providerKey)
404
+
405
+ // 📖 Write models.json with provider config
406
+ const modelsConfig = readJson(paths.piModelsPath, { providers: {} })
407
+ if (!modelsConfig.providers || typeof modelsConfig.providers !== 'object') modelsConfig.providers = {}
408
+ modelsConfig.providers[providerId] = {
409
+ baseUrl,
410
+ api: 'openai-completions',
411
+ apiKey,
412
+ models: models.map((model) => ({ id: model.modelId, name: model.label })),
413
+ }
414
+ const modelsBackupPath = writeJson(paths.piModelsPath, modelsConfig)
415
+
416
+ // 📖 Write settings.json to set default provider
417
+ const settingsConfig = readJson(paths.piSettingsPath, {})
418
+ settingsConfig.defaultProvider = providerId
419
+ settingsConfig.defaultModel = models[0]?.modelId ?? ''
420
+ writeJson(paths.piSettingsPath, settingsConfig, { backup: true })
421
+
422
+ return { path: paths.piModelsPath, backupPath: modelsBackupPath, providerId, modelCount: models.length }
423
+ }
424
+
425
+ // 📖 installIntoAider writes ~/.aider.conf.yml with OpenAI-compatible config
426
+ function installIntoAider(providerKey, models, apiKey, paths) {
427
+ const providerId = getManagedProviderId(providerKey)
428
+ const baseUrl = resolveProviderBaseUrl(providerKey)
429
+ const backupPath = backupIfExists(paths.aiderConfigPath)
430
+ // 📖 Aider YAML config — one model at a time, uses first selected model
431
+ const primaryModel = models[0]
432
+ const lines = [
433
+ '# 📖 Managed by free-coding-models',
434
+ `openai-api-base: ${baseUrl}`,
435
+ `openai-api-key: ${apiKey}`,
436
+ `model: openai/${primaryModel.modelId}`,
437
+ '',
438
+ ]
439
+ ensureDirFor(paths.aiderConfigPath)
440
+ writeFileSync(paths.aiderConfigPath, lines.join('\n'))
441
+ return { path: paths.aiderConfigPath, backupPath, providerId, modelCount: models.length }
442
+ }
443
+
444
+ // 📖 installIntoAmp writes ~/.config/amp/settings.json with model+URL
445
+ function installIntoAmp(providerKey, models, apiKey, paths) {
446
+ const providerId = getManagedProviderId(providerKey)
447
+ const baseUrl = resolveProviderBaseUrl(providerKey)
448
+ const config = readJson(paths.ampConfigPath, {})
449
+ config['amp.url'] = baseUrl
450
+ config['amp.model'] = models[0]?.modelId ?? ''
451
+ const backupPath = writeJson(paths.ampConfigPath, config)
452
+ return { path: paths.ampConfigPath, backupPath, providerId, modelCount: models.length }
453
+ }
454
+
455
+ // 📖 installIntoGemini writes ~/.gemini/settings.json with model ID
456
+ function installIntoGemini(providerKey, models, apiKey, paths) {
457
+ const providerId = getManagedProviderId(providerKey)
458
+ const config = readJson(paths.geminiConfigPath, {})
459
+ config.model = models[0]?.modelId ?? ''
460
+ const backupPath = writeJson(paths.geminiConfigPath, config)
461
+ return { path: paths.geminiConfigPath, backupPath, providerId, modelCount: models.length }
462
+ }
463
+
464
+ // 📖 installIntoQwen writes ~/.qwen/settings.json with modelProviders config
465
+ function installIntoQwen(providerKey, models, apiKey, paths) {
466
+ const providerId = getManagedProviderId(providerKey)
467
+ const baseUrl = resolveProviderBaseUrl(providerKey)
468
+ const config = readJson(paths.qwenConfigPath, {})
469
+ if (!config.modelProviders || typeof config.modelProviders !== 'object') config.modelProviders = {}
470
+ if (!Array.isArray(config.modelProviders.openai)) config.modelProviders.openai = []
471
+
472
+ // 📖 Remove existing FCM-managed entries, then prepend all selected models
473
+ const filtered = config.modelProviders.openai.filter(
474
+ (entry) => !models.some((m) => m.modelId === entry?.id)
475
+ )
476
+ const newEntries = models.map((model) => ({
477
+ id: model.modelId,
478
+ name: model.label,
479
+ envKey: ENV_VAR_NAMES[providerKey] || 'OPENAI_API_KEY',
480
+ baseUrl,
481
+ }))
482
+ config.modelProviders.openai = [...newEntries, ...filtered]
483
+ config.model = models[0]?.modelId ?? ''
484
+ const backupPath = writeJson(paths.qwenConfigPath, config)
485
+ return { path: paths.qwenConfigPath, backupPath, providerId, modelCount: models.length }
486
+ }
487
+
488
+ // 📖 installIntoEnvBasedTool handles tools that rely on env vars only (claude-code, codex, openhands).
489
+ // 📖 We write a small .env-style helper file so users can source it before launching.
490
+ function installIntoEnvBasedTool(providerKey, models, apiKey, toolMode, paths) {
491
+ const providerId = getManagedProviderId(providerKey)
492
+ const baseUrl = resolveProviderBaseUrl(providerKey)
493
+ const home = homedir()
494
+ const envFileName = `.fcm-${toolMode}-env`
495
+ const envFilePath = join(home, envFileName)
496
+
497
+ const primaryModel = models[0]
498
+ const envLines = [
499
+ '# 📖 Managed by free-coding-models — source this file before launching the tool',
500
+ `# 📖 Provider: ${getProviderLabel(providerKey)} (${models.length} models)`,
501
+ `export OPENAI_API_KEY="${apiKey}"`,
502
+ `export OPENAI_BASE_URL="${baseUrl}"`,
503
+ `export OPENAI_MODEL="${primaryModel.modelId}"`,
504
+ `export LLM_API_KEY="${apiKey}"`,
505
+ `export LLM_BASE_URL="${baseUrl}"`,
506
+ `export LLM_MODEL="openai/${primaryModel.modelId}"`,
507
+ ]
508
+
509
+ // 📖 Tool-specific extra env vars
510
+ if (toolMode === 'claude-code') {
511
+ envLines.push(`export ANTHROPIC_AUTH_TOKEN="${apiKey}"`)
512
+ envLines.push(`export ANTHROPIC_BASE_URL="${baseUrl}"`)
513
+ envLines.push(`export ANTHROPIC_MODEL="${primaryModel.modelId}"`)
514
+ }
515
+
516
+ ensureDirFor(envFilePath)
517
+ const backupPath = backupIfExists(envFilePath)
518
+ writeFileSync(envFilePath, envLines.join('\n') + '\n')
519
+ return { path: envFilePath, backupPath, providerId, modelCount: models.length }
520
+ }
521
+
377
522
  export function installProviderEndpoints(config, providerKey, toolMode, options = {}) {
378
523
  const canonicalToolMode = canonicalizeToolMode(toolMode)
524
+ const connectionMode = options.connectionMode || 'direct'
379
525
  const support = getDirectInstallSupport(providerKey)
380
526
  if (!support.supported) {
381
527
  throw new Error(support.reason || 'Direct install is not supported for this provider')
@@ -389,6 +535,7 @@ export function installProviderEndpoints(config, providerKey, toolMode, options
389
535
  }
390
536
 
391
537
  const paths = { ...getDefaultPaths(), ...(options.paths || {}) }
538
+ // 📖 Dispatch to the right installer based on canonical tool mode
392
539
  let installResult
393
540
  if (canonicalToolMode === 'opencode') {
394
541
  installResult = installIntoOpenCode(providerKey, models, apiKey, paths)
@@ -398,6 +545,18 @@ export function installProviderEndpoints(config, providerKey, toolMode, options
398
545
  installResult = installIntoCrush(providerKey, models, apiKey, paths)
399
546
  } else if (canonicalToolMode === 'goose') {
400
547
  installResult = installIntoGoose(providerKey, models, apiKey, paths)
548
+ } else if (canonicalToolMode === 'pi') {
549
+ installResult = installIntoPi(providerKey, models, apiKey, paths)
550
+ } else if (canonicalToolMode === 'aider') {
551
+ installResult = installIntoAider(providerKey, models, apiKey, paths)
552
+ } else if (canonicalToolMode === 'amp') {
553
+ installResult = installIntoAmp(providerKey, models, apiKey, paths)
554
+ } else if (canonicalToolMode === 'gemini') {
555
+ installResult = installIntoGemini(providerKey, models, apiKey, paths)
556
+ } else if (canonicalToolMode === 'qwen') {
557
+ installResult = installIntoQwen(providerKey, models, apiKey, paths)
558
+ } else if (canonicalToolMode === 'claude-code' || canonicalToolMode === 'codex' || canonicalToolMode === 'openhands') {
559
+ installResult = installIntoEnvBasedTool(providerKey, models, apiKey, canonicalToolMode, paths)
401
560
  } else {
402
561
  throw new Error(`Unsupported install target: ${toolMode}`)
403
562
  }
@@ -414,6 +573,7 @@ export function installProviderEndpoints(config, providerKey, toolMode, options
414
573
  providerKey,
415
574
  providerLabel: getProviderLabel(providerKey),
416
575
  scope,
576
+ connectionMode,
417
577
  autoRefreshEnabled: true,
418
578
  models,
419
579
  }
@@ -169,6 +169,7 @@ export function createKeyHandler(ctx) {
169
169
  getInstallTargetModes,
170
170
  getProviderCatalogModels,
171
171
  installProviderEndpoints,
172
+ CONNECTION_MODES,
172
173
  syncFavoriteFlags,
173
174
  toggleFavoriteModel,
174
175
  sortResultsWithPinnedFavorites,
@@ -334,6 +335,7 @@ export function createKeyHandler(ctx) {
334
335
  state.installEndpointsScrollOffset = 0
335
336
  state.installEndpointsProviderKey = null
336
337
  state.installEndpointsToolMode = null
338
+ state.installEndpointsConnectionMode = null
337
339
  state.installEndpointsScope = null
338
340
  state.installEndpointsSelectedModelIds = new Set()
339
341
  state.installEndpointsErrorMsg = null
@@ -349,6 +351,7 @@ export function createKeyHandler(ctx) {
349
351
  {
350
352
  scope: state.installEndpointsScope,
351
353
  modelIds: selectedModelIds,
354
+ connectionMode: state.installEndpointsConnectionMode || 'direct',
352
355
  }
353
356
  )
354
357
 
@@ -414,12 +417,13 @@ export function createKeyHandler(ctx) {
414
417
  return
415
418
  }
416
419
 
417
- // 📖 Install Endpoints overlay: provider → tool → scope → optional model subset.
420
+ // 📖 Install Endpoints overlay: provider → tool → connection → scope → optional model subset.
418
421
  if (state.installEndpointsOpen) {
419
422
  if (key.ctrl && key.name === 'c') { exit(0); return }
420
423
 
421
424
  const providerChoices = getConfiguredInstallableProviders(state.config)
422
425
  const toolChoices = getInstallTargetModes()
426
+ const connectionChoices = CONNECTION_MODES || []
423
427
  const modelChoices = state.installEndpointsProviderKey
424
428
  ? getProviderCatalogModels(state.installEndpointsProviderKey)
425
429
  : []
@@ -428,6 +432,7 @@ export function createKeyHandler(ctx) {
428
432
  const maxIndexByPhase = () => {
429
433
  if (state.installEndpointsPhase === 'providers') return Math.max(0, providerChoices.length - 1)
430
434
  if (state.installEndpointsPhase === 'tools') return Math.max(0, toolChoices.length - 1)
435
+ if (state.installEndpointsPhase === 'connection') return Math.max(0, connectionChoices.length - 1)
431
436
  if (state.installEndpointsPhase === 'scope') return 1
432
437
  if (state.installEndpointsPhase === 'models') return Math.max(0, modelChoices.length - 1)
433
438
  return 0
@@ -470,12 +475,18 @@ export function createKeyHandler(ctx) {
470
475
  state.installEndpointsScrollOffset = 0
471
476
  return
472
477
  }
473
- if (state.installEndpointsPhase === 'scope') {
478
+ if (state.installEndpointsPhase === 'connection') {
474
479
  state.installEndpointsPhase = 'tools'
475
480
  state.installEndpointsCursor = 0
476
481
  state.installEndpointsScrollOffset = 0
477
482
  return
478
483
  }
484
+ if (state.installEndpointsPhase === 'scope') {
485
+ state.installEndpointsPhase = 'connection'
486
+ state.installEndpointsCursor = state.installEndpointsConnectionMode === 'proxy' ? 1 : 0
487
+ state.installEndpointsScrollOffset = 0
488
+ return
489
+ }
479
490
  if (state.installEndpointsPhase === 'models') {
480
491
  state.installEndpointsPhase = 'scope'
481
492
  state.installEndpointsCursor = state.installEndpointsScope === 'selected' ? 1 : 0
@@ -508,6 +519,20 @@ export function createKeyHandler(ctx) {
508
519
  const selectedToolMode = toolChoices[state.installEndpointsCursor]
509
520
  if (!selectedToolMode) return
510
521
  state.installEndpointsToolMode = selectedToolMode
522
+ state.installEndpointsPhase = 'connection'
523
+ state.installEndpointsCursor = 0
524
+ state.installEndpointsScrollOffset = 0
525
+ state.installEndpointsErrorMsg = null
526
+ }
527
+ return
528
+ }
529
+
530
+ // 📖 Connection mode phase: Direct Provider vs FCM Proxy
531
+ if (state.installEndpointsPhase === 'connection') {
532
+ if (key.name === 'return') {
533
+ const selected = connectionChoices[state.installEndpointsCursor]
534
+ if (!selected) return
535
+ state.installEndpointsConnectionMode = selected.key
511
536
  state.installEndpointsPhase = 'scope'
512
537
  state.installEndpointsCursor = 0
513
538
  state.installEndpointsScrollOffset = 0
@@ -1395,6 +1420,7 @@ export function createKeyHandler(ctx) {
1395
1420
  state.installEndpointsScrollOffset = 0
1396
1421
  state.installEndpointsProviderKey = null
1397
1422
  state.installEndpointsToolMode = null
1423
+ state.installEndpointsConnectionMode = null
1398
1424
  state.installEndpointsScope = null
1399
1425
  state.installEndpointsSelectedModelIds = new Set()
1400
1426
  state.installEndpointsErrorMsg = null
package/src/overlays.js CHANGED
@@ -55,6 +55,8 @@ export function createOverlayRenderers(state, deps) {
55
55
  getConfiguredInstallableProviders,
56
56
  getInstallTargetModes,
57
57
  getProviderCatalogModels,
58
+ CONNECTION_MODES,
59
+ getToolMeta,
58
60
  } = deps
59
61
 
60
62
  // 📖 Wrap plain diagnostic text so long Settings messages stay readable inside
@@ -377,7 +379,7 @@ export function createOverlayRenderers(state, deps) {
377
379
  }
378
380
 
379
381
  // ─── Install Endpoints overlay renderer ───────────────────────────────────
380
- // 📖 renderInstallEndpoints drives the provider → tool → scope → model flow
382
+ // 📖 renderInstallEndpoints drives the provider → tool → connection → scope → model flow
381
383
  // 📖 behind the `Y` hotkey. It deliberately reuses the same overlay viewport
382
384
  // 📖 helpers as Settings so long provider/model lists stay navigable.
383
385
  function renderInstallEndpoints() {
@@ -386,6 +388,8 @@ export function createOverlayRenderers(state, deps) {
386
388
  const cursorLineByRow = {}
387
389
  const providerChoices = getConfiguredInstallableProviders(state.config)
388
390
  const toolChoices = getInstallTargetModes()
391
+ const connectionChoices = CONNECTION_MODES || []
392
+ const totalSteps = 5
389
393
  const scopeChoices = [
390
394
  {
391
395
  key: 'all',
@@ -401,18 +405,22 @@ export function createOverlayRenderers(state, deps) {
401
405
  const selectedProviderLabel = state.installEndpointsProviderKey
402
406
  ? (sources[state.installEndpointsProviderKey]?.name || state.installEndpointsProviderKey)
403
407
  : '—'
408
+
409
+ // 📖 Resolve tool label from metadata instead of hard-coded switch
404
410
  const selectedToolLabel = state.installEndpointsToolMode
405
- ? (state.installEndpointsToolMode === 'opencode-desktop'
406
- ? 'OpenCode Desktop (shared opencode.json)'
407
- : (state.installEndpointsToolMode === 'opencode'
408
- ? 'OpenCode CLI (shared opencode.json)'
409
- : state.installEndpointsToolMode === 'openclaw'
410
- ? 'OpenClaw'
411
- : state.installEndpointsToolMode === 'crush'
412
- ? 'Crush'
413
- : 'Goose'))
411
+ ? (() => {
412
+ const meta = getToolMeta(state.installEndpointsToolMode)
413
+ const suffix = state.installEndpointsToolMode.startsWith('opencode') ? ' (shared opencode.json)' : ''
414
+ return `${meta.label}${suffix}`
415
+ })()
414
416
  : '—'
415
417
 
418
+ const selectedConnectionLabel = state.installEndpointsConnectionMode === 'proxy'
419
+ ? 'FCM Proxy'
420
+ : state.installEndpointsConnectionMode === 'direct'
421
+ ? 'Direct Provider'
422
+ : '—'
423
+
416
424
  lines.push('')
417
425
  // 📖 Branding header
418
426
  lines.push(` ${chalk.cyanBright('🚀')} ${chalk.bold.cyanBright('free-coding-models')} ${chalk.dim(`v${LOCAL_VERSION}`)}`)
@@ -425,7 +433,7 @@ export function createOverlayRenderers(state, deps) {
425
433
  lines.push('')
426
434
 
427
435
  if (state.installEndpointsPhase === 'providers') {
428
- lines.push(` ${chalk.bold('Step 1/4')} ${chalk.cyan('Choose a configured provider')}`)
436
+ lines.push(` ${chalk.bold(`Step 1/${totalSteps}`)} ${chalk.cyan('Choose a configured provider')}`)
429
437
  lines.push('')
430
438
 
431
439
  if (providerChoices.length === 0) {
@@ -435,7 +443,7 @@ export function createOverlayRenderers(state, deps) {
435
443
  providerChoices.forEach((provider, idx) => {
436
444
  const isCursor = idx === state.installEndpointsCursor
437
445
  const bullet = isCursor ? chalk.bold.cyan(' ❯ ') : chalk.dim(' ')
438
- const row = `${bullet}${chalk.bold(provider.label.padEnd(24))} ${chalk.dim(`${provider.modelCount} models`)}`
446
+ const row = `${bullet}${chalk.bold(provider.label.padEnd(24))} ${chalk.dim(`${provider.modelCount} models`)}`
439
447
  cursorLineByRow[idx] = lines.length
440
448
  lines.push(isCursor ? chalk.bgRgb(24, 44, 62)(row) : row)
441
449
  })
@@ -444,37 +452,51 @@ export function createOverlayRenderers(state, deps) {
444
452
  lines.push('')
445
453
  lines.push(chalk.dim(' ↑↓ Navigate • Enter Choose provider • Esc Close'))
446
454
  } else if (state.installEndpointsPhase === 'tools') {
447
- lines.push(` ${chalk.bold('Step 2/4')} ${chalk.cyan('Choose the target tool')}`)
455
+ lines.push(` ${chalk.bold(`Step 2/${totalSteps}`)} ${chalk.cyan('Choose the target tool')}`)
448
456
  lines.push(chalk.dim(` Provider: ${selectedProviderLabel}`))
449
457
  lines.push('')
450
458
 
459
+ // 📖 Use getToolMeta for labels instead of hard-coded ternary chains
451
460
  toolChoices.forEach((toolMode, idx) => {
452
461
  const isCursor = idx === state.installEndpointsCursor
453
- const label = toolMode === 'opencode-desktop'
454
- ? 'OpenCode Desktop'
455
- : toolMode === 'opencode'
456
- ? 'OpenCode CLI'
457
- : toolMode === 'openclaw'
458
- ? 'OpenClaw'
459
- : toolMode === 'crush'
460
- ? 'Crush'
461
- : 'Goose'
462
+ const meta = getToolMeta(toolMode)
463
+ const label = `${meta.emoji} ${meta.label}`
462
464
  const note = toolMode.startsWith('opencode')
463
465
  ? chalk.dim('shared config file')
464
- : chalk.dim('managed provider install')
466
+ : ['claude-code', 'codex', 'openhands'].includes(toolMode)
467
+ ? chalk.dim('env file (~/.fcm-*-env)')
468
+ : chalk.dim('managed config install')
465
469
  const bullet = isCursor ? chalk.bold.cyan(' ❯ ') : chalk.dim(' ')
466
- const row = `${bullet}${chalk.bold(label.padEnd(22))} ${note}`
470
+ const row = `${bullet}${chalk.bold(label.padEnd(26))} ${note}`
467
471
  cursorLineByRow[idx] = lines.length
468
472
  lines.push(isCursor ? chalk.bgRgb(24, 44, 62)(row) : row)
469
473
  })
470
474
 
471
475
  lines.push('')
472
476
  lines.push(chalk.dim(' ↑↓ Navigate • Enter Choose tool • Esc Back'))
473
- } else if (state.installEndpointsPhase === 'scope') {
474
- lines.push(` ${chalk.bold('Step 3/4')} ${chalk.cyan('Choose the install scope')}`)
477
+ } else if (state.installEndpointsPhase === 'connection') {
478
+ // 📖 Step 3: Choose connection mode — Direct Provider vs FCM Proxy
479
+ lines.push(` ${chalk.bold(`Step 3/${totalSteps}`)} ${chalk.cyan('Choose connection mode')}`)
475
480
  lines.push(chalk.dim(` Provider: ${selectedProviderLabel} • Tool: ${selectedToolLabel}`))
476
481
  lines.push('')
477
482
 
483
+ connectionChoices.forEach((mode, idx) => {
484
+ const isCursor = idx === state.installEndpointsCursor
485
+ const bullet = isCursor ? chalk.bold.cyan(' ❯ ') : chalk.dim(' ')
486
+ const icon = mode.key === 'proxy' ? '🔄' : '⚡'
487
+ const row = `${bullet}${icon} ${chalk.bold(mode.label)}`
488
+ cursorLineByRow[idx] = lines.length
489
+ lines.push(isCursor ? chalk.bgRgb(24, 44, 62)(row) : row)
490
+ lines.push(chalk.dim(` ${mode.hint}`))
491
+ lines.push('')
492
+ })
493
+
494
+ lines.push(chalk.dim(' Enter Continue • Esc Back'))
495
+ } else if (state.installEndpointsPhase === 'scope') {
496
+ lines.push(` ${chalk.bold(`Step 4/${totalSteps}`)} ${chalk.cyan('Choose the install scope')}`)
497
+ lines.push(chalk.dim(` Provider: ${selectedProviderLabel} • Tool: ${selectedToolLabel} • ${selectedConnectionLabel}`))
498
+ lines.push('')
499
+
478
500
  scopeChoices.forEach((scope, idx) => {
479
501
  const isCursor = idx === state.installEndpointsCursor
480
502
  const bullet = isCursor ? chalk.bold.cyan(' ❯ ') : chalk.dim(' ')
@@ -490,8 +512,8 @@ export function createOverlayRenderers(state, deps) {
490
512
  const models = getProviderCatalogModels(state.installEndpointsProviderKey)
491
513
  const selectedCount = state.installEndpointsSelectedModelIds.size
492
514
 
493
- lines.push(` ${chalk.bold('Step 4/4')} ${chalk.cyan('Choose which models to install')}`)
494
- lines.push(chalk.dim(` Provider: ${selectedProviderLabel} • Tool: ${selectedToolLabel}`))
515
+ lines.push(` ${chalk.bold(`Step 5/${totalSteps}`)} ${chalk.cyan('Choose which models to install')}`)
516
+ lines.push(chalk.dim(` Provider: ${selectedProviderLabel} • Tool: ${selectedToolLabel} • ${selectedConnectionLabel}`))
495
517
  lines.push(chalk.dim(` Selected: ${selectedCount}/${models.length}`))
496
518
  lines.push('')
497
519
 
@@ -608,9 +630,9 @@ export function createOverlayRenderers(state, deps) {
608
630
  lines.push(` ${chalk.yellow('W')} Toggle ping mode ${chalk.dim('(speed 2s → normal 10s → slow 30s → forced 4s)')}`)
609
631
  lines.push(` ${chalk.yellow('E')} Toggle configured models only ${chalk.dim('(enabled by default, persisted globally + in profiles)')}`)
610
632
  lines.push(` ${chalk.yellow('X')} Toggle token log page ${chalk.dim('(shows recent request usage from request-log.jsonl)')}`)
611
- lines.push(` ${chalk.yellow('Z')} Cycle tool mode ${chalk.dim('(OpenCode → Desktop → OpenClaw → Crush → Goose)')}`)
633
+ lines.push(` ${chalk.yellow('Z')} Cycle tool mode ${chalk.dim('(OpenCode → Desktop → OpenClaw → Crush → Goose → Pi → Aider → Claude Code → Codex → Gemini → Qwen → OpenHands → Amp)')}`)
612
634
  lines.push(` ${chalk.yellow('F')} Toggle favorite on selected row ${chalk.dim('(⭐ pinned at top, persisted)')}`)
613
- lines.push(` ${chalk.yellow('Y')} Install endpoints ${chalk.dim('(provider catalog → OpenCode/OpenClaw/Crush/Goose, no proxy)')}`)
635
+ lines.push(` ${chalk.yellow('Y')} Install endpoints ${chalk.dim('(provider catalog → all tools, Direct or FCM Proxy)')}`)
614
636
  lines.push(` ${chalk.yellow('Q')} Smart Recommend ${chalk.dim('(🎯 find the best model for your task — questionnaire + live analysis)')}`)
615
637
  lines.push(` ${chalk.rgb(57, 255, 20).bold('J')} Request Feature ${chalk.dim('(📝 send anonymous feedback to the project team)')}`)
616
638
  lines.push(` ${chalk.rgb(255, 87, 51).bold('I')} Report Bug ${chalk.dim('(🐛 send anonymous bug report to the project team)')}`)
@@ -641,15 +663,14 @@ export function createOverlayRenderers(state, deps) {
641
663
  lines.push(` ${chalk.cyan('free-coding-models --openclaw')} ${chalk.dim('OpenClaw mode')}`)
642
664
  lines.push(` ${chalk.cyan('free-coding-models --crush')} ${chalk.dim('Crush mode')}`)
643
665
  lines.push(` ${chalk.cyan('free-coding-models --goose')} ${chalk.dim('Goose mode')}`)
644
- // 📖 Temporarily disabled launchers kept out of the public help until their flows are hardened.
645
- // lines.push(` ${chalk.cyan('free-coding-models --aider')} ${chalk.dim('Aider mode')}`)
646
- // lines.push(` ${chalk.cyan('free-coding-models --claude-code')} ${chalk.dim('Claude Code proxy mode')}`)
647
- // lines.push(` ${chalk.cyan('free-coding-models --codex')} ${chalk.dim('Codex CLI proxy mode')}`)
648
- // lines.push(` ${chalk.cyan('free-coding-models --gemini')} ${chalk.dim('Gemini CLI proxy mode')}`)
649
- // lines.push(` ${chalk.cyan('free-coding-models --qwen')} ${chalk.dim('Qwen Code mode')}`)
650
- // lines.push(` ${chalk.cyan('free-coding-models --openhands')} ${chalk.dim('OpenHands mode')}`)
651
- // lines.push(` ${chalk.cyan('free-coding-models --amp')} ${chalk.dim('Amp mode')}`)
652
- // lines.push(` ${chalk.cyan('free-coding-models --pi')} ${chalk.dim('Pi mode')}`)
666
+ lines.push(` ${chalk.cyan('free-coding-models --pi')} ${chalk.dim('Pi mode')}`)
667
+ lines.push(` ${chalk.cyan('free-coding-models --aider')} ${chalk.dim('Aider mode')}`)
668
+ lines.push(` ${chalk.cyan('free-coding-models --claude-code')} ${chalk.dim('Claude Code mode')}`)
669
+ lines.push(` ${chalk.cyan('free-coding-models --codex')} ${chalk.dim('Codex CLI mode')}`)
670
+ lines.push(` ${chalk.cyan('free-coding-models --gemini')} ${chalk.dim('Gemini CLI mode')}`)
671
+ lines.push(` ${chalk.cyan('free-coding-models --qwen')} ${chalk.dim('Qwen Code mode')}`)
672
+ lines.push(` ${chalk.cyan('free-coding-models --openhands')} ${chalk.dim('OpenHands mode')}`)
673
+ lines.push(` ${chalk.cyan('free-coding-models --amp')} ${chalk.dim('Amp mode')}`)
653
674
  lines.push(` ${chalk.cyan('free-coding-models --best')} ${chalk.dim('Only top tiers (A+, S, S+)')}`)
654
675
  lines.push(` ${chalk.cyan('free-coding-models --fiable')} ${chalk.dim('10s reliability analysis')}`)
655
676
  lines.push(` ${chalk.cyan('free-coding-models --tier S|A|B|C')} ${chalk.dim('Filter by tier letter')}`)
@@ -1290,12 +1311,32 @@ export function createOverlayRenderers(state, deps) {
1290
1311
  if (changes[key]) itemCount += changes[key].length
1291
1312
  }
1292
1313
 
1293
- // 📖 Format version line with selection highlight
1294
- const versionStr = ` v${version.padEnd(8)} — ${itemCount} ${itemCount === 1 ? 'change' : 'changes'}`
1314
+ // 📖 Build a short summary from the first few items (max ~15 words, stripped of markdown)
1315
+ const allItems = []
1316
+ for (const k of ['added', 'fixed', 'changed', 'updated']) {
1317
+ if (changes[k]) for (const item of changes[k]) allItems.push(item)
1318
+ }
1319
+ let summary = ''
1320
+ if (allItems.length > 0) {
1321
+ // 📖 Extract the bold title part if present, otherwise use the raw text
1322
+ const firstItem = allItems[0]
1323
+ const boldMatch = firstItem.match(/\*\*([^*]+)\*\*/)
1324
+ const rawText = boldMatch ? boldMatch[1] : firstItem.replace(/\*\*([^*]+)\*\*/g, '$1').replace(/`([^`]+)`/g, '$1')
1325
+ // 📖 Truncate to ~15 words max
1326
+ const words = rawText.split(/\s+/).slice(0, 15)
1327
+ summary = words.join(' ')
1328
+ if (rawText.split(/\s+/).length > 15) summary += '…'
1329
+ }
1330
+
1331
+ // 📖 Format version line with selection highlight + dim summary
1332
+ const countStr = `${itemCount} ${itemCount === 1 ? 'change' : 'changes'}`
1333
+ const prefix = ` v${version.padEnd(8)} — ${countStr}`
1295
1334
  if (isSelected) {
1296
- lines.push(chalk.inverse(versionStr))
1335
+ const full = summary ? `${prefix} · ${summary}` : prefix
1336
+ lines.push(chalk.inverse(full))
1297
1337
  } else {
1298
- lines.push(versionStr)
1338
+ const dimSummary = summary ? chalk.dim(` · ${summary}`) : ''
1339
+ lines.push(`${prefix}${dimSummary}`)
1299
1340
  }
1300
1341
  }
1301
1342
 
@@ -1332,6 +1373,17 @@ export function createOverlayRenderers(state, deps) {
1332
1373
  }
1333
1374
  }
1334
1375
 
1376
+ // 📖 Keep selected changelog row visible by scrolling the overlay viewport (index phase)
1377
+ if (state.changelogPhase === 'index') {
1378
+ const targetLine = 4 + state.changelogCursor // 📖 3 header lines + 1 blank = versions start at line 4
1379
+ state.changelogScrollOffset = keepOverlayTargetVisible(
1380
+ state.changelogScrollOffset,
1381
+ targetLine,
1382
+ lines.length,
1383
+ state.terminalRows
1384
+ )
1385
+ }
1386
+
1335
1387
  // 📖 Use scrolling with overlay handler
1336
1388
  const CHANGELOG_OVERLAY_BG = chalk.bgRgb(10, 40, 80) // Dark blue background
1337
1389
  const { visible, offset } = sliceOverlayLines(lines, state.changelogScrollOffset, state.terminalRows)
@@ -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) {