free-coding-models 0.3.16 → 0.3.18
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 +23 -0
- package/README.md +195 -120
- package/package.json +1 -1
- package/src/app.js +20 -2
- package/src/config.js +3 -0
- package/src/key-handler.js +184 -38
- package/src/openclaw.js +39 -5
- package/src/opencode.js +2 -1
- package/src/overlays.js +314 -223
- package/src/render-helpers.js +1 -1
- package/src/render-table.js +152 -180
- package/src/theme.js +315 -0
- package/src/tier-colors.js +15 -17
- package/src/tool-bootstrap.js +310 -0
- package/src/tool-launchers.js +12 -7
- package/src/ui-config.js +24 -31
package/src/key-handler.js
CHANGED
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
import { loadChangelog } from './changelog-loader.js'
|
|
31
31
|
import { loadConfig, replaceConfigContents } from './config.js'
|
|
32
32
|
import { cleanupLegacyProxyArtifacts } from './legacy-proxy-cleanup.js'
|
|
33
|
+
import { cycleThemeSetting, detectActiveTheme } from './theme.js'
|
|
33
34
|
|
|
34
35
|
// 📖 Some providers need an explicit probe model because the first catalog entry
|
|
35
36
|
// 📖 is not guaranteed to be accepted by their chat endpoint.
|
|
@@ -184,6 +185,9 @@ export function createKeyHandler(ctx) {
|
|
|
184
185
|
startOpenCode,
|
|
185
186
|
startExternalTool,
|
|
186
187
|
getToolModeOrder,
|
|
188
|
+
getToolInstallPlan,
|
|
189
|
+
isToolInstalled,
|
|
190
|
+
installToolWithPlan,
|
|
187
191
|
startRecommendAnalysis,
|
|
188
192
|
stopRecommendAnalysis,
|
|
189
193
|
sendBugReport,
|
|
@@ -200,12 +204,104 @@ export function createKeyHandler(ctx) {
|
|
|
200
204
|
noteUserActivity,
|
|
201
205
|
intervalToPingMode,
|
|
202
206
|
PING_MODE_CYCLE,
|
|
207
|
+
themeRowIdx,
|
|
203
208
|
setResults,
|
|
204
209
|
readline,
|
|
205
210
|
} = ctx
|
|
206
211
|
|
|
207
212
|
let userSelected = null
|
|
208
213
|
|
|
214
|
+
function resetToolInstallPrompt() {
|
|
215
|
+
state.toolInstallPromptOpen = false
|
|
216
|
+
state.toolInstallPromptCursor = 0
|
|
217
|
+
state.toolInstallPromptScrollOffset = 0
|
|
218
|
+
state.toolInstallPromptMode = null
|
|
219
|
+
state.toolInstallPromptModel = null
|
|
220
|
+
state.toolInstallPromptPlan = null
|
|
221
|
+
state.toolInstallPromptErrorMsg = null
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function shouldCheckMissingTool(mode) {
|
|
225
|
+
return mode !== 'opencode-desktop'
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
async function launchSelectedModel(selected, options = {}) {
|
|
229
|
+
const { uiAlreadyStopped = false } = options
|
|
230
|
+
userSelected = { modelId: selected.modelId, label: selected.label, tier: selected.tier, providerKey: selected.providerKey }
|
|
231
|
+
|
|
232
|
+
if (!uiAlreadyStopped) {
|
|
233
|
+
readline.emitKeypressEvents(process.stdin)
|
|
234
|
+
if (process.stdin.isTTY) process.stdin.setRawMode(true)
|
|
235
|
+
stopUi()
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// 📖 Show selection status before handing control to the target tool.
|
|
239
|
+
if (selected.status === 'timeout') {
|
|
240
|
+
console.log(chalk.yellow(` ⚠ Selected: ${selected.label} (currently timing out)`))
|
|
241
|
+
} else if (selected.status === 'down') {
|
|
242
|
+
console.log(chalk.red(` ⚠ Selected: ${selected.label} (currently down)`))
|
|
243
|
+
} else {
|
|
244
|
+
console.log(chalk.cyan(` ✓ Selected: ${selected.label}`))
|
|
245
|
+
}
|
|
246
|
+
console.log()
|
|
247
|
+
|
|
248
|
+
// 📖 OpenClaw manages API keys inside its own config file. All other tools
|
|
249
|
+
// 📖 still need a provider key to be useful, so keep the existing warning.
|
|
250
|
+
if (state.mode !== 'openclaw') {
|
|
251
|
+
const selectedApiKey = getApiKey(state.config, selected.providerKey)
|
|
252
|
+
if (!selectedApiKey) {
|
|
253
|
+
console.log(chalk.yellow(` Warning: No API key configured for ${selected.providerKey}.`))
|
|
254
|
+
console.log(chalk.yellow(` The selected tool may not be able to use ${selected.label}.`))
|
|
255
|
+
console.log(chalk.dim(` Set ${ENV_VAR_NAMES[selected.providerKey] || selected.providerKey.toUpperCase() + '_API_KEY'} or configure via settings (P key).`))
|
|
256
|
+
console.log()
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
let exitCode = 0
|
|
261
|
+
if (state.mode === 'openclaw') {
|
|
262
|
+
exitCode = await startOpenClaw(userSelected, state.config, { launchCli: true })
|
|
263
|
+
} else if (state.mode === 'opencode-desktop') {
|
|
264
|
+
exitCode = await startOpenCodeDesktop(userSelected, state.config)
|
|
265
|
+
} else if (state.mode === 'opencode') {
|
|
266
|
+
exitCode = await startOpenCode(userSelected, state.config)
|
|
267
|
+
} else {
|
|
268
|
+
exitCode = await startExternalTool(state.mode, userSelected, state.config)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
process.exit(typeof exitCode === 'number' ? exitCode : 0)
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
async function installMissingToolAndLaunch(selected, installPlan) {
|
|
275
|
+
const currentPlan = installPlan || getToolInstallPlan(state.mode)
|
|
276
|
+
stopUi({ resetRawMode: true })
|
|
277
|
+
|
|
278
|
+
console.log(chalk.cyan(` 📦 Installing missing tool for ${state.mode}...`))
|
|
279
|
+
if (currentPlan?.summary) console.log(chalk.dim(` ${currentPlan.summary}`))
|
|
280
|
+
if (currentPlan?.shellCommand) console.log(chalk.dim(` ${currentPlan.shellCommand}`))
|
|
281
|
+
if (currentPlan?.note) console.log(chalk.dim(` ${currentPlan.note}`))
|
|
282
|
+
console.log()
|
|
283
|
+
|
|
284
|
+
const installResult = await installToolWithPlan(currentPlan)
|
|
285
|
+
if (!installResult.ok) {
|
|
286
|
+
console.log(chalk.red(` X Tool installation failed with exit code ${installResult.exitCode}.`))
|
|
287
|
+
if (currentPlan?.docsUrl) console.log(chalk.dim(` Docs: ${currentPlan.docsUrl}`))
|
|
288
|
+
console.log()
|
|
289
|
+
process.exit(installResult.exitCode || 1)
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (shouldCheckMissingTool(state.mode) && !isToolInstalled(state.mode)) {
|
|
293
|
+
console.log(chalk.yellow(' ⚠ The installer finished, but the tool is still not reachable from this terminal session.'))
|
|
294
|
+
console.log(chalk.dim(' Restart your shell or add the tool bin directory to PATH, then retry the launch.'))
|
|
295
|
+
if (currentPlan?.docsUrl) console.log(chalk.dim(` Docs: ${currentPlan.docsUrl}`))
|
|
296
|
+
console.log()
|
|
297
|
+
process.exit(1)
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
console.log(chalk.green(' ✓ Tool installed successfully. Continuing with the selected model...'))
|
|
301
|
+
console.log()
|
|
302
|
+
await launchSelectedModel(selected, { uiAlreadyStopped: true })
|
|
303
|
+
}
|
|
304
|
+
|
|
209
305
|
// ─── Settings key test helper ───────────────────────────────────────────────
|
|
210
306
|
// 📖 Fires a single ping to the selected provider to verify the API key works.
|
|
211
307
|
async function testProviderKey(providerKey) {
|
|
@@ -382,6 +478,20 @@ export function createKeyHandler(ctx) {
|
|
|
382
478
|
saveConfig(state.config)
|
|
383
479
|
}
|
|
384
480
|
|
|
481
|
+
// 📖 Theme switches need to update both persisted preference and the live
|
|
482
|
+
// 📖 semantic palette immediately so every screen redraw adopts the new colors.
|
|
483
|
+
function applyThemeSetting(nextTheme) {
|
|
484
|
+
if (!state.config.settings) state.config.settings = {}
|
|
485
|
+
state.config.settings.theme = nextTheme
|
|
486
|
+
saveConfig(state.config)
|
|
487
|
+
detectActiveTheme(nextTheme)
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
function cycleGlobalTheme() {
|
|
491
|
+
const currentTheme = state.config.settings?.theme || 'auto'
|
|
492
|
+
applyThemeSetting(cycleThemeSetting(currentTheme))
|
|
493
|
+
}
|
|
494
|
+
|
|
385
495
|
function resetInstallEndpointsOverlay() {
|
|
386
496
|
state.installEndpointsOpen = false
|
|
387
497
|
state.installEndpointsPhase = 'providers'
|
|
@@ -430,6 +540,11 @@ export function createKeyHandler(ctx) {
|
|
|
430
540
|
if (!key) return
|
|
431
541
|
noteUserActivity()
|
|
432
542
|
|
|
543
|
+
if (!state.feedbackOpen && !state.settingsEditMode && !state.settingsAddKeyMode && key.name === 'g' && !key.ctrl && !key.meta) {
|
|
544
|
+
cycleGlobalTheme()
|
|
545
|
+
return
|
|
546
|
+
}
|
|
547
|
+
|
|
433
548
|
// 📖 Profile system removed - API keys now persist permanently across all sessions
|
|
434
549
|
|
|
435
550
|
// 📖 Install Endpoints overlay: provider → tool → connection → scope → optional model subset.
|
|
@@ -613,6 +728,44 @@ export function createKeyHandler(ctx) {
|
|
|
613
728
|
return
|
|
614
729
|
}
|
|
615
730
|
|
|
731
|
+
if (state.toolInstallPromptOpen) {
|
|
732
|
+
if (key.ctrl && key.name === 'c') { exit(0); return }
|
|
733
|
+
|
|
734
|
+
const installPlan = state.toolInstallPromptPlan || getToolInstallPlan(state.toolInstallPromptMode)
|
|
735
|
+
const installSupported = Boolean(installPlan?.supported)
|
|
736
|
+
|
|
737
|
+
if (key.name === 'escape') {
|
|
738
|
+
resetToolInstallPrompt()
|
|
739
|
+
return
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
if (installSupported && key.name === 'up') {
|
|
743
|
+
state.toolInstallPromptCursor = Math.max(0, state.toolInstallPromptCursor - 1)
|
|
744
|
+
return
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
if (installSupported && key.name === 'down') {
|
|
748
|
+
state.toolInstallPromptCursor = Math.min(1, state.toolInstallPromptCursor + 1)
|
|
749
|
+
return
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
if (key.name === 'return') {
|
|
753
|
+
if (!installSupported) {
|
|
754
|
+
resetToolInstallPrompt()
|
|
755
|
+
return
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
const selectedModel = state.toolInstallPromptModel
|
|
759
|
+
const shouldInstall = state.toolInstallPromptCursor === 0
|
|
760
|
+
resetToolInstallPrompt()
|
|
761
|
+
|
|
762
|
+
if (!shouldInstall || !selectedModel) return
|
|
763
|
+
await installMissingToolAndLaunch(selectedModel, installPlan)
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
return
|
|
767
|
+
}
|
|
768
|
+
|
|
616
769
|
// 📖 Feedback overlay: intercept ALL keys while overlay is active.
|
|
617
770
|
// 📖 Enter → send to Discord, Esc → cancel, Backspace → delete char, printable → append to buffer.
|
|
618
771
|
if (state.feedbackOpen) {
|
|
@@ -926,7 +1079,8 @@ export function createKeyHandler(ctx) {
|
|
|
926
1079
|
const providerKeys = Object.keys(sources)
|
|
927
1080
|
const updateRowIdx = providerKeys.length
|
|
928
1081
|
const widthWarningRowIdx = updateRowIdx + 1
|
|
929
|
-
const
|
|
1082
|
+
const themeRowIdx = widthWarningRowIdx + 1
|
|
1083
|
+
const cleanupLegacyProxyRowIdx = themeRowIdx + 1
|
|
930
1084
|
const changelogViewRowIdx = cleanupLegacyProxyRowIdx + 1
|
|
931
1085
|
// 📖 Profile system removed - API keys now persist permanently across all sessions
|
|
932
1086
|
const maxRowIdx = changelogViewRowIdx
|
|
@@ -1076,6 +1230,11 @@ export function createKeyHandler(ctx) {
|
|
|
1076
1230
|
return
|
|
1077
1231
|
}
|
|
1078
1232
|
|
|
1233
|
+
if (state.settingsCursor === themeRowIdx) {
|
|
1234
|
+
cycleGlobalTheme()
|
|
1235
|
+
return
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1079
1238
|
if (state.settingsCursor === cleanupLegacyProxyRowIdx) {
|
|
1080
1239
|
runLegacyProxyCleanup()
|
|
1081
1240
|
return
|
|
@@ -1096,6 +1255,7 @@ export function createKeyHandler(ctx) {
|
|
|
1096
1255
|
|
|
1097
1256
|
// 📖 Enter edit mode for the selected provider's key
|
|
1098
1257
|
const pk = providerKeys[state.settingsCursor]
|
|
1258
|
+
if (!pk) return
|
|
1099
1259
|
state.settingsEditBuffer = resolveApiKeys(state.config, pk)[0] ?? ''
|
|
1100
1260
|
state.settingsEditMode = true
|
|
1101
1261
|
return
|
|
@@ -1108,6 +1268,11 @@ export function createKeyHandler(ctx) {
|
|
|
1108
1268
|
|| state.settingsCursor === cleanupLegacyProxyRowIdx
|
|
1109
1269
|
|| state.settingsCursor === changelogViewRowIdx
|
|
1110
1270
|
) return
|
|
1271
|
+
// 📖 Theme configuration cycle inside settings
|
|
1272
|
+
if (state.settingsCursor === themeRowIdx) {
|
|
1273
|
+
cycleGlobalTheme()
|
|
1274
|
+
return
|
|
1275
|
+
}
|
|
1111
1276
|
// 📖 Widths Warning toggle (disable/enable)
|
|
1112
1277
|
if (state.settingsCursor === widthWarningRowIdx) {
|
|
1113
1278
|
toggleWidthsWarningSetting()
|
|
@@ -1127,6 +1292,8 @@ export function createKeyHandler(ctx) {
|
|
|
1127
1292
|
if (key.name === 't') {
|
|
1128
1293
|
if (
|
|
1129
1294
|
state.settingsCursor === updateRowIdx
|
|
1295
|
+
|| state.settingsCursor === widthWarningRowIdx
|
|
1296
|
+
|| state.settingsCursor === themeRowIdx
|
|
1130
1297
|
|| state.settingsCursor === cleanupLegacyProxyRowIdx
|
|
1131
1298
|
|| state.settingsCursor === changelogViewRowIdx
|
|
1132
1299
|
) return
|
|
@@ -1134,6 +1301,7 @@ export function createKeyHandler(ctx) {
|
|
|
1134
1301
|
|
|
1135
1302
|
// 📖 Test the selected provider's key (fires a real ping)
|
|
1136
1303
|
const pk = providerKeys[state.settingsCursor]
|
|
1304
|
+
if (!pk) return
|
|
1137
1305
|
testProviderKey(pk)
|
|
1138
1306
|
return
|
|
1139
1307
|
}
|
|
@@ -1427,46 +1595,24 @@ export function createKeyHandler(ctx) {
|
|
|
1427
1595
|
// 📖 Use the cached visible+sorted array — guaranteed to match what's on screen
|
|
1428
1596
|
const selected = state.visibleSorted[state.cursor]
|
|
1429
1597
|
if (!selected) return // 📖 Guard: empty visible list (all filtered out)
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
} else if (selected.status === 'down') {
|
|
1442
|
-
console.log(chalk.red(` ⚠ Selected: ${selected.label} (currently down)`))
|
|
1443
|
-
} else {
|
|
1444
|
-
console.log(chalk.cyan(` ✓ Selected: ${selected.label}`))
|
|
1445
|
-
}
|
|
1446
|
-
console.log()
|
|
1447
|
-
|
|
1448
|
-
// 📖 Warn if no API key is configured for the selected model's provider
|
|
1449
|
-
if (state.mode !== 'openclaw') {
|
|
1450
|
-
const selectedApiKey = getApiKey(state.config, selected.providerKey)
|
|
1451
|
-
if (!selectedApiKey) {
|
|
1452
|
-
console.log(chalk.yellow(` Warning: No API key configured for ${selected.providerKey}.`))
|
|
1453
|
-
console.log(chalk.yellow(` The selected tool may not be able to use ${selected.label}.`))
|
|
1454
|
-
console.log(chalk.dim(` Set ${ENV_VAR_NAMES[selected.providerKey] || selected.providerKey.toUpperCase() + '_API_KEY'} or configure via settings (P key).`))
|
|
1455
|
-
console.log()
|
|
1598
|
+
if (shouldCheckMissingTool(state.mode) && !isToolInstalled(state.mode)) {
|
|
1599
|
+
state.toolInstallPromptOpen = true
|
|
1600
|
+
state.toolInstallPromptCursor = 0
|
|
1601
|
+
state.toolInstallPromptScrollOffset = 0
|
|
1602
|
+
state.toolInstallPromptMode = state.mode
|
|
1603
|
+
state.toolInstallPromptModel = {
|
|
1604
|
+
modelId: selected.modelId,
|
|
1605
|
+
label: selected.label,
|
|
1606
|
+
tier: selected.tier,
|
|
1607
|
+
providerKey: selected.providerKey,
|
|
1608
|
+
status: selected.status,
|
|
1456
1609
|
}
|
|
1610
|
+
state.toolInstallPromptPlan = getToolInstallPlan(state.mode)
|
|
1611
|
+
state.toolInstallPromptErrorMsg = null
|
|
1612
|
+
return
|
|
1457
1613
|
}
|
|
1458
1614
|
|
|
1459
|
-
|
|
1460
|
-
if (state.mode === 'openclaw') {
|
|
1461
|
-
await startOpenClaw(userSelected, state.config)
|
|
1462
|
-
} else if (state.mode === 'opencode-desktop') {
|
|
1463
|
-
await startOpenCodeDesktop(userSelected, state.config)
|
|
1464
|
-
} else if (state.mode === 'opencode') {
|
|
1465
|
-
await startOpenCode(userSelected, state.config)
|
|
1466
|
-
} else {
|
|
1467
|
-
await startExternalTool(state.mode, userSelected, state.config)
|
|
1468
|
-
}
|
|
1469
|
-
process.exit(0)
|
|
1615
|
+
await launchSelectedModel(selected)
|
|
1470
1616
|
}
|
|
1471
1617
|
}
|
|
1472
1618
|
}
|
package/src/openclaw.js
CHANGED
|
@@ -3,10 +3,12 @@
|
|
|
3
3
|
* @description OpenClaw config helpers for persisting the selected provider/model as the default.
|
|
4
4
|
*
|
|
5
5
|
* @details
|
|
6
|
-
* 📖 OpenClaw is config-driven
|
|
6
|
+
* 📖 OpenClaw is primarily config-driven, but FCM can now optionally launch the
|
|
7
|
+
* 📖 installed CLI right after persisting the selected default model.
|
|
7
8
|
* 📖 Pressing Enter in `OpenClaw` mode must therefore do two things reliably:
|
|
8
9
|
* - install the selected provider/model into `~/.openclaw/openclaw.json`
|
|
9
10
|
* - set that exact model as the default primary model for the next OpenClaw session
|
|
11
|
+
* - optionally start `openclaw` immediately when the caller asks for it
|
|
10
12
|
*
|
|
11
13
|
* 📖 The old implementation was hard-coded to `nvidia/*`, which meant selecting
|
|
12
14
|
* 📖 a Groq/Cerebras/etc. row silently wrote the wrong provider/model into the
|
|
@@ -30,6 +32,7 @@ import { dirname, join } from 'path'
|
|
|
30
32
|
import { installProviderEndpoints } from './endpoint-installer.js'
|
|
31
33
|
import { ENV_VAR_NAMES } from './provider-metadata.js'
|
|
32
34
|
import { PROVIDER_COLOR } from './render-table.js'
|
|
35
|
+
import { resolveToolBinaryPath } from './tool-bootstrap.js'
|
|
33
36
|
|
|
34
37
|
const OPENCLAW_CONFIG = join(homedir(), '.openclaw', 'openclaw.json')
|
|
35
38
|
|
|
@@ -53,13 +56,38 @@ export function saveOpenClawConfig(config, options = {}) {
|
|
|
53
56
|
writeFileSync(filePath, JSON.stringify(config, null, 2))
|
|
54
57
|
}
|
|
55
58
|
|
|
59
|
+
function spawnOpenClawCli() {
|
|
60
|
+
return new Promise(async (resolve, reject) => {
|
|
61
|
+
const { spawn } = await import('child_process')
|
|
62
|
+
const command = resolveToolBinaryPath('openclaw') || 'openclaw'
|
|
63
|
+
const child = spawn(command, [], {
|
|
64
|
+
stdio: 'inherit',
|
|
65
|
+
shell: false,
|
|
66
|
+
detached: false,
|
|
67
|
+
env: process.env,
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
child.on('exit', (code) => resolve(typeof code === 'number' ? code : 0))
|
|
71
|
+
child.on('error', (error) => {
|
|
72
|
+
if (error?.code === 'ENOENT') {
|
|
73
|
+
console.log(chalk.red(' X Could not find "openclaw" in PATH.'))
|
|
74
|
+
console.log(chalk.dim(' Install: npm install -g openclaw@latest or see https://docs.openclaw.ai/install'))
|
|
75
|
+
console.log()
|
|
76
|
+
resolve(1)
|
|
77
|
+
return
|
|
78
|
+
}
|
|
79
|
+
reject(error)
|
|
80
|
+
})
|
|
81
|
+
})
|
|
82
|
+
}
|
|
83
|
+
|
|
56
84
|
/**
|
|
57
85
|
* 📖 startOpenClaw installs the selected provider/model into OpenClaw and sets
|
|
58
86
|
* 📖 it as the primary default model. OpenClaw itself is not launched here.
|
|
59
87
|
*
|
|
60
88
|
* @param {{ providerKey: string, modelId: string, label: string }} model
|
|
61
89
|
* @param {Record<string, unknown>} config
|
|
62
|
-
* @param {{ paths?: { openclawConfigPath?: string } }} [options]
|
|
90
|
+
* @param {{ paths?: { openclawConfigPath?: string }, launchCli?: boolean }} [options]
|
|
63
91
|
* @returns {Promise<ReturnType<typeof installProviderEndpoints> | null>}
|
|
64
92
|
*/
|
|
65
93
|
export async function startOpenClaw(model, config, options = {}) {
|
|
@@ -85,9 +113,15 @@ export async function startOpenClaw(model, config, options = {}) {
|
|
|
85
113
|
if (result.backupPath) console.log(chalk.dim(` 💾 Backup: ${result.backupPath}`))
|
|
86
114
|
if (providerEnvName) console.log(chalk.dim(` 🔑 API key synced under config env.${providerEnvName}`))
|
|
87
115
|
console.log()
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
116
|
+
if (options.launchCli) {
|
|
117
|
+
console.log(chalk.dim(' Starting OpenClaw...'))
|
|
118
|
+
console.log()
|
|
119
|
+
await spawnOpenClawCli()
|
|
120
|
+
} else {
|
|
121
|
+
console.log(chalk.dim(' 💡 OpenClaw will reload config automatically when it notices the file change.'))
|
|
122
|
+
console.log(chalk.dim(` To apply manually: openclaw models set ${result.primaryModelRef || `${result.providerId}/${model.modelId}`}`))
|
|
123
|
+
console.log()
|
|
124
|
+
}
|
|
91
125
|
return result
|
|
92
126
|
} catch (error) {
|
|
93
127
|
console.log(chalk.red(` X Could not configure OpenClaw: ${error instanceof Error ? error.message : String(error)}`))
|
package/src/opencode.js
CHANGED
|
@@ -32,6 +32,7 @@ import { PROVIDER_COLOR } from './render-table.js'
|
|
|
32
32
|
import { loadOpenCodeConfig, saveOpenCodeConfig } from './opencode-config.js'
|
|
33
33
|
import { getApiKey } from './config.js'
|
|
34
34
|
import { ENV_VAR_NAMES, OPENCODE_MODEL_MAP, isWindows, isMac, isLinux } from './provider-metadata.js'
|
|
35
|
+
import { resolveToolBinaryPath } from './tool-bootstrap.js'
|
|
35
36
|
|
|
36
37
|
// 📖 OpenCode config location: ~/.config/opencode/opencode.json on ALL platforms.
|
|
37
38
|
// 📖 OpenCode uses xdg-basedir which resolves to %USERPROFILE%\.config on Windows.
|
|
@@ -177,7 +178,7 @@ async function spawnOpenCode(args, providerKey, fcmConfig, existingZaiProxy = nu
|
|
|
177
178
|
}
|
|
178
179
|
|
|
179
180
|
const { spawn } = await import('child_process')
|
|
180
|
-
const child = spawn('opencode', finalArgs, {
|
|
181
|
+
const child = spawn(resolveToolBinaryPath('opencode') || 'opencode', finalArgs, {
|
|
181
182
|
stdio: 'inherit',
|
|
182
183
|
shell: true,
|
|
183
184
|
detached: false,
|