@memoraone/mcp 0.1.22 → 0.1.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.cjs 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.22",
33
+ version: "0.1.23",
34
34
  type: "module",
35
35
  main: "dist/index.cjs",
36
36
  bin: {
@@ -67,22 +67,84 @@ var require_package = __commonJS({
67
67
  });
68
68
 
69
69
  // src/cli.ts
70
- var path4 = __toESM(require("path"), 1);
70
+ var path5 = __toESM(require("path"), 1);
71
71
  var net = __toESM(require("net"), 1);
72
- var import_node_child_process = require("child_process");
72
+ var import_node_child_process2 = require("child_process");
73
73
 
74
74
  // src/socketPaths.ts
75
75
  var os = __toESM(require("os"), 1);
76
76
  var path = __toESM(require("path"), 1);
77
77
  var fs = __toESM(require("fs"), 1);
78
78
  var BASE_DIR = process.env.MEMORAONE_MCP_LOCK_DIR || path.join(os.homedir(), ".memoraone-mcp");
79
- function getSocketPath(projectId) {
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
+ }
80
110
  return path.join(BASE_DIR, `mcp-${projectId}.sock`);
81
111
  }
112
+ function getDaemonSocketPath(projectId, env = process.env) {
113
+ return getSocketPath(projectId, resolveIdeTypeFromEnv(env));
114
+ }
82
115
  function ensureBaseDir() {
83
116
  fs.mkdirSync(BASE_DIR, { recursive: true });
84
117
  return BASE_DIR;
85
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";
145
+ }
146
+ return socketIde === ide;
147
+ }
86
148
 
87
149
  // src/projectBinding.ts
88
150
  var fs2 = __toESM(require("fs/promises"), 1);
@@ -230,8 +292,400 @@ function encodeResolvedBinding(binding) {
230
292
  }
231
293
 
232
294
  // src/setupIdeFiles.ts
295
+ var fs4 = __toESM(require("fs/promises"), 1);
296
+ var path4 = __toESM(require("path"), 1);
297
+
298
+ // src/cleanup.ts
233
299
  var fs3 = __toESM(require("fs/promises"), 1);
234
300
  var path3 = __toESM(require("path"), 1);
