@llmnesia/mcp 0.1.13 → 0.1.14

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.
@@ -1,6 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire as __llmnesiaCreateRequire } from "node:module";
3
3
  const require = __llmnesiaCreateRequire(import.meta.url);
4
+ import {
5
+ VERSION
6
+ } from "./chunk-QGVH3T5W.js";
4
7
 
5
8
  // src/config.ts
6
9
  import { readFileSync } from "fs";
@@ -102,6 +105,7 @@ function canonicalConversationDoc(c) {
102
105
  createdAt: c.createdAt,
103
106
  contentUpdatedAt: c.contentUpdatedAt,
104
107
  indexedAt: c.indexedAt,
108
+ corpusChangedAt: c.corpusChangedAt,
105
109
  updatedAt: c.updatedAt,
106
110
  lastSeenAt: c.lastSeenAt,
107
111
  messageCount: c.messageCount,
@@ -207,10 +211,10 @@ var Corpus = class {
207
211
  `);
208
212
  }
209
213
  /**
210
- * Insert or replace a conversation, keyed on docId with last-write-wins by
211
- * `conversation.updatedAt`. A stale write (older updatedAt than what's on
212
- * disk) is a no-op. Re-writing identical content produces byte-identical
213
- * output, so replaying a delta is safe.
214
+ * Insert or replace a conversation, keyed on docId. New bridge records use
215
+ * the local `corpusChangedAt` mutation clock; older records fall back to the
216
+ * source `updatedAt`. If mutation clocks tie, source freshness breaks the
217
+ * tie. Re-writing identical content remains a byte-identical no-op.
214
218
  */
215
219
  async upsert(record) {
216
220
  return this.enqueueWrite(async () => {
@@ -219,9 +223,11 @@ var Corpus = class {
219
223
  const file = this.paths.conversationFile(conversation.platform, conversation.docId);
220
224
  const existing = await readJsonIfExists(file);
221
225
  if (existing) {
226
+ const existingChangedAt = existing.conversation.corpusChangedAt ?? existing.conversation.updatedAt ?? 0;
227
+ const incomingChangedAt = conversation.corpusChangedAt ?? conversation.updatedAt ?? 0;
222
228
  const existingUpdatedAt = existing.conversation.updatedAt ?? 0;
223
229
  const incomingUpdatedAt = conversation.updatedAt ?? 0;
224
- if (incomingUpdatedAt < existingUpdatedAt) {
230
+ if (incomingChangedAt < existingChangedAt || incomingChangedAt === existingChangedAt && incomingUpdatedAt < existingUpdatedAt) {
225
231
  return { docId: conversation.docId, outcome: "skipped" };
226
232
  }
227
233
  }
@@ -841,6 +847,295 @@ function normalizeMatchRole(role) {
841
847
  return role === "assistant" || role === "user" || role === "system" ? role : "unknown";
842
848
  }
843
849
 
850
+ // src/runtimeInstall.ts
851
+ import { cp, mkdir, readdir, rename, rm, writeFile } from "fs/promises";
852
+ import { existsSync } from "fs";
853
+ import { homedir as homedir2 } from "os";
854
+ import { dirname as dirname2, join as join5 } from "path";
855
+ import { fileURLToPath } from "url";
856
+ function defaultSourceDir() {
857
+ return dirname2(fileURLToPath(import.meta.url));
858
+ }
859
+ function runtimeBinDir(home = homedir2()) {
860
+ return join5(home, ".llmnesia", "bin");
861
+ }
862
+ async function installRuntime(options = {}) {
863
+ const home = options.home ?? homedir2();
864
+ const sourceDir = options.sourceDir ?? defaultSourceDir();
865
+ const nodePath = options.nodePath ?? process.execPath;
866
+ const binDir = runtimeBinDir(home);
867
+ const sourceEntry = join5(sourceDir, "cli.js");
868
+ if (!existsSync(sourceEntry)) {
869
+ throw new Error(
870
+ `Cannot locate the built runtime: no cli.js in ${sourceDir}. Run this from an installed package (npx -y @llmnesia/mcp@latest install) or build first (npm run build).`
871
+ );
872
+ }
873
+ const parentDir = dirname2(binDir);
874
+ const nonce = `${process.pid}-${Date.now()}`;
875
+ const stagingDir = join5(parentDir, `.bin-install-${nonce}`);
876
+ const backupDir = join5(parentDir, `.bin-backup-${nonce}`);
877
+ await mkdir(parentDir, { recursive: true });
878
+ await rm(stagingDir, { recursive: true, force: true });
879
+ await rm(backupDir, { recursive: true, force: true });
880
+ await mkdir(stagingDir, { recursive: true });
881
+ try {
882
+ for (const name of await readdir(sourceDir)) {
883
+ await cp(join5(sourceDir, name), join5(stagingDir, name), { recursive: true });
884
+ }
885
+ await writeFile(
886
+ join5(stagingDir, "package.json"),
887
+ `${JSON.stringify(
888
+ {
889
+ name: "llmnesia-mcp-runtime",
890
+ private: true,
891
+ type: "module",
892
+ // Stamped so the installed build is identifiable without spawning it.
893
+ // Its absence is how a machine sat on 0.1.3 while npm was ten
894
+ // releases ahead and nothing, doctor included, could say so.
895
+ version: options.version ?? VERSION,
896
+ comment: "Installed copy of @llmnesia/mcp's dist. Managed by the LLMnesia installer; edits are lost on reinstall."
897
+ },
898
+ null,
899
+ 2
900
+ )}
901
+ `,
902
+ "utf8"
903
+ );
904
+ const hadPreviousRuntime = existsSync(binDir);
905
+ if (hadPreviousRuntime) {
906
+ await rename(binDir, backupDir);
907
+ }
908
+ try {
909
+ await rename(stagingDir, binDir);
910
+ } catch (err) {
911
+ if (hadPreviousRuntime && !existsSync(binDir) && existsSync(backupDir)) {
912
+ await rename(backupDir, binDir);
913
+ }
914
+ throw err;
915
+ }
916
+ await rm(backupDir, { recursive: true, force: true });
917
+ } finally {
918
+ await rm(stagingDir, { recursive: true, force: true });
919
+ }
920
+ const entry = join5(binDir, "cli.js");
921
+ return {
922
+ binDir,
923
+ entry,
924
+ serve: { command: nodePath, args: [entry, "serve"] },
925
+ sourceDir,
926
+ nodePath
927
+ };
928
+ }
929
+ async function uninstallRuntime(home = homedir2()) {
930
+ const binDir = runtimeBinDir(home);
931
+ if (!existsSync(binDir)) {
932
+ return null;
933
+ }
934
+ await rm(binDir, { recursive: true, force: true });
935
+ return binDir;
936
+ }
937
+
938
+ // src/selfUpdate.ts
939
+ import { spawn } from "child_process";
940
+ import { mkdtemp, readFile, rm as rm2, writeFile as writeFile2 } from "fs/promises";
941
+ import { dirname as dirname3, join as join6 } from "path";
942
+ import { homedir as homedir3, tmpdir } from "os";
943
+ import { fileURLToPath as fileURLToPath2 } from "url";
944
+ var PACKAGE_NAME = "@llmnesia/mcp";
945
+ var REGISTRY_LATEST_URL = "https://registry.npmjs.org/@llmnesia%2Fmcp/latest";
946
+ var UPDATE_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
947
+ var REGISTRY_TIMEOUT_MS = 5e3;
948
+ var INSTALL_TIMEOUT_MS = 12e4;
949
+ var UPDATE_CHECK_DELAY_MS = 3e4;
950
+ function stateFile(home) {
951
+ return join6(home, ".llmnesia", "update-state.json");
952
+ }
953
+ async function readState(home) {
954
+ try {
955
+ const parsed = JSON.parse(await readFile(stateFile(home), "utf8"));
956
+ return typeof parsed === "object" && parsed !== null ? parsed : {};
957
+ } catch {
958
+ return {};
959
+ }
960
+ }
961
+ async function writeState(home, state) {
962
+ try {
963
+ await writeFile2(stateFile(home), `${JSON.stringify(state, null, 2)}
964
+ `, "utf8");
965
+ } catch {
966
+ }
967
+ }
968
+ function compareVersions(a, b) {
969
+ const parse = (v) => {
970
+ const match = /^(\d+)\.(\d+)\.(\d+)/.exec(v.trim());
971
+ return match ? [Number(match[1]), Number(match[2]), Number(match[3])] : [];
972
+ };
973
+ const left = parse(a);
974
+ const right = parse(b);
975
+ if (left.length === 0 || right.length === 0) {
976
+ return 0;
977
+ }
978
+ for (let i = 0; i < 3; i += 1) {
979
+ if (left[i] !== right[i]) {
980
+ return left[i] - right[i];
981
+ }
982
+ }
983
+ return 0;
984
+ }
985
+ function isStableRelease(version) {
986
+ return /^\d+\.\d+\.\d+$/.test(version.trim());
987
+ }
988
+ function isSelfUpdateDisabled(env = process.env) {
989
+ const raw = env.LLMNESIA_NO_AUTO_UPDATE;
990
+ return raw === "1" || raw === "true";
991
+ }
992
+ async function fetchLatestFromRegistry() {
993
+ const controller = new AbortController();
994
+ const timer = setTimeout(() => controller.abort(), REGISTRY_TIMEOUT_MS);
995
+ try {
996
+ const response = await fetch(REGISTRY_LATEST_URL, {
997
+ signal: controller.signal,
998
+ headers: { accept: "application/json" }
999
+ });
1000
+ if (!response.ok) {
1001
+ throw new Error(`registry responded ${response.status}`);
1002
+ }
1003
+ const body = await response.json();
1004
+ if (typeof body.version !== "string") {
1005
+ throw new Error("registry response had no version");
1006
+ }
1007
+ return body.version;
1008
+ } finally {
1009
+ clearTimeout(timer);
1010
+ }
1011
+ }
1012
+ function npmCommand() {
1013
+ const binDir = dirname3(process.execPath);
1014
+ return process.platform === "win32" ? join6(binDir, "npm.cmd") : join6(binDir, "npm");
1015
+ }
1016
+ async function npmDownload(version) {
1017
+ const prefix = await mkdtemp(join6(tmpdir(), "llmnesia-update-"));
1018
+ await new Promise((resolve2, reject) => {
1019
+ const child = spawn(
1020
+ npmCommand(),
1021
+ [
1022
+ "install",
1023
+ `${PACKAGE_NAME}@${version}`,
1024
+ "--prefix",
1025
+ prefix,
1026
+ "--no-audit",
1027
+ "--no-fund",
1028
+ "--no-save",
1029
+ "--loglevel",
1030
+ "error"
1031
+ ],
1032
+ { stdio: ["ignore", "ignore", "pipe"], windowsHide: true }
1033
+ );
1034
+ let stderr = "";
1035
+ child.stderr?.on("data", (chunk) => {
1036
+ stderr += String(chunk);
1037
+ });
1038
+ const timer = setTimeout(() => child.kill(), INSTALL_TIMEOUT_MS);
1039
+ child.once("error", (err) => {
1040
+ clearTimeout(timer);
1041
+ reject(err);
1042
+ });
1043
+ child.once("close", (code) => {
1044
+ clearTimeout(timer);
1045
+ if (code === 0) {
1046
+ resolve2();
1047
+ } else {
1048
+ reject(new Error(`npm install exited ${code}${stderr ? `: ${stderr.trim()}` : ""}`));
1049
+ }
1050
+ });
1051
+ });
1052
+ return join6(prefix, "node_modules", PACKAGE_NAME, "dist");
1053
+ }
1054
+ async function maybeSelfUpdate(options = {}) {
1055
+ const env = options.env ?? process.env;
1056
+ if (isSelfUpdateDisabled(env)) {
1057
+ return { action: "disabled" };
1058
+ }
1059
+ const home = options.home ?? homedir3();
1060
+ const currentVersion = options.currentVersion ?? VERSION;
1061
+ const runningDir = options.runningDir ?? dirname3(fileURLToPath2(import.meta.url));
1062
+ if (runningDir !== runtimeBinDir(home)) {
1063
+ return { action: "not-installed-runtime" };
1064
+ }
1065
+ if (!isStableRelease(currentVersion)) {
1066
+ return { action: "not-a-release-build", currentVersion };
1067
+ }
1068
+ const now = options.now ?? Date.now;
1069
+ const state = await readState(home);
1070
+ if (state.lastCheckedAt !== void 0 && now() - state.lastCheckedAt < UPDATE_CHECK_INTERVAL_MS) {
1071
+ return { action: "throttled" };
1072
+ }
1073
+ let latest;
1074
+ try {
1075
+ latest = await (options.fetchLatestVersion ?? fetchLatestFromRegistry)();
1076
+ } catch (err) {
1077
+ await writeState(home, { ...state, lastCheckedAt: now() });
1078
+ return { action: "check-failed", error: err instanceof Error ? err.message : String(err) };
1079
+ }
1080
+ const checked = { ...state, lastCheckedAt: now(), lastSeenVersion: latest };
1081
+ if (!isStableRelease(latest) || compareVersions(latest, currentVersion) <= 0) {
1082
+ await writeState(home, checked);
1083
+ return { action: "up-to-date", latest };
1084
+ }
1085
+ if (state.lastFailedVersion === latest) {
1086
+ await writeState(home, checked);
1087
+ return { action: "skipped-failed-before", latest };
1088
+ }
1089
+ let sourceDir;
1090
+ try {
1091
+ sourceDir = await (options.downloadPackage ?? npmDownload)(latest);
1092
+ await installRuntime({ home, sourceDir, version: latest });
1093
+ await writeState(home, { ...checked, lastUpdatedTo: latest, lastFailedVersion: void 0 });
1094
+ return { action: "updated", from: currentVersion, to: latest };
1095
+ } catch (err) {
1096
+ await writeState(home, { ...checked, lastFailedVersion: latest });
1097
+ return { action: "update-failed", latest, error: err instanceof Error ? err.message : String(err) };
1098
+ } finally {
1099
+ if (sourceDir) {
1100
+ await rm2(join6(sourceDir, "..", "..", ".."), { recursive: true, force: true }).catch(() => {
1101
+ });
1102
+ }
1103
+ }
1104
+ }
1105
+ function scheduleSelfUpdate(options = {}) {
1106
+ const { delayMs = UPDATE_CHECK_DELAY_MS, onOutcome, ...rest } = options;
1107
+ const timer = setTimeout(() => {
1108
+ void maybeSelfUpdate(rest).then(
1109
+ (outcome) => onOutcome?.(outcome),
1110
+ () => {
1111
+ }
1112
+ );
1113
+ }, delayMs);
1114
+ timer.unref?.();
1115
+ }
1116
+ function describeOutcome(outcome) {
1117
+ switch (outcome.action) {
1118
+ case "updated":
1119
+ return `updated ${outcome.from} to ${outcome.to}; it takes effect next launch`;
1120
+ case "update-failed":
1121
+ return `could not install ${outcome.latest}: ${outcome.error}`;
1122
+ case "check-failed":
1123
+ return `could not reach the npm registry: ${outcome.error}`;
1124
+ case "up-to-date":
1125
+ return `already current (latest published is ${outcome.latest})`;
1126
+ case "skipped-failed-before":
1127
+ return `${outcome.latest} failed to install previously; not retrying automatically`;
1128
+ case "throttled":
1129
+ return "checked recently";
1130
+ case "disabled":
1131
+ return "automatic updates are turned off (LLMNESIA_NO_AUTO_UPDATE)";
1132
+ case "not-installed-runtime":
1133
+ return "not running from ~/.llmnesia/bin, so nothing to update";
1134
+ case "not-a-release-build":
1135
+ return `running an unreleased build (${outcome.currentVersion}); leaving the installed runtime alone`;
1136
+ }
1137
+ }
1138
+
844
1139
  export {
845
1140
  resolveCorpusDir,
846
1141
  Corpus,
@@ -849,5 +1144,11 @@ export {
849
1144
  drainInbox,
850
1145
  SearchIndex,
851
1146
  openSearchIndex,
852
- deleteIndexDb
1147
+ deleteIndexDb,
1148
+ runtimeBinDir,
1149
+ installRuntime,
1150
+ uninstallRuntime,
1151
+ isSelfUpdateDisabled,
1152
+ scheduleSelfUpdate,
1153
+ describeOutcome
853
1154
  };
@@ -38,7 +38,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
38
38
  ));
39
39
 
40
40
  // src/version.ts
41
- var VERSION = true ? "0.1.13" : "0.0.0-dev";
41
+ var VERSION = true ? "0.1.14" : "0.0.0-dev";
42
42
 
43
43
  export {
44
44
  __require,
@@ -7,15 +7,19 @@ import {
7
7
  deleteIndexDb,
8
8
  drainInbox,
9
9
  ingestFile,
10
+ installRuntime,
11
+ isSelfUpdateDisabled,
10
12
  openSearchIndex,
11
- resolveCorpusDir
12
- } from "./chunk-3JQ7CGCP.js";
13
+ resolveCorpusDir,
14
+ runtimeBinDir,
15
+ uninstallRuntime
16
+ } from "./chunk-JS2GFGNW.js";
13
17
  import {
14
18
  VERSION,
15
19
  __commonJS,
16
20
  __require,
17
21
  __toESM
18
- } from "./chunk-7DAYOGB6.js";
22
+ } from "./chunk-QGVH3T5W.js";
19
23
 
20
24
  // ../node_modules/isexe/windows.js
21
25
  var require_windows = __commonJS({
@@ -455,19 +459,19 @@ var require_enoent = __commonJS({
455
459
  spawnargs: original.args
456
460
  });
457
461
  }
458
- function hookChildProcess(cp2, parsed) {
462
+ function hookChildProcess(cp, parsed) {
459
463
  if (!isWin) {
460
464
  return;
461
465
  }
462
- const originalEmit = cp2.emit;
463
- cp2.emit = function(name, arg1) {
466
+ const originalEmit = cp.emit;
467
+ cp.emit = function(name, arg1) {
464
468
  if (name === "exit") {
465
469
  const err = verifyENOENT(arg1, parsed);
466
470
  if (err) {
467
- return originalEmit.call(cp2, "error", err);
471
+ return originalEmit.call(cp, "error", err);
468
472
  }
469
473
  }
470
- return originalEmit.apply(cp2, arguments);
474
+ return originalEmit.apply(cp, arguments);
471
475
  };
472
476
  }
473
477
  function verifyENOENT(status, parsed) {
@@ -495,18 +499,18 @@ var require_enoent = __commonJS({
495
499
  var require_cross_spawn = __commonJS({
496
500
  "../node_modules/cross-spawn/index.js"(exports, module) {
497
501
  "use strict";
498
- var cp2 = __require("child_process");
502
+ var cp = __require("child_process");
499
503
  var parse = require_parse();
500
504
  var enoent = require_enoent();
501
505
  function spawn(command, args, options) {
502
506
  const parsed = parse(command, args, options);
503
- const spawned = cp2.spawn(parsed.command, parsed.args, parsed.options);
507
+ const spawned = cp.spawn(parsed.command, parsed.args, parsed.options);
504
508
  enoent.hookChildProcess(spawned, parsed);
505
509
  return spawned;
506
510
  }
507
511
  function spawnSync(command, args, options) {
508
512
  const parsed = parse(command, args, options);
509
- const result = cp2.spawnSync(parsed.command, parsed.args, parsed.options);
513
+ const result = cp.spawnSync(parsed.command, parsed.args, parsed.options);
510
514
  result.error = result.error || enoent.verifyENOENTSync(result.status, parsed);
511
515
  return result;
512
516
  }
@@ -1602,9 +1606,9 @@ var NodeFsHandler = class {
1602
1606
  if (this.fsw.closed) {
1603
1607
  return;
1604
1608
  }
1605
- const dirname7 = sysPath.dirname(file);
1609
+ const dirname6 = sysPath.dirname(file);
1606
1610
  const basename3 = sysPath.basename(file);
1607
- const parent = this.fsw._getWatchedDir(dirname7);
1611
+ const parent = this.fsw._getWatchedDir(dirname6);
1608
1612
  let prevStats = stats;
1609
1613
  if (parent.has(basename3))
1610
1614
  return;
@@ -1631,7 +1635,7 @@ var NodeFsHandler = class {
1631
1635
  prevStats = newStats2;
1632
1636
  }
1633
1637
  } catch (error) {
1634
- this.fsw._remove(dirname7, basename3);
1638
+ this.fsw._remove(dirname6, basename3);
1635
1639
  }
1636
1640
  } else if (parent.has(basename3)) {
1637
1641
  const at = newStats.atimeMs;
@@ -3420,100 +3424,14 @@ async function unregisterClients(options = {}) {
3420
3424
  }
3421
3425
 
3422
3426
  // src/doctor.ts
3423
- import { existsSync as existsSync2, readFileSync } from "fs";
3424
- import { delimiter, dirname as dirname6, isAbsolute as isAbsolute2, join as join6, sep } from "path";
3425
- import { homedir as homedir4, platform as platform3 } from "os";
3426
-
3427
- // src/runtimeInstall.ts
3428
- import { cp, mkdir as mkdir3, readdir as readdir3, rename, rm as rm2, writeFile as writeFile3 } from "fs/promises";
3429
- import { existsSync } from "fs";
3430
- import { homedir as homedir3 } from "os";
3431
- import { dirname as dirname5, join as join5 } from "path";
3432
- import { fileURLToPath as fileURLToPath2 } from "url";
3433
- function defaultSourceDir() {
3434
- return dirname5(fileURLToPath2(import.meta.url));
3435
- }
3436
- function runtimeBinDir(home2 = homedir3()) {
3437
- return join5(home2, ".llmnesia", "bin");
3438
- }
3439
- async function installRuntime(options = {}) {
3440
- const home2 = options.home ?? homedir3();
3441
- const sourceDir = options.sourceDir ?? defaultSourceDir();
3442
- const nodePath = options.nodePath ?? process.execPath;
3443
- const binDir = runtimeBinDir(home2);
3444
- const sourceEntry = join5(sourceDir, "cli.js");
3445
- if (!existsSync(sourceEntry)) {
3446
- throw new Error(
3447
- `Cannot locate the built runtime: no cli.js in ${sourceDir}. Run this from an installed package (npx -y @llmnesia/mcp@latest install) or build first (npm run build).`
3448
- );
3449
- }
3450
- const parentDir = dirname5(binDir);
3451
- const nonce = `${process.pid}-${Date.now()}`;
3452
- const stagingDir = join5(parentDir, `.bin-install-${nonce}`);
3453
- const backupDir = join5(parentDir, `.bin-backup-${nonce}`);
3454
- await mkdir3(parentDir, { recursive: true });
3455
- await rm2(stagingDir, { recursive: true, force: true });
3456
- await rm2(backupDir, { recursive: true, force: true });
3457
- await mkdir3(stagingDir, { recursive: true });
3458
- try {
3459
- for (const name of await readdir3(sourceDir)) {
3460
- await cp(join5(sourceDir, name), join5(stagingDir, name), { recursive: true });
3461
- }
3462
- await writeFile3(
3463
- join5(stagingDir, "package.json"),
3464
- `${JSON.stringify(
3465
- {
3466
- name: "llmnesia-mcp-runtime",
3467
- private: true,
3468
- type: "module",
3469
- comment: "Installed copy of @llmnesia/mcp's dist. Managed by the LLMnesia installer; edits are lost on reinstall."
3470
- },
3471
- null,
3472
- 2
3473
- )}
3474
- `,
3475
- "utf8"
3476
- );
3477
- const hadPreviousRuntime = existsSync(binDir);
3478
- if (hadPreviousRuntime) {
3479
- await rename(binDir, backupDir);
3480
- }
3481
- try {
3482
- await rename(stagingDir, binDir);
3483
- } catch (err) {
3484
- if (hadPreviousRuntime && !existsSync(binDir) && existsSync(backupDir)) {
3485
- await rename(backupDir, binDir);
3486
- }
3487
- throw err;
3488
- }
3489
- await rm2(backupDir, { recursive: true, force: true });
3490
- } finally {
3491
- await rm2(stagingDir, { recursive: true, force: true });
3492
- }
3493
- const entry = join5(binDir, "cli.js");
3494
- return {
3495
- binDir,
3496
- entry,
3497
- serve: { command: nodePath, args: [entry, "serve"] },
3498
- sourceDir,
3499
- nodePath
3500
- };
3501
- }
3502
- async function uninstallRuntime(home2 = homedir3()) {
3503
- const binDir = runtimeBinDir(home2);
3504
- if (!existsSync(binDir)) {
3505
- return null;
3506
- }
3507
- await rm2(binDir, { recursive: true, force: true });
3508
- return binDir;
3509
- }
3510
-
3511
- // src/doctor.ts
3427
+ import { existsSync, readFileSync } from "fs";
3428
+ import { delimiter, dirname as dirname5, isAbsolute as isAbsolute2, join as join5, sep } from "path";
3429
+ import { homedir as homedir3, platform as platform3 } from "os";
3512
3430
  var INSTALL_COMMAND = "npx -y @llmnesia/mcp@latest install";
3513
3431
  function checkNativeHostManifest(home2, osPlatform2) {
3514
3432
  if (osPlatform2 === "win32") {
3515
- const manifestPath = join6(home2, ".llmnesia", `${HOST_NAME}.json`);
3516
- if (existsSync2(manifestPath)) {
3433
+ const manifestPath = join5(home2, ".llmnesia", `${HOST_NAME}.json`);
3434
+ if (existsSync(manifestPath)) {
3517
3435
  return {
3518
3436
  name: "Native messaging host",
3519
3437
  status: "ok",
@@ -3528,11 +3446,11 @@ function checkNativeHostManifest(home2, osPlatform2) {
3528
3446
  }
3529
3447
  const found = [];
3530
3448
  for (const [label, browserDir] of posixBrowsers(home2, osPlatform2)) {
3531
- if (!existsSync2(browserDir)) {
3449
+ if (!existsSync(browserDir)) {
3532
3450
  continue;
3533
3451
  }
3534
- const manifestPath = join6(browserDir, "NativeMessagingHosts", `${HOST_NAME}.json`);
3535
- if (existsSync2(manifestPath)) {
3452
+ const manifestPath = join5(browserDir, "NativeMessagingHosts", `${HOST_NAME}.json`);
3453
+ if (existsSync(manifestPath)) {
3536
3454
  found.push(label);
3537
3455
  }
3538
3456
  }
@@ -3546,8 +3464,8 @@ function checkNativeHostManifest(home2, osPlatform2) {
3546
3464
  };
3547
3465
  }
3548
3466
  async function checkCorpus(home2, corpusRoot) {
3549
- const configPath = join6(home2, ".llmnesia", "config.json");
3550
- if (!corpusRoot && !existsSync2(configPath)) {
3467
+ const configPath = join5(home2, ".llmnesia", "config.json");
3468
+ if (!corpusRoot && !existsSync(configPath)) {
3551
3469
  return {
3552
3470
  name: "Corpus config",
3553
3471
  status: "missing",
@@ -3555,7 +3473,7 @@ async function checkCorpus(home2, corpusRoot) {
3555
3473
  };
3556
3474
  }
3557
3475
  const root = corpusRoot ?? resolveCorpusDir();
3558
- if (!existsSync2(root)) {
3476
+ if (!existsSync(root)) {
3559
3477
  return {
3560
3478
  name: "Corpus",
3561
3479
  status: "warn",
@@ -3588,14 +3506,14 @@ async function checkCorpus(home2, corpusRoot) {
3588
3506
  function commandProblem(command, args = []) {
3589
3507
  const looksLikePath = isAbsolute2(command) || command.includes(sep) || command.includes("/");
3590
3508
  if (looksLikePath) {
3591
- if (!existsSync2(command)) {
3509
+ if (!existsSync(command)) {
3592
3510
  return `its command does not exist: ${command}`;
3593
3511
  }
3594
3512
  } else if (!resolveOnPath(command)) {
3595
3513
  return `its command "${command}" is not on PATH. GUI clients get a minimal PATH (no nvm, no homebrew), so a bare command name is unreliable even when your shell can find it. Re-run: ${INSTALL_COMMAND}`;
3596
3514
  }
3597
3515
  const script = args.find((a) => a.endsWith(".js") || a.endsWith(".mjs"));
3598
- if (script && (isAbsolute2(script) || script.includes(sep)) && !existsSync2(script)) {
3516
+ if (script && (isAbsolute2(script) || script.includes(sep)) && !existsSync(script)) {
3599
3517
  return `its script does not exist: ${script}. Re-run: ${INSTALL_COMMAND}`;
3600
3518
  }
3601
3519
  return null;
@@ -3607,29 +3525,72 @@ function resolveOnPath(command) {
3607
3525
  continue;
3608
3526
  }
3609
3527
  for (const ext of exts) {
3610
- if (existsSync2(join6(dir, command + ext))) {
3528
+ if (existsSync(join5(dir, command + ext))) {
3611
3529
  return true;
3612
3530
  }
3613
3531
  }
3614
3532
  }
3615
3533
  return false;
3616
3534
  }
3535
+ function installedRuntimeVersion(home2) {
3536
+ try {
3537
+ const parsed = JSON.parse(readFileSync(join5(runtimeBinDir(home2), "package.json"), "utf8"));
3538
+ return typeof parsed.version === "string" ? parsed.version : null;
3539
+ } catch {
3540
+ return null;
3541
+ }
3542
+ }
3617
3543
  function checkRuntime(home2) {
3618
- const entry = join6(runtimeBinDir(home2), "cli.js");
3619
- if (existsSync2(entry)) {
3620
- return { name: "MCP server runtime", status: "ok", detail: `Installed at ${entry}.` };
3544
+ const entry = join5(runtimeBinDir(home2), "cli.js");
3545
+ if (!existsSync(entry)) {
3546
+ return {
3547
+ name: "MCP server runtime",
3548
+ status: "missing",
3549
+ detail: `No runtime at ${entry}. Clients spawn this copy, so registration alone is not enough. Run: ${INSTALL_COMMAND}`
3550
+ };
3621
3551
  }
3622
- return {
3623
- name: "MCP server runtime",
3624
- status: "missing",
3625
- detail: `No runtime at ${entry}. Clients spawn this copy, so registration alone is not enough. Run: ${INSTALL_COMMAND}`
3626
- };
3552
+ const installed = installedRuntimeVersion(home2);
3553
+ if (installed === null) {
3554
+ return {
3555
+ name: "MCP server runtime",
3556
+ status: "warn",
3557
+ detail: `Installed at ${entry}, but its version is not recorded, so it predates automatic updates. Refresh it with: ${INSTALL_COMMAND}`
3558
+ };
3559
+ }
3560
+ const suffix = installed === VERSION ? "" : ` (this command is ${VERSION})`;
3561
+ return { name: "MCP server runtime", status: "ok", detail: `Version ${installed} at ${entry}${suffix}.` };
3562
+ }
3563
+ function checkAutoUpdate(home2, env) {
3564
+ const name = "Automatic updates";
3565
+ if (isSelfUpdateDisabled(env)) {
3566
+ return { name, status: "warn", detail: "Turned off by LLMNESIA_NO_AUTO_UPDATE." };
3567
+ }
3568
+ let state = {};
3569
+ try {
3570
+ state = JSON.parse(readFileSync(join5(home2, ".llmnesia", "update-state.json"), "utf8"));
3571
+ } catch {
3572
+ return {
3573
+ name,
3574
+ status: "ok",
3575
+ detail: "On. No check has run yet; the server checks shortly after it starts."
3576
+ };
3577
+ }
3578
+ if (state.lastFailedVersion) {
3579
+ return {
3580
+ name,
3581
+ status: "warn",
3582
+ detail: `On, but ${state.lastFailedVersion} could not be installed automatically. Run: ${INSTALL_COMMAND}`
3583
+ };
3584
+ }
3585
+ const checkedAt = state.lastCheckedAt ? new Date(state.lastCheckedAt).toISOString() : "never";
3586
+ const latest = state.lastSeenVersion ? `; latest published ${state.lastSeenVersion}` : "";
3587
+ return { name, status: "ok", detail: `On. Last checked ${checkedAt}${latest}.` };
3627
3588
  }
3628
3589
  async function checkJsonClient(label, configPath) {
3629
- if (!existsSync2(dirname6(configPath))) {
3630
- return { name: label, status: "warn", detail: `Not installed (no ${dirname6(configPath)}).` };
3590
+ if (!existsSync(dirname5(configPath))) {
3591
+ return { name: label, status: "warn", detail: `Not installed (no ${dirname5(configPath)}).` };
3631
3592
  }
3632
- if (!existsSync2(configPath)) {
3593
+ if (!existsSync(configPath)) {
3633
3594
  return {
3634
3595
  name: label,
3635
3596
  status: "missing",
@@ -3718,8 +3679,8 @@ async function checkCodex(home2, cli) {
3718
3679
  return { name: "Codex", status: "warn", detail: `Could not inspect Codex MCP settings: ${err instanceof Error ? err.message : String(err)}` };
3719
3680
  }
3720
3681
  }
3721
- const configPath = join6(home2, ".codex", "config.toml");
3722
- if (!existsSync2(configPath)) {
3682
+ const configPath = join5(home2, ".codex", "config.toml");
3683
+ if (!existsSync(configPath)) {
3723
3684
  return { name: "Codex", status: "warn", detail: `Not installed (no ${configPath}), or never configured.` };
3724
3685
  }
3725
3686
  let text;
@@ -3747,11 +3708,11 @@ async function checkCodex(home2, cli) {
3747
3708
  };
3748
3709
  }
3749
3710
  async function runDoctor(options = {}) {
3750
- const home2 = options.home ?? homedir4();
3711
+ const home2 = options.home ?? homedir3();
3751
3712
  const osPlatform2 = options.osPlatform ?? platform3();
3752
3713
  const claudeCli = options.claudeCli ?? realClaudeCli;
3753
3714
  const codexCli = options.codexCli ?? realCodexCli;
3754
- const claudeDesktopPath = osPlatform2 === "darwin" ? join6(home2, "Library", "Application Support", "Claude", "claude_desktop_config.json") : osPlatform2 === "win32" ? join6(process.env.APPDATA || join6(home2, "AppData", "Roaming"), "Claude", "claude_desktop_config.json") : null;
3715
+ const claudeDesktopPath = osPlatform2 === "darwin" ? join5(home2, "Library", "Application Support", "Claude", "claude_desktop_config.json") : osPlatform2 === "win32" ? join5(process.env.APPDATA || join5(home2, "AppData", "Roaming"), "Claude", "claude_desktop_config.json") : null;
3755
3716
  const checks = [];
3756
3717
  const run = async (name, fn) => {
3757
3718
  try {
@@ -3766,11 +3727,12 @@ async function runDoctor(options = {}) {
3766
3727
  };
3767
3728
  await run("Native messaging host", () => checkNativeHostManifest(home2, osPlatform2));
3768
3729
  await run("MCP server runtime", () => checkRuntime(home2));
3730
+ await run("Automatic updates", () => checkAutoUpdate(home2, options.env ?? process.env));
3769
3731
  await run("Corpus", () => checkCorpus(home2, options.corpusRoot));
3770
3732
  if (claudeDesktopPath) {
3771
3733
  await run("Claude Desktop", () => checkJsonClient("Claude Desktop", claudeDesktopPath));
3772
3734
  }
3773
- await run("Cursor", () => checkJsonClient("Cursor", join6(home2, ".cursor", "mcp.json")));
3735
+ await run("Cursor", () => checkJsonClient("Cursor", join5(home2, ".cursor", "mcp.json")));
3774
3736
  await run("Claude Code", () => checkClaudeCode(claudeCli));
3775
3737
  await run("Codex", () => checkCodex(home2, codexCli));
3776
3738
  checks.push({
@@ -3857,7 +3819,7 @@ function summarizeClientRegistrations(results) {
3857
3819
  return { connected, prepared, errors };
3858
3820
  }
3859
3821
  function formatInstallSuccess() {
3860
- return "\nSetup complete.\nFully close and reopen your browser and any desktop AI app you use.\nLLMnesia will be available in the desktop AI apps connected during setup.\nLLMnesia Settings will confirm when your history is ready.\n";
3822
+ return "\nSetup complete.\nFully close and reopen your browser and any desktop AI app you use.\nLLMnesia will be available in the desktop AI apps connected during setup.\nLLMnesia Settings will confirm when your history is ready.\n\nLLMnesia keeps itself up to date, checking for a new version once a day.\nOnly a version number is fetched. Your conversations never leave this machine.\nTo turn updates off, set LLMNESIA_NO_AUTO_UPDATE=1.\n";
3861
3823
  }
3862
3824
  async function runInstall(positionals, options, flags) {
3863
3825
  let hadProblems = false;
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-7DAYOGB6.js";
6
+ } from "./chunk-QGVH3T5W.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-OJM47LQR.js");
144
+ const { main } = await import("./cli-I2P4TSZW.js");
145
145
  return main();
146
146
  }
147
147
  bootstrap().then((code) => {
Binary file
package/dist/mcp.js CHANGED
@@ -3,17 +3,19 @@ import { createRequire as __llmnesiaCreateRequire } from "node:module";
3
3
  const require = __llmnesiaCreateRequire(import.meta.url);
4
4
  import {
5
5
  Corpus,
6
+ describeOutcome,
6
7
  drainInbox,
7
8
  openSearchIndex,
8
9
  pendingInboxFiles,
9
- resolveCorpusDir
10
- } from "./chunk-3JQ7CGCP.js";
10
+ resolveCorpusDir,
11
+ scheduleSelfUpdate
12
+ } from "./chunk-JS2GFGNW.js";
11
13
  import {
12
14
  VERSION,
13
15
  __commonJS,
14
16
  __export,
15
17
  __toESM
16
- } from "./chunk-7DAYOGB6.js";
18
+ } from "./chunk-QGVH3T5W.js";
17
19
 
18
20
  // ../node_modules/ajv/dist/compile/codegen/code.js
19
21
  var require_code = __commonJS({
@@ -21656,7 +21658,17 @@ async function runServe() {
21656
21658
  })
21657
21659
  );
21658
21660
  }
21659
- server.server.oninitialized = () => service.start();
21661
+ server.server.oninitialized = () => {
21662
+ service.start();
21663
+ scheduleSelfUpdate({
21664
+ onOutcome: (outcome) => {
21665
+ if (outcome.action === "updated" || outcome.action === "update-failed") {
21666
+ process.stderr.write(`llmnesia: ${describeOutcome(outcome)}
21667
+ `);
21668
+ }
21669
+ }
21670
+ });
21671
+ };
21660
21672
  const transport = new StdioServerTransport();
21661
21673
  await server.connect(transport);
21662
21674
  process.stderr.write(`llmnesia MCP protocol ready; waiting for client initialization (${root})
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llmnesia/mcp",
3
- "version": "0.1.13",
3
+ "version": "0.1.14",
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.1.13",
6
+ "version": "0.1.14",
7
7
  "packages": [
8
8
  {
9
9
  "registryType": "npm",
10
10
  "registryBaseUrl": "https://registry.npmjs.org",
11
11
  "identifier": "@llmnesia/mcp",
12
- "version": "0.1.13",
12
+ "version": "0.1.14",
13
13
  "transport": {
14
14
  "type": "stdio"
15
15
  }