@memoraone/mcp 0.1.30 → 0.1.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/dist/cli.cjs +1818 -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.31",
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,1339 @@ 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
+ function buildMemoraoneCursorMcpServer(npxPath, workspaceRoot) {
1165
+ const env = {
1166
+ MEMORAONE_API_URL: "https://api.memoraone.com",
1167
+ MEMORAONE_IDE_TYPE: "cursor"
1168
+ };
1169
+ if (workspaceRoot !== void 0) {
1170
+ env[MEMORAONE_WORKSPACE_ROOT_ENV] = path6.resolve(workspaceRoot);
1171
+ }
1172
+ return {
1173
+ command: npxPath,
1174
+ args: ["-y", "@memoraone/mcp@latest"],
1175
+ env
1176
+ };
1177
+ }
1178
+ async function pathExists(filePath) {
1179
+ try {
1180
+ await fs4.access(filePath);
1181
+ return true;
1182
+ } catch {
1183
+ return false;
1184
+ }
1185
+ }
1186
+ function stripLeadingLineComments(text) {
1187
+ return text.split("\n").filter((line) => !/^\s*\/\//.test(line)).join("\n");
1188
+ }
1189
+ function getKnownCursorGlobalMcpConfigCandidates(homeDir) {
1190
+ return [path6.join(homeDir, ".cursor", "mcp.json")];
1191
+ }
1192
+ async function detectCursorGlobalMcpConfig(options) {
1193
+ if (options?.explicitPath) {
1194
+ return { ok: true, path: options.explicitPath, detectedExisting: await pathExists(options.explicitPath) };
1195
+ }
1196
+ const homeDir = options?.homeDir ?? os2.homedir();
1197
+ const candidates = getKnownCursorGlobalMcpConfigCandidates(homeDir);
1198
+ const existing = [];
1199
+ for (const candidate of candidates) {
1200
+ if (await pathExists(candidate)) existing.push(candidate);
1201
+ }
1202
+ if (existing.length > 1) {
1203
+ return {
1204
+ ok: false,
1205
+ error: "[setup-ide-files] Multiple Cursor global MCP config paths found. Specify one explicitly.",
1206
+ candidates: existing
1207
+ };
1208
+ }
1209
+ if (existing.length === 1) {
1210
+ return { ok: true, path: existing[0], detectedExisting: true };
1211
+ }
1212
+ const defaultPath = candidates[0];
1213
+ if (!defaultPath) {
1214
+ return {
1215
+ ok: false,
1216
+ error: "[setup-ide-files] No known Cursor global MCP config path.",
1217
+ candidates: []
1218
+ };
1219
+ }
1220
+ return { ok: true, path: defaultPath, detectedExisting: false };
1221
+ }
1222
+ function formatBackupTimestamp(d = /* @__PURE__ */ new Date()) {
1223
+ const pad = (n) => String(n).padStart(2, "0");
1224
+ return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
1225
+ }
1226
+ async function isWorkingNpx(npxPath) {
1227
+ try {
1228
+ if (!await pathExists(npxPath)) return false;
1229
+ if (process.platform !== "win32") {
1230
+ try {
1231
+ await fs4.access(npxPath, fs4.constants.X_OK);
1232
+ } catch {
1233
+ return false;
1234
+ }
1235
+ }
1236
+ await execFileAsync(npxPath, ["--version"], { timeout: 1e4 });
1237
+ return true;
1238
+ } catch {
1239
+ return false;
1240
+ }
1241
+ }
1242
+ async function resolveNpxPath() {
1243
+ const npxName = process.platform === "win32" ? "npx.cmd" : "npx";
1244
+ const candidates = [];
1245
+ if (process.platform === "darwin") {
1246
+ candidates.push("/opt/homebrew/bin/npx", "/usr/local/bin/npx");
1247
+ } else if (process.platform === "linux") {
1248
+ candidates.push("/usr/local/bin/npx");
1249
+ }
1250
+ const pathSep = process.platform === "win32" ? ";" : ":";
1251
+ for (const dir of (process.env.PATH ?? "").split(pathSep)) {
1252
+ if (!dir) continue;
1253
+ candidates.push(path6.join(dir, npxName));
1254
+ }
1255
+ try {
1256
+ const lookupCmd = process.platform === "win32" ? "where" : "which";
1257
+ const { stdout } = await execFileAsync(lookupCmd, [npxName], { timeout: 5e3 });
1258
+ const first = stdout.trim().split(/\r?\n/).map((line) => line.trim()).find(Boolean);
1259
+ if (first) candidates.unshift(first);
1260
+ } catch {
1261
+ }
1262
+ const seen = /* @__PURE__ */ new Set();
1263
+ for (const candidate of candidates) {
1264
+ const abs = path6.isAbsolute(candidate) ? candidate : path6.resolve(candidate);
1265
+ const key = process.platform === "win32" ? abs.toLowerCase() : abs;
1266
+ if (seen.has(key)) continue;
1267
+ seen.add(key);
1268
+ if (await isWorkingNpx(abs)) return abs;
1269
+ }
1270
+ return null;
1271
+ }
1272
+ function mergeCursorRepoMcpConfigObject(existing, npxPath, repoRoot) {
1273
+ const base = existing && typeof existing === "object" ? { ...existing } : { mcpServers: {} };
1274
+ const mcpServers = typeof base.mcpServers === "object" && base.mcpServers !== null && !Array.isArray(base.mcpServers) ? { ...base.mcpServers } : {};
1275
+ mcpServers.memoraone = buildMemoraoneCursorMcpServer(npxPath, repoRoot);
1276
+ return { ...base, mcpServers };
1277
+ }
1278
+ function isManagedMemoraoneCursorServer(server) {
1279
+ if (!server || typeof server !== "object") return false;
1280
+ const s = server;
1281
+ if (!Array.isArray(s.args) || s.args.length !== 2) return false;
1282
+ if (s.args[0] !== "-y" || s.args[1] !== "@memoraone/mcp@latest") return false;
1283
+ const env = s.env;
1284
+ if (!env || typeof env !== "object") return false;
1285
+ return memoraoneEnvMatchesBase(env);
1286
+ }
1287
+ function cursorConfigHasManagedMemoraone(parsed) {
1288
+ if (!parsed || typeof parsed !== "object") return false;
1289
+ const mcpServers = parsed.mcpServers;
1290
+ if (!mcpServers || typeof mcpServers !== "object" || Array.isArray(mcpServers)) return false;
1291
+ return isManagedMemoraoneCursorServer(mcpServers.memoraone);
1292
+ }
1293
+ function memoraoneEnvMatchesBase(env) {
1294
+ return env.MEMORAONE_API_URL === "https://api.memoraone.com" && env.MEMORAONE_IDE_TYPE === "cursor";
1295
+ }
1296
+ function getCursorRepoMcpConfigPath(repoRoot) {
1297
+ return path6.join(repoRoot, ".cursor", "mcp.json");
1298
+ }
1299
+ async function readCursorMcpConfigObject(configPath) {
1300
+ try {
1301
+ const raw = await fs4.readFile(configPath, "utf8");
1302
+ return JSON.parse(stripLeadingLineComments(raw));
1303
+ } catch (err) {
1304
+ const code = err && typeof err === "object" && "code" in err ? err.code : void 0;
1305
+ if (code === "ENOENT") return null;
1306
+ throw err;
1307
+ }
1308
+ }
1309
+ async function removeMemoraoneFromCursorGlobalConfig(options) {
1310
+ const { configPath, dryRun } = options;
1311
+ const parsed = await readCursorMcpConfigObject(configPath);
1312
+ if (!parsed || !cursorConfigHasManagedMemoraone(parsed)) {
1313
+ return { changed: false };
1314
+ }
1315
+ if (dryRun) {
1316
+ return { changed: true, backupPath: `${configPath}.backup-<timestamp>` };
1317
+ }
1318
+ const backupPath = `${configPath}.backup-${formatBackupTimestamp()}`;
1319
+ await fs4.copyFile(configPath, backupPath);
1320
+ const mcpServers = typeof parsed.mcpServers === "object" && parsed.mcpServers !== null && !Array.isArray(parsed.mcpServers) ? { ...parsed.mcpServers } : {};
1321
+ delete mcpServers.memoraone;
1322
+ const hasOtherServers = Object.keys(mcpServers).length > 0;
1323
+ if (!hasOtherServers) {
1324
+ await fs4.unlink(configPath);
1325
+ return { changed: true, backupPath };
1326
+ }
1327
+ const next = { ...parsed, mcpServers };
1328
+ await fs4.mkdir(path6.dirname(configPath), { recursive: true });
1329
+ await fs4.writeFile(configPath, JSON.stringify(next, null, 2) + "\n", "utf8");
1330
+ return { changed: true, backupPath };
1331
+ }
1332
+ async function auditCursorMcpConfig(options) {
1333
+ const repoRoot = path6.resolve(options?.repoRoot ?? process.cwd());
1334
+ const repoConfigPath = getCursorRepoMcpConfigPath(repoRoot);
1335
+ const globalDetection = await detectCursorGlobalMcpConfig({
1336
+ homeDir: options?.homeDir,
1337
+ explicitPath: options?.explicitGlobalPath
1338
+ });
1339
+ const globalConfigPath = globalDetection.ok ? globalDetection.path : getKnownCursorGlobalMcpConfigCandidates(
1340
+ options?.homeDir ?? os2.homedir()
1341
+ )[0];
1342
+ let repoHasManagedMemoraone = false;
1343
+ try {
1344
+ const repoParsed = await readCursorMcpConfigObject(repoConfigPath);
1345
+ repoHasManagedMemoraone = cursorConfigHasManagedMemoraone(repoParsed);
1346
+ } catch {
1347
+ repoHasManagedMemoraone = false;
1348
+ }
1349
+ let globalHasManagedMemoraone = false;
1350
+ if (globalDetection.ok) {
1351
+ try {
1352
+ const globalParsed = await readCursorMcpConfigObject(globalConfigPath);
1353
+ globalHasManagedMemoraone = cursorConfigHasManagedMemoraone(globalParsed);
1354
+ } catch {
1355
+ globalHasManagedMemoraone = false;
1356
+ }
1357
+ }
1358
+ return {
1359
+ repoConfigPath,
1360
+ repoHasManagedMemoraone,
1361
+ globalConfigPath,
1362
+ globalHasManagedMemoraone,
1363
+ conflict: repoHasManagedMemoraone && globalHasManagedMemoraone
1364
+ };
1365
+ }
1366
+ function logCursorMcpConfigAudit(prefix, audit) {
1367
+ console.log(`${prefix} Cursor MCP config audit:`);
1368
+ console.log(
1369
+ `${prefix} repo ${audit.repoConfigPath}: managed memoraone=${audit.repoHasManagedMemoraone}`
1370
+ );
1371
+ console.log(
1372
+ `${prefix} global ${audit.globalConfigPath}: managed memoraone=${audit.globalHasManagedMemoraone}`
1373
+ );
1374
+ if (audit.conflict) {
1375
+ console.warn(
1376
+ `${prefix} WARNING: Both repo and global Cursor MCP define memoraone. Global shared MCP cannot bind per-window repos; remove global memoraone and use repo .cursor/mcp.json only.`
1377
+ );
1378
+ } else if (audit.globalHasManagedMemoraone && !audit.repoHasManagedMemoraone) {
1379
+ console.warn(
1380
+ `${prefix} WARNING: Cursor global MCP has memoraone but this repo lacks .cursor/mcp.json. Global MCP shares one process across windows (first-window-wins roots). Run setup-ide-files --cursor in this repo.`
1381
+ );
1382
+ } else if (audit.repoHasManagedMemoraone && !audit.globalHasManagedMemoraone) {
1383
+ console.log(
1384
+ `${prefix} Cursor MCP is repo-scoped (.cursor/mcp.json) with no global memoraone entry (recommended for multi-repo windows).`
1385
+ );
1386
+ }
1387
+ }
1388
+ function logCursorMcpCliSummary(info, dryRun) {
1389
+ const { repoConfigPath, repoOutcome, npxPath, repoBackupPath, globalConfigPath, globalMemoraoneRemoved, globalBackupPath } = info;
1390
+ console.log(`[setup-ide-files] Cursor repo MCP config: ${repoConfigPath}`);
1391
+ console.log(`[setup-ide-files] Resolved npx: ${npxPath}`);
1392
+ if (repoBackupPath) {
1393
+ console.log(`[setup-ide-files] Cursor repo MCP config backup: ${repoBackupPath}`);
1394
+ }
1395
+ if (repoOutcome === "created") {
1396
+ console.log(
1397
+ dryRun ? `[setup-ide-files] Cursor repo MCP config would be created: ${repoConfigPath}` : `[setup-ide-files] Cursor repo MCP config created: ${repoConfigPath}`
1398
+ );
1399
+ } else if (repoOutcome === "updated") {
1400
+ console.log(
1401
+ dryRun ? `[setup-ide-files] Cursor repo MCP config would be updated: ${repoConfigPath}` : `[setup-ide-files] Cursor repo MCP config updated: ${repoConfigPath}`
1402
+ );
1403
+ } else if (repoOutcome === "skipped") {
1404
+ console.log(`[setup-ide-files] Cursor repo MCP config unchanged: ${repoConfigPath}`);
1405
+ }
1406
+ if (globalMemoraoneRemoved && globalConfigPath) {
1407
+ console.log(
1408
+ dryRun ? `[setup-ide-files] Would remove memoraone from Cursor global MCP config: ${globalConfigPath}` : `[setup-ide-files] Removed memoraone from Cursor global MCP config: ${globalConfigPath}`
1409
+ );
1410
+ if (globalBackupPath) {
1411
+ console.log(`[setup-ide-files] Cursor global MCP config backup: ${globalBackupPath}`);
1412
+ }
1413
+ } else if (globalConfigPath) {
1414
+ console.log(
1415
+ `[setup-ide-files] Cursor global MCP config unchanged (no managed memoraone to remove): ${globalConfigPath}`
1416
+ );
1417
+ }
1418
+ console.log(
1419
+ "[setup-ide-files] Each Cursor window uses this repo\u2019s .cursor/mcp.json (separate MCP process per repo)."
1420
+ );
1421
+ console.log(
1422
+ "[setup-ide-files] Fully quit Cursor and reopen this repo for MCP changes to take effect."
1423
+ );
1424
+ }
1425
+
1426
+ // src/cleanup.ts
1427
+ var execFileAsync2 = (0, import_node_util2.promisify)(import_node_child_process3.execFile);
1428
+ var DAEMON_PROJECT_ID_RE = /--project-id\s+([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/i;
1429
+ var PROJECT_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
1430
+ var MEMORAONE_MCP_COMMAND_RE = /memoraone-mcp|memoraOne-mcp|@memoraone\/mcp/;
1431
+ var CLEANUP_PROJECT_ID_REQUIRED_ERROR = "Provide --project-id <id> or run from a folder containing memoraone.m1.";
1432
+ function isMemoraoneMcpCommandLine(commandLine) {
1433
+ return MEMORAONE_MCP_COMMAND_RE.test(commandLine);
1434
+ }
1435
+ function parseDaemonProjectIdFromCommandLine(commandLine) {
1436
+ if (!commandLine.includes("--daemon")) {
1437
+ return null;
1438
+ }
1439
+ const match = commandLine.match(DAEMON_PROJECT_ID_RE);
1440
+ return match ? match[1].toLowerCase() : null;
1441
+ }
1442
+ function parseDaemonIdeFromCommandLine(commandLine) {
1443
+ if (!commandLine.includes("--daemon")) {
1444
+ return void 0;
1445
+ }
1446
+ return parseIdeTypeFromCommandLine(commandLine);
1447
+ }
1448
+ function parseDaemonProcessLines(lines) {
1449
+ const processes = [];
1450
+ for (const line of lines) {
1451
+ const trimmed = line.trim();
1452
+ if (!trimmed) continue;
1453
+ const spaceIdx = trimmed.indexOf(" ");
1454
+ if (spaceIdx <= 0) continue;
1455
+ const pid = Number.parseInt(trimmed.slice(0, spaceIdx), 10);
1456
+ if (!Number.isFinite(pid) || pid <= 0) continue;
1457
+ const command = trimmed.slice(spaceIdx + 1);
1458
+ if (!isMemoraoneMcpCommandLine(command)) continue;
1459
+ const projectId = parseDaemonProjectIdFromCommandLine(command);
1460
+ if (projectId === null) continue;
1461
+ const ide = parseDaemonIdeFromCommandLine(command);
1462
+ processes.push({ pid, command, projectId, ...ide !== void 0 ? { ide } : {} });
1463
+ }
1464
+ return processes;
1465
+ }
1466
+ function normalizeCleanupProjectId(projectId) {
1467
+ const trimmed = projectId.trim();
1468
+ if (!PROJECT_ID_RE.test(trimmed)) {
1469
+ return { error: `Invalid project id: ${projectId}` };
1470
+ }
1471
+ return trimmed.toLowerCase();
1472
+ }
1473
+ async function defaultListDaemonProcesses() {
1474
+ const { stdout } = await execFileAsync2("ps", ["-eo", "pid=,args="], {
1475
+ maxBuffer: 10 * 1024 * 1024
1476
+ });
1477
+ return parseDaemonProcessLines(stdout.split("\n"));
1478
+ }
1479
+ async function defaultListSocketPaths(projectId) {
1480
+ const baseDir = getMcpBaseDir();
1481
+ let entries;
1482
+ try {
1483
+ entries = await fs5.readdir(baseDir);
1484
+ } catch (err) {
1485
+ const code = err && typeof err === "object" && "code" in err ? err.code : void 0;
1486
+ if (code === "ENOENT") {
1487
+ return [];
1488
+ }
1489
+ throw err;
1490
+ }
1491
+ const paths = [];
1492
+ for (const name of entries) {
1493
+ if (!name.endsWith(".sock") || !isMemoraoneSocketFilename(name)) {
1494
+ continue;
1495
+ }
1496
+ const socketPath = path7.join(baseDir, name);
1497
+ if (projectId === null) {
1498
+ paths.push(socketPath);
1499
+ continue;
1500
+ }
1501
+ const normalizedProjectId = projectId.trim().toLowerCase();
1502
+ if (isLegacySocketFilename(name) && isSocketFilenameForProject(name, normalizedProjectId)) {
1503
+ paths.push(socketPath);
1504
+ continue;
1505
+ }
1506
+ if (isHashSocketFilename(name)) {
1507
+ const record = readBindingSidecarRecord(socketPath);
1508
+ if (record?.projectId.trim().toLowerCase() === normalizedProjectId) {
1509
+ paths.push(socketPath);
1510
+ }
1511
+ }
1512
+ }
1513
+ return paths.sort();
1514
+ }
1515
+ async function filterSocketPathsByIde(socketPaths, projectId, ide) {
1516
+ if (ide === void 0) return socketPaths;
1517
+ const normalizedProjectId = projectId.trim().toLowerCase();
1518
+ const filtered = [];
1519
+ for (const socketPath of socketPaths) {
1520
+ const basename4 = path7.basename(socketPath);
1521
+ if (isLegacySocketFilename(basename4)) {
1522
+ if (isSocketFilenameForProjectAndIde(basename4, normalizedProjectId, ide)) {
1523
+ filtered.push(socketPath);
1524
+ }
1525
+ continue;
1526
+ }
1527
+ if (isHashSocketFilename(basename4)) {
1528
+ const record = readBindingSidecarRecord(socketPath);
1529
+ if (record?.projectId.trim().toLowerCase() === normalizedProjectId && record.ideType === ide) {
1530
+ filtered.push(socketPath);
1531
+ }
1532
+ }
1533
+ }
1534
+ return filtered;
1535
+ }
1536
+ async function defaultKillProcess(pid) {
1537
+ process.kill(pid, "SIGTERM");
1538
+ }
1539
+ async function defaultRemoveSocket(socketPath) {
1540
+ await fs5.unlink(socketPath);
1541
+ try {
1542
+ await fs5.unlink(bindingSidecarPath(socketPath));
1543
+ } catch {
1544
+ }
1545
+ }
1546
+ async function defaultConfirm(message) {
1547
+ if (!import_node_process.stdin.isTTY) {
1548
+ return false;
1549
+ }
1550
+ const rl = readline3.createInterface({ input: import_node_process.stdin, output: import_node_process.stdout });
1551
+ try {
1552
+ const answer = await rl.question(`${message} [y/N] `);
1553
+ return /^y(es)?$/i.test(answer.trim());
1554
+ } finally {
1555
+ rl.close();
1556
+ }
1557
+ }
1558
+ async function resolveCleanupTarget(cwd) {
1559
+ try {
1560
+ const binding = await resolveAuthoritativeBinding([path7.resolve(cwd)]);
1561
+ return {
1562
+ workspaceRoot: binding.workspaceRoot,
1563
+ m1Path: binding.m1Path,
1564
+ projectId: binding.projectId
1565
+ };
1566
+ } catch {
1567
+ return { error: CLEANUP_PROJECT_ID_REQUIRED_ERROR };
1568
+ }
1569
+ }
1570
+ function filterProcessesForScope(processes, projectId) {
1571
+ if (projectId === null) {
1572
+ return { matching: processes, skipped: [] };
1573
+ }
1574
+ const normalized = projectId.toLowerCase();
1575
+ const matching = [];
1576
+ const skipped = [];
1577
+ for (const proc of processes) {
1578
+ if (proc.projectId === normalized) {
1579
+ matching.push(proc);
1580
+ } else {
1581
+ skipped.push(proc);
1582
+ }
1583
+ }
1584
+ return { matching, skipped };
433
1585
  }
434
1586
  function logPrefix(dryRun) {
435
1587
  return dryRun ? "[cleanup][dry-run]" : "[cleanup]";
436
1588
  }
437
- function logReconnectNotice(prefix, ide) {
1589
+ function logReconnectNotice(opts, prefix, ide) {
1590
+ if (opts.quiet) return;
438
1591
  if (ide) {
439
1592
  console.log(
440
1593
  `${prefix} Note: Valid ${ide} connections for this project may disconnect temporarily; they should reconnect automatically. Stale connections will remain cleared.`
@@ -445,6 +1598,16 @@ function logReconnectNotice(prefix, ide) {
445
1598
  );
446
1599
  }
447
1600
  }
1601
+ function cleanupLog(opts, message) {
1602
+ if (!opts.quiet) {
1603
+ console.log(message);
1604
+ }
1605
+ }
1606
+ function cleanupWarn(opts, message) {
1607
+ if (!opts.quiet) {
1608
+ console.warn(message);
1609
+ }
1610
+ }
448
1611
  async function runCleanup(opts) {
449
1612
  const listProcesses = opts.listProcesses ?? defaultListDaemonProcesses;
450
1613
  const listSocketPaths = opts.listSocketPaths ?? defaultListSocketPaths;
@@ -465,8 +1628,9 @@ async function runCleanup(opts) {
465
1628
  error: "Cannot combine --all-projects with --project-id."
466
1629
  };
467
1630
  }
468
- console.log(`${prefix} Mode: all projects (--all-projects)`);
469
- console.warn(
1631
+ cleanupLog(opts, `${prefix} Mode: all projects (--all-projects)`);
1632
+ cleanupWarn(
1633
+ opts,
470
1634
  `${prefix} WARNING: This stops every MemoraOne MCP daemon and removes all project sockets under ${getMcpBaseDir()}.`
471
1635
  );
472
1636
  } else if (opts.projectId) {
@@ -475,9 +1639,9 @@ async function runCleanup(opts) {
475
1639
  return { exitCode: 1, killedPids: [], removedSockets: [], skippedProcesses: [], error: normalized.error };
476
1640
  }
477
1641
  targetProjectId = normalized;
478
- console.log(`${prefix} Project id: ${targetProjectId}`);
1642
+ cleanupLog(opts, `${prefix} Project id: ${targetProjectId}`);
479
1643
  if (opts.ide) {
480
- console.log(`${prefix} IDE filter: ${opts.ide}`);
1644
+ cleanupLog(opts, `${prefix} IDE filter: ${opts.ide}`);
481
1645
  }
482
1646
  } else {
483
1647
  const target = await resolveCleanupTarget(opts.cwd);
@@ -487,15 +1651,23 @@ async function runCleanup(opts) {
487
1651
  targetProjectId = target.projectId;
488
1652
  workspaceRoot = target.workspaceRoot;
489
1653
  m1Path = target.m1Path;
490
- console.log(`${prefix} Workspace root: ${workspaceRoot}`);
491
- console.log(`${prefix} memoraone.m1: ${m1Path}`);
492
- console.log(`${prefix} Project id: ${targetProjectId}`);
1654
+ cleanupLog(opts, `${prefix} Workspace root: ${workspaceRoot}`);
1655
+ cleanupLog(opts, `${prefix} memoraone.m1: ${m1Path}`);
1656
+ cleanupLog(opts, `${prefix} Project id: ${targetProjectId}`);
493
1657
  if (opts.ide) {
494
- console.log(`${prefix} IDE filter: ${opts.ide}`);
1658
+ cleanupLog(opts, `${prefix} IDE filter: ${opts.ide}`);
1659
+ }
1660
+ if (workspaceRoot && (!opts.ide || opts.ide === "cursor")) {
1661
+ try {
1662
+ const cursorAudit = await auditCursorMcpConfig({ repoRoot: workspaceRoot });
1663
+ logCursorMcpConfigAudit(prefix, cursorAudit);
1664
+ } catch (err) {
1665
+ cleanupWarn(opts, `${prefix} Cursor MCP config audit failed: ${String(err)}`);
1666
+ }
495
1667
  }
496
1668
  }
497
1669
  if (targetProjectId !== null || opts.ide) {
498
- logReconnectNotice(prefix, opts.ide);
1670
+ logReconnectNotice(opts, prefix, opts.ide);
499
1671
  }
500
1672
  const allDaemonProcesses = await listProcesses();
501
1673
  const { matching: projectProcesses, skipped: skippedProcesses } = filterProcessesForScope(
@@ -511,392 +1683,198 @@ async function runCleanup(opts) {
511
1683
  processesToStop.push(proc);
512
1684
  } else if (proc.ide === void 0) {
513
1685
  ideSkippedProcesses.push(proc);
514
- console.log(
1686
+ cleanupLog(
1687
+ opts,
515
1688
  `${prefix} Skipped daemon pid=${proc.pid} because IDE could not be safely determined.`
516
1689
  );
517
1690
  } else {
518
1691
  ideSkippedProcesses.push(proc);
519
- console.log(
1692
+ cleanupLog(
1693
+ opts,
520
1694
  `${prefix} Skipped daemon pid=${proc.pid} (IDE ${proc.ide} does not match filter ${opts.ide}).`
521
1695
  );
522
1696
  }
523
1697
  }
524
1698
  }
525
1699
  const allSocketPaths = await listSocketPaths(targetProjectId);
526
- const socketPaths = targetProjectId === null ? allSocketPaths : filterSocketPathsByIde(allSocketPaths, targetProjectId, opts.ide);
1700
+ const socketPaths = targetProjectId === null ? allSocketPaths : await filterSocketPathsByIde(allSocketPaths, targetProjectId, opts.ide);
527
1701
  if (opts.allProjects) {
528
1702
  const projectIds = /* @__PURE__ */ new Set();
529
1703
  for (const proc of processesToStop) {
530
1704
  projectIds.add(proc.projectId);
531
1705
  }
532
1706
  for (const socketPath of socketPaths) {
533
- const id = extractProjectIdFromSocketFilename(path3.basename(socketPath));
534
- if (id) projectIds.add(id);
1707
+ const id = extractProjectIdFromSocketFilename(path7.basename(socketPath));
1708
+ if (id) {
1709
+ projectIds.add(id);
1710
+ continue;
1711
+ }
1712
+ const record = readBindingSidecarRecord(socketPath);
1713
+ if (record) {
1714
+ projectIds.add(record.projectId.trim().toLowerCase());
1715
+ }
535
1716
  }
536
- console.log(`${prefix} Projects affected: ${projectIds.size ? [...projectIds].sort().join(", ") : "(none found)"}`);
1717
+ cleanupLog(
1718
+ opts,
1719
+ `${prefix} Projects affected: ${projectIds.size ? [...projectIds].sort().join(", ") : "(none found)"}`
1720
+ );
537
1721
  }
538
1722
  if (processesToStop.length) {
539
- console.log(`${prefix} Daemon processes to stop:`);
1723
+ cleanupLog(opts, `${prefix} Daemon processes to stop:`);
540
1724
  for (const proc of processesToStop) {
541
1725
  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.");
1726
+ cleanupLog(opts, `${prefix} pid=${proc.pid} project=${proc.projectId}${ideLabel}`);
1727
+ }
1728
+ } else if (ideSkippedProcesses.length) {
1729
+ cleanupLog(opts, `${prefix} No matching daemon processes for IDE filter ${opts.ide}.`);
1730
+ } else {
1731
+ cleanupLog(opts, `${prefix} No matching daemon processes found.`);
820
1732
  }
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.");
1733
+ if (socketPaths.length) {
1734
+ cleanupLog(opts, `${prefix} Sockets to remove:`);
1735
+ for (const socketPath of socketPaths) {
1736
+ cleanupLog(opts, `${prefix} ${socketPath}`);
1737
+ }
1738
+ } else {
1739
+ cleanupLog(opts, `${prefix} No matching sockets found under ${getMcpBaseDir()}.`);
824
1740
  }
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
- );
1741
+ if (skippedProcesses.length) {
1742
+ cleanupLog(opts, `${prefix} Skipped unrelated daemon processes:`);
1743
+ for (const proc of skippedProcesses) {
1744
+ cleanupLog(opts, `${prefix} pid=${proc.pid} project=${proc.projectId}`);
1745
+ }
830
1746
  }
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
- );
1747
+ if (opts.allProjects && !opts.dryRun) {
1748
+ const ok = opts.assumeYes ? true : await confirm(`${prefix} Proceed with cleanup for ALL projects?`);
1749
+ if (!ok) {
1750
+ cleanupLog(opts, `${prefix} Aborted.`);
1751
+ return {
1752
+ exitCode: 1,
1753
+ workspaceRoot,
1754
+ m1Path,
1755
+ projectId: targetProjectId ?? void 0,
1756
+ killedPids: [],
1757
+ removedSockets: [],
1758
+ skippedProcesses: [...skippedProcesses, ...ideSkippedProcesses],
1759
+ error: opts.assumeYes ? void 0 : "Aborted (--all requires --yes in non-interactive mode)"
1760
+ };
844
1761
  }
845
1762
  }
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" };
1763
+ const killedPids = [];
1764
+ const removedSockets = [];
1765
+ if (opts.dryRun) {
1766
+ cleanupLog(opts, `${prefix} Dry run complete \u2014 no processes stopped, no sockets removed.`);
1767
+ return {
1768
+ exitCode: 0,
1769
+ workspaceRoot,
1770
+ m1Path,
1771
+ projectId: targetProjectId ?? void 0,
1772
+ killedPids: processesToStop.map((p) => p.pid),
1773
+ removedSockets: socketPaths,
1774
+ skippedProcesses: [...skippedProcesses, ...ideSkippedProcesses]
1775
+ };
1776
+ }
1777
+ for (const proc of processesToStop) {
1778
+ try {
1779
+ await killProcess(proc.pid);
1780
+ killedPids.push(proc.pid);
1781
+ cleanupLog(opts, `${prefix} Stopped daemon pid=${proc.pid} project=${proc.projectId}`);
1782
+ } catch (err) {
1783
+ cleanupWarn(opts, `${prefix} Could not stop pid=${proc.pid}: ${String(err)}`);
852
1784
  }
853
1785
  }
854
- if (dryRun) {
855
- return { outcome: existed ? "updated" : "created" };
1786
+ if (killedPids.length) {
1787
+ await new Promise((r) => setTimeout(r, 300));
856
1788
  }
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}`);
1789
+ for (const socketPath of socketPaths) {
1790
+ try {
1791
+ await removeSocket(socketPath);
1792
+ removedSockets.push(socketPath);
1793
+ cleanupLog(opts, `${prefix} Removed socket ${socketPath}`);
1794
+ } catch (err) {
1795
+ const code = err && typeof err === "object" && "code" in err ? err.code : void 0;
1796
+ if (code !== "ENOENT") {
1797
+ cleanupWarn(opts, `${prefix} Could not remove socket ${socketPath}: ${String(err)}`);
1798
+ }
1799
+ }
875
1800
  }
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}`
1801
+ cleanupLog(opts, `${prefix} Done. stopped=${killedPids.length} socketsRemoved=${removedSockets.length}`);
1802
+ return {
1803
+ exitCode: 0,
1804
+ workspaceRoot,
1805
+ m1Path,
1806
+ projectId: targetProjectId ?? void 0,
1807
+ killedPids,
1808
+ removedSockets,
1809
+ skippedProcesses: [...skippedProcesses, ...ideSkippedProcesses]
1810
+ };
1811
+ }
1812
+ function parseCleanupFlags(argv) {
1813
+ let dryRun = false;
1814
+ let allProjects = false;
1815
+ let assumeYes = false;
1816
+ let projectId;
1817
+ let ide;
1818
+ let invalidIde;
1819
+ const unknown = [];
1820
+ for (let i = 0; i < argv.length; i++) {
1821
+ const arg = argv[i];
1822
+ if (arg === "--dry-run") dryRun = true;
1823
+ else if (arg === "--all-projects" || arg === "--all") allProjects = true;
1824
+ else if (arg === "--yes" || arg === "-y") assumeYes = true;
1825
+ else if (arg === "--project-id") {
1826
+ if (i + 1 >= argv.length) {
1827
+ unknown.push("--project-id (missing value)");
1828
+ } else {
1829
+ projectId = argv[++i];
1830
+ }
1831
+ } else if (arg === "--ide") {
1832
+ if (i + 1 >= argv.length) {
1833
+ unknown.push("--ide (missing value)");
1834
+ } else {
1835
+ const value = argv[++i];
1836
+ if (IDE_TYPES.includes(value)) {
1837
+ ide = value;
1838
+ } else {
1839
+ invalidIde = value;
1840
+ }
1841
+ }
1842
+ } else if (arg.startsWith("-")) unknown.push(arg);
1843
+ else unknown.push(arg);
1844
+ }
1845
+ return { dryRun, allProjects, assumeYes, projectId, ide, invalidIde, unknown };
1846
+ }
1847
+ async function cliCleanup(argv) {
1848
+ const { dryRun, allProjects, assumeYes, projectId, ide, invalidIde, unknown } = parseCleanupFlags(argv);
1849
+ if (invalidIde) {
1850
+ console.error(
1851
+ `[cleanup] Invalid --ide value: ${invalidIde}. Expected one of: ${IDE_TYPES.join(", ")}.`
883
1852
  );
884
- } else if (outcome === "skipped") {
885
- console.log(`[setup-ide-files] Cursor global MCP config unchanged: ${configPath}`);
1853
+ return 1;
886
1854
  }
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
- );
1855
+ if (unknown.length) {
1856
+ console.error(`[cleanup] Unknown option(s): ${unknown.join(", ")}`);
1857
+ return 1;
1858
+ }
1859
+ const result = await runCleanup({
1860
+ cwd: process.cwd(),
1861
+ dryRun,
1862
+ allProjects,
1863
+ assumeYes,
1864
+ projectId,
1865
+ ide
1866
+ });
1867
+ if (result.error) {
1868
+ console.error(`[cleanup] ${result.error}`);
1869
+ }
1870
+ return result.exitCode;
893
1871
  }
894
1872
 
895
1873
  // src/jetbrainsMcpConfig.ts
896
- var fs5 = __toESM(require("fs/promises"), 1);
1874
+ var fs6 = __toESM(require("fs/promises"), 1);
897
1875
  var os3 = __toESM(require("os"), 1);
898
- var path5 = __toESM(require("path"), 1);
899
- var import_node_child_process3 = require("child_process");
1876
+ var path8 = __toESM(require("path"), 1);
1877
+ var import_node_child_process4 = require("child_process");
900
1878
 
901
1879
  // src/configUtils.ts
902
1880
  var DEV_API_URL = "http://localhost:3001";
@@ -914,7 +1892,7 @@ function stripLeadingLineComments2(text) {
914
1892
  }
915
1893
  async function pathExists2(filePath) {
916
1894
  try {
917
- await fs5.access(filePath);
1895
+ await fs6.access(filePath);
918
1896
  return true;
919
1897
  } catch {
920
1898
  return false;
@@ -925,12 +1903,12 @@ function formatJetBrainsBackupTimestamp(d = /* @__PURE__ */ new Date()) {
925
1903
  return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
926
1904
  }
927
1905
  function getJetBrainsGlobalMcpConfigPath(homeDir) {
928
- return path5.join(homeDir, ".ai", "mcp", "mcp.json");
1906
+ return path8.join(homeDir, ".ai", "mcp", "mcp.json");
929
1907
  }
930
1908
  function getJetBrainsProjectMcpConfigPaths(repoRoot) {
931
1909
  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") }
1910
+ { kind: "project-ai", path: path8.join(repoRoot, ".ai", "mcp", "mcp.json") },
1911
+ { kind: "project-ij", path: path8.join(repoRoot, ".ij", "mcp", "mcp.json") }
934
1912
  ];
935
1913
  }
936
1914
  function getKnownJetBrainsMcpConfigLocations(homeDir, repoRoot) {
@@ -941,7 +1919,7 @@ function getKnownJetBrainsMcpConfigLocations(homeDir, repoRoot) {
941
1919
  }
942
1920
  async function isZeroByteConfigFile(filePath) {
943
1921
  if (!await pathExists2(filePath)) return false;
944
- const stat2 = await fs5.stat(filePath);
1922
+ const stat2 = await fs6.stat(filePath);
945
1923
  return stat2.size === 0;
946
1924
  }
947
1925
  function buildMemoraoneJetBrainsMcpServer(options) {
@@ -965,7 +1943,7 @@ function mergeJetBrainsMcpConfigObject(existing, memoraone) {
965
1943
  mcpServers.memoraone = memoraone;
966
1944
  return { ...base, mcpServers };
967
1945
  }
968
- function memoraoneServerMatches2(server, expected) {
1946
+ function memoraoneServerMatches(server, expected) {
969
1947
  if (!server || typeof server !== "object") return false;
970
1948
  const s = server;
971
1949
  if (s.command !== expected.command) return false;
@@ -993,20 +1971,20 @@ function validateJetBrainsMcpConfig(parsed, expected) {
993
1971
  throw new Error("[setup-ide-files] JetBrains MCP config missing mcpServers object.");
994
1972
  }
995
1973
  const memoraone = mcpServers.memoraone;
996
- if (!memoraoneServerMatches2(memoraone, expected)) {
1974
+ if (!memoraoneServerMatches(memoraone, expected)) {
997
1975
  throw new Error(
998
1976
  "[setup-ide-files] JetBrains MCP config mcpServers.memoraone is missing or invalid."
999
1977
  );
1000
1978
  }
1001
1979
  }
1002
1980
  async function readJsonConfig(filePath) {
1003
- const raw = await fs5.readFile(filePath, "utf8");
1981
+ const raw = await fs6.readFile(filePath, "utf8");
1004
1982
  if (raw.trim() === "") return null;
1005
1983
  return JSON.parse(stripLeadingLineComments2(raw));
1006
1984
  }
1007
1985
  async function backupConfigFile(filePath) {
1008
1986
  const backupPath = `${filePath}.bak-${formatJetBrainsBackupTimestamp()}`;
1009
- await fs5.copyFile(filePath, backupPath);
1987
+ await fs6.copyFile(filePath, backupPath);
1010
1988
  return backupPath;
1011
1989
  }
1012
1990
  async function repairZeroByteConfigFile(filePath, dryRun) {
@@ -1017,7 +1995,7 @@ async function repairZeroByteConfigFile(filePath, dryRun) {
1017
1995
  return { repaired: true, backupPath: `${filePath}.bak-<timestamp>` };
1018
1996
  }
1019
1997
  const backupPath = await backupConfigFile(filePath);
1020
- await fs5.unlink(filePath);
1998
+ await fs6.unlink(filePath);
1021
1999
  return { repaired: true, backupPath };
1022
2000
  }
1023
2001
  function configHasMemoraone(parsed) {
@@ -1048,24 +2026,24 @@ async function removeMemoraoneFromProjectConfig(options) {
1048
2026
  delete mcpServers.memoraone;
1049
2027
  const hasOtherServers = Object.keys(mcpServers).length > 0;
1050
2028
  if (!hasOtherServers) {
1051
- await fs5.unlink(configPath);
2029
+ await fs6.unlink(configPath);
1052
2030
  return { changed: true, backupPath };
1053
2031
  }
1054
2032
  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");
2033
+ await fs6.mkdir(path8.dirname(configPath), { recursive: true });
2034
+ await fs6.writeFile(configPath, JSON.stringify(next, null, 2) + "\n", "utf8");
1057
2035
  return { changed: true, backupPath };
1058
2036
  }
1059
2037
  async function resolveLocalCliPathAsync() {
1060
- const here = process.argv[1] ? path5.dirname(path5.resolve(process.argv[1])) : process.cwd();
2038
+ const here = process.argv[1] ? path8.dirname(path8.resolve(process.argv[1])) : process.cwd();
1061
2039
  const candidates = [
1062
- path5.join(here, "cli.cjs"),
1063
- path5.join(here, "..", "dist", "cli.cjs"),
1064
- path5.join(here, "..", "..", "dist", "cli.cjs")
2040
+ path8.join(here, "cli.cjs"),
2041
+ path8.join(here, "..", "dist", "cli.cjs"),
2042
+ path8.join(here, "..", "..", "dist", "cli.cjs")
1065
2043
  ];
1066
2044
  for (const candidate of candidates) {
1067
2045
  if (await pathExists2(candidate)) {
1068
- return path5.resolve(candidate);
2046
+ return path8.resolve(candidate);
1069
2047
  }
1070
2048
  }
1071
2049
  return null;
@@ -1107,7 +2085,7 @@ async function buildJetBrainsMemoraoneServer(options) {
1107
2085
  async function verifyJetBrainsMcpHandshake(options) {
1108
2086
  const timeoutMs = options.timeoutMs ?? 15e3;
1109
2087
  const { server } = options;
1110
- return new Promise((resolve7) => {
2088
+ return new Promise((resolve8) => {
1111
2089
  let settled = false;
1112
2090
  const finish = (ok, detail) => {
1113
2091
  if (settled) return;
@@ -1117,9 +2095,9 @@ async function verifyJetBrainsMcpHandshake(options) {
1117
2095
  child.kill();
1118
2096
  } catch {
1119
2097
  }
1120
- resolve7({ ok, detail });
2098
+ resolve8({ ok, detail });
1121
2099
  };
1122
- const child = (0, import_node_child_process3.spawn)(server.command, [...server.args], {
2100
+ const child = (0, import_node_child_process4.spawn)(server.command, [...server.args], {
1123
2101
  env: { ...process.env, ...server.env },
1124
2102
  stdio: ["pipe", "pipe", "pipe"]
1125
2103
  });
@@ -1186,7 +2164,7 @@ async function verifyJetBrainsMcpHandshake(options) {
1186
2164
  async function setupJetBrainsMcpConfig(options) {
1187
2165
  const homeDir = options.homeDir ?? os3.homedir();
1188
2166
  const globalPath = options.globalConfigPath ?? getJetBrainsGlobalMcpConfigPath(homeDir);
1189
- const m1Path = path5.join(path5.resolve(options.repoRoot), "memoraone.m1");
2167
+ const m1Path = path8.join(path8.resolve(options.repoRoot), "memoraone.m1");
1190
2168
  const repairActions = [];
1191
2169
  const allLocations = getKnownJetBrainsMcpConfigLocations(homeDir, options.repoRoot);
1192
2170
  for (const location of allLocations) {
@@ -1241,7 +2219,7 @@ async function setupJetBrainsMcpConfig(options) {
1241
2219
  path: globalPath,
1242
2220
  backupPath: backupPath2
1243
2221
  });
1244
- await fs5.unlink(globalPath);
2222
+ await fs6.unlink(globalPath);
1245
2223
  existing = null;
1246
2224
  }
1247
2225
  }
@@ -1250,7 +2228,7 @@ async function setupJetBrainsMcpConfig(options) {
1250
2228
  const body = JSON.stringify(merged, null, 2) + "\n";
1251
2229
  if (existed && existing) {
1252
2230
  const currentMemoraone = existing.mcpServers && typeof existing.mcpServers === "object" && !Array.isArray(existing.mcpServers) ? existing.mcpServers.memoraone : void 0;
1253
- if (memoraoneServerMatches2(currentMemoraone, memoraone)) {
2231
+ if (memoraoneServerMatches(currentMemoraone, memoraone)) {
1254
2232
  repairActions.push({ type: "wrote-global-config", path: globalPath, outcome: "skipped" });
1255
2233
  return { outcome: "skipped", repairActions, memoraone };
1256
2234
  }
@@ -1264,9 +2242,9 @@ async function setupJetBrainsMcpConfig(options) {
1264
2242
  if (existed) {
1265
2243
  backupPath = await backupConfigFile(globalPath);
1266
2244
  }
1267
- await fs5.mkdir(path5.dirname(globalPath), { recursive: true });
1268
- await fs5.writeFile(globalPath, body, "utf8");
1269
- const verifyRaw = await fs5.readFile(globalPath, "utf8");
2245
+ await fs6.mkdir(path8.dirname(globalPath), { recursive: true });
2246
+ await fs6.writeFile(globalPath, body, "utf8");
2247
+ const verifyRaw = await fs6.readFile(globalPath, "utf8");
1270
2248
  const verifyParsed = JSON.parse(stripLeadingLineComments2(verifyRaw));
1271
2249
  validateJetBrainsMcpConfig(verifyParsed, memoraone);
1272
2250
  const outcome = existed ? "updated" : "created";
@@ -1336,15 +2314,15 @@ function buildMemoraoneMcpServer(ideType, command = "npx") {
1336
2314
  };
1337
2315
  }
1338
2316
  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)) {
2317
+ const normRoot = path9.resolve(repoRoot) + path9.sep;
2318
+ const normPath = path9.resolve(absPath);
2319
+ if (normPath !== path9.resolve(repoRoot) && !normPath.startsWith(normRoot)) {
1342
2320
  throw new Error(`[setup-ide-files] Refusing to write outside repo root: ${absPath}`);
1343
2321
  }
1344
2322
  }
1345
2323
  async function pathExists3(filePath) {
1346
2324
  try {
1347
- await fs6.access(filePath);
2325
+ await fs7.access(filePath);
1348
2326
  return true;
1349
2327
  } catch {
1350
2328
  return false;
@@ -1365,12 +2343,12 @@ ${GITIGNORE_MEMORAONE_ENTRY}
1365
2343
  }
1366
2344
  async function ensureGitignoreMemoraone(repoRoot, opts) {
1367
2345
  if (opts.noGitignore) return "skipped";
1368
- const abs = path6.join(repoRoot, ".gitignore");
2346
+ const abs = path9.join(repoRoot, ".gitignore");
1369
2347
  assertUnderRepoRoot(repoRoot, abs);
1370
2348
  let prior = "";
1371
2349
  let existed = false;
1372
2350
  try {
1373
- prior = await fs6.readFile(abs, "utf8");
2351
+ prior = await fs7.readFile(abs, "utf8");
1374
2352
  existed = true;
1375
2353
  } catch (err) {
1376
2354
  const code = err && typeof err === "object" && "code" in err ? err.code : void 0;
@@ -1381,22 +2359,22 @@ async function ensureGitignoreMemoraone(repoRoot, opts) {
1381
2359
  const separator = existed && prior.length > 0 ? prior.endsWith("\n") ? "\n" : "\n\n" : "";
1382
2360
  const next = (existed ? prior : "") + separator + block;
1383
2361
  if (opts.dryRun) return existed ? "updated" : "created";
1384
- await fs6.writeFile(abs, next, "utf8");
2362
+ await fs7.writeFile(abs, next, "utf8");
1385
2363
  return existed ? "updated" : "created";
1386
2364
  }
1387
2365
  async function findRepoRoot(startDir) {
1388
- let current = path6.resolve(startDir);
1389
- const root = path6.parse(current).root;
2366
+ let current = path9.resolve(startDir);
2367
+ const root = path9.parse(current).root;
1390
2368
  while (true) {
1391
- const gitPath = path6.join(current, ".git");
1392
- const m1Path = path6.join(current, "memoraone.m1");
2369
+ const gitPath = path9.join(current, ".git");
2370
+ const m1Path = path9.join(current, "memoraone.m1");
1393
2371
  if (await pathExists3(gitPath) || await pathExists3(m1Path)) {
1394
2372
  return current;
1395
2373
  }
1396
2374
  if (current === root) {
1397
2375
  return null;
1398
2376
  }
1399
- current = path6.dirname(current);
2377
+ current = path9.dirname(current);
1400
2378
  }
1401
2379
  }
1402
2380
  function stripLeadingLineComments3(text) {
@@ -1407,7 +2385,7 @@ function cursorRuleBody() {
1407
2385
 
1408
2386
  ## MemoraOne MCP (IDE agent only)
1409
2387
 
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.
2388
+ This repository uses **MemoraOne** via the MCP server named **memoraone** (repo-level .cursor/mcp.json). This guidance applies to the **IDE coding agent** only \u2014 not the MemoraOne Studio runtime path.
1411
2389
 
1412
2390
  ### Tools
1413
2391
 
@@ -1449,43 +2427,47 @@ function buildVscodeMcpJsonBody(existing) {
1449
2427
  const merged = { ...base, servers };
1450
2428
  return mcpJsonHeader() + JSON.stringify(merged, null, 2) + "\n";
1451
2429
  }
2430
+ function buildCursorMcpJsonBody(existing, npxPath, repoRoot) {
2431
+ const merged = mergeCursorRepoMcpConfigObject(existing, npxPath, repoRoot);
2432
+ return mcpJsonHeader() + JSON.stringify(merged, null, 2) + "\n";
2433
+ }
1452
2434
  async function writeManagedMarkdown(repoRoot, relPath, fullContent, opts) {
1453
- const abs = path6.join(repoRoot, relPath);
2435
+ const abs = path9.join(repoRoot, relPath);
1454
2436
  assertUnderRepoRoot(repoRoot, abs);
1455
2437
  let prior = "";
1456
2438
  let existed = false;
1457
2439
  try {
1458
- prior = await fs6.readFile(abs, "utf8");
2440
+ prior = await fs7.readFile(abs, "utf8");
1459
2441
  existed = true;
1460
2442
  } catch (err) {
1461
2443
  if (err?.code !== "ENOENT") throw err;
1462
2444
  }
1463
2445
  if (!existed) {
1464
2446
  if (opts.dryRun) return "created";
1465
- await fs6.mkdir(path6.dirname(abs), { recursive: true });
1466
- await fs6.writeFile(abs, fullContent, "utf8");
2447
+ await fs7.mkdir(path9.dirname(abs), { recursive: true });
2448
+ await fs7.writeFile(abs, fullContent, "utf8");
1467
2449
  return "created";
1468
2450
  }
1469
2451
  if (prior.includes(MANAGED_MARKER)) {
1470
2452
  if (prior === fullContent) return "skipped";
1471
2453
  if (opts.dryRun) return "updated";
1472
- await fs6.mkdir(path6.dirname(abs), { recursive: true });
1473
- await fs6.writeFile(abs, fullContent, "utf8");
2454
+ await fs7.mkdir(path9.dirname(abs), { recursive: true });
2455
+ await fs7.writeFile(abs, fullContent, "utf8");
1474
2456
  return "updated";
1475
2457
  }
1476
2458
  if (!opts.force) return "skipped-untracked";
1477
2459
  if (opts.dryRun) return "updated";
1478
- await fs6.mkdir(path6.dirname(abs), { recursive: true });
1479
- await fs6.writeFile(abs, fullContent, "utf8");
2460
+ await fs7.mkdir(path9.dirname(abs), { recursive: true });
2461
+ await fs7.writeFile(abs, fullContent, "utf8");
1480
2462
  return "updated";
1481
2463
  }
1482
2464
  async function writeIdeMcpJson(repoRoot, relPath, buildBody, opts) {
1483
- const abs = path6.join(repoRoot, relPath);
2465
+ const abs = path9.join(repoRoot, relPath);
1484
2466
  assertUnderRepoRoot(repoRoot, abs);
1485
2467
  let raw = "";
1486
2468
  let existed = false;
1487
2469
  try {
1488
- raw = await fs6.readFile(abs, "utf8");
2470
+ raw = await fs7.readFile(abs, "utf8");
1489
2471
  existed = true;
1490
2472
  } catch (err) {
1491
2473
  if (err?.code !== "ENOENT") throw err;
@@ -1493,8 +2475,8 @@ async function writeIdeMcpJson(repoRoot, relPath, buildBody, opts) {
1493
2475
  if (!existed) {
1494
2476
  const body = buildBody(null);
1495
2477
  if (opts.dryRun) return "created";
1496
- await fs6.mkdir(path6.dirname(abs), { recursive: true });
1497
- await fs6.writeFile(abs, body, "utf8");
2478
+ await fs7.mkdir(path9.dirname(abs), { recursive: true });
2479
+ await fs7.writeFile(abs, body, "utf8");
1498
2480
  return "created";
1499
2481
  }
1500
2482
  const managed = raw.includes(MANAGED_MARKER);
@@ -1509,8 +2491,8 @@ async function writeIdeMcpJson(repoRoot, relPath, buildBody, opts) {
1509
2491
  const next = buildBody(parsed);
1510
2492
  if (managed && next === raw) return "skipped";
1511
2493
  if (opts.dryRun) return "updated";
1512
- await fs6.mkdir(path6.dirname(abs), { recursive: true });
1513
- await fs6.writeFile(abs, next, "utf8");
2494
+ await fs7.mkdir(path9.dirname(abs), { recursive: true });
2495
+ await fs7.writeFile(abs, next, "utf8");
1514
2496
  return "updated";
1515
2497
  }
1516
2498
  function parseSetupIdeFlags(argv) {
@@ -1567,9 +2549,144 @@ function summarizeOutcomes(outcomes) {
1567
2549
  }
1568
2550
  console.log(lines.join("\n"));
1569
2551
  }
2552
+ function ideTypesFromSetupTargets(targets) {
2553
+ const ides = [];
2554
+ if (targets.cursor) ides.push("cursor");
2555
+ if (targets.vscode) ides.push("copilot-vscode");
2556
+ if (targets.jetbrains) ides.push("jetbrains");
2557
+ return ides;
2558
+ }
2559
+ function setupTargetsAllIdes(targets) {
2560
+ return targets.cursor && targets.vscode && targets.jetbrains;
2561
+ }
2562
+ function aggregateCleanupResults(results) {
2563
+ const killedPids = /* @__PURE__ */ new Set();
2564
+ const removedSockets = /* @__PURE__ */ new Set();
2565
+ const skippedUnrelated = /* @__PURE__ */ new Map();
2566
+ let foundDaemonCount = 0;
2567
+ let error;
2568
+ for (const result of results) {
2569
+ if (result.error) error = result.error;
2570
+ for (const pid of result.killedPids) {
2571
+ killedPids.add(pid);
2572
+ foundDaemonCount += 1;
2573
+ }
2574
+ for (const socketPath of result.removedSockets) {
2575
+ removedSockets.add(socketPath);
2576
+ }
2577
+ for (const proc of result.skippedProcesses) {
2578
+ if (proc.projectId !== result.projectId) {
2579
+ skippedUnrelated.set(proc.pid, proc);
2580
+ }
2581
+ }
2582
+ }
2583
+ return {
2584
+ foundDaemonCount,
2585
+ stoppedDaemonCount: killedPids.size,
2586
+ removedSocketCount: removedSockets.size,
2587
+ skippedUnrelatedDaemonCount: skippedUnrelated.size,
2588
+ error
2589
+ };
2590
+ }
2591
+ function logSetupIdeCleanupSummary(cleanup) {
2592
+ if (cleanup.skipped) return;
2593
+ console.log(`[setup-ide-files] Project id: ${cleanup.projectId}`);
2594
+ if (cleanup.foundDaemonCount > 0) {
2595
+ console.log(`[setup-ide-files] Found ${cleanup.foundDaemonCount} stale daemon(s)`);
2596
+ if (cleanup.dryRun) {
2597
+ console.log(`[setup-ide-files] Would stop ${cleanup.foundDaemonCount} stale daemon(s)`);
2598
+ } else if (cleanup.stoppedDaemonCount > 0) {
2599
+ console.log(`[setup-ide-files] Stopped ${cleanup.stoppedDaemonCount} stale daemon(s)`);
2600
+ }
2601
+ } else {
2602
+ console.log("[setup-ide-files] No stale daemons found for this project and IDE target(s).");
2603
+ }
2604
+ if (cleanup.removedSocketCount > 0) {
2605
+ if (cleanup.dryRun) {
2606
+ console.log(`[setup-ide-files] Would remove ${cleanup.removedSocketCount} stale socket(s)`);
2607
+ } else {
2608
+ console.log(`[setup-ide-files] Removed ${cleanup.removedSocketCount} stale socket(s)`);
2609
+ }
2610
+ }
2611
+ if (cleanup.skippedUnrelatedDaemonCount > 0) {
2612
+ console.log(
2613
+ `[setup-ide-files] Skipped ${cleanup.skippedUnrelatedDaemonCount} unrelated project daemon(s)`
2614
+ );
2615
+ }
2616
+ }
2617
+ async function runSetupIdeDaemonCleanup(opts) {
2618
+ const ides = ideTypesFromSetupTargets(opts.targets);
2619
+ if (ides.length === 0) {
2620
+ return {
2621
+ skipped: true,
2622
+ skipReason: "no-targets",
2623
+ foundDaemonCount: 0,
2624
+ stoppedDaemonCount: 0,
2625
+ removedSocketCount: 0,
2626
+ skippedUnrelatedDaemonCount: 0,
2627
+ dryRun: opts.dryRun
2628
+ };
2629
+ }
2630
+ const target = await resolveCleanupTarget(opts.repoRoot);
2631
+ if ("error" in target) {
2632
+ return {
2633
+ skipped: true,
2634
+ skipReason: "no-m1",
2635
+ foundDaemonCount: 0,
2636
+ stoppedDaemonCount: 0,
2637
+ removedSocketCount: 0,
2638
+ skippedUnrelatedDaemonCount: 0,
2639
+ dryRun: opts.dryRun
2640
+ };
2641
+ }
2642
+ const baseCleanupOpts = {
2643
+ cwd: opts.repoRoot,
2644
+ dryRun: opts.dryRun,
2645
+ allProjects: false,
2646
+ assumeYes: true,
2647
+ projectId: target.projectId,
2648
+ quiet: true,
2649
+ listProcesses: opts.listProcesses,
2650
+ listSocketPaths: opts.listSocketPaths,
2651
+ killProcess: opts.killProcess,
2652
+ removeSocket: opts.removeSocket
2653
+ };
2654
+ const results = [];
2655
+ if (setupTargetsAllIdes(opts.targets)) {
2656
+ results.push(await runCleanup(baseCleanupOpts));
2657
+ } else {
2658
+ for (const ide of ides) {
2659
+ results.push(await runCleanup({ ...baseCleanupOpts, ide }));
2660
+ }
2661
+ }
2662
+ const aggregated = aggregateCleanupResults(results);
2663
+ const exitError = results.find((r) => r.exitCode !== 0)?.error ?? aggregated.error;
2664
+ return {
2665
+ skipped: false,
2666
+ projectId: target.projectId,
2667
+ foundDaemonCount: aggregated.foundDaemonCount,
2668
+ stoppedDaemonCount: opts.dryRun ? 0 : aggregated.stoppedDaemonCount,
2669
+ removedSocketCount: aggregated.removedSocketCount,
2670
+ skippedUnrelatedDaemonCount: aggregated.skippedUnrelatedDaemonCount,
2671
+ dryRun: opts.dryRun,
2672
+ error: exitError
2673
+ };
2674
+ }
2675
+ function restartIdeInstruction(targets) {
2676
+ const names = [];
2677
+ if (targets.cursor) names.push("Cursor");
2678
+ if (targets.vscode) names.push("VS Code");
2679
+ if (targets.jetbrains) names.push("JetBrains IDE");
2680
+ if (names.length === 0) return "Fully quit your IDE and reopen this repo for MCP changes to take effect.";
2681
+ if (names.length === 1) {
2682
+ return `Fully quit ${names[0]} and reopen this repo for MCP changes to take effect.`;
2683
+ }
2684
+ const last = names.pop();
2685
+ return `Fully quit ${names.join(", ")} and ${last}, then reopen this repo for MCP changes to take effect.`;
2686
+ }
1570
2687
  async function runSetupIdeFiles(o) {
1571
2688
  const outcomes = {};
1572
- let cursorGlobalMcp;
2689
+ let cursorMcp;
1573
2690
  let jetbrainsMcp;
1574
2691
  const repoRoot = await findRepoRoot(o.cwd);
1575
2692
  if (!repoRoot) {
@@ -1580,6 +2697,27 @@ async function runSetupIdeFiles(o) {
1580
2697
  error: "[setup-ide-files] No repo root found (looked for .git or memoraone.m1)."
1581
2698
  };
1582
2699
  }
2700
+ let daemonCleanup;
2701
+ if (!o.skipDaemonCleanup) {
2702
+ daemonCleanup = await runSetupIdeDaemonCleanup({
2703
+ repoRoot,
2704
+ targets: o.targets,
2705
+ dryRun: o.dryRun,
2706
+ listProcesses: o.listDaemonProcesses,
2707
+ listSocketPaths: o.listCleanupSocketPaths,
2708
+ killProcess: o.killDaemonProcess,
2709
+ removeSocket: o.removeCleanupSocket
2710
+ });
2711
+ if (daemonCleanup.error && !daemonCleanup.skipped) {
2712
+ return {
2713
+ exitCode: 1,
2714
+ repoRoot,
2715
+ outcomes,
2716
+ daemonCleanup,
2717
+ error: `[setup-ide-files] Daemon cleanup failed: ${daemonCleanup.error}`
2718
+ };
2719
+ }
2720
+ }
1583
2721
  outcomes[".gitignore"] = await ensureGitignoreMemoraone(repoRoot, {
1584
2722
  dryRun: o.dryRun,
1585
2723
  noGitignore: o.noGitignore ?? false
@@ -1590,19 +2728,6 @@ description: MemoraOne MCP \u2014 IDE agent instructions
1590
2728
 
1591
2729
  ` + cursorRuleBody();
1592
2730
  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
2731
  let npxPath;
1607
2732
  if (o.npxPathOverride !== void 0) {
1608
2733
  npxPath = o.npxPathOverride;
@@ -1614,7 +2739,7 @@ description: MemoraOne MCP \u2014 IDE agent instructions
1614
2739
  exitCode: 1,
1615
2740
  repoRoot,
1616
2741
  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."
2742
+ error: "[setup-ide-files] Could not resolve a working npx executable. Install Node.js/npm or ensure npx is on PATH before configuring Cursor MCP."
1618
2743
  };
1619
2744
  }
1620
2745
  outcomes[".cursor/rules/memoraone-mcp.mdc"] = await writeManagedMarkdown(
@@ -1623,29 +2748,58 @@ description: MemoraOne MCP \u2014 IDE agent instructions
1623
2748
  cursorContent,
1624
2749
  { force: o.force, dryRun: o.dryRun }
1625
2750
  );
2751
+ outcomes[".cursor/mcp.json"] = await writeIdeMcpJson(
2752
+ repoRoot,
2753
+ ".cursor/mcp.json",
2754
+ (existing) => buildCursorMcpJsonBody(existing, npxPath, repoRoot),
2755
+ { force: o.force, dryRun: o.dryRun }
2756
+ );
2757
+ const repoConfigPath = getCursorRepoMcpConfigPath(repoRoot);
2758
+ const repoOutcome = outcomes[".cursor/mcp.json"] ?? "skipped";
2759
+ let globalConfigPath;
2760
+ let globalMemoraoneRemoved = false;
2761
+ let globalBackupPath;
2762
+ const globalDetection = await detectCursorGlobalMcpConfig({
2763
+ homeDir: o.homeDir,
2764
+ explicitPath: o.cursorGlobalMcpConfigPath
2765
+ });
2766
+ if (!globalDetection.ok) {
2767
+ return {
2768
+ exitCode: 1,
2769
+ repoRoot,
2770
+ outcomes,
2771
+ error: `${globalDetection.error}
2772
+ ${globalDetection.candidates.join("\n ")}`
2773
+ };
2774
+ }
2775
+ globalConfigPath = globalDetection.path;
1626
2776
  try {
1627
- const globalSetup = await setupCursorGlobalMcpConfig({
1628
- configPath: detection.path,
1629
- npxPath,
2777
+ const removal = await removeMemoraoneFromCursorGlobalConfig({
2778
+ configPath: globalDetection.path,
1630
2779
  dryRun: o.dryRun
1631
2780
  });
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;
2781
+ if (removal.changed) {
2782
+ globalMemoraoneRemoved = true;
2783
+ globalBackupPath = removal.backupPath;
2784
+ outcomes[`cursor-global-removed:${globalDetection.path}`] = o.dryRun ? "updated" : "updated";
2785
+ }
1639
2786
  } catch (err) {
1640
2787
  const message = err instanceof Error ? err.message : String(err);
1641
2788
  return {
1642
2789
  exitCode: 1,
1643
2790
  repoRoot,
1644
2791
  outcomes,
1645
- cursorGlobalMcp: { configPath: detection.path, outcome: "skipped", npxPath },
1646
2792
  error: message
1647
2793
  };
1648
2794
  }
2795
+ cursorMcp = {
2796
+ repoConfigPath,
2797
+ repoOutcome,
2798
+ npxPath,
2799
+ globalConfigPath,
2800
+ globalMemoraoneRemoved,
2801
+ globalBackupPath
2802
+ };
1649
2803
  }
1650
2804
  if (o.targets.vscode) {
1651
2805
  outcomes[".vscode/mcp.json"] = await writeIdeMcpJson(
@@ -1701,12 +2855,12 @@ description: MemoraOne MCP \u2014 IDE agent instructions
1701
2855
  exitCode: 1,
1702
2856
  repoRoot,
1703
2857
  outcomes,
1704
- cursorGlobalMcp,
2858
+ cursorMcp,
1705
2859
  error: message
1706
2860
  };
1707
2861
  }
1708
2862
  }
1709
- return { exitCode: 0, repoRoot, outcomes, cursorGlobalMcp, jetbrainsMcp };
2863
+ return { exitCode: 0, repoRoot, outcomes, cursorMcp, jetbrainsMcp, daemonCleanup };
1710
2864
  }
1711
2865
  async function cliSetupIdeFiles(argv) {
1712
2866
  const { targets, force, dryRun, noGitignore, cleanup, devMode, repair, unknown } = parseSetupIdeFlags(argv);
@@ -1734,8 +2888,11 @@ async function cliSetupIdeFiles(argv) {
1734
2888
  if (result.repoRoot) {
1735
2889
  console.log(`[setup-ide-files] Repo root: ${result.repoRoot}`);
1736
2890
  }
1737
- if (targets.cursor && result.cursorGlobalMcp) {
1738
- logCursorGlobalMcpCliSummary(result.cursorGlobalMcp, dryRun);
2891
+ if (result.daemonCleanup && !result.daemonCleanup.skipped) {
2892
+ logSetupIdeCleanupSummary(result.daemonCleanup);
2893
+ }
2894
+ if (targets.cursor && result.cursorMcp) {
2895
+ logCursorMcpCliSummary(result.cursorMcp, dryRun);
1739
2896
  }
1740
2897
  if (targets.jetbrains && result.jetbrainsMcp) {
1741
2898
  logJetBrainsMcpCliSummary(result.jetbrainsMcp, dryRun);
@@ -1743,9 +2900,13 @@ async function cliSetupIdeFiles(argv) {
1743
2900
  summarizeOutcomes(result.outcomes);
1744
2901
  if (dryRun) {
1745
2902
  console.log("[setup-ide-files] Dry run: no files written.");
2903
+ if (result.daemonCleanup && !result.daemonCleanup.skipped) {
2904
+ console.log("[setup-ide-files] Dry run: no daemons stopped, no sockets removed.");
2905
+ }
1746
2906
  }
2907
+ console.log(`[setup-ide-files] ${restartIdeInstruction(targets)}`);
1747
2908
  if (cleanup) {
1748
- console.log("[setup-ide-files] Running project-scoped cleanup...");
2909
+ console.log("[setup-ide-files] Running additional full-project cleanup (--cleanup)...");
1749
2910
  const cleanupResult = await runCleanup({
1750
2911
  cwd: process.cwd(),
1751
2912
  dryRun,
@@ -1756,13 +2917,6 @@ async function cliSetupIdeFiles(argv) {
1756
2917
  console.error(`[setup-ide-files] cleanup failed: ${cleanupResult.error}`);
1757
2918
  return cleanupResult.exitCode;
1758
2919
  }
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
2920
  }
1767
2921
  return 0;
1768
2922
  }
@@ -1799,92 +2953,9 @@ if (args[0] === "cleanup") {
1799
2953
  process.exit(1);
1800
2954
  });
1801
2955
  } 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}
2956
+ runBridgeProxy({ cliPath: process.argv[1] }).catch((err) => {
2957
+ process.stderr.write(`[memoraone-mcp][bridge] fatal: ${String(err)}
1840
2958
  `);
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
2959
  process.exit(1);
1889
2960
  });
1890
2961
  }