@memoraone/mcp 0.1.21 → 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.21",
33
+ version: "0.1.23",
34
34
  type: "module",
35
35
  main: "dist/index.cjs",
36
36
  bin: {
@@ -67,27 +67,96 @@ 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);
89
151
  var path2 = __toESM(require("path"), 1);
90
152
  var uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
153
+ function normalizeEnvironment(raw) {
154
+ if (raw === void 0 || raw === null || typeof raw !== "string") {
155
+ return void 0;
156
+ }
157
+ const trimmed = raw.trim();
158
+ return trimmed === "" ? void 0 : trimmed;
159
+ }
91
160
  function parseAndValidateM1(content, markerPath) {
92
161
  let parsed;
93
162
  try {
@@ -104,7 +173,8 @@ function parseAndValidateM1(content, markerPath) {
104
173
  }
105
174
  const apiKeyRaw = parsed?.MEMORAONE_API_KEY ?? parsed?.api_key;
106
175
  const apiKey = apiKeyRaw !== void 0 && apiKeyRaw !== null && typeof apiKeyRaw === "string" && apiKeyRaw.trim() !== "" ? apiKeyRaw.trim() : null;
107
- return { projectId: projectId.trim(), apiKey };
176
+ const environment = normalizeEnvironment(parsed?.environment);
177
+ return environment === void 0 ? { projectId: projectId.trim(), apiKey } : { projectId: projectId.trim(), apiKey, environment };
108
178
  }
109
179
  async function resolveProjectIdFromExplicitM1Path() {
110
180
  const raw = process.env.MEMORAONE_M1_PATH;
@@ -114,8 +184,8 @@ async function resolveProjectIdFromExplicitM1Path() {
114
184
  const markerPath = path2.resolve(raw);
115
185
  try {
116
186
  const content = await fs2.readFile(markerPath, "utf8");
117
- const { projectId, apiKey } = parseAndValidateM1(content, markerPath);
118
- return { projectId, apiKey, foundAt: markerPath };
187
+ const { projectId, apiKey, environment } = parseAndValidateM1(content, markerPath);
188
+ return environment === void 0 ? { projectId, apiKey, foundAt: markerPath } : { projectId, apiKey, environment, foundAt: markerPath };
119
189
  } catch (err) {
120
190
  if (err?.code === "ENOENT") {
121
191
  return null;
@@ -129,9 +199,9 @@ async function findM1WalkingUp(workspaceRoot) {
129
199
  const markerPath = path2.join(current, "memoraone.m1");
130
200
  try {
131
201
  const content = await fs2.readFile(markerPath, "utf8");
132
- const { projectId, apiKey } = parseAndValidateM1(content, markerPath);
202
+ const { projectId, apiKey, environment } = parseAndValidateM1(content, markerPath);
133
203
  const repoRoot = path2.dirname(markerPath);
134
- return { projectId, apiKey, repoRoot, markerPath };
204
+ return environment === void 0 ? { projectId, apiKey, repoRoot, markerPath } : { projectId, apiKey, environment, repoRoot, markerPath };
135
205
  } catch (err) {
136
206
  if (err?.code !== "ENOENT") {
137
207
  throw err;
@@ -191,6 +261,7 @@ async function resolveAuthoritativeBinding(workspaceRoot) {
191
261
  workspaceRoot: path2.dirname(explicitBinding.foundAt),
192
262
  m1Path: explicitBinding.foundAt,
193
263
  apiKey: resolved.apiKey,
264
+ ...explicitBinding.environment !== void 0 ? { environment: explicitBinding.environment } : {},
194
265
  bindingSource: "explicit-m1-path",
195
266
  apiKeySource: resolved.apiKeySource
196
267
  };
@@ -208,6 +279,7 @@ async function resolveAuthoritativeBinding(workspaceRoot) {
208
279
  workspaceRoot: binding.repoRoot,
209
280
  m1Path: binding.markerPath,
210
281
  apiKey: resolved.apiKey,
282
+ ...binding.environment !== void 0 ? { environment: binding.environment } : {},
211
283
  bindingSource: "workspace-search",
212
284
  apiKeySource: resolved.apiKeySource
213
285
  };
@@ -220,8 +292,400 @@ function encodeResolvedBinding(binding) {
220
292
  }
221
293
 
222
294
  // src/setupIdeFiles.ts
295
+ var fs4 = __toESM(require("fs/promises"), 1);
296
+ var path4 = __toESM(require("path"), 1);
297
+
298
+ // src/cleanup.ts
223
299
  var fs3 = __toESM(require("fs/promises"), 1);
224
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
225
689
  var MANAGED_MARKER = "<!-- MemoraOne managed IDE helper -->";
226
690
  var GITIGNORE_MEMORAONE_COMMENT = "# MemoraOne local project binding / API key";
227
691
  var GITIGNORE_MEMORAONE_ENTRY = "memoraone.m1";
@@ -230,15 +694,15 @@ var MEMORAONE_MCP_SERVER = {
230
694
  args: ["-y", "@memoraone/mcp"]
231
695
  };
232
696
  function assertUnderRepoRoot(repoRoot, absPath) {
233
- const normRoot = path3.resolve(repoRoot) + path3.sep;
234
- const normPath = path3.resolve(absPath);
235
- 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)) {
236
700
  throw new Error(`[setup-ide-files] Refusing to write outside repo root: ${absPath}`);
237
701
  }
238
702
  }
239
703
  async function pathExists(filePath) {
240
704
  try {
241
- await fs3.access(filePath);
705
+ await fs4.access(filePath);
242
706
  return true;
243
707
  } catch {
244
708
  return false;
@@ -259,12 +723,12 @@ ${GITIGNORE_MEMORAONE_ENTRY}
259
723
  }
260
724
  async function ensureGitignoreMemoraone(repoRoot, opts) {
261
725
  if (opts.noGitignore) return "skipped";
262
- const abs = path3.join(repoRoot, ".gitignore");
726
+ const abs = path4.join(repoRoot, ".gitignore");
263
727
  assertUnderRepoRoot(repoRoot, abs);
264
728
  let prior = "";
265
729
  let existed = false;
266
730
  try {
267
- prior = await fs3.readFile(abs, "utf8");
731
+ prior = await fs4.readFile(abs, "utf8");
268
732
  existed = true;
269
733
  } catch (err) {
270
734
  const code = err && typeof err === "object" && "code" in err ? err.code : void 0;
@@ -275,22 +739,22 @@ async function ensureGitignoreMemoraone(repoRoot, opts) {
275
739
  const separator = existed && prior.length > 0 ? prior.endsWith("\n") ? "\n" : "\n\n" : "";
276
740
  const next = (existed ? prior : "") + separator + block;
277
741
  if (opts.dryRun) return existed ? "updated" : "created";
278
- await fs3.writeFile(abs, next, "utf8");
742
+ await fs4.writeFile(abs, next, "utf8");
279
743
  return existed ? "updated" : "created";
280
744
  }
281
745
  async function findRepoRoot(startDir) {
282
- let current = path3.resolve(startDir);
283
- const root = path3.parse(current).root;
746
+ let current = path4.resolve(startDir);
747
+ const root = path4.parse(current).root;
284
748
  while (true) {
285
- const gitPath = path3.join(current, ".git");
286
- const m1Path = path3.join(current, "memoraone.m1");
749
+ const gitPath = path4.join(current, ".git");
750
+ const m1Path = path4.join(current, "memoraone.m1");
287
751
  if (await pathExists(gitPath) || await pathExists(m1Path)) {
288
752
  return current;
289
753
  }
290
754
  if (current === root) {
291
755
  return null;
292
756
  }
293
- current = path3.dirname(current);
757
+ current = path4.dirname(current);
294
758
  }
295
759
  }
296
760
  function stripLeadingLineComments(text) {
@@ -306,9 +770,12 @@ This repository uses **MemoraOne** via the MCP server named **user-memoraone** (
306
770
  ### Tools
307
771
 
308
772
  - Before answering questions about **prior decisions**, **remembered facts**, **identity or personal recall**, **preferences**, **repo history**, or **what to do next**, call **\`memora_ask_with_memory\`** so replies stay aligned with MemoraOne memory.
309
- - After **meaningful repository changes** (decisions, fixes, new endpoints or schema, migrations, meaningful wiring), call **\`memora_post_event\`** to append a concise timeline note.
773
+ - Use **\`memora_post_event\`** for durable project decisions, wiring, migrations, fixes, and meaningful product behavior changes. Prefer kind \`note\`, \`content.title\`, \`content.body\` (one concise, fact-promotable statement), and metadata \`source\` (e.g. \`cursor\`), \`purpose\`: \`dev-log\`, \`schema\`: \`v1\`.
774
+ - Use **\`memora_log_change_summary\`** for concise code or feature deltas after implementation.
775
+
776
+ Do not spam events for trivial edits, formatting-only changes, or temporary WIP. Prefer concise, fact-promotable statements.
310
777
 
311
- Keep tool usage proportional to the task; do not spam events for trivial edits.
778
+ Example \`content.body\`: "MemoraOne Studio added a live dashboard summary, replacing the static dashboard placeholder with authenticated project and memory activity data."
312
779
  `;
313
780
  }
314
781
  function copilotAndJetBrainsBody(title) {
@@ -321,9 +788,12 @@ This repo is set up to use **MemoraOne** through MCP where your editor exposes i
321
788
  ## Behavior
322
789
 
323
790
  - For questions about **earlier decisions**, **stored facts**, **personal or identity recall**, **preferences**, **project history**, or **recommended next steps**, use MemoraOne memory tools (e.g. **\`memora_ask_with_memory\`**) when available before answering.
324
- - After **substantive changes** to this codebase (feature work, bugfixes, API/schema changes, migrations, meaningful integration steps), record a short timeline note with **\`memora_post_event\`** when that tool is available.
791
+ - Use **\`memora_post_event\`** for durable project decisions, wiring, migrations, fixes, and meaningful product behavior changes. Prefer kind \`note\`, \`content.title\`, \`content.body\` (one concise, fact-promotable statement), and metadata \`source\` (e.g. your agent name), \`purpose\`: \`dev-log\`, \`schema\`: \`v1\`.
792
+ - Use **\`memora_log_change_summary\`** for concise code or feature deltas after implementation.
325
793
 
326
- Use judgment: skip logging for trivial typo-only edits.
794
+ Do not spam events for trivial edits, formatting-only changes, or temporary WIP. Prefer concise, fact-promotable statements.
795
+
796
+ Example \`content.body\`: "MemoraOne Studio added a live dashboard summary, replacing the static dashboard placeholder with authenticated project and memory activity data."
327
797
  `;
328
798
  }
329
799
  function mcpJsonHeader() {
@@ -338,42 +808,42 @@ function buildMcpJsonBody(existing) {
338
808
  return mcpJsonHeader() + JSON.stringify(merged, null, 2) + "\n";
339
809
  }
340
810
  async function writeManagedMarkdown(repoRoot, relPath, fullContent, opts) {
341
- const abs = path3.join(repoRoot, relPath);
811
+ const abs = path4.join(repoRoot, relPath);
342
812
  assertUnderRepoRoot(repoRoot, abs);
343
813
  let prior = "";
344
814
  let existed = false;
345
815
  try {
346
- prior = await fs3.readFile(abs, "utf8");
816
+ prior = await fs4.readFile(abs, "utf8");
347
817
  existed = true;
348
818
  } catch (err) {
349
819
  if (err?.code !== "ENOENT") throw err;
350
820
  }
351
821
  if (!existed) {
352
822
  if (opts.dryRun) return "created";
353
- await fs3.mkdir(path3.dirname(abs), { recursive: true });
354
- await fs3.writeFile(abs, fullContent, "utf8");
823
+ await fs4.mkdir(path4.dirname(abs), { recursive: true });
824
+ await fs4.writeFile(abs, fullContent, "utf8");
355
825
  return "created";
356
826
  }
357
827
  if (prior.includes(MANAGED_MARKER)) {
358
828
  if (prior === fullContent) return "skipped";
359
829
  if (opts.dryRun) return "updated";
360
- await fs3.mkdir(path3.dirname(abs), { recursive: true });
361
- await fs3.writeFile(abs, fullContent, "utf8");
830
+ await fs4.mkdir(path4.dirname(abs), { recursive: true });
831
+ await fs4.writeFile(abs, fullContent, "utf8");
362
832
  return "updated";
363
833
  }
364
834
  if (!opts.force) return "skipped-untracked";
365
835
  if (opts.dryRun) return "updated";
366
- await fs3.mkdir(path3.dirname(abs), { recursive: true });
367
- await fs3.writeFile(abs, fullContent, "utf8");
836
+ await fs4.mkdir(path4.dirname(abs), { recursive: true });
837
+ await fs4.writeFile(abs, fullContent, "utf8");
368
838
  return "updated";
369
839
  }
370
840
  async function writeMcpJson(repoRoot, opts) {
371
- const abs = path3.join(repoRoot, ".vscode/mcp.json");
841
+ const abs = path4.join(repoRoot, ".vscode/mcp.json");
372
842
  assertUnderRepoRoot(repoRoot, abs);
373
843
  let raw = "";
374
844
  let existed = false;
375
845
  try {
376
- raw = await fs3.readFile(abs, "utf8");
846
+ raw = await fs4.readFile(abs, "utf8");
377
847
  existed = true;
378
848
  } catch (err) {
379
849
  if (err?.code !== "ENOENT") throw err;
@@ -381,8 +851,8 @@ async function writeMcpJson(repoRoot, opts) {
381
851
  if (!existed) {
382
852
  const body = buildMcpJsonBody(null);
383
853
  if (opts.dryRun) return "created";
384
- await fs3.mkdir(path3.dirname(abs), { recursive: true });
385
- await fs3.writeFile(abs, body, "utf8");
854
+ await fs4.mkdir(path4.dirname(abs), { recursive: true });
855
+ await fs4.writeFile(abs, body, "utf8");
386
856
  return "created";
387
857
  }
388
858
  const managed = raw.includes(MANAGED_MARKER);
@@ -397,8 +867,8 @@ async function writeMcpJson(repoRoot, opts) {
397
867
  const next = buildMcpJsonBody(parsed);
398
868
  if (managed && next === raw) return "skipped";
399
869
  if (opts.dryRun) return "updated";
400
- await fs3.mkdir(path3.dirname(abs), { recursive: true });
401
- await fs3.writeFile(abs, next, "utf8");
870
+ await fs4.mkdir(path4.dirname(abs), { recursive: true });
871
+ await fs4.writeFile(abs, next, "utf8");
402
872
  return "updated";
403
873
  }
404
874
  function parseSetupIdeFlags(argv) {
@@ -409,6 +879,7 @@ function parseSetupIdeFlags(argv) {
409
879
  let force = false;
410
880
  let dryRun = false;
411
881
  let noGitignore = false;
882
+ let cleanup = false;
412
883
  const unknown = [];
413
884
  for (const a of argv) {
414
885
  if (a === "--cursor") cursor = true;
@@ -418,6 +889,7 @@ function parseSetupIdeFlags(argv) {
418
889
  else if (a === "--force") force = true;
419
890
  else if (a === "--dry-run") dryRun = true;
420
891
  else if (a === "--no-gitignore") noGitignore = true;
892
+ else if (a === "--cleanup") cleanup = true;
421
893
  else if (a.startsWith("-")) unknown.push(a);
422
894
  }
423
895
  const specific = cursor || vscode || jetbrains;
@@ -427,7 +899,7 @@ function parseSetupIdeFlags(argv) {
427
899
  } else {
428
900
  targets = { cursor, vscode, jetbrains };
429
901
  }
430
- return { targets, force, dryRun, noGitignore, unknown };
902
+ return { targets, force, dryRun, noGitignore, cleanup, unknown };
431
903
  }
432
904
  function summarizeOutcomes(outcomes) {
433
905
  const created = [];
@@ -500,7 +972,7 @@ description: MemoraOne MCP \u2014 IDE agent instructions
500
972
  return { exitCode: 0, repoRoot, outcomes };
501
973
  }
502
974
  async function cliSetupIdeFiles(argv) {
503
- const { targets, force, dryRun, noGitignore, unknown } = parseSetupIdeFlags(argv);
975
+ const { targets, force, dryRun, noGitignore, cleanup, unknown } = parseSetupIdeFlags(argv);
504
976
  if (unknown.length) {
505
977
  console.error(`[setup-ide-files] Unknown option(s): ${unknown.join(", ")}`);
506
978
  return 1;
@@ -523,6 +995,26 @@ async function cliSetupIdeFiles(argv) {
523
995
  if (dryRun) {
524
996
  console.log("[setup-ide-files] Dry run: no files written.");
525
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
+ }
526
1018
  return 0;
527
1019
  }
528
1020
 
@@ -535,11 +1027,17 @@ if (args.includes("--version") || args.includes("-v")) {
535
1027
  }
536
1028
  if (args.includes("--help") || args.includes("-h")) {
537
1029
  console.log(
538
- "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]"
539
1031
  );
540
1032
  process.exit(0);
541
1033
  }
542
- 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") {
543
1041
  cliSetupIdeFiles(args.slice(1)).then((code) => process.exit(code)).catch((err) => {
544
1042
  process.stderr.write(`[memoraone-mcp] setup-ide-files fatal: ${String(err)}
545
1043
  `);
@@ -556,8 +1054,8 @@ if (args[0] === "setup-ide-files") {
556
1054
  const raw = process.env.WORKSPACE_FOLDER_PATHS;
557
1055
  const parts = [];
558
1056
  if (raw !== void 0 && raw.trim() !== "") {
559
- for (const p of raw.split(path4.delimiter).map((s) => s.trim()).filter(Boolean)) {
560
- 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));
561
1059
  }
562
1060
  }
563
1061
  parts.push(process.cwd());
@@ -571,10 +1069,10 @@ if (args[0] === "setup-ide-files") {
571
1069
  }
572
1070
  return deduped;
573
1071
  }, connectWithRetry = function(socketPath) {
574
- return new Promise((resolve4, reject) => {
1072
+ return new Promise((resolve5, reject) => {
575
1073
  const tryConnect = (attempt) => {
576
1074
  const socket = net.connect(socketPath, () => {
577
- resolve4(socket);
1075
+ resolve5(socket);
578
1076
  });
579
1077
  socket.on("error", (err) => {
580
1078
  if (attempt >= MAX_RETRIES) {
@@ -600,23 +1098,28 @@ if (args[0] === "setup-ide-files") {
600
1098
  async function runBridge() {
601
1099
  ensureBaseDir();
602
1100
  const binding = await resolveBinding();
603
- const socketPath = getSocketPath(binding.projectId);
1101
+ const socketPath = getDaemonSocketPath(binding.projectId);
1102
+ const environmentLog = binding.environment !== void 0 ? ` environment=${binding.environment}` : "";
604
1103
  log(
605
- `authoritative binding project=${binding.projectId} workspace=${binding.workspaceRoot} m1=${binding.m1Path} source=${binding.bindingSource} apiKeySource=${binding.apiKeySource}`
1104
+ `authoritative binding project=${binding.projectId} workspace=${binding.workspaceRoot} m1=${binding.m1Path} source=${binding.bindingSource} apiKeySource=${binding.apiKeySource}${environmentLog}`
606
1105
  );
607
1106
  let socket;
608
1107
  try {
609
1108
  socket = await connectWithRetry(socketPath);
610
1109
  } catch {
611
1110
  log("daemon not running, spawning...");
612
- const child = (0, import_node_child_process.spawn)(process.execPath, [process.argv[1], "--daemon", "--project-id", binding.projectId], {
613
- detached: true,
614
- stdio: "ignore",
615
- env: {
616
- ...process.env,
617
- 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
+ }
618
1121
  }
619
- });
1122
+ );
620
1123
  child.unref();
621
1124
  await new Promise((r) => setTimeout(r, RETRY_DELAY_MS));
622
1125
  socket = await connectWithRetry(socketPath);