@llmnesia/mcp 0.1.12 → 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.
- package/README.md +44 -8
- package/dist/{chunk-3JQ7CGCP.js → chunk-JS2GFGNW.js} +307 -6
- package/dist/{chunk-MI5RBP7Y.js → chunk-QGVH3T5W.js} +1 -1
- package/dist/{cli-GKHVUCEP.js → cli-I2P4TSZW.js} +114 -198
- package/dist/cli.js +2 -2
- package/dist/llmnesia.mcpb +0 -0
- package/dist/mcp.js +39 -25
- package/package.json +1 -1
- package/server.json +2 -2
package/README.md
CHANGED
|
@@ -48,19 +48,38 @@ The installer:
|
|
|
48
48
|
1. sets up the native host that bridges the browser extension to your local
|
|
49
49
|
corpus,
|
|
50
50
|
2. copies the server into `~/.llmnesia/bin` so it has a permanent home,
|
|
51
|
-
3. sets up the supported desktop AI apps (Claude Code,
|
|
51
|
+
3. sets up the supported desktop AI apps (Claude Code, Cursor,
|
|
52
52
|
and Codex/ChatGPT), and
|
|
53
53
|
4. registers that copy using the client's own CLI where available, or with an
|
|
54
54
|
automatic backup before editing its documented user config. It prepares all
|
|
55
55
|
of those standard config paths, so an app installed later does not require
|
|
56
56
|
running setup again.
|
|
57
57
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
58
|
+
Claude Desktop on Windows does not provide a supported API for third-party
|
|
59
|
+
installers to add a local desktop extension. LLMnesia therefore leaves any
|
|
60
|
+
existing Claude configuration untouched rather than claiming that Claude has
|
|
61
|
+
been connected.
|
|
62
|
+
|
|
63
|
+
### Claude Desktop on Windows (current one-time manual step)
|
|
64
|
+
|
|
65
|
+
Until LLMnesia is available in Claude's extension directory, Claude Desktop on
|
|
66
|
+
Windows has one extra, manual step. First run `install` above so the local
|
|
67
|
+
browser bridge and corpus exist. Then download the official LLMnesia
|
|
68
|
+
`llmnesia.mcpb` desktop-extension file from LLMnesia and use Claude Desktop's
|
|
69
|
+
**Settings → Extensions → Install extension** flow to install it. Fully quit
|
|
70
|
+
and reopen Claude Desktop afterwards.
|
|
71
|
+
|
|
72
|
+
This is deliberately separate from the npm command: Claude does not provide a
|
|
73
|
+
supported Windows API for another installer to silently add an `.mcpb`
|
|
74
|
+
extension. The command therefore never edits Claude's internal extension state
|
|
75
|
+
or reports a false success. The packaged extension itself is read-only: it can
|
|
76
|
+
search, list, and read the local corpus but cannot modify it.
|
|
77
|
+
|
|
78
|
+
The future Claude Directory listing is a convenience distribution route, not a
|
|
79
|
+
dependency for the general LLMnesia release. It will replace this one manual
|
|
80
|
+
Claude step with Claude's own in-app install button if Anthropic selects the
|
|
81
|
+
extension. See [`docs/CLAUDE_DESKTOP_RELEASE_PLAN.md`](../docs/CLAUDE_DESKTOP_RELEASE_PLAN.md)
|
|
82
|
+
for the release and directory-submission plan.
|
|
64
83
|
|
|
65
84
|
The `install` command is the approval to perform those setup steps. It does not
|
|
66
85
|
interrupt setup with a separate prompt for every detected app, and it never
|
|
@@ -128,7 +147,7 @@ received data — check it before concluding a chat does not exist.
|
|
|
128
147
|
| Client | How `install` wires it |
|
|
129
148
|
| --- | --- |
|
|
130
149
|
| Claude Code | Runs `claude mcp add --scope user` with the absolute Node and installed runtime paths when available. Otherwise it writes the documented user-scope `~/.claude.json` entry, so Windows users do not need the `claude` command on PATH. |
|
|
131
|
-
| Claude Desktop |
|
|
150
|
+
| Claude Desktop | macOS uses the working local JSON registration. On Windows, run `install` for browser sync, then install the official `llmnesia.mcpb` once in Claude Desktop; Claude exposes no supported external installation API for custom desktop extensions. |
|
|
132
151
|
| Cursor | Edits `~/.cursor/mcp.json`, preparing it in advance if Cursor is installed later. |
|
|
133
152
|
| Codex / ChatGPT desktop | Runs `codex mcp add` with the absolute runtime paths when available. If the bundled CLI is unavailable, the installer safely edits `~/.codex/config.toml` itself and backs up existing settings first, including preparing it for a later installation. Read-only LLMnesia tools are configured to run without repeated approval prompts; writes still ask. |
|
|
134
153
|
| Others (Windsurf, Zed, LM Studio, …) | Prints the generic stdio block for you to paste. |
|
|
@@ -185,6 +204,23 @@ llmnesia-mcp --version # print the installed version
|
|
|
185
204
|
|
|
186
205
|
Questions and bug reports: [hello@llmnesia.com](mailto:hello@llmnesia.com).
|
|
187
206
|
|
|
207
|
+
## Privacy Policy
|
|
208
|
+
|
|
209
|
+
The MCP server reads and writes only the LLMnesia corpus stored on the user's
|
|
210
|
+
own computer. It does not upload conversation content, titles, URLs, prompts,
|
|
211
|
+
responses, or search queries to LLMnesia or to any third party, and it makes no
|
|
212
|
+
analytics or advertising requests of its own.
|
|
213
|
+
|
|
214
|
+
The browser extension separately sends limited, non-content product analytics
|
|
215
|
+
to PostHog. Conversation data and MCP searches are excluded. The complete
|
|
216
|
+
policy explains collection, local storage, third-party processing, retention,
|
|
217
|
+
deletion, and contact details:
|
|
218
|
+
[LLMnesia Privacy Policy](https://www.llmnesia.com/privacy-policy).
|
|
219
|
+
|
|
220
|
+
The local MCP corpus remains on the device until the user deletes it. Running
|
|
221
|
+
`uninstall` deliberately leaves that corpus intact so uninstalling the helper
|
|
222
|
+
cannot accidentally destroy the user's history.
|
|
223
|
+
|
|
188
224
|
## License
|
|
189
225
|
|
|
190
226
|
MIT — see [LICENSE](./LICENSE).
|
|
@@ -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
|
|
211
|
-
* `
|
|
212
|
-
*
|
|
213
|
-
*
|
|
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
|
};
|
|
@@ -7,15 +7,19 @@ import {
|
|
|
7
7
|
deleteIndexDb,
|
|
8
8
|
drainInbox,
|
|
9
9
|
ingestFile,
|
|
10
|
+
installRuntime,
|
|
11
|
+
isSelfUpdateDisabled,
|
|
10
12
|
openSearchIndex,
|
|
11
|
-
resolveCorpusDir
|
|
12
|
-
|
|
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-
|
|
22
|
+
} from "./chunk-QGVH3T5W.js";
|
|
19
23
|
|
|
20
24
|
// ../node_modules/isexe/windows.js
|
|
21
25
|
var require_windows = __commonJS({
|
|
@@ -41,15 +45,15 @@ var require_windows = __commonJS({
|
|
|
41
45
|
}
|
|
42
46
|
return false;
|
|
43
47
|
}
|
|
44
|
-
function checkStat(
|
|
45
|
-
if (!
|
|
48
|
+
function checkStat(stat6, path, options) {
|
|
49
|
+
if (!stat6.isSymbolicLink() && !stat6.isFile()) {
|
|
46
50
|
return false;
|
|
47
51
|
}
|
|
48
52
|
return checkPathExt(path, options);
|
|
49
53
|
}
|
|
50
54
|
function isexe(path, options, cb) {
|
|
51
|
-
fs.stat(path, function(er,
|
|
52
|
-
cb(er, er ? false : checkStat(
|
|
55
|
+
fs.stat(path, function(er, stat6) {
|
|
56
|
+
cb(er, er ? false : checkStat(stat6, path, options));
|
|
53
57
|
});
|
|
54
58
|
}
|
|
55
59
|
function sync(path, options) {
|
|
@@ -66,20 +70,20 @@ var require_mode = __commonJS({
|
|
|
66
70
|
isexe.sync = sync;
|
|
67
71
|
var fs = __require("fs");
|
|
68
72
|
function isexe(path, options, cb) {
|
|
69
|
-
fs.stat(path, function(er,
|
|
70
|
-
cb(er, er ? false : checkStat(
|
|
73
|
+
fs.stat(path, function(er, stat6) {
|
|
74
|
+
cb(er, er ? false : checkStat(stat6, options));
|
|
71
75
|
});
|
|
72
76
|
}
|
|
73
77
|
function sync(path, options) {
|
|
74
78
|
return checkStat(fs.statSync(path), options);
|
|
75
79
|
}
|
|
76
|
-
function checkStat(
|
|
77
|
-
return
|
|
80
|
+
function checkStat(stat6, options) {
|
|
81
|
+
return stat6.isFile() && checkMode(stat6, options);
|
|
78
82
|
}
|
|
79
|
-
function checkMode(
|
|
80
|
-
var mod =
|
|
81
|
-
var uid =
|
|
82
|
-
var gid =
|
|
83
|
+
function checkMode(stat6, options) {
|
|
84
|
+
var mod = stat6.mode;
|
|
85
|
+
var uid = stat6.uid;
|
|
86
|
+
var gid = stat6.gid;
|
|
83
87
|
var myUid = options.uid !== void 0 ? options.uid : process.getuid && process.getuid();
|
|
84
88
|
var myGid = options.gid !== void 0 ? options.gid : process.getgid && process.getgid();
|
|
85
89
|
var u = parseInt("100", 8);
|
|
@@ -251,8 +255,8 @@ var require_path_key = __commonJS({
|
|
|
251
255
|
"use strict";
|
|
252
256
|
var pathKey = (options = {}) => {
|
|
253
257
|
const environment = options.env || process.env;
|
|
254
|
-
const
|
|
255
|
-
if (
|
|
258
|
+
const platform4 = options.platform || process.platform;
|
|
259
|
+
if (platform4 !== "win32") {
|
|
256
260
|
return "PATH";
|
|
257
261
|
}
|
|
258
262
|
return Object.keys(environment).reverse().find((key) => key.toUpperCase() === "PATH") || "Path";
|
|
@@ -455,19 +459,19 @@ var require_enoent = __commonJS({
|
|
|
455
459
|
spawnargs: original.args
|
|
456
460
|
});
|
|
457
461
|
}
|
|
458
|
-
function hookChildProcess(
|
|
462
|
+
function hookChildProcess(cp, parsed) {
|
|
459
463
|
if (!isWin) {
|
|
460
464
|
return;
|
|
461
465
|
}
|
|
462
|
-
const originalEmit =
|
|
463
|
-
|
|
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(
|
|
471
|
+
return originalEmit.call(cp, "error", err);
|
|
468
472
|
}
|
|
469
473
|
}
|
|
470
|
-
return originalEmit.apply(
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
1609
|
+
const dirname6 = sysPath.dirname(file);
|
|
1606
1610
|
const basename3 = sysPath.basename(file);
|
|
1607
|
-
const parent = this.fsw._getWatchedDir(
|
|
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(
|
|
1638
|
+
this.fsw._remove(dirname6, basename3);
|
|
1635
1639
|
}
|
|
1636
1640
|
} else if (parent.has(basename3)) {
|
|
1637
1641
|
const at = newStats.atimeMs;
|
|
@@ -2439,8 +2443,8 @@ var FSWatcher = class extends EventEmitter {
|
|
|
2439
2443
|
}
|
|
2440
2444
|
return this._userIgnored(path, stats);
|
|
2441
2445
|
}
|
|
2442
|
-
_isntIgnored(path,
|
|
2443
|
-
return !this._isIgnored(path,
|
|
2446
|
+
_isntIgnored(path, stat6) {
|
|
2447
|
+
return !this._isIgnored(path, stat6);
|
|
2444
2448
|
}
|
|
2445
2449
|
/**
|
|
2446
2450
|
* Provides a set of common helpers and properties relating to symlink handling.
|
|
@@ -3259,14 +3263,11 @@ async function registerClients(options) {
|
|
|
3259
3263
|
);
|
|
3260
3264
|
if (claudeDesktop) {
|
|
3261
3265
|
if (osPlatform2 === "win32") {
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
detail: "Claude Desktop uses the packaged LLMnesia extension on Windows."
|
|
3268
|
-
}
|
|
3269
|
-
);
|
|
3266
|
+
results.push({
|
|
3267
|
+
client: "Claude Desktop",
|
|
3268
|
+
action: "skipped",
|
|
3269
|
+
detail: "Automatic Claude Desktop setup is not available on Windows; existing configuration was left unchanged."
|
|
3270
|
+
});
|
|
3270
3271
|
} else {
|
|
3271
3272
|
results.push(
|
|
3272
3273
|
await safe(
|
|
@@ -3423,100 +3424,14 @@ async function unregisterClients(options = {}) {
|
|
|
3423
3424
|
}
|
|
3424
3425
|
|
|
3425
3426
|
// src/doctor.ts
|
|
3426
|
-
import { existsSync
|
|
3427
|
-
import { delimiter, dirname as
|
|
3428
|
-
import { homedir as
|
|
3429
|
-
|
|
3430
|
-
// src/runtimeInstall.ts
|
|
3431
|
-
import { cp, mkdir as mkdir3, readdir as readdir3, rename, rm as rm2, writeFile as writeFile3 } from "fs/promises";
|
|
3432
|
-
import { existsSync } from "fs";
|
|
3433
|
-
import { homedir as homedir3 } from "os";
|
|
3434
|
-
import { dirname as dirname5, join as join5 } from "path";
|
|
3435
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3436
|
-
function defaultSourceDir() {
|
|
3437
|
-
return dirname5(fileURLToPath2(import.meta.url));
|
|
3438
|
-
}
|
|
3439
|
-
function runtimeBinDir(home2 = homedir3()) {
|
|
3440
|
-
return join5(home2, ".llmnesia", "bin");
|
|
3441
|
-
}
|
|
3442
|
-
async function installRuntime(options = {}) {
|
|
3443
|
-
const home2 = options.home ?? homedir3();
|
|
3444
|
-
const sourceDir = options.sourceDir ?? defaultSourceDir();
|
|
3445
|
-
const nodePath = options.nodePath ?? process.execPath;
|
|
3446
|
-
const binDir = runtimeBinDir(home2);
|
|
3447
|
-
const sourceEntry = join5(sourceDir, "cli.js");
|
|
3448
|
-
if (!existsSync(sourceEntry)) {
|
|
3449
|
-
throw new Error(
|
|
3450
|
-
`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).`
|
|
3451
|
-
);
|
|
3452
|
-
}
|
|
3453
|
-
const parentDir = dirname5(binDir);
|
|
3454
|
-
const nonce = `${process.pid}-${Date.now()}`;
|
|
3455
|
-
const stagingDir = join5(parentDir, `.bin-install-${nonce}`);
|
|
3456
|
-
const backupDir = join5(parentDir, `.bin-backup-${nonce}`);
|
|
3457
|
-
await mkdir3(parentDir, { recursive: true });
|
|
3458
|
-
await rm2(stagingDir, { recursive: true, force: true });
|
|
3459
|
-
await rm2(backupDir, { recursive: true, force: true });
|
|
3460
|
-
await mkdir3(stagingDir, { recursive: true });
|
|
3461
|
-
try {
|
|
3462
|
-
for (const name of await readdir3(sourceDir)) {
|
|
3463
|
-
await cp(join5(sourceDir, name), join5(stagingDir, name), { recursive: true });
|
|
3464
|
-
}
|
|
3465
|
-
await writeFile3(
|
|
3466
|
-
join5(stagingDir, "package.json"),
|
|
3467
|
-
`${JSON.stringify(
|
|
3468
|
-
{
|
|
3469
|
-
name: "llmnesia-mcp-runtime",
|
|
3470
|
-
private: true,
|
|
3471
|
-
type: "module",
|
|
3472
|
-
comment: "Installed copy of @llmnesia/mcp's dist. Managed by the LLMnesia installer; edits are lost on reinstall."
|
|
3473
|
-
},
|
|
3474
|
-
null,
|
|
3475
|
-
2
|
|
3476
|
-
)}
|
|
3477
|
-
`,
|
|
3478
|
-
"utf8"
|
|
3479
|
-
);
|
|
3480
|
-
const hadPreviousRuntime = existsSync(binDir);
|
|
3481
|
-
if (hadPreviousRuntime) {
|
|
3482
|
-
await rename(binDir, backupDir);
|
|
3483
|
-
}
|
|
3484
|
-
try {
|
|
3485
|
-
await rename(stagingDir, binDir);
|
|
3486
|
-
} catch (err) {
|
|
3487
|
-
if (hadPreviousRuntime && !existsSync(binDir) && existsSync(backupDir)) {
|
|
3488
|
-
await rename(backupDir, binDir);
|
|
3489
|
-
}
|
|
3490
|
-
throw err;
|
|
3491
|
-
}
|
|
3492
|
-
await rm2(backupDir, { recursive: true, force: true });
|
|
3493
|
-
} finally {
|
|
3494
|
-
await rm2(stagingDir, { recursive: true, force: true });
|
|
3495
|
-
}
|
|
3496
|
-
const entry = join5(binDir, "cli.js");
|
|
3497
|
-
return {
|
|
3498
|
-
binDir,
|
|
3499
|
-
entry,
|
|
3500
|
-
serve: { command: nodePath, args: [entry, "serve"] },
|
|
3501
|
-
sourceDir,
|
|
3502
|
-
nodePath
|
|
3503
|
-
};
|
|
3504
|
-
}
|
|
3505
|
-
async function uninstallRuntime(home2 = homedir3()) {
|
|
3506
|
-
const binDir = runtimeBinDir(home2);
|
|
3507
|
-
if (!existsSync(binDir)) {
|
|
3508
|
-
return null;
|
|
3509
|
-
}
|
|
3510
|
-
await rm2(binDir, { recursive: true, force: true });
|
|
3511
|
-
return binDir;
|
|
3512
|
-
}
|
|
3513
|
-
|
|
3514
|
-
// 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";
|
|
3515
3430
|
var INSTALL_COMMAND = "npx -y @llmnesia/mcp@latest install";
|
|
3516
3431
|
function checkNativeHostManifest(home2, osPlatform2) {
|
|
3517
3432
|
if (osPlatform2 === "win32") {
|
|
3518
|
-
const manifestPath =
|
|
3519
|
-
if (
|
|
3433
|
+
const manifestPath = join5(home2, ".llmnesia", `${HOST_NAME}.json`);
|
|
3434
|
+
if (existsSync(manifestPath)) {
|
|
3520
3435
|
return {
|
|
3521
3436
|
name: "Native messaging host",
|
|
3522
3437
|
status: "ok",
|
|
@@ -3531,11 +3446,11 @@ function checkNativeHostManifest(home2, osPlatform2) {
|
|
|
3531
3446
|
}
|
|
3532
3447
|
const found = [];
|
|
3533
3448
|
for (const [label, browserDir] of posixBrowsers(home2, osPlatform2)) {
|
|
3534
|
-
if (!
|
|
3449
|
+
if (!existsSync(browserDir)) {
|
|
3535
3450
|
continue;
|
|
3536
3451
|
}
|
|
3537
|
-
const manifestPath =
|
|
3538
|
-
if (
|
|
3452
|
+
const manifestPath = join5(browserDir, "NativeMessagingHosts", `${HOST_NAME}.json`);
|
|
3453
|
+
if (existsSync(manifestPath)) {
|
|
3539
3454
|
found.push(label);
|
|
3540
3455
|
}
|
|
3541
3456
|
}
|
|
@@ -3549,8 +3464,8 @@ function checkNativeHostManifest(home2, osPlatform2) {
|
|
|
3549
3464
|
};
|
|
3550
3465
|
}
|
|
3551
3466
|
async function checkCorpus(home2, corpusRoot) {
|
|
3552
|
-
const configPath =
|
|
3553
|
-
if (!corpusRoot && !
|
|
3467
|
+
const configPath = join5(home2, ".llmnesia", "config.json");
|
|
3468
|
+
if (!corpusRoot && !existsSync(configPath)) {
|
|
3554
3469
|
return {
|
|
3555
3470
|
name: "Corpus config",
|
|
3556
3471
|
status: "missing",
|
|
@@ -3558,7 +3473,7 @@ async function checkCorpus(home2, corpusRoot) {
|
|
|
3558
3473
|
};
|
|
3559
3474
|
}
|
|
3560
3475
|
const root = corpusRoot ?? resolveCorpusDir();
|
|
3561
|
-
if (!
|
|
3476
|
+
if (!existsSync(root)) {
|
|
3562
3477
|
return {
|
|
3563
3478
|
name: "Corpus",
|
|
3564
3479
|
status: "warn",
|
|
@@ -3591,14 +3506,14 @@ async function checkCorpus(home2, corpusRoot) {
|
|
|
3591
3506
|
function commandProblem(command, args = []) {
|
|
3592
3507
|
const looksLikePath = isAbsolute2(command) || command.includes(sep) || command.includes("/");
|
|
3593
3508
|
if (looksLikePath) {
|
|
3594
|
-
if (!
|
|
3509
|
+
if (!existsSync(command)) {
|
|
3595
3510
|
return `its command does not exist: ${command}`;
|
|
3596
3511
|
}
|
|
3597
3512
|
} else if (!resolveOnPath(command)) {
|
|
3598
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}`;
|
|
3599
3514
|
}
|
|
3600
3515
|
const script = args.find((a) => a.endsWith(".js") || a.endsWith(".mjs"));
|
|
3601
|
-
if (script && (isAbsolute2(script) || script.includes(sep)) && !
|
|
3516
|
+
if (script && (isAbsolute2(script) || script.includes(sep)) && !existsSync(script)) {
|
|
3602
3517
|
return `its script does not exist: ${script}. Re-run: ${INSTALL_COMMAND}`;
|
|
3603
3518
|
}
|
|
3604
3519
|
return null;
|
|
@@ -3610,29 +3525,72 @@ function resolveOnPath(command) {
|
|
|
3610
3525
|
continue;
|
|
3611
3526
|
}
|
|
3612
3527
|
for (const ext of exts) {
|
|
3613
|
-
if (
|
|
3528
|
+
if (existsSync(join5(dir, command + ext))) {
|
|
3614
3529
|
return true;
|
|
3615
3530
|
}
|
|
3616
3531
|
}
|
|
3617
3532
|
}
|
|
3618
3533
|
return false;
|
|
3619
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
|
+
}
|
|
3620
3543
|
function checkRuntime(home2) {
|
|
3621
|
-
const entry =
|
|
3622
|
-
if (
|
|
3623
|
-
return {
|
|
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
|
+
};
|
|
3624
3551
|
}
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
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}.` };
|
|
3630
3588
|
}
|
|
3631
3589
|
async function checkJsonClient(label, configPath) {
|
|
3632
|
-
if (!
|
|
3633
|
-
return { name: label, status: "warn", detail: `Not installed (no ${
|
|
3590
|
+
if (!existsSync(dirname5(configPath))) {
|
|
3591
|
+
return { name: label, status: "warn", detail: `Not installed (no ${dirname5(configPath)}).` };
|
|
3634
3592
|
}
|
|
3635
|
-
if (!
|
|
3593
|
+
if (!existsSync(configPath)) {
|
|
3636
3594
|
return {
|
|
3637
3595
|
name: label,
|
|
3638
3596
|
status: "missing",
|
|
@@ -3721,8 +3679,8 @@ async function checkCodex(home2, cli) {
|
|
|
3721
3679
|
return { name: "Codex", status: "warn", detail: `Could not inspect Codex MCP settings: ${err instanceof Error ? err.message : String(err)}` };
|
|
3722
3680
|
}
|
|
3723
3681
|
}
|
|
3724
|
-
const configPath =
|
|
3725
|
-
if (!
|
|
3682
|
+
const configPath = join5(home2, ".codex", "config.toml");
|
|
3683
|
+
if (!existsSync(configPath)) {
|
|
3726
3684
|
return { name: "Codex", status: "warn", detail: `Not installed (no ${configPath}), or never configured.` };
|
|
3727
3685
|
}
|
|
3728
3686
|
let text;
|
|
@@ -3750,11 +3708,11 @@ async function checkCodex(home2, cli) {
|
|
|
3750
3708
|
};
|
|
3751
3709
|
}
|
|
3752
3710
|
async function runDoctor(options = {}) {
|
|
3753
|
-
const home2 = options.home ??
|
|
3711
|
+
const home2 = options.home ?? homedir3();
|
|
3754
3712
|
const osPlatform2 = options.osPlatform ?? platform3();
|
|
3755
3713
|
const claudeCli = options.claudeCli ?? realClaudeCli;
|
|
3756
3714
|
const codexCli = options.codexCli ?? realCodexCli;
|
|
3757
|
-
const claudeDesktopPath = osPlatform2 === "darwin" ?
|
|
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;
|
|
3758
3716
|
const checks = [];
|
|
3759
3717
|
const run = async (name, fn) => {
|
|
3760
3718
|
try {
|
|
@@ -3769,11 +3727,12 @@ async function runDoctor(options = {}) {
|
|
|
3769
3727
|
};
|
|
3770
3728
|
await run("Native messaging host", () => checkNativeHostManifest(home2, osPlatform2));
|
|
3771
3729
|
await run("MCP server runtime", () => checkRuntime(home2));
|
|
3730
|
+
await run("Automatic updates", () => checkAutoUpdate(home2, options.env ?? process.env));
|
|
3772
3731
|
await run("Corpus", () => checkCorpus(home2, options.corpusRoot));
|
|
3773
3732
|
if (claudeDesktopPath) {
|
|
3774
3733
|
await run("Claude Desktop", () => checkJsonClient("Claude Desktop", claudeDesktopPath));
|
|
3775
3734
|
}
|
|
3776
|
-
await run("Cursor", () => checkJsonClient("Cursor",
|
|
3735
|
+
await run("Cursor", () => checkJsonClient("Cursor", join5(home2, ".cursor", "mcp.json")));
|
|
3777
3736
|
await run("Claude Code", () => checkClaudeCode(claudeCli));
|
|
3778
3737
|
await run("Codex", () => checkCodex(home2, codexCli));
|
|
3779
3738
|
checks.push({
|
|
@@ -3788,36 +3747,6 @@ function formatDoctorReport(report) {
|
|
|
3788
3747
|
return report.checks.map((c) => `${icon[c.status]} ${c.name}: ${c.detail}`).join("\n");
|
|
3789
3748
|
}
|
|
3790
3749
|
|
|
3791
|
-
// src/claudeDesktopExtension.ts
|
|
3792
|
-
import { execFile as execFile3 } from "child_process";
|
|
3793
|
-
import { access as access2, stat as stat6 } from "fs/promises";
|
|
3794
|
-
import { homedir as homedir5, platform as platform4 } from "os";
|
|
3795
|
-
import { join as join7 } from "path";
|
|
3796
|
-
import { promisify as promisify3 } from "util";
|
|
3797
|
-
var execFileAsync3 = promisify3(execFile3);
|
|
3798
|
-
async function defaultOpenBundle(bundlePath) {
|
|
3799
|
-
await execFileAsync3("rundll32.exe", ["url.dll,FileProtocolHandler", bundlePath], { windowsHide: true });
|
|
3800
|
-
}
|
|
3801
|
-
async function promptClaudeDesktopExtension(options) {
|
|
3802
|
-
const home2 = options.home ?? homedir5();
|
|
3803
|
-
const osPlatform2 = options.osPlatform ?? platform4();
|
|
3804
|
-
const bundlePath = join7(options.binDir, "llmnesia.mcpb");
|
|
3805
|
-
if (osPlatform2 !== "win32") {
|
|
3806
|
-
return { status: "unsupported", bundlePath };
|
|
3807
|
-
}
|
|
3808
|
-
const appData = options.appData ?? process.env.APPDATA ?? join7(home2, "AppData", "Roaming");
|
|
3809
|
-
try {
|
|
3810
|
-
if (!(await stat6(join7(appData, "Claude"))).isDirectory()) {
|
|
3811
|
-
return { status: "not-installed", bundlePath };
|
|
3812
|
-
}
|
|
3813
|
-
} catch {
|
|
3814
|
-
return { status: "not-installed", bundlePath };
|
|
3815
|
-
}
|
|
3816
|
-
await access2(bundlePath);
|
|
3817
|
-
await (options.openBundle ?? defaultOpenBundle)(bundlePath);
|
|
3818
|
-
return { status: "opened", bundlePath };
|
|
3819
|
-
}
|
|
3820
|
-
|
|
3821
3750
|
// src/cli.ts
|
|
3822
3751
|
var USAGE = `llmnesia-mcp ${VERSION}
|
|
3823
3752
|
|
|
@@ -3890,7 +3819,7 @@ function summarizeClientRegistrations(results) {
|
|
|
3890
3819
|
return { connected, prepared, errors };
|
|
3891
3820
|
}
|
|
3892
3821
|
function formatInstallSuccess() {
|
|
3893
|
-
return "\nSetup complete.\nFully close and reopen your browser and any desktop AI app you use.\nLLMnesia will be available in
|
|
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";
|
|
3894
3823
|
}
|
|
3895
3824
|
async function runInstall(positionals, options, flags) {
|
|
3896
3825
|
let hadProblems = false;
|
|
@@ -3929,19 +3858,6 @@ async function runInstall(positionals, options, flags) {
|
|
|
3929
3858
|
`);
|
|
3930
3859
|
}
|
|
3931
3860
|
hadProblems ||= registrations.errors.length > 0;
|
|
3932
|
-
if (process.platform === "win32") {
|
|
3933
|
-
try {
|
|
3934
|
-
const claude = await promptClaudeDesktopExtension({ binDir: runtime.binDir });
|
|
3935
|
-
if (claude.status === "opened") {
|
|
3936
|
-
process.stdout.write("Claude opened. Click Install to add LLMnesia.\n");
|
|
3937
|
-
}
|
|
3938
|
-
} catch (err) {
|
|
3939
|
-
process.stderr.write(
|
|
3940
|
-
`Claude Desktop needs one more step: open ${runtime.binDir}\\llmnesia.mcpb and click Install. (${err instanceof Error ? err.message : String(err)})
|
|
3941
|
-
`
|
|
3942
|
-
);
|
|
3943
|
-
}
|
|
3944
|
-
}
|
|
3945
3861
|
if (hadProblems) {
|
|
3946
3862
|
process.stderr.write("\nSetup needs attention. Fix the message above, then run the same command again.\n");
|
|
3947
3863
|
return 1;
|
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-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-
|
|
144
|
+
const { main } = await import("./cli-I2P4TSZW.js");
|
|
145
145
|
return main();
|
|
146
146
|
}
|
|
147
147
|
bootstrap().then((code) => {
|
package/dist/llmnesia.mcpb
CHANGED
|
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
|
-
|
|
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-
|
|
18
|
+
} from "./chunk-QGVH3T5W.js";
|
|
17
19
|
|
|
18
20
|
// ../node_modules/ajv/dist/compile/codegen/code.js
|
|
19
21
|
var require_code = __commonJS({
|
|
@@ -21632,29 +21634,41 @@ async function runServe() {
|
|
|
21632
21634
|
return jsonResult(status);
|
|
21633
21635
|
})
|
|
21634
21636
|
);
|
|
21635
|
-
|
|
21636
|
-
|
|
21637
|
-
|
|
21638
|
-
|
|
21639
|
-
|
|
21640
|
-
|
|
21641
|
-
|
|
21642
|
-
|
|
21643
|
-
|
|
21644
|
-
|
|
21645
|
-
|
|
21646
|
-
|
|
21647
|
-
|
|
21648
|
-
|
|
21649
|
-
|
|
21650
|
-
|
|
21651
|
-
|
|
21652
|
-
|
|
21653
|
-
|
|
21637
|
+
if (process.env.LLMNESIA_READ_ONLY !== "1") {
|
|
21638
|
+
server.registerTool(
|
|
21639
|
+
"save_conversation",
|
|
21640
|
+
{
|
|
21641
|
+
title: "Save conversation",
|
|
21642
|
+
description: "Add or update a conversation in the user's local corpus. Provide platform, title, and the messages; identity is keyed on docId (or platform:sourceId), so re-saving the same conversation updates it in place.",
|
|
21643
|
+
inputSchema: saveConversationSchema.shape,
|
|
21644
|
+
annotations: {
|
|
21645
|
+
readOnlyHint: false,
|
|
21646
|
+
destructiveHint: false,
|
|
21647
|
+
idempotentHint: false,
|
|
21648
|
+
openWorldHint: false
|
|
21649
|
+
}
|
|
21650
|
+
},
|
|
21651
|
+
async (args) => withService(async (service2) => {
|
|
21652
|
+
try {
|
|
21653
|
+
const result = await service2.saveConversation(args);
|
|
21654
|
+
return jsonResult(result);
|
|
21655
|
+
} catch (err) {
|
|
21656
|
+
return errorResult(`save_conversation failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
21657
|
+
}
|
|
21658
|
+
})
|
|
21659
|
+
);
|
|
21660
|
+
}
|
|
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
|
+
}
|
|
21654
21669
|
}
|
|
21655
|
-
})
|
|
21656
|
-
|
|
21657
|
-
server.server.oninitialized = () => service.start();
|
|
21670
|
+
});
|
|
21671
|
+
};
|
|
21658
21672
|
const transport = new StdioServerTransport();
|
|
21659
21673
|
await server.connect(transport);
|
|
21660
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.
|
|
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.
|
|
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.
|
|
12
|
+
"version": "0.1.14",
|
|
13
13
|
"transport": {
|
|
14
14
|
"type": "stdio"
|
|
15
15
|
}
|