free-coding-models 0.3.5 → 0.3.9

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 CHANGED
@@ -2,6 +2,30 @@
2
2
 
3
3
  ---
4
4
 
5
+ ## 0.3.9
6
+
7
+ ### Improved
8
+ - **Enhanced `--premium` flag**: Now applies strict elite-only constraints. Shows only **S/S+** tier models with perfect health (**UP**) and a good verdict (**Perfect**, **Normal**, or **Slow**). Models with 429 errors, auth failures, or poor performance are automatically hidden.
9
+ - **Accurate Token Usage Tracking**: The "Used" column now uses the persistent `token-stats.json` file as the source of truth, providing accurate historical totals instead of only the most recent logs.
10
+ - **Enhanced Log Transparency**: The request log page now always shows the requested model and the actual upstream model (e.g., `llama-3.1-405b → meta/llama-3.1-405b-instruct`) whenever they differ.
11
+ - **Pretty Provider Labels**: The request log page now uses human-readable provider labels (e.g., "NVIDIA NIM", "SambaNova") instead of raw internal keys.
12
+ - **Fixed Tier Filtering Family Logic**: Updated `--tier S` behavior to correctly include both **S** and **S+** models (matching documentation).
13
+
14
+ ---
15
+
16
+ ## 0.3.6
17
+
18
+ ### Added
19
+ - **AI `/testfcm` workflow**: Added a repo-local PTY runner, workflow doc, slash-command prompts, and artifact/report directories so an agent can drive the real TUI, launch a tool, send `hi`, and write a Markdown bug report with evidence.
20
+ - **Mock tool verification path**: Added a tiny fake `crush` binary plus `test:fcm:mock` so maintainers can validate the TUI → launcher → prompt plumbing even when a real coding tool is not installed locally.
21
+
22
+ ### Fixed
23
+ - **`--json` startup crash**: JSON mode now reuses the same provider-aware ping function as the TUI without crashing on `pingModel is not a function`.
24
+ - **Managed endpoint installs no longer resurrect stale disk entries**: install/refresh saves now replace the tracked `endpointInstalls` snapshot so old provider-tool records from another config state do not leak back into the current catalog set.
25
+ - **Favorites persistence is now much harder to break**: favorite toggles now reload the latest disk config before saving, keep the active profile snapshot in sync, and use atomic config writes so pinned rows no longer disappear after unrelated saves or updates.
26
+ - **API key saves no longer clobber the rest of the config**: editing one provider now persists only that provider against the latest on-disk snapshot, preserves rotated extra keys, and stops stale config writes from wiping other saved keys.
27
+ - **Configured Only no longer hides favorites**: starred rows now stay visible and pinned at the top even when the provider has no currently configured key.
28
+
5
29
  ## 0.3.5
6
30
 
7
31
  ### Fixed
package/README.md CHANGED
@@ -84,6 +84,7 @@ By Vanessa Depraute
84
84
  - **📊 Token usage tracking** — The proxy logs prompt+completion token usage per exact provider/model pair, and the TUI surfaces that history in the `Used` column and the request log overlay.
85
85
  - **📜 Request Log Overlay** — Press `X` to inspect recent proxied requests and token usage for exact provider/model pairs.
86
86
  - **📋 Changelog Overlay** — Press `N` to browse all versions in an index, then `Enter` to view details for any version with full scroll support
87
+ - **🧪 AI end-to-end workflow** — Run the repo-local `/testfcm` flow to drive the TUI in a PTY, launch one tool, send `hi`, and generate a Markdown bug report plus raw artifacts under `task/`
87
88
  - **🛠 MODEL_NOT_FOUND Rotation** — If a specific provider returns a 404 for a model, the TUI intelligently rotates through other available providers for the same model.
88
89
  - **🔄 Auto-retry** — Timeout models keep getting retried, nothing is ever "given up on"
89
90
  - **🎮 Interactive selection** — Navigate with arrow keys directly in the table, press Enter to act
@@ -199,6 +200,30 @@ free-coding-models
199
200
  # Explicitly target OpenCode CLI (TUI + Enter launches OpenCode CLI)
200
201
  free-coding-models --opencode
201
202
 
