free-coding-models 0.3.17 β 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 +13 -0
- package/README.md +9 -1
- package/package.json +1 -1
- package/src/app.js +18 -2
- package/src/config.js +3 -3
- package/src/key-handler.js +177 -46
- package/src/openclaw.js +39 -5
- package/src/opencode.js +2 -1
- package/src/overlays.js +300 -207
- package/src/render-helpers.js +1 -1
- package/src/render-table.js +140 -177
- package/src/theme.js +291 -43
- 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/overlays.js
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
|
|
23
23
|
import { loadChangelog } from './changelog-loader.js'
|
|
24
24
|
import { buildCliHelpLines } from './cli-help.js'
|
|
25
|
-
import { themeColors } from './theme.js'
|
|
25
|
+
import { themeColors, getThemeStatusLabel, getProviderRgb } from './theme.js'
|
|
26
26
|
|
|
27
27
|
export function createOverlayRenderers(state, deps) {
|
|
28
28
|
const {
|
|
@@ -54,8 +54,13 @@ export function createOverlayRenderers(state, deps) {
|
|
|
54
54
|
getInstallTargetModes,
|
|
55
55
|
getProviderCatalogModels,
|
|
56
56
|
getToolMeta,
|
|
57
|
+
getToolInstallPlan,
|
|
58
|
+
padEndDisplay,
|
|
57
59
|
} = deps
|
|
58
60
|
|
|
61
|
+
const bullet = (isCursor) => (isCursor ? themeColors.accentBold(' β― ') : themeColors.dim(' '))
|
|
62
|
+
const activeThemeSetting = () => state.config.settings?.theme || 'auto'
|
|
63
|
+
|
|
59
64
|
// π Wrap plain diagnostic text so long Settings messages stay readable inside
|
|
60
65
|
// π the overlay instead of turning into one truncated red line.
|
|
61
66
|
// π Uses 100% of terminal width minus padding for better readability.
|
|
@@ -88,25 +93,26 @@ export function createOverlayRenderers(state, deps) {
|
|
|
88
93
|
const providerKeys = Object.keys(sources)
|
|
89
94
|
const updateRowIdx = providerKeys.length
|
|
90
95
|
const widthWarningRowIdx = updateRowIdx + 1
|
|
91
|
-
const
|
|
96
|
+
const themeRowIdx = widthWarningRowIdx + 1
|
|
97
|
+
const cleanupLegacyProxyRowIdx = themeRowIdx + 1
|
|
92
98
|
const changelogViewRowIdx = cleanupLegacyProxyRowIdx + 1
|
|
93
99
|
const EL = '\x1b[K'
|
|
94
100
|
const lines = []
|
|
95
101
|
const cursorLineByRow = {}
|
|
96
102
|
|
|
97
103
|
// π Branding header
|
|
98
|
-
lines.push(` ${
|
|
99
|
-
lines.push(` ${
|
|
104
|
+
lines.push(` ${themeColors.accent('π')} ${themeColors.accentBold('free-coding-models')} ${themeColors.dim(`v${LOCAL_VERSION}`)}`)
|
|
105
|
+
lines.push(` ${themeColors.textBold('β Settings')}`)
|
|
100
106
|
|
|
101
107
|
if (state.settingsErrorMsg) {
|
|
102
|
-
lines.push(` ${
|
|
108
|
+
lines.push(` ${themeColors.errorBold(state.settingsErrorMsg)}`)
|
|
103
109
|
lines.push('')
|
|
104
110
|
}
|
|
105
111
|
|
|
106
|
-
lines.push(` ${
|
|
112
|
+
lines.push(` ${themeColors.textBold('π§© Providers')}`)
|
|
107
113
|
// π Dynamic separator line using 100% terminal width
|
|
108
114
|
const separatorWidth = Math.max(20, state.terminalCols - 10)
|
|
109
|
-
lines.push(` ${
|
|
115
|
+
lines.push(` ${themeColors.dim(' ' + 'β'.repeat(separatorWidth))}`)
|
|
110
116
|
lines.push('')
|
|
111
117
|
|
|
112
118
|
for (let i = 0; i < providerKeys.length; i++) {
|
|
@@ -124,42 +130,41 @@ export function createOverlayRenderers(state, deps) {
|
|
|
124
130
|
let keyDisplay
|
|
125
131
|
if ((state.settingsEditMode || state.settingsAddKeyMode) && isCursor) {
|
|
126
132
|
// π Inline editing/adding: show typed buffer with cursor indicator
|
|
127
|
-
const modePrefix = state.settingsAddKeyMode ?
|
|
128
|
-
keyDisplay =
|
|
133
|
+
const modePrefix = state.settingsAddKeyMode ? themeColors.dim('[+] ') : ''
|
|
134
|
+
keyDisplay = themeColors.accentBold(`${modePrefix}${state.settingsEditBuffer || ''}β`)
|
|
129
135
|
} else if (keyCount > 0) {
|
|
130
136
|
// π Show the primary (first/string) key masked + count indicator for extras
|
|
131
137
|
const primaryKey = allKeys[0]
|
|
132
138
|
const visible = primaryKey.slice(-4)
|
|
133
139
|
const masked = 'β’'.repeat(Math.min(16, Math.max(4, primaryKey.length - 4)))
|
|
134
|
-
const keyMasked =
|
|
135
|
-
const extra = keyCount > 1 ?
|
|
140
|
+
const keyMasked = themeColors.dim(masked + visible)
|
|
141
|
+
const extra = keyCount > 1 ? themeColors.info(` (+${keyCount - 1} more)`) : ''
|
|
136
142
|
keyDisplay = keyMasked + extra
|
|
137
143
|
} else {
|
|
138
|
-
keyDisplay =
|
|
144
|
+
keyDisplay = themeColors.dim('(no key set)')
|
|
139
145
|
}
|
|
140
146
|
|
|
141
147
|
// π Test result badge
|
|
142
148
|
const testResult = state.settingsTestResults[pk]
|
|
143
149
|
// π Default badge reflects configuration first: a saved key should look
|
|
144
150
|
// π ready to test even before the user has run the probe once.
|
|
145
|
-
let testBadge = keyCount > 0 ?
|
|
146
|
-
if (testResult === 'pending') testBadge =
|
|
147
|
-
else if (testResult === 'ok') testBadge =
|
|
148
|
-
else if (testResult === 'missing_key') testBadge =
|
|
149
|
-
else if (testResult === 'auth_error') testBadge =
|
|
150
|
-
else if (testResult === 'rate_limited') testBadge =
|
|
151
|
-
else if (testResult === 'no_callable_model') testBadge = chalk.
|
|
152
|
-
else if (testResult === 'fail') testBadge =
|
|
151
|
+
let testBadge = keyCount > 0 ? themeColors.info('[Test]') : themeColors.dim('[Missing Key π]')
|
|
152
|
+
if (testResult === 'pending') testBadge = themeColors.warning('[Testingβ¦]')
|
|
153
|
+
else if (testResult === 'ok') testBadge = themeColors.successBold('[Test β
]')
|
|
154
|
+
else if (testResult === 'missing_key') testBadge = themeColors.dim('[Missing Key π]')
|
|
155
|
+
else if (testResult === 'auth_error') testBadge = themeColors.error('[Auth β]')
|
|
156
|
+
else if (testResult === 'rate_limited') testBadge = themeColors.warning('[Rate limit β³]')
|
|
157
|
+
else if (testResult === 'no_callable_model') testBadge = chalk.rgb(...getProviderRgb('openrouter'))('[No model β ]')
|
|
158
|
+
else if (testResult === 'fail') testBadge = themeColors.error('[Test β]')
|
|
153
159
|
// π No truncation of rate limits - overlay now uses 100% terminal width
|
|
154
|
-
const rateSummary =
|
|
160
|
+
const rateSummary = themeColors.dim(meta.rateLimits || 'No limit info')
|
|
155
161
|
|
|
156
|
-
const enabledBadge = enabled ?
|
|
162
|
+
const enabledBadge = enabled ? themeColors.successBold('β
') : themeColors.errorBold('β')
|
|
157
163
|
// π Color provider names the same way as in the main table
|
|
158
164
|
const providerRgb = PROVIDER_COLOR[pk] ?? [105, 190, 245]
|
|
159
165
|
const providerName = chalk.bold.rgb(...providerRgb)((meta.label || src.name || pk).slice(0, 22).padEnd(22))
|
|
160
|
-
const bullet = isCursor ? chalk.bold.cyan(' β― ') : chalk.dim(' ')
|
|
161
166
|
|
|
162
|
-
const row = `${bullet}[ ${enabledBadge} ] ${providerName} ${keyDisplay
|
|
167
|
+
const row = `${bullet(isCursor)}[ ${enabledBadge} ] ${providerName} ${padEndDisplay(keyDisplay, 30)} ${testBadge} ${rateSummary}`
|
|
163
168
|
cursorLineByRow[i] = lines.length
|
|
164
169
|
lines.push(isCursor ? themeColors.bgCursor(row) : row)
|
|
165
170
|
}
|
|
@@ -170,73 +175,74 @@ export function createOverlayRenderers(state, deps) {
|
|
|
170
175
|
const selectedMeta = PROVIDER_METADATA[selectedProviderKey] || {}
|
|
171
176
|
if (selectedSource && state.settingsCursor < providerKeys.length) {
|
|
172
177
|
const selectedKey = getApiKey(state.config, selectedProviderKey)
|
|
173
|
-
const setupStatus = selectedKey ?
|
|
178
|
+
const setupStatus = selectedKey ? themeColors.success('API key detected β
') : themeColors.warning('API key missing β ')
|
|
174
179
|
// π Color the provider name in the setup instructions header
|
|
175
180
|
const selectedProviderRgb = PROVIDER_COLOR[selectedProviderKey] ?? [105, 190, 245]
|
|
176
181
|
const coloredProviderName = chalk.bold.rgb(...selectedProviderRgb)(selectedMeta.label || selectedSource.name || selectedProviderKey)
|
|
177
|
-
lines.push(` ${
|
|
178
|
-
lines.push(
|
|
179
|
-
lines.push(
|
|
180
|
-
lines.push(
|
|
182
|
+
lines.push(` ${themeColors.textBold('Setup Instructions')} β ${coloredProviderName}`)
|
|
183
|
+
lines.push(themeColors.dim(` 1) Create a ${selectedMeta.label || selectedSource.name} account: ${selectedMeta.signupUrl || 'signup link missing'}`))
|
|
184
|
+
lines.push(themeColors.dim(` 2) ${selectedMeta.signupHint || 'Generate an API key and paste it with Enter on this row'}`))
|
|
185
|
+
lines.push(themeColors.dim(` 3) Press ${themeColors.hotkey('T')} to test your key. Status: ${setupStatus}`))
|
|
181
186
|
if (selectedProviderKey === 'cloudflare') {
|
|
182
187
|
const hasAccountId = Boolean((process.env.CLOUDFLARE_ACCOUNT_ID || '').trim())
|
|
183
|
-
const accountIdStatus = hasAccountId ?
|
|
184
|
-
lines.push(
|
|
188
|
+
const accountIdStatus = hasAccountId ? themeColors.success('CLOUDFLARE_ACCOUNT_ID detected β
') : themeColors.warning('Set CLOUDFLARE_ACCOUNT_ID β ')
|
|
189
|
+
lines.push(themeColors.dim(` 4) Export ${themeColors.hotkey('CLOUDFLARE_ACCOUNT_ID')} in your shell. Status: ${accountIdStatus}`))
|
|
185
190
|
}
|
|
186
191
|
const testDetail = state.settingsTestDetails?.[selectedProviderKey]
|
|
187
192
|
if (testDetail) {
|
|
188
193
|
lines.push('')
|
|
189
|
-
lines.push(
|
|
194
|
+
lines.push(themeColors.errorBold(' Test Diagnostics'))
|
|
190
195
|
for (const detailLine of wrapPlainText(testDetail)) {
|
|
191
|
-
lines.push(
|
|
196
|
+
lines.push(themeColors.error(` ${detailLine}`))
|
|
192
197
|
}
|
|
193
198
|
}
|
|
194
199
|
lines.push('')
|
|
195
200
|
}
|
|
196
201
|
|
|
197
202
|
lines.push('')
|
|
198
|
-
lines.push(` ${
|
|
199
|
-
lines.push(` ${
|
|
203
|
+
lines.push(` ${themeColors.textBold('π Maintenance')}`)
|
|
204
|
+
lines.push(` ${themeColors.dim(' ' + 'β'.repeat(separatorWidth))}`)
|
|
200
205
|
lines.push('')
|
|
201
206
|
|
|
202
207
|
const updateCursor = state.settingsCursor === updateRowIdx
|
|
203
|
-
const updateBullet = updateCursor ? chalk.bold.cyan(' β― ') : chalk.dim(' ')
|
|
204
208
|
const updateState = state.settingsUpdateState
|
|
205
209
|
const latestFound = state.settingsUpdateLatestVersion
|
|
206
210
|
const updateActionLabel = updateState === 'available' && latestFound
|
|
207
211
|
? `Install update (v${latestFound})`
|
|
208
212
|
: 'Check for updates manually'
|
|
209
|
-
let updateStatus =
|
|
210
|
-
if (updateState === 'checking') updateStatus =
|
|
211
|
-
if (updateState === 'available' && latestFound) updateStatus =
|
|
212
|
-
if (updateState === 'up-to-date') updateStatus =
|
|
213
|
-
if (updateState === 'error') updateStatus =
|
|
214
|
-
if (updateState === 'installing') updateStatus =
|
|
215
|
-
const updateRow = `${
|
|
213
|
+
let updateStatus = themeColors.dim('Press Enter or U to check npm registry')
|
|
214
|
+
if (updateState === 'checking') updateStatus = themeColors.warning('Checking npm registryβ¦')
|
|
215
|
+
if (updateState === 'available' && latestFound) updateStatus = themeColors.successBold(`Update available: v${latestFound} (Enter to install)`)
|
|
216
|
+
if (updateState === 'up-to-date') updateStatus = themeColors.success('Already on latest version')
|
|
217
|
+
if (updateState === 'error') updateStatus = themeColors.error('Check failed (press U to retry)')
|
|
218
|
+
if (updateState === 'installing') updateStatus = themeColors.info('Installing updateβ¦')
|
|
219
|
+
const updateRow = `${bullet(updateCursor)}${themeColors.textBold(updateActionLabel).padEnd(44)} ${updateStatus}`
|
|
216
220
|
cursorLineByRow[updateRowIdx] = lines.length
|
|
217
221
|
lines.push(updateCursor ? themeColors.bgCursor(updateRow) : updateRow)
|
|
218
222
|
// π Width warning visibility row for the startup narrow-terminal overlay.
|
|
219
223
|
const disableWidthsWarning = Boolean(state.config.settings?.disableWidthsWarning)
|
|
220
|
-
const widthWarningBullet = state.settingsCursor === widthWarningRowIdx ? chalk.bold.cyan(' β― ') : chalk.dim(' ')
|
|
221
224
|
const widthWarningStatus = disableWidthsWarning
|
|
222
|
-
?
|
|
223
|
-
:
|
|
224
|
-
const widthWarningRow = `${
|
|
225
|
+
? themeColors.errorBold('π Disabled')
|
|
226
|
+
: themeColors.successBold('π Enabled')
|
|
227
|
+
const widthWarningRow = `${bullet(state.settingsCursor === widthWarningRowIdx)}${themeColors.textBold('Small Width Warnings').padEnd(44)} ${widthWarningStatus}`
|
|
225
228
|
cursorLineByRow[widthWarningRowIdx] = lines.length
|
|
226
229
|
lines.push(state.settingsCursor === widthWarningRowIdx ? themeColors.bgCursor(widthWarningRow) : widthWarningRow)
|
|
230
|
+
const themeStatus = getThemeStatusLabel(activeThemeSetting())
|
|
231
|
+
const themeStatusColor = themeStatus.includes('Dark') ? themeColors.warningBold : themeColors.info
|
|
232
|
+
const themeRow = `${bullet(state.settingsCursor === themeRowIdx)}${themeColors.textBold('Global Theme').padEnd(44)} ${themeStatusColor(themeStatus)}`
|
|
233
|
+
cursorLineByRow[themeRowIdx] = lines.length
|
|
234
|
+
lines.push(state.settingsCursor === themeRowIdx ? themeColors.bgCursor(themeRow) : themeRow)
|
|
227
235
|
if (updateState === 'error' && state.settingsUpdateError) {
|
|
228
|
-
lines.push(
|
|
236
|
+
lines.push(themeColors.error(` ${state.settingsUpdateError}`))
|
|
229
237
|
}
|
|
230
238
|
|
|
231
239
|
// π Cleanup row removes stale proxy-era config left behind by older builds.
|
|
232
|
-
const
|
|
233
|
-
const cleanupLegacyProxyRow = `${cleanupLegacyProxyBullet}${chalk.bold('Clean Legacy Proxy Config').padEnd(44)} ${chalk.magentaBright('Enter remove discontinued bridge leftovers')}`
|
|
240
|
+
const cleanupLegacyProxyRow = `${bullet(state.settingsCursor === cleanupLegacyProxyRowIdx)}${themeColors.textBold('Clean Legacy Proxy Config').padEnd(44)} ${themeColors.warning('Enter remove discontinued bridge leftovers')}`
|
|
234
241
|
cursorLineByRow[cleanupLegacyProxyRowIdx] = lines.length
|
|
235
242
|
lines.push(state.settingsCursor === cleanupLegacyProxyRowIdx ? themeColors.bgCursorLegacy(cleanupLegacyProxyRow) : cleanupLegacyProxyRow)
|
|
236
243
|
|
|
237
244
|
// π Changelog viewer row
|
|
238
|
-
const
|
|
239
|
-
const changelogViewRow = `${changelogViewBullet}${chalk.bold('View Changelog').padEnd(44)} ${chalk.dim('Enter browse version history')}`
|
|
245
|
+
const changelogViewRow = `${bullet(state.settingsCursor === changelogViewRowIdx)}${themeColors.textBold('View Changelog').padEnd(44)} ${themeColors.dim('Enter browse version history')}`
|
|
240
246
|
cursorLineByRow[changelogViewRowIdx] = lines.length
|
|
241
247
|
lines.push(state.settingsCursor === changelogViewRowIdx ? themeColors.bgCursorSettingsList(changelogViewRow) : changelogViewRow)
|
|
242
248
|
|
|
@@ -244,26 +250,26 @@ export function createOverlayRenderers(state, deps) {
|
|
|
244
250
|
|
|
245
251
|
lines.push('')
|
|
246
252
|
if (state.settingsEditMode) {
|
|
247
|
-
lines.push(
|
|
253
|
+
lines.push(themeColors.dim(' Type API key β’ Enter Save β’ Esc Cancel'))
|
|
248
254
|
} else {
|
|
249
|
-
lines.push(
|
|
255
|
+
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'))
|
|
250
256
|
}
|
|
251
257
|
// π Show sync/restore status message if set
|
|
252
258
|
if (state.settingsSyncStatus) {
|
|
253
259
|
const { type, msg } = state.settingsSyncStatus
|
|
254
|
-
lines.push(type === 'success' ?
|
|
260
|
+
lines.push(type === 'success' ? themeColors.successBold(` ${msg}`) : themeColors.warning(` ${msg}`))
|
|
255
261
|
}
|
|
256
262
|
lines.push('')
|
|
257
263
|
|
|
258
264
|
// π Footer with credits
|
|
259
265
|
lines.push('')
|
|
260
266
|
lines.push(
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
+
themeColors.dim(' ') +
|
|
268
|
+
themeColors.footerLove('Made with π & β by ') +
|
|
269
|
+
themeColors.link('\x1b]8;;https://github.com/vava-nessa\x1b\\vava-nessa\x1b]8;;\x1b\\') +
|
|
270
|
+
themeColors.dim(' β’ β ') +
|
|
271
|
+
themeColors.footerCoffee('\x1b]8;;https://buymeacoffee.com/vavanessadev\x1b\\Buy me a coffee\x1b]8;;\x1b\\') +
|
|
272
|
+
themeColors.dim(' β’ ') +
|
|
267
273
|
'Esc to close'
|
|
268
274
|
)
|
|
269
275
|
|
|
@@ -323,37 +329,36 @@ export function createOverlayRenderers(state, deps) {
|
|
|
323
329
|
|
|
324
330
|
lines.push('')
|
|
325
331
|
// π Branding header
|
|
326
|
-
lines.push(` ${
|
|
327
|
-
lines.push(` ${
|
|
332
|
+
lines.push(` ${themeColors.accent('π')} ${themeColors.accentBold('free-coding-models')} ${themeColors.dim(`v${LOCAL_VERSION}`)}`)
|
|
333
|
+
lines.push(` ${themeColors.textBold('π Install Endpoints')}`)
|
|
328
334
|
lines.push('')
|
|
329
|
-
lines.push(
|
|
335
|
+
lines.push(themeColors.dim(' β install provider catalogs into supported coding tools'))
|
|
330
336
|
if (state.installEndpointsErrorMsg) {
|
|
331
|
-
lines.push(` ${
|
|
337
|
+
lines.push(` ${themeColors.warning(state.installEndpointsErrorMsg)}`)
|
|
332
338
|
}
|
|
333
339
|
lines.push('')
|
|
334
340
|
|
|
335
341
|
if (state.installEndpointsPhase === 'providers') {
|
|
336
|
-
lines.push(` ${
|
|
342
|
+
lines.push(` ${themeColors.textBold(`Step 1/${totalSteps}`)} ${themeColors.info('Choose a configured provider')}`)
|
|
337
343
|
lines.push('')
|
|
338
344
|
|
|
339
345
|
if (providerChoices.length === 0) {
|
|
340
|
-
lines.push(
|
|
341
|
-
lines.push(
|
|
346
|
+
lines.push(themeColors.dim(' No configured providers can be installed directly right now.'))
|
|
347
|
+
lines.push(themeColors.dim(' Add an API key in Settings (`P`) first, then reopen this screen.'))
|
|
342
348
|
} else {
|
|
343
349
|
providerChoices.forEach((provider, idx) => {
|
|
344
350
|
const isCursor = idx === state.installEndpointsCursor
|
|
345
|
-
const
|
|
346
|
-
const row = `${bullet}${chalk.bold(provider.label.padEnd(24))} ${chalk.dim(`${provider.modelCount} models`)}`
|
|
351
|
+
const row = `${bullet(isCursor)}${themeColors.textBold(provider.label.padEnd(24))} ${themeColors.dim(`${provider.modelCount} models`)}`
|
|
347
352
|
cursorLineByRow[idx] = lines.length
|
|
348
353
|
lines.push(isCursor ? themeColors.bgCursorInstall(row) : row)
|
|
349
354
|
})
|
|
350
355
|
}
|
|
351
356
|
|
|
352
357
|
lines.push('')
|
|
353
|
-
lines.push(
|
|
358
|
+
lines.push(themeColors.dim(' ββ Navigate β’ Enter Choose provider β’ Esc Close'))
|
|
354
359
|
} else if (state.installEndpointsPhase === 'tools') {
|
|
355
|
-
lines.push(` ${
|
|
356
|
-
lines.push(
|
|
360
|
+
lines.push(` ${themeColors.textBold(`Step 2/${totalSteps}`)} ${themeColors.info('Choose the target tool')}`)
|
|
361
|
+
lines.push(themeColors.dim(` Provider: ${selectedProviderLabel}`))
|
|
357
362
|
lines.push('')
|
|
358
363
|
|
|
359
364
|
// π Use getToolMeta for labels instead of hard-coded ternary chains
|
|
@@ -362,60 +367,57 @@ export function createOverlayRenderers(state, deps) {
|
|
|
362
367
|
const meta = getToolMeta(toolMode)
|
|
363
368
|
const label = `${meta.emoji} ${meta.label}`
|
|
364
369
|
const note = toolMode.startsWith('opencode')
|
|
365
|
-
?
|
|
370
|
+
? themeColors.dim('shared config file')
|
|
366
371
|
: toolMode === 'openhands'
|
|
367
|
-
?
|
|
368
|
-
:
|
|
369
|
-
const
|
|
370
|
-
const row = `${bullet}${chalk.bold(label.padEnd(26))} ${note}`
|
|
372
|
+
? themeColors.dim('env file (~/.fcm-*-env)')
|
|
373
|
+
: themeColors.dim('managed config install')
|
|
374
|
+
const row = `${bullet(isCursor)}${themeColors.textBold(label.padEnd(26))} ${note}`
|
|
371
375
|
cursorLineByRow[idx] = lines.length
|
|
372
376
|
lines.push(isCursor ? themeColors.bgCursorInstall(row) : row)
|
|
373
377
|
})
|
|
374
378
|
|
|
375
379
|
lines.push('')
|
|
376
|
-
lines.push(
|
|
380
|
+
lines.push(themeColors.dim(' ββ Navigate β’ Enter Choose tool β’ Esc Back'))
|
|
377
381
|
} else if (state.installEndpointsPhase === 'scope') {
|
|
378
|
-
lines.push(` ${
|
|
379
|
-
lines.push(
|
|
382
|
+
lines.push(` ${themeColors.textBold(`Step 3/${totalSteps}`)} ${themeColors.info('Choose the install scope')}`)
|
|
383
|
+
lines.push(themeColors.dim(` Provider: ${selectedProviderLabel} β’ Tool: ${selectedToolLabel} β’ ${selectedConnectionLabel}`))
|
|
380
384
|
lines.push('')
|
|
381
385
|
|
|
382
386
|
scopeChoices.forEach((scope, idx) => {
|
|
383
387
|
const isCursor = idx === state.installEndpointsCursor
|
|
384
|
-
const
|
|
385
|
-
const row = `${bullet}${chalk.bold(scope.label)}`
|
|
388
|
+
const row = `${bullet(isCursor)}${themeColors.textBold(scope.label)}`
|
|
386
389
|
cursorLineByRow[idx] = lines.length
|
|
387
390
|
lines.push(isCursor ? themeColors.bgCursorInstall(row) : row)
|
|
388
|
-
lines.push(
|
|
391
|
+
lines.push(themeColors.dim(` ${scope.hint}`))
|
|
389
392
|
lines.push('')
|
|
390
393
|
})
|
|
391
394
|
|
|
392
|
-
lines.push(
|
|
395
|
+
lines.push(themeColors.dim(' Enter Continue β’ Esc Back'))
|
|
393
396
|
} else if (state.installEndpointsPhase === 'models') {
|
|
394
397
|
const models = getProviderCatalogModels(state.installEndpointsProviderKey)
|
|
395
398
|
const selectedCount = state.installEndpointsSelectedModelIds.size
|
|
396
399
|
|
|
397
|
-
lines.push(` ${
|
|
398
|
-
lines.push(
|
|
399
|
-
lines.push(
|
|
400
|
+
lines.push(` ${themeColors.textBold(`Step 4/${totalSteps}`)} ${themeColors.info('Choose which models to install')}`)
|
|
401
|
+
lines.push(themeColors.dim(` Provider: ${selectedProviderLabel} β’ Tool: ${selectedToolLabel} β’ ${selectedConnectionLabel}`))
|
|
402
|
+
lines.push(themeColors.dim(` Selected: ${selectedCount}/${models.length}`))
|
|
400
403
|
lines.push('')
|
|
401
404
|
|
|
402
405
|
models.forEach((model, idx) => {
|
|
403
406
|
const isCursor = idx === state.installEndpointsCursor
|
|
404
407
|
const selected = state.installEndpointsSelectedModelIds.has(model.modelId)
|
|
405
|
-
const
|
|
406
|
-
const
|
|
407
|
-
const
|
|
408
|
-
const row = `${bullet}${checkbox} ${chalk.bold(model.label.padEnd(26))} ${tier} ${chalk.dim(model.ctx.padEnd(6))} ${chalk.dim(model.modelId)}`
|
|
408
|
+
const checkbox = selected ? themeColors.successBold('[β]') : themeColors.dim('[ ]')
|
|
409
|
+
const tier = themeColors.info(model.tier.padEnd(2))
|
|
410
|
+
const row = `${bullet(isCursor)}${checkbox} ${themeColors.textBold(model.label.padEnd(26))} ${tier} ${themeColors.dim(model.ctx.padEnd(6))} ${themeColors.dim(model.modelId)}`
|
|
409
411
|
cursorLineByRow[idx] = lines.length
|
|
410
412
|
lines.push(isCursor ? themeColors.bgCursorInstall(row) : row)
|
|
411
413
|
})
|
|
412
414
|
|
|
413
415
|
lines.push('')
|
|
414
|
-
lines.push(
|
|
416
|
+
lines.push(themeColors.dim(' ββ Navigate β’ Space Toggle model β’ A All/None β’ Enter Install β’ Esc Back'))
|
|
415
417
|
} else if (state.installEndpointsPhase === 'result') {
|
|
416
418
|
const result = state.installEndpointsResult
|
|
417
|
-
const accent = result?.type === 'success' ?
|
|
418
|
-
lines.push(` ${
|
|
419
|
+
const accent = result?.type === 'success' ? themeColors.successBold : themeColors.errorBold
|
|
420
|
+
lines.push(` ${themeColors.textBold('Result')} ${accent(result?.title || 'Install result unavailable')}`)
|
|
419
421
|
lines.push('')
|
|
420
422
|
|
|
421
423
|
for (const detail of result?.lines || []) {
|
|
@@ -424,14 +426,100 @@ export function createOverlayRenderers(state, deps) {
|
|
|
424
426
|
|
|
425
427
|
if (result?.type === 'success') {
|
|
426
428
|
lines.push('')
|
|
427
|
-
lines.push(
|
|
429
|
+
lines.push(themeColors.dim(' Future FCM launches will refresh this catalog automatically when the provider list evolves.'))
|
|
428
430
|
}
|
|
429
431
|
|
|
430
432
|
lines.push('')
|
|
431
|
-
lines.push(
|
|
433
|
+
lines.push(themeColors.dim(' Enter or Esc Close'))
|
|
432
434
|
}
|
|
433
435
|
|
|
434
436
|
const targetLine = cursorLineByRow[state.installEndpointsCursor] ?? 0
|
|
437
|
+
state.toolInstallPromptScrollOffset = keepOverlayTargetVisible(
|
|
438
|
+
state.toolInstallPromptScrollOffset,
|
|
439
|
+
targetLine,
|
|
440
|
+
lines.length,
|
|
441
|
+
state.terminalRows
|
|
442
|
+
)
|
|
443
|
+
const { visible, offset } = sliceOverlayLines(lines, state.toolInstallPromptScrollOffset, state.terminalRows)
|
|
444
|
+
state.toolInstallPromptScrollOffset = offset
|
|
445
|
+
|
|
446
|
+
const tintedLines = tintOverlayLines(visible, themeColors.overlayBgSettings, state.terminalCols)
|
|
447
|
+
const cleared = tintedLines.map((line) => line + EL)
|
|
448
|
+
return cleared.join('\n')
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// βββ Missing-tool install confirmation overlay ββββββββββββββββββββββββββββ
|
|
452
|
+
// π renderToolInstallPrompt keeps the user inside the TUI long enough to
|
|
453
|
+
// π confirm the auto-install, then the key handler exits the alt screen and
|
|
454
|
+
// π runs the official installer before retrying the selected launch.
|
|
455
|
+
function renderToolInstallPrompt() {
|
|
456
|
+
const EL = '\x1b[K'
|
|
457
|
+
const lines = []
|
|
458
|
+
const cursorLineByRow = {}
|
|
459
|
+
const installPlan = state.toolInstallPromptPlan || getToolInstallPlan(state.toolInstallPromptMode)
|
|
460
|
+
const toolMeta = state.toolInstallPromptMode ? getToolMeta(state.toolInstallPromptMode) : null
|
|
461
|
+
const selectedModel = state.toolInstallPromptModel
|
|
462
|
+
const options = [
|
|
463
|
+
{
|
|
464
|
+
label: 'Yes, install it now',
|
|
465
|
+
hint: installPlan?.summary || 'Run the official installer, then continue with the selected model.',
|
|
466
|
+
},
|
|
467
|
+
{
|
|
468
|
+
label: 'No, go back',
|
|
469
|
+
hint: 'Return to the model list without installing anything.',
|
|
470
|
+
},
|
|
471
|
+
]
|
|
472
|
+
|
|
473
|
+
lines.push(` ${chalk.cyanBright('π')} ${chalk.bold.cyanBright('free-coding-models')}`)
|
|
474
|
+
lines.push(` ${chalk.bold('π¦ Missing Tool')}`)
|
|
475
|
+
lines.push('')
|
|
476
|
+
|
|
477
|
+
if (!toolMeta || !installPlan) {
|
|
478
|
+
lines.push(chalk.red(' No install metadata is available for the selected tool.'))
|
|
479
|
+
lines.push('')
|
|
480
|
+
lines.push(chalk.dim(' Esc Close'))
|
|
481
|
+
} else {
|
|
482
|
+
const title = `${toolMeta.emoji} ${toolMeta.label}`
|
|
483
|
+
lines.push(` ${chalk.bold(title)} is not installed on this machine.`)
|
|
484
|
+
lines.push(chalk.dim(` Selected model: ${selectedModel?.label || 'Unknown model'}`))
|
|
485
|
+
lines.push('')
|
|
486
|
+
|
|
487
|
+
if (!installPlan.supported) {
|
|
488
|
+
lines.push(chalk.yellow(` ${installPlan.reason || 'FCM cannot auto-install this tool on the current platform.'}`))
|
|
489
|
+
if (installPlan.docsUrl) {
|
|
490
|
+
lines.push(chalk.dim(` Docs: ${installPlan.docsUrl}`))
|
|
491
|
+
}
|
|
492
|
+
lines.push('')
|
|
493
|
+
lines.push(chalk.dim(' Enter or Esc Close'))
|
|
494
|
+
} else {
|
|
495
|
+
lines.push(chalk.dim(` Command: ${installPlan.shellCommand}`))
|
|
496
|
+
if (installPlan.note) {
|
|
497
|
+
lines.push(chalk.dim(` Note: ${installPlan.note}`))
|
|
498
|
+
}
|
|
499
|
+
if (installPlan.docsUrl) {
|
|
500
|
+
lines.push(chalk.dim(` Docs: ${installPlan.docsUrl}`))
|
|
501
|
+
}
|
|
502
|
+
if (state.toolInstallPromptErrorMsg) {
|
|
503
|
+
lines.push('')
|
|
504
|
+
lines.push(chalk.yellow(` ${state.toolInstallPromptErrorMsg}`))
|
|
505
|
+
}
|
|
506
|
+
lines.push('')
|
|
507
|
+
|
|
508
|
+
options.forEach((option, idx) => {
|
|
509
|
+
const isCursor = idx === state.toolInstallPromptCursor
|
|
510
|
+
const bullet = isCursor ? chalk.bold.cyan(' β― ') : chalk.dim(' ')
|
|
511
|
+
const row = `${bullet}${chalk.bold(option.label)}`
|
|
512
|
+
cursorLineByRow[idx] = lines.length
|
|
513
|
+
lines.push(isCursor ? themeColors.bgCursorInstall(row) : row)
|
|
514
|
+
lines.push(chalk.dim(` ${option.hint}`))
|
|
515
|
+
lines.push('')
|
|
516
|
+
})
|
|
517
|
+
|
|
518
|
+
lines.push(chalk.dim(' ββ Navigate β’ Enter Confirm β’ Esc Cancel'))
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
const targetLine = cursorLineByRow[state.toolInstallPromptCursor] ?? 0
|
|
435
523
|
state.installEndpointsScrollOffset = keepOverlayTargetVisible(
|
|
436
524
|
state.installEndpointsScrollOffset,
|
|
437
525
|
targetLine,
|
|
@@ -452,85 +540,92 @@ export function createOverlayRenderers(state, deps) {
|
|
|
452
540
|
function renderHelp() {
|
|
453
541
|
const EL = '\x1b[K'
|
|
454
542
|
const lines = []
|
|
543
|
+
const label = themeColors.info
|
|
544
|
+
const hint = themeColors.dim
|
|
545
|
+
const key = themeColors.hotkey
|
|
546
|
+
const heading = themeColors.textBold
|
|
455
547
|
|
|
456
548
|
// π Branding header
|
|
457
|
-
lines.push(` ${
|
|
458
|
-
lines.push(` ${
|
|
549
|
+
lines.push(` ${themeColors.accent('π')} ${themeColors.accentBold('free-coding-models')} ${themeColors.dim(`v${LOCAL_VERSION}`)}`)
|
|
550
|
+
lines.push(` ${heading('β Help & Keyboard Shortcuts')}`)
|
|
459
551
|
lines.push('')
|
|
460
|
-
lines.push(` ${
|
|
461
|
-
lines.push(` ${
|
|
552
|
+
lines.push(` ${hint('β ββ / PgUp / PgDn / Home / End scroll β’ K or Esc close')}`)
|
|
553
|
+
lines.push(` ${heading('Columns')}`)
|
|
462
554
|
lines.push('')
|
|
463
|
-
lines.push(` ${
|
|
464
|
-
lines.push(` ${
|
|
555
|
+
lines.push(` ${label('Rank')} SWE-bench rank (1 = best coding score) ${hint('Sort:')} ${key('R')}`)
|
|
556
|
+
lines.push(` ${hint('Quick glance at which model is objectively the best coder right now.')}`)
|
|
465
557
|
lines.push('')
|
|
466
|
-
lines.push(` ${
|
|
467
|
-
lines.push(` ${
|
|
558
|
+
lines.push(` ${label('Tier')} S+ / S / A+ / A / A- / B+ / B / C based on SWE-bench score ${hint('Cycle:')} ${key('T')}`)
|
|
559
|
+
lines.push(` ${hint('Skip the noise β S/S+ models solve real GitHub issues, C models are for light tasks.')}`)
|
|
468
560
|
lines.push('')
|
|
469
|
-
lines.push(` ${
|
|
470
|
-
lines.push(` ${
|
|
561
|
+
lines.push(` ${label('SWE%')} SWE-bench score β coding ability benchmark (color-coded) ${hint('Sort:')} ${key('S')}`)
|
|
562
|
+
lines.push(` ${hint('The raw number behind the tier. Higher = better at writing, fixing, and refactoring code.')}`)
|
|
471
563
|
lines.push('')
|
|
472
|
-
lines.push(` ${
|
|
473
|
-
lines.push(` ${
|
|
564
|
+
lines.push(` ${label('CTX')} Context window size (128k, 200k, 256k, 1m, etc.) ${hint('Sort:')} ${key('C')}`)
|
|
565
|
+
lines.push(` ${hint('Bigger context = the model can read more of your codebase at once without forgetting.')}`)
|
|
474
566
|
lines.push('')
|
|
475
|
-
lines.push(` ${
|
|
476
|
-
lines.push(` ${
|
|
567
|
+
lines.push(` ${label('Model')} Model name (β = favorited, pinned at top) ${hint('Sort:')} ${key('M')} ${hint('Favorite:')} ${key('F')}`)
|
|
568
|
+
lines.push(` ${hint('Star the ones you like β they stay pinned at the top across restarts.')}`)
|
|
477
569
|
lines.push('')
|
|
478
|
-
lines.push(` ${
|
|
479
|
-
lines.push(` ${
|
|
570
|
+
lines.push(` ${label('Provider')} Provider source (NIM, Groq, Cerebras, etc.) ${hint('Sort:')} ${key('O')} ${hint('Cycle:')} ${key('D')}`)
|
|
571
|
+
lines.push(` ${hint('Same model on different providers can have very different speed and uptime.')}`)
|
|
480
572
|
lines.push('')
|
|
481
|
-
lines.push(` ${
|
|
482
|
-
lines.push(` ${
|
|
573
|
+
lines.push(` ${label('Latest')} Most recent ping response time (ms) ${hint('Sort:')} ${key('L')}`)
|
|
574
|
+
lines.push(` ${hint('Shows how fast the server is responding right now β useful to catch live slowdowns.')}`)
|
|
483
575
|
lines.push('')
|
|
484
|
-
lines.push(` ${
|
|
485
|
-
lines.push(` ${
|
|
576
|
+
lines.push(` ${label('Avg Ping')} Average response time across all measurable pings (200 + 401) (ms) ${hint('Sort:')} ${key('A')}`)
|
|
577
|
+
lines.push(` ${hint('The long-term truth. Even without a key, a 401 still gives real latency so the average stays useful.')}`)
|
|
486
578
|
lines.push('')
|
|
487
|
-
lines.push(` ${
|
|
488
|
-
lines.push(` ${
|
|
579
|
+
lines.push(` ${label('Health')} Live status: β
UP / π₯ 429 / β³ TIMEOUT / β ERR / π NO KEY ${hint('Sort:')} ${key('H')}`)
|
|
580
|
+
lines.push(` ${hint('Tells you instantly if a model is reachable or down β no guesswork needed.')}`)
|
|
489
581
|
lines.push('')
|
|
490
|
-
lines.push(` ${
|
|
491
|
-
lines.push(` ${
|
|
582
|
+
lines.push(` ${label('Verdict')} Overall assessment: Perfect / Normal / Spiky / Slow / Overloaded ${hint('Sort:')} ${key('V')}`)
|
|
583
|
+
lines.push(` ${hint('One-word summary so you don\'t have to cross-check speed, health, and stability yourself.')}`)
|
|
492
584
|
lines.push('')
|
|
493
|
-
lines.push(` ${
|
|
494
|
-
lines.push(` ${
|
|
585
|
+
lines.push(` ${label('Stability')} Composite 0β100 score: p95 + jitter + spike rate + uptime ${hint('Sort:')} ${key('B')}`)
|
|
586
|
+
lines.push(` ${hint('A fast model that randomly freezes is worse than a steady one. This catches that.')}`)
|
|
495
587
|
lines.push('')
|
|
496
|
-
lines.push(` ${
|
|
497
|
-
lines.push(` ${
|
|
588
|
+
lines.push(` ${label('Up%')} Uptime β ratio of successful pings to total pings ${hint('Sort:')} ${key('U')}`)
|
|
589
|
+
lines.push(` ${hint('If a model only works half the time, you\'ll waste time retrying. Higher = more reliable.')}`)
|
|
498
590
|
lines.push('')
|
|
499
|
-
lines.push(` ${
|
|
500
|
-
lines.push(` ${
|
|
591
|
+
lines.push(` ${label('Used')} Historical prompt+completion tokens tracked for this exact provider/model pair`)
|
|
592
|
+
lines.push(` ${hint('Loaded from local stats snapshots. Displayed in K tokens, or M tokens above one million.')}`)
|
|
501
593
|
lines.push('')
|
|
502
594
|
|
|
503
595
|
|
|
504
596
|
lines.push('')
|
|
505
|
-
lines.push(` ${
|
|
506
|
-
lines.push(` ${
|
|
507
|
-
lines.push(` ${
|
|
508
|
-
lines.push(` ${
|
|
597
|
+
lines.push(` ${heading('Main TUI')}`)
|
|
598
|
+
lines.push(` ${heading('Navigation')}`)
|
|
599
|
+
lines.push(` ${key('ββ')} Navigate rows`)
|
|
600
|
+
lines.push(` ${key('Enter')} Select model and launch`)
|
|
601
|
+
lines.push(` ${hint('If the active CLI is missing, FCM offers a one-click install prompt first.')}`)
|
|
509
602
|
lines.push('')
|
|
510
|
-
lines.push(` ${
|
|
511
|
-
lines.push(` ${
|
|
512
|
-
lines.push(` ${
|
|
513
|
-
lines.push(` ${
|
|
514
|
-
lines.push(` ${
|
|
515
|
-
lines.push(` ${
|
|
516
|
-
lines.push(` ${
|
|
517
|
-
lines.push(` ${
|
|
518
|
-
lines.push(` ${
|
|
603
|
+
lines.push(` ${heading('Controls')}`)
|
|
604
|
+
lines.push(` ${key('W')} Toggle ping mode ${hint('(speed 2s β normal 10s β slow 30s β forced 4s)')}`)
|
|
605
|
+
lines.push(` ${key('E')} Toggle configured models only ${hint('(enabled by default)')}`)
|
|
606
|
+
lines.push(` ${key('Z')} Cycle tool mode ${hint('(OpenCode β Desktop β OpenClaw β Crush β Goose β Pi β Aider β Qwen β OpenHands β Amp)')}`)
|
|
607
|
+
lines.push(` ${key('F')} Toggle favorite on selected row ${hint('(β pinned at top, persisted)')}`)
|
|
608
|
+
lines.push(` ${key('Y')} Install endpoints ${hint('(provider catalog β compatible tools, direct provider only)')}`)
|
|
609
|
+
lines.push(` ${key('Q')} Smart Recommend ${hint('(π― find the best model for your task β questionnaire + live analysis)')}`)
|
|
610
|
+
lines.push(` ${key('G')} Cycle theme ${hint('(auto β dark β light)')}`)
|
|
611
|
+
lines.push(` ${themeColors.errorBold('I')} Feedback, bugs & requests ${hint('(π send anonymous feedback, bug reports, or feature requests)')}`)
|
|
612
|
+
lines.push(` ${key('P')} Open settings ${hint('(manage API keys, provider toggles, updates, legacy cleanup)')}`)
|
|
519
613
|
// π Profile system removed - API keys now persist permanently across all sessions
|
|
520
|
-
lines.push(` ${
|
|
521
|
-
lines.push(` ${
|
|
522
|
-
lines.push(` ${
|
|
523
|
-
lines.push(` ${
|
|
524
|
-
lines.push('')
|
|
525
|
-
lines.push(` ${
|
|
526
|
-
lines.push(` ${
|
|
527
|
-
lines.push(` ${
|
|
528
|
-
lines.push(` ${
|
|
529
|
-
lines.push(` ${
|
|
530
|
-
lines.push(` ${
|
|
531
|
-
lines.push(` ${
|
|
532
|
-
lines.push(` ${
|
|
533
|
-
lines.push(` ${
|
|
614
|
+
lines.push(` ${key('Shift+R')} Reset view settings ${hint('(tier filter, sort, provider filter β defaults)')}`)
|
|
615
|
+
lines.push(` ${key('N')} Changelog ${hint('(π browse all versions, Enter to view details)')}`)
|
|
616
|
+
lines.push(` ${key('K')} / ${key('Esc')} Show/hide this help`)
|
|
617
|
+
lines.push(` ${key('Ctrl+C')} Exit`)
|
|
618
|
+
lines.push('')
|
|
619
|
+
lines.push(` ${heading('Settings (P)')}`)
|
|
620
|
+
lines.push(` ${key('ββ')} Navigate rows`)
|
|
621
|
+
lines.push(` ${key('PgUp/PgDn')} Jump by page`)
|
|
622
|
+
lines.push(` ${key('Home/End')} Jump first/last row`)
|
|
623
|
+
lines.push(` ${key('Enter')} Edit key / run selected maintenance action`)
|
|
624
|
+
lines.push(` ${key('Space')} Toggle provider enable/disable`)
|
|
625
|
+
lines.push(` ${key('T')} Test selected provider key`)
|
|
626
|
+
lines.push(` ${key('U')} Check updates manually`)
|
|
627
|
+
lines.push(` ${key('G')} Cycle theme globally`)
|
|
628
|
+
lines.push(` ${key('Esc')} Close settings`)
|
|
534
629
|
lines.push('')
|
|
535
630
|
lines.push(...buildCliHelpLines({ chalk, indent: ' ', title: 'CLI Flags' }))
|
|
536
631
|
lines.push('')
|
|
@@ -552,10 +647,10 @@ export function createOverlayRenderers(state, deps) {
|
|
|
552
647
|
|
|
553
648
|
// π Branding header
|
|
554
649
|
lines.push('')
|
|
555
|
-
lines.push(` ${
|
|
556
|
-
lines.push(` ${
|
|
650
|
+
lines.push(` ${themeColors.accent('π')} ${themeColors.accentBold('free-coding-models')} ${themeColors.dim(`v${LOCAL_VERSION}`)}`)
|
|
651
|
+
lines.push(` ${themeColors.textBold('π― Smart Recommend')}`)
|
|
557
652
|
lines.push('')
|
|
558
|
-
lines.push(
|
|
653
|
+
lines.push(themeColors.dim(' β find the best model for your task'))
|
|
559
654
|
lines.push('')
|
|
560
655
|
|
|
561
656
|
if (state.recommendPhase === 'questionnaire') {
|
|
@@ -588,7 +683,7 @@ export function createOverlayRenderers(state, deps) {
|
|
|
588
683
|
const answered = state.recommendAnswers[questions[i].answerKey]
|
|
589
684
|
if (i < state.recommendQuestion && answered) {
|
|
590
685
|
const answeredLabel = questions[i].options.find(o => o.key === answered)?.label || answered
|
|
591
|
-
breadcrumbs +=
|
|
686
|
+
breadcrumbs += themeColors.successBold(` β ${questions[i].title} ${themeColors.textBold(answeredLabel)}`) + '\n'
|
|
592
687
|
}
|
|
593
688
|
}
|
|
594
689
|
if (breadcrumbs) {
|
|
@@ -596,19 +691,18 @@ export function createOverlayRenderers(state, deps) {
|
|
|
596
691
|
lines.push('')
|
|
597
692
|
}
|
|
598
693
|
|
|
599
|
-
lines.push(` ${
|
|
694
|
+
lines.push(` ${themeColors.textBold(`Question ${qNum}/${qTotal}:`)} ${themeColors.info(q.title)}`)
|
|
600
695
|
lines.push('')
|
|
601
696
|
|
|
602
697
|
for (let i = 0; i < q.options.length; i++) {
|
|
603
698
|
const opt = q.options[i]
|
|
604
699
|
const isCursor = i === state.recommendCursor
|
|
605
|
-
const bullet = isCursor ? chalk.bold.cyan(' β― ') : chalk.dim(' ')
|
|
606
700
|
const label = isCursor ? themeColors.textBold(opt.label) : themeColors.text(opt.label)
|
|
607
|
-
lines.push(`${bullet}${label}`)
|
|
701
|
+
lines.push(`${bullet(isCursor)}${label}`)
|
|
608
702
|
}
|
|
609
703
|
|
|
610
704
|
lines.push('')
|
|
611
|
-
lines.push(
|
|
705
|
+
lines.push(themeColors.dim(' ββ navigate β’ Enter select β’ Esc cancel'))
|
|
612
706
|
|
|
613
707
|
} else if (state.recommendPhase === 'analyzing') {
|
|
614
708
|
// π Loading screen with progress bar
|
|
@@ -616,38 +710,38 @@ export function createOverlayRenderers(state, deps) {
|
|
|
616
710
|
const barWidth = 40
|
|
617
711
|
const filled = Math.round(barWidth * pct / 100)
|
|
618
712
|
const empty = barWidth - filled
|
|
619
|
-
const bar =
|
|
713
|
+
const bar = themeColors.successBold('β'.repeat(filled)) + themeColors.dim('β'.repeat(empty))
|
|
620
714
|
|
|
621
|
-
lines.push(` ${
|
|
715
|
+
lines.push(` ${themeColors.textBold('Analyzing models...')}`)
|
|
622
716
|
lines.push('')
|
|
623
|
-
lines.push(` ${bar} ${
|
|
717
|
+
lines.push(` ${bar} ${themeColors.textBold(String(pct) + '%')}`)
|
|
624
718
|
lines.push('')
|
|
625
719
|
|
|
626
720
|
// π Show what we're doing
|
|
627
721
|
const taskLabel = TASK_TYPES[state.recommendAnswers.taskType]?.label || 'β'
|
|
628
722
|
const prioLabel = PRIORITY_TYPES[state.recommendAnswers.priority]?.label || 'β'
|
|
629
723
|
const ctxLabel = CONTEXT_BUDGETS[state.recommendAnswers.contextBudget]?.label || 'β'
|
|
630
|
-
lines.push(
|
|
724
|
+
lines.push(themeColors.dim(` Task: ${taskLabel} β’ Priority: ${prioLabel} β’ Context: ${ctxLabel}`))
|
|
631
725
|
lines.push('')
|
|
632
726
|
|
|
633
727
|
// π Spinning indicator
|
|
634
728
|
const spinIdx = state.frame % FRAMES.length
|
|
635
|
-
lines.push(` ${
|
|
729
|
+
lines.push(` ${themeColors.warning(FRAMES[spinIdx])} Pinging models at 2 pings/sec to gather fresh latency data...`)
|
|
636
730
|
lines.push('')
|
|
637
|
-
lines.push(
|
|
731
|
+
lines.push(themeColors.dim(' Esc to cancel'))
|
|
638
732
|
|
|
639
733
|
} else if (state.recommendPhase === 'results') {
|
|
640
734
|
// π Show Top 3 results with detailed info
|
|
641
735
|
const taskLabel = TASK_TYPES[state.recommendAnswers.taskType]?.label || 'β'
|
|
642
736
|
const prioLabel = PRIORITY_TYPES[state.recommendAnswers.priority]?.label || 'β'
|
|
643
737
|
const ctxLabel = CONTEXT_BUDGETS[state.recommendAnswers.contextBudget]?.label || 'β'
|
|
644
|
-
lines.push(
|
|
738
|
+
lines.push(themeColors.dim(` Task: ${taskLabel} β’ Priority: ${prioLabel} β’ Context: ${ctxLabel}`))
|
|
645
739
|
lines.push('')
|
|
646
740
|
|
|
647
741
|
if (state.recommendResults.length === 0) {
|
|
648
|
-
lines.push(` ${
|
|
742
|
+
lines.push(` ${themeColors.warning('No models could be scored. Try different criteria or wait for more pings.')}`)
|
|
649
743
|
} else {
|
|
650
|
-
lines.push(` ${
|
|
744
|
+
lines.push(` ${themeColors.textBold('Top Recommendations:')}`)
|
|
651
745
|
lines.push('')
|
|
652
746
|
|
|
653
747
|
for (let i = 0; i < state.recommendResults.length; i++) {
|
|
@@ -655,7 +749,7 @@ export function createOverlayRenderers(state, deps) {
|
|
|
655
749
|
const r = rec.result
|
|
656
750
|
const medal = i === 0 ? 'π₯' : i === 1 ? 'π₯' : 'π₯'
|
|
657
751
|
const providerName = sources[r.providerKey]?.name ?? r.providerKey
|
|
658
|
-
const tierFn = TIER_COLOR[r.tier] ?? (
|
|
752
|
+
const tierFn = TIER_COLOR[r.tier] ?? ((text) => themeColors.text(text))
|
|
659
753
|
const avg = getAvg(r)
|
|
660
754
|
const avgStr = avg === Infinity ? 'β' : Math.round(avg) + 'ms'
|
|
661
755
|
const sweStr = r.sweScore ?? 'β'
|
|
@@ -664,18 +758,18 @@ export function createOverlayRenderers(state, deps) {
|
|
|
664
758
|
const stabStr = stability === -1 ? 'β' : String(stability)
|
|
665
759
|
|
|
666
760
|
const isCursor = i === state.recommendCursor
|
|
667
|
-
const highlight = isCursor ? themeColors.bgCursor : (
|
|
761
|
+
const highlight = isCursor ? themeColors.bgCursor : (text) => text
|
|
668
762
|
|
|
669
|
-
lines.push(highlight(` ${medal} ${
|
|
670
|
-
lines.push(highlight(` Score: ${
|
|
763
|
+
lines.push(highlight(` ${medal} ${themeColors.textBold('#' + (i + 1))} ${themeColors.textBold(r.label)} ${themeColors.dim('(' + providerName + ')')}`))
|
|
764
|
+
lines.push(highlight(` Score: ${themeColors.successBold(String(rec.score) + '/100')} β Tier: ${tierFn(r.tier)} β SWE: ${themeColors.info(sweStr)} β Avg: ${themeColors.warning(avgStr)} β CTX: ${themeColors.info(ctxStr)} β Stability: ${themeColors.info(stabStr)}`))
|
|
671
765
|
lines.push('')
|
|
672
766
|
}
|
|
673
767
|
}
|
|
674
768
|
|
|
675
769
|
lines.push('')
|
|
676
|
-
lines.push(` ${
|
|
770
|
+
lines.push(` ${themeColors.dim('These models are now')} ${themeColors.successBold('highlighted')} ${themeColors.dim('and')} π― ${themeColors.dim('pinned in the main table.')}`)
|
|
677
771
|
lines.push('')
|
|
678
|
-
lines.push(
|
|
772
|
+
lines.push(themeColors.dim(' ββ navigate β’ Enter select & close β’ Esc close β’ Q new search'))
|
|
679
773
|
}
|
|
680
774
|
|
|
681
775
|
lines.push('')
|
|
@@ -782,34 +876,34 @@ export function createOverlayRenderers(state, deps) {
|
|
|
782
876
|
|
|
783
877
|
// π Branding header
|
|
784
878
|
lines.push('')
|
|
785
|
-
lines.push(` ${
|
|
786
|
-
lines.push(` ${
|
|
879
|
+
lines.push(` ${themeColors.accent('π')} ${themeColors.accentBold('free-coding-models')} ${themeColors.dim(`v${LOCAL_VERSION}`)}`)
|
|
880
|
+
lines.push(` ${themeColors.successBold('π Feedback, bugs & requests')}`)
|
|
787
881
|
lines.push('')
|
|
788
|
-
lines.push(
|
|
882
|
+
lines.push(themeColors.dim(" β don't hesitate to send us feedback, bug reports, or just your feeling about the app"))
|
|
789
883
|
lines.push('')
|
|
790
884
|
|
|
791
885
|
// π Status messages (if any)
|
|
792
886
|
if (state.bugReportStatus === 'sending') {
|
|
793
|
-
lines.push(` ${
|
|
887
|
+
lines.push(` ${themeColors.warning('β³ Sending...')}`)
|
|
794
888
|
lines.push('')
|
|
795
889
|
} else if (state.bugReportStatus === 'success') {
|
|
796
|
-
lines.push(` ${
|
|
890
|
+
lines.push(` ${themeColors.successBold('β
Successfully sent!')} ${themeColors.dim('Closing overlay in 3 seconds...')}`)
|
|
797
891
|
lines.push('')
|
|
798
|
-
lines.push(` ${
|
|
892
|
+
lines.push(` ${themeColors.dim('Thank you for your feedback! It has been sent to the project team.')}`)
|
|
799
893
|
lines.push('')
|
|
800
894
|
} else if (state.bugReportStatus === 'error') {
|
|
801
|
-
lines.push(` ${
|
|
802
|
-
lines.push(` ${
|
|
895
|
+
lines.push(` ${themeColors.error('β Error:')} ${themeColors.warning(state.bugReportError || 'Failed to send')}`)
|
|
896
|
+
lines.push(` ${themeColors.dim('Press Backspace to edit, or Esc to close')}`)
|
|
803
897
|
lines.push('')
|
|
804
898
|
} else {
|
|
805
|
-
lines.push(` ${
|
|
806
|
-
lines.push(` ${
|
|
899
|
+
lines.push(` ${themeColors.dim('Type your feedback below. Press Enter to send, Esc to cancel.')}`)
|
|
900
|
+
lines.push(` ${themeColors.dim('Your message will be sent anonymously to the project team.')}`)
|
|
807
901
|
lines.push('')
|
|
808
902
|
}
|
|
809
903
|
|
|
810
904
|
// π Simple input area β left-aligned, framed by horizontal lines
|
|
811
|
-
lines.push(` ${
|
|
812
|
-
lines.push(` ${
|
|
905
|
+
lines.push(` ${themeColors.info('Message')} (${state.bugReportBuffer.length}/500 chars)`)
|
|
906
|
+
lines.push(` ${themeColors.dim('β'.repeat(maxInputWidth))}`)
|
|
813
907
|
// π Input lines β left-aligned, or placeholder when empty
|
|
814
908
|
if (displayLines.length > 0) {
|
|
815
909
|
for (const line of displayLines) {
|
|
@@ -817,19 +911,18 @@ export function createOverlayRenderers(state, deps) {
|
|
|
817
911
|
}
|
|
818
912
|
// π Show cursor on last line
|
|
819
913
|
if (state.bugReportStatus === 'idle' || state.bugReportStatus === 'error') {
|
|
820
|
-
lines[lines.length - 1] +=
|
|
914
|
+
lines[lines.length - 1] += themeColors.accentBold('β')
|
|
821
915
|
}
|
|
822
916
|
} else {
|
|
823
|
-
const placeholderBR = state.bugReportStatus === 'idle' ? chalk.
|
|
824
|
-
lines.push(` ${placeholderBR}${
|
|
917
|
+
const placeholderBR = state.bugReportStatus === 'idle' ? chalk.italic.rgb(...getProviderRgb('googleai'))('Type your message here...') : ''
|
|
918
|
+
lines.push(` ${placeholderBR}${themeColors.accentBold('β')}`)
|
|
825
919
|
}
|
|
826
|
-
lines.push(` ${
|
|
920
|
+
lines.push(` ${themeColors.dim('β'.repeat(maxInputWidth))}`)
|
|
827
921
|
lines.push('')
|
|
828
|
-
lines.push(
|
|
922
|
+
lines.push(themeColors.dim(' Enter Send β’ Esc Cancel β’ Backspace Delete'))
|
|
829
923
|
|
|
830
924
|
// π Apply overlay tint and return
|
|
831
|
-
const
|
|
832
|
-
const tintedLines = tintOverlayLines(lines, BUG_REPORT_OVERLAY_BG, state.terminalCols)
|
|
925
|
+
const tintedLines = tintOverlayLines(lines, themeColors.overlayBgFeedback, state.terminalCols)
|
|
833
926
|
const cleared = tintedLines.map(l => l + EL)
|
|
834
927
|
return cleared.join('\n')
|
|
835
928
|
}
|
|
@@ -853,14 +946,14 @@ export function createOverlayRenderers(state, deps) {
|
|
|
853
946
|
})
|
|
854
947
|
|
|
855
948
|
// π Branding header
|
|
856
|
-
lines.push(` ${
|
|
949
|
+
lines.push(` ${themeColors.accent('π')} ${themeColors.accentBold('free-coding-models')} ${themeColors.dim(`v${LOCAL_VERSION}`)}`)
|
|
857
950
|
|
|
858
951
|
if (state.changelogPhase === 'index') {
|
|
859
952
|
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
860
953
|
// π INDEX PHASE: Show all versions with selection
|
|
861
954
|
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
862
|
-
lines.push(` ${
|
|
863
|
-
lines.push(` ${
|
|
955
|
+
lines.push(` ${themeColors.textBold('π Changelog - All Versions')}`)
|
|
956
|
+
lines.push(` ${themeColors.dim('β ββ navigate β’ Enter select β’ Esc close')}`)
|
|
864
957
|
lines.push('')
|
|
865
958
|
|
|
866
959
|
for (let i = 0; i < versionList.length; i++) {
|
|
@@ -896,22 +989,22 @@ export function createOverlayRenderers(state, deps) {
|
|
|
896
989
|
const prefix = ` v${version.padEnd(8)} β ${countStr}`
|
|
897
990
|
if (isSelected) {
|
|
898
991
|
const full = summary ? `${prefix} Β· ${summary}` : prefix
|
|
899
|
-
lines.push(
|
|
992
|
+
lines.push(themeColors.bgCursor(full))
|
|
900
993
|
} else {
|
|
901
|
-
const dimSummary = summary ?
|
|
994
|
+
const dimSummary = summary ? themeColors.dim(` Β· ${summary}`) : ''
|
|
902
995
|
lines.push(`${prefix}${dimSummary}`)
|
|
903
996
|
}
|
|
904
997
|
}
|
|
905
998
|
|
|
906
999
|
lines.push('')
|
|
907
|
-
lines.push(` ${
|
|
1000
|
+
lines.push(` ${themeColors.dim(`Total: ${versionList.length} versions`)}`)
|
|
908
1001
|
|
|
909
1002
|
} else if (state.changelogPhase === 'details') {
|
|
910
1003
|
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
911
1004
|
// π DETAILS PHASE: Show detailed changes for selected version
|
|
912
1005
|
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
913
|
-
lines.push(` ${
|
|
914
|
-
lines.push(` ${
|
|
1006
|
+
lines.push(` ${themeColors.textBold(`π v${state.changelogSelectedVersion}`)}`)
|
|
1007
|
+
lines.push(` ${themeColors.dim('β ββ / PgUp / PgDn scroll β’ B back β’ Esc close')}`)
|
|
915
1008
|
lines.push('')
|
|
916
1009
|
|
|
917
1010
|
const changes = versions[state.changelogSelectedVersion]
|
|
@@ -919,7 +1012,7 @@ export function createOverlayRenderers(state, deps) {
|
|
|
919
1012
|
const sections = { added: 'β¨ Added', fixed: 'π Fixed', changed: 'π Changed', updated: 'π Updated' }
|
|
920
1013
|
for (const [key, label] of Object.entries(sections)) {
|
|
921
1014
|
if (changes[key] && changes[key].length > 0) {
|
|
922
|
-
lines.push(` ${
|
|
1015
|
+
lines.push(` ${themeColors.warning(label)}`)
|
|
923
1016
|
for (const item of changes[key]) {
|
|
924
1017
|
// π Unwrap markdown bold/code markers for display
|
|
925
1018
|
let displayText = item.replace(/\*\*([^*]+)\*\*/g, '$1').replace(/`([^`]+)`/g, '$1')
|
|
@@ -948,10 +1041,9 @@ export function createOverlayRenderers(state, deps) {
|
|
|
948
1041
|
}
|
|
949
1042
|
|
|
950
1043
|
// π Use scrolling with overlay handler
|
|
951
|
-
const CHANGELOG_OVERLAY_BG = chalk.bgRgb(10, 40, 80) // Dark blue background
|
|
952
1044
|
const { visible, offset } = sliceOverlayLines(lines, state.changelogScrollOffset, state.terminalRows)
|
|
953
1045
|
state.changelogScrollOffset = offset
|
|
954
|
-
const tintedLines = tintOverlayLines(visible,
|
|
1046
|
+
const tintedLines = tintOverlayLines(visible, themeColors.overlayBgChangelog, state.terminalCols)
|
|
955
1047
|
const cleared = tintedLines.map(l => l + EL)
|
|
956
1048
|
return cleared.join('\n')
|
|
957
1049
|
}
|
|
@@ -965,6 +1057,7 @@ export function createOverlayRenderers(state, deps) {
|
|
|
965
1057
|
return {
|
|
966
1058
|
renderSettings,
|
|
967
1059
|
renderInstallEndpoints,
|
|
1060
|
+
renderToolInstallPrompt,
|
|
968
1061
|
renderHelp,
|
|
969
1062
|
renderRecommend,
|
|
970
1063
|
renderFeedback,
|