@nxuss/lemma 0.5.1 → 0.5.2

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 (40) hide show
  1. package/README.md +1 -1
  2. package/dashboard/dist/assets/{index-B4TA31pA.js → index-CIlkFwDs.js} +98 -98
  3. package/dashboard/dist/assets/index-CIlkFwDs.js.map +1 -0
  4. package/dashboard/dist/index.html +1 -1
  5. package/dist/cjs/cli/lemma-proxy.d.ts +2 -0
  6. package/dist/cjs/cli/lemma-proxy.d.ts.map +1 -1
  7. package/dist/cjs/cli/lemma-proxy.js +243 -37
  8. package/dist/cjs/cli/lemma-proxy.js.map +1 -1
  9. package/dist/cjs/mcp/index.js +81 -0
  10. package/dist/cjs/mcp/index.js.map +1 -1
  11. package/dist/cjs/utils/ClipboardWatcher.d.ts.map +1 -1
  12. package/dist/cjs/utils/ClipboardWatcher.js +3 -0
  13. package/dist/cjs/utils/ClipboardWatcher.js.map +1 -1
  14. package/dist/cjs/utils/SavingsLedger.d.ts +52 -0
  15. package/dist/cjs/utils/SavingsLedger.d.ts.map +1 -0
  16. package/dist/cjs/utils/SavingsLedger.js +101 -0
  17. package/dist/cjs/utils/SavingsLedger.js.map +1 -0
  18. package/dist/cjs/utils/reportSavings.d.ts +24 -0
  19. package/dist/cjs/utils/reportSavings.d.ts.map +1 -0
  20. package/dist/cjs/utils/reportSavings.js +53 -0
  21. package/dist/cjs/utils/reportSavings.js.map +1 -0
  22. package/dist/esm/cli/lemma-proxy.d.ts +2 -0
  23. package/dist/esm/cli/lemma-proxy.d.ts.map +1 -1
  24. package/dist/esm/cli/lemma-proxy.js +241 -37
  25. package/dist/esm/cli/lemma-proxy.js.map +1 -1
  26. package/dist/esm/mcp/index.js +81 -0
  27. package/dist/esm/mcp/index.js.map +1 -1
  28. package/dist/esm/utils/ClipboardWatcher.d.ts.map +1 -1
  29. package/dist/esm/utils/ClipboardWatcher.js +3 -0
  30. package/dist/esm/utils/ClipboardWatcher.js.map +1 -1
  31. package/dist/esm/utils/SavingsLedger.d.ts +52 -0
  32. package/dist/esm/utils/SavingsLedger.d.ts.map +1 -0
  33. package/dist/esm/utils/SavingsLedger.js +98 -0
  34. package/dist/esm/utils/SavingsLedger.js.map +1 -0
  35. package/dist/esm/utils/reportSavings.d.ts +24 -0
  36. package/dist/esm/utils/reportSavings.d.ts.map +1 -0
  37. package/dist/esm/utils/reportSavings.js +47 -0
  38. package/dist/esm/utils/reportSavings.js.map +1 -0
  39. package/package.json +2 -2
  40. package/dashboard/dist/assets/index-B4TA31pA.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SavingsLedger.d.ts","sourceRoot":"","sources":["../../../src/utils/SavingsLedger.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,MAAM,aAAa,GAAG,WAAW,GAAG,gBAAgB,GAAG,cAAc,GAAG,mBAAmB,GAAG,OAAO,CAAC;AAE5G,MAAM,WAAW,WAAW;IAC1B,uDAAuD;IACvD,MAAM,EAAE,MAAM,CAAC;IACf,yCAAyC;IACzC,WAAW,EAAE,MAAM,CAAC;IACpB,8DAA8D;IAC9D,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,WAAW,CAAC;IACvB,cAAc,EAAE,WAAW,CAAC;IAC5B,YAAY,EAAE,WAAW,CAAC;IAC1B,iBAAiB,EAAE,WAAW,CAAC;IAC/B,KAAK,EAAE,WAAW,CAAC;IACnB,KAAK,EAAE,WAAW,CAAC;IACnB,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAKD,cAAM,aAAa;IACjB,OAAO,CAAC,OAAO,CAAqC;IACpD,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;;IAa1C;;;;;OAKG;IACH,MAAM,CAAC,MAAM,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI;IAY5E;;OAEG;IACH,YAAY,CAAC,MAAM,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,GAAG,IAAI;IAQ9D,wDAAwD;IACxD,WAAW,IAAI,eAAe;IAgB9B,8CAA8C;IAC9C,kBAAkB,IAAI,MAAM;IA+B5B,gDAAgD;IAChD,KAAK,IAAI,IAAI;CAKd;AAED,gDAAgD;AAChD,eAAO,MAAM,aAAa,eAAsB,CAAC;AACjD,eAAe,aAAa,CAAC"}
