@memoraone/mcp 0.1.30 → 0.1.32

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 +1827 -747
  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.30",
33
+ version: "0.1.32",
34
34
  type: "module",
35
35
  main: "dist/index.cjs",
36
36
  bin: {
@@ -66,88 +66,57 @@ var require_package = __commonJS({
66
66
  }
67
67
  });
68
68
 
69
- // src/cli.ts
70
- var path7 = __toESM(require("path"), 1);
69
+ // src/bridgeProxy.ts
71
70
  var net = __toESM(require("net"), 1);
72
- var import_node_child_process4 = require("child_process");
71
+ var readline2 = __toESM(require("readline"), 1);
72
+ var import_node_child_process = require("child_process");
73
73
 
74
- // src/socketPaths.ts
75
- var os = __toESM(require("os"), 1);
74
+ // src/bindingIdentity.ts
75
+ var crypto = __toESM(require("crypto"), 1);
76
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;
80
- function getMcpBaseDir() {
81
- return BASE_DIR;
82
- }
83
- var IDE_TYPES = ["cursor", "copilot-vscode", "jetbrains"];
84
- var IDE_TYPE_SET = new Set(IDE_TYPES);
85
- function parseIdeType(value) {
86
- if (value === void 0 || value.trim() === "" || !IDE_TYPE_SET.has(value)) {
87
- return void 0;
88
- }
89
- return value;
90
- }
91
- function resolveIdeTypeFromEnv(env = process.env) {
92
- return parseIdeType(env.MEMORAONE_IDE_TYPE);
93
- }
94
- function parseIdeTypeFromCommandLine(commandLine) {
95
- const match = commandLine.match(/--ide\s+(cursor|copilot-vscode|jetbrains)(?:\s|$)/);
96
- return match ? match[1] : void 0;
97
- }
98
- function buildDaemonSpawnArgs(scriptPath, projectId, env = process.env) {
99
- const args2 = [scriptPath, "--daemon", "--project-id", projectId];
100
- const ideType = resolveIdeTypeFromEnv(env);
101
- if (ideType) {
102
- args2.push("--ide", ideType);
103
- }
104
- return args2;
105
- }
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`);
111
- }
112
- function getDaemonSocketPath(projectId, env = process.env) {
113
- return getSocketPath(projectId, resolveIdeTypeFromEnv(env));
114
- }
115
- function ensureBaseDir() {
116
- fs.mkdirSync(BASE_DIR, { recursive: true });
117
- return BASE_DIR;
118
- }
119
- function isMemoraoneSocketFilename(filename) {
120
- return SOCKET_PROJECT_ID_RE.test(path.basename(filename));
121
- }
122
- function extractProjectIdFromSocketFilename(filename) {
123
- const match = path.basename(filename).match(SOCKET_PROJECT_ID_RE);
124
- return match ? match[1].toLowerCase() : null;
125
- }
126
- function isSocketFilenameForProject(filename, projectId) {
127
- const extracted = extractProjectIdFromSocketFilename(filename);
128
- return extracted !== null && extracted === projectId.trim().toLowerCase();
129
- }
130
- function extractIdeTypeFromSocketFilename(filename) {
131
- const match = path.basename(filename).match(SOCKET_PROJECT_ID_RE);
132
- 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;
136
- return null;
137
- }
138
- function isSocketFilenameForProjectAndIde(filename, projectId, ide) {
139
- if (!isSocketFilenameForProject(filename, projectId)) return false;
140
- if (ide === void 0) return true;
141
- const socketIde = extractIdeTypeFromSocketFilename(filename);
142
- if (socketIde === null) return false;
143
- if (ide === "cursor") {
144
- return socketIde === "cursor" || socketIde === "legacy";
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");
145
107
  }
146
- return socketIde === ide;
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");
147
112
  }
148
113
 
114
+ // src/bindingSidecar.ts
115
+ var fs3 = __toESM(require("fs"), 1);
116
+ var path4 = __toESM(require("path"), 1);
117
+
149
118
  // src/projectBinding.ts
150
- var fs2 = __toESM(require("fs/promises"), 1);
119
+ var fs = __toESM(require("fs/promises"), 1);
151
120
  var path2 = __toESM(require("path"), 1);
152
121
  var uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
153
122
  function normalizeEnvironment(raw) {
@@ -183,7 +152,7 @@ async function resolveProjectIdFromExplicitM1Path() {
183
152
  }
184
153
  const markerPath = path2.resolve(raw);
185
154
  try {
186
- const content = await fs2.readFile(markerPath, "utf8");
155
+ const content = await fs.readFile(markerPath, "utf8");
187
156
  const { projectId, apiKey, environment } = parseAndValidateM1(content, markerPath);
188
157
  return environment === void 0 ? { projectId, apiKey, foundAt: markerPath } : { projectId, apiKey, environment, foundAt: markerPath };
189
158
  } catch (err) {
@@ -198,7 +167,7 @@ async function findM1WalkingUp(workspaceRoot) {
198
167
  while (true) {
199
168
  const markerPath = path2.join(current, "memoraone.m1");
200
169
  try {
201
- const content = await fs2.readFile(markerPath, "utf8");
170
+ const content = await fs.readFile(markerPath, "utf8");
202
171
  const { projectId, apiKey, environment } = parseAndValidateM1(content, markerPath);
203
172
  const repoRoot = path2.dirname(markerPath);
204
173
  return environment === void 0 ? { projectId, apiKey, repoRoot, markerPath } : { projectId, apiKey, environment, repoRoot, markerPath };
@@ -252,29 +221,33 @@ function resolveApiKeyWithSource(fileApiKey) {
252
221
  }
253
222
  return { apiKey: null, apiKeySource: "none" };
254
223
  }
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
- };
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
+ }
268
240
  }
269
241
  const candidates = normalizeWorkspaceSearchRoots(workspaceRoot);
270
242
  if (candidates.length === 0) {
271
243
  throw new Error("Could not find memoraone.m1 in workspace.\nOpen a folder containing memoraone.m1.");
272
244
  }
245
+ const bindings = [];
273
246
  for (const root of candidates) {
274
247
  const binding = await findM1WalkingUp(root);
275
248
  if (binding) {
276
249
  const resolved = resolveApiKeyWithSource(binding.apiKey);
277
- return {
250
+ bindings.push({
278
251
  projectId: binding.projectId,
279
252
  workspaceRoot: binding.repoRoot,
280
253
  m1Path: binding.markerPath,
@@ -282,159 +255,1348 @@ async function resolveAuthoritativeBinding(workspaceRoot) {
282
255
  ...binding.environment !== void 0 ? { environment: binding.environment } : {},
283
256
  bindingSource: "workspace-search",
284
257
  apiKeySource: resolved.apiKeySource
285
- };
258
+ });
286
259
  }
287
260
  }
288
- throw new Error("Could not find memoraone.m1 in workspace.\nOpen a folder containing memoraone.m1.");
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];
289
274
  }
290
275
  function encodeResolvedBinding(binding) {
291
276
  return Buffer.from(JSON.stringify(binding), "utf8").toString("base64");
292
277
  }
293
-
294
- // src/setupIdeFiles.ts
295
- var fs6 = __toESM(require("fs/promises"), 1);
296
- var os4 = __toESM(require("os"), 1);
297
- var path6 = __toESM(require("path"), 1);
298
-
299
- // src/cleanup.ts
300
- var fs3 = __toESM(require("fs/promises"), 1);
301
- var path3 = __toESM(require("path"), 1);
302
- var readline = __toESM(require("readline/promises"), 1);
303
- var import_node_child_process = require("child_process");
304
- var import_node_util = require("util");
305
- var import_node_process = require("process");
306
- var execFileAsync = (0, import_node_util.promisify)(import_node_child_process.execFile);
307
- 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;
308
- 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;
309
- var MEMORAONE_MCP_COMMAND_RE = /memoraone-mcp|memoraOne-mcp|@memoraone\/mcp/;
310
- var CLEANUP_PROJECT_ID_REQUIRED_ERROR = "Provide --project-id <id> or run from a folder containing memoraone.m1.";
311
- function isMemoraoneMcpCommandLine(commandLine) {
312
- return MEMORAONE_MCP_COMMAND_RE.test(commandLine);
313
- }
314
- function parseDaemonProjectIdFromCommandLine(commandLine) {
315
- if (!commandLine.includes("--daemon")) {
278
+ function decodeResolvedBinding(value) {
279
+ if (!value) {
316
280
  return null;
317
281
  }
318
- const match = commandLine.match(DAEMON_PROJECT_ID_RE);
319
- return match ? match[1].toLowerCase() : null;
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
+ };
320
322
  }
321
- function parseDaemonIdeFromCommandLine(commandLine) {
322
- if (!commandLine.includes("--daemon")) {
323
+
324
+ // src/socketPaths.ts
325
+ var os = __toESM(require("os"), 1);
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;
335
+ function getMcpBaseDir() {
336
+ return BASE_DIR;
337
+ }
338
+ var IDE_TYPES = ["cursor", "copilot-vscode", "jetbrains"];
339
+ var IDE_TYPE_SET = new Set(IDE_TYPES);
340
+ function parseIdeType(value) {
341
+ if (value === void 0 || value.trim() === "" || !IDE_TYPE_SET.has(value)) {
323
342
  return void 0;
324
343
  }
325
- return parseIdeTypeFromCommandLine(commandLine);
344
+ return value;
326
345
  }
327
- function parseDaemonProcessLines(lines) {
328
- const processes = [];
329
- for (const line of lines) {
330
- const trimmed = line.trim();
331
- if (!trimmed) continue;
332
- const spaceIdx = trimmed.indexOf(" ");
333
- if (spaceIdx <= 0) continue;
334
- const pid = Number.parseInt(trimmed.slice(0, spaceIdx), 10);
335
- if (!Number.isFinite(pid) || pid <= 0) continue;
336
- const command = trimmed.slice(spaceIdx + 1);
337
- if (!isMemoraoneMcpCommandLine(command)) continue;
338
- const projectId = parseDaemonProjectIdFromCommandLine(command);
339
- if (projectId === null) continue;
340
- const ide = parseDaemonIdeFromCommandLine(command);
341
- processes.push({ pid, command, projectId, ...ide !== void 0 ? { ide } : {} });
342
- }
343
- return processes;
346
+ function resolveIdeTypeFromEnv(env = process.env) {
347
+ return parseIdeType(env.MEMORAONE_IDE_TYPE);
344
348
  }
345
- function normalizeCleanupProjectId(projectId) {
346
- const trimmed = projectId.trim();
347
- if (!PROJECT_ID_RE.test(trimmed)) {
348
- return { error: `Invalid project id: ${projectId}` };
349
+ function parseIdeTypeFromCommandLine(commandLine) {
350
+ const match = commandLine.match(/--ide\s+(cursor|copilot-vscode|jetbrains)(?:\s|$)/);
351
+ return match ? match[1] : void 0;
352
+ }
353
+ function buildDaemonSpawnArgs(scriptPath, projectId, env = process.env) {
354
+ const args2 = [scriptPath, "--daemon", "--project-id", projectId];
355
+ const ideType = resolveIdeTypeFromEnv(env);
356
+ if (ideType) {
357
+ args2.push("--ide", ideType);
349
358
  }
350
- return trimmed.toLowerCase();
359
+ return args2;
351
360
  }
352
- async function defaultListDaemonProcesses() {
353
- const { stdout } = await execFileAsync("ps", ["-eo", "pid=,args="], {
354
- maxBuffer: 10 * 1024 * 1024
355
- });
356
- return parseDaemonProcessLines(stdout.split("\n"));
361
+ function resolveBindingIdeType(env = process.env) {
362
+ return resolveIdeTypeFromEnv(env) ?? "";
357
363
  }
358
- async function defaultListSocketPaths(projectId) {
359
- const baseDir = getMcpBaseDir();
360
- let entries;
361
- try {
362
- entries = await fs3.readdir(baseDir);
363
- } catch (err) {
364
- const code = err && typeof err === "object" && "code" in err ? err.code : void 0;
365
- if (code === "ENOENT") {
366
- return [];
367
- }
368
- throw err;
369
- }
370
- const paths = [];
371
- for (const name of entries) {
372
- if (projectId === null) {
373
- if (isMemoraoneSocketFilename(name)) {
374
- paths.push(path3.join(baseDir, name));
375
- }
376
- } else if (isSocketFilenameForProject(name, projectId)) {
377
- paths.push(path3.join(baseDir, name));
378
- }
379
- }
380
- return paths.sort();
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`;
381
368
  }
