@memoraone/mcp 0.1.22 → 0.1.24

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.24",
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,25 +292,447 @@ 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/configUtils.ts
689
+ var PROD_API_URL = "https://api.memoraone.com";
690
+
691
+ // src/setupIdeFiles.ts
235
692
  var MANAGED_MARKER = "<!-- MemoraOne managed IDE helper -->";
236
693
  var GITIGNORE_MEMORAONE_COMMENT = "# MemoraOne local project binding / API key";
237
694
  var GITIGNORE_MEMORAONE_ENTRY = "memoraone.m1";
238
- var MEMORAONE_MCP_SERVER = {
239
- command: "npx",
240
- args: ["-y", "@memoraone/mcp"]
241
- };
695
+ var MCP_PACKAGE_ARG = "@memoraone/mcp@latest";
696
+ var VSCODE_MCP_SERVERS_KEY = "servers";
697
+ function isProdBoundEnvironment(environment) {
698
+ if (environment === void 0) return true;
699
+ const lower = environment.toLowerCase();
700
+ return lower === "production" || lower === "prod";
701
+ }
702
+ async function readMemoraoneM1Environment(repoRoot) {
703
+ const m1Path = path4.join(repoRoot, "memoraone.m1");
704
+ try {
705
+ const content = await fs4.readFile(m1Path, "utf8");
706
+ const parsed = JSON.parse(content);
707
+ if (typeof parsed?.environment === "string") {
708
+ const trimmed = parsed.environment.trim();
709
+ if (trimmed !== "") return trimmed;
710
+ }
711
+ } catch {
712
+ }
713
+ return void 0;
714
+ }
715
+ async function buildMemoraoneMcpServerEntry(repoRoot) {
716
+ const entry = {
717
+ command: "npx",
718
+ args: ["-y", MCP_PACKAGE_ARG]
719
+ };
720
+ const environment = await readMemoraoneM1Environment(repoRoot);
721
+ if (isProdBoundEnvironment(environment)) {
722
+ entry.env = { MEMORAONE_API_URL: PROD_API_URL };
723
+ }
724
+ return entry;
725
+ }
242
726
  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)) {
727
+ const normRoot = path4.resolve(repoRoot) + path4.sep;
728
+ const normPath = path4.resolve(absPath);
729
+ if (normPath !== path4.resolve(repoRoot) && !normPath.startsWith(normRoot)) {
246
730
  throw new Error(`[setup-ide-files] Refusing to write outside repo root: ${absPath}`);
247
731
  }
248
732
  }