@@ -0,0 +1,101 @@
1
+ "use strict";
2
+ /**
3
+ * SavingsLedger — Singleton accumulator for token savings across all Lemma modules.
4
+ *
5
+ * Every module (ClipboardWatcher, ContextSqueezer, ComplexityRouter, SemanticCache)
6
+ * calls `ledger.record(source, charsBefore, charsAfter)` — no one knows about the dashboard.
7
+ *
8
+ * The proxy exposes the ledger via GET /api/savings-breakdown for the dashboard to consume.
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.savingsLedger = void 0;
12
+ /** Average blended cost per input token across GPT-4o / Claude Sonnet / Gemini Pro */
13
+ const AVG_COST_PER_TOKEN = 0.000008; // $0.008 per 1K tokens
14
+ class SavingsLedger {
15
+ constructor() {
16
+ this.sessionStartedAt = new Date().toISOString();
17
+ this.sources = {
18
+ clipboard: { events: 0, tokensSaved: 0, costSaved: 0 },
19
+ contextSqueeze: { events: 0, tokensSaved: 0, costSaved: 0 },
20
+ historyPrune: { events: 0, tokensSaved: 0, costSaved: 0 },
21
+ complexityRouting: { events: 0, tokensSaved: 0, costSaved: 0 },
22
+ cache: { events: 0, tokensSaved: 0, costSaved: 0 },
23
+ };
24
+ }
25
+ /**
26
+ * Record a savings event by char counts (automatically converts to token estimate).
27
+ * @param source Which module recorded the savings
28
+ * @param charsBefore Original char count before compression/routing
29
+ * @param charsAfter Resulting char count after compression/routing
30
+ */
31
+ record(source, charsBefore, charsAfter) {
32
+ const charsSaved = Math.max(0, charsBefore - charsAfter);
33
+ if (charsSaved === 0)
34
+ return;
35
+ const tokensSaved = Math.floor(charsSaved / 4);
36
+ const costSaved = tokensSaved * AVG_COST_PER_TOKEN;
37
+ this.sources[source].events++;
38
+ this.sources[source].tokensSaved += tokensSaved;
39
+ this.sources[source].costSaved += costSaved;
40
+ }
41
+ /**
42
+ * Record a savings event directly in tokens (for sources that already know their token count).
43
+ */
44
+ recordTokens(source, tokensSaved) {
45
+ if (tokensSaved <= 0)
46
+ return;
47
+ const costSaved = tokensSaved * AVG_COST_PER_TOKEN;
48
+ this.sources[source].events++;
49
+ this.sources[source].tokensSaved += tokensSaved;
50
+ this.sources[source].costSaved += costSaved;
51
+ }
52
+ /** Returns a full snapshot including computed totals */
53
+ getSnapshot() {
54
+ const total = { events: 0, tokensSaved: 0, costSaved: 0 };
55
+ for (const src of Object.values(this.sources)) {
56
+ total.events += src.events;
57
+ total.tokensSaved += src.tokensSaved;
58
+ total.costSaved += src.costSaved;
59
+ }
60
+ return {
61
+ ...this.sources,
62
+ total,
63
+ sessionStartedAt: this.sessionStartedAt,
64
+ };
65
+ }
66
+ /** Returns a pretty terminal summary block */
67
+ getTerminalSummary() {
68
+ const snap = this.getSnapshot();
69
+ const fmt = (n) => n.toLocaleString('en-US');
70
+ const fmtUSD = (n) => new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', minimumFractionDigits: 4 }).format(n);
71
+ const rows = [
72
+ ['📋 Clipboard Squeeze', snap.clipboard.tokensSaved, snap.clipboard.costSaved],
73
+ ['🗜️ Context Squeeze ', snap.contextSqueeze.tokensSaved, snap.contextSqueeze.costSaved],
74
+ ['📜 History Prune ', snap.historyPrune.tokensSaved, snap.historyPrune.costSaved],
75
+ ['🔀 Model Routing ', snap.complexityRouting.tokensSaved, snap.complexityRouting.costSaved],
76
+ ['⚡ Semantic Cache ', snap.cache.tokensSaved, snap.cache.costSaved],
77
+ ];
78
+ const lines = [
79
+ '┌──────────────────────────────────────────────────────┐',
80
+ '│ 💰 Lemma Savings Report (this session) │',
81
+ '├──────────────────────────────────────────────────────┤',
82
+ ...rows
83
+ .filter(([, tokens]) => tokens > 0)
84
+ .map(([label, tokens, cost]) => `│ ${label} ${fmt(tokens).padStart(7)} tokens ${fmtUSD(cost).padStart(10)} │`),
85
+ '│ ────────────────────────────────────────────────── │',
86
+ `│ TOTAL ${fmt(snap.total.tokensSaved).padStart(7)} tokens ${fmtUSD(snap.total.costSaved).padStart(10)} │`,
87
+ '└──────────────────────────────────────────────────────┘',
88
+ ];
89
+ return lines.join('\n');
90
+ }
91
+ /** Reset all counters (e.g., on cache clear) */
92
+ reset() {
93
+ for (const key of Object.keys(this.sources)) {
94
+ this.sources[key] = { events: 0, tokensSaved: 0, costSaved: 0 };
95
+ }
96
+ }
97
+ }
98
+ /** Global singleton — import this everywhere */
99
+ exports.savingsLedger = new SavingsLedger();
100
+ exports.default = exports.savingsLedger;
101
+ //# sourceMappingURL=SavingsLedger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SavingsLedger.js","sourceRoot":"","sources":["../../../src/utils/SavingsLedger.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * reportSavings — Fire-and-forget helper to report token savings to the proxy's SavingsLedger.
3
+ *
4
+ * Used by processes that run separately from the proxy (e.g., MCP server, CLI tools).
5
+ * The proxy accumulates all savings in its in-memory SavingsLedger and serves them to the dashboard.
6
+ */
7
+ type SavingsSource = 'clipboard' | 'contextSqueeze' | 'historyPrune' | 'complexityRouting' | 'cache';
8
+ interface ReportByChars {
9
+ source: SavingsSource;
10
+ charsBefore: number;
11
+ charsAfter: number;
12
+ }
13
+ interface ReportByTokens {
14
+ source: SavingsSource;
15
+ tokens: number;
16
+ }
17
+ type ReportPayload = ReportByChars | ReportByTokens;
18
+ /**
19
+ * Report savings to the proxy's SavingsLedger via HTTP POST.
20
+ * Fire-and-forget — never throws, never blocks.
21
+ */
22
+ export declare function reportSavings(payload: ReportPayload): void;
23
+ export {};
24
+ //# sourceMappingURL=reportSavings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reportSavings.d.ts","sourceRoot":"","sources":["../../../src/utils/reportSavings.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,KAAK,aAAa,GAAG,WAAW,GAAG,gBAAgB,GAAG,cAAc,GAAG,mBAAmB,GAAG,OAAO,CAAC;AAErG,UAAU,aAAa;IACrB,MAAM,EAAE,aAAa,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,UAAU,cAAc;IACtB,MAAM,EAAE,aAAa,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,KAAK,aAAa,GAAG,aAAa,GAAG,cAAc,CAAC;AAapD;;;GAGG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI,CAsB1D"}
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ /**
3
+ * reportSavings — Fire-and-forget helper to report token savings to the proxy's SavingsLedger.
4
+ *
5
+ * Used by processes that run separately from the proxy (e.g., MCP server, CLI tools).
6
+ * The proxy accumulates all savings in its in-memory SavingsLedger and serves them to the dashboard.
7
+ */
8
+ var __importDefault = (this && this.__importDefault) || function (mod) {
9
+ return (mod && mod.__esModule) ? mod : { "default": mod };
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.reportSavings = reportSavings;
13
+ const fs_1 = __importDefault(require("fs"));
14
+ const path_1 = __importDefault(require("path"));
15
+ const http_1 = __importDefault(require("http"));
16
+ function getProxyPort() {
17
+ try {
18
+ const HOME = process.env.HOME || process.env.USERPROFILE || '~';
19
+ const portFile = path_1.default.join(HOME, '.lemma-cache/proxy.port');
20
+ if (fs_1.default.existsSync(portFile)) {
21
+ return fs_1.default.readFileSync(portFile, 'utf8').trim();
22
+ }
23
+ }
24
+ catch { }
25
+ return '8081';
26
+ }
27
+ /**
28
+ * Report savings to the proxy's SavingsLedger via HTTP POST.
29
+ * Fire-and-forget — never throws, never blocks.
30
+ */
31
+ function reportSavings(payload) {
32
+ try {
33
+ const port = getProxyPort();
34
+ const body = JSON.stringify(payload);
35
+ const req = http_1.default.request({
36
+ hostname: '127.0.0.1',
37
+ port: parseInt(port),
38
+ path: '/api/savings-record',
39
+ method: 'POST',
40
+ headers: {
41
+ 'Content-Type': 'application/json',
42
+ 'Content-Length': Buffer.byteLength(body),
43
+ },
44
+ });
45
+ req.on('error', () => { }); // Silently ignore if proxy isn't running
46
+ req.write(body);
47
+ req.end();
48
+ }
49
+ catch {
50
+ // Never throw — savings reporting is non-critical
51
+ }
52
+ }
53
+ //# sourceMappingURL=reportSavings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reportSavings.js","sourceRoot":"","sources":["../../../src/utils/reportSavings.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;;;AAoCH,sCAsBC;AAxDD,4CAAoB;AACpB,gDAAwB;AACxB,gDAAwB;AAiBxB,SAAS,YAAY;IACnB,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,GAAG,CAAC;QAChE,MAAM,QAAQ,GAAG,cAAI,CAAC,IAAI,CAAC,IAAI,EAAE,yBAAyB,CAAC,CAAC;QAC5D,IAAI,YAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC5B,OAAO,YAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;QAClD,CAAC;IACH,CAAC;IAAC,MAAM,CAAC,CAAA,CAAC;IACV,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;GAGG;AACH,SAAgB,aAAa,CAAC,OAAsB;IAClD,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,YAAY,EAAE,CAAC;QAC5B,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QAErC,MAAM,GAAG,GAAG,cAAI,CAAC,OAAO,CAAC;YACvB,QAAQ,EAAE,WAAW;YACrB,IAAI,EAAE,QAAQ,CAAC,IAAI,CAAC;YACpB,IAAI,EAAE,qBAAqB;YAC3B,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,cAAc,EAAE,kBAAkB;gBAClC,gBAAgB,EAAE,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC;aAC1C;SACF,CAAC,CAAC;QAEH,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC,CAAC,yCAAyC;QACpE,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAChB,GAAG,CAAC,GAAG,EAAE,CAAC;IACZ,CAAC;IAAC,MAAM,CAAC;QACP,kDAAkD;IACpD,CAAC;AACH,CAAC"}
@@ -1,4 +1,6 @@
1
1
  #!/usr/bin/env node
2
+ export declare function autoConfigureAll(projectName?: string): void;
3
+ export declare function startBackgroundClipboardWatcher(): void;
2
4
  export declare function performAutoHeal(apply: boolean): Promise<{
3
5
  success: boolean;
4
6
  message: string;
@@ -1 +1 @@
1
- {"version":3,"file":"lemma-proxy.d.ts","sourceRoot":"","sources":["../../../src/cli/lemma-proxy.ts"],"names":[],"mappings":";AAw3CA,wBAAsB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CA6KhL"}
1
+ {"version":3,"file":"lemma-proxy.d.ts","sourceRoot":"","sources":["../../../src/cli/lemma-proxy.ts"],"names":[],"mappings":";AAoNA,wBAAgB,gBAAgB,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CA0I3D;AAED,wBAAgB,+BAA+B,IAAI,IAAI,CAyCtD;AAmsCD,wBAAsB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CA6KhL"}
@@ -24,7 +24,7 @@ function getVersion() {
24
24
  catch { }
25
25
  }
26
26
  }
27
- return '0.4.6';
27
+ return '0.5.2';
28
28
  }
