@nxuss/lemma 0.7.1 โ†’ 0.7.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.
Files changed (80) hide show
  1. package/README.md +6 -5
  2. package/dashboard/dist/assets/{index-BJzMIGrT.js โ†’ index-BNYoIN8-.js} +114 -114
  3. package/dashboard/dist/assets/index-BNYoIN8-.js.map +1 -0
  4. package/dashboard/dist/assets/index-DtEKr0hI.css +1 -0
  5. package/dashboard/dist/index.html +2 -2
  6. package/dist/cjs/api/dashboardRoutes.d.ts.map +1 -1
  7. package/dist/cjs/api/dashboardRoutes.js +41 -1
  8. package/dist/cjs/api/dashboardRoutes.js.map +1 -1
  9. package/dist/cjs/cli/lemma-proxy.d.ts +0 -6
  10. package/dist/cjs/cli/lemma-proxy.d.ts.map +1 -1
  11. package/dist/cjs/cli/lemma-proxy.js +233 -359
  12. package/dist/cjs/cli/lemma-proxy.js.map +1 -1
  13. package/dist/cjs/mcp/index.js +165 -1192
  14. package/dist/cjs/mcp/index.js.map +1 -1
  15. package/dist/cjs/mcp/prompts.d.ts +4 -0
  16. package/dist/cjs/mcp/prompts.d.ts.map +1 -0
  17. package/dist/cjs/mcp/prompts.js +84 -0
  18. package/dist/cjs/mcp/prompts.js.map +1 -0
  19. package/dist/cjs/mcp/resources.d.ts +3 -0
  20. package/dist/cjs/mcp/resources.d.ts.map +1 -0
  21. package/dist/cjs/mcp/resources.js +254 -0
  22. package/dist/cjs/mcp/resources.js.map +1 -0
  23. package/dist/cjs/mcp/tools.d.ts +11 -0
  24. package/dist/cjs/mcp/tools.d.ts.map +1 -0
  25. package/dist/cjs/mcp/tools.js +1192 -0
  26. package/dist/cjs/mcp/tools.js.map +1 -0
  27. package/dist/cjs/mcp/utils.d.ts +11 -0
  28. package/dist/cjs/mcp/utils.d.ts.map +1 -0
  29. package/dist/cjs/mcp/utils.js +85 -0
  30. package/dist/cjs/mcp/utils.js.map +1 -0
  31. package/dist/cjs/proxy/ContextInjector.d.ts +14 -0
  32. package/dist/cjs/proxy/ContextInjector.d.ts.map +1 -0
  33. package/dist/cjs/proxy/ContextInjector.js +148 -0
  34. package/dist/cjs/proxy/ContextInjector.js.map +1 -0
  35. package/dist/cjs/proxy/ProjectRegistry.d.ts +17 -0
  36. package/dist/cjs/proxy/ProjectRegistry.d.ts.map +1 -0
  37. package/dist/cjs/proxy/ProjectRegistry.js +60 -0
  38. package/dist/cjs/proxy/ProjectRegistry.js.map +1 -0
  39. package/dist/cjs/utils/reportSavings.d.ts +6 -0
  40. package/dist/cjs/utils/reportSavings.d.ts.map +1 -1
  41. package/dist/cjs/utils/reportSavings.js.map +1 -1
  42. package/dist/esm/api/dashboardRoutes.d.ts.map +1 -1
  43. package/dist/esm/api/dashboardRoutes.js +41 -1
  44. package/dist/esm/api/dashboardRoutes.js.map +1 -1
  45. package/dist/esm/cli/lemma-proxy.d.ts +0 -6
  46. package/dist/esm/cli/lemma-proxy.d.ts.map +1 -1
  47. package/dist/esm/cli/lemma-proxy.js +233 -358
  48. package/dist/esm/cli/lemma-proxy.js.map +1 -1
  49. package/dist/esm/mcp/index.js +164 -1191
  50. package/dist/esm/mcp/index.js.map +1 -1
  51. package/dist/esm/mcp/prompts.d.ts +4 -0
  52. package/dist/esm/mcp/prompts.d.ts.map +1 -0
  53. package/dist/esm/mcp/prompts.js +80 -0
  54. package/dist/esm/mcp/prompts.js.map +1 -0
  55. package/dist/esm/mcp/resources.d.ts +3 -0
  56. package/dist/esm/mcp/resources.d.ts.map +1 -0
  57. package/dist/esm/mcp/resources.js +248 -0
  58. package/dist/esm/mcp/resources.js.map +1 -0
  59. package/dist/esm/mcp/tools.d.ts +11 -0
  60. package/dist/esm/mcp/tools.d.ts.map +1 -0
  61. package/dist/esm/mcp/tools.js +1153 -0
  62. package/dist/esm/mcp/tools.js.map +1 -0
  63. package/dist/esm/mcp/utils.d.ts +11 -0
  64. package/dist/esm/mcp/utils.d.ts.map +1 -0
  65. package/dist/esm/mcp/utils.js +73 -0
  66. package/dist/esm/mcp/utils.js.map +1 -0
  67. package/dist/esm/proxy/ContextInjector.d.ts +14 -0
  68. package/dist/esm/proxy/ContextInjector.d.ts.map +1 -0
  69. package/dist/esm/proxy/ContextInjector.js +108 -0
  70. package/dist/esm/proxy/ContextInjector.js.map +1 -0
  71. package/dist/esm/proxy/ProjectRegistry.d.ts +17 -0
  72. package/dist/esm/proxy/ProjectRegistry.d.ts.map +1 -0
  73. package/dist/esm/proxy/ProjectRegistry.js +56 -0
  74. package/dist/esm/proxy/ProjectRegistry.js.map +1 -0
  75. package/dist/esm/utils/reportSavings.d.ts +6 -0
  76. package/dist/esm/utils/reportSavings.d.ts.map +1 -1
  77. package/dist/esm/utils/reportSavings.js.map +1 -1
  78. package/package.json +1 -1
  79. package/dashboard/dist/assets/index-BJzMIGrT.js.map +0 -1
  80. package/dashboard/dist/assets/index-DUOrThix.css +0 -1
