@nxuss/lemma 0.9.3 → 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.
Files changed (69) hide show
  1. package/README.md +18 -3
  2. package/bin/cli.js +97 -0
  3. package/bin/init.js +307 -0
  4. package/cache-proxy.js +191 -0
  5. package/dist/cjs/cli/lemma-proxy.d.ts.map +1 -1
  6. package/dist/cjs/cli/lemma-proxy.js +2 -4
  7. package/dist/cjs/cli/lemma-proxy.js.map +1 -1
  8. package/dist/cjs/mcp/index.js +1 -1
  9. package/dist/cjs/mcp/tools.d.ts.map +1 -1
  10. package/dist/cjs/mcp/tools.js +292 -71
  11. package/dist/cjs/mcp/tools.js.map +1 -1
  12. package/dist/cjs/subconscious/TheBrainV2.d.ts +102 -0
  13. package/dist/cjs/subconscious/TheBrainV2.d.ts.map +1 -0
  14. package/dist/cjs/subconscious/TheBrainV2.js +444 -0
  15. package/dist/cjs/subconscious/TheBrainV2.js.map +1 -0
  16. package/dist/cjs/utils/ContextCollapser.d.ts +21 -0
  17. package/dist/cjs/utils/ContextCollapser.d.ts.map +1 -0
  18. package/dist/cjs/utils/ContextCollapser.js +132 -0
  19. package/dist/cjs/utils/ContextCollapser.js.map +1 -0
  20. package/dist/cjs/utils/ExecutiveReport.d.ts +29 -0
  21. package/dist/cjs/utils/ExecutiveReport.d.ts.map +1 -0
  22. package/dist/cjs/utils/ExecutiveReport.js +111 -0
  23. package/dist/cjs/utils/ExecutiveReport.js.map +1 -0
  24. package/dist/cjs/utils/SurgicalASTInsert.d.ts +21 -0
  25. package/dist/cjs/utils/SurgicalASTInsert.d.ts.map +1 -0
  26. package/dist/cjs/utils/SurgicalASTInsert.js +173 -0
  27. package/dist/cjs/utils/SurgicalASTInsert.js.map +1 -0
  28. package/dist/cjs/utils/SymbolSurgicalContext.d.ts +25 -0
  29. package/dist/cjs/utils/SymbolSurgicalContext.d.ts.map +1 -0
  30. package/dist/cjs/utils/SymbolSurgicalContext.js +265 -0
  31. package/dist/cjs/utils/SymbolSurgicalContext.js.map +1 -0
  32. package/dist/cjs/utils/WormholeCompressor.d.ts +22 -0
  33. package/dist/cjs/utils/WormholeCompressor.d.ts.map +1 -0
  34. package/dist/cjs/utils/WormholeCompressor.js +152 -0
  35. package/dist/cjs/utils/WormholeCompressor.js.map +1 -0
  36. package/dist/esm/cli/lemma-proxy.d.ts.map +1 -1
  37. package/dist/esm/cli/lemma-proxy.js +2 -4
  38. package/dist/esm/cli/lemma-proxy.js.map +1 -1
  39. package/dist/esm/mcp/index.js +1 -1
  40. package/dist/esm/mcp/tools.d.ts.map +1 -1
  41. package/dist/esm/mcp/tools.js +292 -71
  42. package/dist/esm/mcp/tools.js.map +1 -1
  43. package/dist/esm/subconscious/TheBrainV2.d.ts +102 -0
  44. package/dist/esm/subconscious/TheBrainV2.d.ts.map +1 -0
  45. package/dist/esm/subconscious/TheBrainV2.js +431 -0
  46. package/dist/esm/subconscious/TheBrainV2.js.map +1 -0
  47. package/dist/esm/utils/ContextCollapser.d.ts +21 -0
  48. package/dist/esm/utils/ContextCollapser.d.ts.map +1 -0
  49. package/dist/esm/utils/ContextCollapser.js +128 -0
  50. package/dist/esm/utils/ContextCollapser.js.map +1 -0
  51. package/dist/esm/utils/ExecutiveReport.d.ts +29 -0
  52. package/dist/esm/utils/ExecutiveReport.d.ts.map +1 -0
  53. package/dist/esm/utils/ExecutiveReport.js +105 -0
  54. package/dist/esm/utils/ExecutiveReport.js.map +1 -0
  55. package/dist/esm/utils/SurgicalASTInsert.d.ts +21 -0
  56. package/dist/esm/utils/SurgicalASTInsert.d.ts.map +1 -0
  57. package/dist/esm/utils/SurgicalASTInsert.js +134 -0
  58. package/dist/esm/utils/SurgicalASTInsert.js.map +1 -0
  59. package/dist/esm/utils/SymbolSurgicalContext.d.ts +25 -0
  60. package/dist/esm/utils/SymbolSurgicalContext.d.ts.map +1 -0
  61. package/dist/esm/utils/SymbolSurgicalContext.js +226 -0
  62. package/dist/esm/utils/SymbolSurgicalContext.js.map +1 -0
  63. package/dist/esm/utils/WormholeCompressor.d.ts +22 -0
  64. package/dist/esm/utils/WormholeCompressor.d.ts.map +1 -0
  65. package/dist/esm/utils/WormholeCompressor.js +148 -0
  66. package/dist/esm/utils/WormholeCompressor.js.map +1 -0
  67. package/lemma-proxy.cjs +31 -14
  68. package/mcp-server.js +220 -0
  69. package/package.json +5 -1
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # 🧠 Lemma v0.9.3
1
+ # 🧠 Lemma v1.0.0
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/@nxuss/lemma.svg?color=orange&style=flat-square)](https://www.npmjs.com/package/@nxuss/lemma)
4
4
  [![License](https://img.shields.io/npm/l/@nxuss/lemma.svg?color=blue&style=flat-square)](https://github.com/Nxusbets/lemma/blob/main/LICENSE)
@@ -45,8 +45,8 @@ Lemma is a high-performance orchestration layer that sits between your developme
45
45
 
46
46
  ## ⚡ What's in the Pro MCP
47
47
 
48
- ### 🧠 Shared Semantic Memory — The Brain
49
- 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.
50
50
 
51
51
  ### 🛡️ Privacy Firewall (Semantic Scrubber)
52
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.
@@ -117,6 +117,21 @@ Exposes `write_workspace_file`, smart `apply_workspace_patch` (collision-safe se
117
117
  ### 🩺 AutoPilot & Auto-Heal
118
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.
119
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
+
120
135
  ---
121
136
 
122
137
  ## 🚀 Quick Start (Zero Config)
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
+ });
package/cache-proxy.js ADDED
@@ -0,0 +1,191 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const http = require('http');
5
+ const https = require('https');
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+ const os = require('os');
9
+
10
+ const PORT = parseInt(process.env.LEMMA_PROXY_PORT || '9090', 10);
11
+ const UPSTREAM = process.env.LEMMA_UPSTREAM || 'https://api.openai.com';
12
+ const API_KEY = process.env.LEMMA_API_KEY || process.env.OPENAI_API_KEY || '';
13
+ const BRAIN_FILE = path.join(os.homedir(), '.lemma-cache', 'lemma_brain.json');
14
+
15
+ function loadBrain() {
16
+ try {
17
+ if (!fs.existsSync(BRAIN_FILE)) {
18
+ fs.mkdirSync(path.dirname(BRAIN_FILE), { recursive: true });
19
+ fs.writeFileSync(BRAIN_FILE, '[]', 'utf8');
20
+ return [];
21
+ }
22
+ return JSON.parse(fs.readFileSync(BRAIN_FILE, 'utf8'));
23
+ } catch (e) {
24
+ console.error('[cache-proxy] Failed to load brain:', e.message);
25
+ return [];
26
+ }
27
+ }
28
+
29
+ function saveBrain(data) {
30
+ try {
31
+ fs.mkdirSync(path.dirname(BRAIN_FILE), { recursive: true });
32
+ fs.writeFileSync(BRAIN_FILE, JSON.stringify(data), 'utf8');
33
+ } catch (e) {
34
+ console.error('[cache-proxy] Failed to save brain:', e.message);
35
+ }
36
+ }
37
+
38
+ const STOP_WORDS = new Set(['the','a','an','is','are','was','were','be','been','being','have','has','had','do','does','did','will','would','could','should','may','might','shall','can','need','dare','ought','used','to','of','in','for','on','with','at','by','from','as','into','through','during','before','after','above','below','between','out','off','over','under','again','further','then','once','here','there','when','where','why','how','all','each','every','both','few','more','most','other','some','such','no','nor','not','only','own','same','so','than','too','very','just','because','but','and','or','if','while','although','since','until','unless','about','up','it','its','this','that','these','those','i','me','my','myself','we','our','ours','ourselves','you','your','yours','yourself','yourselves','he','him','his','himself','she','her','hers','herself','they','them','their','theirs','themselves','what','which','who','whom','this']);
39
+
40
+ function tokenize(text) {
41
+ return text.toLowerCase().replace(/[^a-z0-9\s]/g, '').split(/\s+/).filter(w => w.length > 1 && !STOP_WORDS.has(w));
42
+ }
43
+
44
+ function idf(corpusTokens) {
45
+ const df = {}, N = corpusTokens.length;
46
+ for (const tokens of corpusTokens) {
47
+ const seen = new Set(tokens);
48
+ for (const t of seen) df[t] = (df[t] || 0) + 1;
49
+ }
50
+ const scores = {};
51
+ for (const [t, count] of Object.entries(df)) scores[t] = Math.log(1 + (N - count + 0.5) / (count + 0.5));
52
+ return scores;
53
+ }
54
+
55
+ function bm25(queryTokens, docTokens, idfScores, avgDocLen) {
56
+ const k1 = 1.5, b = 0.75;
57
+ let score = 0;
58
+ const docLen = docTokens.length;
59
+ const tf = {};
60
+ for (const t of docTokens) tf[t] = (tf[t] || 0) + 1;
61
+ for (const q of queryTokens) {
62
+ const f = tf[q] || 0;
63
+ const idfVal = idfScores[q] || 0;
64
+ if (f === 0) continue;
65
+ score += idfVal * ((f * (k1 + 1)) / (f + k1 * (1 - b + b * (docLen / avgDocLen))));
66
+ }
67
+ return score;
68
+ }
69
+
70
+ function buildIndex(entries) {
71
+ if (!entries || entries.length === 0) return { corpusTokens: [], idfScores: {}, avgDocLen: 0 };
72
+ const corpusTokens = entries.map(e => tokenize(e.prompt || ''));
73
+ const avgDocLen = corpusTokens.reduce((s, t) => s + t.length, 0) / corpusTokens.length;
74
+ return { corpusTokens, idfScores: idf(corpusTokens), avgDocLen };
75
+ }
76
+
77
+ function search(entries, query, threshold) {
78
+ if (!entries || entries.length === 0) return [];
79
+ const qt = tokenize(query);
80
+ if (qt.length === 0) return [];
81
+ const idx = buildIndex(entries);
82
+ const maxScore = bm25(qt, qt, idx.idfScores, idx.avgDocLen);
83
+ return entries.map((e, i) => {
84
+ const raw = bm25(qt, idx.corpusTokens[i], idx.idfScores, idx.avgDocLen);
85
+ return { entry: e, similarity: maxScore > 0 ? raw / maxScore : 0, index: i };
86
+ }).filter(r => r.similarity >= threshold).sort((a, b) => b.similarity - a.similarity);
87
+ }
88
+
89
+ function extractPrompt(body) {
90
+ try {
91
+ if (body.messages) {
92
+ return body.messages.map(m => `${m.role}: ${m.content}`).join('\n');
93
+ }
94
+ if (body.prompt) return body.prompt;
95
+ if (body.input) return typeof body.input === 'string' ? body.input : JSON.stringify(body.input);
96
+ } catch {}
97
+ return JSON.stringify(body).substring(0, 500);
98
+ }
99
+
100
+ function extractResponse(data) {
101
+ try {
102
+ if (data.choices && data.choices[0]) {
103
+ return data.choices[0].message?.content || data.choices[0].text || JSON.stringify(data);
104
+ }
105
+ return JSON.stringify(data);
106
+ } catch { return JSON.stringify(data); }
107
+ }
108
+
109
+ function proxyRequest(body) {
110
+ return new Promise((resolve, reject) => {
111
+ const url = new URL(UPSTREAM + '/v1/chat/completions');
112
+ const data = JSON.stringify(body);
113
+ const opts = {
114
+ hostname: url.hostname, port: url.port || 443, path: url.pathname,
115
+ method: 'POST',
116
+ headers: {
117
+ 'Content-Type': 'application/json',
118
+ 'Authorization': `Bearer ${API_KEY}`,
119
+ 'Content-Length': Buffer.byteLength(data)
120
+ }
121
+ };
122
+ const mod = url.protocol === 'https:' ? https : http;
123
+ const req = mod.request(opts, (res) => {
124
+ let chunks = '';
125
+ res.on('data', c => chunks += c);
126
+ res.on('end', () => {
127
+ try { resolve(JSON.parse(chunks)); }
128
+ catch { resolve({ choices: [{ message: { content: chunks } }] }); }
129
+ });
130
+ });
131
+ req.on('error', reject);
132
+ req.write(data);
133
+ req.end();
134
+ });
135
+ }
136
+
137
+ const server = http.createServer((req, res) => {
138
+ res.setHeader('Access-Control-Allow-Origin', '*');
139
+ res.setHeader('Access-Control-Allow-Methods', 'POST, GET, OPTIONS');
140
+ res.setHeader('Access-Control-Allow-Headers', '*');
141
+
142
+ if (req.method === 'OPTIONS') { res.writeHead(200); res.end(); return; }
143
+
144
+ let body = '';
145
+ req.on('data', c => body += c);
146
+ req.on('end', async () => {
147
+ try {
148
+ const parsed = JSON.parse(body);
149
+ const prompt = extractPrompt(parsed);
150
+
151
+ if (req.url === '/v1/chat/completions' && prompt) {
152
+ const brain = loadBrain();
153
+ const results = search(brain, prompt, 0.85);
154
+
155
+ if (results.length > 0) {
156
+ const hit = results[0];
157
+ brain[hit.index].hits = (brain[hit.index].hits || 0) + 1;
158
+ saveBrain(brain);
159
+ const cached = JSON.parse(hit.entry.response);
160
+ console.error(`[cache-proxy] HIT (${(hit.similarity * 100).toFixed(1)}%) — returning cached (${hit.entry.hits} hits)`);
161
+ res.writeHead(200, { 'Content-Type': 'application/json' });
162
+ res.end(JSON.stringify(cached));
163
+ return;
164
+ }
165
+
166
+ console.error('[cache-proxy] MISS — forwarding to upstream');
167
+ const upstreamResp = await proxyRequest(parsed);
168
+ const responseText = extractResponse(upstreamResp);
169
+
170
+ brain.push({ prompt, response: responseText, hits: 0, createdAt: Date.now() });
171
+ saveBrain(brain);
172
+
173
+ res.writeHead(200, { 'Content-Type': 'application/json' });
174
+ res.end(JSON.stringify(upstreamResp));
175
+ } else {
176
+ res.writeHead(404); res.end('Not found');
177
+ }
178
+ } catch (e) {
179
+ console.error('[cache-proxy] Error:', e.message);
180
+ res.writeHead(500, { 'Content-Type': 'application/json' });
181
+ res.end(JSON.stringify({ error: e.message }));
182
+ }
183
+ });
184
+ });
185
+
186
+ server.listen(PORT, () => {
187
+ console.error(`[cache-proxy] Lemma Cache Proxy on :${PORT}`);
188
+ console.error(`[cache-proxy] Upstream: ${UPSTREAM}`);
189
+ console.error(`[cache-proxy] Caching ANY provider via OpenAI-compatible endpoint`);
190
+ console.error(`[cache-proxy] Point your tool to: http://localhost:${PORT}/v1/chat/completions`);
191
+ });
@@ -1 +1 @@
1
- {"version":3,"file":"lemma-proxy.d.ts","sourceRoot":"","sources":["../../../src/cli/lemma-proxy.ts"],"names":[],"mappings":";AA2cA,wBAAgB,+BAA+B,CAAC,OAAO,CAAC,EAAE;IAAE,SAAS,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,CA4DvF;AAugED,wBAAsB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CA6KhL"}
1
+ {"version":3,"file":"lemma-proxy.d.ts","sourceRoot":"","sources":["../../../src/cli/lemma-proxy.ts"],"names":[],"mappings":";AA2cA,wBAAgB,+BAA+B,CAAC,OAAO,CAAC,EAAE;IAAE,SAAS,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,CA4DvF;AAogED,wBAAsB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CA6KhL"}
@@ -2000,14 +2000,12 @@ Adjusted Answer:`;
2000
2000
  console.log(`🔒 Privacy Firewall : \x1b[32m[ACTIVE]\x1b[0m`);
2001
2001
  console.log(`🔀 Complexity Router : \x1b[32m[ACTIVE]\x1b[0m`);
2002
2002
  console.log(`💾 Exact Cache : \x1b[32m[ACTIVE]\x1b[0m`);
2003
- const ollamaOk = await checkDependency('http://localhost:11434/api/tags');
2004
- const chromaOk = await checkDependency('http://localhost:8000/');
2005
2003
  if (pro) {
2006
- console.log(`🎯 Semantic Cache : ${ollamaOk && chromaOk ? '\x1b[32m[ACTIVE]\x1b[0m' : '\x1b[33m[OFFLINE - Check Ollama/Chroma]\x1b[0m'}`);
2004
+ console.log(`🎯 Semantic Cache : \x1b[32m[ACTIVE - Local TheBrainV2 Okapi BM25 Engine]\x1b[0m`);
2007
2005
  console.log(`🌐 Hive Mind (Cloud) : \x1b[32m[ACTIVE]\x1b[0m`);
2008
2006
  }
2009
2007
  else {
2010
- console.log(`🎯 Semantic Cache : \x1b[90m[PRO ONLY]\x1b[0m -> https://lemma.nxus.studio/upgrade`);
2008
+ console.log(`🎯 Semantic Cache : \x1b[32m[ACTIVE - Local TheBrainV2 Okapi BM25 Engine]\x1b[0m`);
2011
2009
  console.log(`🌐 Hive Mind (Cloud) : \x1b[90m[PRO ONLY]\x1b[0m -> https://lemma.nxus.studio/upgrade`);
2012
2010
  }
2013
2011
  const hasStackDir = fs_1.default.existsSync(path_1.default.join(process.cwd(), '.lemma'));