29
29
  const VERSION = getVersion();
30
30
  import ComplexityRouter from '../proxy/ComplexityRouter';
@@ -32,6 +32,7 @@ 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 { savingsLedger } from '../utils/SavingsLedger';
35
36
  const chroma = new ChromaClient({ host: 'localhost', port: 8000 });
36
37
  const dummyEmbeddingFunction = { generate: (texts) => Promise.resolve([]) };
37
38
  const complexityRouter = new ComplexityRouter();
@@ -53,6 +54,13 @@ function logEvent(event) {
53
54
  EVENT_LOG.push(ev);
54
55
  if (EVENT_LOG.length > MAX_EVENTS)
55
56
  EVENT_LOG.shift();
57
+ // Print a terminal savings summary every 10 events
58
+ if (EVENT_LOG.length > 0 && EVENT_LOG.length % 10 === 0) {
59
+ const snap = savingsLedger.getSnapshot();
60
+ if (snap.total.tokensSaved > 0) {
61
+ console.log('\n' + savingsLedger.getTerminalSummary() + '\n');
62
+ }
63
+ }
56
64
  }
57
65
  const FREE_LIMIT = 500;
58
66
  const WARN_PCT = 0.8;
@@ -193,6 +201,182 @@ function ensureGitIgnore() {
193
201
  catch { }
194
202
  return added;
195
203
  }
