lynkr 9.11.0 → 9.13.0

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
@@ -5,7 +5,7 @@
5
5
  **84% fewer tokens on JSON tool results. 53% fewer tokens on tool-heavy requests. Sub-300ms semantic cache hits. Zero code changes.**
6
6
 
7
7
  [![npm version](https://img.shields.io/npm/v/lynkr.svg)](https://www.npmjs.com/package/lynkr)
8
- [![Tests](https://img.shields.io/badge/tests-1041%20passing-brightgreen)](https://github.com/Fast-Editor/Lynkr)
8
+ [![Tests](https://img.shields.io/badge/tests-1249%20passing-brightgreen)](https://github.com/Fast-Editor/Lynkr)
9
9
  [![License: Apache 2.0](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE)
10
10
  [![Node.js](https://img.shields.io/badge/node-20%2B-green)](https://nodejs.org)
11
11
  [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/Fast-Editor/Lynkr)
@@ -15,7 +15,7 @@
15
15
  <td align="center"><strong>84%</strong><br/>JSON Compression</td>
16
16
  <td align="center"><strong>53%</strong><br/>Tool Token Reduction</td>
17
17
  <td align="center"><strong>&lt;300ms</strong><br/>Semantic Cache Hits</td>
18
- <td align="center"><strong>13+</strong><br/>LLM Providers</td>
18
+ <td align="center"><strong>14+</strong><br/>LLM Providers</td>
19
19
  <td align="center"><strong>0</strong><br/>Code Changes Required</td>
20
20
  </tr>
21
21
  </table>
@@ -68,7 +68,7 @@ The fastest path is the interactive wizard:
68
68
  lynkr init
69
69
  ```
70
70
 
71
- It asks four questions — usage mode (Claude Pro/Max via wrap, or direct API keys), tier picks for SIMPLE/MEDIUM/COMPLEX/REASONING across the 12 supported providers, credentials for what you chose, and a few routing-intelligence knobs — then writes a fully-populated `.env` with sensible production defaults for everything else (caching, compression, policy budgets, MCP sandbox, rate limiting).
71
+ It asks four questions — usage mode (Claude Pro/Max via wrap, or direct API keys), tier picks for SIMPLE/MEDIUM/COMPLEX/REASONING across the 14 supported providers, credentials for what you chose, and a few routing-intelligence knobs — then writes a fully-populated `.env` with sensible production defaults for everything else (caching, compression, policy budgets, MCP sandbox, rate limiting).
72
72
 
73
73
  Useful flags:
74
74
 
@@ -216,9 +216,24 @@ Claude Code / Cursor / Codex / Cline / Continue
216
216
  | **Azure OpenAI** | Cloud | GPT-4o, o1, o3 | $$$ |
217
217
  | **Azure Anthropic** | Cloud | Claude Sonnet, Opus | $$$ |
218
218
  | **OpenAI** | Cloud | GPT-4o, o3-mini | $$$ |
219
+ | **Atlas Cloud** | Cloud | Qwen, DeepSeek, and other OpenAI-compatible models | $-$$$ |
219
220
  | **DeepSeek** | Cloud | DeepSeek R1, Reasoner | $ |
220
-
221
- **4 local providers** for 100% offline, free usage. **10+ cloud providers** for scale.
221
+ | **Z.ai** | Cloud | GLM-4.7, GLM-4.5-Air | $ |
222
+ | **Moonshot AI** | Cloud | Kimi K2.6, Kimi K3 | $ |
223
+ | **Baidu Qianfan** | Cloud | ERNIE 4.5 Turbo, ERNIE X1.1 | $ (unverified — not yet probed against a live key) |
224
+
225
+ **4 local providers** for 100% offline, free usage. **14+ cloud providers** for scale.
226
+
227
+ <div align="center">
228
+ <a href="https://www.atlascloud.ai/?utm_source=github&amp;utm_medium=readme&amp;utm_campaign=lynkr">
229
+ <picture>
230
+ <source media="(prefers-color-scheme: dark)" srcset="https://www.atlascloud.ai/logo-white.svg">
231
+ <img src="https://www.atlascloud.ai/logo.svg" alt="Atlas Cloud" width="196">
232
+ </picture>
233
+ </a>
234
+ <br>
235
+ <sub>Use Lynkr with Atlas Cloud's OpenAI-compatible API and route across its live model catalog with one optional provider configuration. <a href="https://www.atlascloud.ai/?utm_source=github&amp;utm_medium=readme&amp;utm_campaign=lynkr">Explore Atlas Cloud</a>.</sub>
236
+ </div>
222
237
 
223
238
  ---
224
239
 
@@ -326,12 +341,45 @@ the local telemetry store:
326
341
  - **Routing accuracy** — over-/under-provisioned request counts, a self-audit
327
342
  of the tier router's decisions
328
343
  - **Request logs** — filterable by provider, tier, and errors, with latency,
329
- tokens, and cost per request
344
+ tokens, and cost per request; every session id links to a drill-down
330
345
  - **Provider health** — configured providers, credential warnings, circuit
331
346
  breaker states
347
+ - **Insights** — analyzer findings ranked by *past overspend* (auditable
348
+ against spend already recorded, never projected savings): dead tool
349
+ schemas paying rent, harness side-traffic served on expensive tiers,
350
+ weak prompt-cache hit ratios, and Wilson-bounded tier-downsize verdicts.
351
+ Each finding ships its evidence rows, the fix, the caveat, and how the
352
+ figure was computed. "Not measured yet" is rendered distinctly from
353
+ zero — an un-run scan is not an all-clear.
354
+ - **Explore** — a Metric × Dimension (× Stack) pivot over routing telemetry
355
+ (spend/tokens/requests/sessions by provider, model, tier, request type,
356
+ day…) with preset views, period-over-period deltas, and CSV export
357
+ - **Session drill-down** — per-model cost mix and an input-context-per-call
358
+ chart where distillation and cache behavior are directly visible
359
+ - **Cache receipts** — projected switch/hold economics from cache-aware
360
+ routing next to the *measured* companion (dollars saved by cache reads
361
+ actually served)
362
+
363
+ JSON APIs behind it
364
+ (`/dashboard/api/overview|usage|routing|logs|recommendations|analytics|sessions/:id|statusline`)
365
+ if you want the raw numbers.
366
+
367
+ ### Status line for Claude Code
368
+
369
+ One line after every turn — routed tier, served model, today's spend, and
370
+ cache re-read share — with zero token cost (out-of-band, never enters model
371
+ context). Add to `~/.claude/settings.json`:
372
+
373
+ ```json
374
+ "statusLine": { "type": "command", "command": "lynkr-statusline" }
375
+ ```
376
+
377
+ ```
378
+ ◆ Opus 4.8 · MEDIUM → minimax-m3:cloud (ollama) · today $0.06 · cache 67%
379
+ ```
332
380
 
333
- JSON APIs behind it (`/dashboard/api/overview|usage|routing|logs`) if you want
334
- the raw numbers.
381
+ Always prints exactly one line and always exits 0 — a down proxy can never
382
+ break the harness. Set `LYNKR_PORT` if Lynkr isn't on 8081.
335
383
 
336
384
  ### Cost tracking & model pricing
337
385
  Per-request cost is computed from a model-pricing registry (LiteLLM → models.dev,
@@ -551,7 +599,8 @@ With tier routing + token optimization: **additional 50-87% savings** on cloud p
551
599
  | **TOON JSON compression** | ✅ up to 87.6% | ❌ | ❌ | ❌ |
552
600
  | **Upstream SSE streaming** | ✅ native passthrough + cross-format transform | ⚠️ passthrough only | ✅ | ⚠️ |
553
601
  | **Semantic cache** | ✅ 171ms hits, 0 tokens | ❌ | ❌ | ✅ Prompt cache only |
554
- | **Savings & routing dashboard** | ✅ spend, savings vs flagship, tier mix, routing accuracy, request logs | ⚠️ spend UI only | ⚠️ usage page | ✅ observability suite (no routing accuracy) |
602
+ | **Savings & routing dashboard** | ✅ spend, savings vs flagship, tier mix, routing accuracy, request logs, pivot explorer, session drill-down | ⚠️ spend UI only | ⚠️ usage page | ✅ observability suite (no routing accuracy) |
603
+ | **Cost recommendations** | ✅ evidence-backed findings (dead tool schemas, side-traffic, cache weak spots, Wilson-proven downsizes) ranked by past overspend | ❌ | ❌ | ⚠️ alerts, no findings |
555
604
  | **Long-term memory** | ✅ SQLite, per-session | ❌ | ❌ | ❌ |
556
605
  | **MCP integration** | ✅ | ❌ | ❌ | ❌ |
557
606
  | **Self-hosted** | ✅ Node.js only | ✅ Python stack | ❌ SaaS | ✅ Docker |
package/bin/lynkr-init.js CHANGED
@@ -89,6 +89,17 @@ const PROVIDERS = {
89
89
  extras: [],
90
90
  defaultModel: 'gpt-4o',
91
91
  },
92
+ atlas: {
93
+ label: 'Atlas Cloud (OpenAI-compatible)',
94
+ local: false,
95
+ creds: [
96
+ { key: 'ATLASCLOUD_API_KEY', label: 'Atlas Cloud API key', secret: true },
97
+ ],
98
+ extras: [
99
+ { key: 'ATLASCLOUD_ENDPOINT', label: 'Chat Completions endpoint', default: 'https://api.atlascloud.ai/v1/chat/completions' },
100
+ ],
101
+ defaultModel: 'qwen/qwen3.8-max',
102
+ },
92
103
  openrouter: {
93
104
  label: 'OpenRouter (100+ models, one key)',
94
105
  local: false,
@@ -159,12 +170,21 @@ const PROVIDERS = {
159
170
  extras: [],
160
171
  defaultModel: 'kimi-k2-turbo-preview',
161
172
  },
173
+ baidu: {
174
+ label: 'Baidu Qianfan (ERNIE)',
175
+ local: false,
176
+ creds: [
177
+ { key: 'BAIDU_API_KEY', label: 'Baidu Qianfan API key (bce-v3/ALTAK-...)', secret: true },
178
+ ],
179
+ extras: [],
180
+ defaultModel: 'ernie-4.5-turbo-128k',
181
+ },
162
182
  };
163
183
 
164
184
  const PROVIDER_ORDER = [
165
185
  'ollama', 'llamacpp', 'lmstudio',
166
- 'azure-anthropic', 'azure-openai', 'openai', 'openrouter', 'edenai',
167
- 'databricks', 'bedrock', 'vertex', 'zai', 'moonshot',
186
+ 'azure-anthropic', 'azure-openai', 'openai', 'atlas', 'openrouter', 'edenai',
187
+ 'databricks', 'bedrock', 'vertex', 'zai', 'moonshot', 'baidu',
168
188
  ];
169
189
  const TIERS = ['SIMPLE', 'MEDIUM', 'COMPLEX', 'REASONING'];
170
190
 
@@ -319,6 +339,9 @@ const BASELINE_ENV = {
319
339
  MOONSHOT_API_KEY: '',
320
340
  MOONSHOT_ENDPOINT: 'https://api.moonshot.ai/v1/chat/completions',
321
341
  MOONSHOT_MODEL: 'kimi-k2.6',
342
+ BAIDU_API_KEY: '',
343
+ BAIDU_ENDPOINT: 'https://qianfan.baidubce.com/v2/chat/completions',
344
+ BAIDU_MODEL: 'ernie-4.5-turbo-128k',
322
345
  LLAMACPP_ENDPOINT: 'http://localhost:8080',
323
346
  LLAMACPP_MODEL: 'default',
324
347
  LLAMACPP_TIMEOUT_MS: '120000',
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * lynkr statusline — zero-token Claude Code status line.
4
+ *
5
+ * Wire into ~/.claude/settings.json:
6
+ * "statusLine": { "type": "command", "command": "lynkr-statusline" }
7
+ *
8
+ * Claude Code pipes session JSON on stdin after each turn. This command:
9
+ * - never enters model context (out-of-band — an in-loop MCP integration
10
+ * was measured at +36% model-weighted tokens by TokenJam; don't do that)
11
+ * - makes ONE local HTTP call to the Lynkr dashboard API with a hard
12
+ * 250ms timeout
13
+ * - always exits 0 and always prints exactly one line, no matter what
14
+ * fails — a broken status line must never break the harness.
15
+ *
16
+ * Output: ◆ <model> · <tier> → <served model> (<provider>)[ · pin] · today $X · cache NN%
17
+ */
18
+
19
+ 'use strict';
20
+
21
+ const PORT = Number(process.env.LYNKR_PORT) || Number(process.env.PORT) || 8081;
22
+ const TIMEOUT_MS = 250;
23
+
24
+ function readStdin(maxMs) {
25
+ return new Promise((resolve) => {
26
+ let data = '';
27
+ const timer = setTimeout(() => resolve(data), maxMs);
28
+ process.stdin.on('data', (c) => { data += c; });
29
+ process.stdin.on('end', () => { clearTimeout(timer); resolve(data); });
30
+ process.stdin.on('error', () => { clearTimeout(timer); resolve(data); });
31
+ });
32
+ }
33
+
34
+ async function fetchStatus() {
35
+ const controller = new AbortController();
36
+ const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
37
+ try {
38
+ const res = await fetch(`http://127.0.0.1:${PORT}/dashboard/api/statusline`, {
39
+ signal: controller.signal,
40
+ });
41
+ if (!res.ok) return null;
42
+ return await res.json();
43
+ } catch {
44
+ return null;
45
+ } finally {
46
+ clearTimeout(timer);
47
+ }
48
+ }
49
+
50
+ async function main() {
51
+ const [stdinRaw, status] = await Promise.all([readStdin(150), fetchStatus()]);
52
+
53
+ let clientModel = null;
54
+ try {
55
+ const parsed = JSON.parse(stdinRaw);
56
+ clientModel = parsed?.model?.display_name || parsed?.model?.id || null;
57
+ } catch { /* stdin absent or non-JSON — fine */ }
58
+
59
+ const parts = [];
60
+ if (clientModel) parts.push(clientModel);
61
+
62
+ if (status?.last) {
63
+ const served = `${status.last.tier || '?'} → ${status.last.model || '?'} (${status.last.provider || '?'})`;
64
+ parts.push(served + (status.last.pinned ? ' · pin' : ''));
65
+ } else {
66
+ parts.push('lynkr: no traffic yet');
67
+ }
68
+ if (status && typeof status.todaySpendUsd === 'number') {
69
+ parts.push(`today $${status.todaySpendUsd.toFixed(2)}`);
70
+ }
71
+ if (status && typeof status.cacheReadPct === 'number') {
72
+ parts.push(`cache ${status.cacheReadPct}%`);
73
+ }
74
+
75
+ process.stdout.write('◆ ' + parts.join(' · ') + '\n');
76
+ }
77
+
78
+ main()
79
+ .catch(() => { process.stdout.write('◆ lynkr statusline unavailable\n'); })
80
+ .finally(() => process.exit(0));
package/bin/wrap.js CHANGED
@@ -114,13 +114,16 @@ async function wrapClaude() {
114
114
 
115
115
  let server;
116
116
  try {
117
- const { start } = require('../src/server');
118
-
119
- // Override port if specified
117
+ // Override port BEFORE requiring the server: src/config reads
118
+ // process.env.PORT at module-load time, so setting it after the require
119
+ // is a no-op — the server binds the .env port and --port silently fails
120
+ // (printed 8082, bound 8081).
120
121
  if (port !== 8081) {
121
122
  process.env.PORT = String(port);
122
123
  }
123
124
 
125
+ const { start } = require('../src/server');
126
+
124
127
  server = await start();
125
128
 
126
129
  // Wait for server to be ready
@@ -381,13 +384,14 @@ async function wrapGeneric(opts) {
381
384
 
382
385
  let server;
383
386
  try {
384
- const { start } = require('../src/server');
385
-
386
- // Override port if specified
387
+ // Set PORT before requiring the server — src/config freezes
388
+ // process.env.PORT at module-load time (same fix as the claude path).
387
389
  if (port !== 8081) {
388
390
  process.env.PORT = String(port);
389
391
  }
390
392
 
393
+ const { start } = require('../src/server');
394
+
391
395
  server = await start();
392
396
 
393
397
  // Wait for server to be ready
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lynkr",
3
- "version": "9.11.0",
3
+ "version": "9.13.0",
4
4
  "files": [
5
5
  "index.js",
6
6
  "install.sh",
@@ -25,7 +25,8 @@
25
25
  "main": "index.js",
26
26
  "bin": {
27
27
  "lynkr": "bin/cli.js",
28
- "lynkr-setup": "scripts/setup.js"
28
+ "lynkr-setup": "scripts/setup.js",
29
+ "lynkr-statusline": "bin/statusline.js"
29
30
  },
30
31
  "scripts": {
31
32
  "postinstall": "node scripts/check-native.js",
@@ -36,7 +37,7 @@
36
37
  "dev": "nodemon index.js",
37
38
  "lint": "eslint src index.js",
38
39
  "test": "npm run test:unit && npm run test:performance",
39
- "test:unit": "DATABRICKS_API_KEY=test-key DATABRICKS_API_BASE=http://test.com LOG_FILE_ENABLED=false node --test test/routing.test.js test/hybrid-routing-integration.test.js test/retry-logic.test.js test/sse-transformer.test.js test/passthrough-stream.test.js test/passthrough-mode.test.js test/openrouter-error-resilience.test.js test/format-conversion.test.js test/azure-openai-config.test.js test/azure-openai-format-conversion.test.js test/azure-openai-routing.test.js test/azure-openai-streaming.test.js test/azure-openai-error-resilience.test.js test/azure-openai-integration.test.js test/openai-integration.test.js test/toon-compression.test.js test/gcf-compression.test.js test/llamacpp-integration.test.js test/resilience.test.js test/telemetry-routing.test.js test/memory/store.test.js test/memory/surprise.test.js test/memory/extractor.test.js test/memory/search.test.js test/memory/retriever.test.js test/memory/distiller.test.js test/memory/distiller-freeze.test.js test/memory/wiki.test.js test/memory/skills-cache.test.js test/memory/tencentdb-launcher.test.js test/distill.test.js test/large-payload.test.js test/prompt-cache-injection.test.js test/risk-analyzer.test.js test/interaction-block.test.js test/preflight.test.js test/token-reduction.test.js test/session-affinity.test.js test/cache-state.test.js test/cache-switch-cost.test.js test/model-registry-cost.test.js test/output-format-guard.test.js test/tier-fallback.test.js test/wrap.test.js test/init.test.js test/tool-call-response-metadata.test.js test/degradation.test.js test/routing-telemetry-columns.test.js test/sticky-routing.test.js test/knn-ambiguous-escalate.test.js test/deescalator.test.js test/client-profiles.test.js test/strip-internal-fields.test.js test/complexity-tool-subtraction.test.js test/bandit.test.js test/routing-propensity.test.js test/reward-pipeline.test.js test/knn-cold-start.test.js test/calibration.test.js test/feedback-loop.test.js test/session-fingerprint.test.js test/side-request-guards.test.js test/verifier.test.js test/intent-score.test.js test/difficulty-classifier.test.js test/classifier-setup.test.js test/usage-stats.test.js test/loop-guard.test.js test/moonshot-model-mapping.test.js",
40
+ "test:unit": "DATABRICKS_API_KEY=test-key DATABRICKS_API_BASE=http://test.com LOG_FILE_ENABLED=false node --test test/routing.test.js test/hybrid-routing-integration.test.js test/retry-logic.test.js test/sse-transformer.test.js test/passthrough-stream.test.js test/passthrough-mode.test.js test/openrouter-error-resilience.test.js test/format-conversion.test.js test/azure-openai-config.test.js test/azure-openai-format-conversion.test.js test/azure-openai-routing.test.js test/azure-openai-streaming.test.js test/azure-openai-error-resilience.test.js test/azure-openai-integration.test.js test/openai-integration.test.js test/atlas-integration.test.js test/toon-compression.test.js test/gcf-compression.test.js test/llamacpp-integration.test.js test/resilience.test.js test/telemetry-routing.test.js test/memory/store.test.js test/memory/surprise.test.js test/memory/extractor.test.js test/memory/search.test.js test/memory/retriever.test.js test/memory/distiller.test.js test/memory/distiller-freeze.test.js test/memory/wiki.test.js test/memory/skills-cache.test.js test/memory/tencentdb-launcher.test.js test/distill.test.js test/large-payload.test.js test/prompt-cache-injection.test.js test/risk-analyzer.test.js test/interaction-block.test.js test/preflight.test.js test/token-reduction.test.js test/session-affinity.test.js test/cache-state.test.js test/cache-switch-cost.test.js test/lens-recommendations.test.js test/model-registry-cost.test.js test/output-format-guard.test.js test/tier-fallback.test.js test/wrap.test.js test/init.test.js test/tool-call-response-metadata.test.js test/degradation.test.js test/routing-telemetry-columns.test.js test/sticky-routing.test.js test/knn-ambiguous-escalate.test.js test/deescalator.test.js test/client-profiles.test.js test/strip-internal-fields.test.js test/complexity-tool-subtraction.test.js test/bandit.test.js test/routing-propensity.test.js test/reward-pipeline.test.js test/knn-cold-start.test.js test/calibration.test.js test/feedback-loop.test.js test/session-fingerprint.test.js test/side-request-guards.test.js test/verifier.test.js test/intent-score.test.js test/difficulty-classifier.test.js test/classifier-setup.test.js test/usage-stats.test.js test/loop-guard.test.js test/moonshot-model-mapping.test.js test/baidu-model-mapping.test.js",
40
41
  "test:memory": "DATABRICKS_API_KEY=test-key DATABRICKS_API_BASE=http://test.com node --test test/memory/store.test.js test/memory/surprise.test.js test/memory/extractor.test.js test/memory/search.test.js test/memory/retriever.test.js test/memory/distiller.test.js test/memory/distiller-freeze.test.js test/memory/wiki.test.js test/memory/skills-cache.test.js test/memory/tencentdb-launcher.test.js",
41
42
  "test:new-features": "DATABRICKS_API_KEY=test-key DATABRICKS_API_BASE=http://test.com node --test test/passthrough-mode.test.js test/openrouter-error-resilience.test.js test/format-conversion.test.js",
42
43
  "test:performance": "DATABRICKS_API_KEY=test-key DATABRICKS_API_BASE=http://test.com node test/hybrid-routing-performance.test.js && DATABRICKS_API_KEY=test-key DATABRICKS_API_BASE=http://test.com node test/performance-tests.js",
@@ -92,6 +93,7 @@
92
93
  "@babel/parser": "^7.29.0",
93
94
  "@babel/traverse": "^7.29.0",
94
95
  "@blackwell-systems/gcf": "2.4.0",
96
+ "@tencentdb-agent-memory/memory-tencentdb": "^1.0.1",
95
97
  "@toon-format/toon": "^2.1.0",
96
98
  "cockatiel": "^3.2.1",
97
99
  "compression": "^1.7.4",
@@ -123,5 +125,8 @@
123
125
  "eslint": "^8.57.0",
124
126
  "nodemon": "^3.1.0",
125
127
  "pino-pretty": "^10.2.0"
128
+ },
129
+ "allowScripts": {
130
+ "@tencentdb-agent-memory/memory-tencentdb@1.0.1": true
126
131
  }
127
132
  }