203
+ ## 📋 CLI Flags (expanded)
204
+
205
+ The tool now supports a comprehensive set of flags to fine‑tune its behavior. All flags can be combined in any order.
206
+
207
+ | Flag | Type | Description |
208
+ |------|------|-------------|
209
+ | `--best` | boolean | Show only top‑tier models (A+, S, S+). |
210
+ | `--fiable` | boolean | Run a 10 s reliability analysis and output the most reliable model. |
211
+ | `--json` | boolean | Output results as JSON for scripting/automation. |
212
+ | `--tier <S|A|B|C>` | value | Filter models by tier family (e.g. `S` shows S+ and S). |
213
+ | `--recommend` | boolean | Open Smart Recommend mode immediately on startup. |
214
+ | `--sort <column>` | value | Sort by a specific column (`rank`, `tier`, `origin`, `model`, `ping`, `avg`, `swe`, `ctx`, `condition`, `verdict`, `uptime`, `stability`, `usage`). |
215
+ | `--desc` / `--asc` | boolean | Set sort direction explicitly (descending or ascending). |
216
+ | `--origin <provider>` | value | Filter models by provider origin (e.g. `nvidia`, `groq`). |
217
+ | `--ping-interval <ms>` | value | Override the ping interval in milliseconds (affects live monitoring speed). |
218
+ | `--hide-unconfigured` | boolean | Hide models whose providers have no configured API key. |
219
+ | `--show-unconfigured` | boolean | Show all models regardless of API key configuration. |
220
+ | `--disable-widths-warning` | boolean | Disable the terminal width warning banner. |
221
+ | `--profile <name>` | value | Load a saved configuration profile before startup. |
222
+ | `--no-telemetry` | boolean | Disable anonymous telemetry for this run. |
223
+ | `--clean-proxy`, `--proxy-clean` | boolean | Remove persisted FCM proxy configuration from OpenCode. |
224
+ | `--help`, `-h` | boolean | Print the complete help text and exit. |
225
+
226
+ These flags are also reflected in the built‑in help (`free-coding-models --help`).
202
227
  # Explicitly target OpenCode Desktop (TUI + Enter sets model & opens Desktop app)
203
228
  free-coding-models --opencode-desktop
204
229
 
@@ -235,6 +260,26 @@ free-coding-models --opencode --best
235
260
  free-coding-models --tier S --json
