free-coding-models 0.5.10 → 0.5.11

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 CHANGED
@@ -338,6 +338,40 @@ Configure tools with:
338
338
 
339
339
  The daemon auto-creates a `fast-coding` set from your configured providers on first start. It stores router settings in `~/.free-coding-models.json`, writes lifecycle logs to `~/.free-coding-models-daemon.log`, and tracks token metadata in `~/.free-coding-models-tokens.json`.
340
340
 
341
+ ### Playground — chat with the router
342
+
343
+ Every chat that goes through the FCM router starts with a configurable **pre-prompt** that introduces the assistant as the free-coding-models routing agent. The Playground is the fastest way to try the router without configuring a coding tool.
344
+
345
+ ```bash
346
+ # 1. Start the router (if it isn't already)
347
+ free-coding-models --daemon-bg
348
+
349
+ # 2. Open the Playground in the TUI
350
+ free-coding-models --playground
351
+ # ... or just press ; inside the TUI
352
+ # ... or click "Playground" in the web dashboard header
353
+ ```
354
+
355
+ The Playground:
356
+
357
+ - Streams responses token-by-token (SSE).
358
+ - Shows the routed-via provider/model + latency + tokens on every reply.
359
+ - Lets you pin a specific model (`fcm` = auto-router, or `groq/<id>` / `cerebras/<id>` / etc.) for manual A/B testing.
360
+ - Lets you toggle the pre-prompt per session, so you can see what the model answers *with* and *without* the FCM persona.
361
+
362
+ The pre-prompt lives in the router config under `router.prePrompt` and can be edited from any surface (the daemon reloads it on its 10s config-refresh tick):
363
+
364
+ ```json
365
+ {
366
+ "router": {
367
+ "prePrompt": {
368
+ "enabled": true,
369
+ "text": "You are free-coding-models, the free coding-model routing agent..."
370
+ }
371
+ }
372
+ }
373
+ ```
374
+
341
375
  Router endpoints:
342
376
 
343
377
  | Endpoint | Purpose |
@@ -464,6 +498,7 @@ When a tool mode is active (via `Z`), models incompatible with that tool are hig
464
498
  | `X` | Clear active custom text filter |
465
499
  | `G` | Cycle global theme (`Auto → Dark → Light`) |
466
500
  | `Ctrl+P` | Open ⚡️ command palette (search + run actions) |
501
+ | `;` | Open the Playground chat overlay (chat with the FCM router) |
467
502
  | `Ctrl+A` | Run AI Speed Test for the selected model |
468
503
  | `Ctrl+U` | Run Global AI Speed Test (uses real provider requests) |
469
504
  | `R/S/C/M/O/L/A/H/V/B/U` | Sort columns |
@@ -510,6 +545,8 @@ When a tool mode is active (via `Z`), models incompatible with that tool are hig
510
545
  - **Keyless latency** — models ping even without an API key (show 🔑 NO KEY)
511
546
  - **Smart Recommend** — questionnaire picks the best model for your task type
512
547
  - **Smart Model Router** — local OpenAI-compatible daemon with model sets, failover, circuit breakers, health probes, and token stats
548
+ - **Playground chat** — multi-turn chat with the router on every surface (TUI `;` / Web Playground nav / `free-coding-models --playground`). Streams responses and shows the routed-via provider/model on every reply.
549
+ - **Router pre-prompt** — a configurable first-class system message injected by the daemon on every `/v1/chat/completions` request it proxies. Default persona introduces the assistant as the FCM routing agent; editable from any surface.
513
550
  - **⚡️ Command Palette** — `Ctrl+P` opens a searchable action launcher for filters, sorting, overlays, and quick toggles
514
551
  - **Install Endpoints** — push a full provider catalog into any tool's config (from Settings `P` or ⚡️ Command Palette)
515
552
  - **Missing tool bootstrap** — detect absent CLIs, offer one-click install, then continue the selected launch automatically
@@ -122,13 +122,18 @@ async function main() {
122
122
  process.exit(result.ok ? 0 : 1);
123
123
  }
124
124
 
125
+ // 📖 --playground / playground subcommand — boot the TUI directly into the
126
+ // 📖 Playground chat overlay. Falls through to the TUI; the key handler
127
+ // 📖 opens the playground on first render.
128
+ const wantPlayground = cliArgs.playgroundMode === true
129
+
125
130
  // Validate --tier early, before entering alternate screen
