free-coding-models 0.3.22 → 0.3.24
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 +48 -0
- package/README.md +76 -19
- package/package.json +1 -1
- package/sources.js +60 -0
- package/src/app.js +43 -17
- package/src/command-palette.js +103 -6
- package/src/config.js +4 -2
- package/src/endpoint-installer.js +3 -2
- package/src/key-handler.js +311 -29
- package/src/overlays.js +119 -8
- package/src/provider-metadata.js +25 -0
- package/src/render-helpers.js +15 -2
- package/src/render-table.js +81 -7
- package/src/theme.js +6 -0
- package/src/tool-bootstrap.js +22 -0
- package/src/tool-launchers.js +93 -2
- package/src/tool-metadata.js +94 -11
- package/src/utils.js +5 -1
package/src/overlays.js
CHANGED
|
@@ -94,7 +94,8 @@ export function createOverlayRenderers(state, deps) {
|
|
|
94
94
|
const providerKeys = Object.keys(sources)
|
|
95
95
|
const updateRowIdx = providerKeys.length
|
|
96
96
|
const themeRowIdx = updateRowIdx + 1
|
|
97
|
-
const
|
|
97
|
+
const favoritesModeRowIdx = themeRowIdx + 1
|
|
98
|
+
const cleanupLegacyProxyRowIdx = favoritesModeRowIdx + 1
|
|
98
99
|
const changelogViewRowIdx = cleanupLegacyProxyRowIdx + 1
|
|
99
100
|
const EL = '\x1b[K'
|
|
100
101
|
const lines = []
|
|
@@ -224,6 +225,16 @@ export function createOverlayRenderers(state, deps) {
|
|
|
224
225
|
const themeRow = `${bullet(state.settingsCursor === themeRowIdx)}${themeColors.textBold('Global Theme').padEnd(44)} ${themeStatusColor(themeStatus)}`
|
|
225
226
|
cursorLineByRow[themeRowIdx] = lines.length
|
|
226
227
|
lines.push(state.settingsCursor === themeRowIdx ? themeColors.bgCursor(themeRow) : themeRow)
|
|
228
|
+
|
|
229
|
+
// 📖 Favorites mode row mirrors Y-key behavior from the main table.
|
|
230
|
+
const favoritesModeEnabled = state.favoritesPinnedAndSticky === true
|
|
231
|
+
const favoritesModeStatus = favoritesModeEnabled
|
|
232
|
+
? themeColors.warningBold('Pinned + always visible')
|
|
233
|
+
: themeColors.info('Normal rows (filter/sort)')
|
|
234
|
+
const favoritesModeRow = `${bullet(state.settingsCursor === favoritesModeRowIdx)}${themeColors.textBold('Favorites Display Mode').padEnd(44)} ${favoritesModeStatus}`
|
|
235
|
+
cursorLineByRow[favoritesModeRowIdx] = lines.length
|
|
236
|
+
lines.push(state.settingsCursor === favoritesModeRowIdx ? themeColors.bgCursorSettingsList(favoritesModeRow) : favoritesModeRow)
|
|
237
|
+
|
|
227
238
|
if (updateState === 'error' && state.settingsUpdateError) {
|
|
228
239
|
lines.push(themeColors.error(` ${state.settingsUpdateError}`))
|
|
229
240
|
}
|
|
@@ -244,7 +255,7 @@ export function createOverlayRenderers(state, deps) {
|
|
|
244
255
|
if (state.settingsEditMode) {
|
|
245
256
|
lines.push(themeColors.dim(' Type API key • Enter Save • Esc Cancel'))
|
|
246
257
|
} else {
|
|
247
|
-
lines.push(themeColors.dim(' ↑↓ Navigate • Enter Edit/Run/Cycle • + Add key • - Remove key • Space Toggle/Cycle • T Test key • U Updates • G Global theme • Esc Close'))
|
|
258
|
+
lines.push(themeColors.dim(' ↑↓ Navigate • Enter Edit/Run/Cycle • + Add key • - Remove key • Space Toggle/Cycle • T Test key • U Updates • G Global theme • Y Favorites mode • Esc Close'))
|
|
248
259
|
}
|
|
249
260
|
// 📖 Show sync/restore status message if set
|
|
250
261
|
if (state.settingsSyncStatus) {
|
|
@@ -283,7 +294,7 @@ export function createOverlayRenderers(state, deps) {
|
|
|
283
294
|
|
|
284
295
|
// ─── Install Endpoints overlay renderer ───────────────────────────────────
|
|
285
296
|
// 📖 renderInstallEndpoints drives the provider → tool → scope → model flow
|
|
286
|
-
// 📖
|
|
297
|
+
// 📖 opened from Settings/Command Palette. It deliberately reuses the same overlay viewport
|
|
287
298
|
// 📖 helpers as Settings so long provider/model lists stay navigable.
|
|
288
299
|
function renderInstallEndpoints() {
|
|
289
300
|
const EL = '\x1b[K'
|
|
@@ -713,8 +724,8 @@ export function createOverlayRenderers(state, deps) {
|
|
|
713
724
|
lines.push(` ${label('CTX')} Context window size (128k, 200k, 256k, 1m, etc.) ${hint('Sort:')} ${key('C')}`)
|
|
714
725
|
lines.push(` ${hint('Bigger context = the model can read more of your codebase at once without forgetting.')}`)
|
|
715
726
|
lines.push('')
|
|
716
|
-
lines.push(` ${label('Model')} Model name (⭐ = favorited
|
|
717
|
-
lines.push(` ${hint('Star the ones you like
|
|
727
|
+
lines.push(` ${label('Model')} Model name (⭐ = favorited) ${hint('Sort:')} ${key('M')} ${hint('Favorite:')} ${key('F')}`)
|
|
728
|
+
lines.push(` ${hint('Star the ones you like. Press Y to switch between pinned mode and normal filter/sort mode.')}`)
|
|
718
729
|
lines.push('')
|
|
719
730
|
lines.push(` ${label('Provider')} Provider source (NIM, Groq, Cerebras, etc.) ${hint('Sort:')} ${key('O')} ${hint('Cycle:')} ${key('D')}`)
|
|
720
731
|
lines.push(` ${hint('Same model on different providers can have very different speed and uptime.')}`)
|
|
@@ -753,8 +764,10 @@ export function createOverlayRenderers(state, deps) {
|
|
|
753
764
|
lines.push(` ${key('W')} Toggle ping mode ${hint('(speed 2s → normal 10s → slow 30s → forced 4s)')}`)
|
|
754
765
|
lines.push(` ${key('Ctrl+P')} Open ⚡️ command palette ${hint('(search and run actions quickly)')}`)
|
|
755
766
|
lines.push(` ${key('E')} Toggle configured models only ${hint('(enabled by default)')}`)
|
|
756
|
-
lines.push(` ${key('Z')} Cycle tool mode ${hint('(OpenCode → Desktop → OpenClaw → Crush → Goose → Pi → Aider → Qwen → OpenHands → Amp)')}`)
|
|
757
|
-
lines.push(` ${key('F')} Toggle favorite on selected row ${hint('(⭐
|
|
767
|
+
lines.push(` ${key('Z')} Cycle tool mode ${hint('(📦 OpenCode → 📦 Desktop → 🦞 OpenClaw → 💘 Crush → 🪿 Goose → π Pi → 🛠 Aider → 🐉 Qwen → 🤲 OpenHands → ⚡ Amp → 🦘 Rovo → ♊ Gemini)')}`)
|
|
768
|
+
lines.push(` ${key('F')} Toggle favorite on selected row ${hint('(⭐ persisted across sessions)')}`)
|
|
769
|
+
lines.push(` ${key('Y')} Toggle favorites mode ${hint('(Pinned + always visible ↔ Normal filter/sort behavior)')}`)
|
|
770
|
+
lines.push(` ${key('X')} Clear active text filter ${hint('(remove custom query applied from ⚡️ Command Palette)')}`)
|
|
758
771
|
lines.push(` ${key('Q')} Smart Recommend ${hint('(🎯 find the best model for your task — questionnaire + live analysis)')}`)
|
|
759
772
|
lines.push(` ${key('G')} Cycle theme ${hint('(auto → dark → light)')}`)
|
|
760
773
|
lines.push(` ${themeColors.errorBold('I')} Feedback, bugs & requests ${hint('(📝 send anonymous feedback, bug reports, or feature requests)')}`)
|
|
@@ -770,7 +783,8 @@ export function createOverlayRenderers(state, deps) {
|
|
|
770
783
|
lines.push(` ${key('PgUp/PgDn')} Jump by page`)
|
|
771
784
|
lines.push(` ${key('Home/End')} Jump first/last row`)
|
|
772
785
|
lines.push(` ${key('Enter')} Edit key / run selected maintenance action`)
|
|
773
|
-
lines.push(` ${key('Space')} Toggle provider
|
|
786
|
+
lines.push(` ${key('Space')} Toggle selected row option (provider/theme/favorites)`)
|
|
787
|
+
lines.push(` ${key('Y')} Toggle favorites mode (global)`)
|
|
774
788
|
lines.push(` ${key('T')} Test selected provider key`)
|
|
775
789
|
lines.push(` ${key('U')} Check updates manually`)
|
|
776
790
|
lines.push(` ${key('G')} Cycle theme globally`)
|
|
@@ -1203,6 +1217,102 @@ export function createOverlayRenderers(state, deps) {
|
|
|
1203
1217
|
if (state.recommendPingTimer) { clearInterval(state.recommendPingTimer); state.recommendPingTimer = null }
|
|
1204
1218
|
}
|
|
1205
1219
|
|
|
1220
|
+
// ─── Incompatible fallback overlay ─────────────────────────────────────────
|
|
1221
|
+
// 📖 renderIncompatibleFallback shows when user presses Enter on a model that
|
|
1222
|
+
// 📖 is NOT compatible with the active tool. Two sections:
|
|
1223
|
+
// 📖 Section 1: "Switch to a compatible tool" — lists tools the model CAN run on
|
|
1224
|
+
// 📖 Section 2: "Use a similar model" — lists SWE-similar models compatible with current tool
|
|
1225
|
+
// 📖 Cursor navigates a flat list across both sections. Enter executes, Esc cancels.
|
|
1226
|
+
function renderIncompatibleFallback() {
|
|
1227
|
+
const EL = '\x1b[K'
|
|
1228
|
+
const lines = []
|
|
1229
|
+
const cursorLineByRow = {}
|
|
1230
|
+
|
|
1231
|
+
const model = state.incompatibleFallbackModel
|
|
1232
|
+
const tools = state.incompatibleFallbackTools || []
|
|
1233
|
+
const similarModels = state.incompatibleFallbackSimilarModels || []
|
|
1234
|
+
const totalItems = tools.length + similarModels.length
|
|
1235
|
+
const activeMeta = getToolMeta(state.mode)
|
|
1236
|
+
|
|
1237
|
+
lines.push(` ${chalk.cyanBright('🚀')} ${chalk.bold.cyanBright('free-coding-models')}`)
|
|
1238
|
+
lines.push(` ${chalk.bold('⚠️ Incompatible Model')}`)
|
|
1239
|
+
lines.push('')
|
|
1240
|
+
|
|
1241
|
+
if (!model) {
|
|
1242
|
+
lines.push(chalk.red(' No model data available.'))
|
|
1243
|
+
lines.push('')
|
|
1244
|
+
lines.push(chalk.dim(' Esc Close'))
|
|
1245
|
+
} else {
|
|
1246
|
+
// 📖 Header: explain why it's incompatible
|
|
1247
|
+
const tierFn = TIER_COLOR[model.tier] ?? ((text) => themeColors.text(text))
|
|
1248
|
+
lines.push(` ${themeColors.textBold(model.label)} ${tierFn(model.tier)}`)
|
|
1249
|
+
lines.push(chalk.dim(` This model cannot run on ${activeMeta.emoji} ${activeMeta.label}.`))
|
|
1250
|
+
lines.push('')
|
|
1251
|
+
|
|
1252
|
+
// 📖 Section 1: Switch to a compatible tool
|
|
1253
|
+
if (tools.length > 0) {
|
|
1254
|
+
lines.push(` ${themeColors.textBold('Switch to a compatible tool:')}`)
|
|
1255
|
+
lines.push('')
|
|
1256
|
+
|
|
1257
|
+
for (let i = 0; i < tools.length; i++) {
|
|
1258
|
+
const toolKey = tools[i]
|
|
1259
|
+
const meta = getToolMeta(toolKey)
|
|
1260
|
+
const [r, g, b] = meta.color || [200, 200, 200]
|
|
1261
|
+
const coloredLabel = chalk.rgb(r, g, b)(`${meta.emoji} ${meta.label}`)
|
|
1262
|
+
const isCursor = state.incompatibleFallbackCursor === i
|
|
1263
|
+
const bullet = isCursor ? chalk.bold.cyan(' ❯ ') : chalk.dim(' ')
|
|
1264
|
+
const row = `${bullet}${coloredLabel}`
|
|
1265
|
+
cursorLineByRow[i] = lines.length
|
|
1266
|
+
lines.push(isCursor ? themeColors.bgCursorInstall(row) : row)
|
|
1267
|
+
}
|
|
1268
|
+
lines.push('')
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
// 📖 Section 2: Use a similar model
|
|
1272
|
+
if (similarModels.length > 0) {
|
|
1273
|
+
lines.push(` ${themeColors.textBold('Or pick a similar model for')} ${activeMeta.emoji} ${themeColors.textBold(activeMeta.label + ':')}`)
|
|
1274
|
+
lines.push('')
|
|
1275
|
+
|
|
1276
|
+
for (let i = 0; i < similarModels.length; i++) {
|
|
1277
|
+
const sm = similarModels[i]
|
|
1278
|
+
const flatIdx = tools.length + i
|
|
1279
|
+
const tierFnSm = TIER_COLOR[sm.tier] ?? ((text) => themeColors.text(text))
|
|
1280
|
+
const isCursor = state.incompatibleFallbackCursor === flatIdx
|
|
1281
|
+
const bullet = isCursor ? chalk.bold.cyan(' ❯ ') : chalk.dim(' ')
|
|
1282
|
+
const sweLabel = sm.sweScore !== '-' ? `SWE ${sm.sweScore}` : 'SWE —'
|
|
1283
|
+
const row = `${bullet}${themeColors.textBold(sm.label)} ${tierFnSm(sm.tier)} ${chalk.dim(sweLabel)}`
|
|
1284
|
+
cursorLineByRow[flatIdx] = lines.length
|
|
1285
|
+
lines.push(isCursor ? themeColors.bgCursorInstall(row) : row)
|
|
1286
|
+
}
|
|
1287
|
+
lines.push('')
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
if (totalItems === 0) {
|
|
1291
|
+
lines.push(chalk.yellow(' No compatible tools or similar models found.'))
|
|
1292
|
+
lines.push('')
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
lines.push(chalk.dim(' ↑↓ Navigate • Enter Confirm • Esc Cancel'))
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
lines.push('')
|
|
1299
|
+
|
|
1300
|
+
// 📖 Scroll management — same pattern as other overlays
|
|
1301
|
+
const targetLine = cursorLineByRow[state.incompatibleFallbackCursor] ?? 0
|
|
1302
|
+
state.incompatibleFallbackScrollOffset = keepOverlayTargetVisible(
|
|
1303
|
+
state.incompatibleFallbackScrollOffset,
|
|
1304
|
+
targetLine,
|
|
1305
|
+
lines.length,
|
|
1306
|
+
state.terminalRows
|
|
1307
|
+
)
|
|
1308
|
+
const { visible, offset } = sliceOverlayLines(lines, state.incompatibleFallbackScrollOffset, state.terminalRows)
|
|
1309
|
+
state.incompatibleFallbackScrollOffset = offset
|
|
1310
|
+
|
|
1311
|
+
const tintedLines = tintOverlayLines(visible, themeColors.overlayBgSettings, state.terminalCols)
|
|
1312
|
+
const cleared = tintedLines.map(l => l + EL)
|
|
1313
|
+
return cleared.join('\n')
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1206
1316
|
return {
|
|
1207
1317
|
renderSettings,
|
|
1208
1318
|
renderInstallEndpoints,
|
|
@@ -1212,6 +1322,7 @@ export function createOverlayRenderers(state, deps) {
|
|
|
1212
1322
|
renderRecommend,
|
|
1213
1323
|
renderFeedback,
|
|
1214
1324
|
renderChangelog,
|
|
1325
|
+
renderIncompatibleFallback,
|
|
1215
1326
|
startRecommendAnalysis,
|
|
1216
1327
|
stopRecommendAnalysis,
|
|
1217
1328
|
}
|
package/src/provider-metadata.js
CHANGED
|
@@ -58,6 +58,7 @@ export const ENV_VAR_NAMES = {
|
|
|
58
58
|
cloudflare: 'CLOUDFLARE_API_TOKEN',
|
|
59
59
|
perplexity: 'PERPLEXITY_API_KEY',
|
|
60
60
|
zai: 'ZAI_API_KEY',
|
|
61
|
+
gemini: 'GEMINI_API_KEY',
|
|
61
62
|
}
|
|
62
63
|
|
|
63
64
|
// 📖 OPENCODE_MODEL_MAP: sparse table of model IDs that differ between sources.js and OpenCode's
|
|
@@ -225,4 +226,28 @@ export const PROVIDER_METADATA = {
|
|
|
225
226
|
signupHint: 'Install @mariozechner/pi-coding-agent and set ANTHROPIC_API_KEY',
|
|
226
227
|
rateLimits: 'Depends on provider subscription (e.g., Anthropic, OpenAI)',
|
|
227
228
|
},
|
|
229
|
+
rovo: {
|
|
230
|
+
label: 'Rovo Dev CLI',
|
|
231
|
+
color: chalk.rgb(148, 163, 184), // slate blue
|
|
232
|
+
signupUrl: 'https://www.atlassian.com/rovo',
|
|
233
|
+
signupHint: 'Install ACLI and run: acli rovodev auth login',
|
|
234
|
+
rateLimits: 'Free tier: 5M tokens/day (beta, requires Atlassian account)',
|
|
235
|
+
cliOnly: true,
|
|
236
|
+
},
|
|
237
|
+
gemini: {
|
|
238
|
+
label: 'Gemini CLI',
|
|
239
|
+
color: chalk.rgb(66, 165, 245), // blue
|
|
240
|
+
signupUrl: 'https://github.com/google-gemini/gemini-cli',
|
|
241
|
+
signupHint: 'Install: npm install -g @google/gemini-cli',
|
|
242
|
+
rateLimits: 'Free tier: 1,000 req/day (personal Google account, no credit card)',
|
|
243
|
+
cliOnly: true,
|
|
244
|
+
},
|
|
245
|
+
'opencode-zen': {
|
|
246
|
+
label: 'OpenCode Zen',
|
|
247
|
+
color: chalk.rgb(139, 92, 246), // violet — distinctive from other providers
|
|
248
|
+
signupUrl: 'https://opencode.ai/auth',
|
|
249
|
+
signupHint: 'Login at opencode.ai/auth to get your Zen API key',
|
|
250
|
+
rateLimits: 'Free tier models — requires OpenCode Zen API key',
|
|
251
|
+
zenOnly: true,
|
|
252
|
+
},
|
|
228
253
|
}
|
package/src/render-helpers.js
CHANGED
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
* - chalk: Terminal colors and formatting
|
|
37
37
|
* - ../src/constants.js: OVERLAY_PANEL_WIDTH, TABLE_FIXED_LINES
|
|
38
38
|
* - ../src/utils.js: sortResults
|
|
39
|
+
* - ../src/tool-metadata.js: isModelCompatibleWithTool (for compatible-first partition)
|
|
39
40
|
*
|
|
40
41
|
* ⚙️ Configuration:
|
|
41
42
|
* - OVERLAY_PANEL_WIDTH: Fixed width for overlay panels (from constants.js)
|
|
@@ -168,10 +169,22 @@ export function calculateViewport(terminalRows, scrollOffset, totalModels, extra
|
|
|
168
169
|
|
|
169
170
|
// 📖 sortResultsWithPinnedFavorites: Recommended models are pinned above favorites, favorites above non-favorites.
|
|
170
171
|
// 📖 Recommended: sorted by recommendation score (highest first).
|
|
171
|
-
// 📖 Favorites: keep insertion order (favoriteRank).
|
|
172
|
+
// 📖 Favorites: keep insertion order (favoriteRank) when pinFavorites=true.
|
|
172
173
|
// 📖 Non-favorites: active sort column/direction.
|
|
173
174
|
// 📖 Models that are both recommended AND favorite — show in recommended section.
|
|
174
|
-
|
|
175
|
+
// 📖 pinFavorites=false keeps favorites highlighted but lets normal sort/filter order apply.
|
|
176
|
+
export function sortResultsWithPinnedFavorites(results, sortColumn, sortDirection, { pinFavorites = true } = {}) {
|
|
177
|
+
if (!pinFavorites) {
|
|
178
|
+
const recommendedRows = results
|
|
179
|
+
.filter((r) => r.isRecommended)
|
|
180
|
+
.sort((a, b) => (b.recommendScore || 0) - (a.recommendScore || 0))
|
|
181
|
+
const nonRecommendedRows = sortResults(
|
|
182
|
+
results.filter((r) => !r.isRecommended),
|
|
183
|
+
sortColumn,
|
|
184
|
+
sortDirection
|
|
185
|
+
)
|
|
186
|
+
return [...recommendedRows, ...nonRecommendedRows]
|
|
187
|
+
}
|
|
175
188
|
const recommendedRows = results
|
|
176
189
|
.filter((r) => r.isRecommended && !r.isFavorite)
|
|
177
190
|
.sort((a, b) => (b.recommendScore || 0) - (a.recommendScore || 0))
|
package/src/render-table.js
CHANGED
|
@@ -13,7 +13,8 @@
|
|
|
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)
|
|
16
|
-
* -
|
|
16
|
+
* - Favorites mode hint surfaced directly in footer hints (`Y`)
|
|
17
|
+
* - High-visibility active text-filter banner with one-key clear action (`X`)
|
|
17
18
|
* - Full-width red outdated-version banner when a newer npm release is known
|
|
18
19
|
* - Distinct auth-failure vs missing-key health labels so configured providers stay honest
|
|
19
20
|
*
|
|
@@ -49,7 +50,7 @@ import { TIER_COLOR } from './tier-colors.js'
|
|
|
49
50
|
import { getAvg, getVerdict, getUptime, getStabilityScore, getVersionStatusInfo } from './utils.js'
|
|
50
51
|
import { usagePlaceholderForProvider } from './ping.js'
|
|
51
52
|
import { calculateViewport, sortResultsWithPinnedFavorites, padEndDisplay, displayWidth } from './render-helpers.js'
|
|
52
|
-
import { getToolMeta } from './tool-metadata.js'
|
|
53
|
+
import { getToolMeta, TOOL_METADATA, TOOL_MODE_ORDER, COMPAT_COLUMN_SLOTS, getCompatibleTools, isModelCompatibleWithTool } from './tool-metadata.js'
|
|
53
54
|
import { getColumnSpacing } from './ui-config.js'
|
|
54
55
|
|
|
55
56
|
const require = createRequire(import.meta.url)
|
|
@@ -66,7 +67,7 @@ export const PROVIDER_COLOR = new Proxy({}, {
|
|
|
66
67
|
})
|
|
67
68
|
|
|
68
69
|
// ─── renderTable: mode param controls footer hint text (opencode vs openclaw) ─────────
|
|
69
|
-
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, legacyStatus = null, pingMode = 'normal', pingModeSource = 'auto', hideUnconfiguredModels = false, widthWarningStartedAt = null, widthWarningDismissed = false, widthWarningShowCount = 0, settingsUpdateState = 'idle', settingsUpdateLatestVersion = null, legacyFlag = false, startupLatestVersion = null, versionAlertsEnabled = true) {
|
|
70
|
+
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, legacyStatus = null, pingMode = 'normal', pingModeSource = 'auto', hideUnconfiguredModels = false, widthWarningStartedAt = null, widthWarningDismissed = false, widthWarningShowCount = 0, settingsUpdateState = 'idle', settingsUpdateLatestVersion = null, legacyFlag = false, startupLatestVersion = null, versionAlertsEnabled = true, favoritesPinnedAndSticky = false, customTextFilter = null) {
|
|
70
71
|
// 📖 Filter out hidden models for display
|
|
71
72
|
const visibleResults = results.filter(r => !r.hidden)
|
|
72
73
|
|
|
@@ -108,9 +109,12 @@ export function renderTable(results, pendingPings, frame, cursor = null, sortCol
|
|
|
108
109
|
|
|
109
110
|
// 📖 Tool badge keeps the active launch target visible in the header, so the
|
|
110
111
|
// 📖 footer no longer needs a redundant Enter action or mode toggle reminder.
|
|
112
|
+
// 📖 Tool name is colored with its unique tool color for quick recognition.
|
|
111
113
|
const toolMeta = getToolMeta(mode)
|
|
112
114
|
const toolBadgeColor = mode === 'openclaw' ? themeColors.warningBold : themeColors.accentBold
|
|
113
|
-
const
|
|
115
|
+
const toolColor = toolMeta.color ? chalk.rgb(...toolMeta.color) : toolBadgeColor
|
|
116
|
+
const modeBadge = toolBadgeColor(' [ ') + themeColors.hotkey('Z') + toolBadgeColor(' Tool : ') + toolColor.bold(`${toolMeta.emoji} ${toolMeta.label}`) + toolBadgeColor(' ]')
|
|
117
|
+
|
|
114
118
|
const activeHeaderBadge = (text, bg) => themeColors.badge(text, bg, getReadableTextRgb(bg))
|
|
115
119
|
const versionStatus = getVersionStatusInfo(settingsUpdateState, settingsUpdateLatestVersion, startupLatestVersion, versionAlertsEnabled)
|
|
116
120
|
|
|
@@ -156,6 +160,7 @@ export function renderTable(results, pendingPings, frame, cursor = null, sortCol
|
|
|
156
160
|
const W_STATUS = 18
|
|
157
161
|
const W_VERDICT = 14
|
|
158
162
|
const W_UPTIME = 6
|
|
163
|
+
const W_COMPAT = 22 // 📖 "Compatible with" column — 11 emoji slots (10×2 + 1×1 for π + 1 padding)
|
|
159
164
|
// const W_TOKENS = 7 // Used column removed
|
|
160
165
|
// const W_USAGE = 7 // Usage column removed
|
|
161
166
|
const MIN_TABLE_WIDTH = WIDTH_WARNING_MIN_COLS
|
|
@@ -175,6 +180,7 @@ export function renderTable(results, pendingPings, frame, cursor = null, sortCol
|
|
|
175
180
|
let showUptime = true
|
|
176
181
|
let showTier = true
|
|
177
182
|
let showStability = true
|
|
183
|
+
let showCompat = true // 📖 "Compatible with" column — hidden on narrow terminals
|
|
178
184
|
let isCompact = false
|
|
179
185
|
|
|
180
186
|
if (terminalCols > 0) {
|
|
@@ -186,6 +192,7 @@ export function renderTable(results, pendingPings, frame, cursor = null, sortCol
|
|
|
186
192
|
cols.push(W_SWE, W_CTX, W_MODEL, wSource, wPing, wAvg, wStatus, W_VERDICT)
|
|
187
193
|
if (showStability) cols.push(wStab)
|
|
188
194
|
if (showUptime) cols.push(W_UPTIME)
|
|
195
|
+
if (showCompat) cols.push(W_COMPAT)
|
|
189
196
|
return ROW_MARGIN + cols.reduce((a, b) => a + b, 0) + (cols.length - 1) * SEP_W
|
|
190
197
|
}
|
|
191
198
|
|
|
@@ -199,6 +206,7 @@ export function renderTable(results, pendingPings, frame, cursor = null, sortCol
|
|
|
199
206
|
wStatus = 13 // Health truncated after 6 chars + '…'
|
|
200
207
|
}
|
|
201
208
|
// 📖 Steps 2–5: Progressive column hiding (least useful first)
|
|
209
|
+
if (calcWidth() > terminalCols) showCompat = false
|
|
202
210
|
if (calcWidth() > terminalCols) showRank = false
|
|
203
211
|
if (calcWidth() > terminalCols) showUptime = false
|
|
204
212
|
if (calcWidth() > terminalCols) showTier = false
|
|
@@ -207,7 +215,7 @@ export function renderTable(results, pendingPings, frame, cursor = null, sortCol
|
|
|
207
215
|
const warningDurationMs = 2_000
|
|
208
216
|
const elapsed = widthWarningStartedAt ? Math.max(0, Date.now() - widthWarningStartedAt) : warningDurationMs
|
|
209
217
|
const remainingMs = Math.max(0, warningDurationMs - elapsed)
|
|
210
|
-
const showWidthWarning = terminalCols > 0 && terminalCols < MIN_TABLE_WIDTH && !widthWarningDismissed &&
|
|
218
|
+
const showWidthWarning = terminalCols > 0 && terminalCols < MIN_TABLE_WIDTH && !widthWarningDismissed && remainingMs > 0
|
|
211
219
|
|
|
212
220
|
if (showWidthWarning) {
|
|
213
221
|
const lines = []
|
|
@@ -237,7 +245,9 @@ export function renderTable(results, pendingPings, frame, cursor = null, sortCol
|
|
|
237
245
|
}
|
|
238
246
|
|
|
239
247
|
// 📖 Sort models using the shared helper
|
|
240
|
-
const sorted = sortResultsWithPinnedFavorites(visibleResults, sortColumn, sortDirection
|
|
248
|
+
const sorted = sortResultsWithPinnedFavorites(visibleResults, sortColumn, sortDirection, {
|
|
249
|
+
pinFavorites: favoritesPinnedAndSticky,
|
|
250
|
+
})
|
|
241
251
|
|
|
242
252
|
const lines = [
|
|
243
253
|
` ${themeColors.accentBold(`🚀 free-coding-models v${LOCAL_VERSION}`)}${modeBadge}${pingControlBadge}${tierBadge}${originBadge}${chalk.reset('')} ` +
|
|
@@ -316,6 +326,14 @@ export function renderTable(results, pendingPings, frame, cursor = null, sortCol
|
|
|
316
326
|
const padding = ' '.repeat(Math.max(0, W_UPTIME - plain.length))
|
|
317
327
|
return themeColors.hotkey('U') + themeColors.dim('p%' + padding)
|
|
318
328
|
})()
|
|
329
|
+
// 📖 "Compatible with" column header — show all tool emojis in their colors as the header
|
|
330
|
+
const compatHeaderEmojis = COMPAT_COLUMN_SLOTS.map(slot => {
|
|
331
|
+
return chalk.rgb(...slot.color)(slot.emoji)
|
|
332
|
+
}).join('')
|
|
333
|
+
// 📖 padEndDisplay accounts for emoji widths (most are 2-wide, π is 1-wide)
|
|
334
|
+
const compatHeaderRaw = COMPAT_COLUMN_SLOTS.reduce((w, slot) => w + displayWidth(slot.emoji), 0)
|
|
335
|
+
const compatHeaderPad = Math.max(0, W_COMPAT - compatHeaderRaw)
|
|
336
|
+
const compatH_c = compatHeaderEmojis + ' '.repeat(compatHeaderPad)
|
|
319
337
|
// 📖 Usage column removed from UI – no header or separator for it.
|
|
320
338
|
// 📖 Header row: conditionally include columns based on responsive visibility
|
|
321
339
|
const headerParts = []
|
|
@@ -324,6 +342,7 @@ export function renderTable(results, pendingPings, frame, cursor = null, sortCol
|
|
|
324
342
|
headerParts.push(sweH_c, ctxH_c, modelH_c, originH_c, pingH_c, avgH_c, healthH_c, verdictH_c)
|
|
325
343
|
if (showStability) headerParts.push(stabH_c)
|
|
326
344
|
if (showUptime) headerParts.push(uptimeH_c)
|
|
345
|
+
if (showCompat) headerParts.push(compatH_c)
|
|
327
346
|
lines.push(' ' + headerParts.join(COL_SEP))
|
|
328
347
|
|
|
329
348
|
|
|
@@ -339,6 +358,7 @@ export function renderTable(results, pendingPings, frame, cursor = null, sortCol
|
|
|
339
358
|
}
|
|
340
359
|
|
|
341
360
|
// 📖 Viewport clipping: only render models that fit on screen
|
|
361
|
+
const hasCustomFilter = typeof customTextFilter === 'string' && customTextFilter.trim().length > 0
|
|
342
362
|
const extraFooterLines = versionStatus.isOutdated ? 1 : 0
|
|
343
363
|
const vp = calculateViewport(terminalRows, scrollOffset, sorted.length, extraFooterLines)
|
|
344
364
|
const paintSweScore = (score, paddedText) => {
|
|
@@ -581,6 +601,31 @@ export function renderTable(results, pendingPings, frame, cursor = null, sortCol
|
|
|
581
601
|
const sourceCursorText = providerDisplay.padEnd(wSource)
|
|
582
602
|
const sourceCell = isCursor ? themeColors.provider(r.providerKey, sourceCursorText, { bold: true }) : source
|
|
583
603
|
|
|
604
|
+
// 📖 "Compatible with" column — show colored emojis for compatible tools
|
|
605
|
+
// 📖 Each slot in COMPAT_COLUMN_SLOTS maps to one or more tool keys.
|
|
606
|
+
// 📖 OpenCode CLI + Desktop are merged into a single 📦 slot.
|
|
607
|
+
let compatCell = ''
|
|
608
|
+
if (showCompat) {
|
|
609
|
+
const compatTools = getCompatibleTools(r.providerKey)
|
|
610
|
+
let compatDisplayWidth = 0
|
|
611
|
+
const emojiCells = COMPAT_COLUMN_SLOTS.map(slot => {
|
|
612
|
+
const isCompat = slot.toolKeys.some(tk => compatTools.includes(tk))
|
|
613
|
+
const ew = displayWidth(slot.emoji)
|
|
614
|
+
compatDisplayWidth += isCompat ? ew : ew
|
|
615
|
+
if (isCompat) {
|
|
616
|
+
return chalk.rgb(...slot.color)(slot.emoji)
|
|
617
|
+
}
|
|
618
|
+
// 📖 Replace incompatible emoji with dim spaces matching its display width
|
|
619
|
+
return themeColors.dim(' '.repeat(ew))
|
|
620
|
+
}).join('')
|
|
621
|
+
// 📖 Pad to W_COMPAT — account for actual emoji display widths
|
|
622
|
+
const extraPad = Math.max(0, W_COMPAT - compatDisplayWidth)
|
|
623
|
+
compatCell = emojiCells + ' '.repeat(extraPad)
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// 📖 Check if this model is incompatible with the active tool mode
|
|
627
|
+
const isIncompatible = !isModelCompatibleWithTool(r.providerKey, mode)
|
|
628
|
+
|
|
584
629
|
// 📖 Usage column removed from UI – no usage data displayed.
|
|
585
630
|
// (We keep the logic but do not render it.)
|
|
586
631
|
const usageCell = ''
|
|
@@ -592,10 +637,15 @@ export function renderTable(results, pendingPings, frame, cursor = null, sortCol
|
|
|
592
637
|
rowParts.push(sweCell, ctxCell, nameCell, sourceCell, pingCell, avgCell, status, speedCell)
|
|
593
638
|
if (showStability) rowParts.push(stabCell)
|
|
594
639
|
if (showUptime) rowParts.push(uptimeCell)
|
|
640
|
+
if (showCompat) rowParts.push(compatCell)
|
|
595
641
|
const row = ' ' + rowParts.join(COL_SEP)
|
|
596
642
|
|
|
597
643
|
if (isCursor) {
|
|
598
644
|
lines.push(themeColors.bgModelCursor(row))
|
|
645
|
+
} else if (isIncompatible) {
|
|
646
|
+
// 📖 Dark red background for models incompatible with the active tool mode.
|
|
647
|
+
// 📖 This visually warns the user that selecting this model won't work with their current tool.
|
|
648
|
+
lines.push(chalk.bgRgb(60, 15, 15).rgb(180, 130, 130)(row))
|
|
599
649
|
} else if (r.isRecommended) {
|
|
600
650
|
// 📖 Medium green background for recommended models (distinguishable from favorites)
|
|
601
651
|
lines.push(themeColors.bgModelRecommended(row))
|
|
@@ -618,10 +668,14 @@ export function renderTable(results, pendingPings, frame, cursor = null, sortCol
|
|
|
618
668
|
// 📖 states are obvious even when the user misses the smaller header badges.
|
|
619
669
|
const configuredBadgeBg = getTheme() === 'dark' ? [52, 120, 88] : [195, 234, 206]
|
|
620
670
|
const activeHotkey = (keyLabel, text, bg) => themeColors.badge(`${keyLabel}${text}`, bg, getReadableTextRgb(bg))
|
|
671
|
+
const favoritesModeBg = favoritesPinnedAndSticky ? [157, 122, 48] : [95, 95, 95]
|
|
672
|
+
const favoritesModeLabel = favoritesPinnedAndSticky ? ' Favorites Pinned' : ' Favorites Normal'
|
|
621
673
|
// 📖 Line 1: core navigation + filtering shortcuts
|
|
622
674
|
lines.push(
|
|
623
675
|
' ' + hotkey('F', ' Toggle Favorite') +
|
|
624
676
|
themeColors.dim(` • `) +
|
|
677
|
+
activeHotkey('Y', favoritesModeLabel, favoritesModeBg) +
|
|
678
|
+
themeColors.dim(` • `) +
|
|
625
679
|
(tierFilterMode > 0
|
|
626
680
|
? activeHotkey('T', ` Tier (${activeTierLabel})`, getTierRgb(activeTierLabel))
|
|
627
681
|
: hotkey('T', ' Tier')) +
|
|
@@ -673,9 +727,29 @@ export function renderTable(results, pendingPings, frame, cursor = null, sortCol
|
|
|
673
727
|
lines.push(chalk.bgRed.white.bold(paddedBanner))
|
|
674
728
|
}
|
|
675
729
|
|
|
676
|
-
// 📖 Final footer line: changelog
|
|
730
|
+
// 📖 Final footer line: changelog + optional active text-filter badge + exit hint.
|
|
731
|
+
let filterBadge = ''
|
|
732
|
+
if (hasCustomFilter) {
|
|
733
|
+
const normalizedFilter = customTextFilter.trim().replace(/\s+/g, ' ')
|
|
734
|
+
const filterPrefix = 'X Disable filter: "'
|
|
735
|
+
const filterSuffix = '"'
|
|
736
|
+
const separatorPlain = ' • '
|
|
737
|
+
const baseFooterPlain = ' N Changelog' + separatorPlain + 'Ctrl+C Exit'
|
|
738
|
+
const baseBadgeWidth = displayWidth(` ${filterPrefix}${filterSuffix} `)
|
|
739
|
+
const availableFilterWidth = terminalCols > 0
|
|
740
|
+
? Math.max(8, terminalCols - displayWidth(baseFooterPlain) - displayWidth(separatorPlain) - baseBadgeWidth)
|
|
741
|
+
: normalizedFilter.length
|
|
742
|
+
const visibleFilter = normalizedFilter.length > availableFilterWidth
|
|
743
|
+
? `${normalizedFilter.slice(0, Math.max(3, availableFilterWidth - 3))}...`
|
|
744
|
+
: normalizedFilter
|
|
745
|
+
filterBadge = chalk.bgYellow.black.bold(` ${filterPrefix}${visibleFilter}${filterSuffix} `)
|
|
746
|
+
}
|
|
747
|
+
|
|
677
748
|
lines.push(
|
|
678
749
|
' ' + themeColors.hotkey('N') + themeColors.dim(' Changelog') +
|
|
750
|
+
(filterBadge
|
|
751
|
+
? themeColors.dim(' • ') + filterBadge
|
|
752
|
+
: '') +
|
|
679
753
|
themeColors.dim(' • ') +
|
|
680
754
|
themeColors.dim('Ctrl+C Exit')
|
|
681
755
|
)
|
package/src/theme.js
CHANGED
|
@@ -141,6 +141,9 @@ const PROVIDER_PALETTES = {
|
|
|
141
141
|
qwen: [255, 213, 128],
|
|
142
142
|
zai: [150, 208, 255],
|
|
143
143
|
iflow: [211, 229, 101],
|
|
144
|
+
rovo: [148, 163, 184],
|
|
145
|
+
gemini: [66, 165, 245],
|
|
146
|
+
'opencode-zen': [185, 146, 255],
|
|
144
147
|
},
|
|
145
148
|
light: {
|
|
146
149
|
nvidia: [0, 126, 73],
|
|
@@ -163,6 +166,9 @@ const PROVIDER_PALETTES = {
|
|
|
163
166
|
qwen: [132, 89, 0],
|
|
164
167
|
zai: [0, 104, 171],
|
|
165
168
|
iflow: [107, 130, 0],
|
|
169
|
+
rovo: [90, 100, 126],
|
|
170
|
+
gemini: [15, 97, 175],
|
|
171
|
+
'opencode-zen': [108, 58, 183],
|
|
166
172
|
},
|
|
167
173
|
}
|
|
168
174
|
|
package/src/tool-bootstrap.js
CHANGED
|
@@ -217,6 +217,28 @@ export const TOOL_BOOTSTRAP_METADATA = {
|
|
|
217
217
|
},
|
|
218
218
|
},
|
|
219
219
|
},
|
|
220
|
+
rovo: {
|
|
221
|
+
binary: 'acli',
|
|
222
|
+
docsUrl: 'https://support.atlassian.com/rovo/docs/install-and-run-rovo-dev-cli-on-your-device/',
|
|
223
|
+
install: {
|
|
224
|
+
default: {
|
|
225
|
+
shellCommand: 'npm install -g acli',
|
|
226
|
+
summary: 'Rovo Dev CLI requires ACLI installation. Visit the documentation for platform-specific instructions.',
|
|
227
|
+
note: 'Rovo is an Atlassian tool that requires an Atlassian account with Rovo Dev activated.',
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
gemini: {
|
|
232
|
+
binary: 'gemini',
|
|
233
|
+
docsUrl: 'https://github.com/google-gemini/gemini-cli',
|
|
234
|
+
install: {
|
|
235
|
+
default: {
|
|
236
|
+
shellCommand: 'npm install -g @google/gemini-cli',
|
|
237
|
+
summary: 'Install Gemini CLI globally via npm.',
|
|
238
|
+
note: 'After installation, run `gemini` to authenticate with your Google account.',
|
|
239
|
+
},
|
|
240
|
+
},
|
|
241
|
+
},
|
|
220
242
|
}
|
|
221
243
|
|
|
222
244
|
export function getToolBootstrapMeta(mode) {
|
package/src/tool-launchers.js
CHANGED
|
@@ -41,7 +41,7 @@ import { sources } from '../sources.js'
|
|
|
41
41
|
import { PROVIDER_COLOR } from './render-table.js'
|
|
42
42
|
import { getApiKey } from './config.js'
|
|
43
43
|
import { ENV_VAR_NAMES, isWindows } from './provider-metadata.js'
|
|
44
|
-
import { getToolMeta } from './tool-metadata.js'
|
|
44
|
+
import { getToolMeta, TOOL_METADATA } from './tool-metadata.js'
|
|
45
45
|
import { PROVIDER_METADATA } from './provider-metadata.js'
|
|
46
46
|
import { resolveToolBinaryPath } from './tool-bootstrap.js'
|
|
47
47
|
|
|
@@ -378,6 +378,57 @@ function writeOpenHandsEnv(model, apiKey, baseUrl, paths = getDefaultToolPaths()
|
|
|
378
378
|
return { filePath, backupPath }
|
|
379
379
|
}
|
|
380
380
|
|
|
381
|
+
/**
|
|
382
|
+
* 📖 writeRovoConfig - Configure Rovo Dev CLI model selection
|
|
383
|
+
*
|
|
384
|
+
* Rovo Dev CLI uses ~/.rovodev/config.yml for configuration.
|
|
385
|
+
* We write the model ID to the config file before launching.
|
|
386
|
+
*
|
|
387
|
+
* @param {Object} model - Selected model with modelId
|
|
388
|
+
* @param {string} configPath - Path to Rovo config file
|
|
389
|
+
* @returns {{ filePath: string, backupPath: string | null }}
|
|
390
|
+
*/
|
|
391
|
+
function writeRovoConfig(model, configPath = join(homedir(), '.rovodev', 'config.yml')) {
|
|
392
|
+
const backupPath = backupIfExists(configPath)
|
|
393
|
+
const config = {
|
|
394
|
+
agent: {
|
|
395
|
+
modelId: model.modelId,
|
|
396
|
+
},
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
ensureDir(configPath)
|
|
400
|
+
writeFileSync(configPath, `agent:\n modelId: "${model.modelId}"\n`)
|
|
401
|
+
return { filePath: configPath, backupPath }
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* 📖 buildGeminiEnv - Build environment variables for Gemini CLI
|
|
406
|
+
*
|
|
407
|
+
* Gemini CLI supports OpenAI-compatible APIs via environment variables:
|
|
408
|
+
* - GEMINI_API_BASE_URL: Custom API endpoint
|
|
409
|
+
* - GEMINI_API_KEY: API key for custom endpoint
|
|
410
|
+
*
|
|
411
|
+
* @param {Object} model - Selected model with providerKey
|
|
412
|
+
* @param {Object} config - Full app config
|
|
413
|
+
* @param {Object} options - Env options
|
|
414
|
+
* @returns {NodeJS.ProcessEnv}
|
|
415
|
+
*/
|
|
416
|
+
function buildGeminiEnv(model, config, options = {}) {
|
|
417
|
+
const providerKey = model.providerKey || 'gemini'
|
|
418
|
+
const apiKey = getApiKey(config, providerKey)
|
|
419
|
+
const baseUrl = getProviderBaseUrl(providerKey)
|
|
420
|
+
|
|
421
|
+
const env = cloneInheritedEnv(process.env, SANITIZED_TOOL_ENV_KEYS)
|
|
422
|
+
|
|
423
|
+
// If we have a custom API key and base URL, configure OpenAI-compatible mode
|
|
424
|
+
if (apiKey && baseUrl && options.includeProviderEnv) {
|
|
425
|
+
env.GEMINI_API_BASE_URL = baseUrl
|
|
426
|
+
env.GEMINI_API_KEY = apiKey
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
return env
|
|
430
|
+
}
|
|
431
|
+
|
|
381
432
|
function printConfigArtifacts(toolName, artifacts = []) {
|
|
382
433
|
for (const artifact of artifacts) {
|
|
383
434
|
if (!artifact?.path) continue
|
|
@@ -420,7 +471,9 @@ export function prepareExternalToolLaunch(mode, model, config, options = {}) {
|
|
|
420
471
|
inheritedEnv: options.inheritedEnv,
|
|
421
472
|
})
|
|
422
473
|
|
|
423
|
-
|
|
474
|
+
const isCliOnlyTool = TOOL_METADATA[mode]?.cliOnly === true
|
|
475
|
+
|
|
476
|
+
if (!apiKey && mode !== 'amp' && !isCliOnlyTool) {
|
|
424
477
|
const providerRgb = PROVIDER_COLOR[model.providerKey] ?? [105, 190, 245]
|
|
425
478
|
const providerName = sources[model.providerKey]?.name || model.providerKey
|
|
426
479
|
const coloredProviderName = chalk.bold.rgb(...providerRgb)(providerName)
|
|
@@ -544,6 +597,34 @@ export function prepareExternalToolLaunch(mode, model, config, options = {}) {
|
|
|
544
597
|
}
|
|
545
598
|
}
|
|
546
599
|
|
|
600
|
+
if (mode === 'rovo') {
|
|
601
|
+
const result = writeRovoConfig(model, join(homedir(), '.rovodev', 'config.yml'), paths)
|
|
602
|
+
console.log(chalk.dim(` 📖 Rovo Dev CLI configured with model: ${model.modelId}`))
|
|
603
|
+
return {
|
|
604
|
+
command: 'acli',
|
|
605
|
+
args: ['rovodev', 'run'],
|
|
606
|
+
env,
|
|
607
|
+
apiKey: null,
|
|
608
|
+
baseUrl: null,
|
|
609
|
+
meta,
|
|
610
|
+
configArtifacts: [{ path: result.filePath, backupPath: result.backupPath, label: 'config' }],
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
if (mode === 'gemini') {
|
|
615
|
+
const geminiEnv = buildGeminiEnv(model, config, { includeProviderEnv: options.includeProviderEnv })
|
|
616
|
+
console.log(chalk.dim(` 📖 Gemini CLI will use model: ${model.modelId}`))
|
|
617
|
+
return {
|
|
618
|
+
command: 'gemini',
|
|
619
|
+
args: [],
|
|
620
|
+
env: { ...env, ...geminiEnv },
|
|
621
|
+
apiKey: geminiEnv.GEMINI_API_KEY || null,
|
|
622
|
+
baseUrl: geminiEnv.GEMINI_API_BASE_URL || null,
|
|
623
|
+
meta,
|
|
624
|
+
configArtifacts: [],
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
547
628
|
return {
|
|
548
629
|
blocked: true,
|
|
549
630
|
exitCode: 1,
|
|
@@ -598,6 +679,16 @@ export async function startExternalTool(mode, model, config) {
|
|
|
598
679
|
return spawnCommand(resolveLaunchCommand(mode, launchPlan.command), launchPlan.args, launchPlan.env)
|
|
599
680
|
}
|
|
600
681
|
|
|
682
|
+
if (mode === 'rovo') {
|
|
683
|
+
console.log(chalk.dim(` 📖 Launching Rovo Dev CLI in interactive mode...`))
|
|
684
|
+
return spawnCommand(resolveLaunchCommand(mode, launchPlan.command), launchPlan.args, launchPlan.env)
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
if (mode === 'gemini') {
|
|
688
|
+
console.log(chalk.dim(` 📖 Launching Gemini CLI...`))
|
|
689
|
+
return spawnCommand(resolveLaunchCommand(mode, launchPlan.command), launchPlan.args, launchPlan.env)
|
|
690
|
+
}
|
|
691
|
+
|
|
601
692
|
console.log(chalk.red(` X Unsupported external tool mode: ${mode}`))
|
|
602
693
|
return 1
|
|
603
694
|
}
|