@nxuss/lemma 0.7.2 โ†’ 0.7.4

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 (48) hide show
  1. package/README.md +6 -5
  2. package/dashboard/dist/assets/{index-C3X0fqmd.js โ†’ index-BNYoIN8-.js} +34 -34
  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/cli/lemma-proxy.d.ts +0 -6
  7. package/dist/cjs/cli/lemma-proxy.d.ts.map +1 -1
  8. package/dist/cjs/cli/lemma-proxy.js +251 -338
  9. package/dist/cjs/cli/lemma-proxy.js.map +1 -1
  10. package/dist/cjs/mcp/index.js +103 -1
  11. package/dist/cjs/mcp/index.js.map +1 -1
  12. package/dist/cjs/mcp/resources.d.ts.map +1 -1
  13. package/dist/cjs/mcp/resources.js +75 -0
  14. package/dist/cjs/mcp/resources.js.map +1 -1
  15. package/dist/cjs/mcp/tools.d.ts.map +1 -1
  16. package/dist/cjs/mcp/tools.js +174 -0
  17. package/dist/cjs/mcp/tools.js.map +1 -1
  18. package/dist/cjs/proxy/ContextInjector.d.ts +14 -0
  19. package/dist/cjs/proxy/ContextInjector.d.ts.map +1 -0
  20. package/dist/cjs/proxy/ContextInjector.js +148 -0
  21. package/dist/cjs/proxy/ContextInjector.js.map +1 -0
  22. package/dist/cjs/proxy/ProjectRegistry.d.ts +17 -0
  23. package/dist/cjs/proxy/ProjectRegistry.d.ts.map +1 -0
  24. package/dist/cjs/proxy/ProjectRegistry.js +60 -0
  25. package/dist/cjs/proxy/ProjectRegistry.js.map +1 -0
  26. package/dist/esm/cli/lemma-proxy.d.ts +0 -6
  27. package/dist/esm/cli/lemma-proxy.d.ts.map +1 -1
  28. package/dist/esm/cli/lemma-proxy.js +251 -337
  29. package/dist/esm/cli/lemma-proxy.js.map +1 -1
  30. package/dist/esm/mcp/index.js +103 -1
  31. package/dist/esm/mcp/index.js.map +1 -1
  32. package/dist/esm/mcp/resources.d.ts.map +1 -1
  33. package/dist/esm/mcp/resources.js +75 -0
  34. package/dist/esm/mcp/resources.js.map +1 -1
  35. package/dist/esm/mcp/tools.d.ts.map +1 -1
  36. package/dist/esm/mcp/tools.js +175 -1
  37. package/dist/esm/mcp/tools.js.map +1 -1
  38. package/dist/esm/proxy/ContextInjector.d.ts +14 -0
  39. package/dist/esm/proxy/ContextInjector.d.ts.map +1 -0
  40. package/dist/esm/proxy/ContextInjector.js +108 -0
  41. package/dist/esm/proxy/ContextInjector.js.map +1 -0
  42. package/dist/esm/proxy/ProjectRegistry.d.ts +17 -0
  43. package/dist/esm/proxy/ProjectRegistry.d.ts.map +1 -0
  44. package/dist/esm/proxy/ProjectRegistry.js +56 -0
  45. package/dist/esm/proxy/ProjectRegistry.js.map +1 -0
  46. package/package.json +2 -2
  47. package/dashboard/dist/assets/index-BoZujIjB.css +0 -1
  48. package/dashboard/dist/assets/index-C3X0fqmd.js.map +0 -1
@@ -4,7 +4,6 @@ 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
7
  exports.startBackgroundClipboardWatcher = startBackgroundClipboardWatcher;
9
8
  exports.performAutoHeal = performAutoHeal;
10
9
  const commander_1 = require("commander");
