lynkr 9.6.0 β†’ 9.7.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/install.sh CHANGED
@@ -128,63 +128,17 @@ install_dependencies() {
128
128
  fi
129
129
  }
130
130
 
131
- # Create default .env file
131
+ # Skip .env creation β€” the install script runs without a TTY when invoked via
132
+ # `curl | bash`, so the interactive `lynkr init` wizard can't run here. We leave
133
+ # .env unmade so the user is prompted to run `lynkr init` in their own shell
134
+ # afterward, which produces a fully-populated config (~150 keys grouped by
135
+ # section) instead of the old 892-line .env.example dump.
132
136
  create_env_file() {
133
- if [ ! -f "$INSTALL_DIR/.env" ]; then
134
- print_info "Creating .env configuration file..."
135
-
136
- # Try to copy from .env.example (comprehensive configuration)
137
- if [ -f "$INSTALL_DIR/.env.example" ]; then
138
- cp "$INSTALL_DIR/.env.example" "$INSTALL_DIR/.env"
139
- print_success "Created .env from .env.example (all features documented)"
140
- else
141
- # Fallback: create minimal .env if .env.example doesn't exist
142
- cat > "$INSTALL_DIR/.env" << 'EOF'
143
- # Lynkr Configuration
144
- # For full options, see: https://github.com/Fast-Editor/Lynkr/blob/main/.env.example
145
-
146
- # Model Provider (databricks, openai, azure-openai, azure-anthropic, openrouter, ollama, llamacpp)
147
- MODEL_PROVIDER=ollama
148
-
149
- # Server Configuration
150
- PORT=8081
151
-
152
- # Ollama Configuration (default for local development)
153
- OLLAMA_MODEL=qwen2.5-coder:7b
154
- OLLAMA_ENDPOINT=http://localhost:11434
155
-
156
- # Tier-based routing (uncomment and configure to enable)
157
- # TIER_SIMPLE=ollama:qwen2.5-coder:7b
158
- # TIER_MEDIUM=ollama:qwen2.5-coder:7b
159
- # TIER_COMPLEX=ollama:qwen2.5-coder:7b
160
- # TIER_REASONING=ollama:qwen2.5-coder:7b
161
-
162
- # Long-Term Memory System (Titans-Inspired) - Enabled by default
163
- MEMORY_ENABLED=true
164
- MEMORY_RETRIEVAL_LIMIT=5
165
- MEMORY_SURPRISE_THRESHOLD=0.3
166
-
167
- # Uncomment and configure your preferred cloud provider:
168
- # OPENAI_API_KEY=sk-your-key
169
- # OPENROUTER_API_KEY=your-key
170
- # DATABRICKS_API_KEY=your-key
171
- # DATABRICKS_API_BASE=https://your-workspace.databricks.com
172
- EOF
173
- print_success "Created basic .env file"
174
- fi
175
-
176
- echo ""
177
- print_info "πŸ“ Configuration ready! Key settings:"
178
- echo " β€’ Default provider: Ollama (local, offline)"
179
- echo " β€’ Memory system: Enabled (learns from conversations)"
180
- echo " β€’ Port: 8081"
181
- echo ""
182
- print_warning "To use cloud providers (Databricks/OpenAI/Azure):"
183
- echo " Edit: ${BLUE}nano $INSTALL_DIR/.env${NC}"
184
- echo " Add your API keys and change MODEL_PROVIDER"
185
- else
137
+ if [ -f "$INSTALL_DIR/.env" ]; then
186
138
  print_warning ".env file already exists, skipping"
139
+ return
187
140
  fi
141
+ print_info "Skipping .env creation β€” run ${BLUE}lynkr init${NC} after install for an interactive setup."
188
142
  }
189
143
 
190
144
  # Create symlink for global access