236
261
  ```
237
262
 
263
+ ### AI E2E workflow (`/testfcm`)
264
+
265
+ For repo-level validation, this project now ships a repeatable AI-driven manual test flow:
266
+
267
+ - Preferred: `pnpm test:fcm -- --tool crush`
268
+ - Fallback when `pnpm` is unavailable: `npm run test:fcm -- --tool crush`
269
+ - Mock plumbing check: `pnpm test:fcm:mock`
270
+
271
+ What it does:
272
+
273
+ 1. Copies your current `~/.free-coding-models.json` into an isolated HOME
274
+ 2. Runs a `--json` preflight to catch obvious startup regressions
275
+ 3. Starts the real TUI in a PTY via the system `expect` command
276
+ 4. Presses `Enter` like a user to launch the chosen tool
277
+ 5. Sends `hi`
278
+ 6. Captures the response, `request-log.jsonl`, daemon logs, and generated tool config
279
+ 7. Writes a Markdown report to `task/reports/` and raw artifacts to `task/artifacts/`
280
+
281
+ The command workflow is documented in [task/TESTFCM-WORKFLOW.md](task/TESTFCM-WORKFLOW.md). Project-local slash commands are also included at [.claude/commands/testfcm.md](.claude/commands/testfcm.md) and [.crush/commands/testfcm.md](.crush/commands/testfcm.md).
282
+
238
283
  ### Choosing the target tool
239
284
 
240
285
  Running `free-coding-models` with no launcher flag starts in **OpenCode CLI** mode.
@@ -318,7 +363,8 @@ Press **`P`** to open the Settings screen at any time:
318
363
 
319
364
  Manual update is in the same Settings screen (`P`) under **Maintenance** (Enter to check, Enter again to install when an update is available).
320
365
  When a newer npm release is known, the main footer also adds a full-width red warning line with the manual recovery command `npm install -g free-coding-models@latest`.
321
- Favorites are also persisted in the same config file and survive restarts.
366
+ Favorites are also persisted in the same config file and survive restarts, app relaunches, and package updates.
367
+ Favorite rows stay pinned at the top and remain visible even when `Configured Only` mode is enabled.
322
368
  The main table now starts in `Configured Only` mode, so if nothing is set up yet you can press `P` and add your first API key immediately.
323
369
 
324
370
  ### Environment variable overrides
@@ -898,6 +944,25 @@ This script:
898
944
 
899
945
  ## 📋 API Reference
900
946
 
947
+ ### 🎁 Premium Flag
948
+
949
+ The `--premium` flag provides a quick view of only the elite **S/S+ tier** models with perfect health (**UP**) and a good verdict (**Perfect**, **Normal**, or **Slow**). This is useful when you want to focus exclusively on the highest‑quality, most reliable models that are currently available.
950
+
951
+ ```bash
952
+ free-coding-models --premium
953
+ ```
954
+
955
+ What it does under the hood:
956
+ - Sets `tierFilter` to `S` (showing only S+ and S tier models).
957
+ - Filters out any model that is not currently **UP** (hides 429, 410, auth fail, timeouts, etc.).
958
+ - Filters out models with poor verdicts (hides **Spiky**, **Very Slow**, **Overloaded**, **Unstable**, etc.).
959
+ - Forces the sort column to `verdict` with ascending order, so the best‑rated models appear at the top.
960
+ - Leaves other settings untouched, so you can still combine it with flags like `--json` for scripting.
961
+
962
+ You can combine `--premium` with other flags (e.g., `--json --hide-unconfigured`) to further tailor the output.
963
+
964
+ ---
965
+
901
966
  **Environment variables (override config file):**
902
967
 
903
968
  | Variable | Description |
@@ -1073,6 +1138,7 @@ Profiles let you save and restore different TUI configurations — useful if you
1073
1138
  **Managing profiles:**
1074
1139
  - Open Settings (**P** key) — scroll down to the **Profiles** section
1075
1140
  - **Enter** on a profile row to load it
1141
+ - While a profile is active, edits to favorites and API keys update that active profile immediately
1076
1142
  - **Backspace** on a profile row to delete it
1077
1143
 
1078
1144
  Profiles are stored inside `~/.free-coding-models.json` under the `profiles` key.
@@ -99,7 +99,7 @@ import { homedir } from 'os'
99
99
  import { join, dirname } from 'path'
100
100
  import { MODELS, sources } from '../sources.js'
101
101
  import { getAvg, getVerdict, getUptime, getP95, getJitter, getStabilityScore, sortResults, filterByTier, findBestModel, parseArgs, TIER_ORDER, VERDICT_ORDER, TIER_LETTER_MAP, scoreModelForTask, getTopRecommendations, TASK_TYPES, PRIORITY_TYPES, CONTEXT_BUDGETS, formatCtxWindow, labelFromId, getProxyStatusInfo, formatResultsAsJSON } from '../src/utils.js'
102
- import { loadConfig, saveConfig, getApiKey, getProxySettings, resolveApiKeys, addApiKey, removeApiKey, isProviderEnabled, saveAsProfile, loadProfile, listProfiles, deleteProfile, getActiveProfileName, setActiveProfile, _emptyProfileSettings } from '../src/config.js'
102
+ import { loadConfig, saveConfig, getApiKey, getProxySettings, resolveApiKeys, addApiKey, removeApiKey, isProviderEnabled, saveAsProfile, loadProfile, listProfiles, deleteProfile, getActiveProfileName, setActiveProfile, _emptyProfileSettings, persistApiKeysForProvider } from '../src/config.js'
103
103
  import { buildMergedModels } from '../src/model-merger.js'
104
104
  import { ProxyServer } from '../src/proxy-server.js'
105
105
  import { loadOpenCodeConfig, saveOpenCodeConfig, syncToOpenCode, restoreOpenCodeBackup, cleanupOpenCodeProxyConfig } from '../src/opencode-sync.js'
@@ -203,6 +203,22 @@ async function main() {
203
203
  // 📖 User declined auto-fix or it failed — continue anyway, just warned
204
204
  }
205
205
 
206
+ // 📖 Apply CLI overrides for settings
207
+ if (cliArgs.sortColumn) config.settings.sortColumn = cliArgs.sortColumn
208
+ if (cliArgs.sortDirection) config.settings.sortAsc = cliArgs.sortDirection === 'asc'
209
+ if (cliArgs.originFilter) config.settings.originFilter = cliArgs.originFilter
210
+ if (cliArgs.pingInterval) config.settings.pingInterval = cliArgs.pingInterval
211
+ if (cliArgs.hideUnconfigured) config.settings.hideUnconfiguredModels = true
212
+ if (cliArgs.showUnconfigured) config.settings.hideUnconfiguredModels = false
213
+ if (cliArgs.disableWidthsWarning) config.settings.disableWidthsWarning = true
214
+
215
+ // 📖 Apply premium mode: show only S‑tier models sorted by verdict
216
+ if (cliArgs.premiumMode) {
217
+ config.settings.tierFilter = 'S'
218
+ config.settings.sortColumn = 'verdict'
219
+ config.settings.sortAsc = true
220
+ }
221
+
206
222
  if (cliArgs.cleanProxyMode) {
207
223
  const cleaned = cleanupOpenCodeProxyConfig()
208
224
  console.log()
@@ -307,7 +323,10 @@ async function main() {
307
323
  console.error(chalk.red(` Unknown profile "${cliArgs.profileName}". Available: ${listProfiles(config).join(', ') || '(none)'}`))
308
324
  process.exit(1)
309
325
  }
310
- saveConfig(config)
326
+ saveConfig(config, {
327
+ replaceApiKeys: true,
328
+ replaceFavorites: true,
329
+ })
311
330
  }
312
331
 
313
332
  // 📖 Check if any provider has a key — if not, run the first-time setup wizard
@@ -496,8 +515,9 @@ async function main() {
496
515
  mode, // 📖 'opencode' or 'openclaw' — controls Enter action
497
516
  tierFilterMode: 0, // 📖 Index into TIER_CYCLE (0=All, 1=S+, 2=S, ...)
498
517
  originFilterMode: 0, // 📖 Index into ORIGIN_CYCLE (0=All, then providers)
499
- hideUnconfiguredModels: startupProfileSettings?.hideUnconfiguredModels === true || config.settings?.hideUnconfiguredModels === true, // 📖 Hide providers with no configured API key when true.
500
- disableWidthsWarning: config.settings?.disableWidthsWarning ?? false, // 📖 Disable widths warning toggle (default off)
518
+ premiumMode: cliArgs.premiumMode, // 📖 Special elite-only mode: S/S+ only, Health UP only, Perfect/Normal/Slow verdict only.
519
+ hideUnconfiguredModels: startupProfileSettings?.hideUnconfiguredModels === true || config.settings?.hideUnconfiguredModels === true, // 📖 Hide providers with no configured API key when true.
520
+ disableWidthsWarning: config.settings?.disableWidthsWarning ?? false, // 📖 Disable widths warning toggle (default off)
501
521
  scrollOffset: 0, // 📖 First visible model index in viewport
502
522
  terminalRows: process.stdout.rows || 24, // 📖 Current terminal height
503
523
  terminalCols: process.stdout.columns || 80, // 📖 Current terminal width
@@ -674,6 +694,52 @@ hideUnconfiguredModels: startupProfileSettings?.hideUnconfiguredModels === true
674
694
  }
675
695
  }
676
696
 
697
+ // 📖 Define pingModel before JSON mode so `--json` can reuse the same provider-aware
698
+ // 📖 ping path as the interactive TUI without waiting for the PTY/render loop setup.
699
+ pingModel = async (r) => {
700
+ state.pendingPings += 1
701
+ r.isPinging = true
702
+
703
+ try {
704
+ const providerApiKey = getApiKey(state.config, r.providerKey) ?? null
705
+ const providerUrl = sources[r.providerKey]?.url ?? sources.nvidia.url
706
+ let { code, ms, quotaPercent } = await ping(providerApiKey, r.modelId, r.providerKey, providerUrl)
707
+
708
+ if ((quotaPercent === null || quotaPercent === undefined) && providerApiKey) {
709
+ const providerQuota = await getProviderQuotaPercentCached(r.providerKey, providerApiKey)
710
+ if (typeof providerQuota === 'number' && Number.isFinite(providerQuota)) {
711
+ quotaPercent = providerQuota
712
+ }
713
+ }
714
+
715
+ r.pings.push({ ms, code })
716
+
717
+ if (code === '200') {
718
+ r.status = 'up'
719
+ } else if (code === '000') {
720
+ r.status = 'timeout'
721
+ } else if (code === '401' || code === '403') {
722
+ r.status = providerApiKey ? 'auth_error' : 'noauth'
723
+ r.httpCode = code
724
+ } else {
725
+ r.status = 'down'
726
+ r.httpCode = code
727
+ }
728
+
729
+ if (typeof quotaPercent === 'number' && Number.isFinite(quotaPercent)) {
730
+ r.usagePercent = quotaPercent
731
+ for (const sibling of state.results) {
732
+ if (sibling.providerKey === r.providerKey && (sibling.usagePercent === undefined || sibling.usagePercent === null)) {
733
+ sibling.usagePercent = quotaPercent
734
+ }
735
+ }
736
+ }
737
+ } finally {
738
+ r.isPinging = false
739
+ state.pendingPings = Math.max(0, state.pendingPings - 1)
740
+ }
741
+ }
742
+
677
743
  // 📖 JSON output mode: skip TUI, output results as JSON after initial pings
678
744
  if (cliArgs.jsonMode) {
679
745
  console.log(chalk.cyan(' ⚡ Pinging models for JSON output...'))
@@ -707,6 +773,17 @@ hideUnconfiguredModels: startupProfileSettings?.hideUnconfiguredModels === true
707
773
  outputResults = outputResults.filter(r => ['S+', 'S', 'A+'].includes(r.tier))
708
774
  }
709
775
 
776
+ // 📖 Apply premium mode filter if specified: elite-only (S/S+, UP, Good Verdict)
777
+ if (cliArgs.premiumMode) {
778
+ outputResults = outputResults.filter(r => {
779
+ const isEliteTier = r.tier === 'S' || r.tier === 'S+'
780
+ const isHealthUp = r.status === 'up'
781
+ const verdict = getVerdict(r)
782
+ const isGoodVerdict = ['Perfect', 'Normal', 'Slow'].includes(verdict)
783
+ return isEliteTier && isHealthUp && isGoodVerdict
784
+ })
785
+ }
786
+
710
787
  // 📖 Sort by avg ping (ascending)
711
788
  outputResults = sortResults(outputResults, 'avg', 'asc')
712
789
 
@@ -745,20 +822,34 @@ hideUnconfiguredModels: startupProfileSettings?.hideUnconfiguredModels === true
745
822
  const activeTier = TIER_CYCLE[state.tierFilterMode]
746
823
  const activeOrigin = ORIGIN_CYCLE[state.originFilterMode]
747
824
  state.results.forEach(r => {
825
+ // 📖 Favorites stay visible and pinned regardless of configured-only, tier, or provider filters.
826
+ if (r.isFavorite) {
827
+ r.hidden = false
828
+ return
829
+ }
748
830
  const unconfiguredHide = state.hideUnconfiguredModels && !getApiKey(state.config, r.providerKey)
749
831
  if (unconfiguredHide) {
750
832
  r.hidden = true
751
833
  return
752
834
  }
753
- // 📖 Favorites stay visible regardless of tier/origin filters.
754
- if (r.isFavorite) {
755
- r.hidden = false
756
- return
757
- }
758
835
  // 📖 Apply both tier and origin filters — model is hidden if it fails either
759
- const tierHide = activeTier !== null && r.tier !== activeTier
836
+ // 📖 TIER_LETTER_MAP is used so --tier S also includes S+ models (tier family behavior).
837
+ const allowedTiers = (activeTier && TIER_LETTER_MAP[activeTier]) ? TIER_LETTER_MAP[activeTier] : [activeTier]
838
+ const tierHide = activeTier !== null && !allowedTiers.includes(r.tier)
760
839
  const originHide = activeOrigin !== null && r.providerKey !== activeOrigin
761
840
  r.hidden = tierHide || originHide
841
+
842
+ // 📖 Premium Mode: elite-only constraints (Health UP, Good Verdict, S/S+ only)
843
+ if (state.premiumMode && !r.hidden) {
844
+ const isEliteTier = r.tier === 'S' || r.tier === 'S+'
845
+ const isHealthUp = r.status === 'up'
846
+ const verdict = getVerdict(r)
847
+ const isGoodVerdict = ['Perfect', 'Normal', 'Slow'].includes(verdict)
848
+
849
+ if (!isEliteTier || !isHealthUp || !isGoodVerdict) {
850
+ r.hidden = true
851
+ }
852
+ }
762
853
  })
763
854
  return state.results
764
855
  }
@@ -826,6 +917,7 @@ hideUnconfiguredModels: startupProfileSettings?.hideUnconfiguredModels === true
826
917
  resolveApiKeys,
827
918
  addApiKey,
828
919
  removeApiKey,
920
+ persistApiKeysForProvider,
829
921
  isProviderEnabled,
830
922
  listProfiles,
831
923
  loadProfile,
@@ -954,60 +1046,6 @@ hideUnconfiguredModels: startupProfileSettings?.hideUnconfiguredModels === true
954
1046
 
955
1047
  // ── Continuous ping loop — ping all models every N seconds forever ──────────
956
1048
 
957
- // 📖 Single ping function that updates result
958
- // 📖 Uses per-provider API key and URL from sources.js
959
- // 📖 If no API key is configured, pings without auth — a 401 still tells us latency + server is up
960
- pingModel = async (r) => {
961
- state.pendingPings += 1
962
- r.isPinging = true
963
-
964
- try {
965
- const providerApiKey = getApiKey(state.config, r.providerKey) ?? null
966
- const providerUrl = sources[r.providerKey]?.url ?? sources.nvidia.url
967
- let { code, ms, quotaPercent } = await ping(providerApiKey, r.modelId, r.providerKey, providerUrl)
968
-
969
- if ((quotaPercent === null || quotaPercent === undefined) && providerApiKey) {
970
- const providerQuota = await getProviderQuotaPercentCached(r.providerKey, providerApiKey)
971
- if (typeof providerQuota === 'number' && Number.isFinite(providerQuota)) {
972
- quotaPercent = providerQuota
973
- }
974
- }
975
-
976
- // 📖 Store ping result as object with ms and code
977
- // 📖 ms = actual response time (even for errors like 429)
978
- // 📖 code = HTTP status code ('200', '429', '500', '000' for timeout)
979
- r.pings.push({ ms, code })
980
-
981
- // 📖 Update status based on latest ping
982
- if (code === '200') {
983
- r.status = 'up'
984
- } else if (code === '000') {
985
- r.status = 'timeout'
986
- } else if (code === '401' || code === '403') {
987
- // 📖 Distinguish "no key configured" from "configured key rejected" so the
988
- // 📖 Health column stays honest when Configured Only mode is enabled.
989
- r.status = providerApiKey ? 'auth_error' : 'noauth'
990
- r.httpCode = code
991
- } else {
992
- r.status = 'down'
993
- r.httpCode = code
994
- }
995
-
996
- if (typeof quotaPercent === 'number' && Number.isFinite(quotaPercent)) {
997
- r.usagePercent = quotaPercent
998
- // Provider-level fallback: apply latest known quota to sibling rows on same provider.
999
- for (const sibling of state.results) {
1000
- if (sibling.providerKey === r.providerKey && (sibling.usagePercent === undefined || sibling.usagePercent === null)) {
1001
- sibling.usagePercent = quotaPercent
1002
- }
1003
- }
1004
- }
1005
- } finally {
1006
- r.isPinging = false
1007
- state.pendingPings = Math.max(0, state.pendingPings - 1)
1008
- }
1009
- }
1010
-
1011
1049
  // 📖 Initial ping of all models
1012
1050
  const initialPing = Promise.all(state.results.map(r => pingModel(r)))
1013
1051
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "free-coding-models",
3
- "version": "0.3.5",
3
+ "version": "0.3.9",
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",
@@ -51,7 +51,9 @@
51
51
  ],
52
52
  "scripts": {
53
53
  "start": "node bin/free-coding-models.js",
54
- "test": "node --test test/test.js"
54
+ "test": "node --test test/test.js",
55
+ "test:fcm": "node scripts/testfcm-runner.mjs",
56
+ "test:fcm:mock": "node scripts/testfcm-runner.mjs --tool crush --tool-bin-dir test/fixtures/mock-bin"
55
57
  },
56
58
  "dependencies": {
57
59
  "chalk": "^5.4.1"
package/src/cli-help.js CHANGED
@@ -26,6 +26,14 @@ const ANALYSIS_FLAGS = [
26
26
  { flag: '--json', description: 'Output results as JSON for scripts/automation' },
27
27
  { flag: '--tier <S|A|B|C>', description: 'Filter models by tier family' },
28
28
  { flag: '--recommend', description: 'Open Smart Recommend immediately on startup' },
29
+ { flag: '--premium', description: 'Show only S/S+ models with perfect health and good verdict' },
30
+ { flag: '--sort <column>', description: 'Sort by column (rank, tier, origin, model, ping, avg, swe, ctx, condition, verdict, uptime, stability, usage)' },
31
+ { flag: '--desc | --asc', description: 'Set sort direction (descending or ascending)' },
32
+ { flag: '--origin <provider>', description: 'Filter models by provider origin' },
33
+ { flag: '--ping-interval <ms>', description: 'Override ping interval in milliseconds' },
34
+ { flag: '--hide-unconfigured', description: 'Hide models without configured API keys' },
35
+ { flag: '--show-unconfigured', description: 'Show all models regardless of API key config' },
36
+ { flag: '--disable-widths-warning', description: 'Disable terminal width warning' },
29
37
  ]
30
38
 
31
39
  const CONFIG_FLAGS = [