204
+ const CLIPBOARD_PID_FILE = path.join(CACHE_DIR, 'clipboard.pid');
205
+ export function autoConfigureAll(projectName) {
206
+ const project = projectName || detectProject();
207
+ const HOME = process.env.HOME || process.env.USERPROFILE || '~';
208
+ // 1. Claude Desktop Config
209
+ try {
210
+ const isWin = process.platform === 'win32';
211
+ const claudePath = isWin
212
+ ? path.join(process.env.APPDATA || '', 'Claude/claude_desktop_config.json')
213
+ : path.join(HOME, 'Library/Application Support/Claude/claude_desktop_config.json');
214
+ if (fs.existsSync(path.dirname(claudePath))) {
215
+ let config = { mcpServers: {} };
216
+ if (fs.existsSync(claudePath)) {
217
+ try {
218
+ config = JSON.parse(fs.readFileSync(claudePath, 'utf8'));
219
+ }
220
+ catch { }
221
+ }
222
+ if (!config.mcpServers)
223
+ config.mcpServers = {};
224
+ config.mcpServers.lemma = {
225
+ command: 'npx',
226
+ args: ['-y', '@nxuss/lemma', 'mcp'],
227
+ env: { LEMMA_PROJECT: project }
228
+ };
229
+ fs.writeFileSync(claudePath, JSON.stringify(config, null, 2));
230
+ console.log(`✅ [Claude Desktop] Configured automatically! (Restart Claude to activate)`);
231
+ }
232
+ }
233
+ catch (e) {
234
+ console.log(`⚠️ Could not auto-configure Claude Desktop: ${e.message}`);
235
+ }
236
+ // 2. Shell Profiles Environment Override
237
+ try {
238
+ const profiles = [
239
+ path.join(HOME, '.zshrc'),
240
+ path.join(HOME, '.bashrc'),
241
+ path.join(HOME, '.bash_profile'),
242
+ path.join(HOME, '.profile')
243
+ ];
244
+ const lines = [
245
+ '',
246
+ '# Lemma AI Gateway Overrides',
247
+ 'export OPENAI_BASE_URL="http://localhost:8081/v1"',
248
+ 'export ANTHROPIC_BASE_URL="http://localhost:8081"',
249
+ 'export LEMMA_PROJECT="' + project + '"',
250
+ ''
251
+ ].join('\n');
252
+ for (const profile of profiles) {
253
+ if (fs.existsSync(profile)) {
254
+ try {
255
+ const content = fs.readFileSync(profile, 'utf8');
256
+ if (!content.includes('OPENAI_BASE_URL') && !content.includes('LEMMA_PROJECT')) {
257
+ fs.appendFileSync(profile, lines);
258
+ console.log(`✅ [Shell Profile] Configured ${path.basename(profile)} with local redirect variables!`);
259
+ }
260
+ }
261
+ catch { }
262
+ }
263
+ }
264
+ }
265
+ catch (e) {
266
+ console.log(`⚠️ Could not auto-configure shell profiles: ${e.message}`);
267
+ }
268
+ // 3. Cursor & Windsurf settings.json updates
269
+ try {
270
+ const isWin = process.platform === 'win32';
271
+ const isMac = process.platform === 'darwin';
272
+ const candidates = [];
273
+ if (isMac) {
274
+ candidates.push({
275
+ name: 'Cursor',
276
+ path: path.join(HOME, 'Library/Application Support/Cursor/User/settings.json')
277
+ });
278
+ candidates.push({
279
+ name: 'Windsurf',
280
+ path: path.join(HOME, 'Library/Application Support/Windsurf/User/settings.json')
281
+ });
282
+ }
283
+ else if (isWin) {
284
+ const appData = process.env.APPDATA || '';
285
+ candidates.push({
286
+ name: 'Cursor',
287
+ path: path.join(appData, 'Cursor/User/settings.json')
288
+ });
289
+ candidates.push({
290
+ name: 'Windsurf',
291
+ path: path.join(appData, 'Windsurf/User/settings.json')
292
+ });
293
+ }
294
+ else {
295
+ candidates.push({
296
+ name: 'Cursor',
297
+ path: path.join(HOME, '.config/Cursor/User/settings.json')
298
+ });
299
+ candidates.push({
300
+ name: 'Windsurf',
301
+ path: path.join(HOME, '.config/Windsurf/User/settings.json')
302
+ });
303
+ }
304
+ for (const cand of candidates) {
305
+ const dir = path.dirname(cand.path);
306
+ if (fs.existsSync(dir)) {
307
+ let settings = {};
308
+ if (fs.existsSync(cand.path)) {
309
+ try {
310
+ settings = JSON.parse(fs.readFileSync(cand.path, 'utf8'));
311
+ }
312
+ catch { }
313
+ }
314
+ let updated = false;
315
+ // Settings configuration to route custom openai/anthropic models through the proxy
316
+ const updates = {
317
+ "openai.baseURL": "http://localhost:8081/v1",
318
+ "openai.apiKey": "dummy-key-for-lemma",
319
+ "anthropic.baseURL": "http://localhost:8081",
320
+ "anthropic.apiKey": "dummy-key-for-lemma"
321
+ };
322
+ for (const [key, value] of Object.entries(updates)) {
323
+ if (settings[key] !== value) {
324
+ settings[key] = value;
325
+ updated = true;
326
+ }
327
+ }
328
+ if (updated) {
329
+ fs.writeFileSync(cand.path, JSON.stringify(settings, null, 2));
330
+ console.log(`✅ [${cand.name}] Configured editor settings to route via local gateway!`);
331
+ }
332
+ }
333
+ }
334
+ }
335
+ catch (e) {
336
+ console.log(`⚠️ Could not auto-configure editor settings: ${e.message}`);
337
+ }
338
+ }
339
+ export function startBackgroundClipboardWatcher() {
340
+ try {
341
+ if (fs.existsSync(CLIPBOARD_PID_FILE)) {
342
+ const oldPid = fs.readFileSync(CLIPBOARD_PID_FILE, 'utf8');
343
+ try {
344
+ process.kill(parseInt(oldPid, 10), 0); // Check if process is still alive
345
+ console.log(`✅ [Clipboard] Watcher is already running in background (PID: ${oldPid})`);
346
+ return;
347
+ }
348
+ catch {
349
+ // Process is dead, clean up pid file
350
+ try {
351
+ fs.unlinkSync(CLIPBOARD_PID_FILE);
352
+ }
353
+ catch { }
354
+ }
355
+ }
356
+ console.log(`🔄 Starting Clipboard Watcher in background...`);
357
+ const { spawn } = require('child_process');
358
+ // Resolve path of lemma command or script
359
+ const scriptPath = fs.existsSync(path.join(__dirname, 'dist', 'cjs', 'cli', 'lemma-proxy.js'))
360
+ ? path.join(__dirname, 'dist', 'cjs', 'cli', 'lemma-proxy.js')
361
+ : (fs.existsSync(path.join(__dirname, 'lemma-proxy.ts')) ? path.join(__dirname, 'lemma-proxy.ts') : __filename);
362
+ const isTs = scriptPath.endsWith('.ts');
363
+ const cmd = isTs ? 'npx' : 'node';
364
+ const args = isTs ? ['tsx', scriptPath, 'clipboard'] : [scriptPath, 'clipboard'];
365
+ const cp = spawn(cmd, args, {
366
+ stdio: 'ignore',
367
+ detached: true,
368
+ env: { ...process.env }
369
+ });
370
+ cp.unref();
371
+ if (cp.pid) {
372
+ fs.writeFileSync(CLIPBOARD_PID_FILE, String(cp.pid));
373
+ console.log(`✅ [Clipboard] Watcher has been started (PID: ${cp.pid})`);
374
+ }
375
+ }
376
+ catch (err) {
377
+ console.error(`❌ Failed to automatically start Clipboard Watcher in background: ${err.message}`);
378
+ }
379
+ }
196
380
  // ── License helpers ────────────────────────────────────────────────────────────
