@memoraone/mcp 0.1.34 → 0.1.35
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/dist/cli.cjs +1112 -923
- package/dist/daemon.cjs +111 -36
- package/dist/index.cjs +221 -102
- package/package.json +11 -12
package/dist/cli.cjs
CHANGED
|
@@ -30,7 +30,7 @@ var require_package = __commonJS({
|
|
|
30
30
|
"package.json"(exports2, module2) {
|
|
31
31
|
module2.exports = {
|
|
32
32
|
name: "@memoraone/mcp",
|
|
33
|
-
version: "0.1.
|
|
33
|
+
version: "0.1.35",
|
|
34
34
|
type: "module",
|
|
35
35
|
main: "dist/index.cjs",
|
|
36
36
|
bin: {
|
|
@@ -58,8 +58,8 @@ var require_package = __commonJS({
|
|
|
58
58
|
zod: "^4.0.0"
|
|
59
59
|
},
|
|
60
60
|
devDependencies: {
|
|
61
|
-
tsx: "^4.21.0",
|
|
62
61
|
tsup: "^8.5.1",
|
|
62
|
+
tsx: "^4.21.0",
|
|
63
63
|
typescript: "^5.9.2"
|
|
64
64
|
}
|
|
65
65
|
};
|
|
@@ -68,8 +68,8 @@ var require_package = __commonJS({
|
|
|
68
68
|
|
|
69
69
|
// src/bridgeProxy.ts
|
|
70
70
|
var net = __toESM(require("net"), 1);
|
|
71
|
-
var
|
|
72
|
-
var
|
|
71
|
+
var readline3 = __toESM(require("readline"), 1);
|
|
72
|
+
var import_node_child_process3 = require("child_process");
|
|
73
73
|
|
|
74
74
|
// src/bindingIdentity.ts
|
|
75
75
|
var crypto = __toESM(require("crypto"), 1);
|
|
@@ -84,7 +84,9 @@ function hashBindingIdentity(projectId, workspaceRoot, ideType) {
|
|
|
84
84
|
return crypto.createHash("sha256").update(input2).digest("hex").slice(0, BINDING_SOCKET_HASH_LENGTH);
|
|
85
85
|
}
|
|
86
86
|
function bindingsMatch(a, b) {
|
|
87
|
-
|
|
87
|
+
const envA = a.environment ?? void 0;
|
|
88
|
+
const envB = b.environment ?? void 0;
|
|
89
|
+
return a.projectId.trim().toLowerCase() === b.projectId.trim().toLowerCase() && path.resolve(a.workspaceRoot) === path.resolve(b.workspaceRoot) && path.resolve(a.m1Path) === path.resolve(b.m1Path) && (a.apiKey ?? null) === (b.apiKey ?? null) && envA === envB;
|
|
88
90
|
}
|
|
89
91
|
function formatMissingInitializeWorkspaceError(options) {
|
|
90
92
|
const lines = [
|
|
@@ -119,6 +121,10 @@ var path4 = __toESM(require("path"), 1);
|
|
|
119
121
|
var fs = __toESM(require("fs/promises"), 1);
|
|
120
122
|
var path2 = __toESM(require("path"), 1);
|
|
121
123
|
var uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
124
|
+
var CANONICAL_M1_FILENAME = "memoraone.m1";
|
|
125
|
+
function isCanonicalM1Path(m1Path) {
|
|
126
|
+
return path2.basename(m1Path) === CANONICAL_M1_FILENAME;
|
|
127
|
+
}
|
|
122
128
|
function normalizeEnvironment(raw) {
|
|
123
129
|
if (raw === void 0 || raw === null || typeof raw !== "string") {
|
|
124
130
|
return void 0;
|
|
@@ -165,7 +171,7 @@ async function resolveProjectIdFromExplicitM1Path() {
|
|
|
165
171
|
async function findM1WalkingUp(workspaceRoot) {
|
|
166
172
|
let current = path2.resolve(workspaceRoot);
|
|
167
173
|
while (true) {
|
|
168
|
-
const markerPath = path2.join(current,
|
|
174
|
+
const markerPath = path2.join(current, CANONICAL_M1_FILENAME);
|
|
169
175
|
try {
|
|
170
176
|
const content = await fs.readFile(markerPath, "utf8");
|
|
171
177
|
const { projectId, apiKey, environment } = parseAndValidateM1(content, markerPath);
|
|
@@ -272,6 +278,45 @@ async function resolveAuthoritativeBinding(workspaceRoot, options = {}) {
|
|
|
272
278
|
}
|
|
273
279
|
return bindings[0];
|
|
274
280
|
}
|
|
281
|
+
function bindingRelevantValuesMatch(a, b) {
|
|
282
|
+
const envA = a.environment ?? void 0;
|
|
283
|
+
const envB = b.environment ?? void 0;
|
|
284
|
+
return a.projectId.trim().toLowerCase() === b.projectId.trim().toLowerCase() && path2.resolve(a.workspaceRoot) === path2.resolve(b.workspaceRoot) && path2.resolve(a.m1Path) === path2.resolve(b.m1Path) && (a.apiKey ?? null) === (b.apiKey ?? null) && envA === envB;
|
|
285
|
+
}
|
|
286
|
+
async function reconcileResolvedBindingWithDisk(cached) {
|
|
287
|
+
const m1Path = path2.resolve(cached.m1Path);
|
|
288
|
+
if (cached.bindingSource !== "explicit-m1-path" && !isCanonicalM1Path(m1Path)) {
|
|
289
|
+
throw new Error(
|
|
290
|
+
`[memoraone-mcp] Cached binding m1Path is not the canonical ${CANONICAL_M1_FILENAME}: ${m1Path}`
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
let content;
|
|
294
|
+
try {
|
|
295
|
+
content = await fs.readFile(m1Path, "utf8");
|
|
296
|
+
} catch (err) {
|
|
297
|
+
if (err?.code === "ENOENT") {
|
|
298
|
+
throw new Error(
|
|
299
|
+
`[memoraone-mcp] Cached binding file missing at ${m1Path}. Open a folder containing ${CANONICAL_M1_FILENAME}.`
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
throw err;
|
|
303
|
+
}
|
|
304
|
+
const parsed = parseAndValidateM1(content, m1Path);
|
|
305
|
+
const resolved = resolveApiKeyWithSource(parsed.apiKey);
|
|
306
|
+
const fresh = {
|
|
307
|
+
projectId: parsed.projectId,
|
|
308
|
+
workspaceRoot: path2.resolve(path2.dirname(m1Path)),
|
|
309
|
+
m1Path,
|
|
310
|
+
apiKey: resolved.apiKey,
|
|
311
|
+
...parsed.environment !== void 0 ? { environment: parsed.environment } : {},
|
|
312
|
+
bindingSource: cached.bindingSource,
|
|
313
|
+
apiKeySource: resolved.apiKeySource
|
|
314
|
+
};
|
|
315
|
+
if (bindingRelevantValuesMatch(cached, fresh)) {
|
|
316
|
+
return { binding: fresh, cacheRefreshed: false };
|
|
317
|
+
}
|
|
318
|
+
return { binding: fresh, cacheRefreshed: true };
|
|
319
|
+
}
|
|
275
320
|
function encodeResolvedBinding(binding) {
|
|
276
321
|
return Buffer.from(JSON.stringify(binding), "utf8").toString("base64");
|
|
277
322
|
}
|
|
@@ -444,6 +489,29 @@ function readBindingSidecarRecord(socketPath) {
|
|
|
444
489
|
return null;
|
|
445
490
|
}
|
|
446
491
|
}
|
|
492
|
+
function readBindingSidecar(socketPath) {
|
|
493
|
+
const record = readBindingSidecarRecord(socketPath);
|
|
494
|
+
if (!record) {
|
|
495
|
+
return null;
|
|
496
|
+
}
|
|
497
|
+
return decodeResolvedBinding(record.binding);
|
|
498
|
+
}
|
|
499
|
+
function removeBindingSidecar(socketPath) {
|
|
500
|
+
try {
|
|
501
|
+
fs3.unlinkSync(bindingSidecarPath(socketPath));
|
|
502
|
+
} catch {
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
function removeDaemonSocketArtifacts(socketPath) {
|
|
506
|
+
removeBindingSidecar(socketPath);
|
|
507
|
+
try {
|
|
508
|
+
fs3.unlinkSync(socketPath);
|
|
509
|
+
} catch {
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
function isDaemonBindingMismatchError(err) {
|
|
513
|
+
return err instanceof Error && err.message.includes("Daemon socket binding mismatch");
|
|
514
|
+
}
|
|
447
515
|
function formatBindingMismatchError(socketPath, sidecar, expected, detail) {
|
|
448
516
|
const lines = [
|
|
449
517
|
`[memoraone-mcp] Daemon socket binding mismatch at ${path4.basename(socketPath)}.`,
|
|
@@ -453,7 +521,7 @@ function formatBindingMismatchError(socketPath, sidecar, expected, detail) {
|
|
|
453
521
|
if (detail) {
|
|
454
522
|
lines.push(` ${detail}`);
|
|
455
523
|
}
|
|
456
|
-
lines.push("
|
|
524
|
+
lines.push("The bridge will replace this stale daemon automatically from the current memoraone.m1.");
|
|
457
525
|
return lines.join("\n");
|
|
458
526
|
}
|
|
459
527
|
function verifyDaemonSidecarBinding(socketPath, expected, env = process.env) {
|
|
@@ -635,6 +703,21 @@ async function requestClientRootsListUris(options) {
|
|
|
635
703
|
}
|
|
636
704
|
}
|
|
637
705
|
|
|
706
|
+
// src/cleanup.ts
|
|
707
|
+
var fs5 = __toESM(require("fs/promises"), 1);
|
|
708
|
+
var path7 = __toESM(require("path"), 1);
|
|
709
|
+
var readline2 = __toESM(require("readline/promises"), 1);
|
|
710
|
+
var import_node_child_process2 = require("child_process");
|
|
711
|
+
var import_node_util2 = require("util");
|
|
712
|
+
var import_node_process = require("process");
|
|
713
|
+
|
|
714
|
+
// src/cursorGlobalMcpConfig.ts
|
|
715
|
+
var fs4 = __toESM(require("fs/promises"), 1);
|
|
716
|
+
var os2 = __toESM(require("os"), 1);
|
|
717
|
+
var path6 = __toESM(require("path"), 1);
|
|
718
|
+
var import_node_child_process = require("child_process");
|
|
719
|
+
var import_node_util = require("util");
|
|
720
|
+
|
|
638
721
|
// src/initializeBinding.ts
|
|
639
722
|
var path5 = __toESM(require("path"), 1);
|
|
640
723
|
var import_node_url = require("url");
|
|
@@ -802,477 +885,120 @@ async function resolveBindingFromInitializeParams(params, options = {}) {
|
|
|
802
885
|
});
|
|
803
886
|
}
|
|
804
887
|
|
|
805
|
-
// src/
|
|
806
|
-
var
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
888
|
+
// src/cursorGlobalMcpConfig.ts
|
|
889
|
+
var execFileAsync = (0, import_node_util.promisify)(import_node_child_process.execFile);
|
|
890
|
+
var MEMORAONE_PROD_API_URL = "https://api.memoraone.com";
|
|
891
|
+
var MEMORAONE_LOCAL_API_URL = "http://localhost:3001";
|
|
892
|
+
var MEMORAONE_STAGING_API_URL = "https://memora-api-staging-phbtrzocjq-uk.a.run.app";
|
|
893
|
+
var MEMORAONE_STAGING_API_URL_PREFIX = "https://memora-api-staging-";
|
|
894
|
+
function cursorMcpApiUrl(environment) {
|
|
895
|
+
if (environment === "local") return MEMORAONE_LOCAL_API_URL;
|
|
896
|
+
if (environment === "staging") return MEMORAONE_STAGING_API_URL;
|
|
897
|
+
return MEMORAONE_PROD_API_URL;
|
|
898
|
+
}
|
|
899
|
+
function buildMemoraoneCursorMcpServer(options) {
|
|
900
|
+
const env = {
|
|
901
|
+
MEMORAONE_API_URL: cursorMcpApiUrl(options.environment),
|
|
902
|
+
MEMORAONE_IDE_TYPE: "cursor"
|
|
903
|
+
};
|
|
904
|
+
if (options.workspaceRoot !== void 0) {
|
|
905
|
+
env[MEMORAONE_WORKSPACE_ROOT_ENV] = path6.resolve(options.workspaceRoot);
|
|
906
|
+
}
|
|
907
|
+
if (options.environment === "local") {
|
|
908
|
+
if (!options.cliPath) {
|
|
909
|
+
throw new Error("[setup-ide-files] Local Cursor MCP config requires a built CLI path.");
|
|
818
910
|
}
|
|
819
|
-
return
|
|
911
|
+
return {
|
|
912
|
+
command: "node",
|
|
913
|
+
args: [options.cliPath],
|
|
914
|
+
env
|
|
915
|
+
};
|
|
916
|
+
}
|
|
917
|
+
if (!options.npxPath) {
|
|
918
|
+
throw new Error("[setup-ide-files] Cursor MCP config requires a resolved npx path.");
|
|
919
|
+
}
|
|
920
|
+
return {
|
|
921
|
+
command: options.npxPath,
|
|
922
|
+
args: ["-y", "@memoraone/mcp@latest"],
|
|
923
|
+
env
|
|
924
|
+
};
|
|
925
|
+
}
|
|
926
|
+
async function pathExists(filePath) {
|
|
927
|
+
try {
|
|
928
|
+
await fs4.access(filePath);
|
|
929
|
+
return true;
|
|
820
930
|
} catch {
|
|
821
|
-
return
|
|
931
|
+
return false;
|
|
822
932
|
}
|
|
823
933
|
}
|
|
824
|
-
function
|
|
825
|
-
return
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
934
|
+
function stripLeadingLineComments(text) {
|
|
935
|
+
return text.split("\n").filter((line) => !/^\s*\/\//.test(line)).join("\n");
|
|
936
|
+
}
|
|
937
|
+
function getKnownCursorGlobalMcpConfigCandidates(homeDir) {
|
|
938
|
+
return [path6.join(homeDir, ".cursor", "mcp.json")];
|
|
939
|
+
}
|
|
940
|
+
async function detectCursorGlobalMcpConfig(options) {
|
|
941
|
+
if (options?.explicitPath) {
|
|
942
|
+
return { ok: true, path: options.explicitPath, detectedExisting: await pathExists(options.explicitPath) };
|
|
943
|
+
}
|
|
944
|
+
const homeDir = options?.homeDir ?? os2.homedir();
|
|
945
|
+
const candidates = getKnownCursorGlobalMcpConfigCandidates(homeDir);
|
|
946
|
+
const existing = [];
|
|
947
|
+
for (const candidate of candidates) {
|
|
948
|
+
if (await pathExists(candidate)) existing.push(candidate);
|
|
949
|
+
}
|
|
950
|
+
if (existing.length > 1) {
|
|
951
|
+
return {
|
|
952
|
+
ok: false,
|
|
953
|
+
error: "[setup-ide-files] Multiple Cursor global MCP config paths found. Specify one explicitly.",
|
|
954
|
+
candidates: existing
|
|
835
955
|
};
|
|
836
|
-
|
|
837
|
-
|
|
956
|
+
}
|
|
957
|
+
if (existing.length === 1) {
|
|
958
|
+
return { ok: true, path: existing[0], detectedExisting: true };
|
|
959
|
+
}
|
|
960
|
+
const defaultPath = candidates[0];
|
|
961
|
+
if (!defaultPath) {
|
|
962
|
+
return {
|
|
963
|
+
ok: false,
|
|
964
|
+
error: "[setup-ide-files] No known Cursor global MCP config path.",
|
|
965
|
+
candidates: []
|
|
966
|
+
};
|
|
967
|
+
}
|
|
968
|
+
return { ok: true, path: defaultPath, detectedExisting: false };
|
|
838
969
|
}
|
|
839
|
-
|
|
840
|
-
const
|
|
841
|
-
return
|
|
842
|
-
env,
|
|
843
|
-
fallbackWorkspaceRoots: getEnvWorkspaceRootCandidates(),
|
|
844
|
-
rootsListUris: options.rootsListUris,
|
|
845
|
-
rootsListAttempted: options.rootsListAttempted,
|
|
846
|
-
...bridgeOptions
|
|
847
|
-
});
|
|
970
|
+
function formatBackupTimestamp(d = /* @__PURE__ */ new Date()) {
|
|
971
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
972
|
+
return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
|
|
848
973
|
}
|
|
849
|
-
async function
|
|
850
|
-
const socketPath = getBindingSocketPath(binding, opts.env);
|
|
851
|
-
opts.log(
|
|
852
|
-
`target daemon socket=${socketPath} project=${binding.projectId} workspace=${binding.workspaceRoot}`
|
|
853
|
-
);
|
|
854
|
-
let socket;
|
|
974
|
+
async function isWorkingNpx(npxPath) {
|
|
855
975
|
try {
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
verifyDaemonSidecarBinding(socketPath, binding, opts.env);
|
|
864
|
-
opts.log("reusing running daemon for session binding");
|
|
865
|
-
return socket;
|
|
866
|
-
} catch (err) {
|
|
867
|
-
if (err instanceof Error && err.message.includes("Daemon socket binding mismatch")) {
|
|
868
|
-
throw err;
|
|
976
|
+
if (!await pathExists(npxPath)) return false;
|
|
977
|
+
if (process.platform !== "win32") {
|
|
978
|
+
try {
|
|
979
|
+
await fs4.access(npxPath, fs4.constants.X_OK);
|
|
980
|
+
} catch {
|
|
981
|
+
return false;
|
|
982
|
+
}
|
|
869
983
|
}
|
|
984
|
+
await execFileAsync(npxPath, ["--version"], { timeout: 1e4 });
|
|
985
|
+
return true;
|
|
986
|
+
} catch {
|
|
987
|
+
return false;
|
|
870
988
|
}
|
|
871
|
-
opts.log("daemon not running, spawning...");
|
|
872
|
-
await opts.spawnDaemon(binding, socketPath);
|
|
873
|
-
await new Promise((r) => setTimeout(r, opts.retryDelayMs));
|
|
874
|
-
socket = await connectWithRetry(
|
|
875
|
-
socketPath,
|
|
876
|
-
opts.log,
|
|
877
|
-
opts.maxRetries,
|
|
878
|
-
opts.retryDelayMs,
|
|
879
|
-
opts.connect
|
|
880
|
-
);
|
|
881
|
-
verifyDaemonSidecarBinding(socketPath, binding, opts.env);
|
|
882
|
-
return socket;
|
|
883
989
|
}
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
this.handshakeDeferredClientLines = [];
|
|
892
|
-
this.clientInitializeSeen = false;
|
|
893
|
-
this.env = options.env ?? process.env;
|
|
894
|
-
this.stdout = options.stdout ?? process.stdout;
|
|
895
|
-
this.log = options.log ?? defaultLog;
|
|
896
|
-
this.cliPath = options.cliPath;
|
|
897
|
-
this.maxRetries = options.maxRetries ?? 5;
|
|
898
|
-
this.retryDelayMs = options.retryDelayMs ?? 200;
|
|
899
|
-
this.lineReader = options.lineReader ?? null;
|
|
900
|
-
this.connectImpl = options.connect ?? ((socketPath) => new Promise((resolve9, reject) => {
|
|
901
|
-
const socket = net.connect(socketPath, () => resolve9(socket));
|
|
902
|
-
socket.on("error", reject);
|
|
903
|
-
}));
|
|
904
|
-
this.spawnDaemonImpl = options.spawnDaemon ?? (async (binding) => {
|
|
905
|
-
const child = (0, import_node_child_process.spawn)(
|
|
906
|
-
process.execPath,
|
|
907
|
-
buildDaemonSpawnArgs(this.cliPath, binding.projectId, this.env),
|
|
908
|
-
{
|
|
909
|
-
detached: true,
|
|
910
|
-
stdio: "ignore",
|
|
911
|
-
env: {
|
|
912
|
-
...this.env,
|
|
913
|
-
MEMORAONE_DAEMON_BINDING_B64: encodeResolvedBinding(binding)
|
|
914
|
-
}
|
|
915
|
-
}
|
|
916
|
-
);
|
|
917
|
-
child.on("exit", (code, signal) => {
|
|
918
|
-
logInitializeDebug(
|
|
919
|
-
this.log,
|
|
920
|
-
this.env,
|
|
921
|
-
`spawned daemon exit code=${code ?? "null"} signal=${signal ?? "null"} project=${binding.projectId}`
|
|
922
|
-
);
|
|
923
|
-
});
|
|
924
|
-
child.unref();
|
|
925
|
-
});
|
|
926
|
-
}
|
|
927
|
-
getActiveBinding() {
|
|
928
|
-
return this.activeBinding;
|
|
990
|
+
async function resolveNpxPath() {
|
|
991
|
+
const npxName = process.platform === "win32" ? "npx.cmd" : "npx";
|
|
992
|
+
const candidates = [];
|
|
993
|
+
if (process.platform === "darwin") {
|
|
994
|
+
candidates.push("/opt/homebrew/bin/npx", "/usr/local/bin/npx");
|
|
995
|
+
} else if (process.platform === "linux") {
|
|
996
|
+
candidates.push("/usr/local/bin/npx");
|
|
929
997
|
}
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
logInitializeDebug(
|
|
935
|
-
this.log,
|
|
936
|
-
this.env,
|
|
937
|
-
`initialize payload: ${summarizeInitializeParamsForDebug(params)}`
|
|
938
|
-
);
|
|
939
|
-
const bridgeOptions = getBridgeBindingResolveOptions(this.env);
|
|
940
|
-
const initializeRoots = extractWorkspaceRootsFromInitialize(params);
|
|
941
|
-
let rootsListUris;
|
|
942
|
-
let rootsListAttempted = false;
|
|
943
|
-
const repoHint = getRepoScopedWorkspaceHint(this.env);
|
|
944
|
-
if (initializeRoots.length === 0 && bridgeOptions.allowEnvWorkspaceFallback === false && repoHint === null) {
|
|
945
|
-
if (!this.lineReader) {
|
|
946
|
-
throw new Error(
|
|
947
|
-
"[memoraone-mcp] Internal error: Cursor workspace binding requires stdin line reader for roots/list"
|
|
948
|
-
);
|
|
949
|
-
}
|
|
950
|
-
rootsListAttempted = true;
|
|
951
|
-
this.log("initialize lacks workspace roots; requesting roots/list from Cursor before binding");
|
|
952
|
-
const rootsListResult = await requestClientRootsListUris({
|
|
953
|
-
lineReader: this.lineReader,
|
|
954
|
-
stdout: this.stdout,
|
|
955
|
-
log: this.log,
|
|
956
|
-
env: this.env,
|
|
957
|
-
initializeParams: params
|
|
958
|
-
});
|
|
959
|
-
rootsListUris = rootsListResult.uris;
|
|
960
|
-
if (rootsListResult.deferredLines.length > 0) {
|
|
961
|
-
this.pendingDeferredClientLines = rootsListResult.deferredLines.slice();
|
|
962
|
-
logInitializeDebug(
|
|
963
|
-
this.log,
|
|
964
|
-
this.env,
|
|
965
|
-
`queued ${this.pendingDeferredClientLines.length} deferred client line(s) for replay after initialize`
|
|
966
|
-
);
|
|
967
|
-
}
|
|
968
|
-
}
|
|
969
|
-
const binding = await resolveBridgeSessionBinding(params, this.env, {
|
|
970
|
-
rootsListUris,
|
|
971
|
-
rootsListAttempted
|
|
972
|
-
});
|
|
973
|
-
const environmentLog = binding.environment !== void 0 ? ` environment=${binding.environment}` : "";
|
|
974
|
-
this.log(
|
|
975
|
-
`session binding project=${binding.projectId} workspace=${binding.workspaceRoot} m1=${binding.m1Path} source=${binding.bindingSource} apiKeySource=${binding.apiKeySource}${environmentLog}`
|
|
976
|
-
);
|
|
977
|
-
if (this.activeBinding && bindingsMatch(this.activeBinding, binding) && this.activeSocket) {
|
|
978
|
-
return;
|
|
979
|
-
}
|
|
980
|
-
if (this.activeBinding && !bindingsMatch(this.activeBinding, binding)) {
|
|
981
|
-
this.log(
|
|
982
|
-
`session binding changed from workspace=${this.activeBinding.workspaceRoot} to workspace=${binding.workspaceRoot}; reconnecting daemon`
|
|
983
|
-
);
|
|
984
|
-
this.resetSessionState();
|
|
985
|
-
this.detachSocketReader();
|
|
986
|
-
this.activeSocket?.destroy();
|
|
987
|
-
this.activeSocket = null;
|
|
988
|
-
}
|
|
989
|
-
this.activeBinding = binding;
|
|
990
|
-
await this.connectActiveDaemon();
|
|
991
|
-
this.log("bridge connected");
|
|
992
|
-
}
|
|
993
|
-
recordClientInitialize(line) {
|
|
994
|
-
this.lastInitializeLine = line;
|
|
995
|
-
this.clientInitializeSeen = true;
|
|
996
|
-
}
|
|
997
|
-
async forwardInitializeToDaemon(line) {
|
|
998
|
-
this.recordClientInitialize(line);
|
|
999
|
-
await this.writeToDaemon(line);
|
|
1000
|
-
logInitializeDebug(this.log, this.env, "initialize replay to daemon");
|
|
1001
|
-
this.log("forwarding active");
|
|
1002
|
-
}
|
|
1003
|
-
async replayDeferredClientMessages() {
|
|
1004
|
-
if (this.pendingDeferredClientLines.length === 0) {
|
|
1005
|
-
return;
|
|
1006
|
-
}
|
|
1007
|
-
const lines = this.pendingDeferredClientLines.slice();
|
|
1008
|
-
this.pendingDeferredClientLines = [];
|
|
1009
|
-
this.handshakeDeferredClientLines = lines.slice();
|
|
1010
|
-
const types = lines.map((line) => summarizeJsonRpcMethod(line));
|
|
1011
|
-
logInitializeDebug(
|
|
1012
|
-
this.log,
|
|
1013
|
-
this.env,
|
|
1014
|
-
`deferred message replay count=${lines.length} types=${JSON.stringify(types)}`
|
|
1015
|
-
);
|
|
1016
|
-
for (const line of lines) {
|
|
1017
|
-
await this.writeToDaemon(line);
|
|
1018
|
-
}
|
|
1019
|
-
}
|
|
1020
|
-
async writeToDaemon(line) {
|
|
1021
|
-
await this.ensureActiveDaemonSocket();
|
|
1022
|
-
this.activeSocket.write(`${line}
|
|
1023
|
-
`);
|
|
1024
|
-
}
|
|
1025
|
-
resetSessionState() {
|
|
1026
|
-
this.lastInitializeLine = null;
|
|
1027
|
-
this.pendingDeferredClientLines = [];
|
|
1028
|
-
this.handshakeDeferredClientLines = [];
|
|
1029
|
-
this.clientInitializeSeen = false;
|
|
1030
|
-
this.activeBinding = null;
|
|
1031
|
-
}
|
|
1032
|
-
async connectActiveDaemon() {
|
|
1033
|
-
if (!this.activeBinding) {
|
|
1034
|
-
throw new Error("[memoraone-mcp] Internal error: connectActiveDaemon without active binding");
|
|
1035
|
-
}
|
|
1036
|
-
const socket = await connectOrSpawnDaemonForBinding(this.activeBinding, {
|
|
1037
|
-
env: this.env,
|
|
1038
|
-
cliPath: this.cliPath,
|
|
1039
|
-
log: this.log,
|
|
1040
|
-
maxRetries: this.maxRetries,
|
|
1041
|
-
retryDelayMs: this.retryDelayMs,
|
|
1042
|
-
connect: this.connectImpl,
|
|
1043
|
-
spawnDaemon: this.spawnDaemonImpl
|
|
1044
|
-
});
|
|
1045
|
-
this.activeSocket = socket;
|
|
1046
|
-
this.attachSocketReader(socket);
|
|
1047
|
-
}
|
|
1048
|
-
async ensureActiveDaemonSocket() {
|
|
1049
|
-
if (this.activeSocket && !this.activeSocket.destroyed) {
|
|
1050
|
-
return;
|
|
1051
|
-
}
|
|
1052
|
-
if (!this.clientInitializeSeen || !this.lastInitializeLine || !this.activeBinding) {
|
|
1053
|
-
throw new Error("[memoraone-mcp] MCP request before initialize");
|
|
1054
|
-
}
|
|
1055
|
-
this.log("daemon socket unavailable; reconnecting for session binding");
|
|
1056
|
-
await this.connectActiveDaemon();
|
|
1057
|
-
logInitializeDebug(this.log, this.env, "initialize replay to daemon after reconnect");
|
|
1058
|
-
this.activeSocket.write(`${this.lastInitializeLine}
|
|
1059
|
-
`);
|
|
1060
|
-
if (this.handshakeDeferredClientLines.length > 0) {
|
|
1061
|
-
const types = this.handshakeDeferredClientLines.map(
|
|
1062
|
-
(deferredLine) => summarizeJsonRpcMethod(deferredLine)
|
|
1063
|
-
);
|
|
1064
|
-
logInitializeDebug(
|
|
1065
|
-
this.log,
|
|
1066
|
-
this.env,
|
|
1067
|
-
`deferred message replay after reconnect count=${this.handshakeDeferredClientLines.length} types=${JSON.stringify(types)}`
|
|
1068
|
-
);
|
|
1069
|
-
for (const deferredLine of this.handshakeDeferredClientLines) {
|
|
1070
|
-
this.activeSocket.write(`${deferredLine}
|
|
1071
|
-
`);
|
|
1072
|
-
}
|
|
1073
|
-
}
|
|
1074
|
-
}
|
|
1075
|
-
attachSocketReader(socket) {
|
|
1076
|
-
this.detachSocketReader();
|
|
1077
|
-
this.socketLineReader = readline2.createInterface({ input: socket, crlfDelay: Infinity });
|
|
1078
|
-
this.socketLineReader.on("line", (line) => {
|
|
1079
|
-
this.stdout.write(`${line}
|
|
1080
|
-
`);
|
|
1081
|
-
});
|
|
1082
|
-
socket.on("close", (hadError) => {
|
|
1083
|
-
if (this.activeSocket === socket) {
|
|
1084
|
-
logInitializeDebug(
|
|
1085
|
-
this.log,
|
|
1086
|
-
this.env,
|
|
1087
|
-
`daemon socket closed hadError=${String(hadError)}`
|
|
1088
|
-
);
|
|
1089
|
-
this.log("daemon socket closed; bridge stays alive for reconnect");
|
|
1090
|
-
this.detachSocketReader();
|
|
1091
|
-
this.activeSocket = null;
|
|
1092
|
-
}
|
|
1093
|
-
});
|
|
1094
|
-
socket.on("error", (err) => {
|
|
1095
|
-
this.log(`socket error: ${String(err)}`);
|
|
1096
|
-
logInitializeDebug(this.log, this.env, `daemon socket error: ${String(err)}`);
|
|
1097
|
-
if (this.activeSocket === socket) {
|
|
1098
|
-
this.detachSocketReader();
|
|
1099
|
-
this.activeSocket = null;
|
|
1100
|
-
}
|
|
1101
|
-
});
|
|
1102
|
-
}
|
|
1103
|
-
detachSocketReader() {
|
|
1104
|
-
if (this.socketLineReader) {
|
|
1105
|
-
this.socketLineReader.close();
|
|
1106
|
-
this.socketLineReader = null;
|
|
1107
|
-
}
|
|
1108
|
-
}
|
|
1109
|
-
};
|
|
1110
|
-
async function runBridgeProxy(options) {
|
|
1111
|
-
ensureBaseDir();
|
|
1112
|
-
const stdin = options.stdin ?? process.stdin;
|
|
1113
|
-
const stdout = options.stdout ?? process.stdout;
|
|
1114
|
-
const log = options.log ?? defaultLog;
|
|
1115
|
-
const lineReader = options.lineReader ?? new StdioLineReader(stdin);
|
|
1116
|
-
const router = new BridgeDaemonRouter({ ...options, stdout, lineReader });
|
|
1117
|
-
while (true) {
|
|
1118
|
-
const line = await lineReader.readLine();
|
|
1119
|
-
if (line === null) {
|
|
1120
|
-
break;
|
|
1121
|
-
}
|
|
1122
|
-
const trimmed = line.trim();
|
|
1123
|
-
if (trimmed === "") {
|
|
1124
|
-
continue;
|
|
1125
|
-
}
|
|
1126
|
-
let message;
|
|
1127
|
-
try {
|
|
1128
|
-
message = JSON.parse(trimmed);
|
|
1129
|
-
} catch (err) {
|
|
1130
|
-
throw new Error(`[memoraone-mcp] Invalid JSON-RPC on stdin: ${String(err)}`);
|
|
1131
|
-
}
|
|
1132
|
-
if (message.method === "initialize") {
|
|
1133
|
-
log("resolve binding from initialize request before daemon connect");
|
|
1134
|
-
const params = message.params ?? {};
|
|
1135
|
-
await router.ensureDaemonForInitialize(params);
|
|
1136
|
-
await router.forwardInitializeToDaemon(trimmed);
|
|
1137
|
-
await router.replayDeferredClientMessages();
|
|
1138
|
-
continue;
|
|
1139
|
-
}
|
|
1140
|
-
await router.writeToDaemon(trimmed);
|
|
1141
|
-
}
|
|
1142
|
-
}
|
|
1143
|
-
|
|
1144
|
-
// src/setupIdeFiles.ts
|
|
1145
|
-
var fs8 = __toESM(require("fs/promises"), 1);
|
|
1146
|
-
var os4 = __toESM(require("os"), 1);
|
|
1147
|
-
var path10 = __toESM(require("path"), 1);
|
|
1148
|
-
|
|
1149
|
-
// src/cleanup.ts
|
|
1150
|
-
var fs5 = __toESM(require("fs/promises"), 1);
|
|
1151
|
-
var path7 = __toESM(require("path"), 1);
|
|
1152
|
-
var readline3 = __toESM(require("readline/promises"), 1);
|
|
1153
|
-
var import_node_child_process3 = require("child_process");
|
|
1154
|
-
var import_node_util2 = require("util");
|
|
1155
|
-
var import_node_process = require("process");
|
|
1156
|
-
|
|
1157
|
-
// src/cursorGlobalMcpConfig.ts
|
|
1158
|
-
var fs4 = __toESM(require("fs/promises"), 1);
|
|
1159
|
-
var os2 = __toESM(require("os"), 1);
|
|
1160
|
-
var path6 = __toESM(require("path"), 1);
|
|
1161
|
-
var import_node_child_process2 = require("child_process");
|
|
1162
|
-
var import_node_util = require("util");
|
|
1163
|
-
var execFileAsync = (0, import_node_util.promisify)(import_node_child_process2.execFile);
|
|
1164
|
-
var MEMORAONE_PROD_API_URL = "https://api.memoraone.com";
|
|
1165
|
-
var MEMORAONE_LOCAL_API_URL = "http://localhost:3001";
|
|
1166
|
-
var MEMORAONE_STAGING_API_URL = "https://memora-api-staging-phbtrzocjq-uk.a.run.app";
|
|
1167
|
-
var MEMORAONE_STAGING_API_URL_PREFIX = "https://memora-api-staging-";
|
|
1168
|
-
function cursorMcpApiUrl(environment) {
|
|
1169
|
-
if (environment === "local") return MEMORAONE_LOCAL_API_URL;
|
|
1170
|
-
if (environment === "staging") return MEMORAONE_STAGING_API_URL;
|
|
1171
|
-
return MEMORAONE_PROD_API_URL;
|
|
1172
|
-
}
|
|
1173
|
-
function buildMemoraoneCursorMcpServer(options) {
|
|
1174
|
-
const env = {
|
|
1175
|
-
MEMORAONE_API_URL: cursorMcpApiUrl(options.environment),
|
|
1176
|
-
MEMORAONE_IDE_TYPE: "cursor"
|
|
1177
|
-
};
|
|
1178
|
-
if (options.workspaceRoot !== void 0) {
|
|
1179
|
-
env[MEMORAONE_WORKSPACE_ROOT_ENV] = path6.resolve(options.workspaceRoot);
|
|
1180
|
-
}
|
|
1181
|
-
if (options.environment === "local") {
|
|
1182
|
-
if (!options.cliPath) {
|
|
1183
|
-
throw new Error("[setup-ide-files] Local Cursor MCP config requires a built CLI path.");
|
|
1184
|
-
}
|
|
1185
|
-
return {
|
|
1186
|
-
command: "node",
|
|
1187
|
-
args: [options.cliPath],
|
|
1188
|
-
env
|
|
1189
|
-
};
|
|
1190
|
-
}
|
|
1191
|
-
if (!options.npxPath) {
|
|
1192
|
-
throw new Error("[setup-ide-files] Cursor MCP config requires a resolved npx path.");
|
|
1193
|
-
}
|
|
1194
|
-
return {
|
|
1195
|
-
command: options.npxPath,
|
|
1196
|
-
args: ["-y", "@memoraone/mcp@latest"],
|
|
1197
|
-
env
|
|
1198
|
-
};
|
|
1199
|
-
}
|
|
1200
|
-
async function pathExists(filePath) {
|
|
1201
|
-
try {
|
|
1202
|
-
await fs4.access(filePath);
|
|
1203
|
-
return true;
|
|
1204
|
-
} catch {
|
|
1205
|
-
return false;
|
|
1206
|
-
}
|
|
1207
|
-
}
|
|
1208
|
-
function stripLeadingLineComments(text) {
|
|
1209
|
-
return text.split("\n").filter((line) => !/^\s*\/\//.test(line)).join("\n");
|
|
1210
|
-
}
|
|
1211
|
-
function getKnownCursorGlobalMcpConfigCandidates(homeDir) {
|
|
1212
|
-
return [path6.join(homeDir, ".cursor", "mcp.json")];
|
|
1213
|
-
}
|
|
1214
|
-
async function detectCursorGlobalMcpConfig(options) {
|
|
1215
|
-
if (options?.explicitPath) {
|
|
1216
|
-
return { ok: true, path: options.explicitPath, detectedExisting: await pathExists(options.explicitPath) };
|
|
1217
|
-
}
|
|
1218
|
-
const homeDir = options?.homeDir ?? os2.homedir();
|
|
1219
|
-
const candidates = getKnownCursorGlobalMcpConfigCandidates(homeDir);
|
|
1220
|
-
const existing = [];
|
|
1221
|
-
for (const candidate of candidates) {
|
|
1222
|
-
if (await pathExists(candidate)) existing.push(candidate);
|
|
1223
|
-
}
|
|
1224
|
-
if (existing.length > 1) {
|
|
1225
|
-
return {
|
|
1226
|
-
ok: false,
|
|
1227
|
-
error: "[setup-ide-files] Multiple Cursor global MCP config paths found. Specify one explicitly.",
|
|
1228
|
-
candidates: existing
|
|
1229
|
-
};
|
|
1230
|
-
}
|
|
1231
|
-
if (existing.length === 1) {
|
|
1232
|
-
return { ok: true, path: existing[0], detectedExisting: true };
|
|
1233
|
-
}
|
|
1234
|
-
const defaultPath = candidates[0];
|
|
1235
|
-
if (!defaultPath) {
|
|
1236
|
-
return {
|
|
1237
|
-
ok: false,
|
|
1238
|
-
error: "[setup-ide-files] No known Cursor global MCP config path.",
|
|
1239
|
-
candidates: []
|
|
1240
|
-
};
|
|
1241
|
-
}
|
|
1242
|
-
return { ok: true, path: defaultPath, detectedExisting: false };
|
|
1243
|
-
}
|
|
1244
|
-
function formatBackupTimestamp(d = /* @__PURE__ */ new Date()) {
|
|
1245
|
-
const pad = (n) => String(n).padStart(2, "0");
|
|
1246
|
-
return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
|
|
1247
|
-
}
|
|
1248
|
-
async function isWorkingNpx(npxPath) {
|
|
1249
|
-
try {
|
|
1250
|
-
if (!await pathExists(npxPath)) return false;
|
|
1251
|
-
if (process.platform !== "win32") {
|
|
1252
|
-
try {
|
|
1253
|
-
await fs4.access(npxPath, fs4.constants.X_OK);
|
|
1254
|
-
} catch {
|
|
1255
|
-
return false;
|
|
1256
|
-
}
|
|
1257
|
-
}
|
|
1258
|
-
await execFileAsync(npxPath, ["--version"], { timeout: 1e4 });
|
|
1259
|
-
return true;
|
|
1260
|
-
} catch {
|
|
1261
|
-
return false;
|
|
1262
|
-
}
|
|
1263
|
-
}
|
|
1264
|
-
async function resolveNpxPath() {
|
|
1265
|
-
const npxName = process.platform === "win32" ? "npx.cmd" : "npx";
|
|
1266
|
-
const candidates = [];
|
|
1267
|
-
if (process.platform === "darwin") {
|
|
1268
|
-
candidates.push("/opt/homebrew/bin/npx", "/usr/local/bin/npx");
|
|
1269
|
-
} else if (process.platform === "linux") {
|
|
1270
|
-
candidates.push("/usr/local/bin/npx");
|
|
1271
|
-
}
|
|
1272
|
-
const pathSep = process.platform === "win32" ? ";" : ":";
|
|
1273
|
-
for (const dir of (process.env.PATH ?? "").split(pathSep)) {
|
|
1274
|
-
if (!dir) continue;
|
|
1275
|
-
candidates.push(path6.join(dir, npxName));
|
|
998
|
+
const pathSep = process.platform === "win32" ? ";" : ":";
|
|
999
|
+
for (const dir of (process.env.PATH ?? "").split(pathSep)) {
|
|
1000
|
+
if (!dir) continue;
|
|
1001
|
+
candidates.push(path6.join(dir, npxName));
|
|
1276
1002
|
}
|
|
1277
1003
|
try {
|
|
1278
1004
|
const lookupCmd = process.platform === "win32" ? "where" : "which";
|
|
@@ -1380,539 +1106,1002 @@ async function auditCursorMcpConfig(options) {
|
|
|
1380
1106
|
} catch {
|
|
1381
1107
|
repoHasManagedMemoraone = false;
|
|
1382
1108
|
}
|
|
1383
|
-
let globalHasManagedMemoraone = false;
|
|
1384
|
-
if (globalDetection.ok) {
|
|
1385
|
-
try {
|
|
1386
|
-
const globalParsed = await readCursorMcpConfigObject(globalConfigPath);
|
|
1387
|
-
globalHasManagedMemoraone = cursorConfigHasManagedMemoraone(globalParsed);
|
|
1388
|
-
} catch {
|
|
1389
|
-
globalHasManagedMemoraone = false;
|
|
1109
|
+
let globalHasManagedMemoraone = false;
|
|
1110
|
+
if (globalDetection.ok) {
|
|
1111
|
+
try {
|
|
1112
|
+
const globalParsed = await readCursorMcpConfigObject(globalConfigPath);
|
|
1113
|
+
globalHasManagedMemoraone = cursorConfigHasManagedMemoraone(globalParsed);
|
|
1114
|
+
} catch {
|
|
1115
|
+
globalHasManagedMemoraone = false;
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
return {
|
|
1119
|
+
repoConfigPath,
|
|
1120
|
+
repoHasManagedMemoraone,
|
|
1121
|
+
globalConfigPath,
|
|
1122
|
+
globalHasManagedMemoraone,
|
|
1123
|
+
conflict: repoHasManagedMemoraone && globalHasManagedMemoraone
|
|
1124
|
+
};
|
|
1125
|
+
}
|
|
1126
|
+
function logCursorMcpConfigAudit(prefix, audit) {
|
|
1127
|
+
console.log(`${prefix} Cursor MCP config audit:`);
|
|
1128
|
+
console.log(
|
|
1129
|
+
`${prefix} repo ${audit.repoConfigPath}: managed memoraone=${audit.repoHasManagedMemoraone}`
|
|
1130
|
+
);
|
|
1131
|
+
console.log(
|
|
1132
|
+
`${prefix} global ${audit.globalConfigPath}: managed memoraone=${audit.globalHasManagedMemoraone}`
|
|
1133
|
+
);
|
|
1134
|
+
if (audit.conflict) {
|
|
1135
|
+
console.warn(
|
|
1136
|
+
`${prefix} WARNING: Both repo and global Cursor MCP define memoraone. Global shared MCP cannot bind per-window repos; remove global memoraone and use repo .cursor/mcp.json only.`
|
|
1137
|
+
);
|
|
1138
|
+
} else if (audit.globalHasManagedMemoraone && !audit.repoHasManagedMemoraone) {
|
|
1139
|
+
console.warn(
|
|
1140
|
+
`${prefix} WARNING: Cursor global MCP has memoraone but this repo lacks .cursor/mcp.json. Global MCP shares one process across windows (first-window-wins roots). Run setup-ide-files --cursor in this repo.`
|
|
1141
|
+
);
|
|
1142
|
+
} else if (audit.repoHasManagedMemoraone && !audit.globalHasManagedMemoraone) {
|
|
1143
|
+
console.log(
|
|
1144
|
+
`${prefix} Cursor MCP is repo-scoped (.cursor/mcp.json) with no global memoraone entry (recommended for multi-repo windows).`
|
|
1145
|
+
);
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
function logCursorMcpCliSummary(info, dryRun, opts) {
|
|
1149
|
+
const { repoConfigPath, repoOutcome, npxPath, cliPath, repoBackupPath, globalConfigPath, globalMemoraoneRemoved, globalBackupPath } = info;
|
|
1150
|
+
const interactive = opts?.forInteractivePostSetup === true;
|
|
1151
|
+
console.log(`[setup-ide-files] Cursor repo MCP config: ${repoConfigPath}`);
|
|
1152
|
+
if (cliPath) {
|
|
1153
|
+
console.log(`[setup-ide-files] Resolved local CLI: ${cliPath}`);
|
|
1154
|
+
} else if (npxPath) {
|
|
1155
|
+
console.log(`[setup-ide-files] Resolved npx: ${npxPath}`);
|
|
1156
|
+
}
|
|
1157
|
+
if (repoBackupPath) {
|
|
1158
|
+
console.log(`[setup-ide-files] Cursor repo MCP config backup: ${repoBackupPath}`);
|
|
1159
|
+
}
|
|
1160
|
+
if (!interactive) {
|
|
1161
|
+
if (repoOutcome === "created") {
|
|
1162
|
+
console.log(
|
|
1163
|
+
dryRun ? `[setup-ide-files] Cursor repo MCP config would be created: ${repoConfigPath}` : `[setup-ide-files] Cursor repo MCP config created: ${repoConfigPath}`
|
|
1164
|
+
);
|
|
1165
|
+
} else if (repoOutcome === "updated") {
|
|
1166
|
+
console.log(
|
|
1167
|
+
dryRun ? `[setup-ide-files] Cursor repo MCP config would be updated: ${repoConfigPath}` : `[setup-ide-files] Cursor repo MCP config updated: ${repoConfigPath}`
|
|
1168
|
+
);
|
|
1169
|
+
} else if (repoOutcome === "skipped") {
|
|
1170
|
+
console.log(`[setup-ide-files] Cursor repo MCP config unchanged: ${repoConfigPath}`);
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
if (globalMemoraoneRemoved && globalConfigPath) {
|
|
1174
|
+
console.log(
|
|
1175
|
+
dryRun ? `[setup-ide-files] Would remove memoraone from Cursor global MCP config: ${globalConfigPath}` : `[setup-ide-files] Removed memoraone from Cursor global MCP config: ${globalConfigPath}`
|
|
1176
|
+
);
|
|
1177
|
+
if (globalBackupPath) {
|
|
1178
|
+
console.log(`[setup-ide-files] Cursor global MCP config backup: ${globalBackupPath}`);
|
|
1179
|
+
}
|
|
1180
|
+
} else if (globalConfigPath) {
|
|
1181
|
+
console.log(
|
|
1182
|
+
`[setup-ide-files] Cursor global MCP config unchanged (no managed memoraone to remove): ${globalConfigPath}`
|
|
1183
|
+
);
|
|
1184
|
+
}
|
|
1185
|
+
console.log(
|
|
1186
|
+
"[setup-ide-files] Each Cursor window uses this repo\u2019s .cursor/mcp.json (separate MCP process per repo)."
|
|
1187
|
+
);
|
|
1188
|
+
if (!interactive) {
|
|
1189
|
+
console.log(
|
|
1190
|
+
"[setup-ide-files] Fully quit Cursor and reopen this repo for MCP changes to take effect."
|
|
1191
|
+
);
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
// src/cleanup.ts
|
|
1196
|
+
var execFileAsync2 = (0, import_node_util2.promisify)(import_node_child_process2.execFile);
|
|
1197
|
+
var DAEMON_PROJECT_ID_RE = /--project-id\s+([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/i;
|
|
1198
|
+
var PROJECT_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
1199
|
+
var MEMORAONE_MCP_COMMAND_RE = /memoraone-mcp|memoraOne-mcp|@memoraone\/mcp/;
|
|
1200
|
+
var CLEANUP_PROJECT_ID_REQUIRED_ERROR = "Provide --project-id <id> or run from a folder containing memoraone.m1.";
|
|
1201
|
+
function isMemoraoneMcpCommandLine(commandLine) {
|
|
1202
|
+
return MEMORAONE_MCP_COMMAND_RE.test(commandLine);
|
|
1203
|
+
}
|
|
1204
|
+
function parseDaemonProjectIdFromCommandLine(commandLine) {
|
|
1205
|
+
if (!commandLine.includes("--daemon")) {
|
|
1206
|
+
return null;
|
|
1207
|
+
}
|
|
1208
|
+
const match = commandLine.match(DAEMON_PROJECT_ID_RE);
|
|
1209
|
+
return match ? match[1].toLowerCase() : null;
|
|
1210
|
+
}
|
|
1211
|
+
function isDaemonProcessForProject(commandLine, projectId) {
|
|
1212
|
+
if (!isMemoraoneMcpCommandLine(commandLine)) {
|
|
1213
|
+
return false;
|
|
1214
|
+
}
|
|
1215
|
+
const normalized = projectId.trim().toLowerCase();
|
|
1216
|
+
if (!commandLine.includes(`--project-id ${normalized}`)) {
|
|
1217
|
+
return false;
|
|
1218
|
+
}
|
|
1219
|
+
const daemonProjectId = parseDaemonProjectIdFromCommandLine(commandLine);
|
|
1220
|
+
return daemonProjectId !== null && daemonProjectId === normalized;
|
|
1221
|
+
}
|
|
1222
|
+
function parseDaemonIdeFromCommandLine(commandLine) {
|
|
1223
|
+
if (!commandLine.includes("--daemon")) {
|
|
1224
|
+
return void 0;
|
|
1225
|
+
}
|
|
1226
|
+
return parseIdeTypeFromCommandLine(commandLine);
|
|
1227
|
+
}
|
|
1228
|
+
function isDaemonProcessForProjectAndIde(commandLine, projectId, ide) {
|
|
1229
|
+
if (!isDaemonProcessForProject(commandLine, projectId)) {
|
|
1230
|
+
return false;
|
|
1231
|
+
}
|
|
1232
|
+
const daemonIde = parseDaemonIdeFromCommandLine(commandLine);
|
|
1233
|
+
return daemonIde !== void 0 && daemonIde === ide;
|
|
1234
|
+
}
|
|
1235
|
+
function parseDaemonProcessLines(lines) {
|
|
1236
|
+
const processes = [];
|
|
1237
|
+
for (const line of lines) {
|
|
1238
|
+
const trimmed = line.trim();
|
|
1239
|
+
if (!trimmed) continue;
|
|
1240
|
+
const spaceIdx = trimmed.indexOf(" ");
|
|
1241
|
+
if (spaceIdx <= 0) continue;
|
|
1242
|
+
const pid = Number.parseInt(trimmed.slice(0, spaceIdx), 10);
|
|
1243
|
+
if (!Number.isFinite(pid) || pid <= 0) continue;
|
|
1244
|
+
const command = trimmed.slice(spaceIdx + 1);
|
|
1245
|
+
if (!isMemoraoneMcpCommandLine(command)) continue;
|
|
1246
|
+
const projectId = parseDaemonProjectIdFromCommandLine(command);
|
|
1247
|
+
if (projectId === null) continue;
|
|
1248
|
+
const ide = parseDaemonIdeFromCommandLine(command);
|
|
1249
|
+
processes.push({ pid, command, projectId, ...ide !== void 0 ? { ide } : {} });
|
|
1250
|
+
}
|
|
1251
|
+
return processes;
|
|
1252
|
+
}
|
|
1253
|
+
function normalizeCleanupProjectId(projectId) {
|
|
1254
|
+
const trimmed = projectId.trim();
|
|
1255
|
+
if (!PROJECT_ID_RE.test(trimmed)) {
|
|
1256
|
+
return { error: `Invalid project id: ${projectId}` };
|
|
1257
|
+
}
|
|
1258
|
+
return trimmed.toLowerCase();
|
|
1259
|
+
}
|
|
1260
|
+
async function defaultListDaemonProcesses() {
|
|
1261
|
+
const { stdout } = await execFileAsync2("ps", ["-eo", "pid=,args="], {
|
|
1262
|
+
maxBuffer: 10 * 1024 * 1024
|
|
1263
|
+
});
|
|
1264
|
+
return parseDaemonProcessLines(stdout.split("\n"));
|
|
1265
|
+
}
|
|
1266
|
+
async function defaultListSocketPaths(projectId) {
|
|
1267
|
+
const baseDir = getMcpBaseDir();
|
|
1268
|
+
let entries;
|
|
1269
|
+
try {
|
|
1270
|
+
entries = await fs5.readdir(baseDir);
|
|
1271
|
+
} catch (err) {
|
|
1272
|
+
const code = err && typeof err === "object" && "code" in err ? err.code : void 0;
|
|
1273
|
+
if (code === "ENOENT") {
|
|
1274
|
+
return [];
|
|
1275
|
+
}
|
|
1276
|
+
throw err;
|
|
1277
|
+
}
|
|
1278
|
+
const paths = [];
|
|
1279
|
+
for (const name of entries) {
|
|
1280
|
+
if (!name.endsWith(".sock") || !isMemoraoneSocketFilename(name)) {
|
|
1281
|
+
continue;
|
|
1282
|
+
}
|
|
1283
|
+
const socketPath = path7.join(baseDir, name);
|
|
1284
|
+
if (projectId === null) {
|
|
1285
|
+
paths.push(socketPath);
|
|
1286
|
+
continue;
|
|
1287
|
+
}
|
|
1288
|
+
const normalizedProjectId = projectId.trim().toLowerCase();
|
|
1289
|
+
if (isLegacySocketFilename(name) && isSocketFilenameForProject(name, normalizedProjectId)) {
|
|
1290
|
+
paths.push(socketPath);
|
|
1291
|
+
continue;
|
|
1292
|
+
}
|
|
1293
|
+
if (isHashSocketFilename(name)) {
|
|
1294
|
+
const record = readBindingSidecarRecord(socketPath);
|
|
1295
|
+
if (record?.projectId.trim().toLowerCase() === normalizedProjectId) {
|
|
1296
|
+
paths.push(socketPath);
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
return paths.sort();
|
|
1301
|
+
}
|
|
1302
|
+
async function defaultListSocketPathsForM1Path(m1Path) {
|
|
1303
|
+
const resolvedM1 = path7.resolve(m1Path);
|
|
1304
|
+
const baseDir = getMcpBaseDir();
|
|
1305
|
+
let entries;
|
|
1306
|
+
try {
|
|
1307
|
+
entries = await fs5.readdir(baseDir);
|
|
1308
|
+
} catch (err) {
|
|
1309
|
+
const code = err && typeof err === "object" && "code" in err ? err.code : void 0;
|
|
1310
|
+
if (code === "ENOENT") {
|
|
1311
|
+
return [];
|
|
1312
|
+
}
|
|
1313
|
+
throw err;
|
|
1314
|
+
}
|
|
1315
|
+
const paths = [];
|
|
1316
|
+
for (const name of entries) {
|
|
1317
|
+
if (!name.endsWith(".sock") || !isHashSocketFilename(name)) {
|
|
1318
|
+
continue;
|
|
1319
|
+
}
|
|
1320
|
+
const socketPath = path7.join(baseDir, name);
|
|
1321
|
+
const record = readBindingSidecarRecord(socketPath);
|
|
1322
|
+
if (!record?.m1Path) continue;
|
|
1323
|
+
if (path7.resolve(record.m1Path) === resolvedM1) {
|
|
1324
|
+
paths.push(socketPath);
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
return paths.sort();
|
|
1328
|
+
}
|
|
1329
|
+
async function filterSocketPathsByIdeForCleanup(socketPaths, projectId, ide, m1Path) {
|
|
1330
|
+
if (ide === void 0) return socketPaths;
|
|
1331
|
+
const normalizedProjectId = projectId.trim().toLowerCase();
|
|
1332
|
+
const resolvedM1 = m1Path ? path7.resolve(m1Path) : null;
|
|
1333
|
+
const filtered = [];
|
|
1334
|
+
for (const socketPath of socketPaths) {
|
|
1335
|
+
const basename5 = path7.basename(socketPath);
|
|
1336
|
+
if (isLegacySocketFilename(basename5)) {
|
|
1337
|
+
if (isSocketFilenameForProjectAndIde(basename5, normalizedProjectId, ide)) {
|
|
1338
|
+
filtered.push(socketPath);
|
|
1339
|
+
}
|
|
1340
|
+
continue;
|
|
1341
|
+
}
|
|
1342
|
+
if (isHashSocketFilename(basename5)) {
|
|
1343
|
+
const record = readBindingSidecarRecord(socketPath);
|
|
1344
|
+
if (!record || record.ideType !== ide) continue;
|
|
1345
|
+
const sameProject = record.projectId.trim().toLowerCase() === normalizedProjectId;
|
|
1346
|
+
const sameM1 = resolvedM1 !== null && path7.resolve(record.m1Path) === resolvedM1;
|
|
1347
|
+
if (sameProject || sameM1) {
|
|
1348
|
+
filtered.push(socketPath);
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
return filtered;
|
|
1353
|
+
}
|
|
1354
|
+
async function defaultKillProcess(pid) {
|
|
1355
|
+
process.kill(pid, "SIGTERM");
|
|
1356
|
+
}
|
|
1357
|
+
async function defaultRemoveSocket(socketPath) {
|
|
1358
|
+
await fs5.unlink(socketPath);
|
|
1359
|
+
try {
|
|
1360
|
+
await fs5.unlink(bindingSidecarPath(socketPath));
|
|
1361
|
+
} catch {
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
async function defaultConfirm(message) {
|
|
1365
|
+
if (!import_node_process.stdin.isTTY) {
|
|
1366
|
+
return false;
|
|
1367
|
+
}
|
|
1368
|
+
const rl = readline2.createInterface({ input: import_node_process.stdin, output: import_node_process.stdout });
|
|
1369
|
+
try {
|
|
1370
|
+
const answer = await rl.question(`${message} [y/N] `);
|
|
1371
|
+
return /^y(es)?$/i.test(answer.trim());
|
|
1372
|
+
} finally {
|
|
1373
|
+
rl.close();
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
async function resolveCleanupTarget(cwd) {
|
|
1377
|
+
try {
|
|
1378
|
+
const binding = await resolveAuthoritativeBinding([path7.resolve(cwd)]);
|
|
1379
|
+
return {
|
|
1380
|
+
workspaceRoot: binding.workspaceRoot,
|
|
1381
|
+
m1Path: binding.m1Path,
|
|
1382
|
+
projectId: binding.projectId
|
|
1383
|
+
};
|
|
1384
|
+
} catch {
|
|
1385
|
+
return { error: CLEANUP_PROJECT_ID_REQUIRED_ERROR };
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
function filterProcessesForScope(processes, projectId) {
|
|
1389
|
+
if (projectId === null) {
|
|
1390
|
+
return { matching: processes, skipped: [] };
|
|
1391
|
+
}
|
|
1392
|
+
const normalized = projectId.toLowerCase();
|
|
1393
|
+
const matching = [];
|
|
1394
|
+
const skipped = [];
|
|
1395
|
+
for (const proc of processes) {
|
|
1396
|
+
if (proc.projectId === normalized) {
|
|
1397
|
+
matching.push(proc);
|
|
1398
|
+
} else {
|
|
1399
|
+
skipped.push(proc);
|
|
1390
1400
|
}
|
|
1391
1401
|
}
|
|
1392
|
-
return {
|
|
1393
|
-
repoConfigPath,
|
|
1394
|
-
repoHasManagedMemoraone,
|
|
1395
|
-
globalConfigPath,
|
|
1396
|
-
globalHasManagedMemoraone,
|
|
1397
|
-
conflict: repoHasManagedMemoraone && globalHasManagedMemoraone
|
|
1398
|
-
};
|
|
1402
|
+
return { matching, skipped };
|
|
1399
1403
|
}
|
|
1400
|
-
function
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
);
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
if (audit.conflict) {
|
|
1409
|
-
console.warn(
|
|
1410
|
-
`${prefix} WARNING: Both repo and global Cursor MCP define memoraone. Global shared MCP cannot bind per-window repos; remove global memoraone and use repo .cursor/mcp.json only.`
|
|
1411
|
-
);
|
|
1412
|
-
} else if (audit.globalHasManagedMemoraone && !audit.repoHasManagedMemoraone) {
|
|
1413
|
-
console.warn(
|
|
1414
|
-
`${prefix} WARNING: Cursor global MCP has memoraone but this repo lacks .cursor/mcp.json. Global MCP shares one process across windows (first-window-wins roots). Run setup-ide-files --cursor in this repo.`
|
|
1404
|
+
function logPrefix(dryRun) {
|
|
1405
|
+
return dryRun ? "[cleanup][dry-run]" : "[cleanup]";
|
|
1406
|
+
}
|
|
1407
|
+
function logReconnectNotice(opts, prefix, ide) {
|
|
1408
|
+
if (opts.quiet) return;
|
|
1409
|
+
if (ide) {
|
|
1410
|
+
console.log(
|
|
1411
|
+
`${prefix} Note: Valid ${ide} connections for this project may disconnect temporarily; they should reconnect automatically. Stale connections will remain cleared.`
|
|
1415
1412
|
);
|
|
1416
|
-
} else
|
|
1413
|
+
} else {
|
|
1417
1414
|
console.log(
|
|
1418
|
-
`${prefix}
|
|
1415
|
+
`${prefix} Note: Valid IDE connections for this project may disconnect temporarily; they should reconnect automatically. Stale connections will remain cleared.`
|
|
1419
1416
|
);
|
|
1420
1417
|
}
|
|
1421
1418
|
}
|
|
1422
|
-
function
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
console.log(`[setup-ide-files] Cursor repo MCP config: ${repoConfigPath}`);
|
|
1426
|
-
if (cliPath) {
|
|
1427
|
-
console.log(`[setup-ide-files] Resolved local CLI: ${cliPath}`);
|
|
1428
|
-
} else if (npxPath) {
|
|
1429
|
-
console.log(`[setup-ide-files] Resolved npx: ${npxPath}`);
|
|
1419
|
+
function cleanupLog(opts, message) {
|
|
1420
|
+
if (!opts.quiet) {
|
|
1421
|
+
console.log(message);
|
|
1430
1422
|
}
|
|
1431
|
-
|
|
1432
|
-
|
|
1423
|
+
}
|
|
1424
|
+
function cleanupWarn(opts, message) {
|
|
1425
|
+
if (!opts.quiet) {
|
|
1426
|
+
console.warn(message);
|
|
1433
1427
|
}
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1428
|
+
}
|
|
1429
|
+
async function runCleanup(opts) {
|
|
1430
|
+
const listProcesses = opts.listProcesses ?? defaultListDaemonProcesses;
|
|
1431
|
+
const listSocketPaths = opts.listSocketPaths ?? defaultListSocketPaths;
|
|
1432
|
+
const killProcess = opts.killProcess ?? defaultKillProcess;
|
|
1433
|
+
const removeSocket = opts.removeSocket ?? defaultRemoveSocket;
|
|
1434
|
+
const confirm = opts.confirm ?? defaultConfirm;
|
|
1435
|
+
const prefix = logPrefix(opts.dryRun);
|
|
1436
|
+
let targetProjectId = null;
|
|
1437
|
+
let workspaceRoot;
|
|
1438
|
+
let m1Path;
|
|
1439
|
+
if (opts.allProjects) {
|
|
1440
|
+
if (opts.projectId) {
|
|
1441
|
+
return {
|
|
1442
|
+
exitCode: 1,
|
|
1443
|
+
killedPids: [],
|
|
1444
|
+
removedSockets: [],
|
|
1445
|
+
skippedProcesses: [],
|
|
1446
|
+
error: "Cannot combine --all-projects with --project-id."
|
|
1447
|
+
};
|
|
1445
1448
|
}
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1449
|
+
cleanupLog(opts, `${prefix} Mode: all projects (--all-projects)`);
|
|
1450
|
+
cleanupWarn(
|
|
1451
|
+
opts,
|
|
1452
|
+
`${prefix} WARNING: This stops every MemoraOne MCP daemon and removes all project sockets under ${getMcpBaseDir()}.`
|
|
1450
1453
|
);
|
|
1451
|
-
|
|
1452
|
-
|
|
1454
|
+
} else if (opts.projectId) {
|
|
1455
|
+
const normalized = normalizeCleanupProjectId(opts.projectId);
|
|
1456
|
+
if (typeof normalized !== "string") {
|
|
1457
|
+
return { exitCode: 1, killedPids: [], removedSockets: [], skippedProcesses: [], error: normalized.error };
|
|
1458
|
+
}
|
|
1459
|
+
targetProjectId = normalized;
|
|
1460
|
+
cleanupLog(opts, `${prefix} Project id: ${targetProjectId}`);
|
|
1461
|
+
if (opts.ide) {
|
|
1462
|
+
cleanupLog(opts, `${prefix} IDE filter: ${opts.ide}`);
|
|
1463
|
+
}
|
|
1464
|
+
} else {
|
|
1465
|
+
const target = await resolveCleanupTarget(opts.cwd);
|
|
1466
|
+
if ("error" in target) {
|
|
1467
|
+
return { exitCode: 1, killedPids: [], removedSockets: [], skippedProcesses: [], error: target.error };
|
|
1468
|
+
}
|
|
1469
|
+
targetProjectId = target.projectId;
|
|
1470
|
+
workspaceRoot = target.workspaceRoot;
|
|
1471
|
+
m1Path = target.m1Path;
|
|
1472
|
+
cleanupLog(opts, `${prefix} Workspace root: ${workspaceRoot}`);
|
|
1473
|
+
cleanupLog(opts, `${prefix} memoraone.m1: ${m1Path}`);
|
|
1474
|
+
cleanupLog(opts, `${prefix} Project id: ${targetProjectId}`);
|
|
1475
|
+
if (opts.ide) {
|
|
1476
|
+
cleanupLog(opts, `${prefix} IDE filter: ${opts.ide}`);
|
|
1477
|
+
}
|
|
1478
|
+
if (workspaceRoot && (!opts.ide || opts.ide === "cursor")) {
|
|
1479
|
+
try {
|
|
1480
|
+
const cursorAudit = await auditCursorMcpConfig({ repoRoot: workspaceRoot });
|
|
1481
|
+
logCursorMcpConfigAudit(prefix, cursorAudit);
|
|
1482
|
+
} catch (err) {
|
|
1483
|
+
cleanupWarn(opts, `${prefix} Cursor MCP config audit failed: ${String(err)}`);
|
|
1484
|
+
}
|
|
1453
1485
|
}
|
|
1454
|
-
} else if (globalConfigPath) {
|
|
1455
|
-
console.log(
|
|
1456
|
-
`[setup-ide-files] Cursor global MCP config unchanged (no managed memoraone to remove): ${globalConfigPath}`
|
|
1457
|
-
);
|
|
1458
1486
|
}
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
);
|
|
1462
|
-
if (!interactive) {
|
|
1463
|
-
console.log(
|
|
1464
|
-
"[setup-ide-files] Fully quit Cursor and reopen this repo for MCP changes to take effect."
|
|
1465
|
-
);
|
|
1487
|
+
if (targetProjectId !== null || opts.ide) {
|
|
1488
|
+
logReconnectNotice(opts, prefix, opts.ide);
|
|
1466
1489
|
}
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1490
|
+
const allDaemonProcesses = await listProcesses();
|
|
1491
|
+
const { matching: projectProcesses, skipped: skippedProcesses } = filterProcessesForScope(
|
|
1492
|
+
allDaemonProcesses,
|
|
1493
|
+
targetProjectId
|
|
1494
|
+
);
|
|
1495
|
+
let processesToStop = projectProcesses;
|
|
1496
|
+
const ideSkippedProcesses = [];
|
|
1497
|
+
if (opts.ide && targetProjectId !== null) {
|
|
1498
|
+
processesToStop = [];
|
|
1499
|
+
for (const proc of projectProcesses) {
|
|
1500
|
+
if (proc.ide === opts.ide) {
|
|
1501
|
+
processesToStop.push(proc);
|
|
1502
|
+
} else if (proc.ide === void 0) {
|
|
1503
|
+
ideSkippedProcesses.push(proc);
|
|
1504
|
+
cleanupLog(
|
|
1505
|
+
opts,
|
|
1506
|
+
`${prefix} Skipped daemon pid=${proc.pid} because IDE could not be safely determined.`
|
|
1507
|
+
);
|
|
1508
|
+
} else {
|
|
1509
|
+
ideSkippedProcesses.push(proc);
|
|
1510
|
+
cleanupLog(
|
|
1511
|
+
opts,
|
|
1512
|
+
`${prefix} Skipped daemon pid=${proc.pid} (IDE ${proc.ide} does not match filter ${opts.ide}).`
|
|
1513
|
+
);
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1481
1516
|
}
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1517
|
+
let allSocketPaths = await listSocketPaths(targetProjectId);
|
|
1518
|
+
if (m1Path && !opts.allProjects) {
|
|
1519
|
+
const m1Sockets = await defaultListSocketPathsForM1Path(m1Path);
|
|
1520
|
+
const seen = new Set(allSocketPaths);
|
|
1521
|
+
for (const socketPath of m1Sockets) {
|
|
1522
|
+
if (!seen.has(socketPath)) {
|
|
1523
|
+
seen.add(socketPath);
|
|
1524
|
+
allSocketPaths.push(socketPath);
|
|
1525
|
+
}
|
|
1526
|
+
const record = readBindingSidecarRecord(socketPath);
|
|
1527
|
+
const staleProjectId = record?.projectId?.trim().toLowerCase();
|
|
1528
|
+
if (staleProjectId && targetProjectId !== null && staleProjectId !== targetProjectId) {
|
|
1529
|
+
for (const proc of allDaemonProcesses) {
|
|
1530
|
+
if (proc.projectId !== staleProjectId) continue;
|
|
1531
|
+
if (opts.ide && proc.ide !== void 0 && proc.ide !== opts.ide) continue;
|
|
1532
|
+
if (!processesToStop.some((p) => p.pid === proc.pid)) {
|
|
1533
|
+
processesToStop.push(proc);
|
|
1534
|
+
cleanupLog(
|
|
1535
|
+
opts,
|
|
1536
|
+
`${prefix} Including stale daemon pid=${proc.pid} project=${staleProjectId} (sidecar m1=${m1Path})`
|
|
1537
|
+
);
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
allSocketPaths = [...seen].sort();
|
|
1488
1543
|
}
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1544
|
+
const socketPaths = targetProjectId === null ? allSocketPaths : await filterSocketPathsByIdeForCleanup(allSocketPaths, targetProjectId, opts.ide, m1Path);
|
|
1545
|
+
if (opts.allProjects) {
|
|
1546
|
+
const projectIds = /* @__PURE__ */ new Set();
|
|
1547
|
+
for (const proc of processesToStop) {
|
|
1548
|
+
projectIds.add(proc.projectId);
|
|
1549
|
+
}
|
|
1550
|
+
for (const socketPath of socketPaths) {
|
|
1551
|
+
const id = extractProjectIdFromSocketFilename(path7.basename(socketPath));
|
|
1552
|
+
if (id) {
|
|
1553
|
+
projectIds.add(id);
|
|
1554
|
+
continue;
|
|
1555
|
+
}
|
|
1556
|
+
const record = readBindingSidecarRecord(socketPath);
|
|
1557
|
+
if (record) {
|
|
1558
|
+
projectIds.add(record.projectId.trim().toLowerCase());
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
cleanupLog(
|
|
1562
|
+
opts,
|
|
1563
|
+
`${prefix} Projects affected: ${projectIds.size ? [...projectIds].sort().join(", ") : "(none found)"}`
|
|
1564
|
+
);
|
|
1506
1565
|
}
|
|
1507
|
-
|
|
1508
|
-
}
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1566
|
+
if (processesToStop.length) {
|
|
1567
|
+
cleanupLog(opts, `${prefix} Daemon processes to stop:`);
|
|
1568
|
+
for (const proc of processesToStop) {
|
|
1569
|
+
const ideLabel = proc.ide ? ` ide=${proc.ide}` : "";
|
|
1570
|
+
cleanupLog(opts, `${prefix} pid=${proc.pid} project=${proc.projectId}${ideLabel}`);
|
|
1571
|
+
}
|
|
1572
|
+
} else if (ideSkippedProcesses.length) {
|
|
1573
|
+
cleanupLog(opts, `${prefix} No matching daemon processes for IDE filter ${opts.ide}.`);
|
|
1574
|
+
} else {
|
|
1575
|
+
cleanupLog(opts, `${prefix} No matching daemon processes found.`);
|
|
1513
1576
|
}
|
|
1514
|
-
|
|
1515
|
-
}
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
maxBuffer: 10 * 1024 * 1024
|
|
1519
|
-
});
|
|
1520
|
-
return parseDaemonProcessLines(stdout.split("\n"));
|
|
1521
|
-
}
|
|
1522
|
-
async function defaultListSocketPaths(projectId) {
|
|
1523
|
-
const baseDir = getMcpBaseDir();
|
|
1524
|
-
let entries;
|
|
1525
|
-
try {
|
|
1526
|
-
entries = await fs5.readdir(baseDir);
|
|
1527
|
-
} catch (err) {
|
|
1528
|
-
const code = err && typeof err === "object" && "code" in err ? err.code : void 0;
|
|
1529
|
-
if (code === "ENOENT") {
|
|
1530
|
-
return [];
|
|
1577
|
+
if (socketPaths.length) {
|
|
1578
|
+
cleanupLog(opts, `${prefix} Sockets to remove:`);
|
|
1579
|
+
for (const socketPath of socketPaths) {
|
|
1580
|
+
cleanupLog(opts, `${prefix} ${socketPath}`);
|
|
1531
1581
|
}
|
|
1532
|
-
|
|
1582
|
+
} else {
|
|
1583
|
+
cleanupLog(opts, `${prefix} No matching sockets found under ${getMcpBaseDir()}.`);
|
|
1533
1584
|
}
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1585
|
+
if (skippedProcesses.length) {
|
|
1586
|
+
cleanupLog(opts, `${prefix} Skipped unrelated daemon processes:`);
|
|
1587
|
+
for (const proc of skippedProcesses) {
|
|
1588
|
+
cleanupLog(opts, `${prefix} pid=${proc.pid} project=${proc.projectId}`);
|
|
1538
1589
|
}
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1590
|
+
}
|
|
1591
|
+
if (opts.allProjects && !opts.dryRun) {
|
|
1592
|
+
const ok = opts.assumeYes ? true : await confirm(`${prefix} Proceed with cleanup for ALL projects?`);
|
|
1593
|
+
if (!ok) {
|
|
1594
|
+
cleanupLog(opts, `${prefix} Aborted.`);
|
|
1595
|
+
return {
|
|
1596
|
+
exitCode: 1,
|
|
1597
|
+
workspaceRoot,
|
|
1598
|
+
m1Path,
|
|
1599
|
+
projectId: targetProjectId ?? void 0,
|
|
1600
|
+
killedPids: [],
|
|
1601
|
+
removedSockets: [],
|
|
1602
|
+
skippedProcesses: [...skippedProcesses, ...ideSkippedProcesses],
|
|
1603
|
+
error: opts.assumeYes ? void 0 : "Aborted (--all requires --yes in non-interactive mode)"
|
|
1604
|
+
};
|
|
1543
1605
|
}
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1606
|
+
}
|
|
1607
|
+
const killedPids = [];
|
|
1608
|
+
const removedSockets = [];
|
|
1609
|
+
if (opts.dryRun) {
|
|
1610
|
+
cleanupLog(opts, `${prefix} Dry run complete \u2014 no processes stopped, no sockets removed.`);
|
|
1611
|
+
return {
|
|
1612
|
+
exitCode: 0,
|
|
1613
|
+
workspaceRoot,
|
|
1614
|
+
m1Path,
|
|
1615
|
+
projectId: targetProjectId ?? void 0,
|
|
1616
|
+
killedPids: processesToStop.map((p) => p.pid),
|
|
1617
|
+
removedSockets: socketPaths,
|
|
1618
|
+
skippedProcesses: [...skippedProcesses, ...ideSkippedProcesses]
|
|
1619
|
+
};
|
|
1620
|
+
}
|
|
1621
|
+
for (const proc of processesToStop) {
|
|
1622
|
+
try {
|
|
1623
|
+
await killProcess(proc.pid);
|
|
1624
|
+
killedPids.push(proc.pid);
|
|
1625
|
+
cleanupLog(opts, `${prefix} Stopped daemon pid=${proc.pid} project=${proc.projectId}`);
|
|
1626
|
+
} catch (err) {
|
|
1627
|
+
cleanupWarn(opts, `${prefix} Could not stop pid=${proc.pid}: ${String(err)}`);
|
|
1548
1628
|
}
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1629
|
+
}
|
|
1630
|
+
if (killedPids.length) {
|
|
1631
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
1632
|
+
}
|
|
1633
|
+
for (const socketPath of socketPaths) {
|
|
1634
|
+
try {
|
|
1635
|
+
await removeSocket(socketPath);
|
|
1636
|
+
removedSockets.push(socketPath);
|
|
1637
|
+
cleanupLog(opts, `${prefix} Removed socket ${socketPath}`);
|
|
1638
|
+
} catch (err) {
|
|
1639
|
+
const code = err && typeof err === "object" && "code" in err ? err.code : void 0;
|
|
1640
|
+
if (code !== "ENOENT") {
|
|
1641
|
+
cleanupWarn(opts, `${prefix} Could not remove socket ${socketPath}: ${String(err)}`);
|
|
1553
1642
|
}
|
|
1554
1643
|
}
|
|
1555
1644
|
}
|
|
1556
|
-
|
|
1645
|
+
cleanupLog(opts, `${prefix} Done. stopped=${killedPids.length} socketsRemoved=${removedSockets.length}`);
|
|
1646
|
+
return {
|
|
1647
|
+
exitCode: 0,
|
|
1648
|
+
workspaceRoot,
|
|
1649
|
+
m1Path,
|
|
1650
|
+
projectId: targetProjectId ?? void 0,
|
|
1651
|
+
killedPids,
|
|
1652
|
+
removedSockets,
|
|
1653
|
+
skippedProcesses: [...skippedProcesses, ...ideSkippedProcesses]
|
|
1654
|
+
};
|
|
1557
1655
|
}
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1656
|
+
function parseCleanupFlags(argv) {
|
|
1657
|
+
let dryRun = false;
|
|
1658
|
+
let allProjects = false;
|
|
1659
|
+
let assumeYes = false;
|
|
1660
|
+
let projectId;
|
|
1661
|
+
let ide;
|
|
1662
|
+
let invalidIde;
|
|
1663
|
+
const unknown = [];
|
|
1664
|
+
for (let i = 0; i < argv.length; i++) {
|
|
1665
|
+
const arg = argv[i];
|
|
1666
|
+
if (arg === "--dry-run") dryRun = true;
|
|
1667
|
+
else if (arg === "--all-projects" || arg === "--all") allProjects = true;
|
|
1668
|
+
else if (arg === "--yes" || arg === "-y") assumeYes = true;
|
|
1669
|
+
else if (arg === "--project-id") {
|
|
1670
|
+
if (i + 1 >= argv.length) {
|
|
1671
|
+
unknown.push("--project-id (missing value)");
|
|
1672
|
+
} else {
|
|
1673
|
+
projectId = argv[++i];
|
|
1567
1674
|
}
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1675
|
+
} else if (arg === "--ide") {
|
|
1676
|
+
if (i + 1 >= argv.length) {
|
|
1677
|
+
unknown.push("--ide (missing value)");
|
|
1678
|
+
} else {
|
|
1679
|
+
const value = argv[++i];
|
|
1680
|
+
if (IDE_TYPES.includes(value)) {
|
|
1681
|
+
ide = value;
|
|
1682
|
+
} else {
|
|
1683
|
+
invalidIde = value;
|
|
1684
|
+
}
|
|
1574
1685
|
}
|
|
1575
|
-
}
|
|
1686
|
+
} else if (arg.startsWith("-")) unknown.push(arg);
|
|
1687
|
+
else unknown.push(arg);
|
|
1576
1688
|
}
|
|
1577
|
-
return
|
|
1578
|
-
}
|
|
1579
|
-
async function defaultKillProcess(pid) {
|
|
1580
|
-
process.kill(pid, "SIGTERM");
|
|
1689
|
+
return { dryRun, allProjects, assumeYes, projectId, ide, invalidIde, unknown };
|
|
1581
1690
|
}
|
|
1582
|
-
async function
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1691
|
+
async function cliCleanup(argv) {
|
|
1692
|
+
const { dryRun, allProjects, assumeYes, projectId, ide, invalidIde, unknown } = parseCleanupFlags(argv);
|
|
1693
|
+
if (invalidIde) {
|
|
1694
|
+
console.error(
|
|
1695
|
+
`[cleanup] Invalid --ide value: ${invalidIde}. Expected one of: ${IDE_TYPES.join(", ")}.`
|
|
1696
|
+
);
|
|
1697
|
+
return 1;
|
|
1587
1698
|
}
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
return false;
|
|
1699
|
+
if (unknown.length) {
|
|
1700
|
+
console.error(`[cleanup] Unknown option(s): ${unknown.join(", ")}`);
|
|
1701
|
+
return 1;
|
|
1592
1702
|
}
|
|
1593
|
-
const
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1703
|
+
const result = await runCleanup({
|
|
1704
|
+
cwd: process.cwd(),
|
|
1705
|
+
dryRun,
|
|
1706
|
+
allProjects,
|
|
1707
|
+
assumeYes,
|
|
1708
|
+
projectId,
|
|
1709
|
+
ide
|
|
1710
|
+
});
|
|
1711
|
+
if (result.error) {
|
|
1712
|
+
console.error(`[cleanup] ${result.error}`);
|
|
1599
1713
|
}
|
|
1714
|
+
return result.exitCode;
|
|
1600
1715
|
}
|
|
1601
|
-
|
|
1716
|
+
|
|
1717
|
+
// src/bridgeProxy.ts
|
|
1718
|
+
var defaultLog = (msg) => {
|
|
1719
|
+
process.stderr.write(`[memoraone-mcp][bridge] ${msg}
|
|
1720
|
+
`);
|
|
1721
|
+
};
|
|
1722
|
+
function summarizeJsonRpcMethod(line) {
|
|
1602
1723
|
try {
|
|
1603
|
-
const
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
} catch {
|
|
1610
|
-
return { error: CLEANUP_PROJECT_ID_REQUIRED_ERROR };
|
|
1611
|
-
}
|
|
1612
|
-
}
|
|
1613
|
-
function filterProcessesForScope(processes, projectId) {
|
|
1614
|
-
if (projectId === null) {
|
|
1615
|
-
return { matching: processes, skipped: [] };
|
|
1616
|
-
}
|
|
1617
|
-
const normalized = projectId.toLowerCase();
|
|
1618
|
-
const matching = [];
|
|
1619
|
-
const skipped = [];
|
|
1620
|
-
for (const proc of processes) {
|
|
1621
|
-
if (proc.projectId === normalized) {
|
|
1622
|
-
matching.push(proc);
|
|
1623
|
-
} else {
|
|
1624
|
-
skipped.push(proc);
|
|
1724
|
+
const message = JSON.parse(line.trim());
|
|
1725
|
+
if (typeof message.method === "string") {
|
|
1726
|
+
return message.method;
|
|
1727
|
+
}
|
|
1728
|
+
if (message.id !== void 0) {
|
|
1729
|
+
return `response:id=${String(message.id)}`;
|
|
1625
1730
|
}
|
|
1731
|
+
return "jsonrpc";
|
|
1732
|
+
} catch {
|
|
1733
|
+
return "invalid-json";
|
|
1626
1734
|
}
|
|
1627
|
-
return { matching, skipped };
|
|
1628
1735
|
}
|
|
1629
|
-
function
|
|
1630
|
-
return
|
|
1736
|
+
function connectWithRetry(socketPath, log, maxRetries, retryDelayMs, connect2) {
|
|
1737
|
+
return new Promise((resolve9, reject) => {
|
|
1738
|
+
const tryConnect = (attempt) => {
|
|
1739
|
+
connect2(socketPath).then(resolve9).catch((err) => {
|
|
1740
|
+
if (attempt >= maxRetries) {
|
|
1741
|
+
reject(err);
|
|
1742
|
+
return;
|
|
1743
|
+
}
|
|
1744
|
+
log(`connect attempt ${attempt + 1} failed, retrying in ${retryDelayMs}ms: ${String(err)}`);
|
|
1745
|
+
setTimeout(() => tryConnect(attempt + 1), retryDelayMs);
|
|
1746
|
+
});
|
|
1747
|
+
};
|
|
1748
|
+
tryConnect(0);
|
|
1749
|
+
});
|
|
1631
1750
|
}
|
|
1632
|
-
function
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
);
|
|
1642
|
-
}
|
|
1751
|
+
async function resolveBridgeSessionBinding(params, env = process.env, options = {}) {
|
|
1752
|
+
const bridgeOptions = getBridgeBindingResolveOptions(env);
|
|
1753
|
+
return resolveBindingFromInitializeParams(params, {
|
|
1754
|
+
env,
|
|
1755
|
+
fallbackWorkspaceRoots: getEnvWorkspaceRootCandidates(),
|
|
1756
|
+
rootsListUris: options.rootsListUris,
|
|
1757
|
+
rootsListAttempted: options.rootsListAttempted,
|
|
1758
|
+
...bridgeOptions
|
|
1759
|
+
});
|
|
1643
1760
|
}
|
|
1644
|
-
function
|
|
1645
|
-
|
|
1646
|
-
|
|
1761
|
+
async function stopDaemonsForStaleBinding(stale, env, log) {
|
|
1762
|
+
const ideType = resolveBindingIdeType(env);
|
|
1763
|
+
let processes;
|
|
1764
|
+
try {
|
|
1765
|
+
processes = await defaultListDaemonProcesses();
|
|
1766
|
+
} catch (err) {
|
|
1767
|
+
log(`could not list daemon processes while replacing stale binding: ${String(err)}`);
|
|
1768
|
+
return;
|
|
1647
1769
|
}
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1770
|
+
for (const proc of processes) {
|
|
1771
|
+
const matchesIde = ideType === "" ? isDaemonProcessForProject(proc.command, stale.projectId) : isDaemonProcessForProjectAndIde(proc.command, stale.projectId, ideType);
|
|
1772
|
+
if (!matchesIde) continue;
|
|
1773
|
+
try {
|
|
1774
|
+
process.kill(proc.pid, "SIGTERM");
|
|
1775
|
+
log(`stopped stale daemon pid=${proc.pid} project=${stale.projectId}`);
|
|
1776
|
+
} catch (err) {
|
|
1777
|
+
log(`failed to stop stale daemon pid=${proc.pid}: ${String(err)}`);
|
|
1778
|
+
}
|
|
1652
1779
|
}
|
|
1653
1780
|
}
|
|
1654
|
-
async function
|
|
1655
|
-
const
|
|
1656
|
-
const
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
const prefix = logPrefix(opts.dryRun);
|
|
1661
|
-
let targetProjectId = null;
|
|
1662
|
-
let workspaceRoot;
|
|
1663
|
-
let m1Path;
|
|
1664
|
-
if (opts.allProjects) {
|
|
1665
|
-
if (opts.projectId) {
|
|
1666
|
-
return {
|
|
1667
|
-
exitCode: 1,
|
|
1668
|
-
killedPids: [],
|
|
1669
|
-
removedSockets: [],
|
|
1670
|
-
skippedProcesses: [],
|
|
1671
|
-
error: "Cannot combine --all-projects with --project-id."
|
|
1672
|
-
};
|
|
1673
|
-
}
|
|
1674
|
-
cleanupLog(opts, `${prefix} Mode: all projects (--all-projects)`);
|
|
1675
|
-
cleanupWarn(
|
|
1676
|
-
opts,
|
|
1677
|
-
`${prefix} WARNING: This stops every MemoraOne MCP daemon and removes all project sockets under ${getMcpBaseDir()}.`
|
|
1781
|
+
async function connectOrSpawnDaemonForBinding(binding, opts) {
|
|
1782
|
+
const reconciled = await reconcileResolvedBindingWithDisk(binding);
|
|
1783
|
+
const sessionBinding = reconciled.binding;
|
|
1784
|
+
if (reconciled.cacheRefreshed) {
|
|
1785
|
+
opts.log(
|
|
1786
|
+
`refreshed stale binding from ${sessionBinding.m1Path}: project=${sessionBinding.projectId}`
|
|
1678
1787
|
);
|
|
1679
|
-
}
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
cleanupLog(opts, `${prefix} memoraone.m1: ${m1Path}`);
|
|
1699
|
-
cleanupLog(opts, `${prefix} Project id: ${targetProjectId}`);
|
|
1700
|
-
if (opts.ide) {
|
|
1701
|
-
cleanupLog(opts, `${prefix} IDE filter: ${opts.ide}`);
|
|
1702
|
-
}
|
|
1703
|
-
if (workspaceRoot && (!opts.ide || opts.ide === "cursor")) {
|
|
1788
|
+
}
|
|
1789
|
+
const socketPath = getBindingSocketPath(sessionBinding, opts.env);
|
|
1790
|
+
opts.log(
|
|
1791
|
+
`target daemon socket=${socketPath} project=${sessionBinding.projectId} workspace=${sessionBinding.workspaceRoot}`
|
|
1792
|
+
);
|
|
1793
|
+
let socket;
|
|
1794
|
+
try {
|
|
1795
|
+
socket = await connectWithRetry(
|
|
1796
|
+
socketPath,
|
|
1797
|
+
opts.log,
|
|
1798
|
+
opts.maxRetries,
|
|
1799
|
+
opts.retryDelayMs,
|
|
1800
|
+
opts.connect
|
|
1801
|
+
);
|
|
1802
|
+
verifyDaemonSidecarBinding(socketPath, sessionBinding, opts.env);
|
|
1803
|
+
opts.log("reusing running daemon for session binding");
|
|
1804
|
+
return { socket, binding: sessionBinding, cacheRefreshed: reconciled.cacheRefreshed };
|
|
1805
|
+
} catch (err) {
|
|
1806
|
+
if (socket) {
|
|
1704
1807
|
try {
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
} catch (err) {
|
|
1708
|
-
cleanupWarn(opts, `${prefix} Cursor MCP config audit failed: ${String(err)}`);
|
|
1808
|
+
socket.destroy();
|
|
1809
|
+
} catch {
|
|
1709
1810
|
}
|
|
1811
|
+
socket = void 0;
|
|
1812
|
+
}
|
|
1813
|
+
if (isDaemonBindingMismatchError(err)) {
|
|
1814
|
+
opts.log("stale daemon binding detected; replacing daemon for current memoraone.m1");
|
|
1815
|
+
const staleSidecar = readBindingSidecar(socketPath);
|
|
1816
|
+
if (staleSidecar) {
|
|
1817
|
+
await stopDaemonsForStaleBinding(staleSidecar, opts.env, opts.log);
|
|
1818
|
+
}
|
|
1819
|
+
await stopDaemonsForStaleBinding(sessionBinding, opts.env, opts.log);
|
|
1820
|
+
if (!bindingsMatch(binding, sessionBinding)) {
|
|
1821
|
+
await stopDaemonsForStaleBinding(binding, opts.env, opts.log);
|
|
1822
|
+
}
|
|
1823
|
+
removeDaemonSocketArtifacts(socketPath);
|
|
1710
1824
|
}
|
|
1711
1825
|
}
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1826
|
+
opts.log("daemon not running, spawning...");
|
|
1827
|
+
await opts.spawnDaemon(sessionBinding, socketPath);
|
|
1828
|
+
await new Promise((r) => setTimeout(r, opts.retryDelayMs));
|
|
1829
|
+
socket = await connectWithRetry(
|
|
1830
|
+
socketPath,
|
|
1831
|
+
opts.log,
|
|
1832
|
+
opts.maxRetries,
|
|
1833
|
+
opts.retryDelayMs,
|
|
1834
|
+
opts.connect
|
|
1719
1835
|
);
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1836
|
+
verifyDaemonSidecarBinding(socketPath, sessionBinding, opts.env);
|
|
1837
|
+
return { socket, binding: sessionBinding, cacheRefreshed: reconciled.cacheRefreshed };
|
|
1838
|
+
}
|
|
1839
|
+
var BridgeDaemonRouter = class {
|
|
1840
|
+
constructor(options) {
|
|
1841
|
+
this.activeSocket = null;
|
|
1842
|
+
this.activeBinding = null;
|
|
1843
|
+
this.socketLineReader = null;
|
|
1844
|
+
this.lastInitializeLine = null;
|
|
1845
|
+
this.pendingDeferredClientLines = [];
|
|
1846
|
+
this.handshakeDeferredClientLines = [];
|
|
1847
|
+
this.clientInitializeSeen = false;
|
|
1848
|
+
this.env = options.env ?? process.env;
|
|
1849
|
+
this.stdout = options.stdout ?? process.stdout;
|
|
1850
|
+
this.log = options.log ?? defaultLog;
|
|
1851
|
+
this.cliPath = options.cliPath;
|
|
1852
|
+
this.maxRetries = options.maxRetries ?? 5;
|
|
1853
|
+
this.retryDelayMs = options.retryDelayMs ?? 200;
|
|
1854
|
+
this.lineReader = options.lineReader ?? null;
|
|
1855
|
+
this.connectImpl = options.connect ?? ((socketPath) => new Promise((resolve9, reject) => {
|
|
1856
|
+
const socket = net.connect(socketPath, () => resolve9(socket));
|
|
1857
|
+
socket.on("error", reject);
|
|
1858
|
+
}));
|
|
1859
|
+
this.spawnDaemonImpl = options.spawnDaemon ?? (async (binding) => {
|
|
1860
|
+
const child = (0, import_node_child_process3.spawn)(
|
|
1861
|
+
process.execPath,
|
|
1862
|
+
buildDaemonSpawnArgs(this.cliPath, binding.projectId, this.env),
|
|
1863
|
+
{
|
|
1864
|
+
detached: true,
|
|
1865
|
+
stdio: "ignore",
|
|
1866
|
+
env: {
|
|
1867
|
+
...this.env,
|
|
1868
|
+
MEMORAONE_DAEMON_BINDING_B64: encodeResolvedBinding(binding)
|
|
1869
|
+
}
|
|
1870
|
+
}
|
|
1871
|
+
);
|
|
1872
|
+
child.on("exit", (code, signal) => {
|
|
1873
|
+
logInitializeDebug(
|
|
1874
|
+
this.log,
|
|
1875
|
+
this.env,
|
|
1876
|
+
`spawned daemon exit code=${code ?? "null"} signal=${signal ?? "null"} project=${binding.projectId}`
|
|
1738
1877
|
);
|
|
1739
|
-
}
|
|
1740
|
-
|
|
1878
|
+
});
|
|
1879
|
+
child.unref();
|
|
1880
|
+
});
|
|
1741
1881
|
}
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1882
|
+
getActiveBinding() {
|
|
1883
|
+
return this.activeBinding;
|
|
1884
|
+
}
|
|
1885
|
+
hasClientInitialize() {
|
|
1886
|
+
return this.clientInitializeSeen;
|
|
1887
|
+
}
|
|
1888
|
+
async ensureDaemonForInitialize(params) {
|
|
1889
|
+
logInitializeDebug(
|
|
1890
|
+
this.log,
|
|
1891
|
+
this.env,
|
|
1892
|
+
`initialize payload: ${summarizeInitializeParamsForDebug(params)}`
|
|
1893
|
+
);
|
|
1894
|
+
const bridgeOptions = getBridgeBindingResolveOptions(this.env);
|
|
1895
|
+
const initializeRoots = extractWorkspaceRootsFromInitialize(params);
|
|
1896
|
+
let rootsListUris;
|
|
1897
|
+
let rootsListAttempted = false;
|
|
1898
|
+
const repoHint = getRepoScopedWorkspaceHint(this.env);
|
|
1899
|
+
if (initializeRoots.length === 0 && bridgeOptions.allowEnvWorkspaceFallback === false && repoHint === null) {
|
|
1900
|
+
if (!this.lineReader) {
|
|
1901
|
+
throw new Error(
|
|
1902
|
+
"[memoraone-mcp] Internal error: Cursor workspace binding requires stdin line reader for roots/list"
|
|
1903
|
+
);
|
|
1754
1904
|
}
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1905
|
+
rootsListAttempted = true;
|
|
1906
|
+
this.log("initialize lacks workspace roots; requesting roots/list from Cursor before binding");
|
|
1907
|
+
const rootsListResult = await requestClientRootsListUris({
|
|
1908
|
+
lineReader: this.lineReader,
|
|
1909
|
+
stdout: this.stdout,
|
|
1910
|
+
log: this.log,
|
|
1911
|
+
env: this.env,
|
|
1912
|
+
initializeParams: params
|
|
1913
|
+
});
|
|
1914
|
+
rootsListUris = rootsListResult.uris;
|
|
1915
|
+
if (rootsListResult.deferredLines.length > 0) {
|
|
1916
|
+
this.pendingDeferredClientLines = rootsListResult.deferredLines.slice();
|
|
1917
|
+
logInitializeDebug(
|
|
1918
|
+
this.log,
|
|
1919
|
+
this.env,
|
|
1920
|
+
`queued ${this.pendingDeferredClientLines.length} deferred client line(s) for replay after initialize`
|
|
1921
|
+
);
|
|
1758
1922
|
}
|
|
1759
1923
|
}
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1924
|
+
const binding = await resolveBridgeSessionBinding(params, this.env, {
|
|
1925
|
+
rootsListUris,
|
|
1926
|
+
rootsListAttempted
|
|
1927
|
+
});
|
|
1928
|
+
const environmentLog = binding.environment !== void 0 ? ` environment=${binding.environment}` : "";
|
|
1929
|
+
this.log(
|
|
1930
|
+
`session binding project=${binding.projectId} workspace=${binding.workspaceRoot} m1=${binding.m1Path} source=${binding.bindingSource} apiKeySource=${binding.apiKeySource}${environmentLog}`
|
|
1763
1931
|
);
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
cleanupLog(opts, `${prefix} Daemon processes to stop:`);
|
|
1767
|
-
for (const proc of processesToStop) {
|
|
1768
|
-
const ideLabel = proc.ide ? ` ide=${proc.ide}` : "";
|
|
1769
|
-
cleanupLog(opts, `${prefix} pid=${proc.pid} project=${proc.projectId}${ideLabel}`);
|
|
1932
|
+
if (this.activeBinding && bindingsMatch(this.activeBinding, binding) && this.activeSocket) {
|
|
1933
|
+
return;
|
|
1770
1934
|
}
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1935
|
+
if (this.activeBinding && !bindingsMatch(this.activeBinding, binding)) {
|
|
1936
|
+
this.log(
|
|
1937
|
+
`session binding changed from workspace=${this.activeBinding.workspaceRoot} to workspace=${binding.workspaceRoot}; reconnecting daemon`
|
|
1938
|
+
);
|
|
1939
|
+
this.resetSessionState();
|
|
1940
|
+
this.detachSocketReader();
|
|
1941
|
+
this.activeSocket?.destroy();
|
|
1942
|
+
this.activeSocket = null;
|
|
1943
|
+
}
|
|
1944
|
+
this.activeBinding = binding;
|
|
1945
|
+
await this.connectActiveDaemon();
|
|
1946
|
+
this.log("bridge connected");
|
|
1947
|
+
}
|
|
1948
|
+
recordClientInitialize(line) {
|
|
1949
|
+
this.lastInitializeLine = line;
|
|
1950
|
+
this.clientInitializeSeen = true;
|
|
1775
1951
|
}
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
} else {
|
|
1782
|
-
cleanupLog(opts, `${prefix} No matching sockets found under ${getMcpBaseDir()}.`);
|
|
1952
|
+
async forwardInitializeToDaemon(line) {
|
|
1953
|
+
this.recordClientInitialize(line);
|
|
1954
|
+
await this.writeToDaemon(line);
|
|
1955
|
+
logInitializeDebug(this.log, this.env, "initialize replay to daemon");
|
|
1956
|
+
this.log("forwarding active");
|
|
1783
1957
|
}
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
cleanupLog(opts, `${prefix} pid=${proc.pid} project=${proc.projectId}`);
|
|
1958
|
+
async replayDeferredClientMessages() {
|
|
1959
|
+
if (this.pendingDeferredClientLines.length === 0) {
|
|
1960
|
+
return;
|
|
1788
1961
|
}
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
removedSockets: [],
|
|
1801
|
-
skippedProcesses: [...skippedProcesses, ...ideSkippedProcesses],
|
|
1802
|
-
error: opts.assumeYes ? void 0 : "Aborted (--all requires --yes in non-interactive mode)"
|
|
1803
|
-
};
|
|
1962
|
+
const lines = this.pendingDeferredClientLines.slice();
|
|
1963
|
+
this.pendingDeferredClientLines = [];
|
|
1964
|
+
this.handshakeDeferredClientLines = lines.slice();
|
|
1965
|
+
const types = lines.map((line) => summarizeJsonRpcMethod(line));
|
|
1966
|
+
logInitializeDebug(
|
|
1967
|
+
this.log,
|
|
1968
|
+
this.env,
|
|
1969
|
+
`deferred message replay count=${lines.length} types=${JSON.stringify(types)}`
|
|
1970
|
+
);
|
|
1971
|
+
for (const line of lines) {
|
|
1972
|
+
await this.writeToDaemon(line);
|
|
1804
1973
|
}
|
|
1805
1974
|
}
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
return {
|
|
1811
|
-
exitCode: 0,
|
|
1812
|
-
workspaceRoot,
|
|
1813
|
-
m1Path,
|
|
1814
|
-
projectId: targetProjectId ?? void 0,
|
|
1815
|
-
killedPids: processesToStop.map((p) => p.pid),
|
|
1816
|
-
removedSockets: socketPaths,
|
|
1817
|
-
skippedProcesses: [...skippedProcesses, ...ideSkippedProcesses]
|
|
1818
|
-
};
|
|
1975
|
+
async writeToDaemon(line) {
|
|
1976
|
+
await this.ensureActiveDaemonSocket();
|
|
1977
|
+
this.activeSocket.write(`${line}
|
|
1978
|
+
`);
|
|
1819
1979
|
}
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
cleanupWarn(opts, `${prefix} Could not stop pid=${proc.pid}: ${String(err)}`);
|
|
1827
|
-
}
|
|
1980
|
+
resetSessionState() {
|
|
1981
|
+
this.lastInitializeLine = null;
|
|
1982
|
+
this.pendingDeferredClientLines = [];
|
|
1983
|
+
this.handshakeDeferredClientLines = [];
|
|
1984
|
+
this.clientInitializeSeen = false;
|
|
1985
|
+
this.activeBinding = null;
|
|
1828
1986
|
}
|
|
1829
|
-
|
|
1830
|
-
|
|
1987
|
+
async connectActiveDaemon() {
|
|
1988
|
+
if (!this.activeBinding) {
|
|
1989
|
+
throw new Error("[memoraone-mcp] Internal error: connectActiveDaemon without active binding");
|
|
1990
|
+
}
|
|
1991
|
+
const connected = await connectOrSpawnDaemonForBinding(this.activeBinding, {
|
|
1992
|
+
env: this.env,
|
|
1993
|
+
cliPath: this.cliPath,
|
|
1994
|
+
log: this.log,
|
|
1995
|
+
maxRetries: this.maxRetries,
|
|
1996
|
+
retryDelayMs: this.retryDelayMs,
|
|
1997
|
+
connect: this.connectImpl,
|
|
1998
|
+
spawnDaemon: this.spawnDaemonImpl
|
|
1999
|
+
});
|
|
2000
|
+
this.activeBinding = connected.binding;
|
|
2001
|
+
this.activeSocket = connected.socket;
|
|
2002
|
+
this.attachSocketReader(connected.socket);
|
|
1831
2003
|
}
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
2004
|
+
async ensureActiveDaemonSocket() {
|
|
2005
|
+
if (this.activeSocket && !this.activeSocket.destroyed) {
|
|
2006
|
+
return;
|
|
2007
|
+
}
|
|
2008
|
+
if (!this.clientInitializeSeen || !this.lastInitializeLine || !this.activeBinding) {
|
|
2009
|
+
throw new Error("[memoraone-mcp] MCP request before initialize");
|
|
2010
|
+
}
|
|
2011
|
+
this.log("daemon socket unavailable; reconnecting for session binding");
|
|
2012
|
+
await this.connectActiveDaemon();
|
|
2013
|
+
logInitializeDebug(this.log, this.env, "initialize replay to daemon after reconnect");
|
|
2014
|
+
this.activeSocket.write(`${this.lastInitializeLine}
|
|
2015
|
+
`);
|
|
2016
|
+
if (this.handshakeDeferredClientLines.length > 0) {
|
|
2017
|
+
const types = this.handshakeDeferredClientLines.map(
|
|
2018
|
+
(deferredLine) => summarizeJsonRpcMethod(deferredLine)
|
|
2019
|
+
);
|
|
2020
|
+
logInitializeDebug(
|
|
2021
|
+
this.log,
|
|
2022
|
+
this.env,
|
|
2023
|
+
`deferred message replay after reconnect count=${this.handshakeDeferredClientLines.length} types=${JSON.stringify(types)}`
|
|
2024
|
+
);
|
|
2025
|
+
for (const deferredLine of this.handshakeDeferredClientLines) {
|
|
2026
|
+
this.activeSocket.write(`${deferredLine}
|
|
2027
|
+
`);
|
|
1841
2028
|
}
|
|
1842
2029
|
}
|
|
1843
2030
|
}
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
let invalidIde;
|
|
1862
|
-
const unknown = [];
|
|
1863
|
-
for (let i = 0; i < argv.length; i++) {
|
|
1864
|
-
const arg = argv[i];
|
|
1865
|
-
if (arg === "--dry-run") dryRun = true;
|
|
1866
|
-
else if (arg === "--all-projects" || arg === "--all") allProjects = true;
|
|
1867
|
-
else if (arg === "--yes" || arg === "-y") assumeYes = true;
|
|
1868
|
-
else if (arg === "--project-id") {
|
|
1869
|
-
if (i + 1 >= argv.length) {
|
|
1870
|
-
unknown.push("--project-id (missing value)");
|
|
1871
|
-
} else {
|
|
1872
|
-
projectId = argv[++i];
|
|
2031
|
+
attachSocketReader(socket) {
|
|
2032
|
+
this.detachSocketReader();
|
|
2033
|
+
this.socketLineReader = readline3.createInterface({ input: socket, crlfDelay: Infinity });
|
|
2034
|
+
this.socketLineReader.on("line", (line) => {
|
|
2035
|
+
this.stdout.write(`${line}
|
|
2036
|
+
`);
|
|
2037
|
+
});
|
|
2038
|
+
socket.on("close", (hadError) => {
|
|
2039
|
+
if (this.activeSocket === socket) {
|
|
2040
|
+
logInitializeDebug(
|
|
2041
|
+
this.log,
|
|
2042
|
+
this.env,
|
|
2043
|
+
`daemon socket closed hadError=${String(hadError)}`
|
|
2044
|
+
);
|
|
2045
|
+
this.log("daemon socket closed; bridge stays alive for reconnect");
|
|
2046
|
+
this.detachSocketReader();
|
|
2047
|
+
this.activeSocket = null;
|
|
1873
2048
|
}
|
|
1874
|
-
}
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
} else {
|
|
1882
|
-
invalidIde = value;
|
|
1883
|
-
}
|
|
2049
|
+
});
|
|
2050
|
+
socket.on("error", (err) => {
|
|
2051
|
+
this.log(`socket error: ${String(err)}`);
|
|
2052
|
+
logInitializeDebug(this.log, this.env, `daemon socket error: ${String(err)}`);
|
|
2053
|
+
if (this.activeSocket === socket) {
|
|
2054
|
+
this.detachSocketReader();
|
|
2055
|
+
this.activeSocket = null;
|
|
1884
2056
|
}
|
|
1885
|
-
}
|
|
1886
|
-
else unknown.push(arg);
|
|
1887
|
-
}
|
|
1888
|
-
return { dryRun, allProjects, assumeYes, projectId, ide, invalidIde, unknown };
|
|
1889
|
-
}
|
|
1890
|
-
async function cliCleanup(argv) {
|
|
1891
|
-
const { dryRun, allProjects, assumeYes, projectId, ide, invalidIde, unknown } = parseCleanupFlags(argv);
|
|
1892
|
-
if (invalidIde) {
|
|
1893
|
-
console.error(
|
|
1894
|
-
`[cleanup] Invalid --ide value: ${invalidIde}. Expected one of: ${IDE_TYPES.join(", ")}.`
|
|
1895
|
-
);
|
|
1896
|
-
return 1;
|
|
2057
|
+
});
|
|
1897
2058
|
}
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
2059
|
+
detachSocketReader() {
|
|
2060
|
+
if (this.socketLineReader) {
|
|
2061
|
+
this.socketLineReader.close();
|
|
2062
|
+
this.socketLineReader = null;
|
|
2063
|
+
}
|
|
1901
2064
|
}
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
});
|
|
1910
|
-
|
|
1911
|
-
|
|
2065
|
+
};
|
|
2066
|
+
async function runBridgeProxy(options) {
|
|
2067
|
+
ensureBaseDir();
|
|
2068
|
+
const stdin = options.stdin ?? process.stdin;
|
|
2069
|
+
const stdout = options.stdout ?? process.stdout;
|
|
2070
|
+
const log = options.log ?? defaultLog;
|
|
2071
|
+
const lineReader = options.lineReader ?? new StdioLineReader(stdin);
|
|
2072
|
+
const router = new BridgeDaemonRouter({ ...options, stdout, lineReader });
|
|
2073
|
+
while (true) {
|
|
2074
|
+
const line = await lineReader.readLine();
|
|
2075
|
+
if (line === null) {
|
|
2076
|
+
break;
|
|
2077
|
+
}
|
|
2078
|
+
const trimmed = line.trim();
|
|
2079
|
+
if (trimmed === "") {
|
|
2080
|
+
continue;
|
|
2081
|
+
}
|
|
2082
|
+
let message;
|
|
2083
|
+
try {
|
|
2084
|
+
message = JSON.parse(trimmed);
|
|
2085
|
+
} catch (err) {
|
|
2086
|
+
throw new Error(`[memoraone-mcp] Invalid JSON-RPC on stdin: ${String(err)}`);
|
|
2087
|
+
}
|
|
2088
|
+
if (message.method === "initialize") {
|
|
2089
|
+
log("resolve binding from initialize request before daemon connect");
|
|
2090
|
+
const params = message.params ?? {};
|
|
2091
|
+
await router.ensureDaemonForInitialize(params);
|
|
2092
|
+
await router.forwardInitializeToDaemon(trimmed);
|
|
2093
|
+
await router.replayDeferredClientMessages();
|
|
2094
|
+
continue;
|
|
2095
|
+
}
|
|
2096
|
+
await router.writeToDaemon(trimmed);
|
|
1912
2097
|
}
|
|
1913
|
-
return result.exitCode;
|
|
1914
2098
|
}
|
|
1915
2099
|
|
|
2100
|
+
// src/setupIdeFiles.ts
|
|
2101
|
+
var fs8 = __toESM(require("fs/promises"), 1);
|
|
2102
|
+
var os4 = __toESM(require("os"), 1);
|
|
2103
|
+
var path10 = __toESM(require("path"), 1);
|
|
2104
|
+
|
|
1916
2105
|
// src/jetbrainsMcpConfig.ts
|
|
1917
2106
|
var fs6 = __toESM(require("fs/promises"), 1);
|
|
1918
2107
|
var os3 = __toESM(require("os"), 1);
|