@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.
- package/README.md +1 -1
- package/dashboard/dist/assets/{index-B4TA31pA.js → index-CIlkFwDs.js} +98 -98
- package/dashboard/dist/assets/index-CIlkFwDs.js.map +1 -0
- package/dashboard/dist/index.html +1 -1
- package/dist/cjs/cli/lemma-proxy.d.ts +2 -0
- package/dist/cjs/cli/lemma-proxy.d.ts.map +1 -1
- package/dist/cjs/cli/lemma-proxy.js +243 -37
- package/dist/cjs/cli/lemma-proxy.js.map +1 -1
- package/dist/cjs/mcp/index.js +81 -0
- package/dist/cjs/mcp/index.js.map +1 -1
- package/dist/cjs/utils/ClipboardWatcher.d.ts.map +1 -1
- package/dist/cjs/utils/ClipboardWatcher.js +3 -0
- package/dist/cjs/utils/ClipboardWatcher.js.map +1 -1
- package/dist/cjs/utils/SavingsLedger.d.ts +52 -0
- package/dist/cjs/utils/SavingsLedger.d.ts.map +1 -0
- package/dist/cjs/utils/SavingsLedger.js +101 -0
- package/dist/cjs/utils/SavingsLedger.js.map +1 -0
- package/dist/cjs/utils/reportSavings.d.ts +24 -0
- package/dist/cjs/utils/reportSavings.d.ts.map +1 -0
- package/dist/cjs/utils/reportSavings.js +53 -0
- package/dist/cjs/utils/reportSavings.js.map +1 -0
- package/dist/esm/cli/lemma-proxy.d.ts +2 -0
- package/dist/esm/cli/lemma-proxy.d.ts.map +1 -1
- package/dist/esm/cli/lemma-proxy.js +241 -37
- package/dist/esm/cli/lemma-proxy.js.map +1 -1
- package/dist/esm/mcp/index.js +81 -0
- package/dist/esm/mcp/index.js.map +1 -1
- package/dist/esm/utils/ClipboardWatcher.d.ts.map +1 -1
- package/dist/esm/utils/ClipboardWatcher.js +3 -0
- package/dist/esm/utils/ClipboardWatcher.js.map +1 -1
- package/dist/esm/utils/SavingsLedger.d.ts +52 -0
- package/dist/esm/utils/SavingsLedger.d.ts.map +1 -0
- package/dist/esm/utils/SavingsLedger.js +98 -0
- package/dist/esm/utils/SavingsLedger.js.map +1 -0
- package/dist/esm/utils/reportSavings.d.ts +24 -0
- package/dist/esm/utils/reportSavings.d.ts.map +1 -0
- package/dist/esm/utils/reportSavings.js +47 -0
- package/dist/esm/utils/reportSavings.js.map +1 -0
- package/package.json +2 -2
- 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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lemma-proxy.d.ts","sourceRoot":"","sources":["../../../src/cli/lemma-proxy.ts"],"names":[],"mappings":";
|
|
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.
|
|
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
|
-
|
|
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
|
|
1245
|
-
|
|
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
|
});
|