197
381
  async function loadLicense() {
198
382
  return readJson(LICENSE_FILE, { isPro: false });
@@ -697,6 +881,7 @@ class LemmaServer {
697
881
  const count = CACHE.size;
698
882
  CACHE.clear();
699
883
  EVENT_LOG.length = 0; // Clear live event log
884
+ savingsLedger.reset(); // Reset savings counters
700
885
  // Reset and persist statistics for the current project
701
886
  if (this.stats[this.projectName]) {
702
887
  this.stats[this.projectName] = {
@@ -711,6 +896,25 @@ class LemmaServer {
711
896
  }
712
897
  res.json({ success: true, clearedEntries: count });
713
898
  });
899
+ // Savings breakdown by source for the dashboard
900
+ this.app.get('/api/savings-breakdown', (req, res) => {
901
+ res.json(savingsLedger.getSnapshot());
902
+ });
903
+ // Cross-process savings reporter — MCP and other processes POST here
904
+ this.app.post('/api/savings-record', (req, res) => {
905
+ const { source, charsBefore, charsAfter, tokens } = req.body || {};
906
+ const validSources = ['clipboard', 'contextSqueeze', 'historyPrune', 'complexityRouting', 'cache'];
907
+ if (!source || !validSources.includes(source)) {
908
+ return res.status(400).json({ error: 'Invalid source' });
909
+ }
910
+ if (typeof tokens === 'number' && tokens > 0) {
911
+ savingsLedger.recordTokens(source, tokens);
912
+ }
913
+ else if (typeof charsBefore === 'number' && typeof charsAfter === 'number') {
914
+ savingsLedger.record(source, charsBefore, charsAfter);
915
+ }
916
+ res.json({ ok: true, snapshot: savingsLedger.getSnapshot().total });
917
+ });
714
918
  this.app.get('/dashboard/*', (req, res) => res.sendFile(path.join(this.dashboardPath, 'index.html')));
715
919
  }
716
920
  }
