@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 +532 -46
- package/dist/daemon.cjs +204 -69
- package/dist/index.cjs +93 -66
- package/package.json +11 -10
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.
|
|
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
|
|
70
|
+
var path5 = __toESM(require("path"), 1);
|
|
71
71
|
var net = __toESM(require("net"), 1);
|
|
72
|
-
var
|
|
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
|
-
|
|
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 =
|
|
244
|
-
const normPath =
|
|
245
|
-
if (normPath !==
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
742
|
+
await fs4.writeFile(abs, next, "utf8");
|
|
289
743
|
return existed ? "updated" : "created";
|
|
290
744
|
}
|
|
291
745
|
async function findRepoRoot(startDir) {
|
|
292
|
-
let current =
|
|
293
|
-
const root =
|
|
746
|
+
let current = path4.resolve(startDir);
|
|
747
|
+
const root = path4.parse(current).root;
|
|
294
748
|
while (true) {
|
|
295
|
-
const gitPath =
|
|
296
|
-
const m1Path =
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
370
|
-
await
|
|
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
|
|
377
|
-
await
|
|
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
|
|
383
|
-
await
|
|
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 =
|
|
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
|
|
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
|
|
401
|
-
await
|
|
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
|
|
417
|
-
await
|
|
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] === "
|
|
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(
|
|
576
|
-
parts.push(
|
|
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((
|
|
1072
|
+
return new Promise((resolve5, reject) => {
|
|
591
1073
|
const tryConnect = (attempt) => {
|
|
592
1074
|
const socket = net.connect(socketPath, () => {
|
|
593
|
-
|
|
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 =
|
|
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,
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
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);
|