@nxuss/lemma 0.5.3 → 0.5.5

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.
@@ -32,7 +32,7 @@ function getVersion() {
32
32
  catch { }
33
33
  }
34
34
  }
35
- return '0.5.3';
35
+ return '0.5.4';
36
36
  }
37
37
  const VERSION = getVersion();
38
38
  const ComplexityRouter_1 = __importDefault(require("../proxy/ComplexityRouter"));
@@ -217,141 +217,207 @@ function ensureGitIgnore() {
217
217
  return added;
218
218
  }
219
219
  const CLIPBOARD_PID_FILE = path_1.default.join(CACHE_DIR, 'clipboard.pid');
220
- function autoConfigureAll(projectName) {
220
+ function autoConfigureAll(projectName, cliOpts) {
221
221
  const project = projectName || detectProject();
222
222
  const HOME = process.env.HOME || process.env.USERPROFILE || '~';
223
- // 1. Claude Desktop Config
223
+ // Load configuration if it exists
224
+ let configDisabled = false;
225
+ let configEditor = true;
226
+ let configShell = true;
227
+ let configClaude = true;
224
228
  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'));
229
+ const configPath = path_1.default.join(process.cwd(), 'lemma.config.json');
230
+ if (fs_1.default.existsSync(configPath)) {
231
+ const rawConfig = fs_1.default.readFileSync(configPath, 'utf8');
232
+ const config = JSON.parse(rawConfig);
233
+ const systemConfig = config.system || {};
234
+ const autoConfig = systemConfig.autoConfigure || {};
235
+ if (autoConfig.disabled === true)
236
+ configDisabled = true;
237
+ if (autoConfig.editor === false)
238
+ configEditor = false;
239
+ if (autoConfig.shell === false)
240
+ configShell = false;
241
+ if (autoConfig.claude === false)
242
+ configClaude = false;
243
+ }
244
+ }
245
+ catch { }
246
+ // Merge CLI overrides and config
247
+ const shouldConfigure = cliOpts?.configure !== false && !configDisabled;
248
+ if (!shouldConfigure) {
249
+ console.log(`⏭️ [Auto-Configure] Skipped auto-configuration (disabled via options or config).`);
250
+ return;
251
+ }
252
+ const runEditor = cliOpts?.editor !== false && configEditor;
253
+ const runShell = cliOpts?.shell !== false && configShell;
254
+ const runClaude = cliOpts?.claude !== false && configClaude;
255
+ // 1. Claude Desktop Config
256
+ if (runClaude) {
257
+ try {
258
+ const isWin = process.platform === 'win32';
259
+ const claudePath = isWin
260
+ ? path_1.default.join(process.env.APPDATA || '', 'Claude/claude_desktop_config.json')
261
+ : path_1.default.join(HOME, 'Library/Application Support/Claude/claude_desktop_config.json');
262
+ if (fs_1.default.existsSync(path_1.default.dirname(claudePath))) {
263
+ let config = { mcpServers: {} };
264
+ if (fs_1.default.existsSync(claudePath)) {
265
+ try {
266
+ config = JSON.parse(fs_1.default.readFileSync(claudePath, 'utf8'));
267
+ }
268
+ catch { }
234
269
  }
235
- catch { }
270
+ if (!config.mcpServers)
271
+ config.mcpServers = {};
272
+ config.mcpServers.lemma = {
273
+ command: 'npx',
274
+ args: ['-y', '@nxuss/lemma', 'mcp'],
275
+ env: { LEMMA_PROJECT: project }
276
+ };
277
+ fs_1.default.writeFileSync(claudePath, JSON.stringify(config, null, 2));
278
+ console.log(`✅ [Claude Desktop] Configured automatically in: ${claudePath} (Restart Claude to activate)`);
236
279
  }
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)`);
280
+ }
281
+ catch (e) {
282
+ console.log(`⚠️ Could not auto-configure Claude Desktop: ${e.message}`);
246
283
  }
247
284
  }
248
- catch (e) {
249
- console.log(`⚠️ Could not auto-configure Claude Desktop: ${e.message}`);
285
+ else {
286
+ console.log(`⏭️ [Claude Desktop] Skip auto-configuration.`);
250
287
  }
251
288
  // 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!`);
289
+ if (runShell) {
290
+ try {
291
+ const profiles = [
292
+ path_1.default.join(HOME, '.zshrc'),
293
+ path_1.default.join(HOME, '.bashrc'),
294
+ path_1.default.join(HOME, '.bash_profile'),
295
+ path_1.default.join(HOME, '.profile')
296
+ ];
297
+ const lines = [
298
+ '',
299
+ '# Lemma AI Gateway Overrides',
300
+ 'export OPENAI_BASE_URL="http://localhost:8081/v1"',
301
+ 'export ANTHROPIC_BASE_URL="http://localhost:8081"',
302
+ 'export LEMMA_PROJECT="' + project + '"',
303
+ ''
304
+ ].join('\n');
305
+ for (const profile of profiles) {
306
+ if (fs_1.default.existsSync(profile)) {
307
+ try {
308
+ const content = fs_1.default.readFileSync(profile, 'utf8');
309
+ if (!content.includes('OPENAI_BASE_URL') && !content.includes('LEMMA_PROJECT')) {
310
+ fs_1.default.appendFileSync(profile, lines);
311
+ console.log(`✅ [Shell Profile] Configured ${path_1.default.basename(profile)} with local redirect variables!`);
312
+ }
274
313
  }
314
+ catch { }
275
315
  }
276
- catch { }
277
316
  }
278
317
  }
318
+ catch (e) {
319
+ console.log(`⚠️ Could not auto-configure shell profiles: ${e.message}`);
320
+ }
279
321
  }
280
- catch (e) {
281
- console.log(`⚠️ Could not auto-configure shell profiles: ${e.message}`);
322
+ else {
323
+ console.log(`⏭️ [Shell Profile] Skip auto-configuration.`);
282
324
  }
283
325
  // 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
+ if (runEditor) {
327
+ try {
328
+ const isWin = process.platform === 'win32';
329
+ const isMac = process.platform === 'darwin';
330
+ const candidates = [];
331
+ if (isMac) {
332
+ candidates.push({
333
+ name: 'Cursor',
334
+ path: path_1.default.join(HOME, 'Library/Application Support/Cursor/User/settings.json')
335
+ });
336
+ candidates.push({
337
+ name: 'Windsurf',
338
+ path: path_1.default.join(HOME, 'Library/Application Support/Windsurf/User/settings.json')
339
+ });
340
+ }
341
+ else if (isWin) {
342
+ const appData = process.env.APPDATA || '';
343
+ candidates.push({
344
+ name: 'Cursor',
345
+ path: path_1.default.join(appData, 'Cursor/User/settings.json')
346
+ });
347
+ candidates.push({
348
+ name: 'Windsurf',
349
+ path: path_1.default.join(appData, 'Windsurf/User/settings.json')
350
+ });
351
+ }
352
+ else {
353
+ candidates.push({
354
+ name: 'Cursor',
355
+ path: path_1.default.join(HOME, '.config/Cursor/User/settings.json')
356
+ });
357
+ candidates.push({
358
+ name: 'Windsurf',
359
+ path: path_1.default.join(HOME, '.config/Windsurf/User/settings.json')
360
+ });
361
+ }
362
+ for (const cand of candidates) {
363
+ const dir = path_1.default.dirname(cand.path);
364
+ if (fs_1.default.existsSync(dir)) {
365
+ let settings = {};
366
+ if (fs_1.default.existsSync(cand.path)) {
367
+ try {
368
+ settings = JSON.parse(fs_1.default.readFileSync(cand.path, 'utf8'));
369
+ }
370
+ catch { }
326
371
  }
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;
372
+ let updated = false;
373
+ // Settings configuration to route custom openai/anthropic models through the proxy
374
+ const updates = {
375
+ "openai.baseURL": "http://localhost:8081/v1",
376
+ "openai.apiKey": "dummy-key-for-lemma",
377
+ "anthropic.baseURL": "http://localhost:8081",
378
+ "anthropic.apiKey": "dummy-key-for-lemma"
379
+ };
380
+ for (const [key, value] of Object.entries(updates)) {
381
+ if (settings[key] !== value) {
382
+ settings[key] = value;
383
+ updated = true;
384
+ }
385
+ }
386
+ if (updated) {
387
+ fs_1.default.writeFileSync(cand.path, JSON.stringify(settings, null, 2));
388
+ console.log(`✅ [${cand.name}] Configured editor settings in ${cand.path} to route via local gateway!`);
341
389
  }
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
390
  }
347
391
  }
348
392
  }
393
+ catch (e) {
394
+ console.log(`⚠️ Could not auto-configure editor settings: ${e.message}`);
395
+ }
349
396
  }
350
- catch (e) {
351
- console.log(`⚠️ Could not auto-configure editor settings: ${e.message}`);
397
+ else {
398
+ console.log(`⏭️ [Editor Settings] Skip auto-configuration.`);
352
399
  }
353
400
  }
354
- function startBackgroundClipboardWatcher() {
401
+ function startBackgroundClipboardWatcher(cliOpts) {
402
+ // Load configuration if it exists
403
+ let configDisabled = false;
404
+ try {
405
+ const configPath = path_1.default.join(process.cwd(), 'lemma.config.json');
406
+ if (fs_1.default.existsSync(configPath)) {
407
+ const rawConfig = fs_1.default.readFileSync(configPath, 'utf8');
408
+ const config = JSON.parse(rawConfig);
409
+ const systemConfig = config.system || {};
410
+ const clipConfig = systemConfig.clipboardWatcher || {};
411
+ if (clipConfig.disabled === true)
412
+ configDisabled = true;
413
+ }
414
+ }
415
+ catch { }
416
+ const shouldWatch = cliOpts?.clipboard !== false && !configDisabled;
417
+ if (!shouldWatch) {
418
+ console.log(`⏭️ [Clipboard] Background clipboard watcher is disabled (via options or config).`);
419
+ return;
420
+ }
355
421
  try {
356
422
  if (fs_1.default.existsSync(CLIPBOARD_PID_FILE)) {
357
423
  const oldPid = fs_1.default.readFileSync(CLIPBOARD_PID_FILE, 'utf8');
@@ -470,16 +536,21 @@ async function recordStat(stats, project, fromCache, latencyMs, provider, tokens
470
536
  s.total++;
471
537
  s.totalLatency += latencyMs;
472
538
  fromCache ? s.hits++ : s.misses++;
473
- s.totalTokensSaved += (fromCache ? (tokensSaved || 2000) : (tokensSaved || 0));
539
+ const tkSaved = fromCache ? (tokensSaved || 2000) : (tokensSaved || 0);
540
+ s.totalTokensSaved += tkSaved;
474
541
  if (!s.providers[provider])
475
542
  s.providers[provider] = { hits: 0, misses: 0 };
476
543
  fromCache ? s.providers[provider].hits++ : s.providers[provider].misses++;
544
+ if (fromCache && tkSaved > 0) {
545
+ // Automatically report cache hits directly to the server session ledger for absolute sync consistency
546
+ SavingsLedger_1.savingsLedger.recordTokens('cache', tkSaved);
547
+ }
477
548
  logEvent({
478
549
  type: fromCache ? 'cache:hit' : 'cache:miss',
479
550
  project,
480
551
  latency: latencyMs,
481
552
  provider,
482
- tokens: fromCache ? (tokensSaved || 2000) : (tokensSaved || 0)
553
+ tokens: tkSaved
483
554
  });
484
555
  await writeJson(STATS_FILE, stats);
485
556
  }
@@ -852,6 +923,18 @@ class LemmaServer {
852
923
  const hitRate = stats.total > 0 ? stats.hits / stats.total : 0;
853
924
  const avgLat = stats.total > 0 ? stats.totalLatency / stats.total : 0;
854
925
  const secretsMasked = EVENT_LOG.filter(e => e.type === 'privacy:mask').length;
926
+ const ledgerSnap = SavingsLedger_1.savingsLedger.getSnapshot();
927
+ let activeAgents = 1; // Always include the Gateway itself
928
+ let totalAgents = 1;
929
+ // Query the stack orchestration API to merge active agents
930
+ try {
931
+ const stackMetrics = await axios_1.default.get('http://127.0.0.1:8083/api/metrics', { timeout: 100 });
932
+ if (stackMetrics.data) {
933
+ activeAgents += stackMetrics.data.activeAgents || 0;
934
+ totalAgents += stackMetrics.data.totalAgents || 0;
935
+ }
936
+ }
937
+ catch { }
855
938
  res.json({
856
939
  timestamp: new Date().toISOString(),
857
940
  totalRequests: stats.total,
@@ -859,30 +942,140 @@ class LemmaServer {
859
942
  cacheMisses: stats.misses,
860
943
  hitRate,
861
944
  averageLatency: avgLat,
862
- tokensSaved: stats.totalTokensSaved,
863
- costSaved: (stats.totalTokensSaved / 1000) * 0.02,
945
+ tokensSaved: ledgerSnap.total.tokensSaved,
946
+ costSaved: ledgerSnap.total.costSaved,
864
947
  secretsMasked: secretsMasked,
865
- activeAgents: 1,
866
- totalAgents: 1,
948
+ activeAgents,
949
+ totalAgents,
867
950
  uptime: process.uptime(),
868
951
  tier: (await isPro()) ? 'pro' : 'free'
869
952
  });
870
953
  });
871
954
  this.app.get('/api/agents', async (req, res) => {
955
+ // Load statistics for the Gateway itself
872
956
  const stats = await loadStats();
873
- res.json({ agents: [
874
- { id: 'lemma-gateway', name: 'Lemma Gateway', status: 'active', capabilities: ['Privacy Firewall', 'Semantic Cache', 'Complexity Router'], tasksCompleted: stats[this.projectName]?.total || 0, lastSeen: new Date().toISOString(), errorCount: 0, cacheHitRate: 0.85 }
875
- ] });
957
+ const statsProj = stats[this.projectName] || { total: 0, hits: 0 };
958
+ const total = statsProj.total || 0;
959
+ const hits = statsProj.hits || 0;
960
+ const gatewayAgent = {
961
+ id: 'lemma-gateway',
962
+ name: 'Lemma Gateway',
963
+ status: 'active',
964
+ capabilities: ['Privacy Firewall', 'Semantic Cache', 'Complexity Router'],
965
+ tasksCompleted: total,
966
+ lastSeen: new Date().toISOString(),
967
+ errorCount: 0,
968
+ cacheHitRate: total > 0 ? hits / total : 0
969
+ };
970
+ let agentsList = [gatewayAgent];
971
+ // Query the stack orchestration API to merge dynamic websocket agents if running
972
+ try {
973
+ const stackAgentsResponse = await axios_1.default.get('http://127.0.0.1:8083/api/agents', { timeout: 100 });
974
+ if (stackAgentsResponse.data && Array.isArray(stackAgentsResponse.data.agents)) {
975
+ const stackAgents = stackAgentsResponse.data.agents.filter((a) => a.id !== 'lemma-gateway');
976
+ agentsList = [...agentsList, ...stackAgents];
977
+ }
978
+ }
979
+ catch { }
980
+ res.json({ agents: agentsList });
876
981
  });
877
- this.app.get('/api/events', (req, res) => {
982
+ this.app.get('/api/events', async (req, res) => {
878
983
  const page = parseInt(req.query.page) || 1;
879
984
  const limit = parseInt(req.query.limit) || 50;
880
985
  const start = (page - 1) * limit;
881
- const events = EVENT_LOG.slice().reverse().slice(start, start + limit);
882
- res.json({ events, pagination: { total: EVENT_LOG.length, page, limit } });
986
+ // Get local proxy events (normalized schema matching what dashboard expects)
987
+ const localEvents = EVENT_LOG.map(e => ({
988
+ id: e.id,
989
+ timestamp: new Date(e.timestamp).toISOString(),
990
+ type: e.type, // e.g., 'cache:hit' or 'cache:miss' which client normalizes to 'cache_hit' or 'cache_miss'
991
+ latency: e.latency,
992
+ tokensSaved: e.tokens,
993
+ costSaved: e.tokens ? (e.tokens / 1000) * 0.002 : 0,
994
+ metadata: {
995
+ provider: e.provider,
996
+ project: e.project
997
+ }
998
+ }));
999
+ let mergedEvents = [...localEvents];
1000
+ // Query the stack orchestration API if running to merge stack events
1001
+ try {
1002
+ const stackEventsResponse = await axios_1.default.get(`http://127.0.0.1:8083/api/events?page=1&limit=100`, { timeout: 100 });
1003
+ if (stackEventsResponse.data && Array.isArray(stackEventsResponse.data.events)) {
1004
+ mergedEvents = [...mergedEvents, ...stackEventsResponse.data.events];
1005
+ }
1006
+ }
1007
+ catch { }
1008
+ // Sort merged events chronologically (newest first)
1009
+ mergedEvents.sort((a, b) => {
1010
+ const tA = new Date(a.timestamp).getTime();
1011
+ const tB = new Date(b.timestamp).getTime();
1012
+ return tB - tA;
1013
+ });
1014
+ const total = mergedEvents.length;
1015
+ const paginatedEvents = mergedEvents.slice(start, start + limit);
1016
+ res.json({
1017
+ events: paginatedEvents,
1018
+ pagination: {
1019
+ total,
1020
+ page,
1021
+ limit,
1022
+ hasMore: start + limit < total
1023
+ }
1024
+ });
883
1025
  });
884
- this.app.get('/api/cache-stats', (req, res) => {
885
- res.json({ topQueries: [] });
1026
+ this.app.get('/api/cache-stats', async (req, res) => {
1027
+ let topQueries = [];
1028
+ let totalCached = CACHE.size;
1029
+ // Try to query ChromaDB for semantic cache count
1030
+ try {
1031
+ const collection = await chroma.getOrCreateCollection({
1032
+ name: 'lemma-cache',
1033
+ embeddingFunction: dummyEmbeddingFunction,
1034
+ metadata: { "hnsw:space": "cosine" }
1035
+ });
1036
+ const count = await collection.count();
1037
+ totalCached += count;
1038
+ }
1039
+ catch { }
1040
+ // Build top queries from in-memory CACHE
1041
+ for (const [key, entry] of CACHE.entries()) {
1042
+ const hitCount = EVENT_LOG.filter(e => e.type === 'cache:hit' && e.provider === entry.provider).length || 1;
1043
+ const tokens = 2000;
1044
+ topQueries.push({
1045
+ queryHash: key.substring(0, 16),
1046
+ query: entry.input,
1047
+ hitCount,
1048
+ totalTokens: tokens * hitCount,
1049
+ costSaved: (tokens * hitCount / 1000) * 0.002,
1050
+ lastHit: new Date(entry.createdAt).toISOString()
1051
+ });
1052
+ }
1053
+ // Try to query the stack orchestration API if running to append stack cache stats
1054
+ try {
1055
+ const stackCache = await axios_1.default.get('http://127.0.0.1:8083/api/cache-stats', { timeout: 100 });
1056
+ if (stackCache.data) {
1057
+ totalCached += stackCache.data.totalCached || 0;
1058
+ if (Array.isArray(stackCache.data.topQueries)) {
1059
+ const stackTop = stackCache.data.topQueries.filter((q) => !topQueries.some(t => t.query === q.query));
1060
+ topQueries = [...topQueries, ...stackTop];
1061
+ }
1062
+ }
1063
+ }
1064
+ catch { }
1065
+ // Sort by hitCount desc
1066
+ topQueries.sort((a, b) => b.hitCount - a.hitCount);
1067
+ // Latency percentiles from EVENT_LOG
1068
+ const latencies = EVENT_LOG.filter(e => e.latency).map(e => e.latency).sort((a, b) => a - b);
1069
+ const latencyPercentiles = {
1070
+ p50: latencies[Math.floor(latencies.length * 0.5)] || 0,
1071
+ p95: latencies[Math.floor(latencies.length * 0.95)] || 0,
1072
+ p99: latencies[Math.floor(latencies.length * 0.99)] || 0,
1073
+ };
1074
+ res.json({
1075
+ topQueries: topQueries.slice(0, 10),
1076
+ latencyPercentiles,
1077
+ totalCached
1078
+ });
886
1079
  });
887
1080
  this.app.get('/api/search', async (req, res) => {
888
1081
  const query = req.query.q;
@@ -1018,7 +1211,6 @@ class LemmaServer {
1018
1211
  const hit = cacheGet(provider, prompt);
1019
1212
  if (hit) {
1020
1213
  const cacheTokensSaved = 2000;
1021
- SavingsLedger_1.savingsLedger.recordTokens('cache', cacheTokensSaved);
1022
1214
  await recordStat(this.stats, this.projectName, true, Date.now() - t0, provider, cacheTokensSaved);
1023
1215
  const unmaskedData = semanticScrubber.unmask(hit.data, tokenMap);
1024
1216
  if (isStream)
@@ -1206,6 +1398,43 @@ Adjusted Answer:`;
1206
1398
  }
1207
1399
  async callUpstream(body, provider) {
1208
1400
  let apiKey = provider === 'openai' ? process.env.OPENAI_API_KEY : (provider === 'anthropic' ? process.env.ANTHROPIC_API_KEY : process.env.GEMINI_API_KEY);
1401
+ // If the API key is an OpenAI key (sk-proj-) but requested for Anthropic or Gemini,
1402
+ // multiplex it internally to OpenAI gpt-4o-mini to allow live, cheap simulated provider routing.
1403
+ const isMockedProvider = apiKey && apiKey.startsWith('sk-') && provider !== 'openai';
1404
+ if (isMockedProvider) {
1405
+ console.log(`\n\x1b[36m🔀 [Gateway Router] Multiplexing B2B ${provider.toUpperCase()} request to OpenAI using key: ...${apiKey.substring(8, 14)}...\x1b[0m`);
1406
+ const payload = {
1407
+ model: 'gpt-4o-mini',
1408
+ messages: body.messages || [{ role: 'user', content: extractPrompt(body, provider) || 'Hello' }],
1409
+ stream: false // keep stream false for simulation simplicity
1410
+ };
1411
+ const baseURL = 'https://api.openai.com/v1/chat/completions';
1412
+ const resp = await axios_1.default.post(baseURL, payload, {
1413
+ headers: {
1414
+ Authorization: `Bearer ${apiKey}`,
1415
+ 'Content-Type': 'application/json'
1416
+ }
1417
+ });
1418
+ if (provider === 'anthropic') {
1419
+ const text = resp.data.choices?.[0]?.message?.content || '';
1420
+ return {
1421
+ id: `msg-${Date.now()}`,
1422
+ type: 'message',
1423
+ role: 'assistant',
1424
+ content: [{ type: 'text', text }],
1425
+ model: body.model || 'claude-3-5-sonnet'
1426
+ };
1427
+ }
1428
+ else if (provider === 'gemini') {
1429
+ const text = resp.data.choices?.[0]?.message?.content || '';
1430
+ return {
1431
+ candidates: [{
1432
+ content: { parts: [{ text }] },
1433
+ finishReason: 'STOP'
1434
+ }]
1435
+ };
1436
+ }
1437
+ }
1209
1438
  if (!apiKey) {
1210
1439
  console.log(`\n\x1b[33m⚠️ No API Key found for ${provider.toUpperCase()}. Falling back to local Ollama (Zero-Cost Mode)!\x1b[0m`);
1211
1440
  // If the requested model is a cloud model (e.g. gpt-4o, claude), map it to a local model like llama3
@@ -1351,6 +1580,8 @@ commander_1.program.command('start')
1351
1580
  .option('--project <name>', 'Override project name')
1352
1581
  .option('--stack', 'Launch full development stack (Chroma + Router + Dashboard + Autopilot)')
1353
1582
  .option('--autopilot', 'Launch background compiler watcher & healer', false)
1583
+ .option('--no-configure', 'Skip automatic configuration of editors, shell overrides, and Claude Desktop')
1584
+ .option('--no-clipboard', 'Skip launching background clipboard watcher')
1354
1585
  .action(async (opts) => {
1355
1586
  const port = parseInt(opts.port, 10);
1356
1587
  // Ensure ChromaDB is running for semantic caching/search
@@ -1362,15 +1593,15 @@ commander_1.program.command('start')
1362
1593
  console.log(`💡 Use 'lemma status' for details or 'lemma stop' to restart.\n`);
1363
1594
  // Keep configurations up to date even if server was already started
1364
1595
  const runningProject = resp.data.project || detectProject();
1365
- autoConfigureAll(runningProject);
1366
- startBackgroundClipboardWatcher();
1596
+ autoConfigureAll(runningProject, { configure: opts.configure });
1597
+ startBackgroundClipboardWatcher({ clipboard: opts.clipboard });
1367
1598
  process.exit(0);
1368
1599
  }
1369
1600
  }
1370
1601
  catch { }
1371
1602
  const startProject = opts.project || detectProject();
1372
- autoConfigureAll(startProject);
1373
- startBackgroundClipboardWatcher();
1603
+ autoConfigureAll(startProject, { configure: opts.configure });
1604
+ startBackgroundClipboardWatcher({ clipboard: opts.clipboard });
1374
1605
  // Automatically enable autopilot if stack is enabled and user is Pro
1375
1606
  const runAutopilot = opts.autopilot || opts.stack;
1376
1607
  if (runAutopilot) {
@@ -1546,7 +1777,8 @@ commander_1.program.command('activate <key>')
1546
1777
  });
1547
1778
  commander_1.program.command('init')
1548
1779
  .description('Initialize Lemma in the current project (auto-discovery)')
1549
- .action(async () => {
1780
+ .option('--no-configure', 'Skip automatic configuration of editors, shell overrides, and Claude Desktop')
1781
+ .action(async (opts) => {
1550
1782
  const project = detectProject();
1551
1783
  console.log(`\n🛠️ Initializing Lemma for [${project}]...`);
1552
1784
  // Ensure ChromaDB is running for semantic caching/search
@@ -1576,7 +1808,7 @@ commander_1.program.command('init')
1576
1808
  console.log('✅ Created .env with Lemma configuration');
1577
1809
  }
1578
1810
  console.log('\n🧠 \x1b[35mAuto-Setup MCP and redirection overrides for AI IDEs...\x1b[0m');
1579
- autoConfigureAll(project);
1811
+ autoConfigureAll(project, { configure: opts.configure });
1580
1812
  console.log('\n✨ Project initialized for the Agentic Era!');
1581
1813
  console.log('🚀 Run "lemma start" to begin.\n');
1582
1814
  });