@@ -32,6 +32,8 @@ import SemanticScrubber from '../security/SemanticScrubber';
32
32
  import CloudSyncClient from '../cloud/CloudSyncClient';
33
33
  import { pruneHistoryMessages, squeezePrompt } from '../utils/ContextSqueezer';
34
34
  import { AgentMultiplexer } from '../proxy/AgentMultiplexer';
35
+ import { ProjectRegistry } from '../proxy/ProjectRegistry';
36
+ import { shouldInject, buildMinimalContext, buildDeepContext, sniffProjectFromMessages } from '../proxy/ContextInjector';
35
37
  import { savingsLedger } from '../utils/SavingsLedger';
36
38
  const chroma = new ChromaClient({ host: 'localhost', port: 8000 });
37
39
  const dummyEmbeddingFunction = { generate: (texts) => Promise.resolve([]) };
@@ -49,11 +51,23 @@ const USAGE_FILE = path.join(CACHE_DIR, 'usage.json');
49
51
  const LICENSE_FILE = path.join(CACHE_DIR, 'license.json');
50
52
  const EVENT_LOG = [];
51
53
  const MAX_EVENTS = 100;
54
+ // MCP Status tracking
55
+ const MCP_STATUS = {
56
+ lastActivity: Date.now(),
57
+ toolCallCount: 0,
58
+ status: 'idle'
59
+ };
52
60
  function logEvent(event) {
53
61
  const ev = { id: Math.random().toString(36).slice(2, 11), timestamp: Date.now(), ...event };
54
62
  EVENT_LOG.push(ev);
55
63
  if (EVENT_LOG.length > MAX_EVENTS)
56
64
  EVENT_LOG.shift();
65
+ // Track MCP activity
66
+ if (event.type === 'mcp_tool_call') {
67
+ MCP_STATUS.lastActivity = Date.now();
68
+ MCP_STATUS.toolCallCount++;
69
+ MCP_STATUS.status = 'active';
70
+ }
57
71
  // Print a terminal savings summary every 10 events
58
72
  if (EVENT_LOG.length > 0 && EVENT_LOG.length % 10 === 0) {
59
73
  const snap = savingsLedger.getSnapshot();
@@ -62,354 +76,49 @@ function logEvent(event) {
62
76
  }
63
77
  }
64
78
  }
