mnueron 0.6.1 → 0.6.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/LICENSE-OVERVIEW.md +138 -0
- package/dist/cli.js +58 -1
- package/dist/cli.js.map +1 -1
- package/dist/dashboard/server.js +29 -0
- package/dist/dashboard/server.js.map +1 -1
- package/dist/import/file.js +103 -0
- package/dist/import/file.js.map +1 -0
- package/dist/index.js +36 -2
- package/dist/index.js.map +1 -1
- package/dist/plugins/loader.js +2 -0
- package/dist/plugins/loader.js.map +1 -1
- package/dist/savings/pricing.js +75 -0
- package/dist/savings/pricing.js.map +1 -0
- package/dist/savings/recall-event.js +82 -0
- package/dist/savings/recall-event.js.map +1 -0
- package/dist/savings/recall-logger.js +148 -0
- package/dist/savings/recall-logger.js.map +1 -0
- package/dist/savings/summary.js +118 -0
- package/dist/savings/summary.js.map +1 -0
- package/dist/store/local.js +283 -226
- package/dist/store/local.js.map +1 -1
- package/dist/store/remote.js +37 -0
- package/dist/store/remote.js.map +1 -1
- package/dist/tools.js +281 -5
- package/dist/tools.js.map +1 -1
- package/package.json +66 -63
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider-agnostic recall logger for the MCP server.
|
|
3
|
+
*
|
|
4
|
+
* Why this exists: in hosted mode (MNUERON_API_URL + MNUERON_API_TOKEN set)
|
|
5
|
+
* the MCP server's `provider` is RemoteProvider — every memory_recall call
|
|
6
|
+
* goes over HTTP to a hosted backend. That hosted backend may or may not
|
|
7
|
+
* capture recall_events, and even if it does, the local user has no way to
|
|
8
|
+
* see "how many times my Claude Desktop recalled from mnueron today"
|
|
9
|
+
* unless we ALSO log it locally.
|
|
10
|
+
*
|
|
11
|
+
* In local mode, LocalProvider used to capture inside .search(). That's now
|
|
12
|
+
* removed in favor of this logger so we have a single capture point for
|
|
13
|
+
* BOTH modes. No double-counting, no provider-dependent behavior.
|
|
14
|
+
*
|
|
15
|
+
* The logger opens its own better-sqlite3 connection to cfg.dbPath (which
|
|
16
|
+
* is defined regardless of mode — defaults to ~/.mnueron/memories.db). Runs
|
|
17
|
+
* RECALL_EVENTS_DDL on construction so the table exists even on a fresh
|
|
18
|
+
* install. Fail-open on every insert: a bad log row never breaks recall.
|
|
19
|
+
*/
|
|
20
|
+
import Database from 'better-sqlite3';
|
|
21
|
+
import { RECALL_EVENTS_DDL, buildRecallEvent, approximateTokens, } from './recall-event.js';
|
|
22
|
+
export class RecallLogger {
|
|
23
|
+
db;
|
|
24
|
+
client;
|
|
25
|
+
defaultModelId;
|
|
26
|
+
insertStmt = null;
|
|
27
|
+
nsBaselineStmt = null;
|
|
28
|
+
globalBaselineStmt = null;
|
|
29
|
+
ready = false;
|
|
30
|
+
warned = false;
|
|
31
|
+
constructor(opts) {
|
|
32
|
+
this.client = opts.client ?? null;
|
|
33
|
+
this.defaultModelId = opts.defaultModelId ?? null;
|
|
34
|
+
try {
|
|
35
|
+
this.db = new Database(opts.dbPath);
|
|
36
|
+
this.db.exec(RECALL_EVENTS_DDL);
|
|
37
|
+
this.insertStmt = this.db.prepare(`INSERT INTO recall_events
|
|
38
|
+
(id, created_at, namespace, query_hash, tokens_returned,
|
|
39
|
+
tokens_baseline_namespace, tokens_baseline_capped, model_id,
|
|
40
|
+
context_limit, client)
|
|
41
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
42
|
+
this.nsBaselineStmt = this.db.prepare(`SELECT COALESCE(SUM(LENGTH(content)), 0) AS chars
|
|
43
|
+
FROM memories
|
|
44
|
+
WHERE namespace = ?`);
|
|
45
|
+
this.globalBaselineStmt = this.db.prepare(`SELECT COALESCE(SUM(LENGTH(content)), 0) AS chars FROM memories`);
|
|
46
|
+
this.ready = true;
|
|
47
|
+
process.stderr.write(`[mnueron/recall-logger] ready dbPath=${opts.dbPath} client=${this.client}\n`);
|
|
48
|
+
}
|
|
49
|
+
catch (e) {
|
|
50
|
+
// Don't crash the MCP server if SQLite is unavailable for whatever
|
|
51
|
+
// reason (file locked, disk full, etc.). The MCP service stays up;
|
|
52
|
+
// dashboard just won't show new events until the underlying issue
|
|
53
|
+
// resolves.
|
|
54
|
+
this.db = undefined;
|
|
55
|
+
this.warnOnce(`init failed: ${e instanceof Error ? e.message : e}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Write one row for a completed memory_recall call. Fail-open. Called
|
|
60
|
+
* AFTER the underlying provider.search() succeeds so we never log a row
|
|
61
|
+
* for a failed recall (cleaner aggregates).
|
|
62
|
+
*/
|
|
63
|
+
logRecall(input, returned) {
|
|
64
|
+
process.stderr.write(`[mnueron/recall-logger] logRecall called: ns=${input.namespace ?? '-'} returned=${returned.length} ready=${this.ready}\n`);
|
|
65
|
+
if (!this.ready || !this.insertStmt) {
|
|
66
|
+
process.stderr.write(`[mnueron/recall-logger] SKIPPED (not ready)\n`);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
const tokens_returned = returned.reduce((sum, m) => sum + approximateTokens(m.content ?? ''), 0);
|
|
71
|
+
// Baseline = sum of all content tokens in the namespace (or global if
|
|
72
|
+
// no namespace). LENGTH() over text is cheap; sub-millisecond even on
|
|
73
|
+
// tens of thousands of rows.
|
|
74
|
+
const ns = input.namespace ?? null;
|
|
75
|
+
let baseline_chars = 0;
|
|
76
|
+
if (ns && this.nsBaselineStmt) {
|
|
77
|
+
const row = this.nsBaselineStmt.get(ns);
|
|
78
|
+
baseline_chars = row?.chars ?? 0;
|
|
79
|
+
}
|
|
80
|
+
else if (this.globalBaselineStmt) {
|
|
81
|
+
const row = this.globalBaselineStmt.get();
|
|
82
|
+
baseline_chars = row?.chars ?? 0;
|
|
83
|
+
}
|
|
84
|
+
const tokens_baseline_namespace = Math.ceil(baseline_chars / 4);
|
|
85
|
+
const event = {
|
|
86
|
+
namespace: ns,
|
|
87
|
+
query: input.query,
|
|
88
|
+
tokens_returned,
|
|
89
|
+
tokens_baseline_namespace,
|
|
90
|
+
model_id: input.model_id ?? this.defaultModelId ?? null,
|
|
91
|
+
client: this.client,
|
|
92
|
+
};
|
|
93
|
+
const ev = buildRecallEvent(event);
|
|
94
|
+
this.insertStmt.run(ev.id, ev.created_at, ev.namespace, ev.query_hash, ev.tokens_returned, ev.tokens_baseline_namespace, ev.tokens_baseline_capped, ev.model_id, ev.context_limit, ev.client);
|
|
95
|
+
process.stderr.write(`[mnueron/recall-logger] INSERTED id=${ev.id.slice(0, 8)} tokens_returned=${ev.tokens_returned} tokens_saved=${ev.tokens_baseline_capped - ev.tokens_returned}\n`);
|
|
96
|
+
}
|
|
97
|
+
catch (e) {
|
|
98
|
+
this.warnOnce(`insert failed: ${e instanceof Error ? e.message : e}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
close() {
|
|
102
|
+
if (this.ready && this.db) {
|
|
103
|
+
try {
|
|
104
|
+
this.db.close();
|
|
105
|
+
}
|
|
106
|
+
catch { /* ignore */ }
|
|
107
|
+
this.ready = false;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
warnOnce(msg) {
|
|
111
|
+
if (this.warned)
|
|
112
|
+
return;
|
|
113
|
+
this.warned = true;
|
|
114
|
+
// stderr only — stdout is the JSON-RPC channel.
|
|
115
|
+
process.stderr.write(`[mnueron/recall-logger] ${msg}\n`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Detect the calling client. MCP doesn't pass a User-Agent equivalent on
|
|
120
|
+
* every request, so we use this precedence:
|
|
121
|
+
*
|
|
122
|
+
* 1. MNUERON_CLIENT env var (explicit override — users can set this in
|
|
123
|
+
* claude_desktop_config.json's env section)
|
|
124
|
+
* 2. Best-guess from launch context (CODEX_ env, CURSOR_TRACE_ID, etc.)
|
|
125
|
+
* 3. "mnueron-mcp" as a safe generic default
|
|
126
|
+
*
|
|
127
|
+
* Returns a lowercase slug-shaped string the dashboard can group on.
|
|
128
|
+
*/
|
|
129
|
+
export function detectMcpClient() {
|
|
130
|
+
const explicit = process.env.MNUERON_CLIENT;
|
|
131
|
+
if (explicit && explicit.trim().length > 0)
|
|
132
|
+
return explicit.trim().toLowerCase();
|
|
133
|
+
// Cursor sets CURSOR_TRACE_ID on its MCP child processes.
|
|
134
|
+
if (process.env.CURSOR_TRACE_ID || process.env.CURSOR_USER)
|
|
135
|
+
return 'cursor';
|
|
136
|
+
// Claude Code (CLI) sets CLAUDE_CODE_SSE_PORT or CODEX_HOME.
|
|
137
|
+
if (process.env.CLAUDE_CODE_SSE_PORT || process.env.CODEX_HOME)
|
|
138
|
+
return 'claude-code';
|
|
139
|
+
// Cline / VS Code extensions usually inherit VSCODE_PID.
|
|
140
|
+
if (process.env.VSCODE_PID || process.env.VSCODE_INJECTION) {
|
|
141
|
+
return process.env.CLINE_INSTALLED ? 'cline' : 'vscode';
|
|
142
|
+
}
|
|
143
|
+
// Windsurf identifies via CODEIUM_API_URL or similar.
|
|
144
|
+
if (process.env.CODEIUM_API_URL)
|
|
145
|
+
return 'windsurf';
|
|
146
|
+
return 'mnueron-mcp';
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=recall-logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recall-logger.js","sourceRoot":"","sources":["../../src/savings/recall-logger.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Aggregation queries for the recall savings dashboard.
|
|
3
|
+
*
|
|
4
|
+
* Reads recall_events (capture lives in recall-event.ts) and produces
|
|
5
|
+
* the shape the dashboard widget consumes. Time-windowed counts +
|
|
6
|
+
* top-N savings + a daily sparkline series.
|
|
7
|
+
*
|
|
8
|
+
* Pure-SQLite — no LLM calls, fast enough to call on every dashboard
|
|
9
|
+
* page load. If the recall_events table doesn't exist yet (very fresh
|
|
10
|
+
* install), every query returns zeros so the UI renders cleanly.
|
|
11
|
+
*/
|
|
12
|
+
import { tokensToDollars, DEFAULT_MODEL_ID } from './pricing.js';
|
|
13
|
+
const WINDOW_MS = {
|
|
14
|
+
day: 24 * 60 * 60 * 1000,
|
|
15
|
+
week: 7 * 24 * 60 * 60 * 1000,
|
|
16
|
+
month: 30 * 24 * 60 * 60 * 1000,
|
|
17
|
+
};
|
|
18
|
+
function windowSinceMs(window) {
|
|
19
|
+
if (window === 'all')
|
|
20
|
+
return 0;
|
|
21
|
+
return Date.now() - WINDOW_MS[window];
|
|
22
|
+
}
|
|
23
|
+
function tableExists(db, name) {
|
|
24
|
+
const row = db
|
|
25
|
+
.prepare(`SELECT 1 AS ok FROM sqlite_master WHERE type='table' AND name = ?`)
|
|
26
|
+
.get(name);
|
|
27
|
+
return Boolean(row?.ok);
|
|
28
|
+
}
|
|
29
|
+
function emptySummary(window, modelId) {
|
|
30
|
+
return {
|
|
31
|
+
window,
|
|
32
|
+
recalls_count: 0,
|
|
33
|
+
tokens_returned: 0,
|
|
34
|
+
tokens_baseline_capped: 0,
|
|
35
|
+
tokens_saved: 0,
|
|
36
|
+
dollars_saved: 0,
|
|
37
|
+
ide_crashes_avoided: 0,
|
|
38
|
+
default_model_id: modelId,
|
|
39
|
+
trend: [],
|
|
40
|
+
top_saves: [],
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Aggregate savings for `window`. `defaultModelId` is what cost-per-token
|
|
45
|
+
* to apply to recalls where the captured row didn't know the caller's
|
|
46
|
+
* model (the user picks this in their dashboard settings — we just take
|
|
47
|
+
* it as input here).
|
|
48
|
+
*/
|
|
49
|
+
export function getSavingsSummary(db, window = 'month', defaultModelId = DEFAULT_MODEL_ID) {
|
|
50
|
+
if (!tableExists(db, 'recall_events'))
|
|
51
|
+
return emptySummary(window, defaultModelId);
|
|
52
|
+
const since = windowSinceMs(window);
|
|
53
|
+
// Aggregate over the window. We compute dollars saved in JS rather than
|
|
54
|
+
// SQL because the model_id varies per row and we want per-row pricing.
|
|
55
|
+
const rows = db
|
|
56
|
+
.prepare(`SELECT namespace, client, tokens_returned, tokens_baseline_capped,
|
|
57
|
+
model_id, context_limit, tokens_baseline_namespace, created_at
|
|
58
|
+
FROM recall_events
|
|
59
|
+
WHERE created_at >= ?`)
|
|
60
|
+
.all(since);
|
|
61
|
+
let tokens_returned = 0;
|
|
62
|
+
let tokens_baseline_capped = 0;
|
|
63
|
+
let dollars_saved = 0;
|
|
64
|
+
let ide_crashes_avoided = 0;
|
|
65
|
+
const perDay = new Map();
|
|
66
|
+
const topSaves = [];
|
|
67
|
+
for (const r of rows) {
|
|
68
|
+
tokens_returned += r.tokens_returned;
|
|
69
|
+
tokens_baseline_capped += r.tokens_baseline_capped;
|
|
70
|
+
const saved_tokens = Math.max(0, r.tokens_baseline_capped - r.tokens_returned);
|
|
71
|
+
const model = r.model_id ?? defaultModelId;
|
|
72
|
+
const saved_usd = tokensToDollars(saved_tokens, model);
|
|
73
|
+
dollars_saved += saved_usd;
|
|
74
|
+
if (r.tokens_baseline_namespace > r.context_limit)
|
|
75
|
+
ide_crashes_avoided++;
|
|
76
|
+
const day = new Date(r.created_at).toISOString().slice(0, 10);
|
|
77
|
+
const bucket = perDay.get(day) ?? { usd: 0, recalls: 0 };
|
|
78
|
+
bucket.usd += saved_usd;
|
|
79
|
+
bucket.recalls += 1;
|
|
80
|
+
perDay.set(day, bucket);
|
|
81
|
+
topSaves.push({
|
|
82
|
+
namespace: r.namespace,
|
|
83
|
+
client: r.client,
|
|
84
|
+
tokens_returned: r.tokens_returned,
|
|
85
|
+
tokens_baseline_capped: r.tokens_baseline_capped,
|
|
86
|
+
dollars_saved: saved_usd,
|
|
87
|
+
created_at: r.created_at,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
topSaves.sort((a, b) => b.dollars_saved - a.dollars_saved);
|
|
91
|
+
// Build a 14-day trend (most recent last). Fill missing days with zeros
|
|
92
|
+
// so the sparkline doesn't have gaps.
|
|
93
|
+
const trend = [];
|
|
94
|
+
const today = new Date();
|
|
95
|
+
for (let i = 13; i >= 0; i--) {
|
|
96
|
+
const d = new Date(today.getTime() - i * 24 * 60 * 60 * 1000);
|
|
97
|
+
const key = d.toISOString().slice(0, 10);
|
|
98
|
+
const bucket = perDay.get(key);
|
|
99
|
+
trend.push({
|
|
100
|
+
day: key,
|
|
101
|
+
savings_usd: Math.round((bucket?.usd ?? 0) * 100) / 100,
|
|
102
|
+
recalls: bucket?.recalls ?? 0,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
window,
|
|
107
|
+
recalls_count: rows.length,
|
|
108
|
+
tokens_returned,
|
|
109
|
+
tokens_baseline_capped,
|
|
110
|
+
tokens_saved: Math.max(0, tokens_baseline_capped - tokens_returned),
|
|
111
|
+
dollars_saved: Math.round(dollars_saved * 100) / 100,
|
|
112
|
+
ide_crashes_avoided,
|
|
113
|
+
default_model_id: defaultModelId,
|
|
114
|
+
trend,
|
|
115
|
+
top_saves: topSaves.slice(0, 5),
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=summary.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"summary.js","sourceRoot":"","sources":["../../src/savings/summary.ts"],"names":[],"mappings":"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"}
|