free-coding-models 0.2.1 β 0.2.3
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/README.md +19 -2
- package/bin/free-coding-models.js +36 -9
- package/package.json +1 -1
- package/sources.js +1 -1
- package/src/config.js +42 -2
- package/src/endpoint-installer.js +459 -0
- package/src/key-handler.js +336 -16
- package/src/opencode.js +2 -2
- package/src/overlays.js +204 -3
- package/src/provider-metadata.js +3 -1
- package/src/render-table.js +9 -2
- package/src/utils.js +4 -2
package/README.md
CHANGED
|
@@ -84,6 +84,7 @@
|
|
|
84
84
|
- **π» OpenCode integration** β Auto-detects NIM setup, sets model as default, launches OpenCode
|
|
85
85
|
- **π¦ OpenClaw integration** β Sets selected model as default provider in `~/.openclaw/openclaw.json`
|
|
86
86
|
- **π§° Public tool launchers** β `Enter` can auto-configure and launch `OpenCode CLI`, `OpenCode Desktop`, `OpenClaw`, `Crush`, and `Goose`
|
|
87
|
+
- **π Install Endpoints flow** β Press `Y` to install one configured provider directly into `OpenCode CLI`, `OpenCode Desktop`, `OpenClaw`, `Crush`, or `Goose`, either with the full provider catalog or a curated subset of models
|
|
87
88
|
- **π Feature Request (J key)** β Send anonymous feedback directly to the project team
|
|
88
89
|
- **π Bug Report (I key)** β Send anonymous bug reports directly to the project team
|
|
89
90
|
- **π¨ Clean output** β Zero scrollback pollution, interface stays open until Ctrl+C
|
|
@@ -446,7 +447,7 @@ The main table displays one row per model with the following columns:
|
|
|
446
447
|
| Column | Sort key | Description |
|
|
447
448
|
|--------|----------|-------------|
|
|
448
449
|
| **Rank** | `R` | Position based on current sort order (medals for top 3: π₯π₯π₯) |
|
|
449
|
-
| **Tier** |
|
|
450
|
+
| **Tier** | β | SWE-bench tier (S+, S, A+, A, A-, B+, B, C) |
|
|
450
451
|
| **SWE%** | `S` | SWE-bench Verified score β industry-standard for coding |
|
|
451
452
|
| **CTX** | `C` | Context window size (e.g. `128k`) |
|
|
452
453
|
| **Model** | `M` | Model display name (favorites show β prefix) |
|
|
@@ -844,7 +845,7 @@ This script:
|
|
|
844
845
|
**Keyboard shortcuts (main TUI):**
|
|
845
846
|
- **ββ** β Navigate models
|
|
846
847
|
- **Enter** β Select model and launch the current target tool from the header badge
|
|
847
|
-
- **R/
|
|
848
|
+
- **R/S/C/M/O/L/A/H/V/B/U/G** β Sort by Rank/SWE/Ctx/Model/Provider/Latest/Avg/Health/Verdict/Stability/Up%/Usage
|
|
848
849
|
- **F** β Toggle favorite on selected model (β in Model column, pinned at top)
|
|
849
850
|
- **T** β Cycle tier filter (All β S+ β S β A+ β A β A- β B+ β B β C β All)
|
|
850
851
|
- **D** β Cycle provider filter (All β NIM β Groq β ...)
|
|
@@ -852,6 +853,7 @@ This script:
|
|
|
852
853
|
- **Z** β Cycle target tool (OpenCode CLI β OpenCode Desktop β OpenClaw β Crush β Goose)
|
|
853
854
|
- **X** β Toggle request logs (recent proxied request/token usage logs)
|
|
854
855
|
- **P** β Open Settings (manage API keys, toggles, updates, profiles)
|
|
856
|
+
- **Y** β Open Install Endpoints (`provider β tool β all models` or `selected models only`, no proxy)
|
|
855
857
|
- **Shift+P** β Cycle through saved profiles (switches live TUI settings)
|
|
856
858
|
- **Shift+S** β Save current TUI settings as a named profile (inline prompt)
|
|
857
859
|
- **Q** β Open Smart Recommend overlay (find the best model for your task)
|
|
@@ -862,6 +864,21 @@ This script:
|
|
|
862
864
|
|
|
863
865
|
Pressing **K** now shows a full in-app reference: main hotkeys, settings hotkeys, and CLI flags with usage examples.
|
|
864
866
|
|
|
867
|
+
### π Install Endpoints (`Y`)
|
|
868
|
+
|
|
869
|
+
`Y` opens a dedicated install flow for configured providers. The flow is:
|
|
870
|
+
|
|
871
|
+
1. Pick one provider that already has an API key in Settings
|
|
872
|
+
2. Pick the target tool: `OpenCode CLI`, `OpenCode Desktop`, `OpenClaw`, `Crush`, or `Goose`
|
|
873
|
+
3. Choose either `Install all models` or `Install selected models only`
|
|
874
|
+
|
|
875
|
+
Important behavior:
|
|
876
|
+
|
|
877
|
+
- Installs are written directly into the target tool config as FCM-managed entries, without going through `fcm-proxy`
|
|
878
|
+
- `Install all models` is the recommended path because FCM can refresh that catalog automatically on later launches when the provider model list changes
|
|
879
|
+
- `Install selected models only` is useful when you want a smaller curated picker inside the target tool
|
|
880
|
+
- `OpenCode CLI` and `OpenCode Desktop` share the same `opencode.json`, so the managed provider appears in both
|
|
881
|
+
|
|
865
882
|
**Keyboard shortcuts (Settings screen β `P` key):**
|
|
866
883
|
- **ββ** β Navigate providers, maintenance row, and profile rows
|
|
867
884
|
- **Enter** β Edit API key inline, check/install update, or load a profile
|
|
@@ -20,10 +20,11 @@
|
|
|
20
20
|
* - Automatic config detection and model setup for both tools
|
|
21
21
|
* - JSON config stored in ~/.free-coding-models.json (auto-migrates from old plain-text)
|
|
22
22
|
* - Multi-provider support via sources.js (NIM/Groq/Cerebras/OpenRouter/Hugging Face/Replicate/DeepInfra/... β extensible)
|
|
23
|
-
* - Settings screen (P key) to manage API keys, provider toggles, and
|
|
23
|
+
* - Settings screen (P key) to manage API keys, provider toggles, manual updates, and provider-key diagnostics
|
|
24
|
+
* - Install Endpoints flow (Y key) to push provider catalogs into OpenCode, OpenClaw, Crush, and Goose
|
|
24
25
|
* - Favorites system: toggle with F, pin rows to top, persist between sessions
|
|
25
26
|
* - Uptime percentage tracking (successful pings / total pings)
|
|
26
|
-
* - Sortable columns (R/
|
|
27
|
+
* - Sortable columns (R/O/M/L/A/S/C/H/V/B/U/G keys)
|
|
27
28
|
* - Tier filtering via T key (cycles S+βSβA+βAβA-βB+βBβCβAll)
|
|
28
29
|
*
|
|
29
30
|
* β Functions:
|
|
@@ -118,6 +119,7 @@ import { createOverlayRenderers } from '../src/overlays.js'
|
|
|
118
119
|
import { createKeyHandler } from '../src/key-handler.js'
|
|
119
120
|
import { getToolModeOrder } from '../src/tool-metadata.js'
|
|
120
121
|
import { startExternalTool } from '../src/tool-launchers.js'
|
|
122
|
+
import { getConfiguredInstallableProviders, installProviderEndpoints, refreshInstalledEndpoints, getInstallTargetModes, getProviderCatalogModels } from '../src/endpoint-installer.js'
|
|
121
123
|
|
|
122
124
|
// π mergedModels: cross-provider grouped model list (one entry per label, N providers each)
|
|
123
125
|
// π mergedModelByLabel: fast lookup map from display label β merged model entry
|
|
@@ -310,6 +312,10 @@ async function main() {
|
|
|
310
312
|
console.log(chalk.yellow(' OpenRouter: using cached model list (live fetch failed)'))
|
|
311
313
|
}
|
|
312
314
|
|
|
315
|
+
// π Re-sync tracked external-tool catalogs after the live provider catalog has settled.
|
|
316
|
+
// π This keeps prior `Y` installs aligned with the current FCM model list.
|
|
317
|
+
refreshInstalledEndpoints(config)
|
|
318
|
+
|
|
313
319
|
// π Build results from MODELS β only include enabled providers
|
|
314
320
|
// π Each result gets providerKey so ping() knows which URL + API key to use
|
|
315
321
|
|
|
@@ -393,7 +399,8 @@ async function main() {
|
|
|
393
399
|
settingsAddKeyMode: false, // π Whether we're in add-key mode (append a new key to provider)
|
|
394
400
|
settingsEditBuffer: '', // π Typed characters for the API key being edited
|
|
395
401
|
settingsErrorMsg: null, // π Temporary error message to display in settings
|
|
396
|
-
settingsTestResults: {}, // π { providerKey: 'pending'|'ok'|'fail'|null }
|
|
402
|
+
settingsTestResults: {}, // π { providerKey: 'pending'|'ok'|'auth_error'|'rate_limited'|'no_callable_model'|'fail'|'missing_key'|null }
|
|
403
|
+
settingsTestDetails: {}, // π Long-form diagnostics shown under Setup Instructions after a Settings key test.
|
|
397
404
|
settingsUpdateState: 'idle', // π 'idle'|'checking'|'available'|'up-to-date'|'error'|'installing'
|
|
398
405
|
settingsUpdateLatestVersion: null, // π Latest npm version discovered from manual check
|
|
399
406
|
settingsUpdateError: null, // π Last update-check error message for maintenance row
|
|
@@ -404,6 +411,17 @@ async function main() {
|
|
|
404
411
|
helpVisible: false, // π Whether the help overlay (K key) is active
|
|
405
412
|
settingsScrollOffset: 0, // π Vertical scroll offset for Settings overlay viewport
|
|
406
413
|
helpScrollOffset: 0, // π Vertical scroll offset for Help overlay viewport
|
|
414
|
+
// π Install Endpoints overlay state (Y key opens it)
|
|
415
|
+
installEndpointsOpen: false, // π Whether the install-endpoints overlay is active
|
|
416
|
+
installEndpointsPhase: 'providers', // π providers | tools | scope | models | result
|
|
417
|
+
installEndpointsCursor: 0, // π Selected row within the current install phase
|
|
418
|
+
installEndpointsScrollOffset: 0, // π Vertical scroll offset for the install overlay viewport
|
|
419
|
+
installEndpointsProviderKey: null, // π Selected provider for endpoint installation
|
|
420
|
+
installEndpointsToolMode: null, // π Selected target tool mode
|
|
421
|
+
installEndpointsScope: null, // π all | selected
|
|
422
|
+
installEndpointsSelectedModelIds: new Set(), // π Multi-select buffer for the selected-models phase
|
|
423
|
+
installEndpointsErrorMsg: null, // π Temporary validation/error message inside the install flow
|
|
424
|
+
installEndpointsResult: null, // π Final install result shown in the result phase
|
|
407
425
|
// π Smart Recommend overlay state (Q key opens it)
|
|
408
426
|
recommendOpen: false, // π Whether the recommend overlay is active
|
|
409
427
|
recommendPhase: 'questionnaire', // π 'questionnaire'|'analyzing'|'results' β current phase
|
|
@@ -595,7 +613,10 @@ async function main() {
|
|
|
595
613
|
toFavoriteKey,
|
|
596
614
|
getTopRecommendations,
|
|
597
615
|
adjustScrollOffset,
|
|
598
|
-
getPingModel: () => pingModel
|
|
616
|
+
getPingModel: () => pingModel,
|
|
617
|
+
getConfiguredInstallableProviders,
|
|
618
|
+
getInstallTargetModes,
|
|
619
|
+
getProviderCatalogModels,
|
|
599
620
|
})
|
|
600
621
|
|
|
601
622
|
onKeyPress = createKeyHandler({
|
|
@@ -616,6 +637,10 @@ async function main() {
|
|
|
616
637
|
saveAsProfile,
|
|
617
638
|
setActiveProfile,
|
|
618
639
|
saveConfig,
|
|
640
|
+
getConfiguredInstallableProviders,
|
|
641
|
+
getInstallTargetModes,
|
|
642
|
+
getProviderCatalogModels,
|
|
643
|
+
installProviderEndpoints,
|
|
619
644
|
syncFavoriteFlags,
|
|
620
645
|
toggleFavoriteModel,
|
|
621
646
|
sortResultsWithPinnedFavorites,
|
|
@@ -689,12 +714,14 @@ async function main() {
|
|
|
689
714
|
refreshAutoPingMode()
|
|
690
715
|
state.frame++
|
|
691
716
|
// π Cache visible+sorted models each frame so Enter handler always matches the display
|
|
692
|
-
if (!state.settingsOpen && !state.recommendOpen && !state.featureRequestOpen && !state.bugReportOpen) {
|
|
717
|
+
if (!state.settingsOpen && !state.installEndpointsOpen && !state.recommendOpen && !state.featureRequestOpen && !state.bugReportOpen) {
|
|
693
718
|
const visible = state.results.filter(r => !r.hidden)
|
|
694
719
|
state.visibleSorted = sortResultsWithPinnedFavorites(visible, state.sortColumn, state.sortDirection)
|
|
695
720
|
}
|
|
696
721
|
const content = state.settingsOpen
|
|
697
722
|
? overlays.renderSettings()
|
|
723
|
+
: state.installEndpointsOpen
|
|
724
|
+
? overlays.renderInstallEndpoints()
|
|
698
725
|
: state.recommendOpen
|
|
699
726
|
? overlays.renderRecommend()
|
|
700
727
|
: state.featureRequestOpen
|
|
@@ -758,10 +785,10 @@ async function main() {
|
|
|
758
785
|
r.status = 'up'
|
|
759
786
|
} else if (code === '000') {
|
|
760
787
|
r.status = 'timeout'
|
|
761
|
-
} else if (code === '401') {
|
|
762
|
-
// π
|
|
763
|
-
// π
|
|
764
|
-
r.status = 'noauth'
|
|
788
|
+
} else if (code === '401' || code === '403') {
|
|
789
|
+
// π Distinguish "no key configured" from "configured key rejected" so the
|
|
790
|
+
// π Health column stays honest when Configured Only mode is enabled.
|
|
791
|
+
r.status = providerApiKey ? 'auth_error' : 'noauth'
|
|
765
792
|
r.httpCode = code
|
|
766
793
|
} else {
|
|
767
794
|
r.status = 'down'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "free-coding-models",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "Find the fastest coding LLM models in seconds β ping free models from multiple providers, pick the best one for OpenCode, Cursor, or any AI coding assistant.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"nvidia",
|
package/sources.js
CHANGED
package/src/config.js
CHANGED
|
@@ -64,7 +64,10 @@
|
|
|
64
64
|
* "work": { "apiKeys": {...}, "providers": {...}, "favorites": [...], "settings": {...} },
|
|
65
65
|
* "personal": { "apiKeys": {...}, "providers": {...}, "favorites": [...], "settings": {...} },
|
|
66
66
|
* "fast": { "apiKeys": {...}, "providers": {...}, "favorites": [...], "settings": {...} }
|
|
67
|
-
* }
|
|
67
|
+
* },
|
|
68
|
+
* "endpointInstalls": [
|
|
69
|
+
* { "providerKey": "nvidia", "toolMode": "opencode", "scope": "all", "modelIds": [], "lastSyncedAt": "2026-03-09T10:00:00.000Z" }
|
|
70
|
+
* ]
|
|
68
71
|
* }
|
|
69
72
|
*
|
|
70
73
|
* π Profiles store a snapshot of the user's configuration. Each profile contains:
|
|
@@ -97,11 +100,12 @@
|
|
|
97
100
|
* β setActiveProfile(config, name) β Set which profile is active (null to clear)
|
|
98
101
|
* β _emptyProfileSettings() β Default TUI settings for a profile
|
|
99
102
|
* β getProxySettings(config) β Return normalized proxy settings from config
|
|
103
|
+
* β normalizeEndpointInstalls(endpointInstalls) β Keep tracked endpoint installs stable across app versions
|
|
100
104
|
*
|
|
101
105
|
* @exports loadConfig, saveConfig, getApiKey, isProviderEnabled
|
|
102
106
|
* @exports addApiKey, removeApiKey, listApiKeys β multi-key management helpers
|
|
103
107
|
* @exports saveAsProfile, loadProfile, listProfiles, deleteProfile
|
|
104
|
-
* @exports getActiveProfileName, setActiveProfile, getProxySettings
|
|
108
|
+
* @exports getActiveProfileName, setActiveProfile, getProxySettings, normalizeEndpointInstalls
|
|
105
109
|
* @exports CONFIG_PATH β path to the JSON config file
|
|
106
110
|
*
|
|
107
111
|
* @see bin/free-coding-models.js β main CLI that uses these functions
|
|
@@ -175,6 +179,7 @@ export function loadConfig() {
|
|
|
175
179
|
if (typeof parsed.telemetry.enabled !== 'boolean') parsed.telemetry.enabled = null
|
|
176
180
|
if (typeof parsed.telemetry.consentVersion !== 'number') parsed.telemetry.consentVersion = 0
|
|
177
181
|
if (typeof parsed.telemetry.anonymousId !== 'string' || !parsed.telemetry.anonymousId.trim()) parsed.telemetry.anonymousId = null
|
|
182
|
+
parsed.endpointInstalls = normalizeEndpointInstalls(parsed.endpointInstalls)
|
|
178
183
|
// π Ensure profiles section exists (added in profile system)
|
|
179
184
|
if (!parsed.profiles || typeof parsed.profiles !== 'object') parsed.profiles = {}
|
|
180
185
|
for (const profile of Object.values(parsed.profiles)) {
|
|
@@ -439,6 +444,39 @@ export function getProxySettings(config) {
|
|
|
439
444
|
return normalizeProxySettings(config?.settings?.proxy)
|
|
440
445
|
}
|
|
441
446
|
|
|
447
|
+
/**
|
|
448
|
+
* π normalizeEndpointInstalls keeps the endpoint-install tracking list safe to replay.
|
|
449
|
+
*
|
|
450
|
+
* π Each entry represents one managed catalog install performed through the `Y` flow:
|
|
451
|
+
* - `providerKey`: FCM provider identifier (`nvidia`, `groq`, ...)
|
|
452
|
+
* - `toolMode`: canonical tool id (`opencode`, `openclaw`, `crush`, `goose`)
|
|
453
|
+
* - `scope`: `all` or `selected`
|
|
454
|
+
* - `modelIds`: only used when `scope === 'selected'`
|
|
455
|
+
* - `lastSyncedAt`: informational timestamp updated on successful refresh
|
|
456
|
+
*
|
|
457
|
+
* @param {unknown} endpointInstalls
|
|
458
|
+
* @returns {{ providerKey: string, toolMode: string, scope: 'all'|'selected', modelIds: string[], lastSyncedAt: string | null }[]}
|
|
459
|
+
*/
|
|
460
|
+
export function normalizeEndpointInstalls(endpointInstalls) {
|
|
461
|
+
if (!Array.isArray(endpointInstalls)) return []
|
|
462
|
+
return endpointInstalls
|
|
463
|
+
.map((entry) => {
|
|
464
|
+
if (!entry || typeof entry !== 'object') return null
|
|
465
|
+
const providerKey = typeof entry.providerKey === 'string' ? entry.providerKey.trim() : ''
|
|
466
|
+
const toolMode = typeof entry.toolMode === 'string' ? entry.toolMode.trim() : ''
|
|
467
|
+
if (!providerKey || !toolMode) return null
|
|
468
|
+
const scope = entry.scope === 'selected' ? 'selected' : 'all'
|
|
469
|
+
const modelIds = Array.isArray(entry.modelIds)
|
|
470
|
+
? [...new Set(entry.modelIds.filter((modelId) => typeof modelId === 'string' && modelId.trim().length > 0))]
|
|
471
|
+
: []
|
|
472
|
+
const lastSyncedAt = typeof entry.lastSyncedAt === 'string' && entry.lastSyncedAt.trim().length > 0
|
|
473
|
+
? entry.lastSyncedAt
|
|
474
|
+
: null
|
|
475
|
+
return { providerKey, toolMode, scope, modelIds, lastSyncedAt }
|
|
476
|
+
})
|
|
477
|
+
.filter(Boolean)
|
|
478
|
+
}
|
|
479
|
+
|
|
442
480
|
/**
|
|
443
481
|
* π saveAsProfile: Snapshot the current config state into a named profile.
|
|
444
482
|
*
|
|
@@ -563,6 +601,8 @@ function _emptyConfig() {
|
|
|
563
601
|
consentVersion: 0,
|
|
564
602
|
anonymousId: null,
|
|
565
603
|
},
|
|
604
|
+
// π Tracked `Y` installs β used to refresh external tool catalogs automatically.
|
|
605
|
+
endpointInstalls: [],
|
|
566
606
|
// π Active profile name β null means no profile is loaded (using raw config).
|
|
567
607
|
activeProfile: null,
|
|
568
608
|
// π Named profiles: each is a snapshot of apiKeys + providers + favorites + settings.
|