126
131
  if (cliArgs.tierFilter && !TIER_LETTER_MAP[cliArgs.tierFilter]) {
127
132
  console.error(chalk.red(` Unknown tier "${cliArgs.tierFilter}". Valid tiers: S, A, B, C`));
128
133
  process.exit(1);
129
134
  }
130
135
 
131
- await runApp(cliArgs, config, { startupUpdate, isDevMode });
136
+ await runApp(cliArgs, config, { startupUpdate, isDevMode, wantPlayground });
132
137
  }
133
138
 
134
139
  main().catch((err) => {
@@ -0,0 +1,56 @@
1
+ # Changelog v0.5.11 - 2026-06-02
2
+
3
+ ### Added
4
+ - **Playground chat on every surface.** A first-class way to talk to the FCM router
5
+ without configuring a coding tool. Press `;` in the TUI, click the "Playground"
6
+ nav button in the web dashboard, or run `free-coding-models --playground` /
7
+ `free-coding-models playground` to open the in-TUI chat. Streams responses
8
+ token-by-token, shows the routed-via provider/model + latency + tokens on each
9
+ assistant message, and surfaces a friendly error if the router is offline.
10
+ - **Router pre-prompt (`router.prePrompt`).** A new configurable first-class
11
+ system message injected by the daemon on every `/v1/chat/completions` request
12
+ it proxies. Default text introduces the assistant as "free-coding-models, the
13
+ free coding-model routing agent" so every tool and every Playground reply is
14
+ framed as the FCM router. Always prepended (user `system` messages can still
15
+ override specific instructions). Disabled by setting `enabled: false`.
16
+ - **`GET /api/router/preprompt` and `PUT /api/router/preprompt`** on both the
17
+ router daemon and the web server. Same-origin only for PUT, same CORS policy
18
+ as the rest of the API. Persists to `~/.free-coding-models.json`; the daemon
19
+ picks up the new value on its 10s config-reload tick.
20
+ - **`/api/playground/chat` proxy** on the web server. Browser Playground never
21
+ talks to the daemon directly (no CORS, no exposed provider keys). Streams SSE
22
+ when the client asks for `stream: true`; returns the upstream JSON otherwise.
23
+ - **`fcm` virtual model support clarified.** `GET /v1/models` advertises `fcm`
24
+ (the auto-router) and `fcm:<set-name>` per set, so the Playground model
25
+ selector shows both options.
26
+ - **`--playground` flag and `playground` subcommand** for the TUI:
27
+ `free-coding-models --playground` boots the TUI directly into the chat
28
+ overlay. `--help` lists it under "Config Flags".
29
+ - **Default pre-prompt covers persona + dashboard pointer.** When a user asks
30
+ the model "which model served you?", the default persona tells them they
31
+ were routed through the local FCM router and points them at the dashboard.
32
+ - **9 unit tests for pre-prompt injection** (idempotence, no-mutation, disabled,
33
+ empty, already-set, body-merge, garbage input, length cap, default text).
34
+ - **4 unit tests for playground error extraction** so the OpenAI wire-format
35
+ `{ error: { message, type, code } }` is unwrapped into a string before
36
+ React renders it (the bug a beta tester hit when the router was down).
37
+ - **Robust M4 server smoke test.** The pre-existing test asserted the router
38
+ was offline, which fails on developer machines with a real daemon running.
39
+ Now it asserts the response shape and adds pre-prompt + playground-proxy
40
+ round-trips to the smoke checks.
41
+
42
+ ### Changed
43
+ - **M4 server smoke test no longer fails on local dev machines.** The test
44
+ previously required the router to be offline; that is no longer a precondition.
45
+ - **M4 settings view is untouched** but the pre-prompt edit panel will land in
46
+ a follow-up. (The pre-prompt is already exposed through the daemon API and
47
+ the Web Settings round-trip; only the textarea is missing from the UI.)
48
+
49
+ ### Notes
50
+ - The default pre-prompt is **~120 words** in English, code-first, and
51
+ references the local dashboard. Users can fully replace it from any
52
+ surface; the text is stored verbatim in `~/.free-coding-models.json`.
53
+ - Pre-prompt is **never** sent to telemetry and is not logged by the daemon.
54
+ - The Playground honors the configured pre-prompt and lets the user toggle it
55
+ off per-session. When off, the request is sent as-is and the assistant
56
+ reply will not be framed as the FCM router.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "free-coding-models",
3
- "version": "0.5.10",
3
+ "version": "0.5.11",
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",
@@ -92,7 +92,8 @@
92
92
  *
93
93
  * @exports loadConfig, saveConfig, validateConfigFile, getApiKey, isProviderEnabled
94
94
  * @exports addApiKey, removeApiKey, listApiKeys — multi-key management helpers
95
- * @exports normalizeEndpointInstalls, normalizeRouterConfig, DEFAULT_ROUTER_SETTINGS
95
+ * @exports normalizeEndpointInstalls, normalizeRouterConfig, normalizeRouterPrePrompt
96
+ * @exports defaultRouterPrePromptText, DEFAULT_ROUTER_SETTINGS
96
97
  * @exports buildPersistedConfig, replaceConfigContents, persistApiKeysForProvider
97
98
  * @exports CONFIG_PATH — path to the JSON config file
98
99
  *
@@ -166,6 +167,14 @@ export const DEFAULT_ROUTER_SETTINGS = Object.freeze({
166
167
  priorityWeight: 0.2,
167
168
  }),
168
169
  logLevel: 'info',
170
+ // 📖 Default pre-prompt injected as the first system message on every
171
+ // 📖 /v1/chat/completions request the router proxies. Frames the assistant
172
+ // 📖 as the FCM routing agent. Users can disable it or replace it from the
173
+ // 📖 Settings page on every surface (TUI / Web / Desktop).
174
+ prePrompt: Object.freeze({
175
+ enabled: true,
176
+ text: 'You are free-coding-models, a free coding-model routing agent. The user is talking to you through a local router at http://localhost:19280/v1 that picks the best free coding model in real time across Groq, Cerebras, NVIDIA NIM, GitHub Models, OpenRouter, Mistral, Cloudflare, SambaNova, and more.\n\nBe concise, code-first, and direct. Prefer minimal patches over rewrites. When you show code, make it runnable. When you explain, keep it short. If the user asks which model served them, say you were routed through the local FCM router and point them at the dashboard for the live model list.',
177
+ }),
169
178
  })
