@nxuss/lemma 0.9.2 → 1.0.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 +79 -12
- package/bin/cli.js +97 -0
- package/bin/init.js +307 -0
- package/cache-proxy.js +191 -0
- package/dist/cjs/cli/lemma-proxy.d.ts.map +1 -1
- package/dist/cjs/cli/lemma-proxy.js +2 -4
- package/dist/cjs/cli/lemma-proxy.js.map +1 -1
- package/dist/cjs/mcp/index.js +1 -1
- package/dist/cjs/mcp/tools.d.ts.map +1 -1
- package/dist/cjs/mcp/tools.js +911 -71
- package/dist/cjs/mcp/tools.js.map +1 -1
- package/dist/cjs/subconscious/TheBrainV2.d.ts +102 -0
- package/dist/cjs/subconscious/TheBrainV2.d.ts.map +1 -0
- package/dist/cjs/subconscious/TheBrainV2.js +444 -0
- package/dist/cjs/subconscious/TheBrainV2.js.map +1 -0
- package/dist/cjs/utils/ContextCollapser.d.ts +21 -0
- package/dist/cjs/utils/ContextCollapser.d.ts.map +1 -0
- package/dist/cjs/utils/ContextCollapser.js +132 -0
- package/dist/cjs/utils/ContextCollapser.js.map +1 -0
- package/dist/cjs/utils/ExecutiveReport.d.ts +29 -0
- package/dist/cjs/utils/ExecutiveReport.d.ts.map +1 -0
- package/dist/cjs/utils/ExecutiveReport.js +111 -0
- package/dist/cjs/utils/ExecutiveReport.js.map +1 -0
- package/dist/cjs/utils/SurgicalASTInsert.d.ts +21 -0
- package/dist/cjs/utils/SurgicalASTInsert.d.ts.map +1 -0
- package/dist/cjs/utils/SurgicalASTInsert.js +173 -0
- package/dist/cjs/utils/SurgicalASTInsert.js.map +1 -0
- package/dist/cjs/utils/SymbolSurgicalContext.d.ts +25 -0
- package/dist/cjs/utils/SymbolSurgicalContext.d.ts.map +1 -0
- package/dist/cjs/utils/SymbolSurgicalContext.js +265 -0
- package/dist/cjs/utils/SymbolSurgicalContext.js.map +1 -0
- package/dist/cjs/utils/WormholeCompressor.d.ts +22 -0
- package/dist/cjs/utils/WormholeCompressor.d.ts.map +1 -0
- package/dist/cjs/utils/WormholeCompressor.js +152 -0
- package/dist/cjs/utils/WormholeCompressor.js.map +1 -0
- package/dist/esm/cli/lemma-proxy.d.ts.map +1 -1
- package/dist/esm/cli/lemma-proxy.js +2 -4
- package/dist/esm/cli/lemma-proxy.js.map +1 -1
- package/dist/esm/mcp/index.js +1 -1
- package/dist/esm/mcp/tools.d.ts.map +1 -1
- package/dist/esm/mcp/tools.js +911 -71
- package/dist/esm/mcp/tools.js.map +1 -1
- package/dist/esm/subconscious/TheBrainV2.d.ts +102 -0
- package/dist/esm/subconscious/TheBrainV2.d.ts.map +1 -0
- package/dist/esm/subconscious/TheBrainV2.js +431 -0
- package/dist/esm/subconscious/TheBrainV2.js.map +1 -0
- package/dist/esm/utils/ContextCollapser.d.ts +21 -0
- package/dist/esm/utils/ContextCollapser.d.ts.map +1 -0
- package/dist/esm/utils/ContextCollapser.js +128 -0
- package/dist/esm/utils/ContextCollapser.js.map +1 -0
- package/dist/esm/utils/ExecutiveReport.d.ts +29 -0
- package/dist/esm/utils/ExecutiveReport.d.ts.map +1 -0
- package/dist/esm/utils/ExecutiveReport.js +105 -0
- package/dist/esm/utils/ExecutiveReport.js.map +1 -0
- package/dist/esm/utils/SurgicalASTInsert.d.ts +21 -0
- package/dist/esm/utils/SurgicalASTInsert.d.ts.map +1 -0
- package/dist/esm/utils/SurgicalASTInsert.js +134 -0
- package/dist/esm/utils/SurgicalASTInsert.js.map +1 -0
- package/dist/esm/utils/SymbolSurgicalContext.d.ts +25 -0
- package/dist/esm/utils/SymbolSurgicalContext.d.ts.map +1 -0
- package/dist/esm/utils/SymbolSurgicalContext.js +226 -0
- package/dist/esm/utils/SymbolSurgicalContext.js.map +1 -0
- package/dist/esm/utils/WormholeCompressor.d.ts +22 -0
- package/dist/esm/utils/WormholeCompressor.d.ts.map +1 -0
- package/dist/esm/utils/WormholeCompressor.js +148 -0
- package/dist/esm/utils/WormholeCompressor.js.map +1 -0
- package/lemma-proxy.cjs +31 -14
- package/mcp-server.js +220 -0
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
# 🧠 Lemma
|
|
1
|
+
# 🧠 Lemma v1.0.0
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@nxuss/lemma)
|
|
4
4
|
[](https://github.com/Nxusbets/lemma/blob/main/LICENSE)
|
|
5
|
-
[](https://github.com/Nxusbets/lemma)
|
|
6
6
|
[](https://github.com/Nxusbets/lemma)
|
|
7
7
|
[](https://github.com/Nxusbets/lemma)
|
|
8
8
|
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
> **The Intelligent local-first AI Gateway — Privacy, Performance, and Static Code Intelligence for the Agentic Era.**
|
|
18
18
|
|
|
19
|
-
Lemma is a high-performance orchestration layer that sits between your development environment and LLM providers. It transforms the way you build with AI by providing **Shared Semantic Memory**, **Autonomous Cost Optimization**, **Privacy Guardrails**, and a suite of **
|
|
19
|
+
Lemma is a high-performance orchestration layer that sits between your development environment and LLM providers. It transforms the way you build with AI by providing **Shared Semantic Memory**, **Autonomous Cost Optimization**, **Privacy Guardrails**, and a suite of **38 MCP tools** — including zero-token static code intelligence that runs entirely on your machine with no LLM calls required.
|
|
20
20
|
|
|
21
21
|
---
|
|
22
22
|
|
|
@@ -31,21 +31,22 @@ Lemma is a high-performance orchestration layer that sits between your developme
|
|
|
31
31
|
| **Complexity Router** | ✅ | ✅ |
|
|
32
32
|
| **Dashboard** | Basic stats | ✅ Full time-travel |
|
|
33
33
|
| **Proxy requests/month** | 300 | ✅ Unlimited |
|
|
34
|
-
| **MCP Server (all
|
|
34
|
+
| **MCP Server (all 38 tools)** | ❌ | ✅ |
|
|
35
35
|
| **The Brain — Semantic Memory** | ❌ | ✅ |
|
|
36
36
|
| **Zero-Token Static Analysis** | ❌ | ✅ |
|
|
37
37
|
| **Cross-Project Telepathy** | ❌ | ✅ |
|
|
38
38
|
| **AutoPilot & Auto-Heal** | ❌ | ✅ |
|
|
39
|
+
| **`depgraph` — Dependency Graph** | ❌ | ✅ |
|
|
40
|
+
| **`refactor` — Codemod Engine** | ❌ | ✅ |
|
|
39
41
|
|
|
40
|
-
> **The MCP is Pro.** All
|
|
42
|
+
> **The MCP is Pro.** All 38 tools — The Brain, PreCrime predictor, AST Hologram, Entropy Score, Coupling Radar, `depgraph`, `refactor`, and every optimization tool — require a Pro license. Activate with `lemma activate <key>` → [Get Pro](https://lemma.nxus.studio/upgrade)
|
|
41
43
|
|
|
42
44
|
---
|
|
43
45
|
|
|
44
46
|
## ⚡ What's in the Pro MCP
|
|
45
47
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
Lemma maintains a persistent vector database (ChromaDB) that accumulates knowledge across all your projects. Every solution, bug fix, and architecture decision you work through gets stored and semantically indexed. Future sessions can retrieve this knowledge in milliseconds — no cloud, no tokens, no re-explaining.
|
|
48
|
+
### 🧠 Shared Semantic Memory — The Brain (Zero-Dependency)
|
|
49
|
+
Lemma maintains a persistent semantic cache index (`TheBrainV2`) using Okapi BM25 + Jaccard re-ranking and Bloom Filters that runs entirely on your local machine with NO external ML dependencies (no ChromaDB, no Ollama required). Every solution, bug fix, and architecture decision you work through gets stored and semantically indexed globally across all your projects. Future sessions can retrieve this knowledge in milliseconds (<10ms) — saving you thousands of tokens.
|
|
49
50
|
|
|
50
51
|
### 🛡️ Privacy Firewall (Semantic Scrubber)
|
|
51
52
|
**Enterprise-Grade Privacy.** Prevents API keys, credentials, and PII from ever leaving your machine. Applied automatically to every file read and workspace search via MCP.
|
|
@@ -62,6 +63,48 @@ Mathematical chaos score (0-100) per file using the TS Compiler API. Measures cy
|
|
|
62
63
|
### 🗺️ AST Hologram — Zero Tokens
|
|
63
64
|
Scans the entire workspace and returns a dense JSON index of all exported symbols (classes, functions, interfaces, types, consts) with file paths and line numbers. Use this instead of reading individual files — saves up to 90% tokens when navigating large codebases.
|
|
64
65
|
|
|
66
|
+
### 📡 `depgraph` — Real-Time Dependency Graph *(NEW in v0.9.3)*
|
|
67
|
+
**Architecture-aware blast radius analysis.** Builds a real-time, bidirectional dependency graph for any file in the workspace using the TypeScript Compiler API — zero LLM calls, zero tokens. Shows exactly what a file imports, what imports it (reverse dependencies), and what symbols it exports.
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
📦 src/services/pantry.ts
|
|
71
|
+
├── importa:
|
|
72
|
+
│ ├── src/lib/utils/ingredient-parser.ts → parseIngredientString, normalizeUnit
|
|
73
|
+
│ └── src/db/prisma.ts → PrismaClient
|
|
74
|
+
├── es importado por:
|
|
75
|
+
│ ├── src/app/actions/generate-recipe.ts
|
|
76
|
+
│ └── src/components/PantryWidget.tsx
|
|
77
|
+
└── exporta:
|
|
78
|
+
├── applyRecipeDeductions
|
|
79
|
+
└── getPantryBalance
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Supports `--depth` (1-5), `--format text|mermaid|json`, `--reverse`, `--circular` (cycle detection), and `--external` (include node_modules).
|
|
83
|
+
|
|
84
|
+
### 🔧 `refactor` — Declarative Codemod Engine *(NEW in v0.9.3)*
|
|
85
|
+
**Multi-file refactoring with surgical precision.** Rename any symbol across the entire workspace or move files updating all importers — using the TypeScript Compiler API for accurate AST-based reference detection. Zero LLM calls.
|
|
86
|
+
|
|
87
|
+
**`rename`** — Finds every reference to a symbol (via AST, not text search) and renames it everywhere:
|
|
88
|
+
```bash
|
|
89
|
+
# Preview first
|
|
90
|
+
lemma refactor --operation rename --from "applyRecipeDeductions" --to "deductPantryIngredients" \
|
|
91
|
+
--file src/services/pantry.ts --dryRun
|
|
92
|
+
|
|
93
|
+
# Apply with TypeScript verification
|
|
94
|
+
lemma refactor --operation rename --from "applyRecipeDeductions" --to "deductPantryIngredients" \
|
|
95
|
+
--file src/services/pantry.ts --tscCheck
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
**`move`** — Moves a file physically and rewires every import path in the workspace:
|
|
99
|
+
```bash
|
|
100
|
+
lemma refactor --operation move \
|
|
101
|
+
--from src/lib/validation/recipe.schema.ts \
|
|
102
|
+
--to src/lib/schemas/recipe.ts \
|
|
103
|
+
--tscCheck
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Supports `--dryRun` (unified diff preview), `--tscCheck` (post-refactor type verification), and `--backup` (`.bak` files).
|
|
107
|
+
|
|
65
108
|
### 🚦 Complexity Router
|
|
66
109
|
Intelligently evaluates each prompt and routes it to the most cost-effective model. Reserves premium models for complex reasoning, uses smaller models for simple tasks. Slashes your AI bill without sacrificing quality.
|
|
67
110
|
|
|
@@ -74,6 +117,21 @@ Exposes `write_workspace_file`, smart `apply_workspace_patch` (collision-safe se
|
|
|
74
117
|
### 🩺 AutoPilot & Auto-Heal
|
|
75
118
|
Lemma's background file watcher detects TypeScript compilation errors on every save, parses the error, and calls the auto-heal agent to apply a fix — automatically. When Autopilot succeeds, the fix is stored in The Brain for future sessions.
|
|
76
119
|
|
|
120
|
+
### 🧬 `get_symbol_surgical_context` — Surgical Symbol Context *(NEW in v1.0.0)*
|
|
121
|
+
**Saves up to 90% input tokens.** Instead of reading complete dependency source files, this tool parses your workspace via TS Compiler AST and extracts ONLY the targeted symbol implementation along with the precise function/class signatures of all its direct workspace imports.
|
|
122
|
+
|
|
123
|
+
### ⚙️ `surgical_ast_insert` — AST Surgical Code Injector *(NEW in v1.0.0)*
|
|
124
|
+
**Saves up to 95% output tokens.** Removes the need for the LLM to output massive file diffs or rewrite entire files. The LLM only outputs the exact function snippet, and Lemma compiles it surgically into the class/scope AST at the specified anchor point.
|
|
125
|
+
|
|
126
|
+
### 🩹 `local_semantic_autofix` — Offline Self-Healing *(NEW in v1.0.0)*
|
|
127
|
+
**Resolves compilation/runtime crashes in <15ms with 0 tokens.** When your tests or compilers crash, Lemma queries the global error index in `TheBrainV2`. If a matching traceback fix is found in memory (solved in this or any other repo on your machine), it applies the resolution instantly.
|
|
128
|
+
|
|
129
|
+
### 💰 `generate_executive_roi_report` — Corporate ROI Ledger *(NEW in v1.0.0)*
|
|
130
|
+
Generates a complete executive Markdown/PDF-ready report of total direct API dollars saved, developer hours reclaimed from caching, security masking events, and scalability financial projections for engineering managers.
|
|
131
|
+
|
|
132
|
+
### 🌀 `wormhole_squeeze` — Reversible Code Compression *(NEW in v1.0.0)*
|
|
133
|
+
Compresses your source code using non-colliding mathematical unicode symbols (like `⨃`, `⨂`, `⨁`) and temporary aliases to shrink file sizes by up to 70% while keeping it 100% reversible.
|
|
134
|
+
|
|
77
135
|
---
|
|
78
136
|
|
|
79
137
|
## 🚀 Quick Start (Zero Config)
|
|
@@ -142,11 +200,11 @@ Just run `lemma init`. Lemma automatically registers its MCP server in your `~/.
|
|
|
142
200
|
}
|
|
143
201
|
```
|
|
144
202
|
|
|
145
|
-
Once connected, your IDE gains access to all **
|
|
203
|
+
Once connected, your IDE gains access to all **38 MCP tools** and Lemma auto-injects the TurboMode system prompt on handshake.
|
|
146
204
|
|
|
147
205
|
---
|
|
148
206
|
|
|
149
|
-
## 🛠️ MCP Tools Reference (v0.9.
|
|
207
|
+
## 🛠️ MCP Tools Reference (v0.9.3 — 38 Tools)
|
|
150
208
|
|
|
151
209
|
### 🗂️ Workspace Tools
|
|
152
210
|
|
|
@@ -193,7 +251,7 @@ Once connected, your IDE gains access to all **32 MCP tools** and Lemma auto-inj
|
|
|
193
251
|
| `auto_heal` | Diagnose and auto-heal the latest crash from Lemma's live context logs. |
|
|
194
252
|
| `batch_tool_calls` | Execute multiple tool calls in parallel via `Promise.all`. High-throughput operations in a single MCP round-trip. |
|
|
195
253
|
|
|
196
|
-
### 🔮 Zero-Token Static Intelligence Tools
|
|
254
|
+
### 🔮 Zero-Token Static Intelligence Tools
|
|
197
255
|
|
|
198
256
|
> All tools in this section use **zero LLM tokens, zero Ollama, zero cloud**. Pure local computation via TypeScript Compiler API, git, and math.
|
|
199
257
|
|
|
@@ -207,7 +265,16 @@ Once connected, your IDE gains access to all **32 MCP tools** and Lemma auto-inj
|
|
|
207
265
|
| `semantic_dedup_guard` | **Brain dedup firewall.** Before calling `store_memory`, pass content through here. If similarity ≥ 92% → rejects as duplicate. If 75–91% → warns and shows the similar entry. Keeps The Brain lean and dense. |
|
|
208
266
|
| `dead_export_necromancer` | **Dead code auditor.** Maps every `export` in the workspace against every `import`. Symbols exported but never imported anywhere = zombie code inflating your context. Reports dead exports by file with estimated token waste. |
|
|
209
267
|
|
|
210
|
-
###
|
|
268
|
+
### 🏗️ Architecture & Refactoring Tools *(NEW in v0.9.3)*
|
|
269
|
+
|
|
270
|
+
> Architecture-aware tools that combine dependency analysis with surgical code transformation. **Zero LLM calls.**
|
|
271
|
+
|
|
272
|
+
| Tool | Description |
|
|
273
|
+
| :--- | :--- |
|
|
274
|
+
| `depgraph` | **Real-time dependency graph.** Builds a bidirectional import/export map for any file. Shows what a file imports, what imports it (reverse deps with `--reverse`), and what symbols it exports. Supports `--depth` 1-5, `--format text\|mermaid\|json`, `--circular` (cycle detection), and `--external` (include node_modules). Use before any refactor to measure blast radius. |
|
|
275
|
+
| `refactor` | **Declarative codemod engine.** `rename` renames a symbol across all files using AST-based reference detection (no false positives from string search). `move` relocates a file and rewires every import path in the workspace. Both support `--dryRun` (unified diff preview), `--tscCheck` (post-apply type verification), and `--backup` (`.bak` files). |
|
|
276
|
+
|
|
277
|
+
### 🔍 PR Review Agent Tools
|
|
211
278
|
|
|
212
279
|
| Tool | Description |
|
|
213
280
|
| :--- | :--- |
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const os = require('os');
|
|
7
|
+
const { execSync } = require('child_process');
|
|
8
|
+
|
|
9
|
+
const LEMMA_CACHE = path.join(os.homedir(), '.lemma-cache');
|
|
10
|
+
const BRAIN_FILE = path.join(LEMMA_CACHE, 'lemma_brain.json');
|
|
11
|
+
const PORT = process.env.LEMMA_PROXY_PORT || '9090';
|
|
12
|
+
|
|
13
|
+
const INPUT_COST = 3.00 / 1000000;
|
|
14
|
+
const OUTPUT_COST = 15.00 / 1000000;
|
|
15
|
+
const C = '\x1b[36m', G = '\x1b[32m', Y = '\x1b[33m', D = '\x1b[2m', R = '\x1b[0m', B = '\x1b[1m';
|
|
16
|
+
|
|
17
|
+
function loadBrain() {
|
|
18
|
+
try {
|
|
19
|
+
if (fs.existsSync(BRAIN_FILE)) return JSON.parse(fs.readFileSync(BRAIN_FILE, 'utf8'));
|
|
20
|
+
} catch {}
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function daemonStatus() {
|
|
25
|
+
try {
|
|
26
|
+
const isMac = process.platform === 'darwin';
|
|
27
|
+
if (isMac) {
|
|
28
|
+
execSync('launchctl list com.lemma.cache-proxy 2>/dev/null || true', { stdio: 'pipe' });
|
|
29
|
+
return 'running';
|
|
30
|
+
}
|
|
31
|
+
if (process.platform === 'linux') {
|
|
32
|
+
execSync('systemctl --user is-active lemma-cache-proxy 2>/dev/null || true', { stdio: 'pipe' });
|
|
33
|
+
return 'running';
|
|
34
|
+
}
|
|
35
|
+
} catch {}
|
|
36
|
+
try {
|
|
37
|
+
const port = PORT;
|
|
38
|
+
const http = require('http');
|
|
39
|
+
return new Promise(resolve => {
|
|
40
|
+
const req = http.get(`http://localhost:${port}/v1/models`, res => resolve('running'));
|
|
41
|
+
req.on('error', () => resolve('stopped'));
|
|
42
|
+
req.setTimeout(1000, () => { req.destroy(); resolve('stopped'); });
|
|
43
|
+
});
|
|
44
|
+
} catch { return 'unknown'; }
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function main() {
|
|
48
|
+
const brain = loadBrain();
|
|
49
|
+
const daemon = await daemonStatus();
|
|
50
|
+
|
|
51
|
+
process.stderr.write(`\n ${B}${C}═══ Lemma Cache Dashboard ═══${R}\n\n`);
|
|
52
|
+
|
|
53
|
+
const statusColor = daemon === 'running' ? G : Y;
|
|
54
|
+
process.stderr.write(` ${C}Proxy:${R} :${PORT} ${statusColor}${daemon === 'running' ? '● running' : '○ stopped'}${R}\n`);
|
|
55
|
+
process.stderr.write(` ${C}MCP Server:${R} lemma mcp\n`);
|
|
56
|
+
process.stderr.write(` ${C}Env File:${R} ${D}${LEMMA_CACHE}/env${R}\n\n`);
|
|
57
|
+
|
|
58
|
+
if (!brain || brain.length === 0) {
|
|
59
|
+
process.stderr.write(` ${Y}The Brain is empty.${R} Start using your tools with the cache proxy.\n`);
|
|
60
|
+
process.stderr.write(` ${D}Every cached response saves tokens and money.${R}\n\n`);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
let totalHits = 0, totalInputTokens = 0, totalOutputTokens = 0;
|
|
65
|
+
for (const e of brain) {
|
|
66
|
+
const hits = e.hits || 0;
|
|
67
|
+
totalHits += hits;
|
|
68
|
+
totalInputTokens += Math.ceil((e.prompt || '').length / 4) * (hits + 1);
|
|
69
|
+
totalOutputTokens += Math.ceil((e.response || '').length / 4) * (hits + 1);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const firstInput = Math.ceil(brain.reduce((s, e) => s + (e.prompt || '').length, 0) / 4);
|
|
73
|
+
const firstOutput = Math.ceil(brain.reduce((s, e) => s + (e.response || '').length, 0) / 4);
|
|
74
|
+
const avoidedInput = totalInputTokens - firstInput;
|
|
75
|
+
const avoidedOutput = totalOutputTokens - firstOutput;
|
|
76
|
+
const totalTokens = avoidedInput + avoidedOutput;
|
|
77
|
+
const moneySaved = avoidedInput * INPUT_COST + avoidedOutput * OUTPUT_COST;
|
|
78
|
+
|
|
79
|
+
process.stderr.write(` ${C}Entries:${R} ${brain.length}\n`);
|
|
80
|
+
process.stderr.write(` ${C}Cache Hits:${R} ${totalHits}\n`);
|
|
81
|
+
|
|
82
|
+
const fmt = (n) => n >= 1000000 ? (n/1000000).toFixed(2) + 'M' : n >= 1000 ? (n/1000).toFixed(1) + 'K' : n;
|
|
83
|
+
process.stderr.write(` ${C}Tokens Saved:${R} ${fmt(totalTokens)} (in:${fmt(avoidedInput)} out:${fmt(avoidedOutput)})\n`);
|
|
84
|
+
process.stderr.write(` ${G}Money Saved:${R} $${moneySaved.toFixed(4)}\n\n`);
|
|
85
|
+
|
|
86
|
+
const top = [...brain].sort((a, b) => (b.hits||0) - (a.hits||0)).slice(0, 5);
|
|
87
|
+
if (top.length > 0 && top[0].hits > 0) {
|
|
88
|
+
process.stderr.write(` ${Y}Top Hits:${R}\n`);
|
|
89
|
+
for (let i = 0; i < top.length; i++) {
|
|
90
|
+
const preview = (top[i].prompt || '').replace(/\n/g, ' ').substring(0, 55);
|
|
91
|
+
process.stderr.write(` ${D}${i+1}.${R} ${top[i].hits}x ${preview}${preview.length >= 55 ? '...' : ''}\n`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
process.stderr.write(`\n ${D}Pricing: Claude 3.5 Sonnet — Input $3/M, Output $15/M${R}\n\n`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
main().catch(() => {});
|
package/bin/init.js
ADDED
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const os = require('os');
|
|
7
|
+
const { execSync, spawn } = require('child_process');
|
|
8
|
+
|
|
9
|
+
const LEMMA_CACHE = path.join(os.homedir(), '.lemma-cache');
|
|
10
|
+
const BRAIN_FILE = path.join(LEMMA_CACHE, 'lemma_brain.json');
|
|
11
|
+
const ENV_FILE = path.join(LEMMA_CACHE, 'env');
|
|
12
|
+
const MCP_SERVER_PATH = path.join(__dirname, '..', 'mcp-server.js');
|
|
13
|
+
const PROXY_PATH = path.join(__dirname, '..', 'cache-proxy.js');
|
|
14
|
+
const PORT = '9090';
|
|
15
|
+
const CYAN = '\x1b[36m', GREEN = '\x1b[32m', YELLOW = '\x1b[33m', DIM = '\x1b[2m', RESET = '\x1b[0m', BOLD = '\x1b[1m';
|
|
16
|
+
|
|
17
|
+
function ensureDir(p) { try { fs.mkdirSync(p, { recursive: true }); } catch {} }
|
|
18
|
+
|
|
19
|
+
function step(msg) { process.stderr.write(` ${CYAN}✦${RESET} ${msg}\n`); }
|
|
20
|
+
|
|
21
|
+
function ok(msg) { process.stderr.write(` ${GREEN}✓${RESET} ${msg}\n`); }
|
|
22
|
+
|
|
23
|
+
function skip(msg) { process.stderr.write(` ${DIM}−${RESET} ${DIM}${msg}${RESET}\n`); }
|
|
24
|
+
|
|
25
|
+
function warn(msg) { process.stderr.write(` ${YELLOW}⚠${RESET} ${msg}\n`); }
|
|
26
|
+
|
|
27
|
+
function detectTool(name, checkFn) {
|
|
28
|
+
try {
|
|
29
|
+
const result = checkFn();
|
|
30
|
+
return result || !!result;
|
|
31
|
+
} catch { return false; }
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function configureContinueDotDev() {
|
|
35
|
+
const configDir = path.join(os.homedir(), '.continue');
|
|
36
|
+
const configFile = path.join(configDir, 'config.json');
|
|
37
|
+
if (!fs.existsSync(configFile)) return false;
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
let config = JSON.parse(fs.readFileSync(configFile, 'utf8'));
|
|
41
|
+
if (!config.models) config.models = [];
|
|
42
|
+
const already = config.models.some(m => m.apiBase === `http://localhost:${PORT}`);
|
|
43
|
+
if (!already) {
|
|
44
|
+
config.models.push({
|
|
45
|
+
title: 'Lemma Cache Proxy',
|
|
46
|
+
provider: 'openai',
|
|
47
|
+
model: 'ANY',
|
|
48
|
+
apiBase: `http://localhost:${PORT}`,
|
|
49
|
+
apiKey: 'sk-lemma-cache-proxy',
|
|
50
|
+
description: 'Automatic BM25 semantic cache — local, zero-cost on hits'
|
|
51
|
+
});
|
|
52
|
+
fs.writeFileSync(configFile, JSON.stringify(config, null, 2), 'utf8');
|
|
53
|
+
}
|
|
54
|
+
return true;
|
|
55
|
+
} catch { return false; }
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function configureCursor() {
|
|
59
|
+
const cursorDir = path.join(os.homedir(), '.cursor');
|
|
60
|
+
const settingsFile = path.join(cursorDir, 'settings.json');
|
|
61
|
+
try {
|
|
62
|
+
let settings = {};
|
|
63
|
+
if (fs.existsSync(settingsFile)) {
|
|
64
|
+
settings = JSON.parse(fs.readFileSync(settingsFile, 'utf8'));
|
|
65
|
+
}
|
|
66
|
+
settings['openai.apiBase'] = `http://localhost:${PORT}`;
|
|
67
|
+
fs.writeFileSync(settingsFile, JSON.stringify(settings, null, 2), 'utf8');
|
|
68
|
+
return true;
|
|
69
|
+
} catch {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function configureOpenCodeMcp() {
|
|
75
|
+
const configDir = path.join(os.homedir(), '.opencode');
|
|
76
|
+
const configFile = path.join(configDir, 'mcp.json');
|
|
77
|
+
ensureDir(configDir);
|
|
78
|
+
let config = {};
|
|
79
|
+
try {
|
|
80
|
+
if (fs.existsSync(configFile)) config = JSON.parse(fs.readFileSync(configFile, 'utf8'));
|
|
81
|
+
} catch {}
|
|
82
|
+
if (!config.mcpServers) config.mcpServers = {};
|
|
83
|
+
config.mcpServers['lemma-mcp-server'] = {
|
|
84
|
+
command: 'node', args: [MCP_SERVER_PATH], type: 'local', enabled: true
|
|
85
|
+
};
|
|
86
|
+
fs.writeFileSync(configFile, JSON.stringify(config, null, 2), 'utf8');
|
|
87
|
+
|
|
88
|
+
const localOpenCode = path.join(process.cwd(), 'opencode.json');
|
|
89
|
+
if (fs.existsSync(localOpenCode)) {
|
|
90
|
+
try {
|
|
91
|
+
const lc = JSON.parse(fs.readFileSync(localOpenCode, 'utf8'));
|
|
92
|
+
if (!lc.mcp) lc.mcp = {};
|
|
93
|
+
if (!lc.mcp['lemma-mcp-server']) {
|
|
94
|
+
lc.mcp['lemma-mcp-server'] = {
|
|
95
|
+
type: 'local', command: ['node', MCP_SERVER_PATH], enabled: true
|
|
96
|
+
};
|
|
97
|
+
fs.writeFileSync(localOpenCode, JSON.stringify(lc, null, 2), 'utf8');
|
|
98
|
+
}
|
|
99
|
+
} catch {}
|
|
100
|
+
}
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function configureClaudeDesktop() {
|
|
105
|
+
const configDir = path.join(os.homedir(), '.lemma-cache');
|
|
106
|
+
const configFile = path.join(configDir, 'claude_mcp.json');
|
|
107
|
+
let config = {};
|
|
108
|
+
try {
|
|
109
|
+
if (fs.existsSync(configFile)) config = JSON.parse(fs.readFileSync(configFile, 'utf8'));
|
|
110
|
+
} catch {}
|
|
111
|
+
if (!config.mcpServers) config.mcpServers = {};
|
|
112
|
+
config.mcpServers['lemma-mcp-server'] = {
|
|
113
|
+
command: 'node', args: [MCP_SERVER_PATH]
|
|
114
|
+
};
|
|
115
|
+
fs.writeFileSync(configFile, JSON.stringify(config, null, 2), 'utf8');
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function installProxyDaemon() {
|
|
120
|
+
const isMac = process.platform === 'darwin';
|
|
121
|
+
const isLinux = process.platform === 'linux';
|
|
122
|
+
|
|
123
|
+
if (isMac) {
|
|
124
|
+
const plistDir = path.join(os.homedir(), 'Library', 'LaunchAgents');
|
|
125
|
+
ensureDir(plistDir);
|
|
126
|
+
const plistPath = path.join(plistDir, 'com.lemma.cache-proxy.plist');
|
|
127
|
+
const plist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
128
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
129
|
+
<plist version="1.0">
|
|
130
|
+
<dict>
|
|
131
|
+
<key>Label</key><string>com.lemma.cache-proxy</string>
|
|
132
|
+
<key>ProgramArguments</key>
|
|
133
|
+
<array>
|
|
134
|
+
<string>${process.execPath}</string>
|
|
135
|
+
<string>${PROXY_PATH}</string>
|
|
136
|
+
</array>
|
|
137
|
+
<key>RunAtLoad</key><true/>
|
|
138
|
+
<key>KeepAlive</key><true/>
|
|
139
|
+
<key>WorkingDirectory</key><string>${os.homedir()}</string>
|
|
140
|
+
<key>StandardOutPath</key><string>${LEMMA_CACHE}/proxy-daemon.log</string>
|
|
141
|
+
<key>StandardErrorPath</key><string>${LEMMA_CACHE}/proxy-daemon.log</string>
|
|
142
|
+
<key>EnvironmentVariables</key>
|
|
143
|
+
<dict>
|
|
144
|
+
<key>LEMMA_PROXY_PORT</key><string>${PORT}</string>
|
|
145
|
+
<key>LEMMA_CACHE_DIR</key><string>${LEMMA_CACHE}</string>
|
|
146
|
+
</dict>
|
|
147
|
+
</dict>
|
|
148
|
+
</plist>`;
|
|
149
|
+
fs.writeFileSync(plistPath, plist, 'utf8');
|
|
150
|
+
try { execSync('launchctl load ' + plistPath, { stdio: 'ignore' }); } catch {}
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (isLinux) {
|
|
155
|
+
const svcDir = path.join(os.homedir(), '.config', 'systemd', 'user');
|
|
156
|
+
ensureDir(svcDir);
|
|
157
|
+
const svc = `[Unit]
|
|
158
|
+
Description=Lemma Cache Proxy
|
|
159
|
+
After=network.target
|
|
160
|
+
|
|
161
|
+
[Service]
|
|
162
|
+
ExecStart=${process.execPath} ${PROXY_PATH}
|
|
163
|
+
Restart=always
|
|
164
|
+
RestartSec=3
|
|
165
|
+
Environment=LEMMA_PROXY_PORT=${PORT}
|
|
166
|
+
Environment=LEMMA_CACHE_DIR=${LEMMA_CACHE}
|
|
167
|
+
|
|
168
|
+
[Install]
|
|
169
|
+
WantedBy=default.target
|
|
170
|
+
`;
|
|
171
|
+
const svcPath = path.join(svcDir, 'lemma-cache-proxy.service');
|
|
172
|
+
fs.writeFileSync(svcPath, svc, 'utf8');
|
|
173
|
+
try { execSync('systemctl --user daemon-reload && systemctl --user enable lemma-cache-proxy && systemctl --user start lemma-cache-proxy', { stdio: 'ignore' }); } catch {}
|
|
174
|
+
return true;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function applyEnvToCurrentProcess() {
|
|
181
|
+
const proxyUrl = `http://localhost:${PORT}`;
|
|
182
|
+
process.env.LEMMA_PROXY_PORT = PORT;
|
|
183
|
+
process.env.LEMMA_CACHE_DIR = LEMMA_CACHE;
|
|
184
|
+
process.env.OPENAI_BASE_URL = proxyUrl;
|
|
185
|
+
process.env.OPENAI_API_BASE = proxyUrl;
|
|
186
|
+
process.env.CODEX_BASE_URL = proxyUrl;
|
|
187
|
+
process.env.LITELLM_PROXY_BASE_URL = proxyUrl;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function createEnvFile() {
|
|
191
|
+
const proxyUrl = `http://localhost:${PORT}`;
|
|
192
|
+
const envContent = `# Lemma Cache Proxy — Auto-generated by \`lemma init\`
|
|
193
|
+
# Source this file in your shell to route LLM traffic through the cache:
|
|
194
|
+
# source ${ENV_FILE}
|
|
195
|
+
export LEMMA_PROXY_PORT=${PORT}
|
|
196
|
+
export LEMMA_CACHE_DIR=${LEMMA_CACHE}
|
|
197
|
+
export OPENAI_BASE_URL=${proxyUrl}
|
|
198
|
+
export OPENAI_API_BASE=${proxyUrl}
|
|
199
|
+
export CODEX_BASE_URL=${proxyUrl}
|
|
200
|
+
export LITELLM_PROXY_BASE_URL=${proxyUrl}
|
|
201
|
+
`;
|
|
202
|
+
fs.writeFileSync(ENV_FILE, envContent, 'utf8');
|
|
203
|
+
applyEnvToCurrentProcess();
|
|
204
|
+
return ENV_FILE;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function setupShellIntegration() {
|
|
208
|
+
const rcFiles = [
|
|
209
|
+
{ file: path.join(os.homedir(), '.zshrc'), name: 'zsh' },
|
|
210
|
+
{ file: path.join(os.homedir(), '.bashrc'), name: 'bash' },
|
|
211
|
+
{ file: path.join(os.homedir(), '.config', 'fish', 'config.fish'), name: 'fish' }
|
|
212
|
+
];
|
|
213
|
+
|
|
214
|
+
const sourceLine = `\n# Lemma Cache Proxy — auto-configured by \`lemma init\`\n[ -f "${ENV_FILE}" ] && . "${ENV_FILE}"\n`;
|
|
215
|
+
|
|
216
|
+
let configured = false;
|
|
217
|
+
for (const rc of rcFiles) {
|
|
218
|
+
if (fs.existsSync(rc.file)) {
|
|
219
|
+
const content = fs.readFileSync(rc.file, 'utf8');
|
|
220
|
+
if (content.includes('lemma-cache/env')) { configured = true; continue; }
|
|
221
|
+
fs.appendFileSync(rc.file, sourceLine, 'utf8');
|
|
222
|
+
configured = true;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const fallback = path.join(os.homedir(), '.zshrc');
|
|
227
|
+
if (!fs.existsSync(fallback)) {
|
|
228
|
+
ensureDir(path.dirname(fallback));
|
|
229
|
+
fs.writeFileSync(fallback, sourceLine, 'utf8');
|
|
230
|
+
configured = true;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
try {
|
|
234
|
+
if (process.platform === 'darwin') {
|
|
235
|
+
execSync(`launchctl setenv OPENAI_BASE_URL http://localhost:${PORT}`, { stdio: 'ignore' });
|
|
236
|
+
execSync(`launchctl setenv CODEX_BASE_URL http://localhost:${PORT}`, { stdio: 'ignore' });
|
|
237
|
+
}
|
|
238
|
+
} catch {}
|
|
239
|
+
|
|
240
|
+
return configured;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
async function main() {
|
|
244
|
+
process.stderr.write(`\n ${BOLD}${CYAN}Lemma Init — Magic Setup${RESET}\n\n`);
|
|
245
|
+
|
|
246
|
+
ensureDir(LEMMA_CACHE);
|
|
247
|
+
if (!fs.existsSync(BRAIN_FILE)) {
|
|
248
|
+
fs.writeFileSync(BRAIN_FILE, '[]', 'utf8');
|
|
249
|
+
}
|
|
250
|
+
ok(`Cache directory: ~/.lemma-cache`);
|
|
251
|
+
ok(`Brain file: ~/.lemma-cache/lemma_brain.json`);
|
|
252
|
+
|
|
253
|
+
configureOpenCodeMcp();
|
|
254
|
+
ok('OpenCode MCP configured');
|
|
255
|
+
|
|
256
|
+
configureClaudeDesktop();
|
|
257
|
+
ok('Claude MCP configured');
|
|
258
|
+
|
|
259
|
+
const hasContinue = detectTool('Continue.dev', () => configureContinueDotDev());
|
|
260
|
+
if (hasContinue) ok('Continue.dev configured');
|
|
261
|
+
else skip('Continue.dev not detected');
|
|
262
|
+
|
|
263
|
+
const hasCursor = detectTool('Cursor', () => fs.existsSync(path.join(os.homedir(), '.cursor')));
|
|
264
|
+
if (hasCursor) {
|
|
265
|
+
const configured = configureCursor();
|
|
266
|
+
if (configured) ok('Cursor configured');
|
|
267
|
+
else warn('Cursor detected but config could not be auto-updated');
|
|
268
|
+
} else skip('Cursor not detected');
|
|
269
|
+
|
|
270
|
+
const hasCodex = detectTool('Codex CLI', () => {
|
|
271
|
+
try { execSync('which codex', { stdio: 'ignore' }); return true; } catch { return false; }
|
|
272
|
+
});
|
|
273
|
+
if (hasCodex) ok('Codex CLI detected — set OPENAI_BASE_URL env (auto-configured below)');
|
|
274
|
+
else skip('Codex CLI not detected');
|
|
275
|
+
|
|
276
|
+
const hasWindsurf = detectTool('Windsurf', () => fs.existsSync(path.join(os.homedir(), '.windsurfrules')));
|
|
277
|
+
if (hasWindsurf) ok('Windsurf detected — env auto-configured');
|
|
278
|
+
else skip('Windsurf not detected');
|
|
279
|
+
|
|
280
|
+
const daemonOk = installProxyDaemon();
|
|
281
|
+
if (daemonOk) ok('Cache proxy daemon installed & started');
|
|
282
|
+
else warn('Could not install daemon — run `lemma proxy` manually');
|
|
283
|
+
|
|
284
|
+
const envFile = createEnvFile();
|
|
285
|
+
ok(`Env file created: ${envFile}`);
|
|
286
|
+
|
|
287
|
+
const shellOk = setupShellIntegration();
|
|
288
|
+
if (shellOk) ok('Shell rc files updated (source ~/.lemma-cache/env)');
|
|
289
|
+
else warn('Could not update shell rc — source manually: source ~/.lemma-cache/env');
|
|
290
|
+
|
|
291
|
+
process.stderr.write(`\n ${BOLD}${GREEN}✓ Lemma init complete${RESET}\n`);
|
|
292
|
+
process.stderr.write(` ${DIM}──────────────────────────────────────${RESET}\n`);
|
|
293
|
+
process.stderr.write(` ${GREEN}●${RESET} Cache proxy running on http://localhost:${PORT}\n`);
|
|
294
|
+
process.stderr.write(` ${GREEN}●${RESET} MCP server ready ${DIM}(lemma mcp)${RESET}\n`);
|
|
295
|
+
process.stderr.write(` ${GREEN}●${RESET} Dashboard active ${DIM}(lemma stats)${RESET}\n`);
|
|
296
|
+
process.stderr.write(` ${GREEN}●${RESET} New terminals: env vars auto-loaded\n`);
|
|
297
|
+
process.stderr.write(` ${GREEN}●${RESET} Todos los IDE/CLI nuevos ya apuntan al proxy\n`);
|
|
298
|
+
process.stderr.write(` ${DIM}──────────────────────────────────────${RESET}\n`);
|
|
299
|
+
process.stderr.write(` ${YELLOW}Para activar en ESTA terminal:${RESET}\n`);
|
|
300
|
+
process.stderr.write(` ${BOLD}source ${ENV_FILE}${RESET}\n`);
|
|
301
|
+
process.stderr.write(` ${DIM}Logs: ${LEMMA_CACHE}/proxy-daemon.log${RESET}\n\n`);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
main().catch(e => {
|
|
305
|
+
console.error('init failed:', e.message);
|
|
306
|
+
process.exit(1);
|
|
307
|
+
});
|