301
+ var readline = __toESM(require("readline/promises"), 1);
302
+ var import_node_child_process = require("child_process");
303
+ var import_node_util = require("util");
304
+ var import_node_process = require("process");
305
+ var execFileAsync = (0, import_node_util.promisify)(import_node_child_process.execFile);
306
+ var DAEMON_PROJECT_ID_RE = /--project-id\s+([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/i;
307
+ var PROJECT_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
308
+ var MEMORAONE_MCP_COMMAND_RE = /memoraone-mcp|memoraOne-mcp|@memoraone\/mcp/;
309
+ var CLEANUP_PROJECT_ID_REQUIRED_ERROR = "Provide --project-id <id> or run from a folder containing memoraone.m1.";
310
+ function isMemoraoneMcpCommandLine(commandLine) {
311
+ return MEMORAONE_MCP_COMMAND_RE.test(commandLine);
312
+ }
313
+ function parseDaemonProjectIdFromCommandLine(commandLine) {
314
+ if (!commandLine.includes("--daemon")) {
315
+ return null;
316
+ }
317
+ const match = commandLine.match(DAEMON_PROJECT_ID_RE);
318
+ return match ? match[1].toLowerCase() : null;
319
+ }
320
+ function parseDaemonIdeFromCommandLine(commandLine) {
321
+ if (!commandLine.includes("--daemon")) {
322
+ return void 0;
323
+ }
324
+ return parseIdeTypeFromCommandLine(commandLine);
325
+ }
326
+ function parseDaemonProcessLines(lines) {
327
+ const processes = [];
328
+ for (const line of lines) {
329
+ const trimmed = line.trim();
330
+ if (!trimmed) continue;
331
+ const spaceIdx = trimmed.indexOf(" ");
332
+ if (spaceIdx <= 0) continue;
333
+ const pid = Number.parseInt(trimmed.slice(0, spaceIdx), 10);
334
+ if (!Number.isFinite(pid) || pid <= 0) continue;
335
+ const command = trimmed.slice(spaceIdx + 1);
336
+ if (!isMemoraoneMcpCommandLine(command)) continue;
337
+ const projectId = parseDaemonProjectIdFromCommandLine(command);
338
+ if (projectId === null) continue;
339
+ const ide = parseDaemonIdeFromCommandLine(command);
340
+ processes.push({ pid, command, projectId, ...ide !== void 0 ? { ide } : {} });
341
+ }
342
+ return processes;
343
+ }
344
+ function normalizeCleanupProjectId(projectId) {
345
+ const trimmed = projectId.trim();
346
+ if (!PROJECT_ID_RE.test(trimmed)) {
347
+ return { error: `Invalid project id: ${projectId}` };
348
+ }
349
+ return trimmed.toLowerCase();
350
+ }
351
+ async function defaultListDaemonProcesses() {
352
+ const { stdout } = await execFileAsync("ps", ["-eo", "pid=,args="], {
353
+ maxBuffer: 10 * 1024 * 1024
354
+ });
355
+ return parseDaemonProcessLines(stdout.split("\n"));
356
+ }
357
+ async function defaultListSocketPaths(projectId) {
358
+ const baseDir = getMcpBaseDir();
359
+ let entries;
360
+ try {
361
+ entries = await fs3.readdir(baseDir);
362
+ } catch (err) {
363
+ const code = err && typeof err === "object" && "code" in err ? err.code : void 0;
364
+ if (code === "ENOENT") {
365
+ return [];
366
+ }
367
+ throw err;
368
+ }
369
+ const paths = [];
370
+ for (const name of entries) {
371
+ if (projectId === null) {
372
+ if (isMemoraoneSocketFilename(name)) {
373
+ paths.push(path3.join(baseDir, name));
374
+ }
375
+ } else if (isSocketFilenameForProject(name, projectId)) {
376
+ paths.push(path3.join(baseDir, name));
377
+ }
378
+ }
379
+ return paths.sort();
380
+ }
381
+ function filterSocketPathsByIde(socketPaths, projectId, ide) {
382
+ if (ide === void 0) return socketPaths;
383
+ return socketPaths.filter(
384
+ (socketPath) => isSocketFilenameForProjectAndIde(path3.basename(socketPath), projectId, ide)
385
+ );
386
+ }
387
+ async function defaultKillProcess(pid) {
388
+ process.kill(pid, "SIGTERM");
389
+ }
390
+ async function defaultRemoveSocket(socketPath) {
391
+ await fs3.unlink(socketPath);
392
+ }
393
+ async function defaultConfirm(message) {
394
+ if (!import_node_process.stdin.isTTY) {
395
+ return false;
396
+ }
397
+ const rl = readline.createInterface({ input: import_node_process.stdin, output: import_node_process.stdout });
398
+ try {
399
+ const answer = await rl.question(`${message} [y/N] `);
400
+ return /^y(es)?$/i.test(answer.trim());
401
+ } finally {
402
+ rl.close();
403
+ }
404
+ }
405
+ async function resolveCleanupTarget(cwd) {
406
+ try {
407
+ const binding = await resolveAuthoritativeBinding([path3.resolve(cwd)]);
408
+ return {
409
+ workspaceRoot: binding.workspaceRoot,
410
+ m1Path: binding.m1Path,
411
+ projectId: binding.projectId
412
+ };
413
+ } catch {
414
+ return { error: CLEANUP_PROJECT_ID_REQUIRED_ERROR };
415
+ }
416
+ }
417
+ function filterProcessesForScope(processes, projectId) {
418
+ if (projectId === null) {
419
+ return { matching: processes, skipped: [] };
420
+ }
421
+ const normalized = projectId.toLowerCase();
422
+ const matching = [];
423
+ const skipped = [];
424
+ for (const proc of processes) {
425
+ if (proc.projectId === normalized) {
426
+ matching.push(proc);
427
+ } else {
428
+ skipped.push(proc);
429
+ }
430
+ }
431
+ return { matching, skipped };
432
+ }
433
+ function logPrefix(dryRun) {
434
+ return dryRun ? "[cleanup][dry-run]" : "[cleanup]";
435
+ }
436
+ function logReconnectNotice(prefix, ide) {
437
+ if (ide) {
438
+ console.log(
439
+ `${prefix} Note: Valid ${ide} connections for this project may disconnect temporarily; they should reconnect automatically. Stale connections will remain cleared.`
440
+ );
441
+ } else {
442
+ console.log(
443
+ `${prefix} Note: Valid IDE connections for this project may disconnect temporarily; they should reconnect automatically. Stale connections will remain cleared.`
444
+ );
445
+ }
446
+ }
447
+ async function runCleanup(opts) {
448
+ const listProcesses = opts.listProcesses ?? defaultListDaemonProcesses;
449
+ const listSocketPaths = opts.listSocketPaths ?? defaultListSocketPaths;
450
+ const killProcess = opts.killProcess ?? defaultKillProcess;
451
+ const removeSocket = opts.removeSocket ?? defaultRemoveSocket;
452
+ const confirm = opts.confirm ?? defaultConfirm;
453
+ const prefix = logPrefix(opts.dryRun);
454
+ let targetProjectId = null;
455
+ let workspaceRoot;
456
+ let m1Path;
457
+ if (opts.allProjects) {
458
+ if (opts.projectId) {
459
+ return {
460
+ exitCode: 1,
461
+ killedPids: [],
462
+ removedSockets: [],
463
+ skippedProcesses: [],
464
+ error: "Cannot combine --all-projects with --project-id."
465
+ };
466
+ }
467
+ console.log(`${prefix} Mode: all projects (--all-projects)`);
468
+ console.warn(
469
+ `${prefix} WARNING: This stops every MemoraOne MCP daemon and removes all project sockets under ${getMcpBaseDir()}.`
470
+ );
471
+ } else if (opts.projectId) {
472
+ const normalized = normalizeCleanupProjectId(opts.projectId);
473
+ if (typeof normalized !== "string") {
474
+ return { exitCode: 1, killedPids: [], removedSockets: [], skippedProcesses: [], error: normalized.error };
475
+ }
476
+ targetProjectId = normalized;
477
+ console.log(`${prefix} Project id: ${targetProjectId}`);
478
+ if (opts.ide) {
479
+ console.log(`${prefix} IDE filter: ${opts.ide}`);
480
+ }
481
+ } else {
482
+ const target = await resolveCleanupTarget(opts.cwd);
483
+ if ("error" in target) {
484
+ return { exitCode: 1, killedPids: [], removedSockets: [], skippedProcesses: [], error: target.error };
485
+ }
486
+ targetProjectId = target.projectId;
487
+ workspaceRoot = target.workspaceRoot;
488
+ m1Path = target.m1Path;
489
+ console.log(`${prefix} Workspace root: ${workspaceRoot}`);
490
+ console.log(`${prefix} memoraone.m1: ${m1Path}`);
491
+ console.log(`${prefix} Project id: ${targetProjectId}`);
492
+ if (opts.ide) {
493
+ console.log(`${prefix} IDE filter: ${opts.ide}`);
494
+ }
495
+ }
496
+ if (targetProjectId !== null || opts.ide) {
497
+ logReconnectNotice(prefix, opts.ide);
498
+ }
499
+ const allDaemonProcesses = await listProcesses();
500
+ const { matching: projectProcesses, skipped: skippedProcesses } = filterProcessesForScope(
501
+ allDaemonProcesses,
502
+ targetProjectId
503
+ );
504
+ let processesToStop = projectProcesses;
505
+ const ideSkippedProcesses = [];
506
+ if (opts.ide && targetProjectId !== null) {
507
+ processesToStop = [];
508
+ for (const proc of projectProcesses) {
509
+ if (proc.ide === opts.ide) {
510
+ processesToStop.push(proc);
511
+ } else if (proc.ide === void 0) {
512
+ ideSkippedProcesses.push(proc);
513
+ console.log(
514
+ `${prefix} Skipped daemon pid=${proc.pid} because IDE could not be safely determined.`
515
+ );
516
+ } else {
517
+ ideSkippedProcesses.push(proc);
518
+ console.log(
519
+ `${prefix} Skipped daemon pid=${proc.pid} (IDE ${proc.ide} does not match filter ${opts.ide}).`
520
+ );
521
+ }
522
+ }
523
+ }
524
+ const allSocketPaths = await listSocketPaths(targetProjectId);
525
+ const socketPaths = targetProjectId === null ? allSocketPaths : filterSocketPathsByIde(allSocketPaths, targetProjectId, opts.ide);
526
+ if (opts.allProjects) {
527
+ const projectIds = /* @__PURE__ */ new Set();
528
+ for (const proc of processesToStop) {
529
+ projectIds.add(proc.projectId);
530
+ }
531
+ for (const socketPath of socketPaths) {
532
+ const id = extractProjectIdFromSocketFilename(path3.basename(socketPath));
533
+ if (id) projectIds.add(id);
534
+ }
535
+ console.log(`${prefix} Projects affected: ${projectIds.size ? [...projectIds].sort().join(", ") : "(none found)"}`);
536
+ }
537
+ if (processesToStop.length) {
538
+ console.log(`${prefix} Daemon processes to stop:`);
539
+ for (const proc of processesToStop) {
540
+ const ideLabel = proc.ide ? ` ide=${proc.ide}` : "";
541
+ console.log(`${prefix} pid=${proc.pid} project=${proc.projectId}${ideLabel}`);
542
+ }
543
+ } else if (ideSkippedProcesses.length) {
544
+ console.log(`${prefix} No matching daemon processes for IDE filter ${opts.ide}.`);
545
+ } else {
546
+ console.log(`${prefix} No matching daemon processes found.`);
547
+ }
548
+ if (socketPaths.length) {
549
+ console.log(`${prefix} Sockets to remove:`);
550
+ for (const socketPath of socketPaths) {
551
+ console.log(`${prefix} ${socketPath}`);
552
+ }
553
+ } else {
554
+ console.log(`${prefix} No matching sockets found under ${getMcpBaseDir()}.`);
555
+ }
556
+ if (skippedProcesses.length) {
557
+ console.log(`${prefix} Skipped unrelated daemon processes:`);
558
+ for (const proc of skippedProcesses) {
559
+ console.log(`${prefix} pid=${proc.pid} project=${proc.projectId}`);
560
+ }
561
+ }
562
+ if (opts.allProjects && !opts.dryRun) {
563
+ const ok = opts.assumeYes ? true : await confirm(`${prefix} Proceed with cleanup for ALL projects?`);
564
+ if (!ok) {
565
+ console.log(`${prefix} Aborted.`);
566
+ return {
567
+ exitCode: 1,
568
+ workspaceRoot,
569
+ m1Path,
570
+ projectId: targetProjectId ?? void 0,
571
+ killedPids: [],
572
+ removedSockets: [],
573
+ skippedProcesses: [...skippedProcesses, ...ideSkippedProcesses],
574
+ error: opts.assumeYes ? void 0 : "Aborted (--all requires --yes in non-interactive mode)"
575
+ };
576
+ }
577
+ }
578
+ const killedPids = [];
579
+ const removedSockets = [];
580
+ if (opts.dryRun) {
581
+ console.log(`${prefix} Dry run complete \u2014 no processes stopped, no sockets removed.`);
582
+ return {
583
+ exitCode: 0,
584
+ workspaceRoot,
585
+ m1Path,
586
+ projectId: targetProjectId ?? void 0,
587
+ killedPids: processesToStop.map((p) => p.pid),
588
+ removedSockets: socketPaths,
589
+ skippedProcesses: [...skippedProcesses, ...ideSkippedProcesses]
590
+ };
591
+ }
592
+ for (const proc of processesToStop) {
593
+ try {
594
+ await killProcess(proc.pid);
595
+ killedPids.push(proc.pid);
596
+ console.log(`${prefix} Stopped daemon pid=${proc.pid} project=${proc.projectId}`);
597
+ } catch (err) {
598
+ console.warn(`${prefix} Could not stop pid=${proc.pid}: ${String(err)}`);
599
+ }
600
+ }
601
+ if (killedPids.length) {
602
+ await new Promise((r) => setTimeout(r, 300));
603
+ }
604
+ for (const socketPath of socketPaths) {
605
+ try {
606
+ await removeSocket(socketPath);
607
+ removedSockets.push(socketPath);
608
+ console.log(`${prefix} Removed socket ${socketPath}`);
609
+ } catch (err) {
610
+ const code = err && typeof err === "object" && "code" in err ? err.code : void 0;
611
+ if (code !== "ENOENT") {
612
+ console.warn(`${prefix} Could not remove socket ${socketPath}: ${String(err)}`);
613
+ }
614
+ }
615
+ }
616
+ console.log(`${prefix} Done. stopped=${killedPids.length} socketsRemoved=${removedSockets.length}`);
617
+ return {
618
+ exitCode: 0,
619
+ workspaceRoot,
620
+ m1Path,
621
+ projectId: targetProjectId ?? void 0,
622
+ killedPids,
623
+ removedSockets,
624
+ skippedProcesses: [...skippedProcesses, ...ideSkippedProcesses]
625
+ };
626
+ }
627
+ function parseCleanupFlags(argv) {
628
+ let dryRun = false;
629
+ let allProjects = false;
630
+ let assumeYes = false;
631
+ let projectId;
632
+ let ide;
633
+ let invalidIde;
634
+ const unknown = [];
635
+ for (let i = 0; i < argv.length; i++) {
636
+ const arg = argv[i];
637
+ if (arg === "--dry-run") dryRun = true;
638
+ else if (arg === "--all-projects" || arg === "--all") allProjects = true;
639
+ else if (arg === "--yes" || arg === "-y") assumeYes = true;
640
+ else if (arg === "--project-id") {
641
+ if (i + 1 >= argv.length) {
642
+ unknown.push("--project-id (missing value)");
643
+ } else {
644
+ projectId = argv[++i];
645
+ }
646
+ } else if (arg === "--ide") {
647
+ if (i + 1 >= argv.length) {
648
+ unknown.push("--ide (missing value)");
649
+ } else {
650
+ const value = argv[++i];
651
+ if (IDE_TYPES.includes(value)) {
652
+ ide = value;
653
+ } else {
654
+ invalidIde = value;
655
+ }
656
+ }
657
+ } else if (arg.startsWith("-")) unknown.push(arg);
658
+ else unknown.push(arg);
659
+ }
660
+ return { dryRun, allProjects, assumeYes, projectId, ide, invalidIde, unknown };
661
+ }
662
+ async function cliCleanup(argv) {
663
+ const { dryRun, allProjects, assumeYes, projectId, ide, invalidIde, unknown } = parseCleanupFlags(argv);
664
+ if (invalidIde) {
665
+ console.error(
666
+ `[cleanup] Invalid --ide value: ${invalidIde}. Expected one of: ${IDE_TYPES.join(", ")}.`
667
+ );
668
+ return 1;
669
+ }
670
+ if (unknown.length) {
671
+ console.error(`[cleanup] Unknown option(s): ${unknown.join(", ")}`);
672
+ return 1;
673
+ }
674
+ const result = await runCleanup({
675
+ cwd: process.cwd(),
676
+ dryRun,
677
+ allProjects,
678
+ assumeYes,
679
+ projectId,
680
+ ide
681
+ });
682
+ if (result.error) {
683
+ console.error(`[cleanup] ${result.error}`);
684
+ }
685
+ return result.exitCode;
686
+ }
687
+
688
+ // src/setupIdeFiles.ts
235
689
  var MANAGED_MARKER = "<!-- MemoraOne managed IDE helper -->";
236
690
  var GITIGNORE_MEMORAONE_COMMENT = "# MemoraOne local project binding / API key";
237
691
  var GITIGNORE_MEMORAONE_ENTRY = "memoraone.m1";
@@ -240,15 +694,15 @@ var MEMORAONE_MCP_SERVER = {
240
694
  args: ["-y", "@memoraone/mcp"]
241
695
  };
242
696
  function assertUnderRepoRoot(repoRoot, absPath) {
243
- const normRoot = path3.resolve(repoRoot) + path3.sep;
244
- const normPath = path3.resolve(absPath);
245
- if (normPath !== path3.resolve(repoRoot) && !normPath.startsWith(normRoot)) {
697
+ const normRoot = path4.resolve(repoRoot) + path4.sep;
698
+ const normPath = path4.resolve(absPath);
699
+ if (normPath !== path4.resolve(repoRoot) && !normPath.startsWith(normRoot)) {
246
700
  throw new Error(`[setup-ide-files] Refusing to write outside repo root: ${absPath}`);
247
701
  }
248
702
  }
249
703
  async function pathExists(filePath) {
250
704
  try {
251
- await fs3.access(filePath);
705
+ await fs4.access(filePath);
252
706
  return true;
253
707
  } catch {
254
708
  return false;
@@ -269,12 +723,12 @@ ${GITIGNORE_MEMORAONE_ENTRY}
269
723
  }
270
724
  async function ensureGitignoreMemoraone(repoRoot, opts) {
271
725
  if (opts.noGitignore) return "skipped";
272
- const abs = path3.join(repoRoot, ".gitignore");
726
+ const abs = path4.join(repoRoot, ".gitignore");
273
727
  assertUnderRepoRoot(repoRoot, abs);
274
728
  let prior = "";
275
729
  let existed = false;
276
730
  try {
277
- prior = await fs3.readFile(abs, "utf8");
731
+ prior = await fs4.readFile(abs, "utf8");
278
732
  existed = true;
279
733
  } catch (err) {
280
734
  const code = err && typeof err === "object" && "code" in err ? err.code : void 0;
@@ -285,22 +739,22 @@ async function ensureGitignoreMemoraone(repoRoot, opts) {
285
739
  const separator = existed && prior.length > 0 ? prior.endsWith("\n") ? "\n" : "\n\n" : "";
286
740
  const next = (existed ? prior : "") + separator + block;
287
741
  if (opts.dryRun) return existed ? "updated" : "created";
288
- await fs3.writeFile(abs, next, "utf8");
742
+ await fs4.writeFile(abs, next, "utf8");
289
743
  return existed ? "updated" : "created";
290
744
  }
291
745
  async function findRepoRoot(startDir) {
292
- let current = path3.resolve(startDir);
293
- const root = path3.parse(current).root;
746
+ let current = path4.resolve(startDir);
747
+ const root = path4.parse(current).root;
294
748
  while (true) {
295
- const gitPath = path3.join(current, ".git");
296
- const m1Path = path3.join(current, "memoraone.m1");
749
+ const gitPath = path4.join(current, ".git");
750
+ const m1Path = path4.join(current, "memoraone.m1");
297
751
  if (await pathExists(gitPath) || await pathExists(m1Path)) {
298
752
  return current;
299
753
  }
300
754
  if (current === root) {
301
755
  return null;
302
756
  }
303
- current = path3.dirname(current);
757
+ current = path4.dirname(current);
304
758
  }
305
759
  }
306
760
  function stripLeadingLineComments(text) {
@@ -354,42 +808,42 @@ function buildMcpJsonBody(existing) {
354
808
  return mcpJsonHeader() + JSON.stringify(merged, null, 2) + "\n";
355
809
  }
356
810
  async function writeManagedMarkdown(repoRoot, relPath, fullContent, opts) {
357
- const abs = path3.join(repoRoot, relPath);
811
+ const abs = path4.join(repoRoot, relPath);
358
812
  assertUnderRepoRoot(repoRoot, abs);
359
813
  let prior = "";
360
814
  let existed = false;
361
815
  try {
362
- prior = await fs3.readFile(abs, "utf8");
816
+ prior = await fs4.readFile(abs, "utf8");
363
817
  existed = true;
364
818
  } catch (err) {
365
819
  if (err?.code !== "ENOENT") throw err;
366
820
  }
367
821
  if (!existed) {
368
822
  if (opts.dryRun) return "created";
369
- await fs3.mkdir(path3.dirname(abs), { recursive: true });
370
- await fs3.writeFile(abs, fullContent, "utf8");
823
+ await fs4.mkdir(path4.dirname(abs), { recursive: true });
824
+ await fs4.writeFile(abs, fullContent, "utf8");
371
825
  return "created";
372
826
  }
373
827
  if (prior.includes(MANAGED_MARKER)) {
374
828
  if (prior === fullContent) return "skipped";
375
829
  if (opts.dryRun) return "updated";
376
- await fs3.mkdir(path3.dirname(abs), { recursive: true });
377
- await fs3.writeFile(abs, fullContent, "utf8");
830
+ await fs4.mkdir(path4.dirname(abs), { recursive: true });
831
+ await fs4.writeFile(abs, fullContent, "utf8");
378
832
  return "updated";
379
833
  }
380
834
  if (!opts.force) return "skipped-untracked";
381
835
  if (opts.dryRun) return "updated";
382
- await fs3.mkdir(path3.dirname(abs), { recursive: true });
383
- await fs3.writeFile(abs, fullContent, "utf8");
836
+ await fs4.mkdir(path4.dirname(abs), { recursive: true });
837
+ await fs4.writeFile(abs, fullContent, "utf8");
384
838
  return "updated";
385
839
  }
386
840
  async function writeMcpJson(repoRoot, opts) {
387
- const abs = path3.join(repoRoot, ".vscode/mcp.json");
841
+ const abs = path4.join(repoRoot, ".vscode/mcp.json");
388
842
  assertUnderRepoRoot(repoRoot, abs);
389
843
  let raw = "";
390
844
  let existed = false;
391
845
  try {
392
- raw = await fs3.readFile(abs, "utf8");
846
+ raw = await fs4.readFile(abs, "utf8");
393
847
  existed = true;
394
848
  } catch (err) {
395
849
  if (err?.code !== "ENOENT") throw err;
@@ -397,8 +851,8 @@ async function writeMcpJson(repoRoot, opts) {
397
851
  if (!existed) {
398
852
  const body = buildMcpJsonBody(null);
399
853
  if (opts.dryRun) return "created";
400
- await fs3.mkdir(path3.dirname(abs), { recursive: true });
401
- await fs3.writeFile(abs, body, "utf8");
854
+ await fs4.mkdir(path4.dirname(abs), { recursive: true });
855
+ await fs4.writeFile(abs, body, "utf8");
402
856
  return "created";
403
857
  }
404
858
  const managed = raw.includes(MANAGED_MARKER);
@@ -413,8 +867,8 @@ async function writeMcpJson(repoRoot, opts) {
413
867
  const next = buildMcpJsonBody(parsed);
414
868
  if (managed && next === raw) return "skipped";
415
869
  if (opts.dryRun) return "updated";
416
- await fs3.mkdir(path3.dirname(abs), { recursive: true });
417
- await fs3.writeFile(abs, next, "utf8");
870
+ await fs4.mkdir(path4.dirname(abs), { recursive: true });
871
+ await fs4.writeFile(abs, next, "utf8");
418
872
  return "updated";
419
873
  }
420
874
  function parseSetupIdeFlags(argv) {
@@ -425,6 +879,7 @@ function parseSetupIdeFlags(argv) {
425
879
  let force = false;
426
880
  let dryRun = false;
427
881
  let noGitignore = false;
882
+ let cleanup = false;
428
883
  const unknown = [];
429
884
  for (const a of argv) {
430
885
  if (a === "--cursor") cursor = true;
@@ -434,6 +889,7 @@ function parseSetupIdeFlags(argv) {
434
889
  else if (a === "--force") force = true;
435
890
  else if (a === "--dry-run") dryRun = true;
436
891
  else if (a === "--no-gitignore") noGitignore = true;
892
+ else if (a === "--cleanup") cleanup = true;
437
893
  else if (a.startsWith("-")) unknown.push(a);
438
894
  }
439
895
  const specific = cursor || vscode || jetbrains;
@@ -443,7 +899,7 @@ function parseSetupIdeFlags(argv) {
443
899
  } else {
444
900
  targets = { cursor, vscode, jetbrains };
445
901
  }
446
- return { targets, force, dryRun, noGitignore, unknown };
902
+ return { targets, force, dryRun, noGitignore, cleanup, unknown };
447
903
  }
448
904
  function summarizeOutcomes(outcomes) {
449
905
  const created = [];
@@ -516,7 +972,7 @@ description: MemoraOne MCP \u2014 IDE agent instructions
516
972
  return { exitCode: 0, repoRoot, outcomes };
517
973
  }
518
974
  async function cliSetupIdeFiles(argv) {
519
- const { targets, force, dryRun, noGitignore, unknown } = parseSetupIdeFlags(argv);
975
+ const { targets, force, dryRun, noGitignore, cleanup, unknown } = parseSetupIdeFlags(argv);
520
976
  if (unknown.length) {
521
977
  console.error(`[setup-ide-files] Unknown option(s): ${unknown.join(", ")}`);
522
978
  return 1;
@@ -539,6 +995,26 @@ async function cliSetupIdeFiles(argv) {
539
995
  if (dryRun) {
540
996
  console.log("[setup-ide-files] Dry run: no files written.");
541
997
  }
998
+ if (cleanup) {
999
+ console.log("[setup-ide-files] Running project-scoped cleanup...");
1000
+ const cleanupResult = await runCleanup({
1001
+ cwd: process.cwd(),
1002
+ dryRun,
1003
+ allProjects: false,
1004
+ assumeYes: true
1005
+ });
1006
+ if (cleanupResult.error) {
1007
+ console.error(`[setup-ide-files] cleanup failed: ${cleanupResult.error}`);
1008
+ return cleanupResult.exitCode;
1009
+ }
1010
+ } else {
1011
+ console.log(
1012
+ "[setup-ide-files] If Studio shows stale connections, run: npx -y @memoraone/mcp@latest cleanup --project-id <projectId>"
1013
+ );
1014
+ console.log(
1015
+ "[setup-ide-files] From this repo (uses memoraone.m1): npx -y @memoraone/mcp@latest cleanup"
1016
+ );
1017
+ }
542
1018
  return 0;
543
1019
  }
544
1020
 
@@ -551,11 +1027,17 @@ if (args.includes("--version") || args.includes("-v")) {
551
1027
  }
552
1028
  if (args.includes("--help") || args.includes("-h")) {
553
1029
  console.log(
554
- "Usage: memoraone-mcp [--version] [--help] [--daemon --project-id <uuid>]\n memoraone-mcp setup-ide-files [--all|--cursor|--vscode|--jetbrains] [--force] [--dry-run] [--no-gitignore]"
1030
+ "Usage: memoraone-mcp [--version] [--help] [--daemon --project-id <uuid> [--ide cursor|copilot-vscode|jetbrains]]\n memoraone-mcp setup-ide-files [--all|--cursor|--vscode|--jetbrains] [--force] [--dry-run] [--no-gitignore] [--cleanup]\n memoraone-mcp cleanup [--project-id <uuid>] [--ide cursor|copilot-vscode|jetbrains] [--dry-run] [--all-projects] [--yes]"
555
1031
  );
556
1032
  process.exit(0);
557
1033
  }
558
- if (args[0] === "setup-ide-files") {
1034
+ if (args[0] === "cleanup") {
1035
+ cliCleanup(args.slice(1)).then((code) => process.exit(code)).catch((err) => {
1036
+ process.stderr.write(`[memoraone-mcp] cleanup fatal: ${String(err)}
1037
+ `);
1038
+ process.exit(1);
1039
+ });
1040
+ } else if (args[0] === "setup-ide-files") {
559
1041
  cliSetupIdeFiles(args.slice(1)).then((code) => process.exit(code)).catch((err) => {
560
1042
  process.stderr.write(`[memoraone-mcp] setup-ide-files fatal: ${String(err)}
561
1043
  `);
@@ -572,8 +1054,8 @@ if (args[0] === "setup-ide-files") {
572
1054
  const raw = process.env.WORKSPACE_FOLDER_PATHS;
573
1055
  const parts = [];
574
1056
  if (raw !== void 0 && raw.trim() !== "") {
575
- for (const p of raw.split(path4.delimiter).map((s) => s.trim()).filter(Boolean)) {
576
- parts.push(path4.resolve(p));
1057
+ for (const p of raw.split(path5.delimiter).map((s) => s.trim()).filter(Boolean)) {
1058
+ parts.push(path5.resolve(p));
577
1059
  }
578
1060
  }
579
1061
  parts.push(process.cwd());
@@ -587,10 +1069,10 @@ if (args[0] === "setup-ide-files") {
587
1069
  }
588
1070
  return deduped;
589
1071
  }, connectWithRetry = function(socketPath) {
590
- return new Promise((resolve4, reject) => {
1072
+ return new Promise((resolve5, reject) => {
591
1073
  const tryConnect = (attempt) => {
592
1074
  const socket = net.connect(socketPath, () => {
593
- resolve4(socket);
1075
+ resolve5(socket);
594
1076
  });
595
1077
  socket.on("error", (err) => {
596
1078
  if (attempt >= MAX_RETRIES) {
@@ -616,7 +1098,7 @@ if (args[0] === "setup-ide-files") {
616
1098
  async function runBridge() {
617
1099
  ensureBaseDir();
618
1100
  const binding = await resolveBinding();
619
- const socketPath = getSocketPath(binding.projectId);
1101
+ const socketPath = getDaemonSocketPath(binding.projectId);
620
1102
  const environmentLog = binding.environment !== void 0 ? ` environment=${binding.environment}` : "";
621
1103
  log(
622
1104
  `authoritative binding project=${binding.projectId} workspace=${binding.workspaceRoot} m1=${binding.m1Path} source=${binding.bindingSource} apiKeySource=${binding.apiKeySource}${environmentLog}`
@@ -626,14 +1108,18 @@ if (args[0] === "setup-ide-files") {
626
1108
  socket = await connectWithRetry(socketPath);
627
1109
  } catch {
628
1110
  log("daemon not running, spawning...");
629
- const child = (0, import_node_child_process.spawn)(process.execPath, [process.argv[1], "--daemon", "--project-id", binding.projectId], {
630
- detached: true,
631
- stdio: "ignore",
632
- env: {
633
- ...process.env,
634
- MEMORAONE_DAEMON_BINDING_B64: encodeResolvedBinding(binding)
1111
+ const child = (0, import_node_child_process2.spawn)(
1112
+ process.execPath,
1113
+ buildDaemonSpawnArgs(process.argv[1], binding.projectId),
1114
+ {
1115
+ detached: true,
1116
+ stdio: "ignore",
1117
+ env: {
1118
+ ...process.env,
1119
+ MEMORAONE_DAEMON_BINDING_B64: encodeResolvedBinding(binding)
1120
+ }
635
1121
  }
636
- });
1122
+ );
637
1123
  child.unref();
638
1124
  await new Promise((r) => setTimeout(r, RETRY_DELAY_MS));
639
1125
  socket = await connectWithRetry(socketPath);