@nxuss/lemma 0.5.1 → 0.5.3
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 +1 -1
- package/dashboard/dist/assets/{index-B4TA31pA.js → index-CIlkFwDs.js} +98 -98
- package/dashboard/dist/assets/index-CIlkFwDs.js.map +1 -0
- package/dashboard/dist/index.html +1 -1
- package/dist/cjs/cli/lemma-proxy.d.ts +2 -0
- package/dist/cjs/cli/lemma-proxy.d.ts.map +1 -1
- package/dist/cjs/cli/lemma-proxy.js +416 -121
- package/dist/cjs/cli/lemma-proxy.js.map +1 -1
- package/dist/cjs/config/index.d.ts.map +1 -1
- package/dist/cjs/config/index.js +2 -1
- package/dist/cjs/config/index.js.map +1 -1
- package/dist/cjs/errors/LemmaError.d.ts +52 -0
- package/dist/cjs/errors/LemmaError.d.ts.map +1 -0
- package/dist/cjs/errors/LemmaError.js +84 -0
- package/dist/cjs/errors/LemmaError.js.map +1 -0
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +24 -22
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/mcp/index.js +81 -0
- package/dist/cjs/mcp/index.js.map +1 -1
- package/dist/cjs/subconscious/SemanticCache.d.ts +4 -4
- package/dist/cjs/subconscious/SemanticCache.d.ts.map +1 -1
- package/dist/cjs/subconscious/SemanticCache.js +14 -10
- package/dist/cjs/subconscious/SemanticCache.js.map +1 -1
- package/dist/cjs/types/index.d.ts +8 -8
- package/dist/cjs/types/index.d.ts.map +1 -1
- package/dist/cjs/utils/ClipboardWatcher.d.ts.map +1 -1
- package/dist/cjs/utils/ClipboardWatcher.js +3 -0
- package/dist/cjs/utils/ClipboardWatcher.js.map +1 -1
- package/dist/cjs/utils/SavingsLedger.d.ts +52 -0
- package/dist/cjs/utils/SavingsLedger.d.ts.map +1 -0
- package/dist/cjs/utils/SavingsLedger.js +101 -0
- package/dist/cjs/utils/SavingsLedger.js.map +1 -0
- package/dist/cjs/utils/logger.d.ts +27 -6
- package/dist/cjs/utils/logger.d.ts.map +1 -1
- package/dist/cjs/utils/logger.js +96 -28
- package/dist/cjs/utils/logger.js.map +1 -1
- package/dist/cjs/utils/reportSavings.d.ts +24 -0
- package/dist/cjs/utils/reportSavings.d.ts.map +1 -0
- package/dist/cjs/utils/reportSavings.js +53 -0
- package/dist/cjs/utils/reportSavings.js.map +1 -0
- package/dist/esm/cli/lemma-proxy.d.ts +2 -0
- package/dist/esm/cli/lemma-proxy.d.ts.map +1 -1
- package/dist/esm/cli/lemma-proxy.js +414 -121
- package/dist/esm/cli/lemma-proxy.js.map +1 -1
- package/dist/esm/config/index.d.ts.map +1 -1
- package/dist/esm/config/index.js +2 -1
- package/dist/esm/config/index.js.map +1 -1
- package/dist/esm/errors/LemmaError.d.ts +52 -0
- package/dist/esm/errors/LemmaError.d.ts.map +1 -0
- package/dist/esm/errors/LemmaError.js +75 -0
- package/dist/esm/errors/LemmaError.js.map +1 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +24 -22
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/mcp/index.js +81 -0
- package/dist/esm/mcp/index.js.map +1 -1
- package/dist/esm/subconscious/SemanticCache.d.ts +4 -4
- package/dist/esm/subconscious/SemanticCache.d.ts.map +1 -1
- package/dist/esm/subconscious/SemanticCache.js +14 -10
- package/dist/esm/subconscious/SemanticCache.js.map +1 -1
- package/dist/esm/types/index.d.ts +8 -8
- package/dist/esm/types/index.d.ts.map +1 -1
- package/dist/esm/utils/ClipboardWatcher.d.ts.map +1 -1
- package/dist/esm/utils/ClipboardWatcher.js +3 -0
- package/dist/esm/utils/ClipboardWatcher.js.map +1 -1
- package/dist/esm/utils/SavingsLedger.d.ts +52 -0
- package/dist/esm/utils/SavingsLedger.d.ts.map +1 -0
- package/dist/esm/utils/SavingsLedger.js +98 -0
- package/dist/esm/utils/SavingsLedger.js.map +1 -0
- package/dist/esm/utils/logger.d.ts +27 -6
- package/dist/esm/utils/logger.d.ts.map +1 -1
- package/dist/esm/utils/logger.js +93 -28
- package/dist/esm/utils/logger.js.map +1 -1
- package/dist/esm/utils/reportSavings.d.ts +24 -0
- package/dist/esm/utils/reportSavings.d.ts.map +1 -0
- package/dist/esm/utils/reportSavings.js +47 -0
- package/dist/esm/utils/reportSavings.js.map +1 -0
- package/package.json +27 -24
- package/dashboard/dist/assets/index-B4TA31pA.js.map +0 -1
|
@@ -4,6 +4,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
5
|
};
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.autoConfigureAll = autoConfigureAll;
|
|
8
|
+
exports.startBackgroundClipboardWatcher = startBackgroundClipboardWatcher;
|
|
7
9
|
exports.performAutoHeal = performAutoHeal;
|
|
8
10
|
const commander_1 = require("commander");
|
|
9
11
|
const express_1 = __importDefault(require("express"));
|
|
@@ -30,7 +32,7 @@ function getVersion() {
|
|
|
30
32
|
catch { }
|
|
31
33
|
}
|
|
32
34
|
}
|
|
33
|
-
return '0.
|
|
35
|
+
return '0.5.3';
|
|
34
36
|
}
|
|
35
37
|
const VERSION = getVersion();
|
|
36
38
|
const ComplexityRouter_1 = __importDefault(require("../proxy/ComplexityRouter"));
|
|
@@ -38,6 +40,7 @@ const SemanticScrubber_1 = __importDefault(require("../security/SemanticScrubber
|
|
|
38
40
|
const CloudSyncClient_1 = __importDefault(require("../cloud/CloudSyncClient"));
|
|
39
41
|
const ContextSqueezer_1 = require("../utils/ContextSqueezer");
|
|
40
42
|
const AgentMultiplexer_1 = require("../proxy/AgentMultiplexer");
|
|
43
|
+
const SavingsLedger_1 = require("../utils/SavingsLedger");
|
|
41
44
|
const chroma = new chromadb_1.ChromaClient({ host: 'localhost', port: 8000 });
|
|
42
45
|
const dummyEmbeddingFunction = { generate: (texts) => Promise.resolve([]) };
|
|
43
46
|
const complexityRouter = new ComplexityRouter_1.default();
|
|
@@ -59,6 +62,13 @@ function logEvent(event) {
|
|
|
59
62
|
EVENT_LOG.push(ev);
|
|
60
63
|
if (EVENT_LOG.length > MAX_EVENTS)
|
|
61
64
|
EVENT_LOG.shift();
|
|
65
|
+
// Print a terminal savings summary every 10 events
|
|
66
|
+
if (EVENT_LOG.length > 0 && EVENT_LOG.length % 10 === 0) {
|
|
67
|
+
const snap = SavingsLedger_1.savingsLedger.getSnapshot();
|
|
68
|
+
if (snap.total.tokensSaved > 0) {
|
|
69
|
+
console.log('\n' + SavingsLedger_1.savingsLedger.getTerminalSummary() + '\n');
|
|
70
|
+
}
|
|
71
|
+
}
|
|
62
72
|
}
|
|
63
73
|
const FREE_LIMIT = 500;
|
|
64
74
|
const WARN_PCT = 0.8;
|
|
@@ -76,13 +86,20 @@ async function readJson(file, fallback) {
|
|
|
76
86
|
catch { }
|
|
77
87
|
return fallback;
|
|
78
88
|
}
|
|
89
|
+
const writeQueues = new Map();
|
|
79
90
|
async function writeJson(file, data) {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
91
|
+
const chain = writeQueues.get(file) || Promise.resolve();
|
|
92
|
+
const writeOperation = async () => {
|
|
93
|
+
try {
|
|
94
|
+
const tempFile = `${file}.${crypto_1.default.randomUUID ? crypto_1.default.randomUUID() : Math.random().toString(36).substring(2, 15)}.tmp`;
|
|
95
|
+
await fs_1.default.promises.writeFile(tempFile, JSON.stringify(data, null, 2));
|
|
96
|
+
await fs_1.default.promises.rename(tempFile, file);
|
|
97
|
+
}
|
|
98
|
+
catch { }
|
|
99
|
+
};
|
|
100
|
+
const nextChain = chain.then(writeOperation).catch(() => { });
|
|
101
|
+
writeQueues.set(file, nextChain);
|
|
102
|
+
return nextChain;
|
|
86
103
|
}
|
|
87
104
|
function projectHash(name) {
|
|
88
105
|
return crypto_1.default.createHash('sha1').update(name).digest('hex').slice(0, 12);
|
|
@@ -199,13 +216,198 @@ function ensureGitIgnore() {
|
|
|
199
216
|
catch { }
|
|
200
217
|
return added;
|
|
201
218
|
}
|
|
219
|
+
const CLIPBOARD_PID_FILE = path_1.default.join(CACHE_DIR, 'clipboard.pid');
|
|
220
|
+
function autoConfigureAll(projectName) {
|
|
221
|
+
const project = projectName || detectProject();
|
|
222
|
+
const HOME = process.env.HOME || process.env.USERPROFILE || '~';
|
|
223
|
+
// 1. Claude Desktop Config
|
|
224
|
+
try {
|
|
225
|
+
const isWin = process.platform === 'win32';
|
|
226
|
+
const claudePath = isWin
|
|
227
|
+
? path_1.default.join(process.env.APPDATA || '', 'Claude/claude_desktop_config.json')
|
|
228
|
+
: path_1.default.join(HOME, 'Library/Application Support/Claude/claude_desktop_config.json');
|
|
229
|
+
if (fs_1.default.existsSync(path_1.default.dirname(claudePath))) {
|
|
230
|
+
let config = { mcpServers: {} };
|
|
231
|
+
if (fs_1.default.existsSync(claudePath)) {
|
|
232
|
+
try {
|
|
233
|
+
config = JSON.parse(fs_1.default.readFileSync(claudePath, 'utf8'));
|
|
234
|
+
}
|
|
235
|
+
catch { }
|
|
236
|
+
}
|
|
237
|
+
if (!config.mcpServers)
|
|
238
|
+
config.mcpServers = {};
|
|
239
|
+
config.mcpServers.lemma = {
|
|
240
|
+
command: 'npx',
|
|
241
|
+
args: ['-y', '@nxuss/lemma', 'mcp'],
|
|
242
|
+
env: { LEMMA_PROJECT: project }
|
|
243
|
+
};
|
|
244
|
+
fs_1.default.writeFileSync(claudePath, JSON.stringify(config, null, 2));
|
|
245
|
+
console.log(`✅ [Claude Desktop] Configured automatically! (Restart Claude to activate)`);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
catch (e) {
|
|
249
|
+
console.log(`⚠️ Could not auto-configure Claude Desktop: ${e.message}`);
|
|
250
|
+
}
|
|
251
|
+
// 2. Shell Profiles Environment Override
|
|
252
|
+
try {
|
|
253
|
+
const profiles = [
|
|
254
|
+
path_1.default.join(HOME, '.zshrc'),
|
|
255
|
+
path_1.default.join(HOME, '.bashrc'),
|
|
256
|
+
path_1.default.join(HOME, '.bash_profile'),
|
|
257
|
+
path_1.default.join(HOME, '.profile')
|
|
258
|
+
];
|
|
259
|
+
const lines = [
|
|
260
|
+
'',
|
|
261
|
+
'# Lemma AI Gateway Overrides',
|
|
262
|
+
'export OPENAI_BASE_URL="http://localhost:8081/v1"',
|
|
263
|
+
'export ANTHROPIC_BASE_URL="http://localhost:8081"',
|
|
264
|
+
'export LEMMA_PROJECT="' + project + '"',
|
|
265
|
+
''
|
|
266
|
+
].join('\n');
|
|
267
|
+
for (const profile of profiles) {
|
|
268
|
+
if (fs_1.default.existsSync(profile)) {
|
|
269
|
+
try {
|
|
270
|
+
const content = fs_1.default.readFileSync(profile, 'utf8');
|
|
271
|
+
if (!content.includes('OPENAI_BASE_URL') && !content.includes('LEMMA_PROJECT')) {
|
|
272
|
+
fs_1.default.appendFileSync(profile, lines);
|
|
273
|
+
console.log(`✅ [Shell Profile] Configured ${path_1.default.basename(profile)} with local redirect variables!`);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
catch { }
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
catch (e) {
|
|
281
|
+
console.log(`⚠️ Could not auto-configure shell profiles: ${e.message}`);
|
|
282
|
+
}
|
|
283
|
+
// 3. Cursor & Windsurf settings.json updates
|
|
284
|
+
try {
|
|
285
|
+
const isWin = process.platform === 'win32';
|
|
286
|
+
const isMac = process.platform === 'darwin';
|
|
287
|
+
const candidates = [];
|
|
288
|
+
if (isMac) {
|
|
289
|
+
candidates.push({
|
|
290
|
+
name: 'Cursor',
|
|
291
|
+
path: path_1.default.join(HOME, 'Library/Application Support/Cursor/User/settings.json')
|
|
292
|
+
});
|
|
293
|
+
candidates.push({
|
|
294
|
+
name: 'Windsurf',
|
|
295
|
+
path: path_1.default.join(HOME, 'Library/Application Support/Windsurf/User/settings.json')
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
else if (isWin) {
|
|
299
|
+
const appData = process.env.APPDATA || '';
|
|
300
|
+
candidates.push({
|
|
301
|
+
name: 'Cursor',
|
|
302
|
+
path: path_1.default.join(appData, 'Cursor/User/settings.json')
|
|
303
|
+
});
|
|
304
|
+
candidates.push({
|
|
305
|
+
name: 'Windsurf',
|
|
306
|
+
path: path_1.default.join(appData, 'Windsurf/User/settings.json')
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
310
|
+
candidates.push({
|
|
311
|
+
name: 'Cursor',
|
|
312
|
+
path: path_1.default.join(HOME, '.config/Cursor/User/settings.json')
|
|
313
|
+
});
|
|
314
|
+
candidates.push({
|
|
315
|
+
name: 'Windsurf',
|
|
316
|
+
path: path_1.default.join(HOME, '.config/Windsurf/User/settings.json')
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
for (const cand of candidates) {
|
|
320
|
+
const dir = path_1.default.dirname(cand.path);
|
|
321
|
+
if (fs_1.default.existsSync(dir)) {
|
|
322
|
+
let settings = {};
|
|
323
|
+
if (fs_1.default.existsSync(cand.path)) {
|
|
324
|
+
try {
|
|
325
|
+
settings = JSON.parse(fs_1.default.readFileSync(cand.path, 'utf8'));
|
|
326
|
+
}
|
|
327
|
+
catch { }
|
|
328
|
+
}
|
|
329
|
+
let updated = false;
|
|
330
|
+
// Settings configuration to route custom openai/anthropic models through the proxy
|
|
331
|
+
const updates = {
|
|
332
|
+
"openai.baseURL": "http://localhost:8081/v1",
|
|
333
|
+
"openai.apiKey": "dummy-key-for-lemma",
|
|
334
|
+
"anthropic.baseURL": "http://localhost:8081",
|
|
335
|
+
"anthropic.apiKey": "dummy-key-for-lemma"
|
|
336
|
+
};
|
|
337
|
+
for (const [key, value] of Object.entries(updates)) {
|
|
338
|
+
if (settings[key] !== value) {
|
|
339
|
+
settings[key] = value;
|
|
340
|
+
updated = true;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
if (updated) {
|
|
344
|
+
fs_1.default.writeFileSync(cand.path, JSON.stringify(settings, null, 2));
|
|
345
|
+
console.log(`✅ [${cand.name}] Configured editor settings to route via local gateway!`);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
catch (e) {
|
|
351
|
+
console.log(`⚠️ Could not auto-configure editor settings: ${e.message}`);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
function startBackgroundClipboardWatcher() {
|
|
355
|
+
try {
|
|
356
|
+
if (fs_1.default.existsSync(CLIPBOARD_PID_FILE)) {
|
|
357
|
+
const oldPid = fs_1.default.readFileSync(CLIPBOARD_PID_FILE, 'utf8');
|
|
358
|
+
try {
|
|
359
|
+
process.kill(parseInt(oldPid, 10), 0); // Check if process is still alive
|
|
360
|
+
console.log(`✅ [Clipboard] Watcher is already running in background (PID: ${oldPid})`);
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
catch {
|
|
364
|
+
// Process is dead, clean up pid file
|
|
365
|
+
try {
|
|
366
|
+
fs_1.default.unlinkSync(CLIPBOARD_PID_FILE);
|
|
367
|
+
}
|
|
368
|
+
catch { }
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
console.log(`🔄 Starting Clipboard Watcher in background...`);
|
|
372
|
+
const { spawn } = require('child_process');
|
|
373
|
+
// Resolve path of lemma command or script
|
|
374
|
+
const scriptPath = fs_1.default.existsSync(path_1.default.join(__dirname, 'dist', 'cjs', 'cli', 'lemma-proxy.js'))
|
|
375
|
+
? path_1.default.join(__dirname, 'dist', 'cjs', 'cli', 'lemma-proxy.js')
|
|
376
|
+
: (fs_1.default.existsSync(path_1.default.join(__dirname, 'lemma-proxy.ts')) ? path_1.default.join(__dirname, 'lemma-proxy.ts') : __filename);
|
|
377
|
+
const isTs = scriptPath.endsWith('.ts');
|
|
378
|
+
const cmd = isTs ? 'npx' : 'node';
|
|
379
|
+
const args = isTs ? ['tsx', scriptPath, 'clipboard'] : [scriptPath, 'clipboard'];
|
|
380
|
+
const cp = spawn(cmd, args, {
|
|
381
|
+
stdio: 'ignore',
|
|
382
|
+
detached: true,
|
|
383
|
+
env: { ...process.env }
|
|
384
|
+
});
|
|
385
|
+
cp.unref();
|
|
386
|
+
if (cp.pid) {
|
|
387
|
+
fs_1.default.writeFileSync(CLIPBOARD_PID_FILE, String(cp.pid));
|
|
388
|
+
console.log(`✅ [Clipboard] Watcher has been started (PID: ${cp.pid})`);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
catch (err) {
|
|
392
|
+
console.error(`❌ Failed to automatically start Clipboard Watcher in background: ${err.message}`);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
202
395
|
// ── License helpers ────────────────────────────────────────────────────────────
|
|
396
|
+
let isProCached = null;
|
|
397
|
+
let lastLicenseCheck = 0;
|
|
398
|
+
const LICENSE_CACHE_TTL = 10000; // 10 seconds cache
|
|
203
399
|
async function loadLicense() {
|
|
204
400
|
return readJson(LICENSE_FILE, { isPro: false });
|
|
205
401
|
}
|
|
206
402
|
async function isPro() {
|
|
403
|
+
const now = Date.now();
|
|
404
|
+
if (isProCached !== null && (now - lastLicenseCheck) < LICENSE_CACHE_TTL) {
|
|
405
|
+
return isProCached;
|
|
406
|
+
}
|
|
207
407
|
const l = await loadLicense();
|
|
208
|
-
|
|
408
|
+
isProCached = !!(l && l.isPro);
|
|
409
|
+
lastLicenseCheck = now;
|
|
410
|
+
return isProCached;
|
|
209
411
|
}
|
|
210
412
|
async function validateKeyRemote(key) {
|
|
211
413
|
return new Promise((resolve, reject) => {
|
|
@@ -298,7 +500,15 @@ function cacheGet(provider, prompt) {
|
|
|
298
500
|
return e;
|
|
299
501
|
}
|
|
300
502
|
function cacheSet(provider, prompt, data) {
|
|
301
|
-
|
|
503
|
+
const MAX_CACHE_SIZE = 5000;
|
|
504
|
+
const k = cacheKey(provider, prompt);
|
|
505
|
+
if (!CACHE.has(k) && CACHE.size >= MAX_CACHE_SIZE) {
|
|
506
|
+
// Evict oldest cache entry (FIFO eviction)
|
|
507
|
+
const oldestKey = CACHE.keys().next().value;
|
|
508
|
+
if (oldestKey)
|
|
509
|
+
CACHE.delete(oldestKey);
|
|
510
|
+
}
|
|
511
|
+
CACHE.set(k, { input: prompt, data, createdAt: Date.now(), ttl: TTL_MS, provider });
|
|
302
512
|
}
|
|
303
513
|
// ── Semantic Search (Pro) ──────────────────────────────────────────────────────
|
|
304
514
|
async function getEmbedding(text) {
|
|
@@ -343,7 +553,7 @@ async function semanticGet(provider, prompt) {
|
|
|
343
553
|
distance = distances[0][i];
|
|
344
554
|
}
|
|
345
555
|
const similarity = Math.max(0, 1 - distance);
|
|
346
|
-
const THRESHOLD = 0.7
|
|
556
|
+
const THRESHOLD = 0.82; // raised from 0.7 to 0.82 to prevent false positives
|
|
347
557
|
if (similarity >= THRESHOLD) {
|
|
348
558
|
return {
|
|
349
559
|
data: JSON.parse(metadata.response),
|
|
@@ -570,6 +780,16 @@ async function relayMissStream(res, body, provider, tier) {
|
|
|
570
780
|
req.end();
|
|
571
781
|
});
|
|
572
782
|
}
|
|
783
|
+
class WriteQueue {
|
|
784
|
+
constructor() {
|
|
785
|
+
this.queue = Promise.resolve();
|
|
786
|
+
}
|
|
787
|
+
async enqueue(fn) {
|
|
788
|
+
this.queue = this.queue.then(fn).catch(() => { });
|
|
789
|
+
return this.queue;
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
const usageWriteQueue = new WriteQueue();
|
|
573
793
|
// ── Proxy Server class ─────────────────────────────────────────────────────────
|
|
574
794
|
class LemmaServer {
|
|
575
795
|
constructor(port, projectName, openBrowser = true) {
|
|
@@ -699,23 +919,76 @@ class LemmaServer {
|
|
|
699
919
|
res.status(500).json({ error: e.message });
|
|
700
920
|
}
|
|
701
921
|
});
|
|
702
|
-
this.app.post('/api/cache/clear', (req, res) => {
|
|
703
|
-
const
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
922
|
+
this.app.post('/api/cache/clear', async (req, res) => {
|
|
923
|
+
const { provider } = req.body || {};
|
|
924
|
+
let clearedCount = 0;
|
|
925
|
+
if (!provider) {
|
|
926
|
+
// Global clear
|
|
927
|
+
clearedCount = CACHE.size;
|
|
928
|
+
CACHE.clear();
|
|
929
|
+
EVENT_LOG.length = 0; // Clear live event log
|
|
930
|
+
SavingsLedger_1.savingsLedger.reset(); // Reset savings counters
|
|
931
|
+
// Reset and persist statistics for the current project
|
|
932
|
+
if (this.stats[this.projectName]) {
|
|
933
|
+
this.stats[this.projectName] = {
|
|
934
|
+
total: 0,
|
|
935
|
+
hits: 0,
|
|
936
|
+
misses: 0,
|
|
937
|
+
totalLatency: 0,
|
|
938
|
+
totalTokensSaved: 0,
|
|
939
|
+
providers: {}
|
|
940
|
+
};
|
|
941
|
+
writeJson(STATS_FILE, this.stats).catch(() => { });
|
|
942
|
+
}
|
|
943
|
+
// Clear Chroma DB collection
|
|
944
|
+
try {
|
|
945
|
+
await chroma.deleteCollection({ name: 'lemma-cache' }).catch(() => { });
|
|
946
|
+
}
|
|
947
|
+
catch (e) {
|
|
948
|
+
console.error(`[CacheClear] Failed to clear Chroma collection: ${e.message}`);
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
else {
|
|
952
|
+
// Granular clear
|
|
953
|
+
for (const [k, e] of CACHE.entries()) {
|
|
954
|
+
if (e.provider === provider) {
|
|
955
|
+
CACHE.delete(k);
|
|
956
|
+
clearedCount++;
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
// Selective delete from Chroma DB collection using provider metadata filter
|
|
960
|
+
try {
|
|
961
|
+
const collection = await chroma.getOrCreateCollection({
|
|
962
|
+
name: 'lemma-cache',
|
|
963
|
+
embeddingFunction: dummyEmbeddingFunction,
|
|
964
|
+
metadata: { "hnsw:space": "cosine" }
|
|
965
|
+
});
|
|
966
|
+
await collection.delete({ where: { provider } });
|
|
967
|
+
}
|
|
968
|
+
catch (e) {
|
|
969
|
+
console.error(`[CacheClear] Failed to selectively delete provider ${provider} from Chroma: ${e.message}`);
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
res.json({ success: true, clearedEntries: clearedCount });
|
|
973
|
+
});
|
|
974
|
+
// Savings breakdown by source for the dashboard
|
|
975
|
+
this.app.get('/api/savings-breakdown', (req, res) => {
|
|
976
|
+
res.json(SavingsLedger_1.savingsLedger.getSnapshot());
|
|
977
|
+
});
|
|
978
|
+
// Cross-process savings reporter — MCP and other processes POST here
|
|
979
|
+
this.app.post('/api/savings-record', (req, res) => {
|
|
980
|
+
const { source, charsBefore, charsAfter, tokens } = req.body || {};
|
|
981
|
+
const validSources = ['clipboard', 'contextSqueeze', 'historyPrune', 'complexityRouting', 'cache'];
|
|
982
|
+
if (!source || !validSources.includes(source)) {
|
|
983
|
+
return res.status(400).json({ error: 'Invalid source' });
|
|
984
|
+
}
|
|
985
|
+
if (typeof tokens === 'number' && tokens > 0) {
|
|
986
|
+
SavingsLedger_1.savingsLedger.recordTokens(source, tokens);
|
|
717
987
|
}
|
|
718
|
-
|
|
988
|
+
else if (typeof charsBefore === 'number' && typeof charsAfter === 'number') {
|
|
989
|
+
SavingsLedger_1.savingsLedger.record(source, charsBefore, charsAfter);
|
|
990
|
+
}
|
|
991
|
+
res.json({ ok: true, snapshot: SavingsLedger_1.savingsLedger.getSnapshot().total });
|
|
719
992
|
});
|
|
720
993
|
this.app.get('/dashboard/*', (req, res) => res.sendFile(path_1.default.join(this.dashboardPath, 'index.html')));
|
|
721
994
|
}
|
|
@@ -740,76 +1013,36 @@ class LemmaServer {
|
|
|
740
1013
|
logEvent({ type: 'privacy:mask', count: Object.keys(tokenMap).length, project: this.projectName });
|
|
741
1014
|
}
|
|
742
1015
|
const originalModel = req.body.model || 'gpt-4o';
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
if (req.body.model)
|
|
746
|
-
req.body.model = model;
|
|
747
|
-
// Apply the Agent Multiplexer to local-verify the prompt in parallel
|
|
748
|
-
const enrichedPrompt = await agentMultiplexer.multiplexPrompt(prompt, model);
|
|
749
|
-
// Resolve conversation session key and retrieve/create the fileHistory map
|
|
750
|
-
let fileHistory;
|
|
751
|
-
if (req.body.messages && req.body.messages.length > 0) {
|
|
752
|
-
try {
|
|
753
|
-
const firstMsg = req.body.messages[0];
|
|
754
|
-
const content = typeof firstMsg.content === 'string' ? firstMsg.content : JSON.stringify(firstMsg.content);
|
|
755
|
-
const sessionKey = crypto_1.default.createHash('sha1').update(content).digest('hex');
|
|
756
|
-
if (!this.sessions.has(sessionKey)) {
|
|
757
|
-
this.sessions.set(sessionKey, { fileHistory: new Map() });
|
|
758
|
-
}
|
|
759
|
-
fileHistory = this.sessions.get(sessionKey).fileHistory;
|
|
760
|
-
}
|
|
761
|
-
catch { }
|
|
762
|
-
}
|
|
763
|
-
// Compress long TS/JS codebase file blocks in the enriched prompt using the Context Squeezer AST Tree-Shaker!
|
|
764
|
-
// The query context is the user's primary prompt to ensure we keep relevant methods!
|
|
765
|
-
const squeezeResult = (0, ContextSqueezer_1.squeezePrompt)(enrichedPrompt, prompt, fileHistory);
|
|
766
|
-
const squeezedPrompt = squeezeResult.squeezed;
|
|
767
|
-
const compressionSavings = squeezeResult.originalSize - squeezeResult.squeezedSize;
|
|
768
|
-
if (compressionSavings > 0) {
|
|
769
|
-
console.log(`⚡ [Squeezer] Compressed prompt from ${squeezeResult.originalSize} to ${squeezeResult.squeezedSize} chars (Saved ${(squeezeResult.compressionRatio * 100).toFixed(1)}% context tokens!)`);
|
|
770
|
-
}
|
|
771
|
-
if (provider === 'openai' || provider === 'anthropic') {
|
|
772
|
-
if (req.body.messages && req.body.messages.length > 0) {
|
|
773
|
-
injectSqueezedContent(req.body.messages[req.body.messages.length - 1], squeezedPrompt);
|
|
774
|
-
// Apply conversational history pruning to save tokens on historical messages!
|
|
775
|
-
const pruneResult = (0, ContextSqueezer_1.pruneHistoryMessages)(req.body.messages);
|
|
776
|
-
req.body.messages = pruneResult.messages;
|
|
777
|
-
historyTokensSaved = pruneResult.tokensSavedEstimate + Math.floor(compressionSavings / 4);
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
else if (provider === 'gemini') {
|
|
781
|
-
if (req.body.contents && req.body.contents.length > 0) {
|
|
782
|
-
const lastContent = req.body.contents[req.body.contents.length - 1];
|
|
783
|
-
if (lastContent.parts && lastContent.parts.length > 0) {
|
|
784
|
-
lastContent.parts[lastContent.parts.length - 1].text = squeezedPrompt;
|
|
785
|
-
}
|
|
786
|
-
}
|
|
787
|
-
}
|
|
1016
|
+
// ── 1. Cache-First Hot Path ──
|
|
1017
|
+
// Perform exact cache check immediately (resolves in <1ms)
|
|
788
1018
|
const hit = cacheGet(provider, prompt);
|
|
789
1019
|
if (hit) {
|
|
790
|
-
|
|
1020
|
+
const cacheTokensSaved = 2000;
|
|
1021
|
+
SavingsLedger_1.savingsLedger.recordTokens('cache', cacheTokensSaved);
|
|
1022
|
+
await recordStat(this.stats, this.projectName, true, Date.now() - t0, provider, cacheTokensSaved);
|
|
791
1023
|
const unmaskedData = semanticScrubber.unmask(hit.data, tokenMap);
|
|
792
1024
|
if (isStream)
|
|
793
|
-
return simulateHitStream(res, unmaskedData, provider,
|
|
1025
|
+
return simulateHitStream(res, unmaskedData, provider, originalModel, tier, 1.0);
|
|
794
1026
|
res.setHeader('X-Lemma-Cache', 'HIT');
|
|
795
1027
|
res.setHeader('X-Lemma-Similarity', '1.000');
|
|
796
1028
|
res.setHeader('X-Lemma-Tier', tier);
|
|
797
1029
|
return res.json(unmaskedData);
|
|
798
1030
|
}
|
|
1031
|
+
// Perform semantic cache check (resolves in ~15ms)
|
|
799
1032
|
if (pro) {
|
|
800
1033
|
const semHit = await semanticGet(provider, prompt);
|
|
801
1034
|
if (semHit) {
|
|
802
1035
|
if (semHit.similarity >= 0.90) {
|
|
803
|
-
await recordStat(this.stats, this.projectName, true, Date.now() - t0, provider, 2000
|
|
1036
|
+
await recordStat(this.stats, this.projectName, true, Date.now() - t0, provider, 2000);
|
|
804
1037
|
const unmaskedData = semanticScrubber.unmask(semHit.data, tokenMap);
|
|
805
1038
|
if (isStream)
|
|
806
|
-
return simulateHitStream(res, unmaskedData, provider,
|
|
1039
|
+
return simulateHitStream(res, unmaskedData, provider, originalModel, tier, semHit.similarity);
|
|
807
1040
|
res.setHeader('X-Lemma-Cache', 'HIT');
|
|
808
1041
|
res.setHeader('X-Lemma-Similarity', semHit.similarity.toFixed(3));
|
|
809
1042
|
res.setHeader('X-Lemma-Tier', tier);
|
|
810
1043
|
return res.json(unmaskedData);
|
|
811
1044
|
}
|
|
812
|
-
else if (semHit.similarity >= 0.
|
|
1045
|
+
else if (semHit.similarity >= 0.82) { // raised from 0.70 to 0.82 to prevent false positives
|
|
813
1046
|
console.log(`💡 [CARS] Near Cache Hit (Similarity: ${(semHit.similarity * 100).toFixed(1)}%). Synthesizing answer locally via Ollama...`);
|
|
814
1047
|
let cachedContent = '';
|
|
815
1048
|
if (provider === 'openai') {
|
|
@@ -847,10 +1080,10 @@ Adjusted Answer:`;
|
|
|
847
1080
|
else if (provider === 'anthropic') {
|
|
848
1081
|
synthesizedData.content[0].text = adjustedText;
|
|
849
1082
|
}
|
|
850
|
-
await recordStat(this.stats, this.projectName, true, Date.now() - t0, provider, 2000
|
|
1083
|
+
await recordStat(this.stats, this.projectName, true, Date.now() - t0, provider, 2000);
|
|
851
1084
|
const unmaskedData = semanticScrubber.unmask(synthesizedData, tokenMap);
|
|
852
1085
|
if (isStream) {
|
|
853
|
-
return simulateHitStream(res, unmaskedData, provider,
|
|
1086
|
+
return simulateHitStream(res, unmaskedData, provider, originalModel, tier, semHit.similarity);
|
|
854
1087
|
}
|
|
855
1088
|
res.setHeader('X-Lemma-Cache', 'HIT-CARS');
|
|
856
1089
|
res.setHeader('X-Lemma-Similarity', semHit.similarity.toFixed(3));
|
|
@@ -867,22 +1100,86 @@ Adjusted Answer:`;
|
|
|
867
1100
|
const cloudHit = await cloudSync.get(prompt);
|
|
868
1101
|
if (cloudHit) {
|
|
869
1102
|
cacheSet(provider, prompt, cloudHit.data);
|
|
870
|
-
await recordStat(this.stats, this.projectName, true, Date.now() - t0, provider, 2000
|
|
1103
|
+
await recordStat(this.stats, this.projectName, true, Date.now() - t0, provider, 2000);
|
|
871
1104
|
const unmaskedData = semanticScrubber.unmask(cloudHit.data, tokenMap);
|
|
872
1105
|
if (isStream)
|
|
873
|
-
return simulateHitStream(res, unmaskedData, provider,
|
|
1106
|
+
return simulateHitStream(res, unmaskedData, provider, originalModel, tier, cloudHit.similarity || 0.95);
|
|
874
1107
|
res.setHeader('X-Lemma-Cache', 'HIT');
|
|
875
1108
|
res.setHeader('X-Lemma-Similarity', (cloudHit.similarity || 0.95).toFixed(3));
|
|
876
1109
|
res.setHeader('X-Lemma-Tier', tier);
|
|
877
1110
|
return res.json(unmaskedData);
|
|
878
1111
|
}
|
|
879
1112
|
}
|
|
1113
|
+
// ── 2. Cache Miss Path ──
|
|
1114
|
+
const routingDecision = complexityRouter.evaluate(prompt, originalModel);
|
|
1115
|
+
const model = routingDecision.model;
|
|
1116
|
+
if (req.body.model)
|
|
1117
|
+
req.body.model = model;
|
|
1118
|
+
// Record complexity routing savings when model is downgraded
|
|
1119
|
+
if (routingDecision.complexity === 'low' && model !== originalModel) {
|
|
1120
|
+
const estimatedPromptTokens = Math.floor(prompt.length / 4);
|
|
1121
|
+
const routingSavings = Math.floor(estimatedPromptTokens * 0.8);
|
|
1122
|
+
SavingsLedger_1.savingsLedger.recordTokens('complexityRouting', routingSavings);
|
|
1123
|
+
}
|
|
1124
|
+
// Apply the Agent Multiplexer to local-verify the prompt in parallel
|
|
1125
|
+
const enrichedPrompt = await agentMultiplexer.multiplexPrompt(prompt, model);
|
|
1126
|
+
// Resolve conversation session key and retrieve/create the fileHistory map
|
|
1127
|
+
let fileHistory;
|
|
1128
|
+
if (req.body.messages && req.body.messages.length > 0) {
|
|
1129
|
+
try {
|
|
1130
|
+
const firstMsg = req.body.messages[0];
|
|
1131
|
+
const content = typeof firstMsg.content === 'string' ? firstMsg.content : JSON.stringify(firstMsg.content);
|
|
1132
|
+
const sessionKey = crypto_1.default.createHash('sha1').update(content).digest('hex');
|
|
1133
|
+
if (!this.sessions.has(sessionKey)) {
|
|
1134
|
+
// Enforce 500 active sessions cap to prevent memory leak (FIFO eviction)
|
|
1135
|
+
const MAX_SESSIONS = 500;
|
|
1136
|
+
if (this.sessions.size >= MAX_SESSIONS) {
|
|
1137
|
+
const oldestKey = this.sessions.keys().next().value;
|
|
1138
|
+
if (oldestKey)
|
|
1139
|
+
this.sessions.delete(oldestKey);
|
|
1140
|
+
}
|
|
1141
|
+
this.sessions.set(sessionKey, { fileHistory: new Map() });
|
|
1142
|
+
}
|
|
1143
|
+
fileHistory = this.sessions.get(sessionKey).fileHistory;
|
|
1144
|
+
}
|
|
1145
|
+
catch { }
|
|
1146
|
+
}
|
|
1147
|
+
// Compress long TS/JS codebase file blocks in the enriched prompt
|
|
1148
|
+
const squeezeResult = (0, ContextSqueezer_1.squeezePrompt)(enrichedPrompt, prompt, fileHistory);
|
|
1149
|
+
const squeezedPrompt = squeezeResult.squeezed;
|
|
1150
|
+
const compressionSavings = squeezeResult.originalSize - squeezeResult.squeezedSize;
|
|
1151
|
+
if (compressionSavings > 0) {
|
|
1152
|
+
SavingsLedger_1.savingsLedger.record('contextSqueeze', squeezeResult.originalSize, squeezeResult.squeezedSize);
|
|
1153
|
+
console.log(`⚡ [Squeezer] Compressed prompt from ${squeezeResult.originalSize} to ${squeezeResult.squeezedSize} chars (Saved ${(squeezeResult.compressionRatio * 100).toFixed(1)}% context tokens!)`);
|
|
1154
|
+
}
|
|
1155
|
+
if (provider === 'openai' || provider === 'anthropic') {
|
|
1156
|
+
if (req.body.messages && req.body.messages.length > 0) {
|
|
1157
|
+
injectSqueezedContent(req.body.messages[req.body.messages.length - 1], squeezedPrompt);
|
|
1158
|
+
// Apply conversational history pruning
|
|
1159
|
+
const pruneResult = (0, ContextSqueezer_1.pruneHistoryMessages)(req.body.messages);
|
|
1160
|
+
req.body.messages = pruneResult.messages;
|
|
1161
|
+
historyTokensSaved = pruneResult.tokensSavedEstimate + Math.floor(compressionSavings / 4);
|
|
1162
|
+
if (pruneResult.tokensSavedEstimate > 0) {
|
|
1163
|
+
SavingsLedger_1.savingsLedger.recordTokens('historyPrune', pruneResult.tokensSavedEstimate);
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
else if (provider === 'gemini') {
|
|
1168
|
+
if (req.body.contents && req.body.contents.length > 0) {
|
|
1169
|
+
const lastContent = req.body.contents[req.body.contents.length - 1];
|
|
1170
|
+
if (lastContent.parts && lastContent.parts.length > 0) {
|
|
1171
|
+
lastContent.parts[lastContent.parts.length - 1].text = squeezedPrompt;
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
880
1175
|
if (isStream) {
|
|
881
1176
|
await recordStat(this.stats, this.projectName, false, Date.now() - t0, provider, historyTokensSaved);
|
|
882
1177
|
if (!pro) {
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
1178
|
+
await usageWriteQueue.enqueue(async () => {
|
|
1179
|
+
const u = await loadUsage();
|
|
1180
|
+
u.count++;
|
|
1181
|
+
await writeJson(USAGE_FILE, u);
|
|
1182
|
+
});
|
|
886
1183
|
}
|
|
887
1184
|
return relayMissStream(res, req.body, provider, tier);
|
|
888
1185
|
}
|
|
@@ -894,9 +1191,11 @@ Adjusted Answer:`;
|
|
|
894
1191
|
await cloudSync.set(prompt, data);
|
|
895
1192
|
}
|
|
896
1193
|
if (!pro) {
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
1194
|
+
await usageWriteQueue.enqueue(async () => {
|
|
1195
|
+
const u = await loadUsage();
|
|
1196
|
+
u.count++;
|
|
1197
|
+
await writeJson(USAGE_FILE, u);
|
|
1198
|
+
});
|
|
900
1199
|
}
|
|
901
1200
|
await recordStat(this.stats, this.projectName, false, Date.now() - t0, provider, historyTokensSaved);
|
|
902
1201
|
return res.json(semanticScrubber.unmask(data, tokenMap));
|
|
@@ -933,6 +1232,15 @@ Adjusted Answer:`;
|
|
|
933
1232
|
}
|
|
934
1233
|
async start() {
|
|
935
1234
|
this.stats = await loadStats();
|
|
1235
|
+
// Start active cache eviction sweep every hour
|
|
1236
|
+
setInterval(() => {
|
|
1237
|
+
const now = Date.now();
|
|
1238
|
+
for (const [k, e] of CACHE.entries()) {
|
|
1239
|
+
if (e.ttl > 0 && now - e.createdAt > e.ttl) {
|
|
1240
|
+
CACHE.delete(k);
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
}, 3600000).unref();
|
|
936
1244
|
const server = http_1.default.createServer(this.app);
|
|
937
1245
|
server.on('error', (e) => {
|
|
938
1246
|
if (e.code === 'EADDRINUSE') {
|
|
@@ -1052,10 +1360,17 @@ commander_1.program.command('start')
|
|
|
1052
1360
|
if (resp.data && resp.data.status === 'ok') {
|
|
1053
1361
|
console.log(`\n⚠️ Lemma Proxy is already running on port ${port} (Project: ${resp.data.project})`);
|
|
1054
1362
|
console.log(`💡 Use 'lemma status' for details or 'lemma stop' to restart.\n`);
|
|
1363
|
+
// Keep configurations up to date even if server was already started
|
|
1364
|
+
const runningProject = resp.data.project || detectProject();
|
|
1365
|
+
autoConfigureAll(runningProject);
|
|
1366
|
+
startBackgroundClipboardWatcher();
|
|
1055
1367
|
process.exit(0);
|
|
1056
1368
|
}
|
|
1057
1369
|
}
|
|
1058
1370
|
catch { }
|
|
1371
|
+
const startProject = opts.project || detectProject();
|
|
1372
|
+
autoConfigureAll(startProject);
|
|
1373
|
+
startBackgroundClipboardWatcher();
|
|
1059
1374
|
// Automatically enable autopilot if stack is enabled and user is Pro
|
|
1060
1375
|
const runAutopilot = opts.autopilot || opts.stack;
|
|
1061
1376
|
if (runAutopilot) {
|
|
@@ -1154,6 +1469,19 @@ commander_1.program.command('stop')
|
|
|
1154
1469
|
}
|
|
1155
1470
|
if (fs_1.default.existsSync(PID_FILE))
|
|
1156
1471
|
fs_1.default.unlinkSync(PID_FILE);
|
|
1472
|
+
// Stop background Clipboard Watcher if active
|
|
1473
|
+
try {
|
|
1474
|
+
if (fs_1.default.existsSync(CLIPBOARD_PID_FILE)) {
|
|
1475
|
+
const cbPid = fs_1.default.readFileSync(CLIPBOARD_PID_FILE, 'utf8');
|
|
1476
|
+
try {
|
|
1477
|
+
process.kill(parseInt(cbPid, 10), 'SIGTERM');
|
|
1478
|
+
console.log(`✅ Stopped background Clipboard Watcher (PID: ${cbPid})`);
|
|
1479
|
+
}
|
|
1480
|
+
catch { }
|
|
1481
|
+
fs_1.default.unlinkSync(CLIPBOARD_PID_FILE);
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
catch { }
|
|
1157
1485
|
});
|
|
1158
1486
|
commander_1.program.command('status')
|
|
1159
1487
|
.description('Check proxy status')
|
|
@@ -1247,41 +1575,8 @@ commander_1.program.command('init')
|
|
|
1247
1575
|
fs_1.default.writeFileSync(envFile, lemmaConfig);
|
|
1248
1576
|
console.log('✅ Created .env with Lemma configuration');
|
|
1249
1577
|
}
|
|
1250
|
-
console.log('\n🧠 \x1b[35mAuto-Setup MCP for AI IDEs
|
|
1251
|
-
|
|
1252
|
-
const { exec } = require('child_process');
|
|
1253
|
-
const isWin = process.platform === 'win32';
|
|
1254
|
-
const claudePath = isWin
|
|
1255
|
-
? path_1.default.join(process.env.APPDATA || '', 'Claude/claude_desktop_config.json')
|
|
1256
|
-
: path_1.default.join(process.env.HOME || '~', 'Library/Application Support/Claude/claude_desktop_config.json');
|
|
1257
|
-
if (fs_1.default.existsSync(path_1.default.dirname(claudePath))) {
|
|
1258
|
-
try {
|
|
1259
|
-
let config = { mcpServers: {} };
|
|
1260
|
-
if (fs_1.default.existsSync(claudePath)) {
|
|
1261
|
-
config = JSON.parse(fs_1.default.readFileSync(claudePath, 'utf8'));
|
|
1262
|
-
}
|
|
1263
|
-
if (!config.mcpServers)
|
|
1264
|
-
config.mcpServers = {};
|
|
1265
|
-
config.mcpServers.lemma = {
|
|
1266
|
-
command: 'npx',
|
|
1267
|
-
args: ['-y', '@nxuss/lemma', 'mcp'],
|
|
1268
|
-
env: { LEMMA_PROJECT: project }
|
|
1269
|
-
};
|
|
1270
|
-
fs_1.default.writeFileSync(claudePath, JSON.stringify(config, null, 2));
|
|
1271
|
-
console.log(`✅ \x1b[32mClaude Desktop configured automatically!\x1b[0m (Restart Claude to activate)`);
|
|
1272
|
-
}
|
|
1273
|
-
catch (e) {
|
|
1274
|
-
console.log('⚠️ Could not auto-configure Claude Desktop. No worries, you can do it manually.');
|
|
1275
|
-
}
|
|
1276
|
-
}
|
|
1277
|
-
console.log('🚀 Opening IDE configuration pages...');
|
|
1278
|
-
const urls = [
|
|
1279
|
-
'https://docs.anthropic.com/en/docs/agents-and-tools/mcp#using-mcp-in-claude-desktop',
|
|
1280
|
-
'https://cursor.com',
|
|
1281
|
-
'https://codeium.com/windsurf'
|
|
1282
|
-
];
|
|
1283
|
-
const opener = process.platform === 'darwin' ? 'open' : (process.platform === 'win32' ? 'start' : 'xdg-open');
|
|
1284
|
-
urls.forEach(url => exec(`${opener} ${url}`));
|
|
1578
|
+
console.log('\n🧠 \x1b[35mAuto-Setup MCP and redirection overrides for AI IDEs...\x1b[0m');
|
|
1579
|
+
autoConfigureAll(project);
|
|
1285
1580
|
console.log('\n✨ Project initialized for the Agentic Era!');
|
|
1286
1581
|
console.log('🚀 Run "lemma start" to begin.\n');
|
|
1287
1582
|
});
|