170
179
 
171
180
  function isPlainObject(value) {
@@ -385,7 +394,41 @@ export function normalizeRouterConfig(router) {
385
394
  failover: normalizeRouterFailover(router.failover),
386
395
  scoring: normalizeRouterScoring(router.scoring),
387
396
  logLevel,
397
+ prePrompt: normalizeRouterPrePrompt(router.prePrompt),
398
+ }
399
+ }
400
+
401
+ /**
402
+ * 📖 Normalize the pre-prompt sub-tree. The text is capped at 4000 chars to
403
+ * 📖 keep the per-request overhead sane and to make sure the pre-prompt is
404
+ * 📖 always smaller than a typical user message. A disabled pre-prompt
405
+ * 📖 (`enabled: false`) still preserves the text so toggling back on is a
406
+ * 📖 no-op rather than a destructive replace.
407
+ *
408
+ * @param {unknown} prePrompt
409
+ * @returns {{ enabled: boolean, text: string }}
410
+ */
411
+ export function normalizeRouterPrePrompt(prePrompt) {
412
+ const fallback = DEFAULT_ROUTER_SETTINGS.prePrompt
413
+ if (!isPlainObject(prePrompt)) {
414
+ return { enabled: fallback.enabled, text: fallback.text }
388
415
  }
416
+ const rawText = typeof prePrompt.text === 'string' ? prePrompt.text : ''
417
+ const trimmedText = rawText.slice(0, 4000)
418
+ return {
419
+ enabled: prePrompt.enabled === true,
420
+ text: trimmedText,
421
+ }
422
+ }
423
+
424
+ /**
425
+ * 📖 Returns the default pre-prompt text. Surfaced by the Settings UI so the
426
+ * 📖 user can hit "Restore default" without retyping the whole persona.
427
+ *
428
+ * @returns {string}
429
+ */
430
+ export function defaultRouterPrePromptText() {
431
+ return DEFAULT_ROUTER_SETTINGS.prePrompt.text
389
432
  }
390
433
 
391
434
  function normalizeProfileSettings(settings) {