382
- function filterSocketPathsByIde(socketPaths, projectId, ide) {
383
- if (ide === void 0) return socketPaths;
384
- return socketPaths.filter(
385
- (socketPath) => isSocketFilenameForProjectAndIde(path3.basename(socketPath), projectId, ide)
386
- );
369
+ function getBindingSocketPath(binding, env = process.env) {
370
+ return path3.join(BASE_DIR, getBindingSocketFilename(binding, env));
387
371
  }
388
- async function defaultKillProcess(pid) {
389
- process.kill(pid, "SIGTERM");
372
+ function ensureBaseDir() {
373
+ fs2.mkdirSync(BASE_DIR, { recursive: true });
374
+ return BASE_DIR;
390
375
  }
391
- async function defaultRemoveSocket(socketPath) {
392
- await fs3.unlink(socketPath);
376
+ function isHashSocketFilename(filename) {
377
+ return HASH_SOCKET_FILENAME_RE.test(path3.basename(filename));
393
378
  }
394
- async function defaultConfirm(message) {
395
- if (!import_node_process.stdin.isTTY) {
396
- return false;
379
+ function isLegacySocketFilename(filename) {
380
+ return LEGACY_SOCKET_FILENAME_RE.test(path3.basename(filename));
381
+ }
382
+ function isMemoraoneSocketFilename(filename) {
383
+ const base = path3.basename(filename);
384
+ return HASH_SOCKET_FILENAME_RE.test(base) || LEGACY_SOCKET_PROJECT_ID_RE.test(base);
385
+ }
386
+ function extractProjectIdFromSocketFilename(filename) {
387
+ const match = path3.basename(filename).match(LEGACY_SOCKET_PROJECT_ID_RE);
388
+ return match ? match[1].toLowerCase() : null;
389
+ }
390
+ function isSocketFilenameForProject(filename, projectId) {
391
+ const extracted = extractProjectIdFromSocketFilename(filename);
392
+ return extracted !== null && extracted === projectId.trim().toLowerCase();
393
+ }
394
+ function extractIdeTypeFromSocketFilename(filename) {
395
+ const match = path3.basename(filename).match(LEGACY_SOCKET_FILENAME_RE);
396
+ if (!match) return null;
397
+ const ide = match[3];
398
+ if (ide === void 0) return "legacy";
399
+ if (IDE_TYPE_SET.has(ide)) return ide;
400
+ return null;
401
+ }
402
+ function isSocketFilenameForProjectAndIde(filename, projectId, ide) {
403
+ if (!isSocketFilenameForProject(filename, projectId)) return false;
404
+ if (ide === void 0) return true;
405
+ const socketIde = extractIdeTypeFromSocketFilename(filename);
406
+ if (socketIde === null) return false;
407
+ if (ide === "cursor") {
408
+ return socketIde === "cursor" || socketIde === "legacy";
397
409
  }
398
- const rl = readline.createInterface({ input: import_node_process.stdin, output: import_node_process.stdout });
399
- try {
400
- const answer = await rl.question(`${message} [y/N] `);
401
- return /^y(es)?$/i.test(answer.trim());
402
- } finally {
403
- rl.close();
410
+ return socketIde === ide;
411
+ }
412
+
413
+ // src/bindingSidecar.ts
414
+ function bindingSidecarPath(socketPath) {
415
+ if (socketPath.endsWith(".sock")) {
416
+ return `${socketPath.slice(0, -".sock".length)}.binding.json`;
404
417
  }
418
+ return `${socketPath}.binding.json`;
405
419
  }
406
- async function resolveCleanupTarget(cwd) {
420
+ function parseSidecarRecord(raw) {
407
421
  try {
408
- const binding = await resolveAuthoritativeBinding([path3.resolve(cwd)]);
422
+ const parsed = JSON.parse(raw);
423
+ if (!parsed?.binding) {
424
+ return null;
425
+ }
426
+ const binding = decodeResolvedBinding(parsed.binding);
409
427
  return {
410
- workspaceRoot: binding.workspaceRoot,
411
- m1Path: binding.m1Path,
412
- projectId: binding.projectId
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
413
434
  };
414
435
  } catch {
415
- return { error: CLEANUP_PROJECT_ID_REQUIRED_ERROR };
436
+ return null;
416
437
  }
417
438
  }
418
- function filterProcessesForScope(processes, projectId) {
419
- if (projectId === null) {
420
- return { matching: processes, skipped: [] };
421
- }
422
- const normalized = projectId.toLowerCase();
423
- const matching = [];
424
- const skipped = [];
425
- for (const proc of processes) {
426
- if (proc.projectId === normalized) {
427
- matching.push(proc);
428
- } else {
429
- skipped.push(proc);
430
- }
439
+ function readBindingSidecarRecord(socketPath) {
440
+ try {
441
+ const raw = fs3.readFileSync(bindingSidecarPath(socketPath), "utf8");
442
+ return parseSidecarRecord(raw);
443
+ } catch {
444
+ return null;
431
445
  }
432
- return { matching, skipped };
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
+ var MEMORAONE_PROD_API_URL = "https://api.memoraone.com";
1165
+ var MEMORAONE_LOCAL_API_URL = "http://localhost:3001";
1166
+ var MEMORAONE_STAGING_API_URL_PREFIX = "https://memora-api-staging-";
1167
+ function buildMemoraoneCursorMcpServer(npxPath, workspaceRoot) {
1168
+ const env = {
1169
+ MEMORAONE_API_URL: MEMORAONE_PROD_API_URL,
1170
+ MEMORAONE_IDE_TYPE: "cursor"
1171
+ };
1172
+ if (workspaceRoot !== void 0) {
1173
+ env[MEMORAONE_WORKSPACE_ROOT_ENV] = path6.resolve(workspaceRoot);
1174
+ }
1175
+ return {
1176
+ command: npxPath,
1177
+ args: ["-y", "@memoraone/mcp@latest"],
1178
+ env
1179
+ };
1180
+ }
1181
+ async function pathExists(filePath) {
1182
+ try {
1183
+ await fs4.access(filePath);
1184
+ return true;
1185
+ } catch {
1186
+ return false;
1187
+ }
1188
+ }
1189
+ function stripLeadingLineComments(text) {
1190
+ return text.split("\n").filter((line) => !/^\s*\/\//.test(line)).join("\n");
1191
+ }
1192
+ function getKnownCursorGlobalMcpConfigCandidates(homeDir) {
1193
+ return [path6.join(homeDir, ".cursor", "mcp.json")];
1194
+ }
1195
+ async function detectCursorGlobalMcpConfig(options) {
1196
+ if (options?.explicitPath) {
1197
+ return { ok: true, path: options.explicitPath, detectedExisting: await pathExists(options.explicitPath) };
1198
+ }
1199
+ const homeDir = options?.homeDir ?? os2.homedir();
1200
+ const candidates = getKnownCursorGlobalMcpConfigCandidates(homeDir);
1201
+ const existing = [];
1202
+ for (const candidate of candidates) {
1203
+ if (await pathExists(candidate)) existing.push(candidate);
1204
+ }
1205
+ if (existing.length > 1) {
1206
+ return {
1207
+ ok: false,
1208
+ error: "[setup-ide-files] Multiple Cursor global MCP config paths found. Specify one explicitly.",
1209
+ candidates: existing
1210
+ };
1211
+ }
1212
+ if (existing.length === 1) {
1213
+ return { ok: true, path: existing[0], detectedExisting: true };
1214
+ }
1215
+ const defaultPath = candidates[0];
1216
+ if (!defaultPath) {
1217
+ return {
1218
+ ok: false,
1219
+ error: "[setup-ide-files] No known Cursor global MCP config path.",
1220
+ candidates: []
1221
+ };
1222
+ }
1223
+ return { ok: true, path: defaultPath, detectedExisting: false };
1224
+ }
1225
+ function formatBackupTimestamp(d = /* @__PURE__ */ new Date()) {
1226
+ const pad = (n) => String(n).padStart(2, "0");
1227
+ return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
1228
+ }
1229
+ async function isWorkingNpx(npxPath) {
1230
+ try {
1231
+ if (!await pathExists(npxPath)) return false;
1232
+ if (process.platform !== "win32") {
1233
+ try {
1234
+ await fs4.access(npxPath, fs4.constants.X_OK);
1235
+ } catch {
1236
+ return false;
1237
+ }
1238
+ }
1239
+ await execFileAsync(npxPath, ["--version"], { timeout: 1e4 });
1240
+ return true;
1241
+ } catch {
1242
+ return false;
1243
+ }
1244
+ }
1245
+ async function resolveNpxPath() {
1246
+ const npxName = process.platform === "win32" ? "npx.cmd" : "npx";
1247
+ const candidates = [];
1248
+ if (process.platform === "darwin") {
1249
+ candidates.push("/opt/homebrew/bin/npx", "/usr/local/bin/npx");
1250
+ } else if (process.platform === "linux") {
1251
+ candidates.push("/usr/local/bin/npx");
1252
+ }
1253
+ const pathSep = process.platform === "win32" ? ";" : ":";
1254
+ for (const dir of (process.env.PATH ?? "").split(pathSep)) {
1255
+ if (!dir) continue;
1256
+ candidates.push(path6.join(dir, npxName));
1257
+ }
1258
+ try {
1259
+ const lookupCmd = process.platform === "win32" ? "where" : "which";
1260
+ const { stdout } = await execFileAsync(lookupCmd, [npxName], { timeout: 5e3 });
1261
+ const first = stdout.trim().split(/\r?\n/).map((line) => line.trim()).find(Boolean);
1262
+ if (first) candidates.unshift(first);
1263
+ } catch {
1264
+ }
1265
+ const seen = /* @__PURE__ */ new Set();
1266
+ for (const candidate of candidates) {
1267
+ const abs = path6.isAbsolute(candidate) ? candidate : path6.resolve(candidate);
1268
+ const key = process.platform === "win32" ? abs.toLowerCase() : abs;
1269
+ if (seen.has(key)) continue;
1270
+ seen.add(key);
1271
+ if (await isWorkingNpx(abs)) return abs;
1272
+ }
1273
+ return null;
1274
+ }
1275
+ function mergeCursorRepoMcpConfigObject(existing, npxPath, repoRoot) {
1276
+ const base = existing && typeof existing === "object" ? { ...existing } : { mcpServers: {} };
1277
+ const mcpServers = typeof base.mcpServers === "object" && base.mcpServers !== null && !Array.isArray(base.mcpServers) ? { ...base.mcpServers } : {};
1278
+ mcpServers.memoraone = buildMemoraoneCursorMcpServer(npxPath, repoRoot);
1279
+ return { ...base, mcpServers };
1280
+ }
1281
+ function isMemoraoneManagedApiUrl(url) {
1282
+ if (typeof url !== "string" || url.length === 0) return false;
1283
+ if (url === MEMORAONE_PROD_API_URL) return true;
1284
+ if (url === MEMORAONE_LOCAL_API_URL) return true;
1285
+ return url.startsWith(MEMORAONE_STAGING_API_URL_PREFIX);
1286
+ }
1287
+ function isManagedMemoraoneCursorServer(server) {
1288
+ if (!server || typeof server !== "object") return false;
1289
+ const s = server;
1290
+ if (!Array.isArray(s.args) || s.args.length !== 2) return false;
1291
+ if (s.args[0] !== "-y" || s.args[1] !== "@memoraone/mcp@latest") return false;
1292
+ const env = s.env;
1293
+ if (!env || typeof env !== "object") return false;
1294
+ return memoraoneEnvMatchesManagedCleanupShape(env);
1295
+ }
1296
+ function cursorConfigHasManagedMemoraone(parsed) {
1297
+ if (!parsed || typeof parsed !== "object") return false;
1298
+ const mcpServers = parsed.mcpServers;
1299
+ if (!mcpServers || typeof mcpServers !== "object" || Array.isArray(mcpServers)) return false;
1300
+ return isManagedMemoraoneCursorServer(mcpServers.memoraone);
1301
+ }
1302
+ function memoraoneEnvMatchesManagedCleanupShape(env) {
1303
+ return env.MEMORAONE_IDE_TYPE === "cursor" && isMemoraoneManagedApiUrl(env.MEMORAONE_API_URL);
1304
+ }
1305
+ function getCursorRepoMcpConfigPath(repoRoot) {
1306
+ return path6.join(repoRoot, ".cursor", "mcp.json");
1307
+ }
1308
+ async function readCursorMcpConfigObject(configPath) {
1309
+ try {
1310
+ const raw = await fs4.readFile(configPath, "utf8");
1311
+ return JSON.parse(stripLeadingLineComments(raw));
1312
+ } catch (err) {
1313
+ const code = err && typeof err === "object" && "code" in err ? err.code : void 0;
1314
+ if (code === "ENOENT") return null;
1315
+ throw err;
1316
+ }
1317
+ }
1318
+ async function removeMemoraoneFromCursorGlobalConfig(options) {
1319
+ const { configPath, dryRun } = options;
1320
+ const parsed = await readCursorMcpConfigObject(configPath);
1321
+ if (!parsed || !cursorConfigHasManagedMemoraone(parsed)) {
1322
+ return { changed: false };
1323
+ }
1324
+ if (dryRun) {
1325
+ return { changed: true, backupPath: `${configPath}.backup-<timestamp>` };
1326
+ }
1327
+ const backupPath = `${configPath}.backup-${formatBackupTimestamp()}`;
1328
+ await fs4.copyFile(configPath, backupPath);
1329
+ const mcpServers = typeof parsed.mcpServers === "object" && parsed.mcpServers !== null && !Array.isArray(parsed.mcpServers) ? { ...parsed.mcpServers } : {};
1330
+ delete mcpServers.memoraone;
1331
+ const hasOtherServers = Object.keys(mcpServers).length > 0;
1332
+ if (!hasOtherServers) {
1333
+ await fs4.unlink(configPath);
1334
+ return { changed: true, backupPath };
1335
+ }
1336
+ const next = { ...parsed, mcpServers };
1337
+ await fs4.mkdir(path6.dirname(configPath), { recursive: true });
1338
+ await fs4.writeFile(configPath, JSON.stringify(next, null, 2) + "\n", "utf8");
1339
+ return { changed: true, backupPath };
1340
+ }
1341
+ async function auditCursorMcpConfig(options) {
1342
+ const repoRoot = path6.resolve(options?.repoRoot ?? process.cwd());
1343
+ const repoConfigPath = getCursorRepoMcpConfigPath(repoRoot);
1344
+ const globalDetection = await detectCursorGlobalMcpConfig({
1345
+ homeDir: options?.homeDir,
1346
+ explicitPath: options?.explicitGlobalPath
1347
+ });
1348
+ const globalConfigPath = globalDetection.ok ? globalDetection.path : getKnownCursorGlobalMcpConfigCandidates(
1349
+ options?.homeDir ?? os2.homedir()
1350
+ )[0];
1351
+ let repoHasManagedMemoraone = false;
1352
+ try {
1353
+ const repoParsed = await readCursorMcpConfigObject(repoConfigPath);
1354
+ repoHasManagedMemoraone = cursorConfigHasManagedMemoraone(repoParsed);
1355
+ } catch {
1356
+ repoHasManagedMemoraone = false;
1357
+ }
1358
+ let globalHasManagedMemoraone = false;
1359
+ if (globalDetection.ok) {
1360
+ try {
1361
+ const globalParsed = await readCursorMcpConfigObject(globalConfigPath);
1362
+ globalHasManagedMemoraone = cursorConfigHasManagedMemoraone(globalParsed);
1363
+ } catch {
1364
+ globalHasManagedMemoraone = false;
1365
+ }
1366
+ }
1367
+ return {
1368
+ repoConfigPath,
1369
+ repoHasManagedMemoraone,
1370
+ globalConfigPath,
1371
+ globalHasManagedMemoraone,
1372
+ conflict: repoHasManagedMemoraone && globalHasManagedMemoraone
1373
+ };
1374
+ }
1375
+ function logCursorMcpConfigAudit(prefix, audit) {
1376
+ console.log(`${prefix} Cursor MCP config audit:`);
1377
+ console.log(
1378
+ `${prefix} repo ${audit.repoConfigPath}: managed memoraone=${audit.repoHasManagedMemoraone}`
1379
+ );
1380
+ console.log(
1381
+ `${prefix} global ${audit.globalConfigPath}: managed memoraone=${audit.globalHasManagedMemoraone}`
1382
+ );
1383
+ if (audit.conflict) {
1384
+ console.warn(
1385
+ `${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.`
1386
+ );
1387
+ } else if (audit.globalHasManagedMemoraone && !audit.repoHasManagedMemoraone) {
1388
+ console.warn(
1389
+ `${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.`
1390
+ );
1391
+ } else if (audit.repoHasManagedMemoraone && !audit.globalHasManagedMemoraone) {
1392
+ console.log(
1393
+ `${prefix} Cursor MCP is repo-scoped (.cursor/mcp.json) with no global memoraone entry (recommended for multi-repo windows).`
1394
+ );
1395
+ }
1396
+ }
1397
+ function logCursorMcpCliSummary(info, dryRun) {
1398
+ const { repoConfigPath, repoOutcome, npxPath, repoBackupPath, globalConfigPath, globalMemoraoneRemoved, globalBackupPath } = info;
1399
+ console.log(`[setup-ide-files] Cursor repo MCP config: ${repoConfigPath}`);
1400
+ console.log(`[setup-ide-files] Resolved npx: ${npxPath}`);
1401
+ if (repoBackupPath) {
1402
+ console.log(`[setup-ide-files] Cursor repo MCP config backup: ${repoBackupPath}`);
1403
+ }
1404
+ if (repoOutcome === "created") {
1405
+ console.log(
1406
+ dryRun ? `[setup-ide-files] Cursor repo MCP config would be created: ${repoConfigPath}` : `[setup-ide-files] Cursor repo MCP config created: ${repoConfigPath}`
1407
+ );
1408
+ } else if (repoOutcome === "updated") {
1409
+ console.log(
1410
+ dryRun ? `[setup-ide-files] Cursor repo MCP config would be updated: ${repoConfigPath}` : `[setup-ide-files] Cursor repo MCP config updated: ${repoConfigPath}`
1411
+ );
1412
+ } else if (repoOutcome === "skipped") {
1413
+ console.log(`[setup-ide-files] Cursor repo MCP config unchanged: ${repoConfigPath}`);
1414
+ }
1415
+ if (globalMemoraoneRemoved && globalConfigPath) {
1416
+ console.log(
1417
+ dryRun ? `[setup-ide-files] Would remove memoraone from Cursor global MCP config: ${globalConfigPath}` : `[setup-ide-files] Removed memoraone from Cursor global MCP config: ${globalConfigPath}`
1418
+ );
1419
+ if (globalBackupPath) {
1420
+ console.log(`[setup-ide-files] Cursor global MCP config backup: ${globalBackupPath}`);
1421
+ }
1422
+ } else if (globalConfigPath) {
1423
+ console.log(
1424
+ `[setup-ide-files] Cursor global MCP config unchanged (no managed memoraone to remove): ${globalConfigPath}`
1425
+ );
1426
+ }
1427
+ console.log(
1428
+ "[setup-ide-files] Each Cursor window uses this repo\u2019s .cursor/mcp.json (separate MCP process per repo)."
1429
+ );
1430
+ console.log(
1431
+ "[setup-ide-files] Fully quit Cursor and reopen this repo for MCP changes to take effect."
1432
+ );
1433
+ }
1434
+
1435
+ // src/cleanup.ts
1436
+ var execFileAsync2 = (0, import_node_util2.promisify)(import_node_child_process3.execFile);
1437
+ 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;
1438
+ 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;
1439
+ var MEMORAONE_MCP_COMMAND_RE = /memoraone-mcp|memoraOne-mcp|@memoraone\/mcp/;
1440
+ var CLEANUP_PROJECT_ID_REQUIRED_ERROR = "Provide --project-id <id> or run from a folder containing memoraone.m1.";
1441
+ function isMemoraoneMcpCommandLine(commandLine) {
1442
+ return MEMORAONE_MCP_COMMAND_RE.test(commandLine);
1443
+ }
1444
+ function parseDaemonProjectIdFromCommandLine(commandLine) {
1445
+ if (!commandLine.includes("--daemon")) {
1446
+ return null;
1447
+ }
1448
+ const match = commandLine.match(DAEMON_PROJECT_ID_RE);
1449
+ return match ? match[1].toLowerCase() : null;
1450
+ }
1451
+ function parseDaemonIdeFromCommandLine(commandLine) {
1452
+ if (!commandLine.includes("--daemon")) {
1453
+ return void 0;
1454
+ }
1455
+ return parseIdeTypeFromCommandLine(commandLine);
1456
+ }
1457
+ function parseDaemonProcessLines(lines) {
1458
+ const processes = [];
1459
+ for (const line of lines) {
1460
+ const trimmed = line.trim();
1461
+ if (!trimmed) continue;
1462
+ const spaceIdx = trimmed.indexOf(" ");
1463
+ if (spaceIdx <= 0) continue;
1464
+ const pid = Number.parseInt(trimmed.slice(0, spaceIdx), 10);
1465
+ if (!Number.isFinite(pid) || pid <= 0) continue;
1466
+ const command = trimmed.slice(spaceIdx + 1);
1467
+ if (!isMemoraoneMcpCommandLine(command)) continue;
1468
+ const projectId = parseDaemonProjectIdFromCommandLine(command);
1469
+ if (projectId === null) continue;
1470
+ const ide = parseDaemonIdeFromCommandLine(command);
1471
+ processes.push({ pid, command, projectId, ...ide !== void 0 ? { ide } : {} });
1472
+ }
1473
+ return processes;
1474
+ }
1475
+ function normalizeCleanupProjectId(projectId) {
1476
+ const trimmed = projectId.trim();
1477
+ if (!PROJECT_ID_RE.test(trimmed)) {
1478
+ return { error: `Invalid project id: ${projectId}` };
1479
+ }
1480
+ return trimmed.toLowerCase();
1481
+ }
1482
+ async function defaultListDaemonProcesses() {
1483
+ const { stdout } = await execFileAsync2("ps", ["-eo", "pid=,args="], {
1484
+ maxBuffer: 10 * 1024 * 1024
1485
+ });
1486
+ return parseDaemonProcessLines(stdout.split("\n"));
1487
+ }
1488
+ async function defaultListSocketPaths(projectId) {
1489
+ const baseDir = getMcpBaseDir();
1490
+ let entries;
1491
+ try {
1492
+ entries = await fs5.readdir(baseDir);
1493
+ } catch (err) {
1494
+ const code = err && typeof err === "object" && "code" in err ? err.code : void 0;
1495
+ if (code === "ENOENT") {
1496
+ return [];
1497
+ }
1498
+ throw err;
1499
+ }
1500
+ const paths = [];
1501
+ for (const name of entries) {
1502
+ if (!name.endsWith(".sock") || !isMemoraoneSocketFilename(name)) {
1503
+ continue;
1504
+ }
1505
+ const socketPath = path7.join(baseDir, name);
1506
+ if (projectId === null) {
1507
+ paths.push(socketPath);
1508
+ continue;
1509
+ }
1510
+ const normalizedProjectId = projectId.trim().toLowerCase();
1511
+ if (isLegacySocketFilename(name) && isSocketFilenameForProject(name, normalizedProjectId)) {
1512
+ paths.push(socketPath);
1513
+ continue;
1514
+ }
1515
+ if (isHashSocketFilename(name)) {
1516
+ const record = readBindingSidecarRecord(socketPath);
1517
+ if (record?.projectId.trim().toLowerCase() === normalizedProjectId) {
1518
+ paths.push(socketPath);
1519
+ }
1520
+ }
1521
+ }
1522
+ return paths.sort();
1523
+ }
1524
+ async function filterSocketPathsByIde(socketPaths, projectId, ide) {
1525
+ if (ide === void 0) return socketPaths;
1526
+ const normalizedProjectId = projectId.trim().toLowerCase();
1527
+ const filtered = [];
1528
+ for (const socketPath of socketPaths) {
1529
+ const basename4 = path7.basename(socketPath);
1530
+ if (isLegacySocketFilename(basename4)) {
1531
+ if (isSocketFilenameForProjectAndIde(basename4, normalizedProjectId, ide)) {
1532
+ filtered.push(socketPath);
1533
+ }
1534
+ continue;
1535
+ }
1536
+ if (isHashSocketFilename(basename4)) {
1537
+ const record = readBindingSidecarRecord(socketPath);
1538
+ if (record?.projectId.trim().toLowerCase() === normalizedProjectId && record.ideType === ide) {
1539
+ filtered.push(socketPath);
1540
+ }
1541
+ }
1542
+ }
1543
+ return filtered;
1544
+ }
1545
+ async function defaultKillProcess(pid) {
1546
+ process.kill(pid, "SIGTERM");
1547
+ }
1548
+ async function defaultRemoveSocket(socketPath) {
1549
+ await fs5.unlink(socketPath);
1550
+ try {
1551
+ await fs5.unlink(bindingSidecarPath(socketPath));
1552
+ } catch {
1553
+ }
1554
+ }
1555
+ async function defaultConfirm(message) {
1556
+ if (!import_node_process.stdin.isTTY) {
1557
+ return false;
1558
+ }
1559
+ const rl = readline3.createInterface({ input: import_node_process.stdin, output: import_node_process.stdout });
1560
+ try {
1561
+ const answer = await rl.question(`${message} [y/N] `);
1562
+ return /^y(es)?$/i.test(answer.trim());
1563
+ } finally {
1564
+ rl.close();
1565
+ }
1566
+ }
1567
+ async function resolveCleanupTarget(cwd) {
1568
+ try {
1569
+ const binding = await resolveAuthoritativeBinding([path7.resolve(cwd)]);
1570
+ return {
1571
+ workspaceRoot: binding.workspaceRoot,
1572
+ m1Path: binding.m1Path,
1573
+ projectId: binding.projectId
1574
+ };
1575
+ } catch {
1576
+ return { error: CLEANUP_PROJECT_ID_REQUIRED_ERROR };
1577
+ }
1578
+ }
1579
+ function filterProcessesForScope(processes, projectId) {
1580
+ if (projectId === null) {
1581
+ return { matching: processes, skipped: [] };
1582
+ }
1583
+ const normalized = projectId.toLowerCase();
1584
+ const matching = [];
1585
+ const skipped = [];
1586
+ for (const proc of processes) {
1587
+ if (proc.projectId === normalized) {
1588
+ matching.push(proc);
1589
+ } else {
1590
+ skipped.push(proc);
1591
+ }
1592
+ }
1593
+ return { matching, skipped };
433
1594
  }
434
1595
  function logPrefix(dryRun) {
435
1596
  return dryRun ? "[cleanup][dry-run]" : "[cleanup]";
436
1597
  }
437
- function logReconnectNotice(prefix, ide) {
1598
+ function logReconnectNotice(opts, prefix, ide) {
1599
+ if (opts.quiet) return;
438
1600
  if (ide) {
439
1601
  console.log(
440
1602
  `${prefix} Note: Valid ${ide} connections for this project may disconnect temporarily; they should reconnect automatically. Stale connections will remain cleared.`
@@ -445,6 +1607,16 @@ function logReconnectNotice(prefix, ide) {
445
1607
  );
446
1608
  }
447
1609
  }
1610
+ function cleanupLog(opts, message) {
1611
+ if (!opts.quiet) {
1612
+ console.log(message);
1613
+ }
1614
+ }
1615
+ function cleanupWarn(opts, message) {
1616
+ if (!opts.quiet) {
1617
+ console.warn(message);
1618
+ }
1619
+ }
448
1620
  async function runCleanup(opts) {
449
1621
  const listProcesses = opts.listProcesses ?? defaultListDaemonProcesses;
450
1622
  const listSocketPaths = opts.listSocketPaths ?? defaultListSocketPaths;
@@ -465,8 +1637,9 @@ async function runCleanup(opts) {
465
1637
  error: "Cannot combine --all-projects with --project-id."
466
1638
  };
467
1639
  }
468
- console.log(`${prefix} Mode: all projects (--all-projects)`);
469
- console.warn(
1640
+ cleanupLog(opts, `${prefix} Mode: all projects (--all-projects)`);
1641
+ cleanupWarn(
1642
+ opts,
470
1643
  `${prefix} WARNING: This stops every MemoraOne MCP daemon and removes all project sockets under ${getMcpBaseDir()}.`
471
1644
  );
472
1645
  } else if (opts.projectId) {
@@ -475,9 +1648,9 @@ async function runCleanup(opts) {
475
1648
  return { exitCode: 1, killedPids: [], removedSockets: [], skippedProcesses: [], error: normalized.error };
476
1649
  }
477
1650
  targetProjectId = normalized;
478
- console.log(`${prefix} Project id: ${targetProjectId}`);
1651
+ cleanupLog(opts, `${prefix} Project id: ${targetProjectId}`);
479
1652
  if (opts.ide) {
480
- console.log(`${prefix} IDE filter: ${opts.ide}`);
1653
+ cleanupLog(opts, `${prefix} IDE filter: ${opts.ide}`);
481
1654
  }
482
1655
  } else {
483
1656
  const target = await resolveCleanupTarget(opts.cwd);
@@ -487,15 +1660,23 @@ async function runCleanup(opts) {
487
1660
  targetProjectId = target.projectId;
488
1661
  workspaceRoot = target.workspaceRoot;
489
1662
  m1Path = target.m1Path;
490
- console.log(`${prefix} Workspace root: ${workspaceRoot}`);
491
- console.log(`${prefix} memoraone.m1: ${m1Path}`);
492
- console.log(`${prefix} Project id: ${targetProjectId}`);
1663
+ cleanupLog(opts, `${prefix} Workspace root: ${workspaceRoot}`);
1664
+ cleanupLog(opts, `${prefix} memoraone.m1: ${m1Path}`);
1665
+ cleanupLog(opts, `${prefix} Project id: ${targetProjectId}`);
493
1666
  if (opts.ide) {
494
- console.log(`${prefix} IDE filter: ${opts.ide}`);
1667
+ cleanupLog(opts, `${prefix} IDE filter: ${opts.ide}`);
1668
+ }
1669
+ if (workspaceRoot && (!opts.ide || opts.ide === "cursor")) {
1670
+ try {
1671
+ const cursorAudit = await auditCursorMcpConfig({ repoRoot: workspaceRoot });
1672
+ logCursorMcpConfigAudit(prefix, cursorAudit);
1673
+ } catch (err) {
1674
+ cleanupWarn(opts, `${prefix} Cursor MCP config audit failed: ${String(err)}`);
1675
+ }
495
1676
  }
496
1677
  }
497
1678
  if (targetProjectId !== null || opts.ide) {
498
- logReconnectNotice(prefix, opts.ide);
1679
+ logReconnectNotice(opts, prefix, opts.ide);
499
1680
  }
500
1681
  const allDaemonProcesses = await listProcesses();
501
1682
  const { matching: projectProcesses, skipped: skippedProcesses } = filterProcessesForScope(
@@ -511,392 +1692,198 @@ async function runCleanup(opts) {
511
1692
  processesToStop.push(proc);
512
1693
  } else if (proc.ide === void 0) {
513
1694
  ideSkippedProcesses.push(proc);
514
- console.log(
1695
+ cleanupLog(
1696
+ opts,
515
1697
  `${prefix} Skipped daemon pid=${proc.pid} because IDE could not be safely determined.`
516
1698
  );
517
1699
  } else {
518
1700
  ideSkippedProcesses.push(proc);
519
- console.log(
1701
+ cleanupLog(
1702
+ opts,
520
1703
  `${prefix} Skipped daemon pid=${proc.pid} (IDE ${proc.ide} does not match filter ${opts.ide}).`
521
1704
  );
522
1705
  }
523
1706
  }
524
1707
  }
525
1708
  const allSocketPaths = await listSocketPaths(targetProjectId);
526
- const socketPaths = targetProjectId === null ? allSocketPaths : filterSocketPathsByIde(allSocketPaths, targetProjectId, opts.ide);
1709
+ const socketPaths = targetProjectId === null ? allSocketPaths : await filterSocketPathsByIde(allSocketPaths, targetProjectId, opts.ide);
527
1710
  if (opts.allProjects) {
528
1711
  const projectIds = /* @__PURE__ */ new Set();
529
1712
  for (const proc of processesToStop) {
530
1713
  projectIds.add(proc.projectId);
531
1714
  }
532
1715
  for (const socketPath of socketPaths) {
533
- const id = extractProjectIdFromSocketFilename(path3.basename(socketPath));
534
- if (id) projectIds.add(id);
1716
+ const id = extractProjectIdFromSocketFilename(path7.basename(socketPath));
1717
+ if (id) {
1718
+ projectIds.add(id);
1719
+ continue;
1720
+ }
1721
+ const record = readBindingSidecarRecord(socketPath);
1722
+ if (record) {
1723
+ projectIds.add(record.projectId.trim().toLowerCase());
1724
+ }
535
1725
  }
536
- console.log(`${prefix} Projects affected: ${projectIds.size ? [...projectIds].sort().join(", ") : "(none found)"}`);
1726
+ cleanupLog(
1727
+ opts,
1728
+ `${prefix} Projects affected: ${projectIds.size ? [...projectIds].sort().join(", ") : "(none found)"}`
1729
+ );
537
1730
  }
538
1731
  if (processesToStop.length) {
539
- console.log(`${prefix} Daemon processes to stop:`);
1732
+ cleanupLog(opts, `${prefix} Daemon processes to stop:`);
540
1733
  for (const proc of processesToStop) {
541
1734
  const ideLabel = proc.ide ? ` ide=${proc.ide}` : "";
542
- console.log(`${prefix} pid=${proc.pid} project=${proc.projectId}${ideLabel}`);
543
- }
544
- } else if (ideSkippedProcesses.length) {
545
- console.log(`${prefix} No matching daemon processes for IDE filter ${opts.ide}.`);
546
- } else {
547
- console.log(`${prefix} No matching daemon processes found.`);
548
- }
549
- if (socketPaths.length) {
550
- console.log(`${prefix} Sockets to remove:`);
551
- for (const socketPath of socketPaths) {
552
- console.log(`${prefix} ${socketPath}`);
553
- }
554
- } else {
555
- console.log(`${prefix} No matching sockets found under ${getMcpBaseDir()}.`);
556
- }
557
- if (skippedProcesses.length) {
558
- console.log(`${prefix} Skipped unrelated daemon processes:`);
559
- for (const proc of skippedProcesses) {
560
- console.log(`${prefix} pid=${proc.pid} project=${proc.projectId}`);
561
- }
562
- }
563
- if (opts.allProjects && !opts.dryRun) {
564
- const ok = opts.assumeYes ? true : await confirm(`${prefix} Proceed with cleanup for ALL projects?`);
565
- if (!ok) {
566
- console.log(`${prefix} Aborted.`);
567
- return {
568
- exitCode: 1,
569
- workspaceRoot,
570
- m1Path,
571
- projectId: targetProjectId ?? void 0,
572
- killedPids: [],
573
- removedSockets: [],
574
- skippedProcesses: [...skippedProcesses, ...ideSkippedProcesses],
575
- error: opts.assumeYes ? void 0 : "Aborted (--all requires --yes in non-interactive mode)"
576
- };
577
- }
578
- }
579
- const killedPids = [];
580
- const removedSockets = [];
581
- if (opts.dryRun) {
582
- console.log(`${prefix} Dry run complete \u2014 no processes stopped, no sockets removed.`);
583
- return {
584
- exitCode: 0,
585
- workspaceRoot,
586
- m1Path,
587
- projectId: targetProjectId ?? void 0,
588
- killedPids: processesToStop.map((p) => p.pid),
589
- removedSockets: socketPaths,
590
- skippedProcesses: [...skippedProcesses, ...ideSkippedProcesses]
591
- };
592
- }
593
- for (const proc of processesToStop) {
594
- try {
595
- await killProcess(proc.pid);
596
- killedPids.push(proc.pid);
597
- console.log(`${prefix} Stopped daemon pid=${proc.pid} project=${proc.projectId}`);
598
- } catch (err) {
599
- console.warn(`${prefix} Could not stop pid=${proc.pid}: ${String(err)}`);
600
- }
601
- }
602
- if (killedPids.length) {
603
- await new Promise((r) => setTimeout(r, 300));
604
- }
605
- for (const socketPath of socketPaths) {
606
- try {
607
- await removeSocket(socketPath);
608
- removedSockets.push(socketPath);
609
- console.log(`${prefix} Removed socket ${socketPath}`);
610
- } catch (err) {
611
- const code = err && typeof err === "object" && "code" in err ? err.code : void 0;
612
- if (code !== "ENOENT") {
613
- console.warn(`${prefix} Could not remove socket ${socketPath}: ${String(err)}`);
614
- }
615
- }
616
- }
617
- console.log(`${prefix} Done. stopped=${killedPids.length} socketsRemoved=${removedSockets.length}`);
618
- return {
619
- exitCode: 0,
620
- workspaceRoot,
621
- m1Path,
622
- projectId: targetProjectId ?? void 0,
623
- killedPids,
624
- removedSockets,
625
- skippedProcesses: [...skippedProcesses, ...ideSkippedProcesses]
626
- };
627
- }
628
- function parseCleanupFlags(argv) {
629
- let dryRun = false;
630
- let allProjects = false;
631
- let assumeYes = false;
632
- let projectId;
633
- let ide;
634
- let invalidIde;
635
- const unknown = [];
636
- for (let i = 0; i < argv.length; i++) {
637
- const arg = argv[i];
638
- if (arg === "--dry-run") dryRun = true;
639
- else if (arg === "--all-projects" || arg === "--all") allProjects = true;
640
- else if (arg === "--yes" || arg === "-y") assumeYes = true;
641
- else if (arg === "--project-id") {
642
- if (i + 1 >= argv.length) {
643
- unknown.push("--project-id (missing value)");
644
- } else {
645
- projectId = argv[++i];
646
- }
647
- } else if (arg === "--ide") {
648
- if (i + 1 >= argv.length) {
649
- unknown.push("--ide (missing value)");
650
- } else {
651
- const value = argv[++i];
652
- if (IDE_TYPES.includes(value)) {
653
- ide = value;
654
- } else {
655
- invalidIde = value;
656
- }
657
- }
658
- } else if (arg.startsWith("-")) unknown.push(arg);
659
- else unknown.push(arg);
660
- }
661
- return { dryRun, allProjects, assumeYes, projectId, ide, invalidIde, unknown };
662
- }
663
- async function cliCleanup(argv) {
664
- const { dryRun, allProjects, assumeYes, projectId, ide, invalidIde, unknown } = parseCleanupFlags(argv);
665
- if (invalidIde) {
666
- console.error(
667
- `[cleanup] Invalid --ide value: ${invalidIde}. Expected one of: ${IDE_TYPES.join(", ")}.`
668
- );
669
- return 1;
670
- }
671
- if (unknown.length) {
672
- console.error(`[cleanup] Unknown option(s): ${unknown.join(", ")}`);
673
- return 1;
674
- }
675
- const result = await runCleanup({
676
- cwd: process.cwd(),
677
- dryRun,
678
- allProjects,
679
- assumeYes,
680
- projectId,
681
- ide
682
- });
683
- if (result.error) {
684
- console.error(`[cleanup] ${result.error}`);
685
- }
686
- return result.exitCode;
687
- }
688
-
689
- // src/cursorGlobalMcpConfig.ts
690
- var fs4 = __toESM(require("fs/promises"), 1);
691
- var os2 = __toESM(require("os"), 1);
692
- var path4 = __toESM(require("path"), 1);
693
- var import_node_child_process2 = require("child_process");
694
- var import_node_util2 = require("util");
695
- var execFileAsync2 = (0, import_node_util2.promisify)(import_node_child_process2.execFile);
696
- function buildMemoraoneCursorMcpServer(npxPath) {
697
- return {
698
- command: npxPath,
699
- args: ["-y", "@memoraone/mcp@latest"],
700
- env: {
701
- MEMORAONE_API_URL: "https://api.memoraone.com",
702
- MEMORAONE_IDE_TYPE: "cursor"
703
- }
704
- };
705
- }
706
- async function pathExists(filePath) {
707
- try {
708
- await fs4.access(filePath);
709
- return true;
710
- } catch {
711
- return false;
712
- }
713
- }
714
- function stripLeadingLineComments(text) {
715
- return text.split("\n").filter((line) => !/^\s*\/\//.test(line)).join("\n");
716
- }
717
- function getKnownCursorGlobalMcpConfigCandidates(homeDir) {
718
- return [path4.join(homeDir, ".cursor", "mcp.json")];
719
- }
720
- async function detectCursorGlobalMcpConfig(options) {
721
- if (options?.explicitPath) {
722
- return { ok: true, path: options.explicitPath, detectedExisting: await pathExists(options.explicitPath) };
723
- }
724
- const homeDir = options?.homeDir ?? os2.homedir();
725
- const candidates = getKnownCursorGlobalMcpConfigCandidates(homeDir);
726
- const existing = [];
727
- for (const candidate of candidates) {
728
- if (await pathExists(candidate)) existing.push(candidate);
729
- }
730
- if (existing.length > 1) {
731
- return {
732
- ok: false,
733
- error: "[setup-ide-files] Multiple Cursor global MCP config paths found. Specify one explicitly.",
734
- candidates: existing
735
- };
736
- }
737
- if (existing.length === 1) {
738
- return { ok: true, path: existing[0], detectedExisting: true };
739
- }
740
- const defaultPath = candidates[0];
741
- if (!defaultPath) {
742
- return {
743
- ok: false,
744
- error: "[setup-ide-files] No known Cursor global MCP config path.",
745
- candidates: []
746
- };
747
- }
748
- return { ok: true, path: defaultPath, detectedExisting: false };
749
- }
750
- function formatBackupTimestamp(d = /* @__PURE__ */ new Date()) {
751
- const pad = (n) => String(n).padStart(2, "0");
752
- return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
753
- }
754
- async function isWorkingNpx(npxPath) {
755
- try {
756
- if (!await pathExists(npxPath)) return false;
757
- if (process.platform !== "win32") {
758
- try {
759
- await fs4.access(npxPath, fs4.constants.X_OK);
760
- } catch {
761
- return false;
762
- }
763
- }
764
- await execFileAsync2(npxPath, ["--version"], { timeout: 1e4 });
765
- return true;
766
- } catch {
767
- return false;
768
- }
769
- }
770
- async function resolveNpxPath() {
771
- const npxName = process.platform === "win32" ? "npx.cmd" : "npx";
772
- const candidates = [];
773
- if (process.platform === "darwin") {
774
- candidates.push("/opt/homebrew/bin/npx", "/usr/local/bin/npx");
775
- } else if (process.platform === "linux") {
776
- candidates.push("/usr/local/bin/npx");
777
- }
778
- const pathSep = process.platform === "win32" ? ";" : ":";
779
- for (const dir of (process.env.PATH ?? "").split(pathSep)) {
780
- if (!dir) continue;
781
- candidates.push(path4.join(dir, npxName));
782
- }
783
- try {
784
- const lookupCmd = process.platform === "win32" ? "where" : "which";
785
- const { stdout } = await execFileAsync2(lookupCmd, [npxName], { timeout: 5e3 });
786
- const first = stdout.trim().split(/\r?\n/).map((line) => line.trim()).find(Boolean);
787
- if (first) candidates.unshift(first);
788
- } catch {
789
- }
790
- const seen = /* @__PURE__ */ new Set();
791
- for (const candidate of candidates) {
792
- const abs = path4.isAbsolute(candidate) ? candidate : path4.resolve(candidate);
793
- const key = process.platform === "win32" ? abs.toLowerCase() : abs;
794
- if (seen.has(key)) continue;
795
- seen.add(key);
796
- if (await isWorkingNpx(abs)) return abs;
797
- }
798
- return null;
799
- }
800
- function mergeCursorGlobalMcpConfigObject(existing, npxPath) {
801
- const base = existing && typeof existing === "object" ? { ...existing } : { mcpServers: {} };
802
- const mcpServers = typeof base.mcpServers === "object" && base.mcpServers !== null && !Array.isArray(base.mcpServers) ? { ...base.mcpServers } : {};
803
- mcpServers.memoraone = buildMemoraoneCursorMcpServer(npxPath);
804
- return { ...base, mcpServers };
805
- }
806
- function memoraoneServerMatches(server, npxPath) {
807
- if (!server || typeof server !== "object") return false;
808
- const s = server;
809
- if (s.command !== npxPath) return false;
810
- if (!Array.isArray(s.args) || s.args.length !== 2) return false;
811
- if (s.args[0] !== "-y" || s.args[1] !== "@memoraone/mcp@latest") return false;
812
- const env = s.env;
813
- if (!env || typeof env !== "object") return false;
814
- const e = env;
815
- return e.MEMORAONE_API_URL === "https://api.memoraone.com" && e.MEMORAONE_IDE_TYPE === "cursor";
816
- }
817
- function validateCursorGlobalMcpConfig(parsed, npxPath) {
818
- if (!parsed || typeof parsed !== "object") {
819
- throw new Error("[setup-ide-files] Cursor global MCP config must be a JSON object.");
1735
+ cleanupLog(opts, `${prefix} pid=${proc.pid} project=${proc.projectId}${ideLabel}`);
1736
+ }
1737
+ } else if (ideSkippedProcesses.length) {
1738
+ cleanupLog(opts, `${prefix} No matching daemon processes for IDE filter ${opts.ide}.`);
1739
+ } else {
1740
+ cleanupLog(opts, `${prefix} No matching daemon processes found.`);
820
1741
  }
821
- const mcpServers = parsed.mcpServers;
822
- if (!mcpServers || typeof mcpServers !== "object" || Array.isArray(mcpServers)) {
823
- throw new Error("[setup-ide-files] Cursor global MCP config missing mcpServers object.");
1742
+ if (socketPaths.length) {
1743
+ cleanupLog(opts, `${prefix} Sockets to remove:`);
1744
+ for (const socketPath of socketPaths) {
1745
+ cleanupLog(opts, `${prefix} ${socketPath}`);
1746
+ }
1747
+ } else {
1748
+ cleanupLog(opts, `${prefix} No matching sockets found under ${getMcpBaseDir()}.`);
824
1749
  }
825
- const memoraone = mcpServers.memoraone;
826
- if (!memoraoneServerMatches(memoraone, npxPath)) {
827
- throw new Error(
828
- "[setup-ide-files] Cursor global MCP config mcpServers.memoraone is missing or invalid."
829
- );
1750
+ if (skippedProcesses.length) {
1751
+ cleanupLog(opts, `${prefix} Skipped unrelated daemon processes:`);
1752
+ for (const proc of skippedProcesses) {
1753
+ cleanupLog(opts, `${prefix} pid=${proc.pid} project=${proc.projectId}`);
1754
+ }
830
1755
  }
831
- }
832
- async function setupCursorGlobalMcpConfig(options) {
833
- const { configPath, npxPath, dryRun } = options;
834
- const existed = await pathExists(configPath);
835
- let existing = null;
836
- if (existed) {
837
- const raw = await fs4.readFile(configPath, "utf8");
838
- try {
839
- existing = JSON.parse(stripLeadingLineComments(raw));
840
- } catch {
841
- throw new Error(
842
- `[setup-ide-files] Invalid JSON in Cursor global MCP config: ${configPath}`
843
- );
1756
+ if (opts.allProjects && !opts.dryRun) {
1757
+ const ok = opts.assumeYes ? true : await confirm(`${prefix} Proceed with cleanup for ALL projects?`);
1758
+ if (!ok) {
1759
+ cleanupLog(opts, `${prefix} Aborted.`);
1760
+ return {
1761
+ exitCode: 1,
1762
+ workspaceRoot,
1763
+ m1Path,
1764
+ projectId: targetProjectId ?? void 0,
1765
+ killedPids: [],
1766
+ removedSockets: [],
1767
+ skippedProcesses: [...skippedProcesses, ...ideSkippedProcesses],
1768
+ error: opts.assumeYes ? void 0 : "Aborted (--all requires --yes in non-interactive mode)"
1769
+ };
844
1770
  }
845
1771
  }
846
- const merged = mergeCursorGlobalMcpConfigObject(existing, npxPath);
847
- const body = JSON.stringify(merged, null, 2) + "\n";
848
- if (existed) {
849
- const currentMemoraone = existing && typeof existing.mcpServers === "object" && existing.mcpServers !== null && !Array.isArray(existing.mcpServers) ? existing.mcpServers.memoraone : void 0;
850
- if (memoraoneServerMatches(currentMemoraone, npxPath)) {
851
- return { outcome: "skipped" };
1772
+ const killedPids = [];
1773
+ const removedSockets = [];
1774
+ if (opts.dryRun) {
1775
+ cleanupLog(opts, `${prefix} Dry run complete \u2014 no processes stopped, no sockets removed.`);
1776
+ return {
1777
+ exitCode: 0,
1778
+ workspaceRoot,
1779
+ m1Path,
1780
+ projectId: targetProjectId ?? void 0,
1781
+ killedPids: processesToStop.map((p) => p.pid),
1782
+ removedSockets: socketPaths,
1783
+ skippedProcesses: [...skippedProcesses, ...ideSkippedProcesses]
1784
+ };
1785
+ }
1786
+ for (const proc of processesToStop) {
1787
+ try {
1788
+ await killProcess(proc.pid);
1789
+ killedPids.push(proc.pid);
1790
+ cleanupLog(opts, `${prefix} Stopped daemon pid=${proc.pid} project=${proc.projectId}`);
1791
+ } catch (err) {
1792
+ cleanupWarn(opts, `${prefix} Could not stop pid=${proc.pid}: ${String(err)}`);
852
1793
  }
853
1794
  }
854
- if (dryRun) {
855
- return { outcome: existed ? "updated" : "created" };
1795
+ if (killedPids.length) {
1796
+ await new Promise((r) => setTimeout(r, 300));
856
1797
  }
857
- let backupPath;
858
- if (existed) {
859
- backupPath = `${configPath}.backup-${formatBackupTimestamp()}`;
860
- await fs4.copyFile(configPath, backupPath);
861
- }
862
- await fs4.mkdir(path4.dirname(configPath), { recursive: true });
863
- await fs4.writeFile(configPath, body, "utf8");
864
- const verifyRaw = await fs4.readFile(configPath, "utf8");
865
- const verifyParsed = JSON.parse(stripLeadingLineComments(verifyRaw));
866
- validateCursorGlobalMcpConfig(verifyParsed, npxPath);
867
- return { outcome: existed ? "updated" : "created", backupPath };
868
- }
869
- function logCursorGlobalMcpCliSummary(info, dryRun) {
870
- const { configPath, npxPath, backupPath, outcome } = info;
871
- console.log(`[setup-ide-files] Cursor global MCP config: ${configPath}`);
872
- console.log(`[setup-ide-files] Resolved npx: ${npxPath}`);
873
- if (backupPath) {
874
- console.log(`[setup-ide-files] Cursor global MCP config backup: ${backupPath}`);
1798
+ for (const socketPath of socketPaths) {
1799
+ try {
1800
+ await removeSocket(socketPath);
1801
+ removedSockets.push(socketPath);
1802
+ cleanupLog(opts, `${prefix} Removed socket ${socketPath}`);
1803
+ } catch (err) {
1804
+ const code = err && typeof err === "object" && "code" in err ? err.code : void 0;
1805
+ if (code !== "ENOENT") {
1806
+ cleanupWarn(opts, `${prefix} Could not remove socket ${socketPath}: ${String(err)}`);
1807
+ }
1808
+ }
875
1809
  }
876
- if (outcome === "created") {
877
- console.log(
878
- dryRun ? `[setup-ide-files] Cursor global MCP config would be created: ${configPath}` : `[setup-ide-files] Cursor global MCP config created: ${configPath}`
879
- );
880
- } else if (outcome === "updated") {
881
- console.log(
882
- dryRun ? `[setup-ide-files] Cursor global MCP config would be updated: ${configPath}` : `[setup-ide-files] Cursor global MCP config updated: ${configPath}`
1810
+ cleanupLog(opts, `${prefix} Done. stopped=${killedPids.length} socketsRemoved=${removedSockets.length}`);
1811
+ return {
1812
+ exitCode: 0,
1813
+ workspaceRoot,
1814
+ m1Path,
1815
+ projectId: targetProjectId ?? void 0,
1816
+ killedPids,
1817
+ removedSockets,
1818
+ skippedProcesses: [...skippedProcesses, ...ideSkippedProcesses]
1819
+ };
1820
+ }
1821
+ function parseCleanupFlags(argv) {
1822
+ let dryRun = false;
1823
+ let allProjects = false;
1824
+ let assumeYes = false;
1825
+ let projectId;
1826
+ let ide;
1827
+ let invalidIde;
1828
+ const unknown = [];
1829
+ for (let i = 0; i < argv.length; i++) {
1830
+ const arg = argv[i];
1831
+ if (arg === "--dry-run") dryRun = true;
1832
+ else if (arg === "--all-projects" || arg === "--all") allProjects = true;
1833
+ else if (arg === "--yes" || arg === "-y") assumeYes = true;
1834
+ else if (arg === "--project-id") {
1835
+ if (i + 1 >= argv.length) {
1836
+ unknown.push("--project-id (missing value)");
1837
+ } else {
1838
+ projectId = argv[++i];
1839
+ }
1840
+ } else if (arg === "--ide") {
1841
+ if (i + 1 >= argv.length) {
1842
+ unknown.push("--ide (missing value)");
1843
+ } else {
1844
+ const value = argv[++i];
1845
+ if (IDE_TYPES.includes(value)) {
1846
+ ide = value;
1847
+ } else {
1848
+ invalidIde = value;
1849
+ }
1850
+ }
1851
+ } else if (arg.startsWith("-")) unknown.push(arg);
1852
+ else unknown.push(arg);
1853
+ }
1854
+ return { dryRun, allProjects, assumeYes, projectId, ide, invalidIde, unknown };
1855
+ }
1856
+ async function cliCleanup(argv) {
1857
+ const { dryRun, allProjects, assumeYes, projectId, ide, invalidIde, unknown } = parseCleanupFlags(argv);
1858
+ if (invalidIde) {
1859
+ console.error(
1860
+ `[cleanup] Invalid --ide value: ${invalidIde}. Expected one of: ${IDE_TYPES.join(", ")}.`
883
1861
  );
884
- } else if (outcome === "skipped") {
885
- console.log(`[setup-ide-files] Cursor global MCP config unchanged: ${configPath}`);
1862
+ return 1;
886
1863
  }
887
- console.log(
888
- "[setup-ide-files] Repo-level .cursor/mcp.json was not created or modified."
889
- );
890
- console.log(
891
- "[setup-ide-files] Fully quit Cursor and reopen this repo for MCP changes to take effect."
892
- );
1864
+ if (unknown.length) {
1865
+ console.error(`[cleanup] Unknown option(s): ${unknown.join(", ")}`);
1866
+ return 1;
1867
+ }
1868
+ const result = await runCleanup({
1869
+ cwd: process.cwd(),
1870
+ dryRun,
1871
+ allProjects,
1872
+ assumeYes,
1873
+ projectId,
1874
+ ide
1875
+ });
1876
+ if (result.error) {
1877
+ console.error(`[cleanup] ${result.error}`);
1878
+ }
1879
+ return result.exitCode;
893
1880
  }
894
1881
 
895
1882
  // src/jetbrainsMcpConfig.ts
896
- var fs5 = __toESM(require("fs/promises"), 1);
1883
+ var fs6 = __toESM(require("fs/promises"), 1);
897
1884
  var os3 = __toESM(require("os"), 1);
898
- var path5 = __toESM(require("path"), 1);
899
- var import_node_child_process3 = require("child_process");
1885
+ var path8 = __toESM(require("path"), 1);
1886
+ var import_node_child_process4 = require("child_process");
900
1887
 
901
1888
  // src/configUtils.ts
902
1889
  var DEV_API_URL = "http://localhost:3001";
@@ -914,7 +1901,7 @@ function stripLeadingLineComments2(text) {
914
1901
  }
915
1902
  async function pathExists2(filePath) {
916
1903
  try {
917
- await fs5.access(filePath);
1904
+ await fs6.access(filePath);
918
1905
  return true;
919
1906
  } catch {
920
1907
  return false;
@@ -925,12 +1912,12 @@ function formatJetBrainsBackupTimestamp(d = /* @__PURE__ */ new Date()) {
925
1912
  return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
926
1913
  }
927
1914
  function getJetBrainsGlobalMcpConfigPath(homeDir) {
928
- return path5.join(homeDir, ".ai", "mcp", "mcp.json");
1915
+ return path8.join(homeDir, ".ai", "mcp", "mcp.json");
929
1916
  }
930
1917
  function getJetBrainsProjectMcpConfigPaths(repoRoot) {
931
1918
  return [
932
- { kind: "project-ai", path: path5.join(repoRoot, ".ai", "mcp", "mcp.json") },
933
- { kind: "project-ij", path: path5.join(repoRoot, ".ij", "mcp", "mcp.json") }
1919
+ { kind: "project-ai", path: path8.join(repoRoot, ".ai", "mcp", "mcp.json") },
1920
+ { kind: "project-ij", path: path8.join(repoRoot, ".ij", "mcp", "mcp.json") }
934
1921
  ];
935
1922
  }
936
1923
  function getKnownJetBrainsMcpConfigLocations(homeDir, repoRoot) {
@@ -941,7 +1928,7 @@ function getKnownJetBrainsMcpConfigLocations(homeDir, repoRoot) {
941
1928
  }
942
1929
  async function isZeroByteConfigFile(filePath) {
943
1930
  if (!await pathExists2(filePath)) return false;
944
- const stat2 = await fs5.stat(filePath);
1931
+ const stat2 = await fs6.stat(filePath);
945
1932
  return stat2.size === 0;
946
1933
  }
947
1934
  function buildMemoraoneJetBrainsMcpServer(options) {
@@ -965,7 +1952,7 @@ function mergeJetBrainsMcpConfigObject(existing, memoraone) {
965
1952
  mcpServers.memoraone = memoraone;
966
1953
  return { ...base, mcpServers };
967
1954
  }
968
- function memoraoneServerMatches2(server, expected) {
1955
+ function memoraoneServerMatches(server, expected) {
969
1956
  if (!server || typeof server !== "object") return false;
970
1957
  const s = server;
971
1958
  if (s.command !== expected.command) return false;
@@ -993,20 +1980,20 @@ function validateJetBrainsMcpConfig(parsed, expected) {
993
1980
  throw new Error("[setup-ide-files] JetBrains MCP config missing mcpServers object.");
994
1981
  }
995
1982
  const memoraone = mcpServers.memoraone;
996
- if (!memoraoneServerMatches2(memoraone, expected)) {
1983
+ if (!memoraoneServerMatches(memoraone, expected)) {
997
1984
  throw new Error(
998
1985
  "[setup-ide-files] JetBrains MCP config mcpServers.memoraone is missing or invalid."
999
1986
  );
1000
1987
  }
1001
1988
  }
1002
1989
  async function readJsonConfig(filePath) {
1003
- const raw = await fs5.readFile(filePath, "utf8");
1990
+ const raw = await fs6.readFile(filePath, "utf8");
1004
1991
  if (raw.trim() === "") return null;
1005
1992
  return JSON.parse(stripLeadingLineComments2(raw));
1006
1993
  }
1007
1994
  async function backupConfigFile(filePath) {
1008
1995
  const backupPath = `${filePath}.bak-${formatJetBrainsBackupTimestamp()}`;
1009
- await fs5.copyFile(filePath, backupPath);
1996
+ await fs6.copyFile(filePath, backupPath);
1010
1997
  return backupPath;
1011
1998
  }
1012
1999
  async function repairZeroByteConfigFile(filePath, dryRun) {
@@ -1017,7 +2004,7 @@ async function repairZeroByteConfigFile(filePath, dryRun) {
1017
2004
  return { repaired: true, backupPath: `${filePath}.bak-<timestamp>` };
1018
2005
  }
1019
2006
  const backupPath = await backupConfigFile(filePath);
1020
- await fs5.unlink(filePath);
2007
+ await fs6.unlink(filePath);
1021
2008
  return { repaired: true, backupPath };
1022
2009
  }
1023
2010
  function configHasMemoraone(parsed) {
@@ -1048,24 +2035,24 @@ async function removeMemoraoneFromProjectConfig(options) {
1048
2035
  delete mcpServers.memoraone;
1049
2036
  const hasOtherServers = Object.keys(mcpServers).length > 0;
1050
2037
  if (!hasOtherServers) {
1051
- await fs5.unlink(configPath);
2038
+ await fs6.unlink(configPath);
1052
2039
  return { changed: true, backupPath };
1053
2040
  }
1054
2041
  const next = { ...parsed, mcpServers };
1055
- await fs5.mkdir(path5.dirname(configPath), { recursive: true });
1056
- await fs5.writeFile(configPath, JSON.stringify(next, null, 2) + "\n", "utf8");
2042
+ await fs6.mkdir(path8.dirname(configPath), { recursive: true });
2043
+ await fs6.writeFile(configPath, JSON.stringify(next, null, 2) + "\n", "utf8");
1057
2044
  return { changed: true, backupPath };
1058
2045
  }
1059
2046
  async function resolveLocalCliPathAsync() {
1060
- const here = process.argv[1] ? path5.dirname(path5.resolve(process.argv[1])) : process.cwd();
2047
+ const here = process.argv[1] ? path8.dirname(path8.resolve(process.argv[1])) : process.cwd();
1061
2048
  const candidates = [
1062
- path5.join(here, "cli.cjs"),
1063
- path5.join(here, "..", "dist", "cli.cjs"),
1064
- path5.join(here, "..", "..", "dist", "cli.cjs")
2049
+ path8.join(here, "cli.cjs"),
2050
+ path8.join(here, "..", "dist", "cli.cjs"),
2051
+ path8.join(here, "..", "..", "dist", "cli.cjs")
1065
2052
  ];
1066
2053
  for (const candidate of candidates) {
1067
2054
  if (await pathExists2(candidate)) {
1068
- return path5.resolve(candidate);
2055
+ return path8.resolve(candidate);
1069
2056
  }
1070
2057
  }
1071
2058
  return null;
@@ -1107,7 +2094,7 @@ async function buildJetBrainsMemoraoneServer(options) {
1107
2094
  async function verifyJetBrainsMcpHandshake(options) {
1108
2095
  const timeoutMs = options.timeoutMs ?? 15e3;
1109
2096
  const { server } = options;
1110
- return new Promise((resolve7) => {
2097
+ return new Promise((resolve8) => {
1111
2098
  let settled = false;
1112
2099
  const finish = (ok, detail) => {
1113
2100
  if (settled) return;
@@ -1117,9 +2104,9 @@ async function verifyJetBrainsMcpHandshake(options) {
1117
2104
  child.kill();
1118
2105
  } catch {
1119
2106
  }
1120
- resolve7({ ok, detail });
2107
+ resolve8({ ok, detail });
1121
2108
  };
1122
- const child = (0, import_node_child_process3.spawn)(server.command, [...server.args], {
2109
+ const child = (0, import_node_child_process4.spawn)(server.command, [...server.args], {
1123
2110
  env: { ...process.env, ...server.env },
1124
2111
  stdio: ["pipe", "pipe", "pipe"]
1125
2112
  });
@@ -1186,7 +2173,7 @@ async function verifyJetBrainsMcpHandshake(options) {
1186
2173
  async function setupJetBrainsMcpConfig(options) {
1187
2174
  const homeDir = options.homeDir ?? os3.homedir();
1188
2175
  const globalPath = options.globalConfigPath ?? getJetBrainsGlobalMcpConfigPath(homeDir);
1189
- const m1Path = path5.join(path5.resolve(options.repoRoot), "memoraone.m1");
2176
+ const m1Path = path8.join(path8.resolve(options.repoRoot), "memoraone.m1");
1190
2177
  const repairActions = [];
1191
2178
  const allLocations = getKnownJetBrainsMcpConfigLocations(homeDir, options.repoRoot);
1192
2179
  for (const location of allLocations) {
@@ -1241,7 +2228,7 @@ async function setupJetBrainsMcpConfig(options) {
1241
2228
  path: globalPath,
1242
2229
  backupPath: backupPath2
1243
2230
  });
1244
- await fs5.unlink(globalPath);
2231
+ await fs6.unlink(globalPath);
1245
2232
  existing = null;
1246
2233
  }
1247
2234
  }
@@ -1250,7 +2237,7 @@ async function setupJetBrainsMcpConfig(options) {
1250
2237
  const body = JSON.stringify(merged, null, 2) + "\n";
1251
2238
  if (existed && existing) {
1252
2239
  const currentMemoraone = existing.mcpServers && typeof existing.mcpServers === "object" && !Array.isArray(existing.mcpServers) ? existing.mcpServers.memoraone : void 0;
1253
- if (memoraoneServerMatches2(currentMemoraone, memoraone)) {
2240
+ if (memoraoneServerMatches(currentMemoraone, memoraone)) {
1254
2241
  repairActions.push({ type: "wrote-global-config", path: globalPath, outcome: "skipped" });
1255
2242
  return { outcome: "skipped", repairActions, memoraone };
1256
2243
  }
@@ -1264,9 +2251,9 @@ async function setupJetBrainsMcpConfig(options) {
1264
2251
  if (existed) {
1265
2252
  backupPath = await backupConfigFile(globalPath);
1266
2253
  }
1267
- await fs5.mkdir(path5.dirname(globalPath), { recursive: true });
1268
- await fs5.writeFile(globalPath, body, "utf8");
1269
- const verifyRaw = await fs5.readFile(globalPath, "utf8");
2254
+ await fs6.mkdir(path8.dirname(globalPath), { recursive: true });
2255
+ await fs6.writeFile(globalPath, body, "utf8");
2256
+ const verifyRaw = await fs6.readFile(globalPath, "utf8");
1270
2257
  const verifyParsed = JSON.parse(stripLeadingLineComments2(verifyRaw));
1271
2258
  validateJetBrainsMcpConfig(verifyParsed, memoraone);
1272
2259
  const outcome = existed ? "updated" : "created";
@@ -1336,15 +2323,15 @@ function buildMemoraoneMcpServer(ideType, command = "npx") {
1336
2323
  };
1337
2324
  }
1338
2325
  function assertUnderRepoRoot(repoRoot, absPath) {
1339
- const normRoot = path6.resolve(repoRoot) + path6.sep;
1340
- const normPath = path6.resolve(absPath);
1341
- if (normPath !== path6.resolve(repoRoot) && !normPath.startsWith(normRoot)) {
2326
+ const normRoot = path9.resolve(repoRoot) + path9.sep;
2327
+ const normPath = path9.resolve(absPath);
2328
+ if (normPath !== path9.resolve(repoRoot) && !normPath.startsWith(normRoot)) {
1342
2329
  throw new Error(`[setup-ide-files] Refusing to write outside repo root: ${absPath}`);
1343
2330
  }
1344
2331
  }
1345
2332
  async function pathExists3(filePath) {
1346
2333
  try {
1347
- await fs6.access(filePath);
2334
+ await fs7.access(filePath);
1348
2335
  return true;
1349
2336
  } catch {
1350
2337
  return false;
@@ -1365,12 +2352,12 @@ ${GITIGNORE_MEMORAONE_ENTRY}
1365
2352
  }
1366
2353
  async function ensureGitignoreMemoraone(repoRoot, opts) {
1367
2354
  if (opts.noGitignore) return "skipped";
1368
- const abs = path6.join(repoRoot, ".gitignore");
2355
+ const abs = path9.join(repoRoot, ".gitignore");
1369
2356
  assertUnderRepoRoot(repoRoot, abs);
1370
2357
  let prior = "";
1371
2358
  let existed = false;
1372
2359
  try {
1373
- prior = await fs6.readFile(abs, "utf8");
2360
+ prior = await fs7.readFile(abs, "utf8");
1374
2361
  existed = true;
1375
2362
  } catch (err) {
1376
2363
  const code = err && typeof err === "object" && "code" in err ? err.code : void 0;
@@ -1381,22 +2368,22 @@ async function ensureGitignoreMemoraone(repoRoot, opts) {
1381
2368
  const separator = existed && prior.length > 0 ? prior.endsWith("\n") ? "\n" : "\n\n" : "";
1382
2369
  const next = (existed ? prior : "") + separator + block;
1383
2370
  if (opts.dryRun) return existed ? "updated" : "created";
1384
- await fs6.writeFile(abs, next, "utf8");
2371
+ await fs7.writeFile(abs, next, "utf8");
1385
2372
  return existed ? "updated" : "created";
1386
2373
  }
1387
2374
  async function findRepoRoot(startDir) {
1388
- let current = path6.resolve(startDir);
1389
- const root = path6.parse(current).root;
2375
+ let current = path9.resolve(startDir);
2376
+ const root = path9.parse(current).root;
1390
2377
  while (true) {
1391
- const gitPath = path6.join(current, ".git");
1392
- const m1Path = path6.join(current, "memoraone.m1");
2378
+ const gitPath = path9.join(current, ".git");
2379
+ const m1Path = path9.join(current, "memoraone.m1");
1393
2380
  if (await pathExists3(gitPath) || await pathExists3(m1Path)) {
1394
2381
  return current;
1395
2382
  }
1396
2383
  if (current === root) {
1397
2384
  return null;
1398
2385
  }
1399
- current = path6.dirname(current);
2386
+ current = path9.dirname(current);
1400
2387
  }
1401
2388
  }
1402
2389
  function stripLeadingLineComments3(text) {
@@ -1407,7 +2394,7 @@ function cursorRuleBody() {
1407
2394
 
1408
2395
  ## MemoraOne MCP (IDE agent only)
1409
2396
 
1410
- 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.
2397
+ 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.
1411
2398
 
1412
2399
  ### Tools
1413
2400
 
@@ -1449,43 +2436,47 @@ function buildVscodeMcpJsonBody(existing) {
1449
2436
  const merged = { ...base, servers };
1450
2437
  return mcpJsonHeader() + JSON.stringify(merged, null, 2) + "\n";
1451
2438
  }
2439
+ function buildCursorMcpJsonBody(existing, npxPath, repoRoot) {
2440
+ const merged = mergeCursorRepoMcpConfigObject(existing, npxPath, repoRoot);
2441
+ return mcpJsonHeader() + JSON.stringify(merged, null, 2) + "\n";
2442
+ }
1452
2443
  async function writeManagedMarkdown(repoRoot, relPath, fullContent, opts) {
1453
- const abs = path6.join(repoRoot, relPath);
2444
+ const abs = path9.join(repoRoot, relPath);
1454
2445
  assertUnderRepoRoot(repoRoot, abs);
1455
2446
  let prior = "";
1456
2447
  let existed = false;
1457
2448
  try {
1458
- prior = await fs6.readFile(abs, "utf8");
2449
+ prior = await fs7.readFile(abs, "utf8");
1459
2450
  existed = true;
1460
2451
  } catch (err) {
1461
2452
  if (err?.code !== "ENOENT") throw err;
1462
2453
  }
1463
2454
  if (!existed) {
1464
2455
  if (opts.dryRun) return "created";
1465
- await fs6.mkdir(path6.dirname(abs), { recursive: true });
1466
- await fs6.writeFile(abs, fullContent, "utf8");
2456
+ await fs7.mkdir(path9.dirname(abs), { recursive: true });
2457
+ await fs7.writeFile(abs, fullContent, "utf8");
1467
2458
  return "created";
1468
2459
  }
1469
2460
  if (prior.includes(MANAGED_MARKER)) {
1470
2461
  if (prior === fullContent) return "skipped";
1471
2462
  if (opts.dryRun) return "updated";
1472
- await fs6.mkdir(path6.dirname(abs), { recursive: true });
1473
- await fs6.writeFile(abs, fullContent, "utf8");
2463
+ await fs7.mkdir(path9.dirname(abs), { recursive: true });
2464
+ await fs7.writeFile(abs, fullContent, "utf8");
1474
2465
  return "updated";
1475
2466
  }
1476
2467
  if (!opts.force) return "skipped-untracked";
1477
2468
  if (opts.dryRun) return "updated";
1478
- await fs6.mkdir(path6.dirname(abs), { recursive: true });
1479
- await fs6.writeFile(abs, fullContent, "utf8");
2469
+ await fs7.mkdir(path9.dirname(abs), { recursive: true });
2470
+ await fs7.writeFile(abs, fullContent, "utf8");
1480
2471
  return "updated";
1481
2472
  }
1482
2473
  async function writeIdeMcpJson(repoRoot, relPath, buildBody, opts) {
1483
- const abs = path6.join(repoRoot, relPath);
2474
+ const abs = path9.join(repoRoot, relPath);
1484
2475
  assertUnderRepoRoot(repoRoot, abs);
1485
2476
  let raw = "";
1486
2477
  let existed = false;
1487
2478
  try {
1488
- raw = await fs6.readFile(abs, "utf8");
2479
+ raw = await fs7.readFile(abs, "utf8");
1489
2480
  existed = true;
1490
2481
  } catch (err) {
1491
2482
  if (err?.code !== "ENOENT") throw err;
@@ -1493,8 +2484,8 @@ async function writeIdeMcpJson(repoRoot, relPath, buildBody, opts) {
1493
2484
  if (!existed) {
1494
2485
  const body = buildBody(null);
1495
2486
  if (opts.dryRun) return "created";
1496
- await fs6.mkdir(path6.dirname(abs), { recursive: true });
1497
- await fs6.writeFile(abs, body, "utf8");
2487
+ await fs7.mkdir(path9.dirname(abs), { recursive: true });
2488
+ await fs7.writeFile(abs, body, "utf8");
1498
2489
  return "created";
1499
2490
  }
1500
2491
  const managed = raw.includes(MANAGED_MARKER);
@@ -1509,8 +2500,8 @@ async function writeIdeMcpJson(repoRoot, relPath, buildBody, opts) {
1509
2500
  const next = buildBody(parsed);
1510
2501
  if (managed && next === raw) return "skipped";
1511
2502
  if (opts.dryRun) return "updated";
1512
- await fs6.mkdir(path6.dirname(abs), { recursive: true });
1513
- await fs6.writeFile(abs, next, "utf8");
2503
+ await fs7.mkdir(path9.dirname(abs), { recursive: true });
2504
+ await fs7.writeFile(abs, next, "utf8");
1514
2505
  return "updated";
1515
2506
  }
1516
2507
  function parseSetupIdeFlags(argv) {
@@ -1567,9 +2558,144 @@ function summarizeOutcomes(outcomes) {
1567
2558
  }
1568
2559
  console.log(lines.join("\n"));
1569
2560
  }
2561
+ function ideTypesFromSetupTargets(targets) {
2562
+ const ides = [];
2563
+ if (targets.cursor) ides.push("cursor");
2564
+ if (targets.vscode) ides.push("copilot-vscode");
2565
+ if (targets.jetbrains) ides.push("jetbrains");
2566
+ return ides;
2567
+ }
2568
+ function setupTargetsAllIdes(targets) {
2569
+ return targets.cursor && targets.vscode && targets.jetbrains;
2570
+ }
2571
+ function aggregateCleanupResults(results) {
2572
+ const killedPids = /* @__PURE__ */ new Set();
2573
+ const removedSockets = /* @__PURE__ */ new Set();
2574
+ const skippedUnrelated = /* @__PURE__ */ new Map();
2575
+ let foundDaemonCount = 0;
2576
+ let error;
2577
+ for (const result of results) {
2578
+ if (result.error) error = result.error;
2579
+ for (const pid of result.killedPids) {
2580
+ killedPids.add(pid);
2581
+ foundDaemonCount += 1;
2582
+ }
2583
+ for (const socketPath of result.removedSockets) {
2584
+ removedSockets.add(socketPath);
2585
+ }
2586
+ for (const proc of result.skippedProcesses) {
2587
+ if (proc.projectId !== result.projectId) {
2588
+ skippedUnrelated.set(proc.pid, proc);
2589
+ }
2590
+ }
2591
+ }
2592
+ return {
2593
+ foundDaemonCount,
2594
+ stoppedDaemonCount: killedPids.size,
2595
+ removedSocketCount: removedSockets.size,
2596
+ skippedUnrelatedDaemonCount: skippedUnrelated.size,
2597
+ error
2598
+ };
2599
+ }
2600
+ function logSetupIdeCleanupSummary(cleanup) {
2601
+ if (cleanup.skipped) return;
2602
+ console.log(`[setup-ide-files] Project id: ${cleanup.projectId}`);
2603
+ if (cleanup.foundDaemonCount > 0) {
2604
+ console.log(`[setup-ide-files] Found ${cleanup.foundDaemonCount} stale daemon(s)`);
2605
+ if (cleanup.dryRun) {
2606
+ console.log(`[setup-ide-files] Would stop ${cleanup.foundDaemonCount} stale daemon(s)`);
2607
+ } else if (cleanup.stoppedDaemonCount > 0) {
2608
+ console.log(`[setup-ide-files] Stopped ${cleanup.stoppedDaemonCount} stale daemon(s)`);
2609
+ }
2610
+ } else {
2611
+ console.log("[setup-ide-files] No stale daemons found for this project and IDE target(s).");
2612
+ }
2613
+ if (cleanup.removedSocketCount > 0) {
2614
+ if (cleanup.dryRun) {
2615
+ console.log(`[setup-ide-files] Would remove ${cleanup.removedSocketCount} stale socket(s)`);
2616
+ } else {
2617
+ console.log(`[setup-ide-files] Removed ${cleanup.removedSocketCount} stale socket(s)`);
2618
+ }
2619
+ }
2620
+ if (cleanup.skippedUnrelatedDaemonCount > 0) {
2621
+ console.log(
2622
+ `[setup-ide-files] Skipped ${cleanup.skippedUnrelatedDaemonCount} unrelated project daemon(s)`
2623
+ );
2624
+ }
2625
+ }
2626
+ async function runSetupIdeDaemonCleanup(opts) {
2627
+ const ides = ideTypesFromSetupTargets(opts.targets);
2628
+ if (ides.length === 0) {
2629
+ return {
2630
+ skipped: true,
2631
+ skipReason: "no-targets",
2632
+ foundDaemonCount: 0,
2633
+ stoppedDaemonCount: 0,
2634
+ removedSocketCount: 0,
2635
+ skippedUnrelatedDaemonCount: 0,
2636
+ dryRun: opts.dryRun
2637
+ };
2638
+ }
2639
+ const target = await resolveCleanupTarget(opts.repoRoot);
2640
+ if ("error" in target) {
2641
+ return {
2642
+ skipped: true,
2643
+ skipReason: "no-m1",
2644
+ foundDaemonCount: 0,
2645
+ stoppedDaemonCount: 0,
2646
+ removedSocketCount: 0,
2647
+ skippedUnrelatedDaemonCount: 0,
2648
+ dryRun: opts.dryRun
2649
+ };
2650
+ }
2651
+ const baseCleanupOpts = {
2652
+ cwd: opts.repoRoot,
2653
+ dryRun: opts.dryRun,
2654
+ allProjects: false,
2655
+ assumeYes: true,
2656
+ projectId: target.projectId,
2657
+ quiet: true,
2658
+ listProcesses: opts.listProcesses,
2659
+ listSocketPaths: opts.listSocketPaths,
2660
+ killProcess: opts.killProcess,
2661
+ removeSocket: opts.removeSocket
2662
+ };
2663
+ const results = [];
2664
+ if (setupTargetsAllIdes(opts.targets)) {
2665
+ results.push(await runCleanup(baseCleanupOpts));
2666
+ } else {
2667
+ for (const ide of ides) {
2668
+ results.push(await runCleanup({ ...baseCleanupOpts, ide }));
2669
+ }
2670
+ }
2671
+ const aggregated = aggregateCleanupResults(results);
2672
+ const exitError = results.find((r) => r.exitCode !== 0)?.error ?? aggregated.error;
2673
+ return {
2674
+ skipped: false,
2675
+ projectId: target.projectId,
2676
+ foundDaemonCount: aggregated.foundDaemonCount,
2677
+ stoppedDaemonCount: opts.dryRun ? 0 : aggregated.stoppedDaemonCount,
2678
+ removedSocketCount: aggregated.removedSocketCount,
2679
+ skippedUnrelatedDaemonCount: aggregated.skippedUnrelatedDaemonCount,
2680
+ dryRun: opts.dryRun,
2681
+ error: exitError
2682
+ };
2683
+ }
2684
+ function restartIdeInstruction(targets) {
2685
+ const names = [];
2686
+ if (targets.cursor) names.push("Cursor");
2687
+ if (targets.vscode) names.push("VS Code");
2688
+ if (targets.jetbrains) names.push("JetBrains IDE");
2689
+ if (names.length === 0) return "Fully quit your IDE and reopen this repo for MCP changes to take effect.";
2690
+ if (names.length === 1) {
2691
+ return `Fully quit ${names[0]} and reopen this repo for MCP changes to take effect.`;
2692
+ }
2693
+ const last = names.pop();
2694
+ return `Fully quit ${names.join(", ")} and ${last}, then reopen this repo for MCP changes to take effect.`;
2695
+ }
1570
2696
  async function runSetupIdeFiles(o) {
1571
2697
  const outcomes = {};
1572
- let cursorGlobalMcp;
2698
+ let cursorMcp;
1573
2699
  let jetbrainsMcp;
1574
2700
  const repoRoot = await findRepoRoot(o.cwd);
1575
2701
  if (!repoRoot) {
@@ -1580,6 +2706,27 @@ async function runSetupIdeFiles(o) {
1580
2706
  error: "[setup-ide-files] No repo root found (looked for .git or memoraone.m1)."
1581
2707
  };
1582
2708
  }
2709
+ let daemonCleanup;
2710
+ if (!o.skipDaemonCleanup) {
2711
+ daemonCleanup = await runSetupIdeDaemonCleanup({
2712
+ repoRoot,
2713
+ targets: o.targets,
2714
+ dryRun: o.dryRun,
2715
+ listProcesses: o.listDaemonProcesses,
2716
+ listSocketPaths: o.listCleanupSocketPaths,
2717
+ killProcess: o.killDaemonProcess,
2718
+ removeSocket: o.removeCleanupSocket
2719
+ });
2720
+ if (daemonCleanup.error && !daemonCleanup.skipped) {
2721
+ return {
2722
+ exitCode: 1,
2723
+ repoRoot,
2724
+ outcomes,
2725
+ daemonCleanup,
2726
+ error: `[setup-ide-files] Daemon cleanup failed: ${daemonCleanup.error}`
2727
+ };
2728
+ }
2729
+ }
1583
2730
  outcomes[".gitignore"] = await ensureGitignoreMemoraone(repoRoot, {
1584
2731
  dryRun: o.dryRun,
1585
2732
  noGitignore: o.noGitignore ?? false
@@ -1590,19 +2737,6 @@ description: MemoraOne MCP \u2014 IDE agent instructions
1590
2737
 
1591
2738
  ` + cursorRuleBody();
1592
2739
  if (o.targets.cursor) {
1593
- const detection = await detectCursorGlobalMcpConfig({
1594
- homeDir: o.homeDir,
1595
- explicitPath: o.cursorGlobalMcpConfigPath
1596
- });
1597
- if (!detection.ok) {
1598
- return {
1599
- exitCode: 1,
1600
- repoRoot,
1601
- outcomes,
1602
- error: `${detection.error}
1603
- ${detection.candidates.join("\n ")}`
1604
- };
1605
- }
1606
2740
  let npxPath;
1607
2741
  if (o.npxPathOverride !== void 0) {
1608
2742
  npxPath = o.npxPathOverride;
@@ -1614,7 +2748,7 @@ description: MemoraOne MCP \u2014 IDE agent instructions
1614
2748
  exitCode: 1,
1615
2749
  repoRoot,
1616
2750
  outcomes,
1617
- 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."
2751
+ 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."
1618
2752
  };
1619
2753
  }
1620
2754
  outcomes[".cursor/rules/memoraone-mcp.mdc"] = await writeManagedMarkdown(
@@ -1623,29 +2757,58 @@ description: MemoraOne MCP \u2014 IDE agent instructions
1623
2757
  cursorContent,
1624
2758
  { force: o.force, dryRun: o.dryRun }
1625
2759
  );
2760
+ outcomes[".cursor/mcp.json"] = await writeIdeMcpJson(
2761
+ repoRoot,
2762
+ ".cursor/mcp.json",
2763
+ (existing) => buildCursorMcpJsonBody(existing, npxPath, repoRoot),
2764
+ { force: o.force, dryRun: o.dryRun }
2765
+ );
2766
+ const repoConfigPath = getCursorRepoMcpConfigPath(repoRoot);
2767
+ const repoOutcome = outcomes[".cursor/mcp.json"] ?? "skipped";
2768
+ let globalConfigPath;
2769
+ let globalMemoraoneRemoved = false;
2770
+ let globalBackupPath;
2771
+ const globalDetection = await detectCursorGlobalMcpConfig({
2772
+ homeDir: o.homeDir,
2773
+ explicitPath: o.cursorGlobalMcpConfigPath
2774
+ });
2775
+ if (!globalDetection.ok) {
2776
+ return {
2777
+ exitCode: 1,
2778
+ repoRoot,
2779
+ outcomes,
2780
+ error: `${globalDetection.error}
2781
+ ${globalDetection.candidates.join("\n ")}`
2782
+ };
2783
+ }
2784
+ globalConfigPath = globalDetection.path;
1626
2785
  try {
1627
- const globalSetup = await setupCursorGlobalMcpConfig({
1628
- configPath: detection.path,
1629
- npxPath,
2786
+ const removal = await removeMemoraoneFromCursorGlobalConfig({
2787
+ configPath: globalDetection.path,
1630
2788
  dryRun: o.dryRun
1631
2789
  });
1632
- cursorGlobalMcp = {
1633
- configPath: detection.path,
1634
- outcome: globalSetup.outcome,
1635
- npxPath,
1636
- backupPath: globalSetup.backupPath
1637
- };
1638
- outcomes[`cursor-global:${detection.path}`] = globalSetup.outcome;
2790
+ if (removal.changed) {
2791
+ globalMemoraoneRemoved = true;
2792
+ globalBackupPath = removal.backupPath;
2793
+ outcomes[`cursor-global-removed:${globalDetection.path}`] = o.dryRun ? "updated" : "updated";
2794
+ }
1639
2795
  } catch (err) {
1640
2796
  const message = err instanceof Error ? err.message : String(err);
1641
2797
  return {
1642
2798
  exitCode: 1,
1643
2799
  repoRoot,
1644
2800
  outcomes,
1645
- cursorGlobalMcp: { configPath: detection.path, outcome: "skipped", npxPath },
1646
2801
  error: message
1647
2802
  };
1648
2803
  }
2804
+ cursorMcp = {
2805
+ repoConfigPath,
2806
+ repoOutcome,
2807
+ npxPath,
2808
+ globalConfigPath,
2809
+ globalMemoraoneRemoved,
2810
+ globalBackupPath
2811
+ };
1649
2812
  }
1650
2813
  if (o.targets.vscode) {
1651
2814
  outcomes[".vscode/mcp.json"] = await writeIdeMcpJson(
@@ -1701,12 +2864,12 @@ description: MemoraOne MCP \u2014 IDE agent instructions
1701
2864
  exitCode: 1,
1702
2865
  repoRoot,
1703
2866
  outcomes,
1704
- cursorGlobalMcp,
2867
+ cursorMcp,
1705
2868
  error: message
1706
2869
  };
1707
2870
  }
1708
2871
  }
1709
- return { exitCode: 0, repoRoot, outcomes, cursorGlobalMcp, jetbrainsMcp };
2872
+ return { exitCode: 0, repoRoot, outcomes, cursorMcp, jetbrainsMcp, daemonCleanup };
1710
2873
  }
1711
2874
  async function cliSetupIdeFiles(argv) {
1712
2875
  const { targets, force, dryRun, noGitignore, cleanup, devMode, repair, unknown } = parseSetupIdeFlags(argv);
@@ -1734,8 +2897,11 @@ async function cliSetupIdeFiles(argv) {
1734
2897
  if (result.repoRoot) {
1735
2898
  console.log(`[setup-ide-files] Repo root: ${result.repoRoot}`);
1736
2899
  }
1737
- if (targets.cursor && result.cursorGlobalMcp) {
1738
- logCursorGlobalMcpCliSummary(result.cursorGlobalMcp, dryRun);
2900
+ if (result.daemonCleanup && !result.daemonCleanup.skipped) {
2901
+ logSetupIdeCleanupSummary(result.daemonCleanup);
2902
+ }
2903
+ if (targets.cursor && result.cursorMcp) {
2904
+ logCursorMcpCliSummary(result.cursorMcp, dryRun);
1739
2905
  }
1740
2906
  if (targets.jetbrains && result.jetbrainsMcp) {
1741
2907
  logJetBrainsMcpCliSummary(result.jetbrainsMcp, dryRun);
@@ -1743,9 +2909,13 @@ async function cliSetupIdeFiles(argv) {
1743
2909
  summarizeOutcomes(result.outcomes);
1744
2910
  if (dryRun) {
1745
2911
  console.log("[setup-ide-files] Dry run: no files written.");
2912
+ if (result.daemonCleanup && !result.daemonCleanup.skipped) {
2913
+ console.log("[setup-ide-files] Dry run: no daemons stopped, no sockets removed.");
2914
+ }
1746
2915
  }
2916
+ console.log(`[setup-ide-files] ${restartIdeInstruction(targets)}`);
1747
2917
  if (cleanup) {
1748
- console.log("[setup-ide-files] Running project-scoped cleanup...");
2918
+ console.log("[setup-ide-files] Running additional full-project cleanup (--cleanup)...");
1749
2919
  const cleanupResult = await runCleanup({
1750
2920
  cwd: process.cwd(),
1751
2921
  dryRun,
@@ -1756,13 +2926,6 @@ async function cliSetupIdeFiles(argv) {
1756
2926
  console.error(`[setup-ide-files] cleanup failed: ${cleanupResult.error}`);
1757
2927
  return cleanupResult.exitCode;
1758
2928
  }
1759
- } else {
1760
- console.log(
1761
- "[setup-ide-files] If Studio shows stale connections, run: npx -y @memoraone/mcp@latest cleanup --project-id <projectId>"
1762
- );
1763
- console.log(
1764
- "[setup-ide-files] From this repo (uses memoraone.m1): npx -y @memoraone/mcp@latest cleanup"
1765
- );
1766
2929
  }
1767
2930
  return 0;
1768
2931
  }
@@ -1799,92 +2962,9 @@ if (args[0] === "cleanup") {
1799
2962
  process.exit(1);
1800
2963
  });
1801
2964
  } else {
1802
- let getWorkspaceRootCandidates = function() {
1803
- const raw = process.env.WORKSPACE_FOLDER_PATHS;
1804
- const parts = [];
1805
- if (raw !== void 0 && raw.trim() !== "") {
1806
- for (const p of raw.split(path7.delimiter).map((s) => s.trim()).filter(Boolean)) {
1807
- parts.push(path7.resolve(p));
1808
- }
1809
- }
1810
- parts.push(process.cwd());
1811
- const seen = /* @__PURE__ */ new Set();
1812
- const deduped = [];
1813
- for (const p of parts) {
1814
- if (!seen.has(p)) {
1815
- seen.add(p);
1816
- deduped.push(p);
1817
- }
1818
- }
1819
- return deduped;
1820
- }, connectWithRetry = function(socketPath) {
1821
- return new Promise((resolve7, reject) => {
1822
- const tryConnect = (attempt) => {
1823
- const socket = net.connect(socketPath, () => {
1824
- resolve7(socket);
1825
- });
1826
- socket.on("error", (err) => {
1827
- if (attempt >= MAX_RETRIES) {
1828
- reject(err);
1829
- return;
1830
- }
1831
- log(`connect attempt ${attempt + 1} failed, retrying in ${RETRY_DELAY_MS}ms: ${String(err)}`);
1832
- setTimeout(() => tryConnect(attempt + 1), RETRY_DELAY_MS);
1833
- });
1834
- };
1835
- tryConnect(0);
1836
- });
1837
- };
1838
- const log = (msg) => {
1839
- process.stderr.write(`[memoraone-mcp][bridge] ${msg}
2965
+ runBridgeProxy({ cliPath: process.argv[1] }).catch((err) => {
2966
+ process.stderr.write(`[memoraone-mcp][bridge] fatal: ${String(err)}
1840
2967
  `);
1841
- };
1842
- const MAX_RETRIES = 5;
1843
- const RETRY_DELAY_MS = 200;
1844
- async function resolveBinding() {
1845
- return resolveAuthoritativeBinding(getWorkspaceRootCandidates());
1846
- }
1847
- async function runBridge() {
1848
- ensureBaseDir();
1849
- const binding = await resolveBinding();
1850
- const socketPath = getDaemonSocketPath(binding.projectId);
1851
- const environmentLog = binding.environment !== void 0 ? ` environment=${binding.environment}` : "";
1852
- log(
1853
- `authoritative binding project=${binding.projectId} workspace=${binding.workspaceRoot} m1=${binding.m1Path} source=${binding.bindingSource} apiKeySource=${binding.apiKeySource}${environmentLog}`
1854
- );
1855
- let socket;
1856
- try {
1857
- socket = await connectWithRetry(socketPath);
1858
- } catch {
1859
- log("daemon not running, spawning...");
1860
- const child = (0, import_node_child_process4.spawn)(
1861
- process.execPath,
1862
- buildDaemonSpawnArgs(process.argv[1], binding.projectId),
1863
- {
1864
- detached: true,
1865
- stdio: "ignore",
1866
- env: {
1867
- ...process.env,
1868
- MEMORAONE_DAEMON_BINDING_B64: encodeResolvedBinding(binding)
1869
- }
1870
- }
1871
- );
1872
- child.unref();
1873
- await new Promise((r) => setTimeout(r, RETRY_DELAY_MS));
1874
- socket = await connectWithRetry(socketPath);
1875
- }
1876
- log("bridge connected");
1877
- log("forwarding active");
1878
- process.stdin.pipe(socket);
1879
- socket.pipe(process.stdout);
1880
- socket.on("close", () => process.exit(0));
1881
- socket.on("error", (err) => {
1882
- log(`socket error: ${String(err)}`);
1883
- process.exit(1);
1884
- });
1885
- }
1886
- runBridge().catch((err) => {
1887
- log(`fatal: ${String(err)}`);
1888
2968
  process.exit(1);
1889
2969
  });
1890
2970
  }