@memoraone/mcp 0.1.29 → 0.1.31

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.
Files changed (4) hide show
  1. package/dist/cli.cjs +2060 -515
  2. package/dist/daemon.cjs +426 -146
  3. package/dist/index.cjs +371 -120
  4. package/package.json +1 -1
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.29",
33
+ version: "0.1.31",
34
34
  type: "module",
35
35
  main: "dist/index.cjs",
36
36
  bin: {
@@ -66,17 +66,272 @@ var require_package = __commonJS({
66
66
  }
67
67
  });
68
68
 
69
- // src/cli.ts
70
- var path6 = __toESM(require("path"), 1);
69
+ // src/bridgeProxy.ts
71
70
  var net = __toESM(require("net"), 1);
72
- var import_node_child_process3 = require("child_process");
71
+ var readline2 = __toESM(require("readline"), 1);
72
+ var import_node_child_process = require("child_process");
73
+
74
+ // src/bindingIdentity.ts
75
+ var crypto = __toESM(require("crypto"), 1);
76
+ var path = __toESM(require("path"), 1);
77
+ var BINDING_SOCKET_HASH_LENGTH = 16;
78
+ function hashBindingIdentity(projectId, workspaceRoot, ideType) {
79
+ const input2 = [
80
+ projectId.trim().toLowerCase(),
81
+ path.resolve(workspaceRoot),
82
+ ideType
83
+ ].join("|");
84
+ return crypto.createHash("sha256").update(input2).digest("hex").slice(0, BINDING_SOCKET_HASH_LENGTH);
85
+ }
86
+ function bindingsMatch(a, b) {
87
+ 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);
88
+ }
89
+ function formatMissingInitializeWorkspaceError(options) {
90
+ const lines = [
91
+ "[memoraone-mcp] Could not resolve workspace from MCP initialize params."
92
+ ];
93
+ if (options?.rootsListAttempted) {
94
+ lines.push(
95
+ "Cursor initialize lacked workspaceFolders/rootUri; roots/list was attempted but returned no usable repo root."
96
+ );
97
+ if (options.rootsListUris && options.rootsListUris.length > 0) {
98
+ lines.push(`roots/list URIs: ${options.rootsListUris.join(", ")}`);
99
+ }
100
+ lines.push(
101
+ "Global Cursor MCP cannot safely bind per-window repos without a workspace signal from Cursor (initialize roots or roots/list)."
102
+ );
103
+ lines.push(
104
+ "Reload MCP in this Cursor window, or ensure this repo has a managed .cursor/mcp.json from setup-ide-files --cursor."
105
+ );
106
+ return lines.join("\n");
107
+ }
108
+ lines.push(
109
+ "Reload MCP in this Cursor window so initialize includes workspaceFolders, rootUri, or a usable roots/list response for this repo."
110
+ );
111
+ return lines.join("\n");
112
+ }
113
+
114
+ // src/bindingSidecar.ts
115
+ var fs3 = __toESM(require("fs"), 1);
116
+ var path4 = __toESM(require("path"), 1);
117
+
118
+ // src/projectBinding.ts
119
+ var fs = __toESM(require("fs/promises"), 1);
120
+ var path2 = __toESM(require("path"), 1);
121
+ var uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
122
+ function normalizeEnvironment(raw) {
123
+ if (raw === void 0 || raw === null || typeof raw !== "string") {
124
+ return void 0;
125
+ }
126
+ const trimmed = raw.trim();
127
+ return trimmed === "" ? void 0 : trimmed;
128
+ }
129
+ function parseAndValidateM1(content, markerPath) {
130
+ let parsed;
131
+ try {
132
+ parsed = JSON.parse(content);
133
+ } catch {
134
+ throw new Error(`[memoraone-mcp] Invalid memoraone.m1 JSON at ${markerPath}`);
135
+ }
136
+ const projectId = parsed?.projectId ?? parsed?.project_id;
137
+ if (!projectId || typeof projectId !== "string") {
138
+ throw new Error(`[memoraone-mcp] memoraone.m1 missing projectId at ${markerPath}`);
139
+ }
140
+ if (!uuidRegex.test(projectId.trim())) {
141
+ throw new Error(`[memoraone-mcp] memoraone.m1 projectId is not a UUID at ${markerPath}`);
142
+ }
143
+ const apiKeyRaw = parsed?.MEMORAONE_API_KEY ?? parsed?.api_key;
144
+ const apiKey = apiKeyRaw !== void 0 && apiKeyRaw !== null && typeof apiKeyRaw === "string" && apiKeyRaw.trim() !== "" ? apiKeyRaw.trim() : null;
145
+ const environment = normalizeEnvironment(parsed?.environment);
146
+ return environment === void 0 ? { projectId: projectId.trim(), apiKey } : { projectId: projectId.trim(), apiKey, environment };
147
+ }
148
+ async function resolveProjectIdFromExplicitM1Path() {
149
+ const raw = process.env.MEMORAONE_M1_PATH;
150
+ if (raw === void 0 || raw.trim() === "") {
151
+ return null;
152
+ }
153
+ const markerPath = path2.resolve(raw);
154
+ try {
155
+ const content = await fs.readFile(markerPath, "utf8");
156
+ const { projectId, apiKey, environment } = parseAndValidateM1(content, markerPath);
157
+ return environment === void 0 ? { projectId, apiKey, foundAt: markerPath } : { projectId, apiKey, environment, foundAt: markerPath };
158
+ } catch (err) {
159
+ if (err?.code === "ENOENT") {
160
+ return null;
161
+ }
162
+ throw err;
163
+ }
164
+ }
165
+ async function findM1WalkingUp(workspaceRoot) {
166
+ let current = path2.resolve(workspaceRoot);
167
+ while (true) {
168
+ const markerPath = path2.join(current, "memoraone.m1");
169
+ try {
170
+ const content = await fs.readFile(markerPath, "utf8");
171
+ const { projectId, apiKey, environment } = parseAndValidateM1(content, markerPath);
172
+ const repoRoot = path2.dirname(markerPath);
173
+ return environment === void 0 ? { projectId, apiKey, repoRoot, markerPath } : { projectId, apiKey, environment, repoRoot, markerPath };
174
+ } catch (err) {
175
+ if (err?.code !== "ENOENT") {
176
+ throw err;
177
+ }
178
+ }
179
+ const parent = path2.dirname(current);
180
+ if (parent === current) {
181
+ break;
182
+ }
183
+ current = parent;
184
+ }
185
+ return null;
186
+ }
187
+ function normalizeWorkspaceSearchRoots(workspaceRoot) {
188
+ if (workspaceRoot === void 0) {
189
+ return [];
190
+ }
191
+ const list = Array.isArray(workspaceRoot) ? workspaceRoot : [workspaceRoot];
192
+ const seen = /* @__PURE__ */ new Set();
193
+ const out = [];
194
+ for (const raw of list) {
195
+ if (raw === void 0) {
196
+ continue;
197
+ }
198
+ const trimmed = String(raw).trim();
199
+ if (trimmed === "") {
200
+ continue;
201
+ }
202
+ const resolved = path2.resolve(trimmed);
203
+ if (!seen.has(resolved)) {
204
+ seen.add(resolved);
205
+ out.push(resolved);
206
+ }
207
+ }
208
+ return out;
209
+ }
210
+ function resolveApiKeyWithSource(fileApiKey) {
211
+ const envApiKey = process.env.MEMORAONE_API_KEY?.trim();
212
+ if (envApiKey) {
213
+ return { apiKey: envApiKey, apiKeySource: "env" };
214
+ }
215
+ const aliasEnvApiKey = process.env.MEMORA_API_KEY?.trim();
216
+ if (aliasEnvApiKey) {
217
+ return { apiKey: aliasEnvApiKey, apiKeySource: "env" };
218
+ }
219
+ if (fileApiKey) {
220
+ return { apiKey: fileApiKey, apiKeySource: "memoraone.m1" };
221
+ }
222
+ return { apiKey: null, apiKeySource: "none" };
223
+ }
224
+ async function resolveAuthoritativeBinding(workspaceRoot, options = {}) {
225
+ const respectExplicitM1Path = options.respectExplicitM1Path !== false;
226
+ if (respectExplicitM1Path) {
227
+ const explicitBinding = await resolveProjectIdFromExplicitM1Path();
228
+ if (explicitBinding) {
229
+ const resolved = resolveApiKeyWithSource(explicitBinding.apiKey);
230
+ return {
231
+ projectId: explicitBinding.projectId,
232
+ workspaceRoot: path2.dirname(explicitBinding.foundAt),
233
+ m1Path: explicitBinding.foundAt,
234
+ apiKey: resolved.apiKey,
235
+ ...explicitBinding.environment !== void 0 ? { environment: explicitBinding.environment } : {},
236
+ bindingSource: "explicit-m1-path",
237
+ apiKeySource: resolved.apiKeySource
238
+ };
239
+ }
240
+ }
241
+ const candidates = normalizeWorkspaceSearchRoots(workspaceRoot);
242
+ if (candidates.length === 0) {
243
+ throw new Error("Could not find memoraone.m1 in workspace.\nOpen a folder containing memoraone.m1.");
244
+ }
245
+ const bindings = [];
246
+ for (const root of candidates) {
247
+ const binding = await findM1WalkingUp(root);
248
+ if (binding) {
249
+ const resolved = resolveApiKeyWithSource(binding.apiKey);
250
+ bindings.push({
251
+ projectId: binding.projectId,
252
+ workspaceRoot: binding.repoRoot,
253
+ m1Path: binding.markerPath,
254
+ apiKey: resolved.apiKey,
255
+ ...binding.environment !== void 0 ? { environment: binding.environment } : {},
256
+ bindingSource: "workspace-search",
257
+ apiKeySource: resolved.apiKeySource
258
+ });
259
+ }
260
+ }
261
+ if (bindings.length === 0) {
262
+ throw new Error("Could not find memoraone.m1 in workspace.\nOpen a folder containing memoraone.m1.");
263
+ }
264
+ const distinctProjectIds = new Set(bindings.map((b) => b.projectId));
265
+ if (distinctProjectIds.size > 1) {
266
+ const lines = bindings.map(
267
+ (b) => ` - workspace=${b.workspaceRoot} project=${b.projectId} m1=${b.m1Path}`
268
+ );
269
+ throw new Error(
270
+ "[memoraone-mcp] Ambiguous workspace binding: multiple open roots map to different MemoraOne projects.\n" + lines.join("\n") + "\nOpen one repo per Cursor window, or use repo-scoped .cursor/mcp.json from setup-ide-files --cursor."
271
+ );
272
+ }
273
+ return bindings[0];
274
+ }
275
+ function encodeResolvedBinding(binding) {
276
+ return Buffer.from(JSON.stringify(binding), "utf8").toString("base64");
277
+ }
278
+ function decodeResolvedBinding(value) {
279
+ if (!value) {
280
+ return null;
281
+ }
282
+ let parsed;
283
+ try {
284
+ parsed = JSON.parse(Buffer.from(value, "base64").toString("utf8"));
285
+ } catch {
286
+ throw new Error("[memoraone-mcp] Invalid encoded binding payload");
287
+ }
288
+ const projectId = parsed?.projectId;
289
+ const workspaceRoot = parsed?.workspaceRoot;
290
+ const m1Path = parsed?.m1Path;
291
+ const apiKey = parsed?.apiKey;
292
+ const environment = normalizeEnvironment(parsed?.environment);
293
+ const bindingSource = parsed?.bindingSource;
294
+ const apiKeySource = parsed?.apiKeySource;
295
+ if (!projectId || typeof projectId !== "string" || !uuidRegex.test(projectId.trim())) {
296
+ throw new Error("[memoraone-mcp] Invalid binding projectId");
297
+ }
298
+ if (!workspaceRoot || typeof workspaceRoot !== "string") {
299
+ throw new Error("[memoraone-mcp] Invalid binding workspaceRoot");
300
+ }
301
+ if (!m1Path || typeof m1Path !== "string") {
302
+ throw new Error("[memoraone-mcp] Invalid binding m1Path");
303
+ }
304
+ if (apiKey !== null && apiKey !== void 0 && typeof apiKey !== "string") {
305
+ throw new Error("[memoraone-mcp] Invalid binding apiKey");
306
+ }
307
+ if (bindingSource !== "explicit-m1-path" && bindingSource !== "workspace-search") {
308
+ throw new Error("[memoraone-mcp] Invalid binding source");
309
+ }
310
+ if (apiKeySource !== "env" && apiKeySource !== "memoraone.m1" && apiKeySource !== "none") {
311
+ throw new Error("[memoraone-mcp] Invalid binding apiKeySource");
312
+ }
313
+ return {
314
+ projectId: projectId.trim(),
315
+ workspaceRoot,
316
+ m1Path,
317
+ apiKey: typeof apiKey === "string" && apiKey.trim() !== "" ? apiKey.trim() : null,
318
+ ...environment !== void 0 ? { environment } : {},
319
+ bindingSource,
320
+ apiKeySource
321
+ };
322
+ }
73
323
 
74
324
  // src/socketPaths.ts
75
325
  var os = __toESM(require("os"), 1);