@@ -738,6 +942,13 @@ class LemmaServer {
738
942
  const model = routingDecision.model;
739
943
  if (req.body.model)
740
944
  req.body.model = model;
945
+ // Record complexity routing savings when model is downgraded
946
+ if (routingDecision.complexity === 'low' && model !== originalModel) {
947
+ // Estimated token savings: cheap model costs ~5x less, so avg prompt of ~800 tokens saves ~640 tokens
948
+ const estimatedPromptTokens = Math.floor(prompt.length / 4);
949
+ const routingSavings = Math.floor(estimatedPromptTokens * 0.8); // conservative 80% cost reduction
950
+ savingsLedger.recordTokens('complexityRouting', routingSavings);
951
+ }
741
952
  // Apply the Agent Multiplexer to local-verify the prompt in parallel
742
953
  const enrichedPrompt = await agentMultiplexer.multiplexPrompt(prompt, model);
743
954
  // Resolve conversation session key and retrieve/create the fileHistory map
@@ -760,6 +971,7 @@ class LemmaServer {
760
971
  const squeezedPrompt = squeezeResult.squeezed;
761
972
  const compressionSavings = squeezeResult.originalSize - squeezeResult.squeezedSize;
762
973
  if (compressionSavings > 0) {
974
+ savingsLedger.record('contextSqueeze', squeezeResult.originalSize, squeezeResult.squeezedSize);
763
975
  console.log(`⚡ [Squeezer] Compressed prompt from ${squeezeResult.originalSize} to ${squeezeResult.squeezedSize} chars (Saved ${(squeezeResult.compressionRatio * 100).toFixed(1)}% context tokens!)`);
764
976
  }
765
977
  if (provider === 'openai' || provider === 'anthropic') {
@@ -769,6 +981,9 @@ class LemmaServer {
769
981
  const pruneResult = pruneHistoryMessages(req.body.messages);
770
982
  req.body.messages = pruneResult.messages;
771
983
  historyTokensSaved = pruneResult.tokensSavedEstimate + Math.floor(compressionSavings / 4);
984
+ if (pruneResult.tokensSavedEstimate > 0) {
985
+ savingsLedger.recordTokens('historyPrune', pruneResult.tokensSavedEstimate);
986
+ }
772
987
  }
773
988
  }
774
989
  else if (provider === 'gemini') {
@@ -781,7 +996,9 @@ class LemmaServer {
781
996
  }
782
997
  const hit = cacheGet(provider, prompt);
783
998
  if (hit) {
784
- await recordStat(this.stats, this.projectName, true, Date.now() - t0, provider, 2000 + historyTokensSaved);
999
+ const cacheTokensSaved = 2000 + historyTokensSaved;
1000
+ savingsLedger.recordTokens('cache', cacheTokensSaved);
1001
+ await recordStat(this.stats, this.projectName, true, Date.now() - t0, provider, cacheTokensSaved);
785
1002
  const unmaskedData = semanticScrubber.unmask(hit.data, tokenMap);
786
1003
  if (isStream)
787
1004
  return simulateHitStream(res, unmaskedData, provider, model, tier, 1.0);
@@ -1046,10 +1263,17 @@ program.command('start')
1046
1263
  if (resp.data && resp.data.status === 'ok') {
1047
1264
  console.log(`\n⚠️ Lemma Proxy is already running on port ${port} (Project: ${resp.data.project})`);
1048
1265
  console.log(`💡 Use 'lemma status' for details or 'lemma stop' to restart.\n`);
1266
+ // Keep configurations up to date even if server was already started
1267
+ const runningProject = resp.data.project || detectProject();
1268
+ autoConfigureAll(runningProject);
1269
+ startBackgroundClipboardWatcher();
1049
1270
  process.exit(0);
1050
1271
  }
1051
1272
  }
1052
1273
  catch { }
1274
+ const startProject = opts.project || detectProject();
1275
+ autoConfigureAll(startProject);
1276
+ startBackgroundClipboardWatcher();
1053
1277
  // Automatically enable autopilot if stack is enabled and user is Pro
1054
1278
  const runAutopilot = opts.autopilot || opts.stack;
1055
1279
  if (runAutopilot) {
@@ -1148,6 +1372,19 @@ program.command('stop')
1148
1372
  }
1149
1373
  if (fs.existsSync(PID_FILE))
1150
1374
  fs.unlinkSync(PID_FILE);
1375
+ // Stop background Clipboard Watcher if active
1376
+ try {
1377
+ if (fs.existsSync(CLIPBOARD_PID_FILE)) {
1378
+ const cbPid = fs.readFileSync(CLIPBOARD_PID_FILE, 'utf8');
1379
+ try {
1380
+ process.kill(parseInt(cbPid, 10), 'SIGTERM');
1381
+ console.log(`✅ Stopped background Clipboard Watcher (PID: ${cbPid})`);
1382
+ }
1383
+ catch { }
1384
+ fs.unlinkSync(CLIPBOARD_PID_FILE);
1385
+ }
1386
+ }
1387
+ catch { }
1151
1388
  });
1152
1389
  program.command('status')
1153
1390
  .description('Check proxy status')
@@ -1241,41 +1478,8 @@ program.command('init')
1241
1478
  fs.writeFileSync(envFile, lemmaConfig);
1242
1479
  console.log('✅ Created .env with Lemma configuration');
1243
1480
  }