@@ -40,6 +39,8 @@ const SemanticScrubber_1 = __importDefault(require("../security/SemanticScrubber
40
39
  const CloudSyncClient_1 = __importDefault(require("../cloud/CloudSyncClient"));
41
40
  const ContextSqueezer_1 = require("../utils/ContextSqueezer");
42
41
  const AgentMultiplexer_1 = require("../proxy/AgentMultiplexer");
42
+ const ProjectRegistry_1 = require("../proxy/ProjectRegistry");
43
+ const ContextInjector_1 = require("../proxy/ContextInjector");
43
44
  const SavingsLedger_1 = require("../utils/SavingsLedger");
44
45
  const chroma = new chromadb_1.ChromaClient({ host: 'localhost', port: 8000 });
45
46
  const dummyEmbeddingFunction = { generate: (texts) => Promise.resolve([]) };
@@ -55,381 +56,184 @@ const PORT_FILE = path_1.default.join(CACHE_DIR, 'proxy.port');
55
56
  const STATS_FILE = path_1.default.join(CACHE_DIR, 'stats.json');
56
57
  const USAGE_FILE = path_1.default.join(CACHE_DIR, 'usage.json');
57
58
  const LICENSE_FILE = path_1.default.join(CACHE_DIR, 'license.json');
58
- const EVENT_LOG = [];
59
- const MAX_EVENTS = 100;
60
- // MCP Status tracking
61
- const MCP_STATUS = {
62
- lastActivity: Date.now(),
63
- toolCallCount: 0,
64
- status: 'idle'
65
- };
66
- function logEvent(event) {
67
- const ev = { id: Math.random().toString(36).slice(2, 11), timestamp: Date.now(), ...event };
68
- EVENT_LOG.push(ev);
69
- if (EVENT_LOG.length > MAX_EVENTS)
70
- EVENT_LOG.shift();
71
- // Track MCP activity
72
- if (event.type === 'mcp_tool_call') {
73
- MCP_STATUS.lastActivity = Date.now();
74
- MCP_STATUS.toolCallCount++;
75
- MCP_STATUS.status = 'active';
59
+ const CLIPBOARD_PID_FILE = path_1.default.join(CACHE_DIR, 'clipboard.pid');
60
+ const FREE_LIMIT = 300;
61
+ const VALIDATE_URL = 'https://lemma.nxus.studio/api/validate-license';
62
+ async function checkDependency(url) {
63
+ try {
64
+ const resp = await fetch(url, { method: 'HEAD', signal: AbortSignal.timeout(2000) });
65
+ return resp.ok;
76
66
  }
77
- // Print a terminal savings summary every 10 events
78
- if (EVENT_LOG.length > 0 && EVENT_LOG.length % 10 === 0) {
79
- const snap = SavingsLedger_1.savingsLedger.getSnapshot();
80
- if (snap.total.tokensSaved > 0) {
81
- console.log('\n' + SavingsLedger_1.savingsLedger.getTerminalSummary() + '\n');
82
- }
67
+ catch {
68
+ return false;
83
69
  }
84
70
  }
85
- const FREE_LIMIT = 500;
86
- const WARN_PCT = 0.8;
87
- const VALIDATE_URL = 'https://lemma.nxus.studio/api/v1/validate';
88
- if (!fs_1.default.existsSync(CACHE_DIR))
89
- fs_1.default.mkdirSync(CACHE_DIR, { recursive: true });
90
- // โ”€โ”€ Helpers โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
91
- async function readJson(file, fallback) {
71
+ function getPidByPort(port) {
92
72
  try {
93
- if (await fs_1.default.promises.access(file).then(() => true).catch(() => false)) {
94
- const content = await fs_1.default.promises.readFile(file, 'utf8');
95
- return JSON.parse(content);
96
- }
73
+ const result = require('child_process').execSync(`lsof -ti :${port} 2>/dev/null`, { encoding: 'utf8' }).trim();
74
+ return result || null;
75
+ }
76
+ catch {
77
+ return null;
97
78
  }
98
- catch { }
99
- return fallback;
100
79
  }
101
- const writeQueues = new Map();
102
- async function writeJson(file, data) {
103
- const chain = writeQueues.get(file) || Promise.resolve();
104
- const writeOperation = async () => {
105
- try {
106
- const tempFile = `${file}.${crypto_1.default.randomUUID ? crypto_1.default.randomUUID() : Math.random().toString(36).substring(2, 15)}.tmp`;
107
- await fs_1.default.promises.writeFile(tempFile, JSON.stringify(data, null, 2));
108
- await fs_1.default.promises.rename(tempFile, file);
80
+ function readJson(filePath, defaultVal) {
81
+ try {
82
+ if (fs_1.default.existsSync(filePath)) {
83
+ return JSON.parse(fs_1.default.readFileSync(filePath, 'utf8'));
109
84
  }
110
- catch { }
111
- };
112
- const nextChain = chain.then(writeOperation).catch(() => { });
113
- writeQueues.set(file, nextChain);
114
- return nextChain;
85
+ }
86
+ catch { }
87
+ return defaultVal;
115
88
  }
116
- function projectHash(name) {
117
- return crypto_1.default.createHash('sha1').update(name).digest('hex').slice(0, 12);
89
+ function writeJson(filePath, data) {
90
+ try {
91
+ fs_1.default.writeFileSync(filePath, JSON.stringify(data, null, 2));
92
+ }
93
+ catch { }
118
94
  }
119
95
  function getCollectionName(projectName) {
120
- let cleanName = projectName.replace(/[^a-zA-Z0-9._-]/g, '_');
121
- cleanName = cleanName.replace(/^[^a-zA-Z0-9]+/, '');
122
- cleanName = cleanName.replace(/[^a-zA-Z0-9]+$/, '');
123
- if (cleanName.length < 3) {
124
- cleanName = 'lemma_proj_' + cleanName;
125
- }
126
- return `lemma-cache-${cleanName}`;
96
+ return `lemma-cache-${projectName}`;
127
97
  }
128
98
  function detectProject() {
129
- const pkgPath = path_1.default.join(process.cwd(), 'package.json');
99
+ const cwd = process.cwd();
100
+ const pkgPath = path_1.default.join(cwd, 'package.json');
130
101
  if (fs_1.default.existsSync(pkgPath)) {
131
102
  try {
132
- const p = JSON.parse(fs_1.default.readFileSync(pkgPath, 'utf8'));
133
- if (p.name)
134
- return p.name;
103
+ const pkg = JSON.parse(fs_1.default.readFileSync(pkgPath, 'utf8'));
104
+ if (pkg.name)
105
+ return pkg.name;
135
106
  }
136
107
  catch { }
137
108
  }
138
- return path_1.default.basename(process.cwd());
109
+ return path_1.default.basename(cwd);
139
110
  }
140
- function ensureProjectDir(name) {
141
- const dir = path_1.default.join(CACHE_DIR, projectHash(name));
142
- if (!fs_1.default.existsSync(dir))
111
+ function ensureProjectDir(projectName) {
112
+ const hash = crypto_1.default.createHash('sha1').update(projectName).digest('hex').slice(0, 12);
113
+ const dir = path_1.default.join(CACHE_DIR, hash);
114
+ if (!fs_1.default.existsSync(dir)) {
143
115
  fs_1.default.mkdirSync(dir, { recursive: true });
144
- const marker = path_1.default.join(dir, 'project-name.txt');
145
- if (!fs_1.default.existsSync(marker))
146
- fs_1.default.writeFileSync(marker, name);
116
+ }
147
117
  return dir;
148
118
  }
149
- function getPidByPort(port) {
150
- const { execSync } = require('child_process');
151
- try {
152
- if (process.platform === 'win32') {
153
- const out = execSync(`netstat -ano | findstr :${port}`).toString();
154
- const match = out.match(/LISTENING\s+(\d+)/);
155
- return match ? match[1] : null;
156
- }
157
- else {
158
- const out = execSync(`lsof -t -i :${port}`).toString().trim();
159
- return out.split('\n')[0]; // Take first if multiple
160
- }
119
+ function ensureGitIgnore() {
120
+ const gitignorePath = path_1.default.join(process.cwd(), '.gitignore');
121
+ const lemmaRules = '\n# Lemma AI Gateway\n.lemma/\n';
122
+ if (fs_1.default.existsSync(gitignorePath)) {
123
+ const content = fs_1.default.readFileSync(gitignorePath, 'utf8');
124
+ if (content.includes('.lemma/'))
125
+ return false;
126
+ fs_1.default.appendFileSync(gitignorePath, lemmaRules);
161
127
  }
162
- catch {
163
- return null;
128
+ else {
129
+ fs_1.default.writeFileSync(gitignorePath, lemmaRules);
164
130
  }
131
+ return true;
165
132
  }
166
- async function checkDependency(url) {
133
+ async function ensureChromaRunning() {
167
134
  try {
168
- const res = await axios_1.default.get(url, { timeout: 1000, validateStatus: () => true });
169
- return res.status >= 200 && res.status < 500;
135
+ await chroma.heartbeat();
170
136
  }
171
137
  catch {
172
- return false;
173
- }
174
- }
175
- async function ensureChromaRunning() {
176
- const chromaPort = process.env.CHROMA_PORT || '8000';
177
- const chromaHost = process.env.CHROMA_HOST || 'http://localhost';
178
- const chromaUrl = `${chromaHost}:${chromaPort}`;
179
- const isChromaUp = await checkDependency(`${chromaUrl}/api/v1/heartbeat`);
180
- if (isChromaUp) {
181
- console.log(`โœ… \x1b[32m[ChromaDB]\x1b[0m ChromaDB is already running on port ${chromaPort}`);
182
- return true;
183
- }
184
- console.log(`\n๐Ÿ“ฆ \x1b[35m[ChromaDB]\x1b[0m ChromaDB is not running on port ${chromaPort}.`);
185
- console.log(`๐Ÿ”„ Starting ChromaDB in background...`);
186
- const chromaDataPath = path_1.default.join(CACHE_DIR, 'chroma_data');
187
- try {
138
+ console.log('โš ๏ธ ChromaDB is not running. Starting it...');
188
139
  const { spawn } = require('child_process');
189
- if (!fs_1.default.existsSync(chromaDataPath)) {
190
- fs_1.default.mkdirSync(chromaDataPath, { recursive: true });
191
- }
192
- const chromaProcess = spawn('chroma', ['run', '--path', chromaDataPath, '--port', chromaPort], {
140
+ const cp = spawn('chroma', ['run', '--path', path_1.default.join(CACHE_DIR, 'chroma_data')], {
193
141
  stdio: 'ignore',
194
- detached: true
142
+ detached: true,
195
143
  });
196
- chromaProcess.unref();
197
- // Wait up to 5 seconds for ChromaDB to start
198
- for (let i = 0; i < 5; i++) {
199
- await new Promise(r => setTimeout(r, 1000));
200
- const up = await checkDependency(`${chromaUrl}/api/v1/heartbeat`);
201
- if (up) {
202
- console.log(`โœ… \x1b[32m[ChromaDB]\x1b[0m ChromaDB has been started and is ready!`);
203
- return true;
204
- }
205
- }
206
- console.log(`โš ๏ธ \x1b[33m[ChromaDB]\x1b[0m ChromaDB was started but is taking longer to respond. It will continue starting in the background.`);
207
- return true;
208
- }
209
- catch (err) {
210
- console.error(`โŒ Failed to automatically start ChromaDB: ${err.message}`);
211
- console.error(`๐Ÿ‘‰ Please start it manually: chroma run --path ${chromaDataPath} --port ${chromaPort}`);
212
- return false;
213
- }
214
- }
215
- function ensureGitIgnore() {
216
- const gi = path_1.default.join(process.cwd(), '.gitignore');
217
- let added = false;
218
- try {
219
- let content = '';
220
- if (fs_1.default.existsSync(gi)) {
221
- content = fs_1.default.readFileSync(gi, 'utf8');
222
- }
223
- const appendLines = [];
224
- if (!content.includes('.lemma/')) {
225
- appendLines.push('# Lemma Context Logs', '.lemma/');
226
- added = true;
227
- }
228
- if (!content.includes('chroma_data/')) {
229
- appendLines.push('# Lemma Local Vector Database', 'chroma_data/');
230
- added = true;
231
- }
232
- if (appendLines.length > 0) {
233
- const prefix = content.endsWith('\n') || content === '' ? '' : '\n';
234
- fs_1.default.appendFileSync(gi, prefix + appendLines.join('\n') + '\n');
235
- }
144
+ cp.unref();
145
+ await new Promise(resolve => setTimeout(resolve, 3000));
236
146
  }
237
- catch { }
238
- return added;
239
147
  }
240
- const CLIPBOARD_PID_FILE = path_1.default.join(CACHE_DIR, 'clipboard.pid');
241
- function autoConfigureAll(projectName, cliOpts) {
242
- const project = projectName || detectProject();
243
- const HOME = process.env.HOME || process.env.USERPROFILE || '~';
244
- // Load configuration if it exists
245
- let configDisabled = false;
246
- let configEditor = true;
247
- let configShell = true;
248
- let configClaude = true;
249
- try {
250
- const configPath = path_1.default.join(process.cwd(), 'lemma.config.json');
251
- if (fs_1.default.existsSync(configPath)) {
252
- const rawConfig = fs_1.default.readFileSync(configPath, 'utf8');
253
- const config = JSON.parse(rawConfig);
254
- const systemConfig = config.system || {};
255
- const autoConfig = systemConfig.autoConfigure || {};
256
- if (autoConfig.disabled === true)
257
- configDisabled = true;
258
- if (autoConfig.editor === false)
259
- configEditor = false;
260
- if (autoConfig.shell === false)
261
- configShell = false;
262
- if (autoConfig.claude === false)
263
- configClaude = false;
264
- }
265
- }
266
- catch { }
267
- // Merge CLI overrides and config
268
- const shouldConfigure = cliOpts?.configure !== false && !configDisabled;
269
- if (!shouldConfigure) {
270
- console.log(`โญ๏ธ [Auto-Configure] Skipped auto-configuration (disabled via options or config).`);
148
+ function autoConfigureAll(project, opts) {
149
+ if (!opts.configure) {
150
+ console.log('โญ๏ธ Skipping auto-configuration (--no-configure)');
271
151
  return;
272
152
  }
273
- const runEditor = cliOpts?.editor !== false && configEditor;
274
- const runShell = cliOpts?.shell !== false && configShell;
275
- const runClaude = cliOpts?.claude !== false && configClaude;
276
- // 1. Claude Desktop Config
277
- if (runClaude) {
278
- try {
279
- const isWin = process.platform === 'win32';
280
- const claudePath = isWin
281
- ? path_1.default.join(process.env.APPDATA || '', 'Claude/claude_desktop_config.json')
282
- : path_1.default.join(HOME, 'Library/Application Support/Claude/claude_desktop_config.json');
283
- if (fs_1.default.existsSync(path_1.default.dirname(claudePath))) {
284
- let config = { mcpServers: {} };
285
- if (fs_1.default.existsSync(claudePath)) {
286
- try {
287
- config = JSON.parse(fs_1.default.readFileSync(claudePath, 'utf8'));
288
- }
289
- catch { }
290
- }
291
- if (!config.mcpServers)
292
- config.mcpServers = {};
293
- config.mcpServers.lemma = {
294
- command: 'npx',
295
- args: ['-y', '@nxuss/lemma', 'mcp'],
296
- env: { LEMMA_PROJECT: project }
297
- };
298
- fs_1.default.writeFileSync(claudePath, JSON.stringify(config, null, 2));
299
- console.log(`โœ… [Claude Desktop] Configured automatically in: ${claudePath} (Restart Claude to activate)`);
300
- }
301
- }
302
- catch (e) {
303
- console.log(`โš ๏ธ Could not auto-configure Claude Desktop: ${e.message}`);
153
+ const envFile = path_1.default.join(process.cwd(), '.env');
154
+ const lemmaConfig = `\n# Lemma AI Gateway Configuration\nOPENAI_BASE_URL=http://localhost:8081/v1\nLEMMA_PROJECT=${project}\n`;
155
+ if (fs_1.default.existsSync(envFile)) {
156
+ const content = fs_1.default.readFileSync(envFile, 'utf8');
157
+ if (!content.includes('OPENAI_BASE_URL')) {
158
+ fs_1.default.appendFileSync(envFile, lemmaConfig);
304
159
  }
305
160
  }
306
- else {
307
- console.log(`โญ๏ธ [Claude Desktop] Skip auto-configuration.`);
161
+ const mcpDir = path_1.default.join(process.cwd(), '.lemma', 'mcp');
162
+ if (!fs_1.default.existsSync(mcpDir)) {
163
+ fs_1.default.mkdirSync(mcpDir, { recursive: true });
308
164
  }
309
- // 2. Shell Profiles Environment Override
310
- if (runShell) {
311
- try {
312
- const profiles = [
313
- path_1.default.join(HOME, '.zshrc'),
314
- path_1.default.join(HOME, '.bashrc'),
315
- path_1.default.join(HOME, '.bash_profile'),
316
- path_1.default.join(HOME, '.profile')
317
- ];
318
- const lines = [
319
- '',
320
- '# Lemma AI Gateway Overrides',
321
- 'export OPENAI_BASE_URL="http://localhost:8081/v1"',
322
- 'export ANTHROPIC_BASE_URL="http://localhost:8081"',
323
- 'export LEMMA_PROJECT="' + project + '"',
324
- ''
325
- ].join('\n');
326
- for (const profile of profiles) {
327
- if (fs_1.default.existsSync(profile)) {
328
- try {
329
- const content = fs_1.default.readFileSync(profile, 'utf8');
330
- if (!content.includes('OPENAI_BASE_URL') && !content.includes('LEMMA_PROJECT')) {
331
- fs_1.default.appendFileSync(profile, lines);
332
- console.log(`โœ… [Shell Profile] Configured ${path_1.default.basename(profile)} with local redirect variables!`);
333
- }
334
- }
335
- catch { }
336
- }
337
- }
338
- }
339
- catch (e) {
340
- console.log(`โš ๏ธ Could not auto-configure shell profiles: ${e.message}`);
341
- }
165
+ console.log(`โœ… Auto-configured Lemma for [${project}]`);
166
+ }
167
+ const EVENT_LOG = [];
168
+ const MAX_EVENTS = 100;
169
+ // MCP Status tracking
170
+ const MCP_STATUS = {
171
+ lastActivity: Date.now(),
172
+ toolCallCount: 0,
173
+ status: 'idle'
174
+ };
175
+ function logEvent(event) {
176
+ const ev = { id: Math.random().toString(36).slice(2, 11), timestamp: Date.now(), ...event };
177
+ EVENT_LOG.push(ev);
178
+ if (EVENT_LOG.length > MAX_EVENTS)
179
+ EVENT_LOG.shift();
180
+ // Track MCP activity
181
+ if (event.type === 'mcp_tool_call') {
182
+ MCP_STATUS.lastActivity = Date.now();
183
+ MCP_STATUS.toolCallCount++;
184
+ MCP_STATUS.status = 'active';
342
185
  }
343
- else {
344
- console.log(`โญ๏ธ [Shell Profile] Skip auto-configuration.`);
186
+ // Print a terminal savings summary every 10 events
187
+ if (EVENT_LOG.length > 0 && EVENT_LOG.length % 10 === 0) {
188
+ const snap = SavingsLedger_1.savingsLedger.getSnapshot();
189
+ if (snap.total.tokensSaved > 0) {
190
+ console.log('\n' + SavingsLedger_1.savingsLedger.getTerminalSummary() + '\n');
191
+ }
345
192
  }
346
- // 3. Cursor, Windsurf & VS Code settings.json updates
347
- if (runEditor) {
193
+ }
194
+ const LEMMA_RULES_BLOCK = `
195
+ ๐Ÿง  Lemma MCP โ€” Autonomous Behaviors
196
+
197
+ I have Lemma MCP tools connected. I MUST use them proactively:
198
+
199
+ 1. When starting work on a project, call \`get_project_onboarding\` or read \`lemma://project/context\` to understand the architecture.
200
+ 2. Before answering technical questions, call \`search_memory\` for relevant past solutions.
201
+ 3. When examining a file, call \`get_telepathic_hints\` for relevant context.
202
+ 4. After solving a problem, call \`store_memory\` to persist the solution for future sessions.
203
+ 5. Use \`read_workspace_file\`, \`list_workspace_dir\`, \`search_workspace\` instead of asking the user to read or navigate files.
204
+ 6. Always call \`validate_patch_sandbox\` before writing patches.
205
+ 7. Read \`lemma://runtime/context\` first when the user reports a bug.
206
+ `;
207
+ function injectIdeRules(projectName) {
208
+ const content = `\n# ${projectName} โ€” ${LEMMA_RULES_BLOCK.trim()}\n`;
209
+ const rulesTargets = [
210
+ { name: 'Cursor', path: path_1.default.join(process.cwd(), '.cursorrules') },
211
+ { name: 'Cursor (v2)', path: path_1.default.join(process.cwd(), '.cursor/rules/lemma.mdc') },
212
+ { name: 'Windsurf', path: path_1.default.join(process.cwd(), '.windsurfrules') },
213
+ { name: 'Cline', path: path_1.default.join(process.cwd(), '.clinerules') },
214
+ ];
215
+ for (const target of rulesTargets) {
348
216
  try {
349
- const isWin = process.platform === 'win32';
350
- const isMac = process.platform === 'darwin';
351
- const candidates = [];
352
- if (isMac) {
353
- candidates.push({
354
- name: 'Cursor',
355
- path: path_1.default.join(HOME, 'Library/Application Support/Cursor/User/settings.json')
356
- });
357
- candidates.push({
358
- name: 'Windsurf',
359
- path: path_1.default.join(HOME, 'Library/Application Support/Windsurf/User/settings.json')
360
- });
361
- candidates.push({
362
- name: 'VS Code',
363
- path: path_1.default.join(HOME, 'Library/Application Support/Code/User/settings.json')
364
- });
365
- }
366
- else if (isWin) {
367
- const appData = process.env.APPDATA || '';
368
- candidates.push({
369
- name: 'Cursor',
370
- path: path_1.default.join(appData, 'Cursor/User/settings.json')
371
- });
372
- candidates.push({
373
- name: 'Windsurf',
374
- path: path_1.default.join(appData, 'Windsurf/User/settings.json')
375
- });
376
- candidates.push({
377
- name: 'VS Code',
378
- path: path_1.default.join(appData, 'Code/User/settings.json')
379
- });
217
+ const dir = path_1.default.dirname(target.path);
218
+ if (target.name === 'Cursor (v2)') {
219
+ if (!fs_1.default.existsSync(dir))
220
+ fs_1.default.mkdirSync(dir, { recursive: true });
380
221
  }
381
- else {
382
- candidates.push({
383
- name: 'Cursor',
384
- path: path_1.default.join(HOME, '.config/Cursor/User/settings.json')
385
- });
386
- candidates.push({
387
- name: 'Windsurf',
388
- path: path_1.default.join(HOME, '.config/Windsurf/User/settings.json')
389
- });
390
- candidates.push({
391
- name: 'VS Code',
392
- path: path_1.default.join(HOME, '.config/Code/User/settings.json')
393
- });
222
+ else if (!fs_1.default.existsSync(dir)) {
223
+ continue;
394
224
  }
395
- for (const cand of candidates) {
396
- const dir = path_1.default.dirname(cand.path);
397
- if (fs_1.default.existsSync(dir)) {
398
- let settings = {};
399
- if (fs_1.default.existsSync(cand.path)) {
400
- try {
401
- settings = JSON.parse(fs_1.default.readFileSync(cand.path, 'utf8'));
402
- }
403
- catch { }
404
- }
405
- let updated = false;
406
- // Settings configuration to route custom openai/anthropic models through the proxy
407
- const updates = {
408
- "openai.baseURL": "http://localhost:8081/v1",
409
- "openai.apiKey": "dummy-key-for-lemma",
410
- "anthropic.baseURL": "http://localhost:8081",
411
- "anthropic.apiKey": "dummy-key-for-lemma"
412
- };
413
- for (const [key, value] of Object.entries(updates)) {
414
- if (settings[key] !== value) {
415
- settings[key] = value;
416
- updated = true;
417
- }
418
- }
419
- if (updated) {
420
- fs_1.default.writeFileSync(cand.path, JSON.stringify(settings, null, 2));
421
- console.log(`โœ… [${cand.name}] Configured editor settings in ${cand.path} to route via local gateway!`);
422
- }
423
- }
225
+ if (fs_1.default.existsSync(target.path)) {
226
+ const existing = fs_1.default.readFileSync(target.path, 'utf8');
227
+ if (existing.includes('Lemma MCP'))
228
+ continue;
424
229
  }
230
+ fs_1.default.writeFileSync(target.path, content, { flag: 'a' });
231
+ console.log(`โœ… [${target.name}] Injected Lemma rules: ${target.path}`);
425
232
  }
426
- catch (e) {
427
- console.log(`โš ๏ธ Could not auto-configure editor settings: ${e.message}`);
233
+ catch {
234
+ console.log(`โš ๏ธ Could not inject rules for ${target.name}`);
428
235
  }
429
236
  }
430
- else {
431
- console.log(`โญ๏ธ [Editor Settings] Skip auto-configuration.`);
432
- }
433
237
  }
434
238
  function startBackgroundClipboardWatcher(cliOpts) {
435
239
  // Load configuration if it exists
@@ -958,6 +762,8 @@ class LemmaServer {
958
762
  this.sessions = new Map();
959
763
  this.timeline = [];
960
764
  this.lastFileContents = new Map();
765
+ this.registry = new ProjectRegistry_1.ProjectRegistry();
766
+ this.projectSessions = new Map();
961
767
  this.port = port;
962
768
  this.projectName = projectName || detectProject();
963
769
  this.projectDir = ensureProjectDir(this.projectName);
@@ -1003,6 +809,18 @@ class LemmaServer {
1003
809
  uptime: Math.floor(process.uptime())
1004
810
  });
1005
811
  });
812
+ // Project registration from MCP handshake
813
+ this.app.post('/api/handshake', (req, res) => {
814
+ const { projectPath, projectName, stack } = req.body;
815
+ if (projectPath && projectName) {
816
+ this.registry.register(projectPath, projectName, stack || '');
817
+ console.log(`๐Ÿ“‹ [Handshake] Project registered: "${projectName}" (${projectPath})`);
818
+ res.json({ ok: true });
819
+ }
820
+ else {
821
+ res.status(400).json({ error: 'projectPath and projectName required' });
822
+ }
823
+ });
1006
824
  this.app.get('/v1/models', (req, res) => {
1007
825
  res.json({ object: 'list', data: [
1008
826
  { id: 'gpt-4o', object: 'model', owned_by: 'lemma-proxy' },
@@ -1271,7 +1089,7 @@ class LemmaServer {
1271
1089
  totalTokensSaved: 0,
1272
1090
  providers: {}
1273
1091
  };
1274
- writeJson(STATS_FILE, this.stats).catch(() => { });
1092
+ writeJson(STATS_FILE, this.stats);
1275
1093
  }
1276
1094
  // Clear Chroma DB collection
1277
1095
  try {
@@ -1495,7 +1313,7 @@ class LemmaServer {
1495
1313
  s.providers['mcp'] = { hits: 0, misses: 0 };
1496
1314
  source === 'cache' ? s.providers['mcp'].hits++ : s.providers['mcp'].misses++;
1497
1315
  // Write updated stats back to disk immediately
1498
- writeJson(STATS_FILE, this.stats).catch(() => { });
1316
+ writeJson(STATS_FILE, this.stats);
1499
1317
  // Push to live telemetry logEvent array so the Dashboard event timeline updates instantly!
1500
1318
  logEvent({
1501
1319
  type: source === 'cache' ? 'cache:hit' : source,
@@ -1636,6 +1454,15 @@ class LemmaServer {
1636
1454
  }
1637
1455
  console.log(`๐ŸŒŒ \x1b[35m[Multiverse]\x1b[0m Captured micro-snapshot ${snapshot.id} (${changedFiles.length} file edits mapped).`);
1638
1456
  }
1457
+ detectActiveProject(req) {
1458
+ const mcpActive = this.registry.getActive();
1459
+ if (mcpActive)
1460
+ return mcpActive;
1461
+ const sniffed = (0, ContextInjector_1.sniffProjectFromMessages)(req.body.messages || [], this.registry);
1462
+ if (sniffed)
1463
+ return sniffed;
1464
+ return null;
1465
+ }
1639
1466
  async handleCompletion(req, res, provider) {
1640
1467
  const t0 = Date.now();
1641
1468
  const pro = await isPro();
@@ -1756,6 +1583,24 @@ Adjusted Answer:`;
1756
1583
  return res.json(unmaskedData);
1757
1584
  }
1758
1585
  }
1586
+ // โ”€โ”€ 3. Context Injection (for cache misses) โ”€โ”€
1587
+ const activeProject = this.detectActiveProject(req);
1588
+ if (activeProject) {
1589
+ const psKey = activeProject.path;
1590
+ if (!this.projectSessions.has(psKey)) {
1591
+ this.projectSessions.set(psKey, { alreadyInjected: false });
1592
+ }
1593
+ const session = this.projectSessions.get(psKey);
1594
+ const injectionLevel = (0, ContextInjector_1.shouldInject)(activeProject, req.body.messages, session);
1595
+ if (injectionLevel !== 'none') {
1596
+ const context = injectionLevel === 'deep'
1597
+ ? await (0, ContextInjector_1.buildDeepContext)(activeProject, this.port)
1598
+ : (0, ContextInjector_1.buildMinimalContext)(activeProject);
1599
+ req.body.messages.unshift({ role: 'system', content: context });
1600
+ session.alreadyInjected = true;
1601
+ console.log(`๐Ÿง  [Context] Injected ${injectionLevel} context for "${activeProject.name}"`);
1602
+ }
1603
+ }
1759
1604
  // โ”€โ”€ 2. Cache Miss Path โ”€โ”€
1760
1605
  const routingDecision = complexityRouter.evaluate(prompt, originalModel);
1761
1606
  const model = routingDecision.model;
@@ -1996,9 +1841,46 @@ Adjusted Answer:`;
1996
1841
  }
1997
1842
  }
1998
1843
  // โ”€โ”€ CLI โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
1999
- commander_1.program.name('lemma').description('Lemma Proxy CLI โ€” Intelligent AI Gateway').version(VERSION);
1844
+ function showWelcomeScreen() {
1845
+ console.log(`
1846
+ \x1b[1m\x1b[35m _ \x1b[0m
1847
+ \x1b[1m\x1b[35m | | ___ _ __ ___ __ _ \x1b[0m \x1b[1mLEMMA\x1b[0m \x1b[35mv${VERSION}\x1b[0m
1848
+ \x1b[1m\x1b[35m | | / _ \\ '_ \` _ \\ / _\` |\x1b[0m
1849
+ \x1b[1m\x1b[35m | |__| __/ | | | | | (_| |\x1b[0m Intelligent AI Gateway
1850
+ \x1b[1m\x1b[35m |_____\\___|_| |_| |_|\\__,_|\x1b[0m
1851
+
1852
+ \x1b[1mQuick Start\x1b[0m
1853
+ \x1b[33mlemma init\x1b[0m Initialize Lemma in this project
1854
+ \x1b[33mlemma start\x1b[0m Start the proxy server
1855
+ \x1b[33mlemma mcp\x1b[0m Start MCP Server for Cursor/Claude
1856
+
1857
+ \x1b[1mFree Features\x1b[0m (all included)
1858
+ ๐Ÿ”’ Privacy Firewall ๐Ÿ’พ Exact-match Cache
1859
+ ๐Ÿ”€ Complexity Router ๐Ÿ“ก Telepathic Clipboard
1860
+ ๐Ÿ“Š Basic Usage Stats ๐Ÿง  MCP Tools & Resources
1861
+
1862
+ \x1b[1mPro Features\x1b[0m (\x1b[35mlemma activate <key>\x1b[0m or \x1b[36mhttps://lemma.nxus.studio/upgrade\x1b[0m)
1863
+ ๐ŸŽฏ Semantic Vector Cache ๐ŸŒ Hive Mind Cloud Sync
1864
+ ๐Ÿ›ธ Autopilot Healer ๐Ÿฉบ Auto-Diagnose & Heal
1865
+ ๐ŸŒŒ Codebase Multiverse ๐Ÿ—๏ธ Architecture Onboarding
1866
+
1867
+ \x1b[90mRun \`lemma --help\` for all commands.\x1b[0m
1868
+ `);
1869
+ }
1870
+ commander_1.program
1871
+ .name('lemma')
1872
+ .description('Intelligent AI Gateway โ€” cache, route, and optimize AI requests')
1873
+ .version(VERSION)
1874
+ .addHelpText('after', `
1875
+ \x1b[1mPlan Comparison\x1b[0m
1876
+ \x1b[32mFree\x1b[0m 500 req/mo ยท Exact cache ยท Privacy firewall ยท MCP tools
1877
+ \x1b[35mPro\x1b[0m Unlimited ยท Semantic cache ยท Cloud sync ยท Autopilot ยท Heal
1878
+ \x1b[36mhttps://lemma.nxus.studio/upgrade\x1b[0m
1879
+
1880
+ \x1b[90mActivate Pro: lemma activate <key>\x1b[0m
1881
+ `);
2000
1882
  commander_1.program.command('mcp')
2001
- .description('Start the Lemma MCP Server (Model Context Protocol)')
1883
+ .description('Start the MCP Server for Cursor / Claude Desktop integration')
2002
1884
  .action(() => {
2003
1885
  const { spawn } = require('child_process');
2004
1886
  // Robust search candidates for both development (TS source) and compiled (JS production) environments.
@@ -2053,6 +1935,7 @@ commander_1.program.command('start')
2053
1935
  // Keep configurations up to date even if server was already started
2054
1936
  const runningProject = resp.data.project || detectProject();
2055
1937
  autoConfigureAll(runningProject, { configure: opts.configure });
1938
+ injectIdeRules(runningProject);
2056
1939
  startBackgroundClipboardWatcher({ clipboard: opts.clipboard });
2057
1940
  process.exit(0);
2058
1941
  }
@@ -2060,6 +1943,7 @@ commander_1.program.command('start')
2060
1943
  catch { }
2061
1944
  const startProject = opts.project || detectProject();
2062
1945
  autoConfigureAll(startProject, { configure: opts.configure });
1946
+ injectIdeRules(startProject);
2063
1947
  startBackgroundClipboardWatcher({ clipboard: opts.clipboard });
2064
1948
  // Automatically enable autopilot if stack is enabled and user is Pro
2065
1949
  const runAutopilot = opts.autopilot || opts.stack;
@@ -2272,8 +2156,12 @@ commander_1.program.command('init')
2272
2156
  }
2273
2157
  console.log('\n๐Ÿง  \x1b[35mAuto-Setup MCP and redirection overrides for AI IDEs...\x1b[0m');
2274
2158
  autoConfigureAll(project, { configure: opts.configure });
2275
- console.log('\nโœจ Project initialized for the Agentic Era!');
2276
- console.log('๐Ÿš€ Run "lemma start" to begin.\n');
2159
+ injectIdeRules(project);
2160
+ console.log('\nโœจ Project initialized for the Agentic Era!\n');
2161
+ console.log('\x1b[1mNext steps:\x1b[0m');
2162
+ console.log(' \x1b[33mlemma start\x1b[0m Start the proxy & unlock AI savings');
2163
+ console.log(' \x1b[33mlemma upgrade\x1b[0m See Pro features & get a key');
2164
+ console.log(' \x1b[33mlemma --help\x1b[0m All available commands\n');
2277
2165
  });
2278
2166
  // โ”€โ”€ Auto-Healing Diagnostics (Zero-Cost Suite) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
2279
2167
  async function performAutoHeal(apply) {
@@ -2443,7 +2331,7 @@ Ensure you return strictly JSON. Do not return any other text outside the JSON b
2443
2331
  }
2444
2332
  }
2445
2333
  commander_1.program.command('heal')
2446
- .description('Diagnose and auto-heal the latest local server crash from .lemma/live-context.md')
2334
+ .description('[Pro] Diagnose and auto-heal the latest local server crash from .lemma/live-context.md')
2447
2335
  .option('--apply', 'Apply the auto-generated code fix automatically', false)
2448
2336
  .action(async (opts) => {
2449
2337
  try {
@@ -2472,7 +2360,7 @@ commander_1.program.command('heal')
2472
2360
  }
2473
2361
  });
2474
2362
  commander_1.program.command('autopilot')
2475
- .description('Start the autonomous background compiler watcher & active healer')
2363
+ .description('[Pro] Start the autonomous background compiler watcher & active healer')
2476
2364
  .option('--dir <directory>', 'Directory to monitor for changes', 'src')
2477
2365
  .action(async (opts) => {
2478
2366
  try {
@@ -2497,6 +2385,26 @@ commander_1.program.command('autopilot')
2497
2385
  process.exit(1);
2498
2386
  }
2499
2387
  });
2388
+ commander_1.program.command('upgrade')
2389
+ .description('Learn about Pro features and get an activation key')
2390
+ .action(() => {
2391
+ console.log(`
2392
+ \x1b[1m\x1b[35mLemma Pro\x1b[0m โ€” Unlimited AI Gateway power
2393
+
2394
+ \x1b[1mPro features:\x1b[0m
2395
+ ๐ŸŽฏ \x1b[1mSemantic Vector Cache\x1b[0m โ€” Smarter cache hits via vector similarity
2396
+ ๐ŸŒ \x1b[1mHive Mind Cloud Sync\x1b[0m โ€” Cross-project cache & bug telepathy
2397
+ ๐Ÿ›ธ \x1b[1mAutopilot Healer\x1b[0m โ€” Autonomous background compiler watcher
2398
+ ๐Ÿฉบ \x1b[1mAuto-Diagnose & Heal\x1b[0m โ€” AI-driven crash analysis & patch
2399
+ ๐ŸŒŒ \x1b[1mCodebase Multiverse\x1b[0m โ€” AST time-travel & snapshots
2400
+ ๐Ÿ—๏ธ \x1b[1mArchitecture Onboarding\x1b[0m โ€” Zero-shot project understanding
2401
+ ๐Ÿ”“ \x1b[1mUnlimited requests\x1b[0m โ€” No monthly caps
2402
+
2403
+ \x1b[36m https://lemma.nxus.studio/upgrade\x1b[0m
2404
+
2405
+ Already have a key? Run: \x1b[33mlemma activate <key>\x1b[0m
2406
+ `);
2407
+ });
2500
2408
  commander_1.program.command('clipboard')
2501
2409
  .description('Start the Lemma Local Clipboard Telepathic Optimizer (auto-squeezes copied code blocks)')
2502
2410
  .option('--interval <ms>', 'Polling interval in milliseconds', '1000')
@@ -2514,6 +2422,11 @@ commander_1.program.command('clipboard')
2514
2422
  }
2515
2423
  });
2516
2424
  if (process.argv[1] && (process.argv[1].includes('lemma') || process.argv[1].endsWith('.cjs') || process.argv[1].endsWith('.js'))) {
2425
+ // Bare `lemma` with no args โ†’ show welcome screen
2426
+ if (process.argv.length <= 2) {
2427
+ showWelcomeScreen();
2428
+ process.exit(0);
2429
+ }
2517
2430
  commander_1.program.parse(process.argv);
2518
2431
  }
2519
2432
  //# sourceMappingURL=lemma-proxy.js.map