@anjieyang/uncommon-route 0.2.9 → 0.3.1

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
@@ -1,8 +1,8 @@
1
- <p align="right"><strong>English</strong> | <a href="https://github.com/anjieyang/UncommonRoute/blob/main/README.zh-CN.md">简体中文</a></p>
1
+ <p align="right"><strong>English</strong> | <a href="https://github.com/CommonstackAI/UncommonRoute/blob/main/README.zh-CN.md">简体中文</a></p>
2
2
 
3
3
  # @anjieyang/uncommon-route
4
4
 
5
- OpenClaw plugin for [UncommonRoute](https://github.com/anjieyang/UncommonRoute), the local LLM router that sends easy requests to cheaper models and saves stronger models for harder work.
5
+ OpenClaw plugin for [UncommonRoute](https://github.com/CommonstackAI/UncommonRoute), the local LLM router that classifies prompts, scores the discovered upstream pool, and routes virtual model IDs before forwarding requests upstream.
6
6
 
7
7
  If you use OpenClaw and want one local endpoint with smart routing behind it, this plugin is the shortest path.
8
8
 
@@ -17,7 +17,8 @@ This plugin:
17
17
  - installs the Python `uncommon-route` package if needed
18
18
  - starts `uncommon-route serve`
19
19
  - registers the local provider with OpenClaw
20
- - exposes the virtual routing profiles like `uncommon-route/auto`
20
+ - exposes the virtual routing modes like `uncommon-route/auto`
21
+ - syncs the discovered upstream pool into OpenClaw after the local proxy becomes healthy
21
22
 
22
23
  ## Install
23
24
 
@@ -39,7 +40,7 @@ Example plugin config:
39
40
  ```yaml
40
41
  plugins:
41
42
  entries:
42
- "@anjieyang/uncommon-route":
43
+ uncommon-route:
43
44
  port: 8403
44
45
  upstream: "https://api.commonstack.ai/v1"
45
46
  spendLimits:
@@ -47,10 +48,14 @@ plugins:
47
48
  daily: 20.00
48
49
  ```
49
50
 
51
+ > **Note:** OpenClaw uses the unscoped directory name `uncommon-route` as the
52
+ > entries key, not the full npm package name `@anjieyang/uncommon-route`.
53
+ > Config placed under the scoped name will not reach the plugin.
54
+
50
55
  Common upstream choices:
51
56
 
52
57
  | Provider | URL |
53
- |---|---|
58
+ | --- | --- |
54
59
  | [Parallax](https://github.com/GradientHQ/parallax) | `http://127.0.0.1:3001/v1` |
55
60
  | [Commonstack](https://commonstack.ai) | `https://api.commonstack.ai/v1` |
56
61
  | OpenAI | `https://api.openai.com/v1` |
@@ -62,23 +67,22 @@ Parallax is best treated as an experimental local upstream for now: its public d
62
67
 
63
68
  ## What You Get
64
69
 
70
+ - a local OpenClaw provider backed by `http://127.0.0.1:8403/v1`
65
71
  - `uncommon-route/auto` for balanced smart routing
66
- - `uncommon-route/eco` for cheapest capable routing
67
- - `uncommon-route/premium` for quality-first routing
68
- - `uncommon-route/free` for free-first routing
69
- - `uncommon-route/agentic` for tool-heavy workflows
72
+ - always-available virtual modes: `uncommon-route/fast` and `uncommon-route/best`
73
+
74
+ Once the proxy is up and `/v1/models/mapping` is available, the plugin refreshes the OpenClaw provider catalog from the discovered pool. If discovery is unavailable, the virtual modes still work and explicit passthrough model IDs can still be typed manually.
70
75
 
71
- The router also keeps a fallback chain, applies session-aware routing, and exposes a local dashboard at `http://127.0.0.1:8403/dashboard/`.
76
+ The router also keeps a fallback chain, records local feedback, and exposes a local dashboard at `http://127.0.0.1:8403/dashboard/`.
72
77
 
73
78
  ## OpenClaw Commands
74
79
 
75
80
  | Command | Description |
76
- |---|---|
81
+ | --- | --- |
77
82
  | `/route <prompt>` | Preview which model the router would pick |
78
83
  | `/spend status` | Show current spending and limits |
79
84
  | `/spend set hourly 5.00` | Set an hourly spend limit |
80
- | `/feedback ok\|weak\|strong` | Rate the last routing decision |
81
- | `/sessions` | Show active routing sessions |
85
+ | `/feedback <signal>` | Use `ok`, `weak`, `strong`, `status`, or `rollback` to rate the last routing decision or inspect feedback state |
82
86
 
83
87
  ## Troubleshooting
84
88
 
@@ -89,20 +93,111 @@ If the plugin is installed but responses are failing:
89
93
  3. Open `http://127.0.0.1:8403/health`.
90
94
  4. Open `http://127.0.0.1:8403/dashboard/`.
91
95
 
96
+ ## Turn It Off Or Remove It
97
+
98
+ If you want to stop using the OpenClaw plugin, there are three different levels:
99
+
100
+ 1. stop routing traffic from OpenClaw
101
+ 2. clear all local UncommonRoute records and state
102
+ 3. fully uninstall the plugin and the Python package
103
+
104
+ ### 1. Stop routing traffic from OpenClaw
105
+
106
+ ```bash
107
+ openclaw plugins uninstall @anjieyang/uncommon-route
108
+ openclaw gateway restart
109
+ ```
110
+
111
+ If you also started `uncommon-route serve` manually, stop that too:
112
+
113
+ ```bash
114
+ uncommon-route stop
115
+ # or stop the foreground process with Ctrl+C
116
+ ```
117
+
118
+ If you used the config-patch fallback instead of the plugin, remove that registration too:
119
+
120
+ ```bash
121
+ uncommon-route openclaw uninstall
122
+ ```
123
+
124
+ ### 2. Clear all local records
125
+
126
+ By default, UncommonRoute stores local state under:
127
+
128
+ ```text
129
+ ~/.uncommon-route
130
+ ```
131
+
132
+ If you set `UNCOMMON_ROUTE_DATA_DIR`, it uses that directory instead.
133
+
134
+ That local data directory can contain:
135
+
136
+ - route stats and spending history
137
+ - dashboard-saved primary connection and routing overrides
138
+ - BYOK provider keys
139
+ - online-learning weights and feedback buffers
140
+ - learned aliases, model-experience memory, logs, and local artifacts
141
+
142
+ To clear **all** local records, stop the proxy first and then move or delete the active data directory:
143
+
144
+ ```bash
145
+ # Show the active data directory
146
+ echo "${UNCOMMON_ROUTE_DATA_DIR:-$HOME/.uncommon-route}"
147
+
148
+ # Recommended: move it aside as a backup first
149
+ mv "${UNCOMMON_ROUTE_DATA_DIR:-$HOME/.uncommon-route}" \
150
+ "${UNCOMMON_ROUTE_DATA_DIR:-$HOME/.uncommon-route}.backup-$(date +%Y%m%d-%H%M%S)"
151
+
152
+ # Or permanently delete it if you are sure
153
+ # rm -rf "${UNCOMMON_ROUTE_DATA_DIR:-$HOME/.uncommon-route}"
154
+ ```
155
+
156
+ If you only want to clear routing analytics, `uncommon-route stats reset` resets stats and pending feedback. It does **not** remove the rest of the local state.
157
+
158
+ ### 3. Fully uninstall
159
+
160
+ First remove the OpenClaw plugin or config-patch registration:
161
+
162
+ ```bash
163
+ openclaw plugins uninstall @anjieyang/uncommon-route
164
+ uncommon-route openclaw uninstall
165
+ openclaw gateway restart
166
+ ```
167
+
168
+ If you set environment variables for UncommonRoute, clear them:
169
+
170
+ ```bash
171
+ unset UNCOMMON_ROUTE_UPSTREAM
172
+ unset UNCOMMON_ROUTE_API_KEY
173
+ unset OPENAI_BASE_URL
174
+ unset ANTHROPIC_BASE_URL
175
+ ```
176
+
177
+ Then remove the Python package with the same tool you used to install it:
178
+
179
+ ```bash
180
+ pipx uninstall uncommon-route
181
+ # or
182
+ python -m pip uninstall uncommon-route
183
+ # or
184
+ pip uninstall uncommon-route
185
+ ```
186
+
92
187
  ## Benchmarks
93
188
 
94
189
  Current repo benchmarks:
95
190
 
96
- - 92.3% held-out routing accuracy
97
- - ~0.5ms average routing latency
98
- - 67% lower simulated cost than always using Claude Opus in a coding session
191
+ - 97.4% held-out routing accuracy on the current in-repo benchmark set
192
+ - ECE improves from 2.1% to 1.7% after temperature scaling
193
+ - 68% lower simulated cost than always using Claude Opus in a 131-request coding session
99
194
 
100
195
  ## Links
101
196
 
102
- - [GitHub](https://github.com/anjieyang/UncommonRoute)
197
+ - [GitHub](https://github.com/CommonstackAI/UncommonRoute)
103
198
  - [PyPI](https://pypi.org/project/uncommon-route/)
104
- - [Full README](https://github.com/anjieyang/UncommonRoute#readme)
199
+ - [Full README](https://github.com/CommonstackAI/UncommonRoute#readme)
105
200
 
106
201
  ## License
107
202
 
108
- MIT
203
+ Modified MIT
@@ -1,7 +1,7 @@
1
1
  {
2
- "id": "@anjieyang/uncommon-route",
2
+ "id": "uncommon-route",
3
3
  "name": "UncommonRoute",
4
- "description": "Local LLM router that cuts premium-model spend with smart routing, sessions, and spend control",
4
+ "description": "Local LLM router that cuts premium-model spend with smart routing, local feedback, and spend control",
5
5
  "configSchema": {
6
6
  "type": "object",
7
7
  "properties": {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://openclaw.ai/schemas/plugin-security.json",
3
3
  "version": "1.0",
4
- "plugin": "@anjieyang/uncommon-route",
4
+ "plugin": "uncommon-route",
5
5
  "permissions": {
6
6
  "network": {
7
7
  "outbound": ["localhost:8403"],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anjieyang/uncommon-route",
3
- "version": "0.2.9",
3
+ "version": "0.3.1",
4
4
  "description": "OpenClaw plugin for UncommonRoute, the local LLM router that cuts premium-model spend",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
package/src/index.js CHANGED
@@ -10,35 +10,26 @@
10
10
  * → ensures `uncommon-route` Python package is installed (pipx/uv/pip)
11
11
  * → spawns `uncommon-route serve` as a managed subprocess
12
12
  * → registerProvider pointing at localhost proxy
13
- * → registerCommand for /route, /spend, /sessions
13
+ * → syncs the discovered upstream pool into OpenClaw after startup
14
+ * → registerCommand for /route, /spend, /feedback
14
15
  */
15
16
 
16
17
  import { spawn, execSync } from "node:child_process";
17
18
  import { setTimeout as sleep } from "node:timers/promises";
18
19
 
19
- const VERSION = "0.2.9";
20
+ const VERSION = "0.3.1";
20
21
  const DEFAULT_PORT = 8403;
21
22
  const DEFAULT_UPSTREAM = "";
22
23
  const HEALTH_TIMEOUT_MS = 15_000;
23
24
  const HEALTH_POLL_MS = 500;
24
25
  const PY_PACKAGE = "uncommon-route";
26
+ const DEFAULT_CONTEXT_WINDOW = 200_000;
27
+ const DEFAULT_MAX_TOKENS = 16_384;
25
28
 
26
- const MODELS = [
27
- { id: "uncommon-route/auto", name: "UncommonRoute Auto", reasoning: false, input: 0, output: 0, ctx: 200_000, max: 16_384 },
28
- { id: "uncommon-route/eco", name: "UncommonRoute Eco", reasoning: false, input: 0, output: 0, ctx: 200_000, max: 16_384 },
29
- { id: "uncommon-route/premium", name: "UncommonRoute Premium", reasoning: true, input: 0, output: 0, ctx: 200_000, max: 16_384 },
30
- { id: "uncommon-route/free", name: "UncommonRoute Free", reasoning: false, input: 0, output: 0, ctx: 200_000, max: 16_384 },
31
- { id: "uncommon-route/agentic", name: "UncommonRoute Agentic", reasoning: true, input: 0, output: 0, ctx: 200_000, max: 16_384 },
32
- { id: "moonshot/kimi-k2.5", name: "Kimi K2.5", reasoning: false, input: 0.60, output: 3.00, ctx: 128_000, max: 8_192 },
33
- { id: "google/gemini-3.1-pro", name: "Gemini 3.1 Pro", reasoning: false, input: 2.00, output: 12.00, ctx: 200_000, max: 16_384 },
34
- { id: "xai/grok-4-1-fast-reasoning", name: "Grok 4.1 Fast", reasoning: true, input: 0.20, output: 0.50, ctx: 200_000, max: 16_384 },
35
- { id: "deepseek/deepseek-chat", name: "DeepSeek Chat", reasoning: false, input: 0.28, output: 0.42, ctx: 128_000, max: 8_192 },
36
- { id: "deepseek/deepseek-reasoner", name: "DeepSeek Reasoner", reasoning: true, input: 0.28, output: 0.42, ctx: 128_000, max: 8_192 },
37
- { id: "google/gemini-2.5-flash", name: "Gemini 2.5 Flash", reasoning: false, input: 0.30, output: 2.50, ctx: 200_000, max: 16_384 },
38
- { id: "google/gemini-2.5-flash-lite", name: "Gemini 2.5 Flash Lite", reasoning: false, input: 0.10, output: 0.40, ctx: 200_000, max: 16_384 },
39
- { id: "openai/gpt-5.2", name: "GPT-5.2", reasoning: false, input: 1.75, output: 14.00, ctx: 200_000, max: 16_384 },
40
- { id: "openai/o4-mini", name: "o4 Mini", reasoning: true, input: 1.10, output: 4.40, ctx: 200_000, max: 16_384 },
41
- { id: "anthropic/claude-sonnet-4.6", name: "Claude Sonnet 4.6", reasoning: false, input: 3.00, output: 15.00, ctx: 200_000, max: 16_384 },
29
+ const VIRTUAL_MODELS = [
30
+ { id: "uncommon-route/auto", name: "UncommonRoute Auto", reasoning: false },
31
+ { id: "uncommon-route/fast", name: "UncommonRoute Fast", reasoning: false },
32
+ { id: "uncommon-route/best", name: "UncommonRoute Best", reasoning: true },
42
33
  ];
43
34
 
44
35
  // ── Python dependency management ─────────────────────────────────────
@@ -136,21 +127,71 @@ function ensurePythonDeps(logger) {
136
127
 
137
128
  // ── Helpers ──────────────────────────────────────────────────────────
138
129
 
139
- function buildModels(baseUrl) {
130
+ function toFiniteNumber(value) {
131
+ const num = Number(value);
132
+ return Number.isFinite(num) ? num : 0;
133
+ }
134
+
135
+ function modelEntry({
136
+ id,
137
+ name = id,
138
+ reasoning = false,
139
+ input = 0,
140
+ output = 0,
141
+ cacheRead = 0,
142
+ cacheWrite = 0,
143
+ ctx = DEFAULT_CONTEXT_WINDOW,
144
+ max = DEFAULT_MAX_TOKENS,
145
+ }) {
146
+ return {
147
+ id,
148
+ name,
149
+ api: "openai-completions",
150
+ reasoning,
151
+ input: ["text"],
152
+ cost: { input, output, cacheRead, cacheWrite },
153
+ contextWindow: ctx,
154
+ maxTokens: max,
155
+ };
156
+ }
157
+
158
+ function discoveredModelEntries(discoveredPool) {
159
+ if (!Array.isArray(discoveredPool)) return [];
160
+
161
+ const seen = new Set(VIRTUAL_MODELS.map((model) => model.id));
162
+ const models = [];
163
+
164
+ for (const row of discoveredPool) {
165
+ const id = typeof row?.id === "string" ? row.id.trim() : "";
166
+ if (!id || seen.has(id)) continue;
167
+
168
+ seen.add(id);
169
+ const pricing = row?.pricing ?? {};
170
+ const capabilities = row?.capabilities ?? {};
171
+
172
+ models.push(modelEntry({
173
+ id,
174
+ name: id,
175
+ reasoning: Boolean(capabilities.reasoning),
176
+ input: toFiniteNumber(pricing.input),
177
+ output: toFiniteNumber(pricing.output),
178
+ cacheRead: toFiniteNumber(pricing.cached_input),
179
+ cacheWrite: toFiniteNumber(pricing.cache_write),
180
+ }));
181
+ }
182
+
183
+ return models;
184
+ }
185
+
186
+ function buildModels(baseUrl, discoveredPool = []) {
140
187
  return {
141
188
  baseUrl,
142
189
  api: "openai-completions",
143
190
  apiKey: "uncommon-route-local-proxy",
144
- models: MODELS.map((m) => ({
145
- id: m.id,
146
- name: m.name,
147
- api: "openai-completions",
148
- reasoning: m.reasoning,
149
- input: ["text"],
150
- cost: { input: m.input, output: m.output, cacheRead: 0, cacheWrite: 0 },
151
- contextWindow: m.ctx,
152
- maxTokens: m.max,
153
- })),
191
+ models: [
192
+ ...VIRTUAL_MODELS.map((model) => modelEntry(model)),
193
+ ...discoveredModelEntries(discoveredPool),
194
+ ],
154
195
  };
155
196
  }
156
197
 
@@ -192,7 +233,7 @@ async function postJson(url, body) {
192
233
  let pyProc = null;
193
234
 
194
235
  const plugin = {
195
- id: "@anjieyang/uncommon-route",
236
+ id: "uncommon-route",
196
237
  name: "UncommonRoute",
197
238
  description: "Local LLM router plugin that cuts premium-model spend with smart routing",
198
239
  version: VERSION,
@@ -210,6 +251,34 @@ const plugin = {
210
251
  const port = cfg.port || Number(process.env.UNCOMMON_ROUTE_PORT) || DEFAULT_PORT;
211
252
  const upstream = cfg.upstream || process.env.UNCOMMON_ROUTE_UPSTREAM || DEFAULT_UPSTREAM;
212
253
  const baseUrl = `http://127.0.0.1:${port}/v1`;
254
+ let discoveredPool = [];
255
+ let providerCatalog = buildModels(baseUrl, discoveredPool);
256
+
257
+ function applyProviderCatalog(nextPool = discoveredPool) {
258
+ discoveredPool = Array.isArray(nextPool) ? nextPool : [];
259
+ providerCatalog = buildModels(baseUrl, discoveredPool);
260
+ if (!api.config.models) api.config.models = { providers: {} };
261
+ if (!api.config.models.providers) api.config.models.providers = {};
262
+ api.config.models.providers["uncommon-route"] = providerCatalog;
263
+ return providerCatalog;
264
+ }
265
+
266
+ async function syncDiscoveredPool() {
267
+ const mapping = await fetchJson(`http://127.0.0.1:${port}/v1/models/mapping`);
268
+ if (!mapping) {
269
+ api.logger.warn("Could not read /v1/models/mapping; keeping OpenClaw provider catalog on virtual routes only.");
270
+ return false;
271
+ }
272
+
273
+ const nextCatalog = applyProviderCatalog(mapping.pool);
274
+ const discoveredCount = Math.max(nextCatalog.models.length - VIRTUAL_MODELS.length, 0);
275
+ if (mapping.discovered && discoveredCount > 0) {
276
+ api.logger.info(`Synced ${discoveredCount} discovered upstream models into OpenClaw provider catalog`);
277
+ } else {
278
+ api.logger.info("Upstream discovery unavailable; OpenClaw provider catalog remains virtual-mode only");
279
+ }
280
+ return true;
281
+ }
213
282
 
214
283
  if (!upstream) {
215
284
  api.logger.warn("UncommonRoute: No upstream configured. Set UNCOMMON_ROUTE_UPSTREAM or configure 'upstream' in plugin config.");
@@ -217,21 +286,17 @@ const plugin = {
217
286
  }
218
287
 
219
288
  // 1. Register provider immediately (sync, models available right away)
289
+ applyProviderCatalog();
220
290
  api.registerProvider({
221
291
  id: "uncommon-route",
222
292
  label: "UncommonRoute",
223
- docsPath: "https://github.com/anjieyang/UncommonRoute",
293
+ docsPath: "https://github.com/CommonstackAI/UncommonRoute",
224
294
  aliases: ["ur", "uncommon"],
225
295
  envVars: [],
226
- get models() { return buildModels(baseUrl); },
296
+ get models() { return providerCatalog; },
227
297
  auth: [],
228
298
  });
229
-
230
- if (!api.config.models) api.config.models = { providers: {} };
231
- if (!api.config.models.providers) api.config.models.providers = {};
232
- api.config.models.providers["uncommon-route"] = buildModels(baseUrl);
233
-
234
- api.logger.info(`UncommonRoute provider registered (${MODELS.length} models)`);
299
+ api.logger.info(`UncommonRoute provider registered (${providerCatalog.models.length} virtual route models)`);
235
300
 
236
301
  // 2. Register commands
237
302
  api.registerCommand({
@@ -350,23 +415,6 @@ const plugin = {
350
415
  },
351
416
  });
352
417
 
353
- api.registerCommand({
354
- name: "sessions",
355
- description: "View active routing sessions",
356
- acceptsArgs: false,
357
- requireAuth: false,
358
- handler: async () => {
359
- const data = await fetchJson(`http://127.0.0.1:${port}/v1/sessions`);
360
- if (!data) return { text: "Proxy not running.", isError: true };
361
- if (data.count === 0) return { text: "No active sessions" };
362
- const lines = [`**Active Sessions** (${data.count})`, ""];
363
- for (const s of data.sessions) {
364
- lines.push(`• \`${s.id}\` model=${s.model} tier=${s.tier} requests=${s.requests} age=${s.age_s}s`);
365
- }
366
- return { text: lines.join("\n") };
367
- },
368
- });
369
-
370
418
  // 3. Register service for lifecycle
371
419
  api.registerService({
372
420
  id: "uncommon-route-proxy",
@@ -405,23 +453,34 @@ const plugin = {
405
453
 
406
454
  // 6. Auto-install Python deps + spawn proxy
407
455
  const bootstrap = async () => {
408
- const pythonPath = cfg.pythonPath || process.env.UNCOMMON_ROUTE_PYTHON || null;
409
- let python = pythonPath;
456
+ let cliBin = which("uncommon-route");
457
+ let python = cfg.pythonPath || process.env.UNCOMMON_ROUTE_PYTHON || null;
410
458
 
411
- if (!python || !isPythonPackageInstalled(python)) {
459
+ if (!cliBin && (!python || !isPythonPackageInstalled(python))) {
412
460
  api.logger.info("Checking Python dependencies...");
413
461
  python = ensurePythonDeps(api.logger);
414
462
  if (!python) {
415
463
  api.logger.error("Cannot start — Python setup failed. See errors above.");
416
464
  return;
417
465
  }
466
+ cliBin = which("uncommon-route");
418
467
  }
419
468
 
420
- const args = ["-m", "uncommon_route.cli", "serve", "--port", String(port), "--upstream", upstream];
421
- pyProc = spawn(python, args, {
422
- stdio: ["ignore", "pipe", "pipe"],
423
- env: { ...process.env, PYTHONUNBUFFERED: "1" },
424
- });
469
+ const serveArgs = ["serve", "--port", String(port), "--upstream", upstream];
470
+ if (cliBin) {
471
+ pyProc = spawn(cliBin, serveArgs, {
472
+ stdio: ["ignore", "pipe", "pipe"],
473
+ env: { ...process.env, PYTHONUNBUFFERED: "1" },
474
+ });
475
+ } else if (python) {
476
+ pyProc = spawn(python, ["-m", "uncommon_route.cli", ...serveArgs], {
477
+ stdio: ["ignore", "pipe", "pipe"],
478
+ env: { ...process.env, PYTHONUNBUFFERED: "1" },
479
+ });
480
+ } else {
481
+ api.logger.error("Cannot start — neither uncommon-route CLI nor Python module found.");
482
+ return;
483
+ }
425
484
 
426
485
  pyProc.stdout?.on("data", (chunk) => {
427
486
  const line = chunk.toString().trim();
@@ -439,6 +498,7 @@ const plugin = {
439
498
  api.logger.info(`Starting proxy on port ${port}...`);
440
499
  const healthy = await waitForHealth(port);
441
500
  if (healthy) {
501
+ await syncDiscoveredPool();
442
502
  api.logger.info(`UncommonRoute ready at http://127.0.0.1:${port}`);
443
503
  api.logger.info(`Default model: uncommon-route/auto`);
444
504
  } else {