@@ -224,44 +178,35 @@ print_next_steps() {
224
178
  print_success "Lynkr installed successfully!"
225
179
  echo "=============================="
226
180
  echo ""
227
- echo "πŸš€ Quick Start Guide:"
181
+ echo "πŸš€ Quick Start:"
228
182
  echo ""
229
- echo " ${GREEN}Option A: Use Ollama (Free, Local, Offline)${NC}"
230
- echo " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
183
+ echo " 1. Run the setup wizard:"
184
+ echo " ${BLUE}lynkr init${NC} ${GREEN}← interactive config (4 prompts, ~30 sec)${NC}"
231
185
  echo ""
232
- echo " 1. Install Ollama (if not already installed):"
233
- echo " ${BLUE}lynkr-setup${NC} ${GREEN}← Automatic Ollama installer${NC}"
186
+ echo " The wizard asks for your usage mode (Claude Pro/Max via wrap, or direct"
187
+ echo " API), tier picks across 12 supported providers, credentials for what you"
188
+ echo " chose, and a few routing knobs. It writes a fully-populated .env with"
189
+ echo " production defaults for everything else (caching, compression, policy"
190
+ echo " budgets, MCP sandbox, agents, rate limiting)."
234
191
  echo ""
235
192
  echo " 2. Start Lynkr:"
236
- echo " ${BLUE}lynkr${NC}"
193
+ echo " ${BLUE}lynkr${NC} ${GREEN}← run as a proxy server${NC}"
194
+ echo " ${BLUE}lynkr wrap claude${NC} ${GREEN}← OR launch a wrapped AI tool${NC}"
237
195
  echo ""
238
- echo " 3. Configure Claude Code CLI:"
196
+ echo " 3. Point your tool at Lynkr:"
239
197
  echo " ${BLUE}export ANTHROPIC_BASE_URL=http://localhost:8081${NC}"
198
+ echo " ${BLUE}export ANTHROPIC_API_KEY=any-non-empty-value${NC}"
240
199
  echo " ${BLUE}claude${NC}"
241
200
  echo ""
242
- echo " ${YELLOW}Option B: Use Cloud Providers (Databricks/OpenAI/Azure)${NC}"
243
- echo " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
244
- echo ""
245
- echo " 1. Edit configuration file:"
246
- echo " ${BLUE}nano $INSTALL_DIR/.env${NC}"
247
- echo ""
248
- echo " Update these lines:"
249
- echo " ${BLUE}MODEL_PROVIDER=databricks${NC} ${GREEN}← Change from 'ollama'${NC}"
250
- echo " ${BLUE}DATABRICKS_API_KEY=dapi_xxxxx${NC} ${GREEN}← Add your key${NC}"
251
- echo " ${BLUE}DATABRICKS_API_BASE=https://your-workspace.databricks.com${NC}"
252
- echo ""
253
- echo " 2. Start Lynkr:"
254
- echo " ${BLUE}lynkr${NC}"
255
- echo ""
256
- echo " 3. Configure Claude Code CLI:"
257
- echo " ${BLUE}export ANTHROPIC_BASE_URL=http://localhost:8081${NC}"
258
- echo " ${BLUE}export ANTHROPIC_API_KEY=any-non-empty-value${NC} ${GREEN}← Placeholder${NC}"
259
- echo " ${BLUE}claude${NC}"
201
+ echo " ${YELLOW}Manual configuration (alternative)${NC}"
202
+ echo " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
203
+ echo " Copy ${BLUE}.env.example${NC} to ${BLUE}.env${NC} and edit by hand if you prefer."
204
+ echo " The 892-line template documents every available knob."
260
205
  echo ""
261
206
  echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
262
207
  echo ""
263
- echo "πŸ’‘ ${YELLOW}Tip:${NC} Memory system is enabled by default"
264
- echo " Lynkr remembers preferences and project context across sessions"
208
+ echo "πŸ’‘ ${YELLOW}Tip:${NC} Memory system, prompt caching, and TOON compression are all on"
209
+ echo " by default. The wizard's defaults match a production-grade Lynkr setup."
265
210
  echo ""
266
211
  echo "πŸ“š Documentation: ${BLUE}https://github.com/Fast-Editor/Lynkr${NC}"
267
212
  echo "πŸ’¬ Discord: ${BLUE}https://discord.gg/qF7DDxrX${NC}"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lynkr",
3
- "version": "9.6.0",
3
+ "version": "9.7.1",
4
4
  "description": "Self-hosted LLM gateway and tier-routing proxy for Claude Code, Cursor, and Codex. Routes across Ollama, AWS Bedrock, OpenRouter, Databricks, Azure OpenAI, llama.cpp, and LM Studio with prompt caching, MCP tools, and 60-80% cost savings.",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -16,7 +16,7 @@
16
16
  "dev": "nodemon index.js",
17
17
  "lint": "eslint src index.js",
18
18
  "test": "npm run test:unit && npm run test:performance",
19
- "test:unit": "DATABRICKS_API_KEY=test-key DATABRICKS_API_BASE=http://test.com node --test test/routing.test.js test/hybrid-routing-integration.test.js test/web-tools.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/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/distill.test.js test/large-payload.test.js test/code-mode.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/model-registry-cost.test.js test/task-decomposition.test.js test/output-format-guard.test.js test/tier-fallback.test.js",
19
+ "test:unit": "DATABRICKS_API_KEY=test-key DATABRICKS_API_BASE=http://test.com node --test test/routing.test.js test/hybrid-routing-integration.test.js test/web-tools.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/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/distill.test.js test/large-payload.test.js test/code-mode.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/model-registry-cost.test.js test/task-decomposition.test.js test/output-format-guard.test.js test/tier-fallback.test.js test/wrap.test.js test/init.test.js",
20
20
  "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",
21
21
  "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",
22
22
  "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",
@@ -79,6 +79,7 @@
79
79
  "express": "^5.1.0",
80
80
  "express-rate-limit": "^8.2.1",
81
81
  "fast-glob": "^3.3.2",
82
+ "graphify": "^1.0.0",
82
83
  "hnswlib-node": "^3.0.0",
83
84
  "js-tiktoken": "^1.0.20",
84
85
  "js-yaml": "^4.1.1",
@@ -89,7 +90,7 @@
89
90
  "undici": "^6.22.0"
90
91
  },
