@llmnesia/mcp 0.2.3 → 0.2.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.
- package/README.md +35 -0
- package/dist/{chunk-6H2TI5IW.js → chunk-BDR4JVMF.js} +1 -1
- package/dist/{chunk-G42RV536.js → chunk-MTPXE734.js} +793 -68
- package/dist/{cli-77JECNWA.js → cli-OXKBAUBE.js} +81 -6
- package/dist/cli.js +2 -2
- package/dist/llmnesia.mcpb +0 -0
- package/dist/mcp.js +95 -4
- package/package.json +1 -1
- package/server.json +2 -2
|
@@ -4,23 +4,26 @@ const require = __llmnesiaCreateRequire(import.meta.url);
|
|
|
4
4
|
import {
|
|
5
5
|
Corpus,
|
|
6
6
|
SearchIndex,
|
|
7
|
+
compactCorpus,
|
|
7
8
|
deleteIndexDb,
|
|
8
9
|
drainInbox,
|
|
9
10
|
ingestFile,
|
|
11
|
+
inspectCorpusStorage,
|
|
10
12
|
installRuntime,
|
|
11
13
|
isSelfUpdateDisabled,
|
|
12
14
|
openSearchIndex,
|
|
15
|
+
readAutomaticMaintenanceState,
|
|
13
16
|
resolveCorpusDir,
|
|
14
17
|
runtimeBinDir,
|
|
15
18
|
staleIndexFiles,
|
|
16
19
|
uninstallRuntime
|
|
17
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-MTPXE734.js";
|
|
18
21
|
import {
|
|
19
22
|
VERSION,
|
|
20
23
|
__commonJS,
|
|
21
24
|
__require,
|
|
22
25
|
__toESM
|
|
23
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-BDR4JVMF.js";
|
|
24
27
|
|
|
25
28
|
// ../node_modules/isexe/windows.js
|
|
26
29
|
var require_windows = __commonJS({
|
|
@@ -824,7 +827,7 @@ var OPTION_ALIASES = {
|
|
|
824
827
|
h: "help",
|
|
825
828
|
y: "yes"
|
|
826
829
|
};
|
|
827
|
-
var BOOLEAN_FLAGS = /* @__PURE__ */ new Set(["force", "yes", "version", "help"]);
|
|
830
|
+
var BOOLEAN_FLAGS = /* @__PURE__ */ new Set(["force", "yes", "version", "help", "dry-run"]);
|
|
828
831
|
var VALUE_OPTIONS = /* @__PURE__ */ new Set(["corpus", "limit", "platform", "title", "from", "to"]);
|
|
829
832
|
function parseArgs(argv) {
|
|
830
833
|
const positionals = [];
|
|
@@ -3765,9 +3768,12 @@ Setup:
|
|
|
3765
3768
|
Power-user / debugging:
|
|
3766
3769
|
init Create the corpus directory skeleton
|
|
3767
3770
|
ingest <file...> Ingest one or more NDJSON backup files into the corpus
|
|
3768
|
-
drain Ingest every file in <corpus>/inbox
|
|
3771
|
+
drain Ingest every file in <corpus>/inbox; discard clean
|
|
3772
|
+
payloads and quarantine malformed ones
|
|
3769
3773
|
watch Watch <corpus>/inbox and ingest new deltas live
|
|
3770
3774
|
stats Print corpus counts (conversations, messages, platforms)
|
|
3775
|
+
compact Losslessly compress conversations and remove redundant
|
|
3776
|
+
processed batches/stale indexes (confirmation required)
|
|
3771
3777
|
reindex Drop and rebuild the search index from the corpus
|
|
3772
3778
|
search <query> Full-text search the corpus (BM25)
|
|
3773
3779
|
|
|
@@ -3780,6 +3786,7 @@ Options:
|
|
|
3780
3786
|
--to <iso|ms> search: only conversations created on/before this date
|
|
3781
3787
|
--force install: overwrite a differing corpus root in config.json
|
|
3782
3788
|
--yes, -y uninstall: skip its confirmation prompt
|
|
3789
|
+
--dry-run compact: calculate exact savings without writing
|
|
3783
3790
|
--version, -v print the version and exit
|
|
3784
3791
|
`;
|
|
3785
3792
|
function parseDate(value) {
|
|
@@ -3984,7 +3991,7 @@ async function main() {
|
|
|
3984
3991
|
`)
|
|
3985
3992
|
});
|
|
3986
3993
|
process.stdout.write(
|
|
3987
|
-
`Drained ${report.files} inbox file(s): ${report.conversations} conversation(s) \u2014 ${report.created} created, ${report.updated} updated, ${report.skipped} unchanged` + (report.failed > 0 ? `, ${report.failed} file(s) failed` : "") + `
|
|
3994
|
+
`Drained ${report.files} inbox file(s): ${report.conversations} conversation(s) \u2014 ${report.created} created, ${report.updated} updated, ${report.skipped} unchanged` + (report.quarantined > 0 ? `, ${report.quarantined} quarantined` : "") + (report.failed > 0 ? `, ${report.failed} file(s) failed` : "") + `
|
|
3988
3995
|
`
|
|
3989
3996
|
);
|
|
3990
3997
|
} finally {
|
|
@@ -4012,6 +4019,8 @@ async function main() {
|
|
|
4012
4019
|
}
|
|
4013
4020
|
case "stats": {
|
|
4014
4021
|
const stats = await corpus.stats();
|
|
4022
|
+
const storage = await inspectCorpusStorage(corpus);
|
|
4023
|
+
const maintenance = await readAutomaticMaintenanceState(corpus);
|
|
4015
4024
|
const platforms = Object.entries(stats.byPlatform).sort((a, b) => b[1] - a[1]).map(([p, n]) => ` ${p}: ${n}`).join("\n");
|
|
4016
4025
|
process.stdout.write(
|
|
4017
4026
|
`Corpus: ${root}
|
|
@@ -4019,14 +4028,43 @@ async function main() {
|
|
|
4019
4028
|
messages: ${stats.messageCount}
|
|
4020
4029
|
` + (platforms ? ` by platform:
|
|
4021
4030
|
${platforms}
|
|
4031
|
+
` : "") + ` storage: ${formatBytes(storage.totalBytes)}
|
|
4032
|
+
conversations: ${formatBytes(storage.conversations.legacyBytes + storage.conversations.compressedBytes)} (${storage.conversations.compressedFiles} compressed, ${storage.conversations.legacyFiles} legacy)
|
|
4033
|
+
active index: ${formatBytes(storage.activeIndex.bytes)}
|
|
4034
|
+
processed: ${formatBytes(storage.processed.bytes)} (${storage.processed.files} reclaimable files)
|
|
4035
|
+
stale indexes: ${formatBytes(storage.staleIndexes.bytes)}
|
|
4036
|
+
quarantine: ${formatBytes(storage.quarantine.bytes)}
|
|
4037
|
+
` + (maintenance ? ` maintenance: ${formatMaintenanceState(maintenance)}
|
|
4022
4038
|
` : "")
|
|
4023
4039
|
);
|
|
4024
4040
|
return 0;
|
|
4025
4041
|
}
|
|
4042
|
+
case "compact": {
|
|
4043
|
+
const dryRun = flags.has("dry-run");
|
|
4044
|
+
const preview = await compactCorpus(corpus, { dryRun: true });
|
|
4045
|
+
process.stdout.write(formatCompactPreview(preview));
|
|
4046
|
+
if (dryRun) return 0;
|
|
4047
|
+
if (!flags.has("yes")) {
|
|
4048
|
+
const ok = await promptConfirm(
|
|
4049
|
+
"Compact this corpus now? Close desktop AI/MCP clients first. Clean processed sync payloads and stale derived indexes will be removed; canonical conversations are migrated losslessly with read-back verification."
|
|
4050
|
+
);
|
|
4051
|
+
if (!ok) {
|
|
4052
|
+
process.stdout.write("Cancelled.\n");
|
|
4053
|
+
return 0;
|
|
4054
|
+
}
|
|
4055
|
+
}
|
|
4056
|
+
const result = await compactCorpus(corpus);
|
|
4057
|
+
process.stdout.write(
|
|
4058
|
+
`Compaction complete: reclaimed ${formatBytes(result.reclaimedBytes)}; ${result.compression.migrated} conversation(s) compressed, ${result.processedRemoved.files} processed file(s) removed, ${result.staleIndexesRemoved.files} stale index file(s) removed.
|
|
4059
|
+
Corpus now uses ${formatBytes(result.after?.totalBytes ?? 0)}.
|
|
4060
|
+
`
|
|
4061
|
+
);
|
|
4062
|
+
return 0;
|
|
4063
|
+
}
|
|
4026
4064
|
case "reindex": {
|
|
4027
4065
|
await corpus.init();
|
|
4028
4066
|
await deleteIndexDb(corpus.paths.indexDbFile);
|
|
4029
|
-
const index = new SearchIndex(corpus.paths.indexDbFile);
|
|
4067
|
+
const index = new SearchIndex(corpus.paths.indexDbFile, corpus);
|
|
4030
4068
|
try {
|
|
4031
4069
|
const { conversations } = await index.rebuildFrom(corpus);
|
|
4032
4070
|
process.stdout.write(`Rebuilt index: ${conversations} conversation(s)
|
|
@@ -4092,6 +4130,43 @@ ${USAGE}`);
|
|
|
4092
4130
|
function collapse(text) {
|
|
4093
4131
|
return text.replace(/\s+/g, " ").trim();
|
|
4094
4132
|
}
|
|
4133
|
+
function formatBytes(bytes) {
|
|
4134
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
4135
|
+
const units = ["KiB", "MiB", "GiB", "TiB"];
|
|
4136
|
+
let value = bytes / 1024;
|
|
4137
|
+
let unit = units[0];
|
|
4138
|
+
for (let i = 1; i < units.length && value >= 1024; i += 1) {
|
|
4139
|
+
value /= 1024;
|
|
4140
|
+
unit = units[i];
|
|
4141
|
+
}
|
|
4142
|
+
return `${value.toFixed(value >= 10 ? 1 : 2)} ${unit}`;
|
|
4143
|
+
}
|
|
4144
|
+
function formatMaintenanceState(state) {
|
|
4145
|
+
if (state.status === "complete") {
|
|
4146
|
+
return `complete${state.completedAt ? ` (${new Date(state.completedAt).toISOString()})` : ""}`;
|
|
4147
|
+
}
|
|
4148
|
+
if (state.status === "waiting" && state.nextEligibleAt) {
|
|
4149
|
+
return `automatic cleanup scheduled after ${new Date(state.nextEligibleAt).toISOString()}`;
|
|
4150
|
+
}
|
|
4151
|
+
if (state.status === "failed" && state.nextEligibleAt) {
|
|
4152
|
+
return `will retry after ${new Date(state.nextEligibleAt).toISOString()}`;
|
|
4153
|
+
}
|
|
4154
|
+
return state.status;
|
|
4155
|
+
}
|
|
4156
|
+
function formatCompactPreview(result) {
|
|
4157
|
+
const { before, compression } = result;
|
|
4158
|
+
const compressionSavings = Math.max(
|
|
4159
|
+
0,
|
|
4160
|
+
compression.legacyBytes + compression.compressedBytes - compression.projectedCompressedBytes
|
|
4161
|
+
);
|
|
4162
|
+
return `Compaction preview for ${before.root}:
|
|
4163
|
+
current size: ${formatBytes(before.totalBytes)}
|
|
4164
|
+
processed sync payloads: ${formatBytes(before.processed.bytes)} (${before.processed.files} files)
|
|
4165
|
+
stale indexes: ${formatBytes(before.staleIndexes.bytes)} (${before.staleIndexes.files} files)
|
|
4166
|
+
lossless compression: ${formatBytes(compressionSavings)} (${compression.candidates} legacy conversations)
|
|
4167
|
+
estimated reclaimable: ${formatBytes(result.reclaimedBytes)}
|
|
4168
|
+
`;
|
|
4169
|
+
}
|
|
4095
4170
|
export {
|
|
4096
4171
|
formatInstallSuccess,
|
|
4097
4172
|
main,
|
package/dist/cli.js
CHANGED
|
@@ -3,7 +3,7 @@ import { createRequire as __llmnesiaCreateRequire } from "node:module";
|
|
|
3
3
|
const require = __llmnesiaCreateRequire(import.meta.url);
|
|
4
4
|
import {
|
|
5
5
|
VERSION
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-BDR4JVMF.js";
|
|
7
7
|
|
|
8
8
|
// src/nodeDownload.ts
|
|
9
9
|
var NODE_DOWNLOAD_URLS = {
|
|
@@ -141,7 +141,7 @@ async function bootstrap() {
|
|
|
141
141
|
await guideUnsupportedNodeVersion(process.versions.node);
|
|
142
142
|
return 1;
|
|
143
143
|
}
|
|
144
|
-
const { main } = await import("./cli-
|
|
144
|
+
const { main } = await import("./cli-OXKBAUBE.js");
|
|
145
145
|
return main();
|
|
146
146
|
}
|
|
147
147
|
bootstrap().then((code) => {
|
package/dist/llmnesia.mcpb
CHANGED
|
Binary file
|
package/dist/mcp.js
CHANGED
|
@@ -2,20 +2,22 @@
|
|
|
2
2
|
import { createRequire as __llmnesiaCreateRequire } from "node:module";
|
|
3
3
|
const require = __llmnesiaCreateRequire(import.meta.url);
|
|
4
4
|
import {
|
|
5
|
+
AUTO_MAINTENANCE_RECHECK_MS,
|
|
5
6
|
Corpus,
|
|
6
7
|
describeOutcome,
|
|
7
8
|
drainInbox,
|
|
8
9
|
openSearchIndex,
|
|
9
10
|
pendingInboxFiles,
|
|
10
11
|
resolveCorpusDir,
|
|
12
|
+
runAutomaticMaintenance,
|
|
11
13
|
scheduleSelfUpdate
|
|
12
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-MTPXE734.js";
|
|
13
15
|
import {
|
|
14
16
|
VERSION,
|
|
15
17
|
__commonJS,
|
|
16
18
|
__export,
|
|
17
19
|
__toESM
|
|
18
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-BDR4JVMF.js";
|
|
19
21
|
|
|
20
22
|
// ../node_modules/ajv/dist/compile/codegen/code.js
|
|
21
23
|
var require_code = __commonJS({
|
|
@@ -21393,6 +21395,9 @@ function describeIssues(error2) {
|
|
|
21393
21395
|
|
|
21394
21396
|
// src/service.ts
|
|
21395
21397
|
var STALE_AFTER_HOURS = 48;
|
|
21398
|
+
var AUTO_MAINTENANCE_START_DELAY_MS = 6e4;
|
|
21399
|
+
var AUTO_MAINTENANCE_IDLE_MS = 3e4;
|
|
21400
|
+
var AUTO_MAINTENANCE_BUSY_RETRY_MS = 3e4;
|
|
21396
21401
|
var CompanionService = class _CompanionService {
|
|
21397
21402
|
constructor(corpus, index, onDrainError, memoryWriteCap) {
|
|
21398
21403
|
this.corpus = corpus;
|
|
@@ -21409,6 +21414,10 @@ var CompanionService = class _CompanionService {
|
|
|
21409
21414
|
refreshChain = Promise.resolve();
|
|
21410
21415
|
/** Memories written since this process started; the per-session cap counts these. */
|
|
21411
21416
|
memoriesWritten = 0;
|
|
21417
|
+
activeOperations = 0;
|
|
21418
|
+
lastOperationAt = Date.now();
|
|
21419
|
+
maintenanceTimer;
|
|
21420
|
+
closed = false;
|
|
21412
21421
|
static async open(root, options = {}) {
|
|
21413
21422
|
const corpus = new Corpus(root);
|
|
21414
21423
|
const index = await openSearchIndex(corpus);
|
|
@@ -21419,8 +21428,71 @@ var CompanionService = class _CompanionService {
|
|
|
21419
21428
|
options.memoryWriteCap ?? DEFAULT_MEMORY_WRITE_CAP
|
|
21420
21429
|
);
|
|
21421
21430
|
await service.refresh();
|
|
21431
|
+
if (options.automaticMaintenance) {
|
|
21432
|
+
service.scheduleAutomaticMaintenance(options.automaticMaintenance);
|
|
21433
|
+
}
|
|
21422
21434
|
return service;
|
|
21423
21435
|
}
|
|
21436
|
+
/** Let the MCP wrapper expose activity to the idle-only maintenance scheduler. */
|
|
21437
|
+
async runWithActivity(operation) {
|
|
21438
|
+
this.activeOperations += 1;
|
|
21439
|
+
this.lastOperationAt = Date.now();
|
|
21440
|
+
try {
|
|
21441
|
+
return await operation();
|
|
21442
|
+
} finally {
|
|
21443
|
+
this.activeOperations -= 1;
|
|
21444
|
+
this.lastOperationAt = Date.now();
|
|
21445
|
+
}
|
|
21446
|
+
}
|
|
21447
|
+
scheduleAutomaticMaintenance(options) {
|
|
21448
|
+
const {
|
|
21449
|
+
delayMs = AUTO_MAINTENANCE_START_DELAY_MS,
|
|
21450
|
+
idleMs = AUTO_MAINTENANCE_IDLE_MS,
|
|
21451
|
+
busyRetryMs = AUTO_MAINTENANCE_BUSY_RETRY_MS,
|
|
21452
|
+
onOutcome,
|
|
21453
|
+
...runOptions
|
|
21454
|
+
} = options;
|
|
21455
|
+
const schedule = (delay) => {
|
|
21456
|
+
if (this.closed) return;
|
|
21457
|
+
this.maintenanceTimer = setTimeout(
|
|
21458
|
+
attempt,
|
|
21459
|
+
Math.max(0, Math.min(delay, 2147483647))
|
|
21460
|
+
);
|
|
21461
|
+
this.maintenanceTimer.unref?.();
|
|
21462
|
+
};
|
|
21463
|
+
const attempt = () => {
|
|
21464
|
+
this.maintenanceTimer = void 0;
|
|
21465
|
+
if (this.closed) return;
|
|
21466
|
+
const idleFor = Date.now() - this.lastOperationAt;
|
|
21467
|
+
if (this.activeOperations > 0 || idleFor < idleMs) {
|
|
21468
|
+
schedule(Math.max(busyRetryMs, idleMs - idleFor));
|
|
21469
|
+
return;
|
|
21470
|
+
}
|
|
21471
|
+
void runAutomaticMaintenance(this.corpus, this.index, runOptions).then(
|
|
21472
|
+
(outcome) => {
|
|
21473
|
+
onOutcome?.(outcome);
|
|
21474
|
+
const current = Date.now();
|
|
21475
|
+
if (outcome.action === "deferred") schedule(outcome.until - current);
|
|
21476
|
+
else if (outcome.action === "failed") schedule(outcome.retryAt - current);
|
|
21477
|
+
else if (outcome.action === "locked") schedule(busyRetryMs);
|
|
21478
|
+
else if (outcome.action === "recently-checked") {
|
|
21479
|
+
schedule(outcome.completedAt + (runOptions.recheckMs ?? AUTO_MAINTENANCE_RECHECK_MS) - current);
|
|
21480
|
+
} else if (outcome.action === "compacted" || outcome.action === "no-work") {
|
|
21481
|
+
schedule(runOptions.recheckMs ?? AUTO_MAINTENANCE_RECHECK_MS);
|
|
21482
|
+
}
|
|
21483
|
+
},
|
|
21484
|
+
(error2) => {
|
|
21485
|
+
onOutcome?.({
|
|
21486
|
+
action: "failed",
|
|
21487
|
+
error: error2 instanceof Error ? error2.message : String(error2),
|
|
21488
|
+
retryAt: Date.now() + busyRetryMs
|
|
21489
|
+
});
|
|
21490
|
+
schedule(busyRetryMs);
|
|
21491
|
+
}
|
|
21492
|
+
);
|
|
21493
|
+
};
|
|
21494
|
+
schedule(delayMs);
|
|
21495
|
+
}
|
|
21424
21496
|
/**
|
|
21425
21497
|
* Ingest whatever is waiting in `inbox/`. Never throws: a corrupt or
|
|
21426
21498
|
* unreadable delta is reported and skipped rather than failing the read.
|
|
@@ -21547,6 +21619,9 @@ var CompanionService = class _CompanionService {
|
|
|
21547
21619
|
};
|
|
21548
21620
|
}
|
|
21549
21621
|
close() {
|
|
21622
|
+
this.closed = true;
|
|
21623
|
+
if (this.maintenanceTimer) clearTimeout(this.maintenanceTimer);
|
|
21624
|
+
this.maintenanceTimer = void 0;
|
|
21550
21625
|
this.index.close();
|
|
21551
21626
|
}
|
|
21552
21627
|
};
|
|
@@ -21636,12 +21711,28 @@ async function runServe() {
|
|
|
21636
21711
|
onDrainError: (error2) => process.stderr.write(
|
|
21637
21712
|
`llmnesia: inbox drain failed, serving the existing index: ${error2 instanceof Error ? error2.message : String(error2)}
|
|
21638
21713
|
`
|
|
21639
|
-
)
|
|
21714
|
+
),
|
|
21715
|
+
automaticMaintenance: {
|
|
21716
|
+
onOutcome: (outcome) => {
|
|
21717
|
+
if (outcome.action === "failed") {
|
|
21718
|
+
process.stderr.write(
|
|
21719
|
+
`llmnesia: automatic storage maintenance will retry later: ${outcome.error}
|
|
21720
|
+
`
|
|
21721
|
+
);
|
|
21722
|
+
} else if (outcome.action === "compacted" && outcome.report.reclaimedBytes > 0) {
|
|
21723
|
+
process.stderr.write(
|
|
21724
|
+
`llmnesia: automatic storage maintenance reclaimed ${outcome.report.reclaimedBytes} bytes
|
|
21725
|
+
`
|
|
21726
|
+
);
|
|
21727
|
+
}
|
|
21728
|
+
}
|
|
21729
|
+
}
|
|
21640
21730
|
})
|
|
21641
21731
|
);
|
|
21642
21732
|
const withService = async (operation) => {
|
|
21643
21733
|
try {
|
|
21644
|
-
|
|
21734
|
+
const ready = await service.get(5e3);
|
|
21735
|
+
return await ready.runWithActivity(() => operation(ready));
|
|
21645
21736
|
} catch (err) {
|
|
21646
21737
|
if (err instanceof ResourcePreparingError) {
|
|
21647
21738
|
return errorResult("LLMnesia is still preparing the local conversation index. Retry this tool shortly.");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@llmnesia/mcp",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.5",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"comment:mcpName": "The MCP Registry's ownership marker: it must equal server.json's `name`, and the namespace must match how you authenticate with mcp-publisher. `com.llmnesia/...` requires DNS auth (a TXT record on llmnesia.com) — see docs/MCP_INSTALLER_BOARD.md.",
|
package/server.json
CHANGED
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
"name": "com.llmnesia/mcp",
|
|
4
4
|
"title": "LLMnesia",
|
|
5
5
|
"description": "Search your AI chat history from Claude, Cursor, Codex, and other MCP clients.",
|
|
6
|
-
"version": "0.2.
|
|
6
|
+
"version": "0.2.5",
|
|
7
7
|
"packages": [
|
|
8
8
|
{
|
|
9
9
|
"registryType": "npm",
|
|
10
10
|
"registryBaseUrl": "https://registry.npmjs.org",
|
|
11
11
|
"identifier": "@llmnesia/mcp",
|
|
12
|
-
"version": "0.2.
|
|
12
|
+
"version": "0.2.5",
|
|
13
13
|
"transport": {
|
|
14
14
|
"type": "stdio"
|
|
15
15
|
}
|