lynkr 9.11.0 โ†’ 9.12.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)
@@ -28,25 +28,26 @@
28
28
 
29
29
  ## ๐Ÿš€ New: Wrap Mode for AI Coding Tools
30
30
 
31
- **Use Lynkr's routing with your AI coding assistant โ€” maximize your subscription value:**
31
+ **Use Lynkr's routing with your AI coding assistant โ€” BYO API keys, route by task complexity:**
32
32
 
33
33
  ```bash
34
34
  npm install -g lynkr
35
35
 
36
36
 
37
- # Claude Code Pro/Max
37
+ # Claude Code (with your own ANTHROPIC_API_KEY)
38
38
  lynkr wrap claude
39
39
 
40
40
 
41
41
  ```
42
42
 
43
43
  **Wrapping gives you:**
44
- - โœ… Tier routing (send simple tasks to free Ollama, complex to your subscription/API)
44
+ - โœ… Tier routing (send simple tasks to free Ollama, complex to your configured API provider)
45
45
  - โœ… Sticky sessions: one routing decision per conversation via content fingerprinting, with automatic escalation when the task outgrows the model
46
46
  - โœ… TOON/RTK compression (84% token reduction on large JSON tool outputs)
47
47
  - โœ… Semantic caching (sub-300ms cache hits, 0 tokens billed)
48
- - โœ… **3-5x more usage from the same subscription limits**
49
- - โœ… Works with OAuth (Claude, Copilot, Cursor) or API keys (Aider, Codex)
48
+ - โœ… Works with API keys (Claude, Aider, Codex, Copilot, Cursor)
49
+
50
+ > **Note on subscription (OAuth) logins:** Anthropic's Feb 2026 usage policy prohibits third-party tools from using OAuth tokens issued to Free/Pro/Max accounts. Wrap mode's tier routing, compression, and caching require an API key โ€” they do not apply to and are not compatible with Claude Pro/Max subscription auth. See [ToS Compliance](docs/wrap-guide.md#tos-compliance) for details.
50
51
 
51
52
  [Full wrap guide โ†’](docs/wrap-guide.md)
52
53
 
@@ -68,7 +69,7 @@ The fastest path is the interactive wizard:
68
69
  lynkr init
69
70
  ```
70
71
 
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).
72
+ It asks four questions โ€” usage mode (BYO API keys, direct), 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).
72
73
 
73
74
  Useful flags:
74
75
 
@@ -326,12 +327,45 @@ the local telemetry store:
326
327
  - **Routing accuracy** โ€” over-/under-provisioned request counts, a self-audit
327
328
  of the tier router's decisions
328
329
  - **Request logs** โ€” filterable by provider, tier, and errors, with latency,
329
- tokens, and cost per request
330
+ tokens, and cost per request; every session id links to a drill-down
330
331
  - **Provider health** โ€” configured providers, credential warnings, circuit
331
332
  breaker states
333
+ - **Insights** โ€” analyzer findings ranked by *past overspend* (auditable
334
+ against spend already recorded, never projected savings): dead tool
335
+ schemas paying rent, harness side-traffic served on expensive tiers,
336
+ weak prompt-cache hit ratios, and Wilson-bounded tier-downsize verdicts.
337
+ Each finding ships its evidence rows, the fix, the caveat, and how the
338
+ figure was computed. "Not measured yet" is rendered distinctly from
339
+ zero โ€” an un-run scan is not an all-clear.
340
+ - **Explore** โ€” a Metric ร— Dimension (ร— Stack) pivot over routing telemetry
341
+ (spend/tokens/requests/sessions by provider, model, tier, request type,
342
+ dayโ€ฆ) with preset views, period-over-period deltas, and CSV export
343
+ - **Session drill-down** โ€” per-model cost mix and an input-context-per-call
344
+ chart where distillation and cache behavior are directly visible
345
+ - **Cache receipts** โ€” projected switch/hold economics from cache-aware
346
+ routing next to the *measured* companion (dollars saved by cache reads
347
+ actually served)
348
+
349
+ JSON APIs behind it
350
+ (`/dashboard/api/overview|usage|routing|logs|recommendations|analytics|sessions/:id|statusline`)
351
+ if you want the raw numbers.
352
+
353
+ ### Status line for Claude Code
354
+
355
+ One line after every turn โ€” routed tier, served model, today's spend, and
356
+ cache re-read share โ€” with zero token cost (out-of-band, never enters model
357
+ context). Add to `~/.claude/settings.json`:
358
+
359
+ ```json
360
+ "statusLine": { "type": "command", "command": "lynkr-statusline" }
361
+ ```
362
+
363
+ ```
364
+ โ—† Opus 4.8 ยท MEDIUM โ†’ minimax-m3:cloud (ollama) ยท today $0.06 ยท cache 67%
365
+ ```
332
366
 
333
- JSON APIs behind it (`/dashboard/api/overview|usage|routing|logs`) if you want
334
- the raw numbers.
367
+ Always prints exactly one line and always exits 0 โ€” a down proxy can never
368
+ break the harness. Set `LYNKR_PORT` if Lynkr isn't on 8081.
335
369
 
336
370
  ### Cost tracking & model pricing
337
371
  Per-request cost is computed from a model-pricing registry (LiteLLM โ†’ models.dev,
@@ -551,7 +585,8 @@ With tier routing + token optimization: **additional 50-87% savings** on cloud p
551
585
  | **TOON JSON compression** | โœ… up to 87.6% | โŒ | โŒ | โŒ |
552
586
  | **Upstream SSE streaming** | โœ… native passthrough + cross-format transform | โš ๏ธ passthrough only | โœ… | โš ๏ธ |
553
587
  | **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) |
588
+ | **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) |
589
+ | **Cost recommendations** | โœ… evidence-backed findings (dead tool schemas, side-traffic, cache weak spots, Wilson-proven downsizes) ranked by past overspend | โŒ | โŒ | โš ๏ธ alerts, no findings |
555
590
  | **Long-term memory** | โœ… SQLite, per-session | โŒ | โŒ | โŒ |
556
591
  | **MCP integration** | โœ… | โŒ | โŒ | โŒ |
557
592
  | **Self-hosted** | โœ… Node.js only | โœ… Python stack | โŒ SaaS | โœ… Docker |
@@ -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.12.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/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",
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
  }