91
92
  "optionalDependencies": {
92
- "better-sqlite3": "^12.6.2",
93
+ "better-sqlite3": "^12.11.1",
93
94
  "dockerode": "^4.0.2",
94
95
  "tree-sitter": "^0.21.1",
95
96
  "tree-sitter-javascript": "^0.21.0",
@@ -51,7 +51,7 @@ async function _readTelemetry(days) {
51
51
  return db
52
52
  .prepare(
53
53
  `SELECT request_text AS query, provider, model, quality_score AS quality,
54
- cost, total_latency_ms AS latency, tier
54
+ cost_usd AS cost, latency_ms AS latency, tier
55
55
  FROM routing_telemetry
56
56
  WHERE timestamp >= ?
57
57
  AND quality_score IS NOT NULL
@@ -0,0 +1,105 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Convert routellm/gpt4_dataset (train.jsonl) β†’ Lynkr kNN bootstrap JSONL.
4
+
5
+ Stream-downloads from HuggingFace and maps mixtral_score (1-5) to your
6
+ TIER_* model config so the kNN index learns to recommend YOUR real models,
7
+ not RouteLLM's pool (mixtral / gpt-4).
8
+
9
+ Output format matches scripts/build-knn-index.js bootstrap input:
10
+ {"query": "...", "provider": "ollama", "model": "minimax-m2.5:cloud",
11
+ "quality": 95, "cost": 0.001, "latency": 200, "tier": "SIMPLE"}
12
+
13
+ Usage:
14
+ .venv/bin/python scripts/convert-routellm.py [--limit 30000] -o routellm.jsonl
15
+ """
16
+ import argparse
17
+ import json
18
+ import sys
19
+ import urllib.request
20
+ import ssl
21
+
22
+ # Map mixtral_score β†’ user's tier config.
23
+ # Spread is intentionally aggressive: only score=5 (Mixtral truly nailed it)
24
+ # stays SIMPLE; score=4 escalates to MEDIUM so the kNN index has enough MEDIUM
25
+ # examples to actually recommend non-SIMPLE tiers (vs the previous mapping
26
+ # where 86% of records were SIMPLE and kNN never disagreed with the heuristic).
27
+ SCORE_TO_ROUTE = {
28
+ 5: {"tier": "SIMPLE", "provider": "ollama", "model": "minimax-m2.5:cloud", "quality": 95, "cost": 0.001, "latency": 200},
29
+ 4: {"tier": "MEDIUM", "provider": "azure-anthropic", "model": "claude-sonnet-4-6", "quality": 80, "cost": 0.003, "latency": 1500},
30
+ 3: {"tier": "MEDIUM", "provider": "azure-anthropic", "model": "claude-sonnet-4-6", "quality": 70, "cost": 0.003, "latency": 1500},
31
+ 2: {"tier": "COMPLEX", "provider": "azure-anthropic", "model": "claude-sonnet-4-6", "quality": 55, "cost": 0.003, "latency": 1500},
32
+ 1: {"tier": "REASONING", "provider": "azure-anthropic", "model": "claude-opus-4-8", "quality": 40, "cost": 0.015, "latency": 3500},
33
+ }
34
+
35
+ URL = "https://huggingface.co/datasets/routellm/gpt4_dataset/resolve/main/train.jsonl"
36
+
37
+
38
+ def main():
39
+ p = argparse.ArgumentParser()
40
+ p.add_argument("--output", "-o", default="data/routellm-bootstrap.jsonl")
41
+ p.add_argument("--limit", type=int, default=None,
42
+ help="Only convert first N rows")
43
+ args = p.parse_args()
44
+
45
+ print(f"Streaming {URL} …", file=sys.stderr)
46
+ # Some macOS Python builds have SSL issues with huggingface.co β€” fall back
47
+ # to unverified context if needed. The data is public, no auth.
48
+ ctx = ssl.create_default_context()
49
+ try:
50
+ with urllib.request.urlopen(URL, context=ctx, timeout=60) as resp:
51
+ _stream(resp, args.output, args.limit)
52
+ except (ssl.SSLError, OSError) as e:
53
+ print(f"SSL strict failed ({e}); retrying with unverified context", file=sys.stderr)
54
+ ctx = ssl._create_unverified_context()
55
+ with urllib.request.urlopen(URL, context=ctx, timeout=60) as resp:
56
+ _stream(resp, args.output, args.limit)
57
+
58
+
59
+ def _stream(resp, out_path, limit):
60
+ written = 0
61
+ by_score = {s: 0 for s in SCORE_TO_ROUTE}
62
+ skipped = 0
63
+ with open(out_path, "w") as out:
64
+ for line in resp:
65
+ if limit and written >= limit:
66
+ break
67
+ try:
68
+ row = json.loads(line)
69
+ except Exception:
70
+ skipped += 1
71
+ continue
72
+ score = row.get("mixtral_score")
73
+ if score not in SCORE_TO_ROUTE:
74
+ skipped += 1
75
+ continue
76
+ route = SCORE_TO_ROUTE[score]
77
+ rec = {
78
+ "query": row.get("prompt", ""),
79
+ "provider": route["provider"],
80
+ "model": route["model"],
81
+ "quality": route["quality"],
82
+ "cost": route["cost"],
83
+ "latency": route["latency"],
84
+ "tier": route["tier"],
85
+ }
86
+ if not rec["query"]:
87
+ skipped += 1
88
+ continue
89
+ out.write(json.dumps(rec) + "\n")
90
+ written += 1
91
+ by_score[score] += 1
92
+ if written % 5000 == 0:
93
+ print(f" {written} written…", file=sys.stderr)
94
+
95
+ print(f"\nWrote {written} records to {out_path} ({skipped} skipped)", file=sys.stderr)
96
+ print(f"By mixtral_score: {by_score}", file=sys.stderr)
97
+ print(f"\nTier distribution:", file=sys.stderr)
98
+ for score, route in SCORE_TO_ROUTE.items():
99
+ n = by_score[score]
100
+ if n:
101
+ print(f" {route['tier']:>10} {route['provider']}:{route['model']:30s} n={n}", file=sys.stderr)
102
+
103
+
104
+ if __name__ == "__main__":
105
+ main()