free-coding-models 0.3.51 → 0.3.52
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/README.md +4 -1
- package/package.json +1 -1
- package/src/app.js +24 -8
- package/src/command-palette.js +1 -0
- package/src/constants.js +10 -2
- package/src/key-handler.js +23 -1
- package/src/opencode.js +74 -0
- package/src/render-table.js +209 -127
- package/src/shell-env.js +3 -0
- package/src/tool-metadata.js +4 -2
- package/src/updater.js +2 -0
- package/src/utils.js +4 -2
- package/web/dist/assets/{index-CJjHD8Iz.js → index-D49esfAN.js} +1 -1
- package/web/dist/index.html +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
## [0.3.52] - 2026-04-18
|
|
2
|
+
|
|
3
|
+
### Added
|
|
4
|
+
|
|
5
|
+
- **OpenCode WebUI Support** — Added `--opencode-web` flag to open the OpenCode WebUI dashboard after configuring the selected model. This mirrors the existing `--opencode-desktop` behavior.
|
|
6
|
+
|
|
1
7
|
## [0.3.51] - 2026-04-11
|
|
2
8
|
|
|
3
9
|
### Changed
|
package/README.md
CHANGED
|
@@ -11,10 +11,12 @@
|
|
|
11
11
|
</p>
|
|
12
12
|
|
|
13
13
|
<h1 align="center">free-coding-models</h1>
|
|
14
|
+
|
|
15
|
+
|
|
14
16
|
|
|
15
17
|
<p align="center">
|
|
16
18
|
<strong>Find the fastest free coding model in seconds</strong><br>
|
|
17
|
-
<sub>Ping 238 models across 25 AI Free providers in real-time </sub><br> <sub> Install Free API endpoints to your favorite AI coding tool: <br>📦 OpenCode, 🦞 OpenClaw, 💘 Crush, 🪿 Goose, 🛠 Aider, 🐉 Qwen Code, 🤲 OpenHands, ⚡ Amp, 🔮 Hermes, ▶️ Continue, 🧠 Cline, 🛠️ Xcode, π Pi, 🦘 Rovo or ♊ Gemini in one keystroke</sub>
|
|
19
|
+
<sub>Ping 238 models across 25 AI Free providers in real-time </sub><br> <sub> Install Free API endpoints to your favorite AI coding tool: <br>📦 OpenCode, 📦 OpenCode Desktop, 📦 OpenCode WebUI, 🦞 OpenClaw, 💘 Crush, 🪿 Goose, 🛠 Aider, 🐉 Qwen Code, 🤲 OpenHands, ⚡ Amp, 🔮 Hermes, ▶️ Continue, 🧠 Cline, 🛠️ Xcode, π Pi, 🦘 Rovo or ♊ Gemini in one keystroke</sub>
|
|
18
20
|
</p>
|
|
19
21
|
|
|
20
22
|
|
|
@@ -260,6 +262,7 @@ When launching the web dashboard, `free-coding-models` prefers `http://localhost
|
|
|
260
262
|
|------|----------|
|
|
261
263
|
| `--opencode` | 📦 OpenCode CLI |
|
|
262
264
|
| `--opencode-desktop` | 📦 OpenCode Desktop |
|
|
265
|
+
| `--opencode-web` | 📦 OpenCode WebUI |
|
|
263
266
|
| `--openclaw` | 🦞 OpenClaw |
|
|
264
267
|
| `--crush` | 💘 Crush |
|
|
265
268
|
| `--goose` | 🪿 Goose |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "free-coding-models",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.52",
|
|
4
4
|
"description": "Find the fastest coding LLM models in seconds — ping free models from multiple providers, pick the best one for OpenCode, Cursor, or any AI coding assistant.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"nvidia",
|
package/src/app.js
CHANGED
|
@@ -105,7 +105,7 @@ import { usageForRow as _usageForRow } from '../src/usage-reader.js'
|
|
|
105
105
|
import { buildProviderModelTokenKey, loadTokenUsageByProviderModel } from '../src/token-usage-reader.js'
|
|
106
106
|
import { parseOpenRouterResponse, fetchProviderQuota as _fetchProviderQuotaFromModule } from '../src/provider-quota-fetchers.js'
|
|
107
107
|
import { isKnownQuotaTelemetry } from '../src/quota-capabilities.js'
|
|
108
|
-
import { ALT_ENTER, ALT_LEAVE, ALT_HOME, PING_TIMEOUT, PING_INTERVAL, FPS, COL_MODEL, COL_MS, CELL_W, FRAMES, TIER_CYCLE, SETTINGS_OVERLAY_BG, HELP_OVERLAY_BG, RECOMMEND_OVERLAY_BG, OVERLAY_PANEL_WIDTH, TABLE_HEADER_LINES, TABLE_FOOTER_LINES, TABLE_FIXED_LINES, WIDTH_WARNING_MIN_COLS, msCell, spinCell } from '../src/constants.js'
|
|
108
|
+
import { ALT_ENTER, ALT_LEAVE, ALT_HOME, PING_TIMEOUT, PING_INTERVAL, FPS, COL_MODEL, COL_MS, CELL_W, FRAMES, TIER_CYCLE, VERDICT_CYCLE, HEALTH_CYCLE, SETTINGS_OVERLAY_BG, HELP_OVERLAY_BG, RECOMMEND_OVERLAY_BG, OVERLAY_PANEL_WIDTH, TABLE_HEADER_LINES, TABLE_FOOTER_LINES, TABLE_FIXED_LINES, WIDTH_WARNING_MIN_COLS, msCell, spinCell } from '../src/constants.js'
|
|
109
109
|
import { TIER_COLOR } from '../src/tier-colors.js'
|
|
110
110
|
import { resolveCloudflareUrl, buildPingRequest, ping, extractQuotaPercent, getProviderQuotaPercentCached, usagePlaceholderForProvider } from '../src/ping.js'
|
|
111
111
|
import { runFiableMode, filterByTierOrExit, fetchOpenRouterFreeModels } from '../src/analysis.js'
|
|
@@ -117,7 +117,7 @@ import { promptApiKey } from '../src/setup.js'
|
|
|
117
117
|
import { syncShellEnv, ensureShellRcSource, promptShellEnvMigration, removeShellEnv } from '../src/shell-env.js'
|
|
118
118
|
import { stripAnsi, maskApiKey, displayWidth, padEndDisplay, tintOverlayLines, keepOverlayTargetVisible, sliceOverlayLines, calculateViewport, sortResultsWithPinnedFavorites, adjustScrollOffset } from '../src/render-helpers.js'
|
|
119
119
|
import { renderTable, PROVIDER_COLOR } from '../src/render-table.js'
|
|
120
|
-
import { setOpenCodeModelData, startOpenCode, startOpenCodeDesktop } from '../src/opencode.js'
|
|
120
|
+
import { setOpenCodeModelData, startOpenCode, startOpenCodeDesktop, startOpenCodeWeb } from '../src/opencode.js'
|
|
121
121
|
import { startOpenClaw } from '../src/openclaw.js'
|
|
122
122
|
import { createOverlayRenderers } from '../src/overlays.js'
|
|
123
123
|
import { createKeyHandler, createMouseEventHandler } from '../src/key-handler.js'
|
|
@@ -252,6 +252,7 @@ export async function runApp(cliArgs, config) {
|
|
|
252
252
|
const flagByMode = {
|
|
253
253
|
opencode: cliArgs.openCodeMode,
|
|
254
254
|
'opencode-desktop': cliArgs.openCodeDesktopMode,
|
|
255
|
+
'opencode-web': cliArgs.openCodeWebMode,
|
|
255
256
|
openclaw: cliArgs.openClawMode,
|
|
256
257
|
aider: cliArgs.aiderMode,
|
|
257
258
|
crush: cliArgs.crushMode,
|
|
@@ -423,6 +424,8 @@ export async function runApp(cliArgs, config) {
|
|
|
423
424
|
mode, // 📖 'opencode' or 'openclaw' — controls Enter action
|
|
424
425
|
tierFilterMode: 0, // 📖 Index into TIER_CYCLE (0=All, 1=S+, 2=S, ...)
|
|
425
426
|
originFilterMode: 0, // 📖 Index into ORIGIN_CYCLE (0=All, then providers)
|
|
427
|
+
verdictFilterMode: 0, // 📖 Index into VERDICT_CYCLE (0=All, then verdicts)
|
|
428
|
+
healthFilterMode: 0, // 📖 Index into HEALTH_CYCLE (0=All, then health states)
|
|
426
429
|
hideUnconfiguredModels: config.settings?.hideUnconfiguredModels === true, // 📖 Hide providers with no configured API key when true.
|
|
427
430
|
favoritesPinnedAndSticky: config.settings?.favoritesPinnedAndSticky === true, // 📖 false by default: favorites follow normal sort/filter rules until Y enables pinned+sticky mode.
|
|
428
431
|
footerHidden: config.settings?.footerHidden === true, // 📖 true = footer is collapsed to a single toggle hint
|
|
@@ -739,6 +742,8 @@ export async function runApp(cliArgs, config) {
|
|
|
739
742
|
function applyTierFilter() {
|
|
740
743
|
const activeTier = TIER_CYCLE[state.tierFilterMode]
|
|
741
744
|
const activeOrigin = ORIGIN_CYCLE[state.originFilterMode]
|
|
745
|
+
const activeVerdict = VERDICT_CYCLE[state.verdictFilterMode]
|
|
746
|
+
const activeHealth = HEALTH_CYCLE[state.healthFilterMode]
|
|
742
747
|
state.results.forEach(r => {
|
|
743
748
|
// 📖 Sticky-favorites mode keeps favorites visible regardless of configured-only, tier, or provider filters.
|
|
744
749
|
if (state.favoritesPinnedAndSticky && r.isFavorite) {
|
|
@@ -754,12 +759,16 @@ export async function runApp(cliArgs, config) {
|
|
|
754
759
|
r.hidden = true
|
|
755
760
|
return
|
|
756
761
|
}
|
|
757
|
-
// 📖 Apply
|
|
758
|
-
// 📖 TIER_LETTER_MAP is used so --tier S also includes S+ models (tier family behavior).
|
|
762
|
+
// 📖 Apply tier, origin, verdict, and health filters — model is hidden if it fails any
|
|
759
763
|
const allowedTiers = (activeTier && TIER_LETTER_MAP[activeTier]) ? TIER_LETTER_MAP[activeTier] : [activeTier]
|
|
760
764
|
const tierHide = activeTier !== null && !allowedTiers.includes(r.tier)
|
|
761
765
|
const originHide = activeOrigin !== null && r.providerKey !== activeOrigin
|
|
762
|
-
|
|
766
|
+
// 📖 Verdict filter: match against getVerdict(r) when active
|
|
767
|
+
const rVerdict = getVerdict(r)
|
|
768
|
+
const verdictHide = activeVerdict !== null && rVerdict !== activeVerdict
|
|
769
|
+
// 📖 Health filter: match against r.status when active
|
|
770
|
+
const healthHide = activeHealth !== null && r.status !== activeHealth
|
|
771
|
+
if (tierHide || originHide || verdictHide || healthHide) {
|
|
763
772
|
r.hidden = true
|
|
764
773
|
return
|
|
765
774
|
}
|
|
@@ -864,6 +873,7 @@ export async function runApp(cliArgs, config) {
|
|
|
864
873
|
runUpdate,
|
|
865
874
|
startOpenClaw,
|
|
866
875
|
startOpenCodeDesktop,
|
|
876
|
+
startOpenCodeWeb,
|
|
867
877
|
startOpenCode,
|
|
868
878
|
startExternalTool,
|
|
869
879
|
getToolModeOrder,
|
|
@@ -1042,7 +1052,10 @@ export async function runApp(cliArgs, config) {
|
|
|
1042
1052
|
state.versionAlertsEnabled,
|
|
1043
1053
|
state.favoritesPinnedAndSticky,
|
|
1044
1054
|
state.customTextFilter,
|
|
1045
|
-
state.lastReleaseDate
|
|
1055
|
+
state.lastReleaseDate,
|
|
1056
|
+
state.footerHidden,
|
|
1057
|
+
state.verdictFilterMode,
|
|
1058
|
+
state.healthFilterMode
|
|
1046
1059
|
)
|
|
1047
1060
|
}
|
|
1048
1061
|
tableContent = state.commandPaletteFrozenTable
|
|
@@ -1077,7 +1090,10 @@ export async function runApp(cliArgs, config) {
|
|
|
1077
1090
|
state.versionAlertsEnabled,
|
|
1078
1091
|
state.favoritesPinnedAndSticky,
|
|
1079
1092
|
state.customTextFilter,
|
|
1080
|
-
state.lastReleaseDate
|
|
1093
|
+
state.lastReleaseDate,
|
|
1094
|
+
state.footerHidden,
|
|
1095
|
+
state.verdictFilterMode,
|
|
1096
|
+
state.healthFilterMode
|
|
1081
1097
|
)
|
|
1082
1098
|
}
|
|
1083
1099
|
|
|
@@ -1121,7 +1137,7 @@ export async function runApp(cliArgs, config) {
|
|
|
1121
1137
|
pinFavorites: state.favoritesPinnedAndSticky,
|
|
1122
1138
|
})
|
|
1123
1139
|
|
|
1124
|
-
process.stdout.write(ALT_HOME + renderTable(state.results, state.pendingPings, state.frame, state.cursor, state.sortColumn, state.sortDirection, state.pingInterval, state.lastPingTime, state.mode, state.tierFilterMode, state.scrollOffset, state.terminalRows, state.terminalCols, state.originFilterMode, null, state.pingMode, state.pingModeSource, state.hideUnconfiguredModels, state.widthWarningStartedAt, state.widthWarningDismissed, state.widthWarningShowCount, state.settingsUpdateState, state.settingsUpdateLatestVersion, false, state.startupLatestVersion, state.versionAlertsEnabled, state.favoritesPinnedAndSticky, state.customTextFilter, state.lastReleaseDate, state.footerHidden))
|
|
1140
|
+
process.stdout.write(ALT_HOME + renderTable(state.results, state.pendingPings, state.frame, state.cursor, state.sortColumn, state.sortDirection, state.pingInterval, state.lastPingTime, state.mode, state.tierFilterMode, state.scrollOffset, state.terminalRows, state.terminalCols, state.originFilterMode, null, state.pingMode, state.pingModeSource, state.hideUnconfiguredModels, state.widthWarningStartedAt, state.widthWarningDismissed, state.widthWarningShowCount, state.settingsUpdateState, state.settingsUpdateLatestVersion, false, state.startupLatestVersion, state.versionAlertsEnabled, state.favoritesPinnedAndSticky, state.customTextFilter, state.lastReleaseDate, state.footerHidden, state.verdictFilterMode, state.healthFilterMode))
|
|
1125
1141
|
if (process.stdout.isTTY) {
|
|
1126
1142
|
process.stdout.flush && process.stdout.flush()
|
|
1127
1143
|
}
|
package/src/command-palette.js
CHANGED
|
@@ -20,6 +20,7 @@ import { TOOL_METADATA, TOOL_MODE_ORDER } from './tool-metadata.js'
|
|
|
20
20
|
const TOOL_MODE_DESCRIPTIONS = {
|
|
21
21
|
opencode: 'Launch in OpenCode CLI with the selected model.',
|
|
22
22
|
'opencode-desktop': 'Set model in shared config, then open OpenCode Desktop.',
|
|
23
|
+
'opencode-web': 'Set model in shared config, then open OpenCode WebUI.',
|
|
23
24
|
openclaw: 'Set default model in OpenClaw and launch it.',
|
|
24
25
|
crush: 'Launch Crush with this provider/model pair.',
|
|
25
26
|
goose: 'Launch Goose and preselect the active model.',
|
package/src/constants.js
CHANGED
|
@@ -77,6 +77,14 @@ export const FRAMES = ['⠋','⠙','⠹','⠸','⠼','⠴','⠦','⠧','⠇','
|
|
|
77
77
|
// 📖 Index 0 = no filter (show all), then each tier name in descending quality order.
|
|
78
78
|
export const TIER_CYCLE = [null, 'S+', 'S', 'A+', 'A', 'A-', 'B+', 'B', 'C']
|
|
79
79
|
|
|
80
|
+
// 📖 VERDICT_CYCLE: cycles through health verdict labels (0=All, then each verdict).
|
|
81
|
+
// 📖 Based on VERDICT_ORDER from utils.js — includes all possible getVerdict() values.
|
|
82
|
+
export const VERDICT_CYCLE = [null, 'Perfect', 'Normal', 'Slow', 'Spiky', 'Very Slow', 'Overloaded', 'Unstable', 'Not Active', 'Pending']
|
|
83
|
+
|
|
84
|
+
// 📖 HEALTH_CYCLE: cycles through ping status states (0=All, then each status).
|
|
85
|
+
// 📖 Based on the status values in the app: up, timeout, down, auth_error, noauth, pending.
|
|
86
|
+
export const HEALTH_CYCLE = [null, 'up', 'timeout', 'down', 'auth_error', 'noauth', 'pending']
|
|
87
|
+
|
|
80
88
|
// 📖 Overlay background chalk functions — each overlay panel has a distinct tint
|
|
81
89
|
// 📖 so users can tell Settings, Help, Recommend, and Log panels apart at a glance.
|
|
82
90
|
export const SETTINGS_OVERLAY_BG = chalk.bgRgb(0, 0, 0)
|
|
@@ -93,8 +101,8 @@ export const WIDTH_WARNING_MIN_COLS = 80
|
|
|
93
101
|
|
|
94
102
|
// 📖 Table row-budget constants — must stay in sync with renderTable()'s actual output.
|
|
95
103
|
// 📖 If this drifts, model rows overflow and can push the title row out of view.
|
|
96
|
-
export const TABLE_HEADER_LINES =
|
|
97
|
-
export const TABLE_FOOTER_LINES =
|
|
104
|
+
export const TABLE_HEADER_LINES = 5 // 📖 title, filter bar, spacer, column headers, separator
|
|
105
|
+
export const TABLE_FOOTER_LINES = 1 // 📖 single toggle-hint line when collapsed, full footer otherwise
|
|
98
106
|
export const TABLE_FIXED_LINES = TABLE_HEADER_LINES + TABLE_FOOTER_LINES
|
|
99
107
|
|
|
100
108
|
// ─── Small cell-formatting helpers ────────────────────────────────────────────
|
package/src/key-handler.js
CHANGED
|
@@ -39,7 +39,7 @@ import { getLastLayout, COLUMN_SORT_MAP } from './render-table.js'
|
|
|
39
39
|
import { cycleThemeSetting, detectActiveTheme } from './theme.js'
|
|
40
40
|
import { syncShellEnv, ensureShellRcSource, removeShellEnv } from './shell-env.js'
|
|
41
41
|
import { buildCommandPaletteTree, flattenCommandTree, filterCommandPaletteEntries } from './command-palette.js'
|
|
42
|
-
import { WIDTH_WARNING_MIN_COLS } from './constants.js'
|
|
42
|
+
import { WIDTH_WARNING_MIN_COLS, VERDICT_CYCLE, HEALTH_CYCLE } from './constants.js'
|
|
43
43
|
import { scanAllToolConfigs, softDeleteModel } from './installed-models-manager.js'
|
|
44
44
|
import { startExternalTool } from './tool-launchers.js'
|
|
45
45
|
|
|
@@ -264,6 +264,7 @@ export function createKeyHandler(ctx) {
|
|
|
264
264
|
runUpdate,
|
|
265
265
|
startOpenClaw,
|
|
266
266
|
startOpenCodeDesktop,
|
|
267
|
+
startOpenCodeWeb,
|
|
267
268
|
startOpenCode,
|
|
268
269
|
startExternalTool,
|
|
269
270
|
getToolModeOrder,
|
|
@@ -495,6 +496,8 @@ export function createKeyHandler(ctx) {
|
|
|
495
496
|
exitCode = await startOpenClaw(userSelected, state.config, { launchCli: true })
|
|
496
497
|
} else if (state.mode === 'opencode-desktop') {
|
|
497
498
|
exitCode = await startOpenCodeDesktop(userSelected, state.config)
|
|
499
|
+
} else if (state.mode === 'opencode-web') {
|
|
500
|
+
exitCode = await startOpenCodeWeb(userSelected, state.config)
|
|
498
501
|
} else if (state.mode === 'opencode') {
|
|
499
502
|
exitCode = await startOpenCode(userSelected, state.config)
|
|
500
503
|
} else {
|
|
@@ -2414,6 +2417,7 @@ export function createKeyHandler(ctx) {
|
|
|
2414
2417
|
if (!state.config.settings || typeof state.config.settings !== 'object') state.config.settings = {}
|
|
2415
2418
|
state.config.settings.footerHidden = state.footerHidden
|
|
2416
2419
|
saveConfig(state.config)
|
|
2420
|
+
state.frame++ // 📖 Force immediate re-render
|
|
2417
2421
|
return
|
|
2418
2422
|
}
|
|
2419
2423
|
|
|
@@ -2449,6 +2453,24 @@ export function createKeyHandler(ctx) {
|
|
|
2449
2453
|
return
|
|
2450
2454
|
}
|
|
2451
2455
|
|
|
2456
|
+
// 📖 Verdict filter key: V = cycle through each verdict (All → Perfect → Normal → Slow → ... → All)
|
|
2457
|
+
if (key.name === 'v') {
|
|
2458
|
+
state.verdictFilterMode = (state.verdictFilterMode + 1) % VERDICT_CYCLE.length
|
|
2459
|
+
applyTierFilter()
|
|
2460
|
+
refreshVisibleSorted({ resetCursor: true })
|
|
2461
|
+
persistUiSettings()
|
|
2462
|
+
return
|
|
2463
|
+
}
|
|
2464
|
+
|
|
2465
|
+
// 📖 Health filter key: H = cycle through each health status (All → Up → Timeout → Down → ... → All)
|
|
2466
|
+
if (key.name === 'h') {
|
|
2467
|
+
state.healthFilterMode = (state.healthFilterMode + 1) % HEALTH_CYCLE.length
|
|
2468
|
+
applyTierFilter()
|
|
2469
|
+
refreshVisibleSorted({ resetCursor: true })
|
|
2470
|
+
persistUiSettings()
|
|
2471
|
+
return
|
|
2472
|
+
}
|
|
2473
|
+
|
|
2452
2474
|
// 📖 Help overlay key: Ctrl+H = toggle help overlay
|
|
2453
2475
|
if (key.ctrl && key.name === 'h') {
|
|
2454
2476
|
state.helpVisible = !state.helpVisible
|
package/src/opencode.js
CHANGED
|
@@ -543,6 +543,80 @@ export async function startOpenCode(model, fcmConfig) {
|
|
|
543
543
|
await spawnOpenCode(['--model', modelRef], providerKey, fcmConfig)
|
|
544
544
|
}
|
|
545
545
|
|
|
546
|
+
// ─── Start OpenCode Web ───────────────────────────────────────────────────────
|
|
547
|
+
|
|
548
|
+
export async function startOpenCodeWeb(model, fcmConfig) {
|
|
549
|
+
const providerKey = model.providerKey ?? 'nvidia'
|
|
550
|
+
const ocModelId = getOpenCodeModelId(providerKey, model.modelId)
|
|
551
|
+
const modelRef = `${providerKey}/${ocModelId}`
|
|
552
|
+
|
|
553
|
+
const launchWeb = async () => {
|
|
554
|
+
const { exec } = await import('child_process')
|
|
555
|
+
const url = 'https://opencode.ai'
|
|
556
|
+
let command
|
|
557
|
+
if (isMac) {
|
|
558
|
+
command = `open "${url}"`
|
|
559
|
+
} else if (isWindows) {
|
|
560
|
+
command = `start "${url}"`
|
|
561
|
+
} else {
|
|
562
|
+
command = `xdg-open "${url}"`
|
|
563
|
+
}
|
|
564
|
+
exec(command, (err) => {
|
|
565
|
+
if (err) {
|
|
566
|
+
console.error(chalk.red(' Could not open OpenCode WebUI'))
|
|
567
|
+
console.error(chalk.dim(` Please visit ${url} manually`))
|
|
568
|
+
}
|
|
569
|
+
})
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// 📖 Mirror OpenCode Desktop behavior: set the model in opencode.json
|
|
573
|
+
const config = loadOpenCodeConfig()
|
|
574
|
+
const backupPath = `${getOpenCodeConfigPath()}.backup-${Date.now()}`
|
|
575
|
+
|
|
576
|
+
if (existsSync(getOpenCodeConfigPath())) {
|
|
577
|
+
copyFileSync(getOpenCodeConfigPath(), backupPath)
|
|
578
|
+
console.log(chalk.dim(` Backup: ${backupPath}`))
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
if (!config.provider) config.provider = {}
|
|
582
|
+
|
|
583
|
+
// 📖 Provider-specific config setup (same as CLI/Desktop)
|
|
584
|
+
if (providerKey === 'nvidia' && !config.provider.nvidia) {
|
|
585
|
+
config.provider.nvidia = {
|
|
586
|
+
npm: '@ai-sdk/openai-compatible',
|
|
587
|
+
name: 'NVIDIA NIM',
|
|
588
|
+
options: { baseURL: 'https://integrate.api.nvidia.com/v1', apiKey: '{env:NVIDIA_API_KEY}' },
|
|
589
|
+
models: {}
|
|
590
|
+
}
|
|
591
|
+
} else if (providerKey === 'groq' && !config.provider.groq) {
|
|
592
|
+
config.provider.groq = { options: { apiKey: '{env:GROQ_API_KEY}' }, models: {} }
|
|
593
|
+
} else if (providerKey === 'cerebras' && !config.provider.cerebras) {
|
|
594
|
+
config.provider.cerebras = {
|
|
595
|
+
npm: '@ai-sdk/openai-compatible',
|
|
596
|
+
name: 'Cerebras',
|
|
597
|
+
options: { baseURL: 'https://api.cerebras.ai/v1', apiKey: '{env:CEREBRAS_API_KEY}' },
|
|
598
|
+
models: {}
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
// ... other providers are handled as they are selected
|
|
602
|
+
|
|
603
|
+
console.log(chalk.green(` Setting ${chalk.bold(model.label)} as default (mirroring Desktop)...`))
|
|
604
|
+
|
|
605
|
+
if (providerKey !== 'opencode-zen' && config.provider[providerKey]) {
|
|
606
|
+
if (!config.provider[providerKey].models) config.provider[providerKey].models = {}
|
|
607
|
+
config.provider[providerKey].models[ocModelId] = { name: model.label }
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
config.model = providerKey === 'opencode-zen' ? `opencode/${ocModelId}` : modelRef
|
|
611
|
+
saveOpenCodeConfig(config)
|
|
612
|
+
|
|
613
|
+
console.log(chalk.dim(` Config saved to: ${getOpenCodeConfigPath()}`))
|
|
614
|
+
console.log(chalk.dim(' Opening OpenCode WebUI...'))
|
|
615
|
+
console.log()
|
|
616
|
+
|
|
617
|
+
await launchWeb()
|
|
618
|
+
}
|
|
619
|
+
|
|
546
620
|
// ─── Start OpenCode Desktop ───────────────────────────────────────────────────
|
|
547
621
|
|
|
548
622
|
export async function startOpenCodeDesktop(model, fcmConfig) {
|