76
- var path = __toESM(require("path"), 1);
77
- var fs = __toESM(require("fs"), 1);
78
- var BASE_DIR = process.env.MEMORAONE_MCP_LOCK_DIR || path.join(os.homedir(), ".memoraone-mcp");
79
- var SOCKET_PROJECT_ID_RE = /^mcp-([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?:-([a-z-]+))?\.sock$/i;
326
+ var path3 = __toESM(require("path"), 1);
327
+ var fs2 = __toESM(require("fs"), 1);
328
+ var BASE_DIR = process.env.MEMORAONE_MCP_LOCK_DIR || path3.join(os.homedir(), ".memoraone-mcp");
329
+ var HASH_SOCKET_FILENAME_RE = new RegExp(
330
+ `^mcp-[0-9a-f]{${BINDING_SOCKET_HASH_LENGTH}}\\.sock$`,
331
+ "i"
332
+ );
333
+ var LEGACY_SOCKET_FILENAME_RE = /^mcp-([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?:-([0-9a-f]{12}))?(?:-(cursor|jetbrains|copilot-vscode))?\.sock$/i;
334
+ var LEGACY_SOCKET_PROJECT_ID_RE = LEGACY_SOCKET_FILENAME_RE;
80
335
  function getMcpBaseDir() {
81
336
  return BASE_DIR;
82
337
  }
@@ -103,24 +358,33 @@ function buildDaemonSpawnArgs(scriptPath, projectId, env = process.env) {
103
358
  }
104
359
  return args2;
105
360
  }
106
- function getSocketPath(projectId, ideType) {
107
- if (ideType) {
108
- return path.join(BASE_DIR, `mcp-${projectId}-${ideType}.sock`);
109
- }
110
- return path.join(BASE_DIR, `mcp-${projectId}.sock`);
361
+ function resolveBindingIdeType(env = process.env) {
362
+ return resolveIdeTypeFromEnv(env) ?? "";
363
+ }
364
+ function getBindingSocketFilename(binding, env = process.env) {
365
+ const ideType = resolveBindingIdeType(env);
366
+ const hash = hashBindingIdentity(binding.projectId, binding.workspaceRoot, ideType);
367
+ return `mcp-${hash}.sock`;
111
368
  }
112
- function getDaemonSocketPath(projectId, env = process.env) {
113
- return getSocketPath(projectId, resolveIdeTypeFromEnv(env));
369
+ function getBindingSocketPath(binding, env = process.env) {
370
+ return path3.join(BASE_DIR, getBindingSocketFilename(binding, env));
114
371
  }
115
372
  function ensureBaseDir() {
116
- fs.mkdirSync(BASE_DIR, { recursive: true });
373
+ fs2.mkdirSync(BASE_DIR, { recursive: true });
117
374
  return BASE_DIR;
118
375
  }
376
+ function isHashSocketFilename(filename) {
377
+ return HASH_SOCKET_FILENAME_RE.test(path3.basename(filename));
378
+ }
379
+ function isLegacySocketFilename(filename) {
380
+ return LEGACY_SOCKET_FILENAME_RE.test(path3.basename(filename));
381
+ }
119
382
  function isMemoraoneSocketFilename(filename) {
120
- return SOCKET_PROJECT_ID_RE.test(path.basename(filename));
383
+ const base = path3.basename(filename);
384
+ return HASH_SOCKET_FILENAME_RE.test(base) || LEGACY_SOCKET_PROJECT_ID_RE.test(base);
121
385
  }
122
386
  function extractProjectIdFromSocketFilename(filename) {
123
- const match = path.basename(filename).match(SOCKET_PROJECT_ID_RE);
387
+ const match = path3.basename(filename).match(LEGACY_SOCKET_PROJECT_ID_RE);
124
388
  return match ? match[1].toLowerCase() : null;
125
389
  }
126
390
  function isSocketFilenameForProject(filename, projectId) {
@@ -128,11 +392,11 @@ function isSocketFilenameForProject(filename, projectId) {
128
392
  return extracted !== null && extracted === projectId.trim().toLowerCase();
129
393
  }
130
394
  function extractIdeTypeFromSocketFilename(filename) {
131
- const match = path.basename(filename).match(SOCKET_PROJECT_ID_RE);
395
+ const match = path3.basename(filename).match(LEGACY_SOCKET_FILENAME_RE);
132
396
  if (!match) return null;
133
- const suffix = match[2];
134
- if (suffix === void 0) return "legacy";
135
- if (IDE_TYPE_SET.has(suffix)) return suffix;
397
+ const ide = match[3];
398
+ if (ide === void 0) return "legacy";
399
+ if (IDE_TYPE_SET.has(ide)) return ide;
136
400
  return null;
137
401
  }
138
402
  function isSocketFilenameForProjectAndIde(filename, projectId, ide) {
@@ -146,163 +410,1021 @@ function isSocketFilenameForProjectAndIde(filename, projectId, ide) {
146
410
  return socketIde === ide;
147
411
  }
148
412
 
149
- // src/projectBinding.ts
150
- var fs2 = __toESM(require("fs/promises"), 1);
151
- var path2 = __toESM(require("path"), 1);
152
- var uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
153
- function normalizeEnvironment(raw) {
154
- if (raw === void 0 || raw === null || typeof raw !== "string") {
155
- return void 0;
413
+ // src/bindingSidecar.ts
414
+ function bindingSidecarPath(socketPath) {
415
+ if (socketPath.endsWith(".sock")) {
416
+ return `${socketPath.slice(0, -".sock".length)}.binding.json`;
417
+ }
418
+ return `${socketPath}.binding.json`;
419
+ }
420
+ function parseSidecarRecord(raw) {
421
+ try {
422
+ const parsed = JSON.parse(raw);
423
+ if (!parsed?.binding) {
424
+ return null;
425
+ }
426
+ const binding = decodeResolvedBinding(parsed.binding);
427
+ return {
428
+ v: typeof parsed.v === "number" ? parsed.v : 1,
429
+ ...parsed.ideType ? { ideType: parsed.ideType } : {},
430
+ projectId: parsed.projectId ?? binding.projectId,
431
+ workspaceRoot: parsed.workspaceRoot ?? binding.workspaceRoot,
432
+ m1Path: parsed.m1Path ?? binding.m1Path,
433
+ binding: parsed.binding
434
+ };
435
+ } catch {
436
+ return null;
437
+ }
438
+ }
439
+ function readBindingSidecarRecord(socketPath) {
440
+ try {
441
+ const raw = fs3.readFileSync(bindingSidecarPath(socketPath), "utf8");
442
+ return parseSidecarRecord(raw);
443
+ } catch {
444
+ return null;
445
+ }
446
+ }
447
+ function formatBindingMismatchError(socketPath, sidecar, expected, detail) {
448
+ const lines = [
449
+ `[memoraone-mcp] Daemon socket binding mismatch at ${path4.basename(socketPath)}.`,
450
+ ` socket: project=${sidecar.projectId} workspace=${sidecar.workspaceRoot} m1=${sidecar.m1Path}`,
451
+ ` session: project=${expected.projectId} workspace=${expected.workspaceRoot} m1=${expected.m1Path}`
452
+ ];
453
+ if (detail) {
454
+ lines.push(` ${detail}`);
455
+ }
456
+ lines.push("Reload MCP in this IDE window or run memoraone-mcp cleanup for the stale socket.");
457
+ return lines.join("\n");
458
+ }
459
+ function verifyDaemonSidecarBinding(socketPath, expected, env = process.env) {
460
+ const record = readBindingSidecarRecord(socketPath);
461
+ if (!record) {
462
+ return null;
463
+ }
464
+ const sidecar = decodeResolvedBinding(record.binding);
465
+ if (!bindingsMatch(sidecar, expected)) {
466
+ throw new Error(formatBindingMismatchError(socketPath, sidecar, expected));
467
+ }
468
+ const expectedIdeType = resolveBindingIdeType(env);
469
+ if (record.ideType !== void 0 && record.ideType !== expectedIdeType) {
470
+ throw new Error(
471
+ formatBindingMismatchError(
472
+ socketPath,
473
+ sidecar,
474
+ expected,
475
+ `ideType: socket=${record.ideType} session=${expectedIdeType || "(none)"}`
476
+ )
477
+ );
478
+ }
479
+ return sidecar;
480
+ }
481
+
482
+ // src/bridgeClientRoots.ts
483
+ var readline = __toESM(require("readline"), 1);
484
+ var TRUTHY = /* @__PURE__ */ new Set(["1", "true", "yes", "on"]);
485
+ function isInitializeDebugEnabled(env = process.env) {
486
+ return TRUTHY.has(String(env.MEMORAONE_DEBUG_INIT ?? "").trim().toLowerCase());
487
+ }
488
+ function logInitializeDebug(log, env, msg) {
489
+ if (isInitializeDebugEnabled(env)) {
490
+ log(`[init-debug] ${msg}`);
491
+ }
492
+ }
493
+ function clientDeclaresRootsCapability(params) {
494
+ const capabilities = params?.capabilities;
495
+ if (capabilities === null || typeof capabilities !== "object") {
496
+ return false;
497
+ }
498
+ return Object.prototype.hasOwnProperty.call(capabilities, "roots");
499
+ }
500
+ function summarizeInitializeParamsForDebug(params) {
501
+ if (!params) {
502
+ return "(no params)";
503
+ }
504
+ const folders = Array.isArray(params.workspaceFolders) ? params.workspaceFolders.map((folder) => {
505
+ if (folder && typeof folder === "object") {
506
+ const entry = folder;
507
+ return { name: entry.name, uri: entry.uri };
508
+ }
509
+ return folder;
510
+ }) : params.workspaceFolders;
511
+ const capabilityKeys = params.capabilities && typeof params.capabilities === "object" ? Object.keys(params.capabilities) : [];
512
+ return JSON.stringify({
513
+ rootUri: params.rootUri,
514
+ workspaceFolders: folders,
515
+ clientInfo: params.clientInfo,
516
+ capabilityKeys
517
+ });
518
+ }
519
+ function extractRootsUrisFromListResult(result) {
520
+ if (result === null || typeof result !== "object") {
521
+ return [];
522
+ }
523
+ const roots = result.roots;
524
+ if (!Array.isArray(roots)) {
525
+ return [];
526
+ }
527
+ const uris = [];
528
+ for (const root of roots) {
529
+ if (root && typeof root === "object") {
530
+ const uri = root.uri;
531
+ if (typeof uri === "string" && uri.trim() !== "") {
532
+ uris.push(uri);
533
+ }
534
+ }
535
+ }
536
+ return uris;
537
+ }
538
+ var StdioLineReader = class {
539
+ constructor(input2) {
540
+ this.queue = [];
541
+ this.waiters = [];
542
+ this.closed = false;
543
+ this.rl = readline.createInterface({ input: input2, crlfDelay: Infinity });
544
+ this.rl.on("line", (line) => {
545
+ if (this.waiters.length > 0) {
546
+ this.waiters.shift()(line);
547
+ return;
548
+ }
549
+ this.queue.push(line);
550
+ });
551
+ this.rl.on("close", () => {
552
+ this.closed = true;
553
+ while (this.waiters.length > 0) {
554
+ this.waiters.shift()(null);
555
+ }
556
+ });
557
+ }
558
+ async readLine() {
559
+ if (this.queue.length > 0) {
560
+ return this.queue.shift() ?? null;
561
+ }
562
+ if (this.closed) {
563
+ return null;
564
+ }
565
+ return new Promise((resolve8) => {
566
+ this.waiters.push(resolve8);
567
+ });
568
+ }
569
+ /** Re-queue lines read during an intermediate protocol step (e.g. roots/list) for the main bridge loop. */
570
+ prependLines(lines) {
571
+ if (lines.length === 0) {
572
+ return;
573
+ }
574
+ for (let i = lines.length - 1; i >= 0; i--) {
575
+ this.queue.unshift(lines[i]);
576
+ }
577
+ }
578
+ close() {
579
+ this.rl.close();
580
+ }
581
+ };
582
+ var nextBridgeRequestId = 1e5;
583
+ async function requestClientRootsListUris(options) {
584
+ const env = options.env ?? process.env;
585
+ const log = options.log ?? (() => {
586
+ });
587
+ const requestId = nextBridgeRequestId++;
588
+ const deferredLines = [];
589
+ const request = {
590
+ jsonrpc: "2.0",
591
+ id: requestId,
592
+ method: "roots/list",
593
+ params: {}
594
+ };
595
+ logInitializeDebug(
596
+ log,
597
+ env,
598
+ `sending roots/list id=${requestId} clientDeclaresRoots=${clientDeclaresRootsCapability(options.initializeParams)}`
599
+ );
600
+ options.stdout.write(`${JSON.stringify(request)}
601
+ `);
602
+ while (true) {
603
+ const line = await options.lineReader.readLine();
604
+ if (line === null) {
605
+ throw new Error("[memoraone-mcp] Client closed stdin before roots/list response");
606
+ }
607
+ const trimmed = line.trim();
608
+ if (trimmed === "") {
609
+ continue;
610
+ }
611
+ let message;
612
+ try {
613
+ message = JSON.parse(trimmed);
614
+ } catch (err) {
615
+ logInitializeDebug(log, env, `ignored non-JSON line while waiting for roots/list: ${String(err)}`);
616
+ continue;
617
+ }
618
+ if (message.id !== requestId) {
619
+ logInitializeDebug(
620
+ log,
621
+ env,
622
+ `deferred JSON-RPC while waiting for roots/list id=${requestId}: ${trimmed.slice(0, 200)}`
623
+ );
624
+ deferredLines.push(trimmed);
625
+ continue;
626
+ }
627
+ if (message.error) {
628
+ throw new Error(
629
+ `[memoraone-mcp] roots/list failed: ${JSON.stringify(message.error)}`
630
+ );
631
+ }
632
+ const uris = extractRootsUrisFromListResult(message.result);
633
+ logInitializeDebug(log, env, `roots/list id=${requestId} returned ${uris.length}: ${JSON.stringify(uris)}`);
634
+ return { uris, deferredLines };
635
+ }
636
+ }
637
+
638
+ // src/initializeBinding.ts
639
+ var path5 = __toESM(require("path"), 1);
640
+ var import_node_url = require("url");
641
+ var MEMORAONE_WORKSPACE_ROOT_ENV = "MEMORAONE_WORKSPACE_ROOT";
642
+ function getBridgeBindingResolveOptions(env = process.env) {
643
+ const ideType = resolveIdeTypeFromEnv(env);
644
+ if (ideType === "cursor") {
645
+ return { respectExplicitM1Path: false, allowEnvWorkspaceFallback: false };
646
+ }
647
+ return { respectExplicitM1Path: true, allowEnvWorkspaceFallback: true };
648
+ }
649
+ function uriToPath(uri) {
650
+ if (uri.startsWith("file://")) {
651
+ return (0, import_node_url.fileURLToPath)(uri);
652
+ }
653
+ return uri;
654
+ }
655
+ function getEnvWorkspaceRootCandidates() {
656
+ const raw = process.env.WORKSPACE_FOLDER_PATHS;
657
+ const parts = [];
658
+ if (raw !== void 0 && raw.trim() !== "") {
659
+ for (const p of raw.split(path5.delimiter).map((s) => s.trim()).filter(Boolean)) {
660
+ parts.push(path5.resolve(p));
661
+ }
662
+ }
663
+ parts.push(process.cwd());
664
+ const seen = /* @__PURE__ */ new Set();
665
+ const deduped = [];
666
+ for (const p of parts) {
667
+ if (!seen.has(p)) {
668
+ seen.add(p);
669
+ deduped.push(p);
670
+ }
671
+ }
672
+ return deduped;
673
+ }
674
+ function extractWorkspaceRootsFromInitialize(params) {
675
+ if (!params) {
676
+ return [];
677
+ }
678
+ const seen = /* @__PURE__ */ new Set();
679
+ const roots = [];
680
+ const addRoot = (uri) => {
681
+ if (uri === void 0 || uri.trim() === "") {
682
+ return;
683
+ }
684
+ const resolved = path5.resolve(uriToPath(uri));
685
+ if (!seen.has(resolved)) {
686
+ seen.add(resolved);
687
+ roots.push(resolved);
688
+ }
689
+ };
690
+ if (Array.isArray(params.workspaceFolders) && params.workspaceFolders.length > 0) {
691
+ for (const folder of params.workspaceFolders) {
692
+ addRoot(folder?.uri);
693
+ }
694
+ return roots;
695
+ }
696
+ if (params.rootUri) {
697
+ addRoot(params.rootUri);
698
+ }
699
+ return roots;
700
+ }
701
+ function getRepoScopedWorkspaceHint(env = process.env) {
702
+ const raw = env[MEMORAONE_WORKSPACE_ROOT_ENV];
703
+ if (raw === void 0 || raw.trim() === "") {
704
+ return null;
705
+ }
706
+ return path5.resolve(raw.trim());
707
+ }
708
+ function formatWorkspaceAmbiguityError(bindings) {
709
+ const lines = bindings.map(
710
+ (b) => ` - workspace=${b.workspaceRoot} project=${b.projectId} m1=${b.m1Path}`
711
+ );
712
+ return "[memoraone-mcp] Ambiguous workspace binding: multiple open roots map to different MemoraOne projects.\n" + lines.join("\n") + `
713
+ Open one repo per Cursor window, or ensure this repo's managed .cursor/mcp.json includes ${MEMORAONE_WORKSPACE_ROOT_ENV} from setup-ide-files --cursor.`;
714
+ }
715
+ function formatRepoHintInitializeMismatchError(repoHintRoot, initializeBinding) {
716
+ return `[memoraone-mcp] Repo-scoped workspace hint conflicts with MCP initialize workspace.
717
+ ${MEMORAONE_WORKSPACE_ROOT_ENV}: ${repoHintRoot}
718
+ initialize: project=${initializeBinding.projectId} workspace=${initializeBinding.workspaceRoot} m1=${initializeBinding.m1Path}
719
+ Reload MCP in the Cursor window for this repo, or re-run setup-ide-files --cursor here if the hint is stale.`;
720
+ }
721
+ function formatRepoHintNotInRootsListError(repoHintRoot, rootsListPaths) {
722
+ return `[memoraone-mcp] Repo-scoped workspace hint does not match any Cursor roots/list entry.
723
+ ${MEMORAONE_WORKSPACE_ROOT_ENV}: ${repoHintRoot}
724
+ roots/list paths: ${rootsListPaths.join(", ")}
725
+ Reload MCP in the Cursor window for this repo, or re-run setup-ide-files --cursor if the hint is stale.`;
726
+ }
727
+ async function resolveBindingFromWorkspaceRoots(workspaceRoots, options = {}) {
728
+ if (workspaceRoots.length === 0) {
729
+ throw new Error("Could not find memoraone.m1 in workspace.\nOpen a folder containing memoraone.m1.");
730
+ }
731
+ const bindings = [];
732
+ for (const root of workspaceRoots) {
733
+ try {
734
+ bindings.push(
735
+ await resolveAuthoritativeBinding(root, {
736
+ respectExplicitM1Path: options.respectExplicitM1Path
737
+ })
738
+ );
739
+ } catch (err) {
740
+ if (err instanceof Error && err.message.includes("Could not find memoraone.m1")) {
741
+ continue;
742
+ }
743
+ throw err;
744
+ }
745
+ }
746
+ if (bindings.length === 0) {
747
+ throw new Error("Could not find memoraone.m1 in workspace.\nOpen a folder containing memoraone.m1.");
748
+ }
749
+ const distinctProjectIds = new Set(bindings.map((b) => b.projectId));
750
+ if (distinctProjectIds.size > 1) {
751
+ throw new Error(formatWorkspaceAmbiguityError(bindings));
752
+ }
753
+ return bindings[0];
754
+ }
755
+ async function resolveBindingFromInitializeParams(params, options = {}) {
756
+ const env = options.env ?? process.env;
757
+ const resolveOpts = {
758
+ respectExplicitM1Path: options.respectExplicitM1Path,
759
+ allowEnvWorkspaceFallback: options.allowEnvWorkspaceFallback
760
+ };
761
+ const repoHint = getRepoScopedWorkspaceHint(env);
762
+ const initializeRoots = extractWorkspaceRootsFromInitialize(params);
763
+ if (initializeRoots.length > 0) {
764
+ const binding = await resolveBindingFromWorkspaceRoots(initializeRoots, resolveOpts);
765
+ if (repoHint !== null) {
766
+ const hintBinding = await resolveAuthoritativeBinding(repoHint, {
767
+ respectExplicitM1Path: false
768
+ });
769
+ if (!bindingsMatch(binding, hintBinding)) {
770
+ throw new Error(formatRepoHintInitializeMismatchError(repoHint, binding));
771
+ }
772
+ }
773
+ return binding;
774
+ }
775
+ const rootsListUris = options.rootsListUris ?? [];
776
+ const rootsListPaths = rootsListUris.map((uri) => uriToPath(uri)).filter(Boolean);
777
+ if (rootsListPaths.length > 1 && repoHint !== null) {
778
+ const hintResolved = path5.resolve(repoHint);
779
+ const matchingRoot = rootsListPaths.find((root) => path5.resolve(root) === hintResolved);
780
+ if (!matchingRoot) {
781
+ throw new Error(formatRepoHintNotInRootsListError(repoHint, rootsListPaths));
782
+ }
783
+ return resolveAuthoritativeBinding(repoHint, { respectExplicitM1Path: false });
784
+ }
785
+ if (rootsListPaths.length > 0) {
786
+ return resolveBindingFromWorkspaceRoots(rootsListPaths, resolveOpts);
787
+ }
788
+ if (repoHint !== null) {
789
+ return resolveAuthoritativeBinding(repoHint, { respectExplicitM1Path: false });
790
+ }
791
+ if (options.allowEnvWorkspaceFallback === false) {
792
+ throw new Error(
793
+ formatMissingInitializeWorkspaceError({
794
+ rootsListAttempted: options.rootsListAttempted === true,
795
+ rootsListUris
796
+ })
797
+ );
798
+ }
799
+ const fallbackRoots = options.fallbackWorkspaceRoots ?? getEnvWorkspaceRootCandidates();
800
+ return resolveAuthoritativeBinding(fallbackRoots, {
801
+ respectExplicitM1Path: options.respectExplicitM1Path
802
+ });
803
+ }
804
+
805
+ // src/bridgeProxy.ts
806
+ var defaultLog = (msg) => {
807
+ process.stderr.write(`[memoraone-mcp][bridge] ${msg}
808
+ `);
809
+ };
810
+ function summarizeJsonRpcMethod(line) {
811
+ try {
812
+ const message = JSON.parse(line.trim());
813
+ if (typeof message.method === "string") {
814
+ return message.method;
815
+ }
816
+ if (message.id !== void 0) {
817
+ return `response:id=${String(message.id)}`;
818
+ }
819
+ return "jsonrpc";
820
+ } catch {
821
+ return "invalid-json";
822
+ }
823
+ }
824
+ function connectWithRetry(socketPath, log, maxRetries, retryDelayMs, connect2) {
825
+ return new Promise((resolve8, reject) => {
826
+ const tryConnect = (attempt) => {
827
+ connect2(socketPath).then(resolve8).catch((err) => {
828
+ if (attempt >= maxRetries) {
829
+ reject(err);
830
+ return;
831
+ }
832
+ log(`connect attempt ${attempt + 1} failed, retrying in ${retryDelayMs}ms: ${String(err)}`);
833
+ setTimeout(() => tryConnect(attempt + 1), retryDelayMs);
834
+ });
835
+ };
836
+ tryConnect(0);
837
+ });
838
+ }
839
+ async function resolveBridgeSessionBinding(params, env = process.env, options = {}) {
840
+ const bridgeOptions = getBridgeBindingResolveOptions(env);
841
+ return resolveBindingFromInitializeParams(params, {
842
+ env,
843
+ fallbackWorkspaceRoots: getEnvWorkspaceRootCandidates(),
844
+ rootsListUris: options.rootsListUris,
845
+ rootsListAttempted: options.rootsListAttempted,
846
+ ...bridgeOptions
847
+ });
848
+ }
849
+ async function connectOrSpawnDaemonForBinding(binding, opts) {
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;
855
+ try {
856
+ socket = await connectWithRetry(
857
+ socketPath,
858
+ opts.log,
859
+ opts.maxRetries,
860
+ opts.retryDelayMs,
861
+ opts.connect
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;
869
+ }
870
+ }
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
+ }
884
+ var BridgeDaemonRouter = class {
885
+ constructor(options) {
886
+ this.activeSocket = null;
887
+ this.activeBinding = null;
888
+ this.socketLineReader = null;
889
+ this.lastInitializeLine = null;
890
+ this.pendingDeferredClientLines = [];
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((resolve8, reject) => {
901
+ const socket = net.connect(socketPath, () => resolve8(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;
929
+ }
930
+ hasClientInitialize() {
931
+ return this.clientInitializeSeen;
932
+ }
933
+ async ensureDaemonForInitialize(params) {
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 fs7 = __toESM(require("fs/promises"), 1);
1146
+ var os4 = __toESM(require("os"), 1);
1147
+ var path9 = __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
+ function buildMemoraoneCursorMcpServer(npxPath, workspaceRoot) {
1165
+ const env = {
1166
+ MEMORAONE_API_URL: "https://api.memoraone.com",
1167
+ MEMORAONE_IDE_TYPE: "cursor"
1168
+ };
1169
+ if (workspaceRoot !== void 0) {
1170
+ env[MEMORAONE_WORKSPACE_ROOT_ENV] = path6.resolve(workspaceRoot);
1171
+ }
1172
+ return {
1173
+ command: npxPath,
1174
+ args: ["-y", "@memoraone/mcp@latest"],
1175
+ env
1176
+ };
1177
+ }
1178
+ async function pathExists(filePath) {
1179
+ try {
1180
+ await fs4.access(filePath);
1181
+ return true;
1182
+ } catch {
1183
+ return false;
1184
+ }
1185
+ }
1186
+ function stripLeadingLineComments(text) {
1187
+ return text.split("\n").filter((line) => !/^\s*\/\//.test(line)).join("\n");
1188
+ }
1189
+ function getKnownCursorGlobalMcpConfigCandidates(homeDir) {
1190
+ return [path6.join(homeDir, ".cursor", "mcp.json")];
1191
+ }
1192
+ async function detectCursorGlobalMcpConfig(options) {
1193
+ if (options?.explicitPath) {
1194
+ return { ok: true, path: options.explicitPath, detectedExisting: await pathExists(options.explicitPath) };
1195
+ }
1196
+ const homeDir = options?.homeDir ?? os2.homedir();
1197
+ const candidates = getKnownCursorGlobalMcpConfigCandidates(homeDir);
1198
+ const existing = [];
1199
+ for (const candidate of candidates) {
1200
+ if (await pathExists(candidate)) existing.push(candidate);
1201
+ }
1202
+ if (existing.length > 1) {
1203
+ return {
1204
+ ok: false,
1205
+ error: "[setup-ide-files] Multiple Cursor global MCP config paths found. Specify one explicitly.",
1206
+ candidates: existing
1207
+ };
1208
+ }
1209
+ if (existing.length === 1) {
1210
+ return { ok: true, path: existing[0], detectedExisting: true };
1211
+ }
1212
+ const defaultPath = candidates[0];
1213
+ if (!defaultPath) {
1214
+ return {
1215
+ ok: false,
1216
+ error: "[setup-ide-files] No known Cursor global MCP config path.",
1217
+ candidates: []
1218
+ };
1219
+ }
1220
+ return { ok: true, path: defaultPath, detectedExisting: false };
1221
+ }
1222
+ function formatBackupTimestamp(d = /* @__PURE__ */ new Date()) {
1223
+ const pad = (n) => String(n).padStart(2, "0");
1224
+ return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
1225
+ }
1226
+ async function isWorkingNpx(npxPath) {
1227
+ try {
1228
+ if (!await pathExists(npxPath)) return false;
1229
+ if (process.platform !== "win32") {
1230
+ try {
1231
+ await fs4.access(npxPath, fs4.constants.X_OK);
1232
+ } catch {
1233
+ return false;
1234
+ }
1235
+ }
1236
+ await execFileAsync(npxPath, ["--version"], { timeout: 1e4 });
1237
+ return true;
1238
+ } catch {
1239
+ return false;
1240
+ }
1241
+ }
1242
+ async function resolveNpxPath() {
1243
+ const npxName = process.platform === "win32" ? "npx.cmd" : "npx";
1244
+ const candidates = [];
1245
+ if (process.platform === "darwin") {
1246
+ candidates.push("/opt/homebrew/bin/npx", "/usr/local/bin/npx");
1247
+ } else if (process.platform === "linux") {
1248
+ candidates.push("/usr/local/bin/npx");
1249
+ }
1250
+ const pathSep = process.platform === "win32" ? ";" : ":";
1251
+ for (const dir of (process.env.PATH ?? "").split(pathSep)) {
1252
+ if (!dir) continue;
1253
+ candidates.push(path6.join(dir, npxName));
156
1254
  }
157
- const trimmed = raw.trim();
158
- return trimmed === "" ? void 0 : trimmed;
159
- }
160
- function parseAndValidateM1(content, markerPath) {
161
- let parsed;
162
1255
  try {
163
- parsed = JSON.parse(content);
1256
+ const lookupCmd = process.platform === "win32" ? "where" : "which";
1257
+ const { stdout } = await execFileAsync(lookupCmd, [npxName], { timeout: 5e3 });
1258
+ const first = stdout.trim().split(/\r?\n/).map((line) => line.trim()).find(Boolean);
1259
+ if (first) candidates.unshift(first);
164
1260
  } catch {
165
- throw new Error(`[memoraone-mcp] Invalid memoraone.m1 JSON at ${markerPath}`);
166
- }
167
- const projectId = parsed?.projectId ?? parsed?.project_id;
168
- if (!projectId || typeof projectId !== "string") {
169
- throw new Error(`[memoraone-mcp] memoraone.m1 missing projectId at ${markerPath}`);
170
1261
  }
171
- if (!uuidRegex.test(projectId.trim())) {
172
- throw new Error(`[memoraone-mcp] memoraone.m1 projectId is not a UUID at ${markerPath}`);
1262
+ const seen = /* @__PURE__ */ new Set();
1263
+ for (const candidate of candidates) {
1264
+ const abs = path6.isAbsolute(candidate) ? candidate : path6.resolve(candidate);
1265
+ const key = process.platform === "win32" ? abs.toLowerCase() : abs;
1266
+ if (seen.has(key)) continue;
1267
+ seen.add(key);
1268
+ if (await isWorkingNpx(abs)) return abs;
173
1269
  }
174
- const apiKeyRaw = parsed?.MEMORAONE_API_KEY ?? parsed?.api_key;
175
- const apiKey = apiKeyRaw !== void 0 && apiKeyRaw !== null && typeof apiKeyRaw === "string" && apiKeyRaw.trim() !== "" ? apiKeyRaw.trim() : null;
176
- const environment = normalizeEnvironment(parsed?.environment);
177
- return environment === void 0 ? { projectId: projectId.trim(), apiKey } : { projectId: projectId.trim(), apiKey, environment };
1270
+ return null;
178
1271
  }
179
- async function resolveProjectIdFromExplicitM1Path() {
180
- const raw = process.env.MEMORAONE_M1_PATH;
181
- if (raw === void 0 || raw.trim() === "") {
182
- return null;
183
- }
184
- const markerPath = path2.resolve(raw);
1272
+ function mergeCursorRepoMcpConfigObject(existing, npxPath, repoRoot) {
1273
+ const base = existing && typeof existing === "object" ? { ...existing } : { mcpServers: {} };
1274
+ const mcpServers = typeof base.mcpServers === "object" && base.mcpServers !== null && !Array.isArray(base.mcpServers) ? { ...base.mcpServers } : {};
1275
+ mcpServers.memoraone = buildMemoraoneCursorMcpServer(npxPath, repoRoot);
1276
+ return { ...base, mcpServers };
1277
+ }
1278
+ function isManagedMemoraoneCursorServer(server) {
1279
+ if (!server || typeof server !== "object") return false;
1280
+ const s = server;
1281
+ if (!Array.isArray(s.args) || s.args.length !== 2) return false;
1282
+ if (s.args[0] !== "-y" || s.args[1] !== "@memoraone/mcp@latest") return false;
1283
+ const env = s.env;
1284
+ if (!env || typeof env !== "object") return false;
1285
+ return memoraoneEnvMatchesBase(env);
1286
+ }
1287
+ function cursorConfigHasManagedMemoraone(parsed) {
1288
+ if (!parsed || typeof parsed !== "object") return false;
1289
+ const mcpServers = parsed.mcpServers;
1290
+ if (!mcpServers || typeof mcpServers !== "object" || Array.isArray(mcpServers)) return false;
1291
+ return isManagedMemoraoneCursorServer(mcpServers.memoraone);
1292
+ }
1293
+ function memoraoneEnvMatchesBase(env) {
1294
+ return env.MEMORAONE_API_URL === "https://api.memoraone.com" && env.MEMORAONE_IDE_TYPE === "cursor";
1295
+ }
1296
+ function getCursorRepoMcpConfigPath(repoRoot) {
1297
+ return path6.join(repoRoot, ".cursor", "mcp.json");
1298
+ }
1299
+ async function readCursorMcpConfigObject(configPath) {
185
1300
  try {
186
- const content = await fs2.readFile(markerPath, "utf8");
187
- const { projectId, apiKey, environment } = parseAndValidateM1(content, markerPath);
188
- return environment === void 0 ? { projectId, apiKey, foundAt: markerPath } : { projectId, apiKey, environment, foundAt: markerPath };
1301
+ const raw = await fs4.readFile(configPath, "utf8");
1302
+ return JSON.parse(stripLeadingLineComments(raw));
189
1303
  } catch (err) {
190
- if (err?.code === "ENOENT") {
191
- return null;
192
- }
1304
+ const code = err && typeof err === "object" && "code" in err ? err.code : void 0;
1305
+ if (code === "ENOENT") return null;
193
1306
  throw err;
194
1307
  }
195
1308
  }
196
- async function findM1WalkingUp(workspaceRoot) {
197
- let current = path2.resolve(workspaceRoot);
198
- while (true) {
199
- const markerPath = path2.join(current, "memoraone.m1");
200
- try {
201
- const content = await fs2.readFile(markerPath, "utf8");
202
- const { projectId, apiKey, environment } = parseAndValidateM1(content, markerPath);
203
- const repoRoot = path2.dirname(markerPath);
204
- return environment === void 0 ? { projectId, apiKey, repoRoot, markerPath } : { projectId, apiKey, environment, repoRoot, markerPath };
205
- } catch (err) {
206
- if (err?.code !== "ENOENT") {
207
- throw err;
208
- }
209
- }
210
- const parent = path2.dirname(current);
211
- if (parent === current) {
212
- break;
213
- }
214
- current = parent;
1309
+ async function removeMemoraoneFromCursorGlobalConfig(options) {
1310
+ const { configPath, dryRun } = options;
1311
+ const parsed = await readCursorMcpConfigObject(configPath);
1312
+ if (!parsed || !cursorConfigHasManagedMemoraone(parsed)) {
1313
+ return { changed: false };
215
1314
  }
216
- return null;
217
- }
218
- function normalizeWorkspaceSearchRoots(workspaceRoot) {
219
- if (workspaceRoot === void 0) {
220
- return [];
1315
+ if (dryRun) {
1316
+ return { changed: true, backupPath: `${configPath}.backup-<timestamp>` };
1317
+ }
1318
+ const backupPath = `${configPath}.backup-${formatBackupTimestamp()}`;
1319
+ await fs4.copyFile(configPath, backupPath);
1320
+ const mcpServers = typeof parsed.mcpServers === "object" && parsed.mcpServers !== null && !Array.isArray(parsed.mcpServers) ? { ...parsed.mcpServers } : {};
1321
+ delete mcpServers.memoraone;
1322
+ const hasOtherServers = Object.keys(mcpServers).length > 0;
1323
+ if (!hasOtherServers) {
1324
+ await fs4.unlink(configPath);
1325
+ return { changed: true, backupPath };
1326
+ }
1327
+ const next = { ...parsed, mcpServers };
1328
+ await fs4.mkdir(path6.dirname(configPath), { recursive: true });
1329
+ await fs4.writeFile(configPath, JSON.stringify(next, null, 2) + "\n", "utf8");
1330
+ return { changed: true, backupPath };
1331
+ }
1332
+ async function auditCursorMcpConfig(options) {
1333
+ const repoRoot = path6.resolve(options?.repoRoot ?? process.cwd());
1334
+ const repoConfigPath = getCursorRepoMcpConfigPath(repoRoot);
1335
+ const globalDetection = await detectCursorGlobalMcpConfig({
1336
+ homeDir: options?.homeDir,
1337
+ explicitPath: options?.explicitGlobalPath
1338
+ });
1339
+ const globalConfigPath = globalDetection.ok ? globalDetection.path : getKnownCursorGlobalMcpConfigCandidates(
1340
+ options?.homeDir ?? os2.homedir()
1341
+ )[0];
1342
+ let repoHasManagedMemoraone = false;
1343
+ try {
1344
+ const repoParsed = await readCursorMcpConfigObject(repoConfigPath);
1345
+ repoHasManagedMemoraone = cursorConfigHasManagedMemoraone(repoParsed);
1346
+ } catch {
1347
+ repoHasManagedMemoraone = false;
221
1348
  }
222
- const list = Array.isArray(workspaceRoot) ? workspaceRoot : [workspaceRoot];
223
- const seen = /* @__PURE__ */ new Set();
224
- const out = [];
225
- for (const raw of list) {
226
- if (raw === void 0) {
227
- continue;
228
- }
229
- const trimmed = String(raw).trim();
230
- if (trimmed === "") {
231
- continue;
232
- }
233
- const resolved = path2.resolve(trimmed);
234
- if (!seen.has(resolved)) {
235
- seen.add(resolved);
236
- out.push(resolved);
1349
+ let globalHasManagedMemoraone = false;
1350
+ if (globalDetection.ok) {
1351
+ try {
1352
+ const globalParsed = await readCursorMcpConfigObject(globalConfigPath);
1353
+ globalHasManagedMemoraone = cursorConfigHasManagedMemoraone(globalParsed);
1354
+ } catch {
1355
+ globalHasManagedMemoraone = false;
237
1356
  }
238
1357
  }
239
- return out;
1358
+ return {
1359
+ repoConfigPath,
1360
+ repoHasManagedMemoraone,
1361
+ globalConfigPath,
1362
+ globalHasManagedMemoraone,
1363
+ conflict: repoHasManagedMemoraone && globalHasManagedMemoraone
1364
+ };
240
1365
  }
241
- function resolveApiKeyWithSource(fileApiKey) {
242
- const envApiKey = process.env.MEMORAONE_API_KEY?.trim();
243
- if (envApiKey) {
244
- return { apiKey: envApiKey, apiKeySource: "env" };
245
- }
246
- const aliasEnvApiKey = process.env.MEMORA_API_KEY?.trim();
247
- if (aliasEnvApiKey) {
248
- return { apiKey: aliasEnvApiKey, apiKeySource: "env" };
249
- }
250
- if (fileApiKey) {
251
- return { apiKey: fileApiKey, apiKeySource: "memoraone.m1" };
1366
+ function logCursorMcpConfigAudit(prefix, audit) {
1367
+ console.log(`${prefix} Cursor MCP config audit:`);
1368
+ console.log(
1369
+ `${prefix} repo ${audit.repoConfigPath}: managed memoraone=${audit.repoHasManagedMemoraone}`
1370
+ );
1371
+ console.log(
1372
+ `${prefix} global ${audit.globalConfigPath}: managed memoraone=${audit.globalHasManagedMemoraone}`
1373
+ );
1374
+ if (audit.conflict) {
1375
+ console.warn(
1376
+ `${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.`
1377
+ );
1378
+ } else if (audit.globalHasManagedMemoraone && !audit.repoHasManagedMemoraone) {
1379
+ console.warn(
1380
+ `${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.`
1381
+ );
1382
+ } else if (audit.repoHasManagedMemoraone && !audit.globalHasManagedMemoraone) {
1383
+ console.log(
1384
+ `${prefix} Cursor MCP is repo-scoped (.cursor/mcp.json) with no global memoraone entry (recommended for multi-repo windows).`
1385
+ );
252
1386
  }
253
- return { apiKey: null, apiKeySource: "none" };
254
1387
  }
255
- async function resolveAuthoritativeBinding(workspaceRoot) {
256
- const explicitBinding = await resolveProjectIdFromExplicitM1Path();
257
- if (explicitBinding) {
258
- const resolved = resolveApiKeyWithSource(explicitBinding.apiKey);
259
- return {
260
- projectId: explicitBinding.projectId,
261
- workspaceRoot: path2.dirname(explicitBinding.foundAt),
262
- m1Path: explicitBinding.foundAt,
263
- apiKey: resolved.apiKey,
264
- ...explicitBinding.environment !== void 0 ? { environment: explicitBinding.environment } : {},
265
- bindingSource: "explicit-m1-path",
266
- apiKeySource: resolved.apiKeySource
267
- };
1388
+ function logCursorMcpCliSummary(info, dryRun) {
1389
+ const { repoConfigPath, repoOutcome, npxPath, repoBackupPath, globalConfigPath, globalMemoraoneRemoved, globalBackupPath } = info;
1390
+ console.log(`[setup-ide-files] Cursor repo MCP config: ${repoConfigPath}`);
1391
+ console.log(`[setup-ide-files] Resolved npx: ${npxPath}`);
1392
+ if (repoBackupPath) {
1393
+ console.log(`[setup-ide-files] Cursor repo MCP config backup: ${repoBackupPath}`);
268
1394
  }
269
- const candidates = normalizeWorkspaceSearchRoots(workspaceRoot);
270
- if (candidates.length === 0) {
271
- throw new Error("Could not find memoraone.m1 in workspace.\nOpen a folder containing memoraone.m1.");
1395
+ if (repoOutcome === "created") {
1396
+ console.log(
1397
+ dryRun ? `[setup-ide-files] Cursor repo MCP config would be created: ${repoConfigPath}` : `[setup-ide-files] Cursor repo MCP config created: ${repoConfigPath}`
1398
+ );
1399
+ } else if (repoOutcome === "updated") {
1400
+ console.log(
1401
+ dryRun ? `[setup-ide-files] Cursor repo MCP config would be updated: ${repoConfigPath}` : `[setup-ide-files] Cursor repo MCP config updated: ${repoConfigPath}`
1402
+ );
1403
+ } else if (repoOutcome === "skipped") {
1404
+ console.log(`[setup-ide-files] Cursor repo MCP config unchanged: ${repoConfigPath}`);
272
1405
  }
273
- for (const root of candidates) {
274
- const binding = await findM1WalkingUp(root);
275
- if (binding) {
276
- const resolved = resolveApiKeyWithSource(binding.apiKey);
277
- return {
278
- projectId: binding.projectId,
279
- workspaceRoot: binding.repoRoot,
280
- m1Path: binding.markerPath,
281
- apiKey: resolved.apiKey,
282
- ...binding.environment !== void 0 ? { environment: binding.environment } : {},
283
- bindingSource: "workspace-search",
284
- apiKeySource: resolved.apiKeySource
285
- };
1406
+ if (globalMemoraoneRemoved && globalConfigPath) {
1407
+ console.log(
1408
+ dryRun ? `[setup-ide-files] Would remove memoraone from Cursor global MCP config: ${globalConfigPath}` : `[setup-ide-files] Removed memoraone from Cursor global MCP config: ${globalConfigPath}`
1409
+ );
1410
+ if (globalBackupPath) {
1411
+ console.log(`[setup-ide-files] Cursor global MCP config backup: ${globalBackupPath}`);
286
1412
  }
1413
+ } else if (globalConfigPath) {
1414
+ console.log(
1415
+ `[setup-ide-files] Cursor global MCP config unchanged (no managed memoraone to remove): ${globalConfigPath}`
1416
+ );
287
1417
  }
288
- throw new Error("Could not find memoraone.m1 in workspace.\nOpen a folder containing memoraone.m1.");
289
- }
290
- function encodeResolvedBinding(binding) {
291
- return Buffer.from(JSON.stringify(binding), "utf8").toString("base64");
1418
+ console.log(
1419
+ "[setup-ide-files] Each Cursor window uses this repo\u2019s .cursor/mcp.json (separate MCP process per repo)."
1420
+ );
1421
+ console.log(
1422
+ "[setup-ide-files] Fully quit Cursor and reopen this repo for MCP changes to take effect."
1423
+ );
292
1424
  }
293
1425
 
294
- // src/setupIdeFiles.ts
295
- var fs5 = __toESM(require("fs/promises"), 1);
296
- var path5 = __toESM(require("path"), 1);
297
-
298
1426
  // src/cleanup.ts
299
- var fs3 = __toESM(require("fs/promises"), 1);
300
- var path3 = __toESM(require("path"), 1);
301
- var readline = __toESM(require("readline/promises"), 1);
302
- var import_node_child_process = require("child_process");
303
- var import_node_util = require("util");
304
- var import_node_process = require("process");
305
- var execFileAsync = (0, import_node_util.promisify)(import_node_child_process.execFile);
1427
+ var execFileAsync2 = (0, import_node_util2.promisify)(import_node_child_process3.execFile);
306
1428
  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;
307
1429
  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;
308
1430
  var MEMORAONE_MCP_COMMAND_RE = /memoraone-mcp|memoraOne-mcp|@memoraone\/mcp/;
@@ -349,7 +1471,7 @@ function normalizeCleanupProjectId(projectId) {
349
1471
  return trimmed.toLowerCase();
350
1472
  }
351
1473
  async function defaultListDaemonProcesses() {
352
- const { stdout } = await execFileAsync("ps", ["-eo", "pid=,args="], {
1474
+ const { stdout } = await execFileAsync2("ps", ["-eo", "pid=,args="], {
353
1475
  maxBuffer: 10 * 1024 * 1024
354
1476
  });
355
1477
  return parseDaemonProcessLines(stdout.split("\n"));
@@ -358,7 +1480,7 @@ async function defaultListSocketPaths(projectId) {
358
1480
  const baseDir = getMcpBaseDir();
359
1481
  let entries;
360
1482
  try {
361
- entries = await fs3.readdir(baseDir);
1483
+ entries = await fs5.readdir(baseDir);
362
1484
  } catch (err) {
363
1485
  const code = err && typeof err === "object" && "code" in err ? err.code : void 0;
364
1486
  if (code === "ENOENT") {
@@ -368,33 +1490,64 @@ async function defaultListSocketPaths(projectId) {
368
1490
  }
369
1491
  const paths = [];
370
1492
  for (const name of entries) {
1493
+ if (!name.endsWith(".sock") || !isMemoraoneSocketFilename(name)) {
1494
+ continue;
1495
+ }
1496
+ const socketPath = path7.join(baseDir, name);
371
1497
  if (projectId === null) {
372
- if (isMemoraoneSocketFilename(name)) {
373
- paths.push(path3.join(baseDir, name));
1498
+ paths.push(socketPath);
1499
+ continue;
1500
+ }
1501
+ const normalizedProjectId = projectId.trim().toLowerCase();
1502
+ if (isLegacySocketFilename(name) && isSocketFilenameForProject(name, normalizedProjectId)) {
1503
+ paths.push(socketPath);
1504
+ continue;
1505
+ }
1506
+ if (isHashSocketFilename(name)) {
1507
+ const record = readBindingSidecarRecord(socketPath);
1508
+ if (record?.projectId.trim().toLowerCase() === normalizedProjectId) {
1509
+ paths.push(socketPath);
374
1510
  }
375
- } else if (isSocketFilenameForProject(name, projectId)) {
376
- paths.push(path3.join(baseDir, name));
377
1511
  }
378
1512
  }
379
1513
  return paths.sort();
380
1514
  }
381
- function filterSocketPathsByIde(socketPaths, projectId, ide) {
1515
+ async function filterSocketPathsByIde(socketPaths, projectId, ide) {
382
1516
  if (ide === void 0) return socketPaths;
383
- return socketPaths.filter(
384
- (socketPath) => isSocketFilenameForProjectAndIde(path3.basename(socketPath), projectId, ide)
385
- );
1517
+ const normalizedProjectId = projectId.trim().toLowerCase();
1518
+ const filtered = [];
1519
+ for (const socketPath of socketPaths) {
1520
+ const basename4 = path7.basename(socketPath);
1521
+ if (isLegacySocketFilename(basename4)) {
1522
+ if (isSocketFilenameForProjectAndIde(basename4, normalizedProjectId, ide)) {
1523
+ filtered.push(socketPath);
1524
+ }
1525
+ continue;
1526
+ }
1527
+ if (isHashSocketFilename(basename4)) {
1528
+ const record = readBindingSidecarRecord(socketPath);
1529
+ if (record?.projectId.trim().toLowerCase() === normalizedProjectId && record.ideType === ide) {
1530
+ filtered.push(socketPath);
1531
+ }
1532
+ }
1533
+ }
1534
+ return filtered;
386
1535
  }
387
1536
  async function defaultKillProcess(pid) {
388
1537
  process.kill(pid, "SIGTERM");
389
1538
  }
390
1539
  async function defaultRemoveSocket(socketPath) {
391
- await fs3.unlink(socketPath);
1540
+ await fs5.unlink(socketPath);
1541
+ try {
1542
+ await fs5.unlink(bindingSidecarPath(socketPath));
1543
+ } catch {
1544
+ }
392
1545
  }
393
1546
  async function defaultConfirm(message) {
394
1547
  if (!import_node_process.stdin.isTTY) {
395
1548
  return false;
396
1549
  }
397
- const rl = readline.createInterface({ input: import_node_process.stdin, output: import_node_process.stdout });
1550
+ const rl = readline3.createInterface({ input: import_node_process.stdin, output: import_node_process.stdout });
398
1551
  try {
399
1552
  const answer = await rl.question(`${message} [y/N] `);
400
1553
  return /^y(es)?$/i.test(answer.trim());
@@ -404,7 +1557,7 @@ async function defaultConfirm(message) {
404
1557
  }
405
1558
  async function resolveCleanupTarget(cwd) {
406
1559
  try {
407
- const binding = await resolveAuthoritativeBinding([path3.resolve(cwd)]);
1560
+ const binding = await resolveAuthoritativeBinding([path7.resolve(cwd)]);
408
1561
  return {
409
1562
  workspaceRoot: binding.workspaceRoot,
410
1563
  m1Path: binding.m1Path,
@@ -433,7 +1586,8 @@ function filterProcessesForScope(processes, projectId) {
433
1586
  function logPrefix(dryRun) {
434
1587
  return dryRun ? "[cleanup][dry-run]" : "[cleanup]";
435
1588
  }
436
- function logReconnectNotice(prefix, ide) {
1589
+ function logReconnectNotice(opts, prefix, ide) {
1590
+ if (opts.quiet) return;
437
1591
  if (ide) {
438
1592
  console.log(
439
1593
  `${prefix} Note: Valid ${ide} connections for this project may disconnect temporarily; they should reconnect automatically. Stale connections will remain cleared.`
@@ -444,6 +1598,16 @@ function logReconnectNotice(prefix, ide) {
444
1598
  );
445
1599
  }
446
1600
  }
1601
+ function cleanupLog(opts, message) {
1602
+ if (!opts.quiet) {
1603
+ console.log(message);
1604
+ }
1605
+ }
1606
+ function cleanupWarn(opts, message) {
1607
+ if (!opts.quiet) {
1608
+ console.warn(message);
1609
+ }
1610
+ }
447
1611
  async function runCleanup(opts) {
448
1612
  const listProcesses = opts.listProcesses ?? defaultListDaemonProcesses;
449
1613
  const listSocketPaths = opts.listSocketPaths ?? defaultListSocketPaths;
@@ -464,8 +1628,9 @@ async function runCleanup(opts) {
464
1628
  error: "Cannot combine --all-projects with --project-id."
465
1629
  };
466
1630
  }
467
- console.log(`${prefix} Mode: all projects (--all-projects)`);
468
- console.warn(
1631
+ cleanupLog(opts, `${prefix} Mode: all projects (--all-projects)`);
1632
+ cleanupWarn(
1633
+ opts,
469
1634
  `${prefix} WARNING: This stops every MemoraOne MCP daemon and removes all project sockets under ${getMcpBaseDir()}.`
470
1635
  );
471
1636
  } else if (opts.projectId) {
@@ -474,9 +1639,9 @@ async function runCleanup(opts) {
474
1639
  return { exitCode: 1, killedPids: [], removedSockets: [], skippedProcesses: [], error: normalized.error };
475
1640
  }
476
1641
  targetProjectId = normalized;
477
- console.log(`${prefix} Project id: ${targetProjectId}`);
1642
+ cleanupLog(opts, `${prefix} Project id: ${targetProjectId}`);
478
1643
  if (opts.ide) {
479
- console.log(`${prefix} IDE filter: ${opts.ide}`);
1644
+ cleanupLog(opts, `${prefix} IDE filter: ${opts.ide}`);
480
1645
  }
481
1646
  } else {
482
1647
  const target = await resolveCleanupTarget(opts.cwd);
@@ -486,15 +1651,23 @@ async function runCleanup(opts) {
486
1651
  targetProjectId = target.projectId;
487
1652
  workspaceRoot = target.workspaceRoot;
488
1653
  m1Path = target.m1Path;
489
- console.log(`${prefix} Workspace root: ${workspaceRoot}`);
490
- console.log(`${prefix} memoraone.m1: ${m1Path}`);
491
- console.log(`${prefix} Project id: ${targetProjectId}`);
1654
+ cleanupLog(opts, `${prefix} Workspace root: ${workspaceRoot}`);
1655
+ cleanupLog(opts, `${prefix} memoraone.m1: ${m1Path}`);
1656
+ cleanupLog(opts, `${prefix} Project id: ${targetProjectId}`);
492
1657
  if (opts.ide) {
493
- console.log(`${prefix} IDE filter: ${opts.ide}`);
1658
+ cleanupLog(opts, `${prefix} IDE filter: ${opts.ide}`);
1659
+ }
1660
+ if (workspaceRoot && (!opts.ide || opts.ide === "cursor")) {
1661
+ try {
1662
+ const cursorAudit = await auditCursorMcpConfig({ repoRoot: workspaceRoot });
1663
+ logCursorMcpConfigAudit(prefix, cursorAudit);
1664
+ } catch (err) {
1665
+ cleanupWarn(opts, `${prefix} Cursor MCP config audit failed: ${String(err)}`);
1666
+ }
494
1667
  }
495
1668
  }
496
1669
  if (targetProjectId !== null || opts.ide) {
497
- logReconnectNotice(prefix, opts.ide);
1670
+ logReconnectNotice(opts, prefix, opts.ide);
498
1671
  }
499
1672
  const allDaemonProcesses = await listProcesses();
500
1673
  const { matching: projectProcesses, skipped: skippedProcesses } = filterProcessesForScope(
@@ -510,59 +1683,71 @@ async function runCleanup(opts) {
510
1683
  processesToStop.push(proc);
511
1684
  } else if (proc.ide === void 0) {
512
1685
  ideSkippedProcesses.push(proc);
513
- console.log(
1686
+ cleanupLog(
1687
+ opts,
514
1688
  `${prefix} Skipped daemon pid=${proc.pid} because IDE could not be safely determined.`
515
1689
  );
516
1690
  } else {
517
1691
  ideSkippedProcesses.push(proc);
518
- console.log(
1692
+ cleanupLog(
1693
+ opts,
519
1694
  `${prefix} Skipped daemon pid=${proc.pid} (IDE ${proc.ide} does not match filter ${opts.ide}).`
520
1695
  );
521
1696
  }
522
1697
  }
523
1698
  }
524
1699
  const allSocketPaths = await listSocketPaths(targetProjectId);
525
- const socketPaths = targetProjectId === null ? allSocketPaths : filterSocketPathsByIde(allSocketPaths, targetProjectId, opts.ide);
1700
+ const socketPaths = targetProjectId === null ? allSocketPaths : await filterSocketPathsByIde(allSocketPaths, targetProjectId, opts.ide);
526
1701
  if (opts.allProjects) {
527
1702
  const projectIds = /* @__PURE__ */ new Set();
528
1703
  for (const proc of processesToStop) {
529
1704
  projectIds.add(proc.projectId);
530
1705
  }
531
1706
  for (const socketPath of socketPaths) {
532
- const id = extractProjectIdFromSocketFilename(path3.basename(socketPath));
533
- if (id) projectIds.add(id);
1707
+ const id = extractProjectIdFromSocketFilename(path7.basename(socketPath));
1708
+ if (id) {
1709
+ projectIds.add(id);
1710
+ continue;
1711
+ }
1712
+ const record = readBindingSidecarRecord(socketPath);
1713
+ if (record) {
1714
+ projectIds.add(record.projectId.trim().toLowerCase());
1715
+ }
534
1716
  }
535
- console.log(`${prefix} Projects affected: ${projectIds.size ? [...projectIds].sort().join(", ") : "(none found)"}`);
1717
+ cleanupLog(
1718
+ opts,
1719
+ `${prefix} Projects affected: ${projectIds.size ? [...projectIds].sort().join(", ") : "(none found)"}`
1720
+ );
536
1721
  }
537
1722
  if (processesToStop.length) {
538
- console.log(`${prefix} Daemon processes to stop:`);
1723
+ cleanupLog(opts, `${prefix} Daemon processes to stop:`);
539
1724
  for (const proc of processesToStop) {
540
1725
  const ideLabel = proc.ide ? ` ide=${proc.ide}` : "";
541
- console.log(`${prefix} pid=${proc.pid} project=${proc.projectId}${ideLabel}`);
1726
+ cleanupLog(opts, `${prefix} pid=${proc.pid} project=${proc.projectId}${ideLabel}`);
542
1727
  }
543
1728
  } else if (ideSkippedProcesses.length) {
544
- console.log(`${prefix} No matching daemon processes for IDE filter ${opts.ide}.`);
1729
+ cleanupLog(opts, `${prefix} No matching daemon processes for IDE filter ${opts.ide}.`);
545
1730
  } else {
546
- console.log(`${prefix} No matching daemon processes found.`);
1731
+ cleanupLog(opts, `${prefix} No matching daemon processes found.`);
547
1732
  }
548
1733
  if (socketPaths.length) {
549
- console.log(`${prefix} Sockets to remove:`);
1734
+ cleanupLog(opts, `${prefix} Sockets to remove:`);
550
1735
  for (const socketPath of socketPaths) {
551
- console.log(`${prefix} ${socketPath}`);
1736
+ cleanupLog(opts, `${prefix} ${socketPath}`);
552
1737
  }
553
1738
  } else {
554
- console.log(`${prefix} No matching sockets found under ${getMcpBaseDir()}.`);
1739
+ cleanupLog(opts, `${prefix} No matching sockets found under ${getMcpBaseDir()}.`);
555
1740
  }
556
1741
  if (skippedProcesses.length) {
557
- console.log(`${prefix} Skipped unrelated daemon processes:`);
1742
+ cleanupLog(opts, `${prefix} Skipped unrelated daemon processes:`);
558
1743
  for (const proc of skippedProcesses) {
559
- console.log(`${prefix} pid=${proc.pid} project=${proc.projectId}`);
1744
+ cleanupLog(opts, `${prefix} pid=${proc.pid} project=${proc.projectId}`);
560
1745
  }
561
1746
  }
562
1747
  if (opts.allProjects && !opts.dryRun) {
563
1748
  const ok = opts.assumeYes ? true : await confirm(`${prefix} Proceed with cleanup for ALL projects?`);
564
1749
  if (!ok) {
565
- console.log(`${prefix} Aborted.`);
1750
+ cleanupLog(opts, `${prefix} Aborted.`);
566
1751
  return {
567
1752
  exitCode: 1,
568
1753
  workspaceRoot,
@@ -578,7 +1763,7 @@ async function runCleanup(opts) {
578
1763
  const killedPids = [];
579
1764
  const removedSockets = [];
580
1765
  if (opts.dryRun) {
581
- console.log(`${prefix} Dry run complete \u2014 no processes stopped, no sockets removed.`);
1766
+ cleanupLog(opts, `${prefix} Dry run complete \u2014 no processes stopped, no sockets removed.`);
582
1767
  return {
583
1768
  exitCode: 0,
584
1769
  workspaceRoot,
@@ -593,9 +1778,9 @@ async function runCleanup(opts) {
593
1778
  try {
594
1779
  await killProcess(proc.pid);
595
1780
  killedPids.push(proc.pid);
596
- console.log(`${prefix} Stopped daemon pid=${proc.pid} project=${proc.projectId}`);
1781
+ cleanupLog(opts, `${prefix} Stopped daemon pid=${proc.pid} project=${proc.projectId}`);
597
1782
  } catch (err) {
598
- console.warn(`${prefix} Could not stop pid=${proc.pid}: ${String(err)}`);
1783
+ cleanupWarn(opts, `${prefix} Could not stop pid=${proc.pid}: ${String(err)}`);
599
1784
  }
600
1785
  }
601
1786
  if (killedPids.length) {
@@ -605,15 +1790,15 @@ async function runCleanup(opts) {
605
1790
  try {
606
1791
  await removeSocket(socketPath);
607
1792
  removedSockets.push(socketPath);
608
- console.log(`${prefix} Removed socket ${socketPath}`);
1793
+ cleanupLog(opts, `${prefix} Removed socket ${socketPath}`);
609
1794
  } catch (err) {
610
1795
  const code = err && typeof err === "object" && "code" in err ? err.code : void 0;
611
1796
  if (code !== "ENOENT") {
612
- console.warn(`${prefix} Could not remove socket ${socketPath}: ${String(err)}`);
1797
+ cleanupWarn(opts, `${prefix} Could not remove socket ${socketPath}: ${String(err)}`);
613
1798
  }
614
1799
  }
615
1800
  }
616
- console.log(`${prefix} Done. stopped=${killedPids.length} socketsRemoved=${removedSockets.length}`);
1801
+ cleanupLog(opts, `${prefix} Done. stopped=${killedPids.length} socketsRemoved=${removedSockets.length}`);
617
1802
  return {
618
1803
  exitCode: 0,
619
1804
  workspaceRoot,
@@ -685,209 +1870,432 @@ async function cliCleanup(argv) {
685
1870
  return result.exitCode;
686
1871
  }
687
1872
 
688
- // src/cursorGlobalMcpConfig.ts
689
- var fs4 = __toESM(require("fs/promises"), 1);
690
- var os2 = __toESM(require("os"), 1);
691
- var path4 = __toESM(require("path"), 1);
692
- var import_node_child_process2 = require("child_process");
693
- var import_node_util2 = require("util");
694
- var execFileAsync2 = (0, import_node_util2.promisify)(import_node_child_process2.execFile);
695
- function buildMemoraoneCursorMcpServer(npxPath) {
696
- return {
697
- command: npxPath,
698
- args: ["-y", "@memoraone/mcp@latest"],
699
- env: {
700
- MEMORAONE_API_URL: "https://api.memoraone.com",
701
- MEMORAONE_IDE_TYPE: "cursor"
702
- }
703
- };
704
- }
705
- async function pathExists(filePath) {
706
- try {
707
- await fs4.access(filePath);
708
- return true;
709
- } catch {
710
- return false;
711
- }
712
- }
713
- function stripLeadingLineComments(text) {
714
- return text.split("\n").filter((line) => !/^\s*\/\//.test(line)).join("\n");
715
- }
716
- function getKnownCursorGlobalMcpConfigCandidates(homeDir) {
717
- return [path4.join(homeDir, ".cursor", "mcp.json")];
718
- }
719
- async function detectCursorGlobalMcpConfig(options) {
720
- if (options?.explicitPath) {
721
- return { ok: true, path: options.explicitPath, detectedExisting: await pathExists(options.explicitPath) };
722
- }
723
- const homeDir = options?.homeDir ?? os2.homedir();
724
- const candidates = getKnownCursorGlobalMcpConfigCandidates(homeDir);
725
- const existing = [];
726
- for (const candidate of candidates) {
727
- if (await pathExists(candidate)) existing.push(candidate);
728
- }
729
- if (existing.length > 1) {
730
- return {
731
- ok: false,
732
- error: "[setup-ide-files] Multiple Cursor global MCP config paths found. Specify one explicitly.",
733
- candidates: existing
734
- };
735
- }
736
- if (existing.length === 1) {
737
- return { ok: true, path: existing[0], detectedExisting: true };
738
- }
739
- const defaultPath = candidates[0];
740
- if (!defaultPath) {
741
- return {
742
- ok: false,
743
- error: "[setup-ide-files] No known Cursor global MCP config path.",
744
- candidates: []
745
- };
746
- }
747
- return { ok: true, path: defaultPath, detectedExisting: false };
748
- }
749
- function formatBackupTimestamp(d = /* @__PURE__ */ new Date()) {
750
- const pad = (n) => String(n).padStart(2, "0");
751
- return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
1873
+ // src/jetbrainsMcpConfig.ts
1874
+ var fs6 = __toESM(require("fs/promises"), 1);
1875
+ var os3 = __toESM(require("os"), 1);
1876
+ var path8 = __toESM(require("path"), 1);
1877
+ var import_node_child_process4 = require("child_process");
1878
+
1879
+ // src/configUtils.ts
1880
+ var DEV_API_URL = "http://localhost:3001";
1881
+
1882
+ // src/jetbrainsMcpConfig.ts
1883
+ var PROD_API_URL = "https://api.memoraone.com";
1884
+ var JETBRAINS_DEBUG_ENV_VARS = [
1885
+ "MEMORAONE_DEBUG_INIT",
1886
+ "MEMORAONE_DEBUG_MINIMAL_TOOLS",
1887
+ "MEMORAONE_DEBUG_MINIMAL_INITIALIZE",
1888
+ "MEMORAONE_DEBUG_DIRECT_STDIO"
1889
+ ];
1890
+ function stripLeadingLineComments2(text) {
1891
+ return text.split("\n").filter((line) => !/^\s*\/\//.test(line)).join("\n");
752
1892
  }
753
- async function isWorkingNpx(npxPath) {
1893
+ async function pathExists2(filePath) {
754
1894
  try {
755
- if (!await pathExists(npxPath)) return false;
756
- if (process.platform !== "win32") {
757
- try {
758
- await fs4.access(npxPath, fs4.constants.X_OK);
759
- } catch {
760
- return false;
761
- }
762
- }
763
- await execFileAsync2(npxPath, ["--version"], { timeout: 1e4 });
1895
+ await fs6.access(filePath);
764
1896
  return true;
765
1897
  } catch {
766
1898
  return false;
767
1899
  }
768
1900
  }
769
- async function resolveNpxPath() {
770
- const npxName = process.platform === "win32" ? "npx.cmd" : "npx";
771
- const candidates = [];
772
- if (process.platform === "darwin") {
773
- candidates.push("/opt/homebrew/bin/npx", "/usr/local/bin/npx");
774
- } else if (process.platform === "linux") {
775
- candidates.push("/usr/local/bin/npx");
776
- }
777
- const pathSep = process.platform === "win32" ? ";" : ":";
778
- for (const dir of (process.env.PATH ?? "").split(pathSep)) {
779
- if (!dir) continue;
780
- candidates.push(path4.join(dir, npxName));
781
- }
782
- try {
783
- const lookupCmd = process.platform === "win32" ? "where" : "which";
784
- const { stdout } = await execFileAsync2(lookupCmd, [npxName], { timeout: 5e3 });
785
- const first = stdout.trim().split(/\r?\n/).map((line) => line.trim()).find(Boolean);
786
- if (first) candidates.unshift(first);
787
- } catch {
788
- }
789
- const seen = /* @__PURE__ */ new Set();
790
- for (const candidate of candidates) {
791
- const abs = path4.isAbsolute(candidate) ? candidate : path4.resolve(candidate);
792
- const key = process.platform === "win32" ? abs.toLowerCase() : abs;
793
- if (seen.has(key)) continue;
794
- seen.add(key);
795
- if (await isWorkingNpx(abs)) return abs;
1901
+ function formatJetBrainsBackupTimestamp(d = /* @__PURE__ */ new Date()) {
1902
+ const pad = (n) => String(n).padStart(2, "0");
1903
+ return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
1904
+ }
1905
+ function getJetBrainsGlobalMcpConfigPath(homeDir) {
1906
+ return path8.join(homeDir, ".ai", "mcp", "mcp.json");
1907
+ }
1908
+ function getJetBrainsProjectMcpConfigPaths(repoRoot) {
1909
+ return [
1910
+ { kind: "project-ai", path: path8.join(repoRoot, ".ai", "mcp", "mcp.json") },
1911
+ { kind: "project-ij", path: path8.join(repoRoot, ".ij", "mcp", "mcp.json") }
1912
+ ];
1913
+ }
1914
+ function getKnownJetBrainsMcpConfigLocations(homeDir, repoRoot) {
1915
+ return [
1916
+ { kind: "global", path: getJetBrainsGlobalMcpConfigPath(homeDir) },
1917
+ ...getJetBrainsProjectMcpConfigPaths(repoRoot)
1918
+ ];
1919
+ }
1920
+ async function isZeroByteConfigFile(filePath) {
1921
+ if (!await pathExists2(filePath)) return false;
1922
+ const stat2 = await fs6.stat(filePath);
1923
+ return stat2.size === 0;
1924
+ }
1925
+ function buildMemoraoneJetBrainsMcpServer(options) {
1926
+ const env = {
1927
+ MEMORAONE_API_URL: options.devMode ? DEV_API_URL : PROD_API_URL,
1928
+ MEMORAONE_IDE_TYPE: "jetbrains",
1929
+ MEMORAONE_M1_PATH: options.m1Path
1930
+ };
1931
+ if (options.devMode) {
1932
+ env.MEMORAONE_DEV_MODE = "1";
796
1933
  }
797
- return null;
1934
+ return {
1935
+ command: options.command,
1936
+ args: options.args,
1937
+ env
1938
+ };
798
1939
  }
799
- function mergeCursorGlobalMcpConfigObject(existing, npxPath) {
1940
+ function mergeJetBrainsMcpConfigObject(existing, memoraone) {
800
1941
  const base = existing && typeof existing === "object" ? { ...existing } : { mcpServers: {} };
801
1942
  const mcpServers = typeof base.mcpServers === "object" && base.mcpServers !== null && !Array.isArray(base.mcpServers) ? { ...base.mcpServers } : {};
802
- mcpServers.memoraone = buildMemoraoneCursorMcpServer(npxPath);
1943
+ mcpServers.memoraone = memoraone;
803
1944
  return { ...base, mcpServers };
804
1945
  }
805
- function memoraoneServerMatches(server, npxPath) {
1946
+ function memoraoneServerMatches(server, expected) {
806
1947
  if (!server || typeof server !== "object") return false;
807
1948
  const s = server;
808
- if (s.command !== npxPath) return false;
809
- if (!Array.isArray(s.args) || s.args.length !== 2) return false;
810
- if (s.args[0] !== "-y" || s.args[1] !== "@memoraone/mcp@latest") return false;
1949
+ if (s.command !== expected.command) return false;
1950
+ if (!Array.isArray(s.args) || s.args.length !== expected.args.length) return false;
1951
+ for (let i = 0; i < expected.args.length; i += 1) {
1952
+ if (s.args[i] !== expected.args[i]) return false;
1953
+ }
811
1954
  const env = s.env;
812
1955
  if (!env || typeof env !== "object") return false;
813
1956
  const e = env;
814
- return e.MEMORAONE_API_URL === "https://api.memoraone.com" && e.MEMORAONE_IDE_TYPE === "cursor";
1957
+ for (const [key, value] of Object.entries(expected.env)) {
1958
+ if (e[key] !== value) return false;
1959
+ }
1960
+ for (const debugKey of JETBRAINS_DEBUG_ENV_VARS) {
1961
+ if (debugKey in e) return false;
1962
+ }
1963
+ return true;
815
1964
  }
816
- function validateCursorGlobalMcpConfig(parsed, npxPath) {
1965
+ function validateJetBrainsMcpConfig(parsed, expected) {
817
1966
  if (!parsed || typeof parsed !== "object") {
818
- throw new Error("[setup-ide-files] Cursor global MCP config must be a JSON object.");
1967
+ throw new Error("[setup-ide-files] JetBrains MCP config must be a JSON object.");
819
1968
  }
820
1969
  const mcpServers = parsed.mcpServers;
821
1970
  if (!mcpServers || typeof mcpServers !== "object" || Array.isArray(mcpServers)) {
822
- throw new Error("[setup-ide-files] Cursor global MCP config missing mcpServers object.");
1971
+ throw new Error("[setup-ide-files] JetBrains MCP config missing mcpServers object.");
823
1972
  }
824
1973
  const memoraone = mcpServers.memoraone;
825
- if (!memoraoneServerMatches(memoraone, npxPath)) {
1974
+ if (!memoraoneServerMatches(memoraone, expected)) {
1975
+ throw new Error(
1976
+ "[setup-ide-files] JetBrains MCP config mcpServers.memoraone is missing or invalid."
1977
+ );
1978
+ }
1979
+ }
1980
+ async function readJsonConfig(filePath) {
1981
+ const raw = await fs6.readFile(filePath, "utf8");
1982
+ if (raw.trim() === "") return null;
1983
+ return JSON.parse(stripLeadingLineComments2(raw));
1984
+ }
1985
+ async function backupConfigFile(filePath) {
1986
+ const backupPath = `${filePath}.bak-${formatJetBrainsBackupTimestamp()}`;
1987
+ await fs6.copyFile(filePath, backupPath);
1988
+ return backupPath;
1989
+ }
1990
+ async function repairZeroByteConfigFile(filePath, dryRun) {
1991
+ if (!await isZeroByteConfigFile(filePath)) {
1992
+ return { repaired: false };
1993
+ }
1994
+ if (dryRun) {
1995
+ return { repaired: true, backupPath: `${filePath}.bak-<timestamp>` };
1996
+ }
1997
+ const backupPath = await backupConfigFile(filePath);
1998
+ await fs6.unlink(filePath);
1999
+ return { repaired: true, backupPath };
2000
+ }
2001
+ function configHasMemoraone(parsed) {
2002
+ if (!parsed) return false;
2003
+ const mcpServers = parsed.mcpServers;
2004
+ if (!mcpServers || typeof mcpServers !== "object" || Array.isArray(mcpServers)) return false;
2005
+ return Boolean(mcpServers.memoraone);
2006
+ }
2007
+ async function removeMemoraoneFromProjectConfig(options) {
2008
+ const { configPath, dryRun } = options;
2009
+ if (!await pathExists2(configPath)) {
2010
+ return { changed: false };
2011
+ }
2012
+ let parsed = null;
2013
+ try {
2014
+ parsed = await readJsonConfig(configPath);
2015
+ } catch {
2016
+ return { changed: false };
2017
+ }
2018
+ if (!configHasMemoraone(parsed)) {
2019
+ return { changed: false };
2020
+ }
2021
+ if (dryRun) {
2022
+ return { changed: true, backupPath: `${configPath}.bak-<timestamp>` };
2023
+ }
2024
+ const backupPath = await backupConfigFile(configPath);
2025
+ const mcpServers = parsed && typeof parsed.mcpServers === "object" && parsed.mcpServers !== null && !Array.isArray(parsed.mcpServers) ? { ...parsed.mcpServers } : {};
2026
+ delete mcpServers.memoraone;
2027
+ const hasOtherServers = Object.keys(mcpServers).length > 0;
2028
+ if (!hasOtherServers) {
2029
+ await fs6.unlink(configPath);
2030
+ return { changed: true, backupPath };
2031
+ }
2032
+ const next = { ...parsed, mcpServers };
2033
+ await fs6.mkdir(path8.dirname(configPath), { recursive: true });
2034
+ await fs6.writeFile(configPath, JSON.stringify(next, null, 2) + "\n", "utf8");
2035
+ return { changed: true, backupPath };
2036
+ }
2037
+ async function resolveLocalCliPathAsync() {
2038
+ const here = process.argv[1] ? path8.dirname(path8.resolve(process.argv[1])) : process.cwd();
2039
+ const candidates = [
2040
+ path8.join(here, "cli.cjs"),
2041
+ path8.join(here, "..", "dist", "cli.cjs"),
2042
+ path8.join(here, "..", "..", "dist", "cli.cjs")
2043
+ ];
2044
+ for (const candidate of candidates) {
2045
+ if (await pathExists2(candidate)) {
2046
+ return path8.resolve(candidate);
2047
+ }
2048
+ }
2049
+ return null;
2050
+ }
2051
+ async function buildJetBrainsMemoraoneServer(options) {
2052
+ if (options.devMode) {
2053
+ let cliPath = options.cliPathOverride;
2054
+ if (cliPath === void 0) {
2055
+ cliPath = await resolveLocalCliPathAsync();
2056
+ }
2057
+ if (!cliPath) {
2058
+ throw new Error(
2059
+ "[setup-ide-files] Dev mode requires a built CLI at packages/mcp/dist/cli.cjs. Run pnpm build first."
2060
+ );
2061
+ }
2062
+ return buildMemoraoneJetBrainsMcpServer({
2063
+ command: process.execPath,
2064
+ args: [cliPath],
2065
+ m1Path: options.m1Path,
2066
+ devMode: true
2067
+ });
2068
+ }
2069
+ let npxPath = options.npxPathOverride;
2070
+ if (npxPath === void 0) {
2071
+ npxPath = await resolveNpxPath();
2072
+ }
2073
+ if (!npxPath) {
826
2074
  throw new Error(
827
- "[setup-ide-files] Cursor global MCP config mcpServers.memoraone is missing or invalid."
2075
+ "[setup-ide-files] Could not resolve a working npx executable. Install Node.js/npm or ensure npx is on PATH before configuring JetBrains MCP."
828
2076
  );
829
2077
  }
2078
+ return buildMemoraoneJetBrainsMcpServer({
2079
+ command: npxPath,
2080
+ args: ["-y", "@memoraone/mcp@latest"],
2081
+ m1Path: options.m1Path,
2082
+ devMode: false
2083
+ });
2084
+ }
2085
+ async function verifyJetBrainsMcpHandshake(options) {
2086
+ const timeoutMs = options.timeoutMs ?? 15e3;
2087
+ const { server } = options;
2088
+ return new Promise((resolve8) => {
2089
+ let settled = false;
2090
+ const finish = (ok, detail) => {
2091
+ if (settled) return;
2092
+ settled = true;
2093
+ clearTimeout(timer);
2094
+ try {
2095
+ child.kill();
2096
+ } catch {
2097
+ }
2098
+ resolve8({ ok, detail });
2099
+ };
2100
+ const child = (0, import_node_child_process4.spawn)(server.command, [...server.args], {
2101
+ env: { ...process.env, ...server.env },
2102
+ stdio: ["pipe", "pipe", "pipe"]
2103
+ });
2104
+ let buffer = "";
2105
+ let initializeOk = false;
2106
+ let toolsListOk = false;
2107
+ let nextId = 1;
2108
+ const send = (method, params) => {
2109
+ const msg = JSON.stringify({ jsonrpc: "2.0", id: nextId, method, params }) + "\n";
2110
+ nextId += 1;
2111
+ child.stdin?.write(msg);
2112
+ };
2113
+ const timer = setTimeout(() => {
2114
+ finish(false, `handshake timed out after ${timeoutMs}ms`);
2115
+ }, timeoutMs);
2116
+ child.stdout?.on("data", (chunk) => {
2117
+ buffer += chunk.toString("utf8");
2118
+ const lines = buffer.split("\n");
2119
+ buffer = lines.pop() ?? "";
2120
+ for (const line of lines) {
2121
+ if (!line.trim()) continue;
2122
+ let msg;
2123
+ try {
2124
+ msg = JSON.parse(line);
2125
+ } catch {
2126
+ continue;
2127
+ }
2128
+ if (msg.id === 1 && msg.result) {
2129
+ initializeOk = true;
2130
+ send("notifications/initialized", {});
2131
+ send("tools/list", {});
2132
+ }
2133
+ if (msg.id === 2 && msg.result) {
2134
+ toolsListOk = true;
2135
+ finish(true, "initialize OK; tools/list OK");
2136
+ }
2137
+ if (msg.error) {
2138
+ finish(false, `JSON-RPC error: ${JSON.stringify(msg.error)}`);
2139
+ }
2140
+ }
2141
+ });
2142
+ child.on("error", (err) => {
2143
+ finish(false, `spawn error: ${String(err)}`);
2144
+ });
2145
+ child.on("exit", (code) => {
2146
+ if (!settled) {
2147
+ if (initializeOk && toolsListOk) {
2148
+ finish(true, "initialize OK; tools/list OK");
2149
+ } else {
2150
+ finish(
2151
+ false,
2152
+ `process exited code=${code ?? "null"} (initialize=${initializeOk}, tools/list=${toolsListOk})`
2153
+ );
2154
+ }
2155
+ }
2156
+ });
2157
+ send("initialize", {
2158
+ protocolVersion: "2024-11-05",
2159
+ capabilities: {},
2160
+ clientInfo: { name: "memoraone-setup", version: "1.0.0" }
2161
+ });
2162
+ });
830
2163
  }
831
- async function setupCursorGlobalMcpConfig(options) {
832
- const { configPath, npxPath, dryRun } = options;
833
- const existed = await pathExists(configPath);
2164
+ async function setupJetBrainsMcpConfig(options) {
2165
+ const homeDir = options.homeDir ?? os3.homedir();
2166
+ const globalPath = options.globalConfigPath ?? getJetBrainsGlobalMcpConfigPath(homeDir);
2167
+ const m1Path = path8.join(path8.resolve(options.repoRoot), "memoraone.m1");
2168
+ const repairActions = [];
2169
+ const allLocations = getKnownJetBrainsMcpConfigLocations(homeDir, options.repoRoot);
2170
+ for (const location of allLocations) {
2171
+ if (await pathExists2(location.path)) {
2172
+ repairActions.push({ type: "found-config", location });
2173
+ }
2174
+ }
2175
+ for (const location of allLocations) {
2176
+ const zeroByte = await repairZeroByteConfigFile(location.path, options.dryRun);
2177
+ if (zeroByte.repaired) {
2178
+ repairActions.push({
2179
+ type: "repaired-zero-byte",
2180
+ path: location.path,
2181
+ backupPath: zeroByte.backupPath ?? `${location.path}.bak-<timestamp>`
2182
+ });
2183
+ }
2184
+ }
2185
+ const memoraone = await buildJetBrainsMemoraoneServer({
2186
+ m1Path,
2187
+ devMode: options.devMode,
2188
+ npxPathOverride: options.npxPathOverride,
2189
+ cliPathOverride: options.cliPathOverride
2190
+ });
2191
+ for (const location of getJetBrainsProjectMcpConfigPaths(options.repoRoot)) {
2192
+ const removal = await removeMemoraoneFromProjectConfig({
2193
+ configPath: location.path,
2194
+ dryRun: options.dryRun
2195
+ });
2196
+ if (removal.changed) {
2197
+ if (removal.backupPath) {
2198
+ repairActions.push({
2199
+ type: "backed-up-conflicting-project-config",
2200
+ path: location.path,
2201
+ backupPath: removal.backupPath
2202
+ });
2203
+ }
2204
+ repairActions.push({ type: "removed-project-memoraone", path: location.path });
2205
+ }
2206
+ }
2207
+ const existed = await pathExists2(globalPath);
834
2208
  let existing = null;
835
2209
  if (existed) {
836
- const raw = await fs4.readFile(configPath, "utf8");
837
2210
  try {
838
- existing = JSON.parse(stripLeadingLineComments(raw));
2211
+ existing = await readJsonConfig(globalPath);
839
2212
  } catch {
840
- throw new Error(
841
- `[setup-ide-files] Invalid JSON in Cursor global MCP config: ${configPath}`
842
- );
2213
+ if (options.dryRun) {
2214
+ existing = null;
2215
+ } else {
2216
+ const backupPath2 = await backupConfigFile(globalPath);
2217
+ repairActions.push({
2218
+ type: "repaired-zero-byte",
2219
+ path: globalPath,
2220
+ backupPath: backupPath2
2221
+ });
2222
+ await fs6.unlink(globalPath);
2223
+ existing = null;
2224
+ }
843
2225
  }
844
2226
  }
845
- const merged = mergeCursorGlobalMcpConfigObject(existing, npxPath);
2227
+ const merged = mergeJetBrainsMcpConfigObject(existing, memoraone);
846
2228
  const body = JSON.stringify(merged, null, 2) + "\n";
847
- if (existed) {
848
- const currentMemoraone = existing && typeof existing.mcpServers === "object" && existing.mcpServers !== null && !Array.isArray(existing.mcpServers) ? existing.mcpServers.memoraone : void 0;
849
- if (memoraoneServerMatches(currentMemoraone, npxPath)) {
850
- return { outcome: "skipped" };
2229
+ if (existed && existing) {
2230
+ const currentMemoraone = existing.mcpServers && typeof existing.mcpServers === "object" && !Array.isArray(existing.mcpServers) ? existing.mcpServers.memoraone : void 0;
2231
+ if (memoraoneServerMatches(currentMemoraone, memoraone)) {
2232
+ repairActions.push({ type: "wrote-global-config", path: globalPath, outcome: "skipped" });
2233
+ return { outcome: "skipped", repairActions, memoraone };
851
2234
  }
852
2235
  }
853
- if (dryRun) {
854
- return { outcome: existed ? "updated" : "created" };
2236
+ if (options.dryRun) {
2237
+ const outcome2 = existed ? "updated" : "created";
2238
+ repairActions.push({ type: "wrote-global-config", path: globalPath, outcome: outcome2 });
2239
+ return { outcome: outcome2, repairActions, memoraone };
855
2240
  }
856
2241
  let backupPath;
857
2242
  if (existed) {
858
- backupPath = `${configPath}.backup-${formatBackupTimestamp()}`;
859
- await fs4.copyFile(configPath, backupPath);
860
- }
861
- await fs4.mkdir(path4.dirname(configPath), { recursive: true });
862
- await fs4.writeFile(configPath, body, "utf8");
863
- const verifyRaw = await fs4.readFile(configPath, "utf8");
864
- const verifyParsed = JSON.parse(stripLeadingLineComments(verifyRaw));
865
- validateCursorGlobalMcpConfig(verifyParsed, npxPath);
866
- return { outcome: existed ? "updated" : "created", backupPath };
867
- }
868
- function logCursorGlobalMcpCliSummary(info, dryRun) {
869
- const { configPath, npxPath, backupPath, outcome } = info;
870
- console.log(`[setup-ide-files] Cursor global MCP config: ${configPath}`);
871
- console.log(`[setup-ide-files] Resolved npx: ${npxPath}`);
872
- if (backupPath) {
873
- console.log(`[setup-ide-files] Cursor global MCP config backup: ${backupPath}`);
2243
+ backupPath = await backupConfigFile(globalPath);
2244
+ }
2245
+ await fs6.mkdir(path8.dirname(globalPath), { recursive: true });
2246
+ await fs6.writeFile(globalPath, body, "utf8");
2247
+ const verifyRaw = await fs6.readFile(globalPath, "utf8");
2248
+ const verifyParsed = JSON.parse(stripLeadingLineComments2(verifyRaw));
2249
+ validateJetBrainsMcpConfig(verifyParsed, memoraone);
2250
+ const outcome = existed ? "updated" : "created";
2251
+ repairActions.push({ type: "wrote-global-config", path: globalPath, outcome });
2252
+ let verifyOk;
2253
+ let verifyDetail;
2254
+ if (options.verify !== false) {
2255
+ const verify = await verifyJetBrainsMcpHandshake({ server: memoraone });
2256
+ verifyOk = verify.ok;
2257
+ verifyDetail = verify.detail;
2258
+ repairActions.push({ type: "verify-handshake", ok: verify.ok, detail: verify.detail });
2259
+ }
2260
+ return { outcome, backupPath, repairActions, verifyOk, verifyDetail, memoraone };
2261
+ }
2262
+ function logJetBrainsMcpCliSummary(info, dryRun) {
2263
+ for (const action of info.repairActions) {
2264
+ if (action.type === "found-config") {
2265
+ console.log(`[setup-ide-files] Found JetBrains MCP config (${action.location.kind}): ${action.location.path}`);
2266
+ } else if (action.type === "repaired-zero-byte") {
2267
+ console.log(`[setup-ide-files] Repaired zero-byte MCP config: ${action.path}`);
2268
+ console.log(`[setup-ide-files] Backup: ${action.backupPath}`);
2269
+ } else if (action.type === "backed-up-conflicting-project-config") {
2270
+ console.log(`[setup-ide-files] Backed up conflicting project MCP config: ${action.path}`);
2271
+ console.log(`[setup-ide-files] Backup: ${action.backupPath}`);
2272
+ } else if (action.type === "removed-project-memoraone") {
2273
+ console.log(`[setup-ide-files] Removed project-scoped memoraone definition: ${action.path}`);
2274
+ } else if (action.type === "verify-handshake") {
2275
+ if (action.ok) {
2276
+ console.log(`[setup-ide-files] MCP handshake verification: ${action.detail}`);
2277
+ } else {
2278
+ console.log(`[setup-ide-files] MCP handshake verification skipped/failed: ${action.detail}`);
2279
+ }
2280
+ }
874
2281
  }
875
- if (outcome === "created") {
876
- console.log(
877
- dryRun ? `[setup-ide-files] Cursor global MCP config would be created: ${configPath}` : `[setup-ide-files] Cursor global MCP config created: ${configPath}`
878
- );
879
- } else if (outcome === "updated") {
880
- console.log(
881
- dryRun ? `[setup-ide-files] Cursor global MCP config would be updated: ${configPath}` : `[setup-ide-files] Cursor global MCP config updated: ${configPath}`
882
- );
883
- } else if (outcome === "skipped") {
884
- console.log(`[setup-ide-files] Cursor global MCP config unchanged: ${configPath}`);
2282
+ const prefix = dryRun ? "would be " : "";
2283
+ if (info.outcome === "created") {
2284
+ console.log(`[setup-ide-files] JetBrains global MCP config ${prefix}created: ${info.activeConfigPath}`);
2285
+ } else if (info.outcome === "updated") {
2286
+ console.log(`[setup-ide-files] JetBrains global MCP config ${prefix}updated: ${info.activeConfigPath}`);
2287
+ } else {
2288
+ console.log(`[setup-ide-files] JetBrains global MCP config unchanged: ${info.activeConfigPath}`);
885
2289
  }
2290
+ if (info.backupPath) {
2291
+ console.log(`[setup-ide-files] JetBrains global MCP config backup: ${info.backupPath}`);
2292
+ }
2293
+ if (info.npxPath) {
2294
+ console.log(`[setup-ide-files] Resolved npx: ${info.npxPath}`);
2295
+ }
2296
+ console.log(`[setup-ide-files] Final active JetBrains MCP config: ${info.activeConfigPath}`);
886
2297
  console.log(
887
- "[setup-ide-files] Repo-level .cursor/mcp.json was not created or modified."
888
- );
889
- console.log(
890
- "[setup-ide-files] Fully quit Cursor and reopen this repo for MCP changes to take effect."
2298
+ "[setup-ide-files] Fully quit JetBrains IDE and reopen this repo for MCP changes to take effect."
891
2299
  );
892
2300
  }
893
2301
 
@@ -906,15 +2314,15 @@ function buildMemoraoneMcpServer(ideType, command = "npx") {
906
2314
  };
907
2315
  }
908
2316
  function assertUnderRepoRoot(repoRoot, absPath) {
909
- const normRoot = path5.resolve(repoRoot) + path5.sep;
910
- const normPath = path5.resolve(absPath);
911
- if (normPath !== path5.resolve(repoRoot) && !normPath.startsWith(normRoot)) {
2317
+ const normRoot = path9.resolve(repoRoot) + path9.sep;
2318
+ const normPath = path9.resolve(absPath);
2319
+ if (normPath !== path9.resolve(repoRoot) && !normPath.startsWith(normRoot)) {
912
2320
  throw new Error(`[setup-ide-files] Refusing to write outside repo root: ${absPath}`);
913
2321
  }
914
2322
  }
915
- async function pathExists2(filePath) {
2323
+ async function pathExists3(filePath) {
916
2324
  try {
917
- await fs5.access(filePath);
2325
+ await fs7.access(filePath);
918
2326
  return true;
919
2327
  } catch {
920
2328
  return false;
@@ -935,12 +2343,12 @@ ${GITIGNORE_MEMORAONE_ENTRY}
935
2343
  }
936
2344
  async function ensureGitignoreMemoraone(repoRoot, opts) {
937
2345
  if (opts.noGitignore) return "skipped";
938
- const abs = path5.join(repoRoot, ".gitignore");
2346
+ const abs = path9.join(repoRoot, ".gitignore");
939
2347
  assertUnderRepoRoot(repoRoot, abs);
940
2348
  let prior = "";
941
2349
  let existed = false;
942
2350
  try {
943
- prior = await fs5.readFile(abs, "utf8");
2351
+ prior = await fs7.readFile(abs, "utf8");
944
2352
  existed = true;
945
2353
  } catch (err) {
946
2354
  const code = err && typeof err === "object" && "code" in err ? err.code : void 0;
@@ -951,25 +2359,25 @@ async function ensureGitignoreMemoraone(repoRoot, opts) {
951
2359
  const separator = existed && prior.length > 0 ? prior.endsWith("\n") ? "\n" : "\n\n" : "";
952
2360
  const next = (existed ? prior : "") + separator + block;
953
2361
  if (opts.dryRun) return existed ? "updated" : "created";
954
- await fs5.writeFile(abs, next, "utf8");
2362
+ await fs7.writeFile(abs, next, "utf8");
955
2363
  return existed ? "updated" : "created";
956
2364
  }
957
2365
  async function findRepoRoot(startDir) {
958
- let current = path5.resolve(startDir);
959
- const root = path5.parse(current).root;
2366
+ let current = path9.resolve(startDir);
2367
+ const root = path9.parse(current).root;
960
2368
  while (true) {
961
- const gitPath = path5.join(current, ".git");
962
- const m1Path = path5.join(current, "memoraone.m1");
963
- if (await pathExists2(gitPath) || await pathExists2(m1Path)) {
2369
+ const gitPath = path9.join(current, ".git");
2370
+ const m1Path = path9.join(current, "memoraone.m1");
2371
+ if (await pathExists3(gitPath) || await pathExists3(m1Path)) {
964
2372
  return current;
965
2373
  }
966
2374
  if (current === root) {
967
2375
  return null;
968
2376
  }
969
- current = path5.dirname(current);
2377
+ current = path9.dirname(current);
970
2378
  }
971
2379
  }
972
- function stripLeadingLineComments2(text) {
2380
+ function stripLeadingLineComments3(text) {
973
2381
  return text.split("\n").filter((line) => !/^\s*\/\//.test(line)).join("\n");
974
2382
  }
975
2383
  function cursorRuleBody() {
@@ -977,7 +2385,7 @@ function cursorRuleBody() {
977
2385
 
978
2386
  ## MemoraOne MCP (IDE agent only)
979
2387
 
980
- This repository uses **MemoraOne** via the MCP server named **user-memoraone** (Cursor MCP configuration). This guidance applies to the **IDE coding agent** only \u2014 not the MemoraOne Studio runtime path.
2388
+ This repository uses **MemoraOne** via the MCP server named **memoraone** (repo-level .cursor/mcp.json). This guidance applies to the **IDE coding agent** only \u2014 not the MemoraOne Studio runtime path.
981
2389
 
982
2390
  ### Tools
983
2391
 
@@ -1019,43 +2427,47 @@ function buildVscodeMcpJsonBody(existing) {
1019
2427
  const merged = { ...base, servers };
1020
2428
  return mcpJsonHeader() + JSON.stringify(merged, null, 2) + "\n";
1021
2429
  }
2430
+ function buildCursorMcpJsonBody(existing, npxPath, repoRoot) {
2431
+ const merged = mergeCursorRepoMcpConfigObject(existing, npxPath, repoRoot);
2432
+ return mcpJsonHeader() + JSON.stringify(merged, null, 2) + "\n";
2433
+ }
1022
2434
  async function writeManagedMarkdown(repoRoot, relPath, fullContent, opts) {
1023
- const abs = path5.join(repoRoot, relPath);
2435
+ const abs = path9.join(repoRoot, relPath);
1024
2436
  assertUnderRepoRoot(repoRoot, abs);
1025
2437
  let prior = "";
1026
2438
  let existed = false;
1027
2439
  try {
1028
- prior = await fs5.readFile(abs, "utf8");
2440
+ prior = await fs7.readFile(abs, "utf8");
1029
2441
  existed = true;
1030
2442
  } catch (err) {
1031
2443
  if (err?.code !== "ENOENT") throw err;
1032
2444
  }
1033
2445
  if (!existed) {
1034
2446
  if (opts.dryRun) return "created";
1035
- await fs5.mkdir(path5.dirname(abs), { recursive: true });
1036
- await fs5.writeFile(abs, fullContent, "utf8");
2447
+ await fs7.mkdir(path9.dirname(abs), { recursive: true });
2448
+ await fs7.writeFile(abs, fullContent, "utf8");
1037
2449
  return "created";
1038
2450
  }
1039
2451
  if (prior.includes(MANAGED_MARKER)) {
1040
2452
  if (prior === fullContent) return "skipped";
1041
2453
  if (opts.dryRun) return "updated";
1042
- await fs5.mkdir(path5.dirname(abs), { recursive: true });
1043
- await fs5.writeFile(abs, fullContent, "utf8");
2454
+ await fs7.mkdir(path9.dirname(abs), { recursive: true });
2455
+ await fs7.writeFile(abs, fullContent, "utf8");
1044
2456
  return "updated";
1045
2457
  }
1046
2458
  if (!opts.force) return "skipped-untracked";
1047
2459
  if (opts.dryRun) return "updated";
1048
- await fs5.mkdir(path5.dirname(abs), { recursive: true });
1049
- await fs5.writeFile(abs, fullContent, "utf8");
2460
+ await fs7.mkdir(path9.dirname(abs), { recursive: true });
2461
+ await fs7.writeFile(abs, fullContent, "utf8");
1050
2462
  return "updated";
1051
2463
  }
1052
2464
  async function writeIdeMcpJson(repoRoot, relPath, buildBody, opts) {
1053
- const abs = path5.join(repoRoot, relPath);
2465
+ const abs = path9.join(repoRoot, relPath);
1054
2466
  assertUnderRepoRoot(repoRoot, abs);
1055
2467
  let raw = "";
1056
2468
  let existed = false;
1057
2469
  try {
1058
- raw = await fs5.readFile(abs, "utf8");
2470
+ raw = await fs7.readFile(abs, "utf8");
1059
2471
  existed = true;
1060
2472
  } catch (err) {
1061
2473
  if (err?.code !== "ENOENT") throw err;
@@ -1063,15 +2475,15 @@ async function writeIdeMcpJson(repoRoot, relPath, buildBody, opts) {
1063
2475
  if (!existed) {
1064
2476
  const body = buildBody(null);
1065
2477
  if (opts.dryRun) return "created";
1066
- await fs5.mkdir(path5.dirname(abs), { recursive: true });
1067
- await fs5.writeFile(abs, body, "utf8");
2478
+ await fs7.mkdir(path9.dirname(abs), { recursive: true });
2479
+ await fs7.writeFile(abs, body, "utf8");
1068
2480
  return "created";
1069
2481
  }
1070
2482
  const managed = raw.includes(MANAGED_MARKER);
1071
2483
  if (!managed && !opts.force) return "skipped-untracked";
1072
2484
  let parsed = null;
1073
2485
  try {
1074
- parsed = JSON.parse(stripLeadingLineComments2(raw));
2486
+ parsed = JSON.parse(stripLeadingLineComments3(raw));
1075
2487
  } catch {
1076
2488
  parsed = null;
1077
2489
  }
@@ -1079,8 +2491,8 @@ async function writeIdeMcpJson(repoRoot, relPath, buildBody, opts) {
1079
2491
  const next = buildBody(parsed);
1080
2492
  if (managed && next === raw) return "skipped";
1081
2493
  if (opts.dryRun) return "updated";
1082
- await fs5.mkdir(path5.dirname(abs), { recursive: true });
1083
- await fs5.writeFile(abs, next, "utf8");
2494
+ await fs7.mkdir(path9.dirname(abs), { recursive: true });
2495
+ await fs7.writeFile(abs, next, "utf8");
1084
2496
  return "updated";
1085
2497
  }
1086
2498
  function parseSetupIdeFlags(argv) {
@@ -1092,6 +2504,8 @@ function parseSetupIdeFlags(argv) {
1092
2504
  let dryRun = false;
1093
2505
  let noGitignore = false;
1094
2506
  let cleanup = false;
2507
+ let devMode = false;
2508
+ let repair = false;
1095
2509
  const unknown = [];
1096
2510
  for (const a of argv) {
1097
2511
  if (a === "--cursor") cursor = true;
@@ -1102,6 +2516,8 @@ function parseSetupIdeFlags(argv) {
1102
2516
  else if (a === "--dry-run") dryRun = true;
1103
2517
  else if (a === "--no-gitignore") noGitignore = true;
1104
2518
  else if (a === "--cleanup") cleanup = true;
2519
+ else if (a === "--dev") devMode = true;
2520
+ else if (a === "--repair") repair = true;
1105
2521
  else if (a.startsWith("-")) unknown.push(a);
1106
2522
  }
1107
2523
  const specific = cursor || vscode || jetbrains;
@@ -1111,7 +2527,7 @@ function parseSetupIdeFlags(argv) {
1111
2527
  } else {
1112
2528
  targets = { cursor, vscode, jetbrains };
1113
2529
  }
1114
- return { targets, force, dryRun, noGitignore, cleanup, unknown };
2530
+ return { targets, force, dryRun, noGitignore, cleanup, devMode, repair, unknown };
1115
2531
  }
1116
2532
  function summarizeOutcomes(outcomes) {
1117
2533
  const created = [];
@@ -1133,9 +2549,145 @@ function summarizeOutcomes(outcomes) {
1133
2549
  }
1134
2550
  console.log(lines.join("\n"));
1135
2551
  }
2552
+ function ideTypesFromSetupTargets(targets) {
2553
+ const ides = [];
2554
+ if (targets.cursor) ides.push("cursor");
2555
+ if (targets.vscode) ides.push("copilot-vscode");
2556
+ if (targets.jetbrains) ides.push("jetbrains");
2557
+ return ides;
2558
+ }
2559
+ function setupTargetsAllIdes(targets) {
2560
+ return targets.cursor && targets.vscode && targets.jetbrains;
2561
+ }
2562
+ function aggregateCleanupResults(results) {
2563
+ const killedPids = /* @__PURE__ */ new Set();
2564
+ const removedSockets = /* @__PURE__ */ new Set();
2565
+ const skippedUnrelated = /* @__PURE__ */ new Map();
2566
+ let foundDaemonCount = 0;
2567
+ let error;
2568
+ for (const result of results) {
2569
+ if (result.error) error = result.error;
2570
+ for (const pid of result.killedPids) {
2571
+ killedPids.add(pid);
2572
+ foundDaemonCount += 1;
2573
+ }
2574
+ for (const socketPath of result.removedSockets) {
2575
+ removedSockets.add(socketPath);
2576
+ }
2577
+ for (const proc of result.skippedProcesses) {
2578
+ if (proc.projectId !== result.projectId) {
2579
+ skippedUnrelated.set(proc.pid, proc);
2580
+ }
2581
+ }
2582
+ }
2583
+ return {
2584
+ foundDaemonCount,
2585
+ stoppedDaemonCount: killedPids.size,
2586
+ removedSocketCount: removedSockets.size,
2587
+ skippedUnrelatedDaemonCount: skippedUnrelated.size,
2588
+ error
2589
+ };
2590
+ }
2591
+ function logSetupIdeCleanupSummary(cleanup) {
2592
+ if (cleanup.skipped) return;
2593
+ console.log(`[setup-ide-files] Project id: ${cleanup.projectId}`);
2594
+ if (cleanup.foundDaemonCount > 0) {
2595
+ console.log(`[setup-ide-files] Found ${cleanup.foundDaemonCount} stale daemon(s)`);
2596
+ if (cleanup.dryRun) {
2597
+ console.log(`[setup-ide-files] Would stop ${cleanup.foundDaemonCount} stale daemon(s)`);
2598
+ } else if (cleanup.stoppedDaemonCount > 0) {
2599
+ console.log(`[setup-ide-files] Stopped ${cleanup.stoppedDaemonCount} stale daemon(s)`);
2600
+ }
2601
+ } else {
2602
+ console.log("[setup-ide-files] No stale daemons found for this project and IDE target(s).");
2603
+ }
2604
+ if (cleanup.removedSocketCount > 0) {
2605
+ if (cleanup.dryRun) {
2606
+ console.log(`[setup-ide-files] Would remove ${cleanup.removedSocketCount} stale socket(s)`);
2607
+ } else {
2608
+ console.log(`[setup-ide-files] Removed ${cleanup.removedSocketCount} stale socket(s)`);
2609
+ }
2610
+ }
2611
+ if (cleanup.skippedUnrelatedDaemonCount > 0) {
2612
+ console.log(
2613
+ `[setup-ide-files] Skipped ${cleanup.skippedUnrelatedDaemonCount} unrelated project daemon(s)`
2614
+ );
2615
+ }
2616
+ }
2617
+ async function runSetupIdeDaemonCleanup(opts) {
2618
+ const ides = ideTypesFromSetupTargets(opts.targets);
2619
+ if (ides.length === 0) {
2620
+ return {
2621
+ skipped: true,
2622
+ skipReason: "no-targets",
2623
+ foundDaemonCount: 0,
2624
+ stoppedDaemonCount: 0,
2625
+ removedSocketCount: 0,
2626
+ skippedUnrelatedDaemonCount: 0,
2627
+ dryRun: opts.dryRun
2628
+ };
2629
+ }
2630
+ const target = await resolveCleanupTarget(opts.repoRoot);
2631
+ if ("error" in target) {
2632
+ return {
2633
+ skipped: true,
2634
+ skipReason: "no-m1",
2635
+ foundDaemonCount: 0,
2636
+ stoppedDaemonCount: 0,
2637
+ removedSocketCount: 0,
2638
+ skippedUnrelatedDaemonCount: 0,
2639
+ dryRun: opts.dryRun
2640
+ };
2641
+ }
2642
+ const baseCleanupOpts = {
2643
+ cwd: opts.repoRoot,
2644
+ dryRun: opts.dryRun,
2645
+ allProjects: false,
2646
+ assumeYes: true,
2647
+ projectId: target.projectId,
2648
+ quiet: true,
2649
+ listProcesses: opts.listProcesses,
2650
+ listSocketPaths: opts.listSocketPaths,
2651
+ killProcess: opts.killProcess,
2652
+ removeSocket: opts.removeSocket
2653
+ };
2654
+ const results = [];
2655
+ if (setupTargetsAllIdes(opts.targets)) {
2656
+ results.push(await runCleanup(baseCleanupOpts));
2657
+ } else {
2658
+ for (const ide of ides) {
2659
+ results.push(await runCleanup({ ...baseCleanupOpts, ide }));
2660
+ }
2661
+ }
2662
+ const aggregated = aggregateCleanupResults(results);
2663
+ const exitError = results.find((r) => r.exitCode !== 0)?.error ?? aggregated.error;
2664
+ return {
2665
+ skipped: false,
2666
+ projectId: target.projectId,
2667
+ foundDaemonCount: aggregated.foundDaemonCount,
2668
+ stoppedDaemonCount: opts.dryRun ? 0 : aggregated.stoppedDaemonCount,
2669
+ removedSocketCount: aggregated.removedSocketCount,
2670
+ skippedUnrelatedDaemonCount: aggregated.skippedUnrelatedDaemonCount,
2671
+ dryRun: opts.dryRun,
2672
+ error: exitError
2673
+ };
2674
+ }
2675
+ function restartIdeInstruction(targets) {
2676
+ const names = [];
2677
+ if (targets.cursor) names.push("Cursor");
2678
+ if (targets.vscode) names.push("VS Code");
2679
+ if (targets.jetbrains) names.push("JetBrains IDE");
2680
+ if (names.length === 0) return "Fully quit your IDE and reopen this repo for MCP changes to take effect.";
2681
+ if (names.length === 1) {
2682
+ return `Fully quit ${names[0]} and reopen this repo for MCP changes to take effect.`;
2683
+ }
2684
+ const last = names.pop();
2685
+ return `Fully quit ${names.join(", ")} and ${last}, then reopen this repo for MCP changes to take effect.`;
2686
+ }
1136
2687
  async function runSetupIdeFiles(o) {
1137
2688
  const outcomes = {};
1138
- let cursorGlobalMcp;
2689
+ let cursorMcp;
2690
+ let jetbrainsMcp;
1139
2691
  const repoRoot = await findRepoRoot(o.cwd);
1140
2692
  if (!repoRoot) {
1141
2693
  return {
@@ -1145,6 +2697,27 @@ async function runSetupIdeFiles(o) {
1145
2697
  error: "[setup-ide-files] No repo root found (looked for .git or memoraone.m1)."
1146
2698
  };
1147
2699
  }
2700
+ let daemonCleanup;
2701
+ if (!o.skipDaemonCleanup) {
2702
+ daemonCleanup = await runSetupIdeDaemonCleanup({
2703
+ repoRoot,
2704
+ targets: o.targets,
2705
+ dryRun: o.dryRun,
2706
+ listProcesses: o.listDaemonProcesses,
2707
+ listSocketPaths: o.listCleanupSocketPaths,
2708
+ killProcess: o.killDaemonProcess,
2709
+ removeSocket: o.removeCleanupSocket
2710
+ });
2711
+ if (daemonCleanup.error && !daemonCleanup.skipped) {
2712
+ return {
2713
+ exitCode: 1,
2714
+ repoRoot,
2715
+ outcomes,
2716
+ daemonCleanup,
2717
+ error: `[setup-ide-files] Daemon cleanup failed: ${daemonCleanup.error}`
2718
+ };
2719
+ }
2720
+ }
1148
2721
  outcomes[".gitignore"] = await ensureGitignoreMemoraone(repoRoot, {
1149
2722
  dryRun: o.dryRun,
1150
2723
  noGitignore: o.noGitignore ?? false
@@ -1155,19 +2728,6 @@ description: MemoraOne MCP \u2014 IDE agent instructions
1155
2728
 
1156
2729
  ` + cursorRuleBody();
1157
2730
  if (o.targets.cursor) {
1158
- const detection = await detectCursorGlobalMcpConfig({
1159
- homeDir: o.homeDir,
1160
- explicitPath: o.cursorGlobalMcpConfigPath
1161
- });
1162
- if (!detection.ok) {
1163
- return {
1164
- exitCode: 1,
1165
- repoRoot,
1166
- outcomes,
1167
- error: `${detection.error}
1168
- ${detection.candidates.join("\n ")}`
1169
- };
1170
- }
1171
2731
  let npxPath;
1172
2732
  if (o.npxPathOverride !== void 0) {
1173
2733
  npxPath = o.npxPathOverride;
@@ -1179,7 +2739,7 @@ description: MemoraOne MCP \u2014 IDE agent instructions
1179
2739
  exitCode: 1,
1180
2740
  repoRoot,
1181
2741
  outcomes,
1182
- error: "[setup-ide-files] Could not resolve a working npx executable. Install Node.js/npm or ensure npx is on PATH before configuring Cursor global MCP."
2742
+ error: "[setup-ide-files] Could not resolve a working npx executable. Install Node.js/npm or ensure npx is on PATH before configuring Cursor MCP."
1183
2743
  };
1184
2744
  }
1185
2745
  outcomes[".cursor/rules/memoraone-mcp.mdc"] = await writeManagedMarkdown(
@@ -1188,29 +2748,58 @@ description: MemoraOne MCP \u2014 IDE agent instructions
1188
2748
  cursorContent,
1189
2749
  { force: o.force, dryRun: o.dryRun }
1190
2750
  );
2751
+ outcomes[".cursor/mcp.json"] = await writeIdeMcpJson(
2752
+ repoRoot,
2753
+ ".cursor/mcp.json",
2754
+ (existing) => buildCursorMcpJsonBody(existing, npxPath, repoRoot),
2755
+ { force: o.force, dryRun: o.dryRun }
2756
+ );
2757
+ const repoConfigPath = getCursorRepoMcpConfigPath(repoRoot);
2758
+ const repoOutcome = outcomes[".cursor/mcp.json"] ?? "skipped";
2759
+ let globalConfigPath;
2760
+ let globalMemoraoneRemoved = false;
2761
+ let globalBackupPath;
2762
+ const globalDetection = await detectCursorGlobalMcpConfig({
2763
+ homeDir: o.homeDir,
2764
+ explicitPath: o.cursorGlobalMcpConfigPath
2765
+ });
2766
+ if (!globalDetection.ok) {
2767
+ return {
2768
+ exitCode: 1,
2769
+ repoRoot,
2770
+ outcomes,
2771
+ error: `${globalDetection.error}
2772
+ ${globalDetection.candidates.join("\n ")}`
2773
+ };
2774
+ }
2775
+ globalConfigPath = globalDetection.path;
1191
2776
  try {
1192
- const globalSetup = await setupCursorGlobalMcpConfig({
1193
- configPath: detection.path,
1194
- npxPath,
2777
+ const removal = await removeMemoraoneFromCursorGlobalConfig({
2778
+ configPath: globalDetection.path,
1195
2779
  dryRun: o.dryRun
1196
2780
  });
1197
- cursorGlobalMcp = {
1198
- configPath: detection.path,
1199
- outcome: globalSetup.outcome,
1200
- npxPath,
1201
- backupPath: globalSetup.backupPath
1202
- };
1203
- outcomes[`cursor-global:${detection.path}`] = globalSetup.outcome;
2781
+ if (removal.changed) {
2782
+ globalMemoraoneRemoved = true;
2783
+ globalBackupPath = removal.backupPath;
2784
+ outcomes[`cursor-global-removed:${globalDetection.path}`] = o.dryRun ? "updated" : "updated";
2785
+ }
1204
2786
  } catch (err) {
1205
2787
  const message = err instanceof Error ? err.message : String(err);
1206
2788
  return {
1207
2789
  exitCode: 1,
1208
2790
  repoRoot,
1209
2791
  outcomes,
1210
- cursorGlobalMcp: { configPath: detection.path, outcome: "skipped", npxPath },
1211
2792
  error: message
1212
2793
  };
1213
2794
  }
2795
+ cursorMcp = {
2796
+ repoConfigPath,
2797
+ repoOutcome,
2798
+ npxPath,
2799
+ globalConfigPath,
2800
+ globalMemoraoneRemoved,
2801
+ globalBackupPath
2802
+ };
1214
2803
  }
1215
2804
  if (o.targets.vscode) {
1216
2805
  outcomes[".vscode/mcp.json"] = await writeIdeMcpJson(
@@ -1236,11 +2825,45 @@ description: MemoraOne MCP \u2014 IDE agent instructions
1236
2825
  copilotAndJetBrainsBody("MemoraOne MCP \u2014 JetBrains AI Assistant"),
1237
2826
  { force: o.force, dryRun: o.dryRun }
1238
2827
  );
2828
+ try {
2829
+ const homeDir = o.jetbrainsHomeDir ?? os4.homedir();
2830
+ const activePath = o.jetbrainsGlobalMcpConfigPath ?? getJetBrainsGlobalMcpConfigPath(homeDir);
2831
+ const jetbrainsSetup = await setupJetBrainsMcpConfig({
2832
+ homeDir,
2833
+ repoRoot,
2834
+ globalConfigPath: activePath,
2835
+ dryRun: o.dryRun,
2836
+ devMode: o.devMode,
2837
+ repair: o.repair ?? false,
2838
+ verify: o.verifyHandshake ?? !o.dryRun,
2839
+ npxPathOverride: o.npxPathOverride,
2840
+ cliPathOverride: o.cliPathOverride
2841
+ });
2842
+ jetbrainsMcp = {
2843
+ activeConfigPath: activePath,
2844
+ outcome: jetbrainsSetup.outcome,
2845
+ npxPath: jetbrainsSetup.memoraone?.command,
2846
+ backupPath: jetbrainsSetup.backupPath,
2847
+ repairActions: jetbrainsSetup.repairActions,
2848
+ verifyOk: jetbrainsSetup.verifyOk,
2849
+ verifyDetail: jetbrainsSetup.verifyDetail
2850
+ };
2851
+ outcomes[`jetbrains-global:${activePath}`] = jetbrainsSetup.outcome;
2852
+ } catch (err) {
2853
+ const message = err instanceof Error ? err.message : String(err);
2854
+ return {
2855
+ exitCode: 1,
2856
+ repoRoot,
2857
+ outcomes,
2858
+ cursorMcp,
2859
+ error: message
2860
+ };
2861
+ }
1239
2862
  }
1240
- return { exitCode: 0, repoRoot, outcomes, cursorGlobalMcp };
2863
+ return { exitCode: 0, repoRoot, outcomes, cursorMcp, jetbrainsMcp, daemonCleanup };
1241
2864
  }
1242
2865
  async function cliSetupIdeFiles(argv) {
1243
- const { targets, force, dryRun, noGitignore, cleanup, unknown } = parseSetupIdeFlags(argv);
2866
+ const { targets, force, dryRun, noGitignore, cleanup, devMode, repair, unknown } = parseSetupIdeFlags(argv);
1244
2867
  if (unknown.length) {
1245
2868
  console.error(`[setup-ide-files] Unknown option(s): ${unknown.join(", ")}`);
1246
2869
  return 1;
@@ -1250,7 +2873,9 @@ async function cliSetupIdeFiles(argv) {
1250
2873
  targets,
1251
2874
  force,
1252
2875
  dryRun,
1253
- noGitignore
2876
+ noGitignore,
2877
+ devMode,
2878
+ repair
1254
2879
  });
1255
2880
  if (result.error) {
1256
2881
  console.error(result.error);
@@ -1263,15 +2888,25 @@ async function cliSetupIdeFiles(argv) {
1263
2888
  if (result.repoRoot) {
1264
2889
  console.log(`[setup-ide-files] Repo root: ${result.repoRoot}`);
1265
2890
  }
1266
- if (targets.cursor && result.cursorGlobalMcp) {
1267
- logCursorGlobalMcpCliSummary(result.cursorGlobalMcp, dryRun);
2891
+ if (result.daemonCleanup && !result.daemonCleanup.skipped) {
2892
+ logSetupIdeCleanupSummary(result.daemonCleanup);
2893
+ }
2894
+ if (targets.cursor && result.cursorMcp) {
2895
+ logCursorMcpCliSummary(result.cursorMcp, dryRun);
2896
+ }
2897
+ if (targets.jetbrains && result.jetbrainsMcp) {
2898
+ logJetBrainsMcpCliSummary(result.jetbrainsMcp, dryRun);
1268
2899
  }
1269
2900
  summarizeOutcomes(result.outcomes);
1270
2901
  if (dryRun) {
1271
2902
  console.log("[setup-ide-files] Dry run: no files written.");
2903
+ if (result.daemonCleanup && !result.daemonCleanup.skipped) {
2904
+ console.log("[setup-ide-files] Dry run: no daemons stopped, no sockets removed.");
2905
+ }
1272
2906
  }
2907
+ console.log(`[setup-ide-files] ${restartIdeInstruction(targets)}`);
1273
2908
  if (cleanup) {
1274
- console.log("[setup-ide-files] Running project-scoped cleanup...");
2909
+ console.log("[setup-ide-files] Running additional full-project cleanup (--cleanup)...");
1275
2910
  const cleanupResult = await runCleanup({
1276
2911
  cwd: process.cwd(),
1277
2912
  dryRun,
@@ -1282,13 +2917,6 @@ async function cliSetupIdeFiles(argv) {
1282
2917
  console.error(`[setup-ide-files] cleanup failed: ${cleanupResult.error}`);
1283
2918
  return cleanupResult.exitCode;
1284
2919
  }
1285
- } else {
1286
- console.log(
1287
- "[setup-ide-files] If Studio shows stale connections, run: npx -y @memoraone/mcp@latest cleanup --project-id <projectId>"
1288
- );
1289
- console.log(
1290
- "[setup-ide-files] From this repo (uses memoraone.m1): npx -y @memoraone/mcp@latest cleanup"
1291
- );
1292
2920
  }
1293
2921
  return 0;
1294
2922
  }
@@ -1302,7 +2930,7 @@ if (args.includes("--version") || args.includes("-v")) {
1302
2930
  }
1303
2931
  if (args.includes("--help") || args.includes("-h")) {
1304
2932
  console.log(
1305
- "Usage: memoraone-mcp [--version] [--help] [--daemon --project-id <uuid> [--ide cursor|copilot-vscode|jetbrains]]\n memoraone-mcp setup-ide-files [--all|--cursor|--vscode|--jetbrains] [--force] [--dry-run] [--no-gitignore] [--cleanup]\n memoraone-mcp cleanup [--project-id <uuid>] [--ide cursor|copilot-vscode|jetbrains] [--dry-run] [--all-projects] [--yes]"
2933
+ "Usage: memoraone-mcp [--version] [--help] [--daemon --project-id <uuid> [--ide cursor|copilot-vscode|jetbrains]]\n memoraone-mcp setup-ide-files [--all|--cursor|--vscode|--jetbrains] [--force] [--dry-run] [--no-gitignore] [--cleanup] [--dev] [--repair]\n memoraone-mcp cleanup [--project-id <uuid>] [--ide cursor|copilot-vscode|jetbrains] [--dry-run] [--all-projects] [--yes]"
1306
2934
  );
1307
2935
  process.exit(0);
1308
2936
  }
@@ -1325,92 +2953,9 @@ if (args[0] === "cleanup") {
1325
2953
  process.exit(1);
1326
2954
  });
1327
2955
  } else {
1328
- let getWorkspaceRootCandidates = function() {
1329
- const raw = process.env.WORKSPACE_FOLDER_PATHS;
1330
- const parts = [];
1331
- if (raw !== void 0 && raw.trim() !== "") {
1332
- for (const p of raw.split(path6.delimiter).map((s) => s.trim()).filter(Boolean)) {
1333
- parts.push(path6.resolve(p));
1334
- }
1335
- }
1336
- parts.push(process.cwd());
1337
- const seen = /* @__PURE__ */ new Set();
1338
- const deduped = [];
1339
- for (const p of parts) {
1340
- if (!seen.has(p)) {
1341
- seen.add(p);
1342
- deduped.push(p);
1343
- }
1344
- }
1345
- return deduped;
1346
- }, connectWithRetry = function(socketPath) {
1347
- return new Promise((resolve6, reject) => {
1348
- const tryConnect = (attempt) => {
1349
- const socket = net.connect(socketPath, () => {
1350
- resolve6(socket);
1351
- });
1352
- socket.on("error", (err) => {
1353
- if (attempt >= MAX_RETRIES) {
1354
- reject(err);
1355
- return;
1356
- }
1357
- log(`connect attempt ${attempt + 1} failed, retrying in ${RETRY_DELAY_MS}ms: ${String(err)}`);
1358
- setTimeout(() => tryConnect(attempt + 1), RETRY_DELAY_MS);
1359
- });
1360
- };
1361
- tryConnect(0);
1362
- });
1363
- };
1364
- const log = (msg) => {
1365
- process.stderr.write(`[memoraone-mcp][bridge] ${msg}
2956
+ runBridgeProxy({ cliPath: process.argv[1] }).catch((err) => {
2957
+ process.stderr.write(`[memoraone-mcp][bridge] fatal: ${String(err)}
1366
2958
  `);
1367
- };
1368
- const MAX_RETRIES = 5;
1369
- const RETRY_DELAY_MS = 200;
1370
- async function resolveBinding() {
1371
- return resolveAuthoritativeBinding(getWorkspaceRootCandidates());
1372
- }
1373
- async function runBridge() {
1374
- ensureBaseDir();
1375
- const binding = await resolveBinding();
1376
- const socketPath = getDaemonSocketPath(binding.projectId);
1377
- const environmentLog = binding.environment !== void 0 ? ` environment=${binding.environment}` : "";
1378
- log(
1379
- `authoritative binding project=${binding.projectId} workspace=${binding.workspaceRoot} m1=${binding.m1Path} source=${binding.bindingSource} apiKeySource=${binding.apiKeySource}${environmentLog}`
1380
- );
1381
- let socket;
1382
- try {
1383
- socket = await connectWithRetry(socketPath);
1384
- } catch {
1385
- log("daemon not running, spawning...");
1386
- const child = (0, import_node_child_process3.spawn)(
1387
- process.execPath,
1388
- buildDaemonSpawnArgs(process.argv[1], binding.projectId),
1389
- {
1390
- detached: true,
1391
- stdio: "ignore",
1392
- env: {
1393
- ...process.env,
1394
- MEMORAONE_DAEMON_BINDING_B64: encodeResolvedBinding(binding)
1395
- }
1396
- }
1397
- );
1398
- child.unref();
1399
- await new Promise((r) => setTimeout(r, RETRY_DELAY_MS));
1400
- socket = await connectWithRetry(socketPath);
1401
- }
1402
- log("bridge connected");
1403
- log("forwarding active");
1404
- process.stdin.pipe(socket);
1405
- socket.pipe(process.stdout);
1406
- socket.on("close", () => process.exit(0));
1407
- socket.on("error", (err) => {
1408
- log(`socket error: ${String(err)}`);
1409
- process.exit(1);
1410
- });
1411
- }
1412
- runBridge().catch((err) => {
1413
- log(`fatal: ${String(err)}`);
1414
2959
  process.exit(1);
1415
2960
  });
1416
2961
  }