249
733
  async function pathExists(filePath) {
250
734
  try {
251
- await fs3.access(filePath);
735
+ await fs4.access(filePath);
252
736
  return true;
253
737
  } catch {
254
738
  return false;
@@ -269,12 +753,12 @@ ${GITIGNORE_MEMORAONE_ENTRY}
269
753
  }
270
754
  async function ensureGitignoreMemoraone(repoRoot, opts) {
271
755
  if (opts.noGitignore) return "skipped";
272
- const abs = path3.join(repoRoot, ".gitignore");
756
+ const abs = path4.join(repoRoot, ".gitignore");
273
757
  assertUnderRepoRoot(repoRoot, abs);
274
758
  let prior = "";
275
759
  let existed = false;
276
760
  try {
277
- prior = await fs3.readFile(abs, "utf8");
761
+ prior = await fs4.readFile(abs, "utf8");
278
762
  existed = true;
279
763
  } catch (err) {
280
764
  const code = err && typeof err === "object" && "code" in err ? err.code : void 0;
@@ -285,22 +769,22 @@ async function ensureGitignoreMemoraone(repoRoot, opts) {
285
769
  const separator = existed && prior.length > 0 ? prior.endsWith("\n") ? "\n" : "\n\n" : "";
286
770
  const next = (existed ? prior : "") + separator + block;
287
771
  if (opts.dryRun) return existed ? "updated" : "created";
288
- await fs3.writeFile(abs, next, "utf8");
772
+ await fs4.writeFile(abs, next, "utf8");
289
773
  return existed ? "updated" : "created";
290
774
  }
291
775
  async function findRepoRoot(startDir) {
292
- let current = path3.resolve(startDir);
293
- const root = path3.parse(current).root;
776
+ let current = path4.resolve(startDir);
777
+ const root = path4.parse(current).root;
294
778
  while (true) {
295
- const gitPath = path3.join(current, ".git");
296
- const m1Path = path3.join(current, "memoraone.m1");
779
+ const gitPath = path4.join(current, ".git");
780
+ const m1Path = path4.join(current, "memoraone.m1");
297
781
  if (await pathExists(gitPath) || await pathExists(m1Path)) {
298
782
  return current;
299
783
  }
300
784
  if (current === root) {
301
785
  return null;
302
786
  }
303
- current = path3.dirname(current);
787
+ current = path4.dirname(current);
304
788
  }
305
789
  }
306
790
  function stripLeadingLineComments(text) {
@@ -346,59 +830,70 @@ function mcpJsonHeader() {
346
830
  return `// ${MANAGED_MARKER}
347
831
  `;
348
832
  }
349
- function buildMcpJsonBody(existing) {
350
- const base = existing && typeof existing === "object" ? { ...existing } : { mcpServers: {} };
351
- const servers = typeof base.mcpServers === "object" && base.mcpServers !== null && !Array.isArray(base.mcpServers) ? { ...base.mcpServers } : {};
352
- servers.memoraone = { ...MEMORAONE_MCP_SERVER };
353
- const merged = { ...base, mcpServers: servers };
833
+ function extractExistingMcpServers(existing) {
834
+ if (typeof existing.servers === "object" && existing.servers !== null && !Array.isArray(existing.servers)) {
835
+ return { ...existing.servers };
836
+ }
837
+ if (typeof existing.mcpServers === "object" && existing.mcpServers !== null && !Array.isArray(existing.mcpServers)) {
838
+ return { ...existing.mcpServers };
839
+ }
840
+ return {};
841
+ }
842
+ function buildMcpJsonBody(existing, memoraoneServer) {
843
+ const base = existing && typeof existing === "object" ? { ...existing } : { [VSCODE_MCP_SERVERS_KEY]: {} };
844
+ const servers = extractExistingMcpServers(base);
845
+ servers.memoraone = memoraoneServer;
846
+ const { mcpServers: _legacy, ...rest } = base;
847
+ const merged = { ...rest, [VSCODE_MCP_SERVERS_KEY]: servers };
354
848
  return mcpJsonHeader() + JSON.stringify(merged, null, 2) + "\n";
355
849
  }
356
850
  async function writeManagedMarkdown(repoRoot, relPath, fullContent, opts) {
357
- const abs = path3.join(repoRoot, relPath);
851
+ const abs = path4.join(repoRoot, relPath);
358
852
  assertUnderRepoRoot(repoRoot, abs);
359
853
  let prior = "";
360
854
  let existed = false;
361
855
  try {
362
- prior = await fs3.readFile(abs, "utf8");
856
+ prior = await fs4.readFile(abs, "utf8");
363
857
  existed = true;
364
858
  } catch (err) {
365
859
  if (err?.code !== "ENOENT") throw err;
366
860
  }
367
861
  if (!existed) {
368
862
  if (opts.dryRun) return "created";
369
- await fs3.mkdir(path3.dirname(abs), { recursive: true });
370
- await fs3.writeFile(abs, fullContent, "utf8");
863
+ await fs4.mkdir(path4.dirname(abs), { recursive: true });
864
+ await fs4.writeFile(abs, fullContent, "utf8");
371
865
  return "created";
372
866
  }
373
867
  if (prior.includes(MANAGED_MARKER)) {
374
868
  if (prior === fullContent) return "skipped";
375
869
  if (opts.dryRun) return "updated";
376
- await fs3.mkdir(path3.dirname(abs), { recursive: true });
377
- await fs3.writeFile(abs, fullContent, "utf8");
870
+ await fs4.mkdir(path4.dirname(abs), { recursive: true });
871
+ await fs4.writeFile(abs, fullContent, "utf8");
378
872
  return "updated";
379
873
  }
380
874
  if (!opts.force) return "skipped-untracked";
381
875
  if (opts.dryRun) return "updated";
382
- await fs3.mkdir(path3.dirname(abs), { recursive: true });
383
- await fs3.writeFile(abs, fullContent, "utf8");
876
+ await fs4.mkdir(path4.dirname(abs), { recursive: true });
877
+ await fs4.writeFile(abs, fullContent, "utf8");
384
878
  return "updated";
385
879
  }
386
880
  async function writeMcpJson(repoRoot, opts) {
387
- const abs = path3.join(repoRoot, ".vscode/mcp.json");
881
+ const abs = path4.join(repoRoot, ".vscode/mcp.json");
388
882
  assertUnderRepoRoot(repoRoot, abs);
389
883
  let raw = "";
390
884
  let existed = false;
391
885
  try {
392
- raw = await fs3.readFile(abs, "utf8");
886
+ raw = await fs4.readFile(abs, "utf8");
393
887
  existed = true;
394
888
  } catch (err) {
395
889
  if (err?.code !== "ENOENT") throw err;
396
890
  }
891
+ const memoraoneServer = await buildMemoraoneMcpServerEntry(repoRoot);
397
892
  if (!existed) {
398
- const body = buildMcpJsonBody(null);
893
+ const body = buildMcpJsonBody(null, memoraoneServer);
399
894
  if (opts.dryRun) return "created";
400
- await fs3.mkdir(path3.dirname(abs), { recursive: true });
401
- await fs3.writeFile(abs, body, "utf8");
895
+ await fs4.mkdir(path4.dirname(abs), { recursive: true });
896
+ await fs4.writeFile(abs, body, "utf8");
402
897
  return "created";
403
898
  }
404
899
  const managed = raw.includes(MANAGED_MARKER);
@@ -410,11 +905,11 @@ async function writeMcpJson(repoRoot, opts) {
410
905
  parsed = null;
411
906
  }
412
907
  if (!parsed && !opts.force) return "skipped-untracked";
413
- const next = buildMcpJsonBody(parsed);
908
+ const next = buildMcpJsonBody(parsed, memoraoneServer);
414
909
  if (managed && next === raw) return "skipped";
415
910
  if (opts.dryRun) return "updated";
416
- await fs3.mkdir(path3.dirname(abs), { recursive: true });
417
- await fs3.writeFile(abs, next, "utf8");
911
+ await fs4.mkdir(path4.dirname(abs), { recursive: true });
912
+ await fs4.writeFile(abs, next, "utf8");
418
913
  return "updated";
419
914
  }
420
915
  function parseSetupIdeFlags(argv) {
@@ -425,6 +920,7 @@ function parseSetupIdeFlags(argv) {
425
920
  let force = false;
426
921
  let dryRun = false;
427
922
  let noGitignore = false;
923
+ let cleanup = false;
428
924
  const unknown = [];
429
925
  for (const a of argv) {
430
926
  if (a === "--cursor") cursor = true;
@@ -434,6 +930,7 @@ function parseSetupIdeFlags(argv) {
434
930
  else if (a === "--force") force = true;
435
931
  else if (a === "--dry-run") dryRun = true;
436
932
  else if (a === "--no-gitignore") noGitignore = true;
933
+ else if (a === "--cleanup") cleanup = true;
437
934
  else if (a.startsWith("-")) unknown.push(a);
438
935
  }
439
936
  const specific = cursor || vscode || jetbrains;
@@ -443,7 +940,7 @@ function parseSetupIdeFlags(argv) {
443
940
  } else {
444
941
  targets = { cursor, vscode, jetbrains };
445
942
  }
446
- return { targets, force, dryRun, noGitignore, unknown };
943
+ return { targets, force, dryRun, noGitignore, cleanup, unknown };
447
944
  }
448
945
  function summarizeOutcomes(outcomes) {
449
946
  const created = [];
@@ -516,7 +1013,7 @@ description: MemoraOne MCP \u2014 IDE agent instructions
516
1013
  return { exitCode: 0, repoRoot, outcomes };
517
1014
  }
518
1015
  async function cliSetupIdeFiles(argv) {
519
- const { targets, force, dryRun, noGitignore, unknown } = parseSetupIdeFlags(argv);
1016
+ const { targets, force, dryRun, noGitignore, cleanup, unknown } = parseSetupIdeFlags(argv);
520
1017
  if (unknown.length) {
521
1018
  console.error(`[setup-ide-files] Unknown option(s): ${unknown.join(", ")}`);
522
1019
  return 1;
@@ -539,6 +1036,26 @@ async function cliSetupIdeFiles(argv) {
539
1036
  if (dryRun) {
540
1037
  console.log("[setup-ide-files] Dry run: no files written.");
541
1038
  }
1039
+ if (cleanup) {
1040
+ console.log("[setup-ide-files] Running project-scoped cleanup...");
1041
+ const cleanupResult = await runCleanup({
1042
+ cwd: process.cwd(),
1043
+ dryRun,
1044
+ allProjects: false,
1045
+ assumeYes: true
1046
+ });
1047
+ if (cleanupResult.error) {
1048
+ console.error(`[setup-ide-files] cleanup failed: ${cleanupResult.error}`);
1049
+ return cleanupResult.exitCode;
1050
+ }
1051
+ } else {
1052
+ console.log(
1053
+ "[setup-ide-files] If Studio shows stale connections, run: npx -y @memoraone/mcp@latest cleanup --project-id <projectId>"
1054
+ );
1055
+ console.log(
1056
+ "[setup-ide-files] From this repo (uses memoraone.m1): npx -y @memoraone/mcp@latest cleanup"
1057
+ );
1058
+ }
542
1059
  return 0;
543
1060
  }
544
1061
 
@@ -551,11 +1068,17 @@ if (args.includes("--version") || args.includes("-v")) {
551
1068
  }
552
1069
  if (args.includes("--help") || args.includes("-h")) {
553
1070
  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]"
1071
+ "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
1072
  );
556
1073
  process.exit(0);
557
1074
  }
558
- if (args[0] === "setup-ide-files") {
1075
+ if (args[0] === "cleanup") {
1076
+ cliCleanup(args.slice(1)).then((code) => process.exit(code)).catch((err) => {
1077
+ process.stderr.write(`[memoraone-mcp] cleanup fatal: ${String(err)}
1078
+ `);
1079
+ process.exit(1);
1080
+ });
1081
+ } else if (args[0] === "setup-ide-files") {
559
1082
  cliSetupIdeFiles(args.slice(1)).then((code) => process.exit(code)).catch((err) => {
560
1083
  process.stderr.write(`[memoraone-mcp] setup-ide-files fatal: ${String(err)}
561
1084
  `);
@@ -572,8 +1095,8 @@ if (args[0] === "setup-ide-files") {
572
1095
  const raw = process.env.WORKSPACE_FOLDER_PATHS;
573
1096
  const parts = [];
574
1097
  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));
1098
+ for (const p of raw.split(path5.delimiter).map((s) => s.trim()).filter(Boolean)) {
1099
+ parts.push(path5.resolve(p));
577
1100
  }
578
1101
  }
579
1102
  parts.push(process.cwd());
@@ -587,10 +1110,10 @@ if (args[0] === "setup-ide-files") {
587
1110
  }
588
1111
  return deduped;
589
1112
  }, connectWithRetry = function(socketPath) {
590
- return new Promise((resolve4, reject) => {
1113
+ return new Promise((resolve5, reject) => {
591
1114
  const tryConnect = (attempt) => {
592
1115
  const socket = net.connect(socketPath, () => {
593
- resolve4(socket);
1116
+ resolve5(socket);
594
1117
  });
595
1118
  socket.on("error", (err) => {
596
1119
  if (attempt >= MAX_RETRIES) {
@@ -616,7 +1139,7 @@ if (args[0] === "setup-ide-files") {
616
1139
  async function runBridge() {
617
1140
  ensureBaseDir();
618
1141
  const binding = await resolveBinding();
619
- const socketPath = getSocketPath(binding.projectId);
1142
+ const socketPath = getDaemonSocketPath(binding.projectId);
620
1143
  const environmentLog = binding.environment !== void 0 ? ` environment=${binding.environment}` : "";
621
1144
  log(
622
1145
  `authoritative binding project=${binding.projectId} workspace=${binding.workspaceRoot} m1=${binding.m1Path} source=${binding.bindingSource} apiKeySource=${binding.apiKeySource}${environmentLog}`
@@ -626,14 +1149,18 @@ if (args[0] === "setup-ide-files") {
626
1149
  socket = await connectWithRetry(socketPath);
627
1150
  } catch {
628
1151
  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)
1152
+ const child = (0, import_node_child_process2.spawn)(
1153
+ process.execPath,
1154
+ buildDaemonSpawnArgs(process.argv[1], binding.projectId),
1155
+ {
1156
+ detached: true,
1157
+ stdio: "ignore",
1158
+ env: {
1159
+ ...process.env,
1160
+ MEMORAONE_DAEMON_BINDING_B64: encodeResolvedBinding(binding)
1161
+ }
635
1162
  }
636
- });
1163
+ );
637
1164
  child.unref();
638
1165
  await new Promise((r) => setTimeout(r, RETRY_DELAY_MS));
639
1166
  socket = await connectWithRetry(socketPath);