1244
- console.log('\n🧠 \x1b[35mAuto-Setup MCP for AI IDEs?\x1b[0m');
1245
- console.log(' Lemma can automatically configure Claude Desktop and open settings for Cursor/Windsurf.');
1246
- const { exec } = require('child_process');
1247
- const isWin = process.platform === 'win32';
1248
- const claudePath = isWin
1249
- ? path.join(process.env.APPDATA || '', 'Claude/claude_desktop_config.json')
1250
- : path.join(process.env.HOME || '~', 'Library/Application Support/Claude/claude_desktop_config.json');
1251
- if (fs.existsSync(path.dirname(claudePath))) {
1252
- try {
1253
- let config = { mcpServers: {} };
1254
- if (fs.existsSync(claudePath)) {
1255
- config = JSON.parse(fs.readFileSync(claudePath, 'utf8'));
1256
- }
1257
- if (!config.mcpServers)
1258
- config.mcpServers = {};
1259
- config.mcpServers.lemma = {
1260
- command: 'npx',
1261
- args: ['-y', '@nxuss/lemma', 'mcp'],
1262
- env: { LEMMA_PROJECT: project }
1263
- };
1264
- fs.writeFileSync(claudePath, JSON.stringify(config, null, 2));
1265
- console.log(`✅ \x1b[32mClaude Desktop configured automatically!\x1b[0m (Restart Claude to activate)`);
1266
- }
1267
- catch (e) {
1268
- console.log('⚠️ Could not auto-configure Claude Desktop. No worries, you can do it manually.');
1269
- }
1270
- }
1271
- console.log('🚀 Opening IDE configuration pages...');
1272
- const urls = [
1273
- 'https://docs.anthropic.com/en/docs/agents-and-tools/mcp#using-mcp-in-claude-desktop',
1274
- 'https://cursor.com',
1275
- 'https://codeium.com/windsurf'
1276
- ];
1277
- const opener = process.platform === 'darwin' ? 'open' : (process.platform === 'win32' ? 'start' : 'xdg-open');
1278
- urls.forEach(url => exec(`${opener} ${url}`));
1481
+ console.log('\n🧠 \x1b[35mAuto-Setup MCP and redirection overrides for AI IDEs...\x1b[0m');
1482
+ autoConfigureAll(project);
1279
1483
  console.log('\n✨ Project initialized for the Agentic Era!');
1280
1484
  console.log('🚀 Run "lemma start" to begin.\n');
1281
1485
  });