65
- const FREE_LIMIT = 500;
66
- const WARN_PCT = 0.8;
67
- const VALIDATE_URL = 'https://lemma.nxus.studio/api/v1/validate';
68
- if (!fs.existsSync(CACHE_DIR))
69
- fs.mkdirSync(CACHE_DIR, { recursive: true });
70
- // โ”€โ”€ Helpers โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
71
- async function readJson(file, fallback) {
72
- try {
73
- if (await fs.promises.access(file).then(() => true).catch(() => false)) {
74
- const content = await fs.promises.readFile(file, 'utf8');
75
- return JSON.parse(content);
76
- }
77
- }
78
- catch { }
79
- return fallback;
80
- }
81
- const writeQueues = new Map();
82
- async function writeJson(file, data) {
83
- const chain = writeQueues.get(file) || Promise.resolve();
84
- const writeOperation = async () => {
85
- try {
86
- const tempFile = `${file}.${crypto.randomUUID ? crypto.randomUUID() : Math.random().toString(36).substring(2, 15)}.tmp`;
87
- await fs.promises.writeFile(tempFile, JSON.stringify(data, null, 2));
88
- await fs.promises.rename(tempFile, file);
89
- }
90
- catch { }
91
- };
92
- const nextChain = chain.then(writeOperation).catch(() => { });
93
- writeQueues.set(file, nextChain);
94
- return nextChain;
95
- }
96
- function projectHash(name) {
97
- return crypto.createHash('sha1').update(name).digest('hex').slice(0, 12);
98
- }
99
- function getCollectionName(projectName) {
100
- let cleanName = projectName.replace(/[^a-zA-Z0-9._-]/g, '_');
101
- cleanName = cleanName.replace(/^[^a-zA-Z0-9]+/, '');
102
- cleanName = cleanName.replace(/[^a-zA-Z0-9]+$/, '');
103
- if (cleanName.length < 3) {
104
- cleanName = 'lemma_proj_' + cleanName;
105
- }
106
- return `lemma-cache-${cleanName}`;
107
- }
108
- function detectProject() {
109
- const pkgPath = path.join(process.cwd(), 'package.json');
110
- if (fs.existsSync(pkgPath)) {
111
- try {
112
- const p = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
113
- if (p.name)
114
- return p.name;
115
- }
116
- catch { }
117
- }
118
- return path.basename(process.cwd());
119
- }
120
- function ensureProjectDir(name) {
121
- const dir = path.join(CACHE_DIR, projectHash(name));
122
- if (!fs.existsSync(dir))
123
- fs.mkdirSync(dir, { recursive: true });
124
- const marker = path.join(dir, 'project-name.txt');
125
- if (!fs.existsSync(marker))
126
- fs.writeFileSync(marker, name);
127
- return dir;
128
- }
129
- function getPidByPort(port) {
130
- const { execSync } = require('child_process');
131
- try {
132
- if (process.platform === 'win32') {
133
- const out = execSync(`netstat -ano | findstr :${port}`).toString();
134
- const match = out.match(/LISTENING\s+(\d+)/);
135
- return match ? match[1] : null;
136
- }
137
- else {
138
- const out = execSync(`lsof -t -i :${port}`).toString().trim();
139
- return out.split('\n')[0]; // Take first if multiple
140
- }
141
- }
142
- catch {
143
- return null;
144
- }
145
- }
146
- async function checkDependency(url) {
147
- try {
148
- const res = await axios.get(url, { timeout: 1000, validateStatus: () => true });
149
- return res.status >= 200 && res.status < 500;
150
- }
151
- catch {
152
- return false;
153
- }
154
- }
155
- async function ensureChromaRunning() {
156
- const chromaPort = process.env.CHROMA_PORT || '8000';
157
- const chromaHost = process.env.CHROMA_HOST || 'http://localhost';
158
- const chromaUrl = `${chromaHost}:${chromaPort}`;
159
- const isChromaUp = await checkDependency(`${chromaUrl}/api/v1/heartbeat`);
160
- if (isChromaUp) {
161
- console.log(`โœ… \x1b[32m[ChromaDB]\x1b[0m ChromaDB is already running on port ${chromaPort}`);
162
- return true;
163
- }
164
- console.log(`\n๐Ÿ“ฆ \x1b[35m[ChromaDB]\x1b[0m ChromaDB is not running on port ${chromaPort}.`);
165
- console.log(`๐Ÿ”„ Starting ChromaDB in background...`);
166
- const chromaDataPath = path.join(CACHE_DIR, 'chroma_data');
167
- try {
168
- const { spawn } = require('child_process');
169
- if (!fs.existsSync(chromaDataPath)) {
170
- fs.mkdirSync(chromaDataPath, { recursive: true });
171
- }
172
- const chromaProcess = spawn('chroma', ['run', '--path', chromaDataPath, '--port', chromaPort], {
173
- stdio: 'ignore',
174
- detached: true
175
- });
176
- chromaProcess.unref();
177
- // Wait up to 5 seconds for ChromaDB to start
178
- for (let i = 0; i < 5; i++) {
179
- await new Promise(r => setTimeout(r, 1000));
180
- const up = await checkDependency(`${chromaUrl}/api/v1/heartbeat`);
181
- if (up) {
182
- console.log(`โœ… \x1b[32m[ChromaDB]\x1b[0m ChromaDB has been started and is ready!`);
183
- return true;
184
- }
185
- }
186
- console.log(`โš ๏ธ \x1b[33m[ChromaDB]\x1b[0m ChromaDB was started but is taking longer to respond. It will continue starting in the background.`);
187
- return true;
188
- }
189
- catch (err) {
190
- console.error(`โŒ Failed to automatically start ChromaDB: ${err.message}`);
191
- console.error(`๐Ÿ‘‰ Please start it manually: chroma run --path ${chromaDataPath} --port ${chromaPort}`);
192
- return false;
193
- }
194
- }
195
- function ensureGitIgnore() {
196
- const gi = path.join(process.cwd(), '.gitignore');
197
- let added = false;
198
- try {
199
- let content = '';
200
- if (fs.existsSync(gi)) {
201
- content = fs.readFileSync(gi, 'utf8');
202
- }
203
- const appendLines = [];
204
- if (!content.includes('.lemma/')) {
205
- appendLines.push('# Lemma Context Logs', '.lemma/');
206
- added = true;
207
- }
208
- if (!content.includes('chroma_data/')) {
209
- appendLines.push('# Lemma Local Vector Database', 'chroma_data/');
210
- added = true;
211
- }
212
- if (appendLines.length > 0) {
213
- const prefix = content.endsWith('\n') || content === '' ? '' : '\n';
214
- fs.appendFileSync(gi, prefix + appendLines.join('\n') + '\n');
215
- }
216
- }
217
- catch { }
218
- return added;
219
- }
220
- const CLIPBOARD_PID_FILE = path.join(CACHE_DIR, 'clipboard.pid');
221
- export function autoConfigureAll(projectName, cliOpts) {
222
- const project = projectName || detectProject();
223
- const HOME = process.env.HOME || process.env.USERPROFILE || '~';
224
- // Load configuration if it exists
225
- let configDisabled = false;
226
- let configEditor = true;
227
- let configShell = true;
228
- let configClaude = true;
229
- try {
230
- const configPath = path.join(process.cwd(), 'lemma.config.json');
231
- if (fs.existsSync(configPath)) {
232
- const rawConfig = fs.readFileSync(configPath, 'utf8');
233
- const config = JSON.parse(rawConfig);
234
- const systemConfig = config.system || {};
235
- const autoConfig = systemConfig.autoConfigure || {};
236
- if (autoConfig.disabled === true)
237
- configDisabled = true;
238
- if (autoConfig.editor === false)
239
- configEditor = false;
240
- if (autoConfig.shell === false)
241
- configShell = false;
242
- if (autoConfig.claude === false)
243
- configClaude = false;
244
- }
245
- }
246
- catch { }
247
- // Merge CLI overrides and config
248
- const shouldConfigure = cliOpts?.configure !== false && !configDisabled;
249
- if (!shouldConfigure) {
250
- console.log(`โญ๏ธ [Auto-Configure] Skipped auto-configuration (disabled via options or config).`);
251
- return;
252
- }
253
- const runEditor = cliOpts?.editor !== false && configEditor;
254
- const runShell = cliOpts?.shell !== false && configShell;
255
- const runClaude = cliOpts?.claude !== false && configClaude;
256
- // 1. Claude Desktop Config
257
- if (runClaude) {
258
- try {
259
- const isWin = process.platform === 'win32';
260
- const claudePath = isWin
261
- ? path.join(process.env.APPDATA || '', 'Claude/claude_desktop_config.json')
262
- : path.join(HOME, 'Library/Application Support/Claude/claude_desktop_config.json');
263
- if (fs.existsSync(path.dirname(claudePath))) {
264
- let config = { mcpServers: {} };
265
- if (fs.existsSync(claudePath)) {
266
- try {
267
- config = JSON.parse(fs.readFileSync(claudePath, 'utf8'));
268
- }
269
- catch { }
270
- }
271
- if (!config.mcpServers)
272
- config.mcpServers = {};
273
- config.mcpServers.lemma = {
274
- command: 'npx',
275
- args: ['-y', '@nxuss/lemma', 'mcp'],
276
- env: { LEMMA_PROJECT: project }
277
- };
278
- fs.writeFileSync(claudePath, JSON.stringify(config, null, 2));
279
- console.log(`โœ… [Claude Desktop] Configured automatically in: ${claudePath} (Restart Claude to activate)`);
280
- }
281
- }
282
- catch (e) {
283
- console.log(`โš ๏ธ Could not auto-configure Claude Desktop: ${e.message}`);
284
- }
285
- }
286
- else {
287
- console.log(`โญ๏ธ [Claude Desktop] Skip auto-configuration.`);
288
- }
289
- // 2. Shell Profiles Environment Override
290
- if (runShell) {
79
+ const LEMMA_RULES_BLOCK = `
80
+ ๐Ÿง  Lemma MCP โ€” Autonomous Behaviors
81
+
82
+ I have Lemma MCP tools connected. I MUST use them proactively:
83
+
84
+ 1. When starting work on a project, call \`get_project_onboarding\` or read \`lemma://project/context\` to understand the architecture.
85
+ 2. Before answering technical questions, call \`search_memory\` for relevant past solutions.
86
+ 3. When examining a file, call \`get_telepathic_hints\` for relevant context.
87
+ 4. After solving a problem, call \`store_memory\` to persist the solution for future sessions.
88
+ 5. Use \`read_workspace_file\`, \`list_workspace_dir\`, \`search_workspace\` instead of asking the user to read or navigate files.
89
+ 6. Always call \`validate_patch_sandbox\` before writing patches.
90
+ 7. Read \`lemma://runtime/context\` first when the user reports a bug.
91
+ `;
92
+ function injectIdeRules(projectName) {
93
+ const content = `\n# ${projectName} โ€” ${LEMMA_RULES_BLOCK.trim()}\n`;
94
+ const rulesTargets = [
95
+ { name: 'Cursor', path: path.join(process.cwd(), '.cursorrules') },
96
+ { name: 'Cursor (v2)', path: path.join(process.cwd(), '.cursor/rules/lemma.mdc') },
97
+ { name: 'Windsurf', path: path.join(process.cwd(), '.windsurfrules') },
98
+ { name: 'Cline', path: path.join(process.cwd(), '.clinerules') },
99
+ ];
100
+ for (const target of rulesTargets) {
291
101
  try {
292
- const profiles = [
293
- path.join(HOME, '.zshrc'),
294
- path.join(HOME, '.bashrc'),
295
- path.join(HOME, '.bash_profile'),
296
- path.join(HOME, '.profile')
297
- ];
298
- const lines = [
299
- '',
300
- '# Lemma AI Gateway Overrides',
301
- 'export OPENAI_BASE_URL="http://localhost:8081/v1"',
302
- 'export ANTHROPIC_BASE_URL="http://localhost:8081"',
303
- 'export LEMMA_PROJECT="' + project + '"',
304
- ''
305
- ].join('\n');
306
- for (const profile of profiles) {
307
- if (fs.existsSync(profile)) {
308
- try {
309
- const content = fs.readFileSync(profile, 'utf8');
310
- if (!content.includes('OPENAI_BASE_URL') && !content.includes('LEMMA_PROJECT')) {
311
- fs.appendFileSync(profile, lines);
312
- console.log(`โœ… [Shell Profile] Configured ${path.basename(profile)} with local redirect variables!`);
313
- }
314
- }
315
- catch { }
316
- }
102
+ const dir = path.dirname(target.path);
103
+ if (target.name === 'Cursor (v2)') {
104
+ if (!fs.existsSync(dir))
105
+ fs.mkdirSync(dir, { recursive: true });
317
106
  }
318
- }
319
- catch (e) {
320
- console.log(`โš ๏ธ Could not auto-configure shell profiles: ${e.message}`);
321
- }
322
- }
323
- else {
324
- console.log(`โญ๏ธ [Shell Profile] Skip auto-configuration.`);
325
- }
326
- // 3. Cursor, Windsurf & VS Code settings.json updates
327
- if (runEditor) {
328
- try {
329
- const isWin = process.platform === 'win32';
330
- const isMac = process.platform === 'darwin';
331
- const candidates = [];
332
- if (isMac) {
333
- candidates.push({
334
- name: 'Cursor',
335
- path: path.join(HOME, 'Library/Application Support/Cursor/User/settings.json')
336
- });
337
- candidates.push({
338
- name: 'Windsurf',
339
- path: path.join(HOME, 'Library/Application Support/Windsurf/User/settings.json')
340
- });
341
- candidates.push({
342
- name: 'VS Code',
343
- path: path.join(HOME, 'Library/Application Support/Code/User/settings.json')
344
- });
345
- }
346
- else if (isWin) {
347
- const appData = process.env.APPDATA || '';
348
- candidates.push({
349
- name: 'Cursor',
350
- path: path.join(appData, 'Cursor/User/settings.json')
351
- });
352
- candidates.push({
353
- name: 'Windsurf',
354
- path: path.join(appData, 'Windsurf/User/settings.json')
355
- });
356
- candidates.push({
357
- name: 'VS Code',
358
- path: path.join(appData, 'Code/User/settings.json')
359
- });
360
- }
361
- else {
362
- candidates.push({
363
- name: 'Cursor',
364
- path: path.join(HOME, '.config/Cursor/User/settings.json')
365
- });
366
- candidates.push({
367
- name: 'Windsurf',
368
- path: path.join(HOME, '.config/Windsurf/User/settings.json')
369
- });
370
- candidates.push({
371
- name: 'VS Code',
372
- path: path.join(HOME, '.config/Code/User/settings.json')
373
- });
107
+ else if (!fs.existsSync(dir)) {
108
+ continue;
374
109
  }
375
- for (const cand of candidates) {
376
- const dir = path.dirname(cand.path);
377
- if (fs.existsSync(dir)) {
378
- let settings = {};
379
- if (fs.existsSync(cand.path)) {
380
- try {
381
- settings = JSON.parse(fs.readFileSync(cand.path, 'utf8'));
382
- }
383
- catch { }
384
- }
385
- let updated = false;
386
- // Settings configuration to route custom openai/anthropic models through the proxy
387
- const updates = {
388
- "openai.baseURL": "http://localhost:8081/v1",
389
- "openai.apiKey": "dummy-key-for-lemma",
390
- "anthropic.baseURL": "http://localhost:8081",
391
- "anthropic.apiKey": "dummy-key-for-lemma"
392
- };
393
- for (const [key, value] of Object.entries(updates)) {
394
- if (settings[key] !== value) {
395
- settings[key] = value;
396
- updated = true;
397
- }
398
- }
399
- if (updated) {
400
- fs.writeFileSync(cand.path, JSON.stringify(settings, null, 2));
401
- console.log(`โœ… [${cand.name}] Configured editor settings in ${cand.path} to route via local gateway!`);
402
- }
403
- }
110
+ if (fs.existsSync(target.path)) {
111
+ const existing = fs.readFileSync(target.path, 'utf8');
112
+ if (existing.includes('Lemma MCP'))
113
+ continue;
404
114
  }
115
+ fs.writeFileSync(target.path, content, { flag: 'a' });
116
+ console.log(`โœ… [${target.name}] Injected Lemma rules: ${target.path}`);
405
117
  }
406
- catch (e) {
407
- console.log(`โš ๏ธ Could not auto-configure editor settings: ${e.message}`);
118
+ catch {
119
+ console.log(`โš ๏ธ Could not inject rules for ${target.name}`);
408
120
  }
409
121
  }
410
- else {
411
- console.log(`โญ๏ธ [Editor Settings] Skip auto-configuration.`);
412
- }
413
122
  }
414
123
  export function startBackgroundClipboardWatcher(cliOpts) {
415
124
  // Load configuration if it exists
@@ -938,6 +647,8 @@ class LemmaServer {
938
647
  this.sessions = new Map();
939
648
  this.timeline = [];
940
649
  this.lastFileContents = new Map();
650
+ this.registry = new ProjectRegistry();
651
+ this.projectSessions = new Map();
941
652
  this.port = port;
942
653
  this.projectName = projectName || detectProject();
943
654
  this.projectDir = ensureProjectDir(this.projectName);
@@ -963,6 +674,38 @@ class LemmaServer {
963
674
  const pro = await isPro();
964
675
  res.json({ status: 'ok', project: this.projectName, mode: pro ? 'pro' : 'standard', tier: pro ? 'pro' : 'free', port: this.port });
965
676
  });
677
+ // MCP Status endpoint
678
+ this.app.get('/api/mcp-status', (req, res) => {
679
+ // Update status based on inactivity (60 seconds = idle, 300 seconds = offline)
680
+ const now = Date.now();
681
+ const secondsSinceActive = (now - MCP_STATUS.lastActivity) / 1000;
682
+ let status = 'offline';
683
+ if (secondsSinceActive < 60) {
684
+ status = 'active';
685
+ }
686
+ else if (secondsSinceActive < 300) {
687
+ status = 'idle';
688
+ }
689
+ res.json({
690
+ status,
691
+ lastActivity: new Date(MCP_STATUS.lastActivity).toISOString(),
692
+ toolCallCount: MCP_STATUS.toolCallCount,
693
+ secondsSinceActive: Math.floor(secondsSinceActive),
694
+ uptime: Math.floor(process.uptime())
695
+ });
696
+ });
697
+ // Project registration from MCP handshake
698
+ this.app.post('/api/handshake', (req, res) => {
699
+ const { projectPath, projectName, stack } = req.body;
700
+ if (projectPath && projectName) {
701
+ this.registry.register(projectPath, projectName, stack || '');
702
+ console.log(`๐Ÿ“‹ [Handshake] Project registered: "${projectName}" (${projectPath})`);
703
+ res.json({ ok: true });
704
+ }
705
+ else {
706
+ res.status(400).json({ error: 'projectPath and projectName required' });
707
+ }
708
+ });
966
709
  this.app.get('/v1/models', (req, res) => {
967
710
  res.json({ object: 'list', data: [
968
711
  { id: 'gpt-4o', object: 'model', owned_by: 'lemma-proxy' },
@@ -1056,18 +799,28 @@ class LemmaServer {
1056
799
  const limit = parseInt(req.query.limit) || 50;
1057
800
  const start = (page - 1) * limit;
1058
801
  // Get local proxy events (normalized schema matching what dashboard expects)
1059
- const localEvents = EVENT_LOG.map(e => ({
1060
- id: e.id,
1061
- timestamp: new Date(e.timestamp).toISOString(),
1062
- type: e.type, // e.g., 'cache:hit' or 'cache:miss' which client normalizes to 'cache_hit' or 'cache_miss'
1063
- latency: e.latency,
1064
- tokensSaved: e.tokens,
1065
- costSaved: e.tokens ? (e.tokens / 1000) * 0.002 : 0,
1066
- metadata: {
1067
- provider: e.provider,
1068
- project: e.project
802
+ const localEvents = EVENT_LOG.map(e => {
803
+ const eventData = {
804
+ id: e.id,
805
+ timestamp: new Date(e.timestamp).toISOString(),
806
+ type: e.type, // e.g., 'cache:hit' or 'cache:miss' which client normalizes to 'cache_hit' or 'cache_miss'
807
+ latency: e.latency,
808
+ tokensSaved: e.tokens || e.tokensImpact || 0,
809
+ costSaved: (e.tokens || e.tokensImpact || 0) ? ((e.tokens || e.tokensImpact || 0) / 1000) * 0.002 : 0,
810
+ metadata: {
811
+ provider: e.provider,
812
+ project: e.project
813
+ }
814
+ };
815
+ // Include MCP-specific fields if this is an MCP event
816
+ if (e.type === 'mcp_tool_call') {
817
+ eventData.tool = e.tool;
818
+ eventData.args = e.args;
819
+ eventData.result = e.result;
820
+ eventData.tokensImpact = e.tokensImpact;
1069
821
  }
1070
- }));
822
+ return eventData;
823
+ });
1071
824
  let mergedEvents = [...localEvents];
1072
825
  // Query the stack orchestration API if running to merge stack events
1073
826
  try {
@@ -1457,6 +1210,33 @@ class LemmaServer {
1457
1210
  }
1458
1211
  res.json({ ok: true, snapshot: savingsLedger.getSnapshot().total });
1459
1212
  });
1213
+ // MCP Tool Event Endpoint โ€” called by MCP server when tools execute
1214
+ this.app.post('/api/mcp-event', (req, res) => {
1215
+ try {
1216
+ const event = req.body;
1217
+ if (!event.type || !event.tool) {
1218
+ return res.status(400).json({ error: 'Missing type or tool' });
1219
+ }
1220
+ // Create normalized event
1221
+ const mcpEvent = {
1222
+ id: Math.random().toString(36).slice(2, 11),
1223
+ type: event.type || 'mcp_tool_call',
1224
+ tool: event.tool,
1225
+ args: event.args || {},
1226
+ result: event.result || 'UNKNOWN',
1227
+ latency: event.latency || 0,
1228
+ tokensImpact: event.tokensImpact || 0,
1229
+ error: event.error || null,
1230
+ timestamp: event.timestamp || new Date().toISOString(),
1231
+ };
1232
+ // Add to EVENT_LOG
1233
+ logEvent(mcpEvent);
1234
+ res.json({ success: true, eventId: mcpEvent.id });
1235
+ }
1236
+ catch (error) {
1237
+ res.status(500).json({ error: 'Internal server error' });
1238
+ }
1239
+ });
1460
1240
  this.app.get('/dashboard/*', (req, res) => res.sendFile(path.join(this.dashboardPath, 'index.html')));
1461
1241
  }
1462
1242
  }
@@ -1559,6 +1339,15 @@ class LemmaServer {
1559
1339
  }
1560
1340
  console.log(`๐ŸŒŒ \x1b[35m[Multiverse]\x1b[0m Captured micro-snapshot ${snapshot.id} (${changedFiles.length} file edits mapped).`);
1561
1341
  }
1342
+ detectActiveProject(req) {
1343
+ const mcpActive = this.registry.getActive();
1344
+ if (mcpActive)
1345
+ return mcpActive;
1346
+ const sniffed = sniffProjectFromMessages(req.body.messages || [], this.registry);
1347
+ if (sniffed)
1348
+ return sniffed;
1349
+ return null;
1350
+ }
1562
1351
  async handleCompletion(req, res, provider) {
1563
1352
  const t0 = Date.now();
1564
1353
  const pro = await isPro();
@@ -1679,6 +1468,24 @@ Adjusted Answer:`;
1679
1468
  return res.json(unmaskedData);
1680
1469
  }
1681
1470
  }
1471
+ // โ”€โ”€ 3. Context Injection (for cache misses) โ”€โ”€
1472
+ const activeProject = this.detectActiveProject(req);
1473
+ if (activeProject) {
1474
+ const psKey = activeProject.path;
1475
+ if (!this.projectSessions.has(psKey)) {
1476
+ this.projectSessions.set(psKey, { alreadyInjected: false });
1477
+ }
1478
+ const session = this.projectSessions.get(psKey);
1479
+ const injectionLevel = shouldInject(activeProject, req.body.messages, session);
1480
+ if (injectionLevel !== 'none') {
1481
+ const context = injectionLevel === 'deep'
1482
+ ? await buildDeepContext(activeProject, this.port)
1483
+ : buildMinimalContext(activeProject);
1484
+ req.body.messages.unshift({ role: 'system', content: context });
1485
+ session.alreadyInjected = true;
1486
+ console.log(`๐Ÿง  [Context] Injected ${injectionLevel} context for "${activeProject.name}"`);
1487
+ }
1488
+ }
1682
1489
  // โ”€โ”€ 2. Cache Miss Path โ”€โ”€
1683
1490
  const routingDecision = complexityRouter.evaluate(prompt, originalModel);
1684
1491
  const model = routingDecision.model;
@@ -1919,9 +1726,46 @@ Adjusted Answer:`;
1919
1726
  }
1920
1727
  }
1921
1728
  // โ”€โ”€ CLI โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
1922
- program.name('lemma').description('Lemma Proxy CLI โ€” Intelligent AI Gateway').version(VERSION);
1729
+ function showWelcomeScreen() {
1730
+ console.log(`
1731
+ \x1b[1m\x1b[35m _ \x1b[0m
1732
+ \x1b[1m\x1b[35m | | ___ _ __ ___ __ _ \x1b[0m \x1b[1mLEMMA\x1b[0m \x1b[35mv${VERSION}\x1b[0m
1733
+ \x1b[1m\x1b[35m | | / _ \\ '_ \` _ \\ / _\` |\x1b[0m
1734
+ \x1b[1m\x1b[35m | |__| __/ | | | | | (_| |\x1b[0m Intelligent AI Gateway
1735
+ \x1b[1m\x1b[35m |_____\\___|_| |_| |_|\\__,_|\x1b[0m
1736
+
1737
+ \x1b[1mQuick Start\x1b[0m
1738
+ \x1b[33mlemma init\x1b[0m Initialize Lemma in this project
1739
+ \x1b[33mlemma start\x1b[0m Start the proxy server
1740
+ \x1b[33mlemma mcp\x1b[0m Start MCP Server for Cursor/Claude
1741
+
1742
+ \x1b[1mFree Features\x1b[0m (all included)
1743
+ ๐Ÿ”’ Privacy Firewall ๐Ÿ’พ Exact-match Cache
1744
+ ๐Ÿ”€ Complexity Router ๐Ÿ“ก Telepathic Clipboard
1745
+ ๐Ÿ“Š Basic Usage Stats ๐Ÿง  MCP Tools & Resources
1746
+
1747
+ \x1b[1mPro Features\x1b[0m (\x1b[35mlemma activate <key>\x1b[0m or \x1b[36mhttps://lemma.nxus.studio/upgrade\x1b[0m)
1748
+ ๐ŸŽฏ Semantic Vector Cache ๐ŸŒ Hive Mind Cloud Sync
1749
+ ๐Ÿ›ธ Autopilot Healer ๐Ÿฉบ Auto-Diagnose & Heal
1750
+ ๐ŸŒŒ Codebase Multiverse ๐Ÿ—๏ธ Architecture Onboarding
1751
+
1752
+ \x1b[90mRun \`lemma --help\` for all commands.\x1b[0m
1753
+ `);
1754
+ }
1755
+ program
1756
+ .name('lemma')
1757
+ .description('Intelligent AI Gateway โ€” cache, route, and optimize AI requests')
1758
+ .version(VERSION)
1759
+ .addHelpText('after', `
1760
+ \x1b[1mPlan Comparison\x1b[0m
1761
+ \x1b[32mFree\x1b[0m 500 req/mo ยท Exact cache ยท Privacy firewall ยท MCP tools
1762
+ \x1b[35mPro\x1b[0m Unlimited ยท Semantic cache ยท Cloud sync ยท Autopilot ยท Heal
1763
+ \x1b[36mhttps://lemma.nxus.studio/upgrade\x1b[0m
1764
+
1765
+ \x1b[90mActivate Pro: lemma activate <key>\x1b[0m
1766
+ `);
1923
1767
  program.command('mcp')
1924
- .description('Start the Lemma MCP Server (Model Context Protocol)')
1768
+ .description('Start the MCP Server for Cursor / Claude Desktop integration')
1925
1769
  .action(() => {
1926
1770
  const { spawn } = require('child_process');
1927
1771
  // Robust search candidates for both development (TS source) and compiled (JS production) environments.
@@ -1976,6 +1820,7 @@ program.command('start')
1976
1820
  // Keep configurations up to date even if server was already started
1977
1821
  const runningProject = resp.data.project || detectProject();
1978
1822
  autoConfigureAll(runningProject, { configure: opts.configure });
1823
+ injectIdeRules(runningProject);
1979
1824
  startBackgroundClipboardWatcher({ clipboard: opts.clipboard });
1980
1825
  process.exit(0);
1981
1826
  }
@@ -1983,6 +1828,7 @@ program.command('start')
1983
1828
  catch { }
1984
1829
  const startProject = opts.project || detectProject();
1985
1830
  autoConfigureAll(startProject, { configure: opts.configure });
1831
+ injectIdeRules(startProject);
1986
1832
  startBackgroundClipboardWatcher({ clipboard: opts.clipboard });
1987
1833
  // Automatically enable autopilot if stack is enabled and user is Pro
1988
1834
  const runAutopilot = opts.autopilot || opts.stack;
@@ -2195,8 +2041,12 @@ program.command('init')
2195
2041
  }
2196
2042
  console.log('\n๐Ÿง  \x1b[35mAuto-Setup MCP and redirection overrides for AI IDEs...\x1b[0m');
2197
2043
  autoConfigureAll(project, { configure: opts.configure });
2198
- console.log('\nโœจ Project initialized for the Agentic Era!');
2199
- console.log('๐Ÿš€ Run "lemma start" to begin.\n');
2044
+ injectIdeRules(project);
2045
+ console.log('\nโœจ Project initialized for the Agentic Era!\n');
2046
+ console.log('\x1b[1mNext steps:\x1b[0m');
2047
+ console.log(' \x1b[33mlemma start\x1b[0m Start the proxy & unlock AI savings');
2048
+ console.log(' \x1b[33mlemma upgrade\x1b[0m See Pro features & get a key');
2049
+ console.log(' \x1b[33mlemma --help\x1b[0m All available commands\n');
2200
2050
  });
2201
2051
  // โ”€โ”€ Auto-Healing Diagnostics (Zero-Cost Suite) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
2202
2052
  export async function performAutoHeal(apply) {
@@ -2366,7 +2216,7 @@ Ensure you return strictly JSON. Do not return any other text outside the JSON b
2366
2216
  }
2367
2217
  }
2368
2218
  program.command('heal')
2369
- .description('Diagnose and auto-heal the latest local server crash from .lemma/live-context.md')
2219
+ .description('[Pro] Diagnose and auto-heal the latest local server crash from .lemma/live-context.md')
2370
2220
  .option('--apply', 'Apply the auto-generated code fix automatically', false)
2371
2221
  .action(async (opts) => {
2372
2222
  try {
@@ -2395,7 +2245,7 @@ program.command('heal')
2395
2245
  }
2396
2246
  });
2397
2247
  program.command('autopilot')
2398
- .description('Start the autonomous background compiler watcher & active healer')
2248
+ .description('[Pro] Start the autonomous background compiler watcher & active healer')
2399
2249
  .option('--dir <directory>', 'Directory to monitor for changes', 'src')
2400
2250
  .action(async (opts) => {
2401
2251
  try {
@@ -2420,6 +2270,26 @@ program.command('autopilot')
2420
2270
  process.exit(1);
2421
2271
  }
2422
2272
  });
2273
+ program.command('upgrade')
2274
+ .description('Learn about Pro features and get an activation key')
2275
+ .action(() => {
2276
+ console.log(`
2277
+ \x1b[1m\x1b[35mLemma Pro\x1b[0m โ€” Unlimited AI Gateway power
2278
+
2279
+ \x1b[1mPro features:\x1b[0m
2280
+ ๐ŸŽฏ \x1b[1mSemantic Vector Cache\x1b[0m โ€” Smarter cache hits via vector similarity
2281
+ ๐ŸŒ \x1b[1mHive Mind Cloud Sync\x1b[0m โ€” Cross-project cache & bug telepathy
2282
+ ๐Ÿ›ธ \x1b[1mAutopilot Healer\x1b[0m โ€” Autonomous background compiler watcher
2283
+ ๐Ÿฉบ \x1b[1mAuto-Diagnose & Heal\x1b[0m โ€” AI-driven crash analysis & patch
2284
+ ๐ŸŒŒ \x1b[1mCodebase Multiverse\x1b[0m โ€” AST time-travel & snapshots
2285
+ ๐Ÿ—๏ธ \x1b[1mArchitecture Onboarding\x1b[0m โ€” Zero-shot project understanding
2286
+ ๐Ÿ”“ \x1b[1mUnlimited requests\x1b[0m โ€” No monthly caps
2287
+
2288
+ \x1b[36m https://lemma.nxus.studio/upgrade\x1b[0m
2289
+
2290
+ Already have a key? Run: \x1b[33mlemma activate <key>\x1b[0m
2291
+ `);
2292
+ });
2423
2293
  program.command('clipboard')
2424
2294
  .description('Start the Lemma Local Clipboard Telepathic Optimizer (auto-squeezes copied code blocks)')
2425
2295
  .option('--interval <ms>', 'Polling interval in milliseconds', '1000')
@@ -2437,6 +2307,11 @@ program.command('clipboard')
2437
2307
  }
2438
2308
  });
2439
2309
  if (process.argv[1] && (process.argv[1].includes('lemma') || process.argv[1].endsWith('.cjs') || process.argv[1].endsWith('.js'))) {
2310
+ // Bare `lemma` with no args โ†’ show welcome screen
2311
+ if (process.argv.length <= 2) {
2312
+ showWelcomeScreen();
2313
+ process.exit(0);
2314
+ }
2440
2315
  program.parse(process.argv);
2441
2316
  }
2442
2317
  //# sourceMappingURL=lemma-proxy.js.map