@memoraone/mcp 0.1.30 → 0.1.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.cjs +1818 -747
- package/dist/daemon.cjs +426 -146
- package/dist/index.cjs +371 -120
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -30,7 +30,7 @@ var require_package = __commonJS({
|
|
|
30
30
|
"package.json"(exports2, module2) {
|
|
31
31
|
module2.exports = {
|
|
32
32
|
name: "@memoraone/mcp",
|
|
33
|
-
version: "0.1.
|
|
33
|
+
version: "0.1.31",
|
|
34
34
|
type: "module",
|
|
35
35
|
main: "dist/index.cjs",
|
|
36
36
|
bin: {
|
|
@@ -66,88 +66,57 @@ var require_package = __commonJS({
|
|
|
66
66
|
}
|
|
67
67
|
});
|
|
68
68
|
|
|
69
|
-
// src/
|
|
70
|
-
var path7 = __toESM(require("path"), 1);
|
|
69
|
+
// src/bridgeProxy.ts
|
|
71
70
|
var net = __toESM(require("net"), 1);
|
|
72
|
-
var
|
|
71
|
+
var readline2 = __toESM(require("readline"), 1);
|
|
72
|
+
var import_node_child_process = require("child_process");
|
|
73
73
|
|
|
74
|
-
// src/
|
|
75
|
-
var
|
|
74
|
+
// src/bindingIdentity.ts
|
|
75
|
+
var crypto = __toESM(require("crypto"), 1);
|
|
76
76
|
var path = __toESM(require("path"), 1);
|
|
77
|
-
var
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
if (ideType) {
|
|
108
|
-
return path.join(BASE_DIR, `mcp-${projectId}-${ideType}.sock`);
|
|
109
|
-
}
|
|
110
|
-
return path.join(BASE_DIR, `mcp-${projectId}.sock`);
|
|
111
|
-
}
|
|
112
|
-
function getDaemonSocketPath(projectId, env = process.env) {
|
|
113
|
-
return getSocketPath(projectId, resolveIdeTypeFromEnv(env));
|
|
114
|
-
}
|
|
115
|
-
function ensureBaseDir() {
|
|
116
|
-
fs.mkdirSync(BASE_DIR, { recursive: true });
|
|
117
|
-
return BASE_DIR;
|
|
118
|
-
}
|
|
119
|
-
function isMemoraoneSocketFilename(filename) {
|
|
120
|
-
return SOCKET_PROJECT_ID_RE.test(path.basename(filename));
|
|
121
|
-
}
|
|
122
|
-
function extractProjectIdFromSocketFilename(filename) {
|
|
123
|
-
const match = path.basename(filename).match(SOCKET_PROJECT_ID_RE);
|
|
124
|
-
return match ? match[1].toLowerCase() : null;
|
|
125
|
-
}
|
|
126
|
-
function isSocketFilenameForProject(filename, projectId) {
|
|
127
|
-
const extracted = extractProjectIdFromSocketFilename(filename);
|
|
128
|
-
return extracted !== null && extracted === projectId.trim().toLowerCase();
|
|
129
|
-
}
|
|
130
|
-
function extractIdeTypeFromSocketFilename(filename) {
|
|
131
|
-
const match = path.basename(filename).match(SOCKET_PROJECT_ID_RE);
|
|
132
|
-
if (!match) return null;
|
|
133
|
-
const suffix = match[2];
|
|
134
|
-
if (suffix === void 0) return "legacy";
|
|
135
|
-
if (IDE_TYPE_SET.has(suffix)) return suffix;
|
|
136
|
-
return null;
|
|
137
|
-
}
|
|
138
|
-
function isSocketFilenameForProjectAndIde(filename, projectId, ide) {
|
|
139
|
-
if (!isSocketFilenameForProject(filename, projectId)) return false;
|
|
140
|
-
if (ide === void 0) return true;
|
|
141
|
-
const socketIde = extractIdeTypeFromSocketFilename(filename);
|
|
142
|
-
if (socketIde === null) return false;
|
|
143
|
-
if (ide === "cursor") {
|
|
144
|
-
return socketIde === "cursor" || socketIde === "legacy";
|
|
77
|
+
var BINDING_SOCKET_HASH_LENGTH = 16;
|
|
78
|
+
function hashBindingIdentity(projectId, workspaceRoot, ideType) {
|
|
79
|
+
const input2 = [
|
|
80
|
+
projectId.trim().toLowerCase(),
|
|
81
|
+
path.resolve(workspaceRoot),
|
|
82
|
+
ideType
|
|
83
|
+
].join("|");
|
|
84
|
+
return crypto.createHash("sha256").update(input2).digest("hex").slice(0, BINDING_SOCKET_HASH_LENGTH);
|
|
85
|
+
}
|
|
86
|
+
function bindingsMatch(a, b) {
|
|
87
|
+
return a.projectId.trim().toLowerCase() === b.projectId.trim().toLowerCase() && path.resolve(a.workspaceRoot) === path.resolve(b.workspaceRoot) && path.resolve(a.m1Path) === path.resolve(b.m1Path);
|
|
88
|
+
}
|
|
89
|
+
function formatMissingInitializeWorkspaceError(options) {
|
|
90
|
+
const lines = [
|
|
91
|
+
"[memoraone-mcp] Could not resolve workspace from MCP initialize params."
|
|
92
|
+
];
|
|
93
|
+
if (options?.rootsListAttempted) {
|
|
94
|
+
lines.push(
|
|
95
|
+
"Cursor initialize lacked workspaceFolders/rootUri; roots/list was attempted but returned no usable repo root."
|
|
96
|
+
);
|
|
97
|
+
if (options.rootsListUris && options.rootsListUris.length > 0) {
|
|
98
|
+
lines.push(`roots/list URIs: ${options.rootsListUris.join(", ")}`);
|
|
99
|
+
}
|
|
100
|
+
lines.push(
|
|
101
|
+
"Global Cursor MCP cannot safely bind per-window repos without a workspace signal from Cursor (initialize roots or roots/list)."
|
|
102
|
+
);
|
|
103
|
+
lines.push(
|
|
104
|
+
"Reload MCP in this Cursor window, or ensure this repo has a managed .cursor/mcp.json from setup-ide-files --cursor."
|
|
105
|
+
);
|
|
106
|
+
return lines.join("\n");
|
|
145
107
|
}
|
|
146
|
-
|
|
108
|
+
lines.push(
|
|
109
|
+
"Reload MCP in this Cursor window so initialize includes workspaceFolders, rootUri, or a usable roots/list response for this repo."
|
|
110
|
+
);
|
|
111
|
+
return lines.join("\n");
|
|
147
112
|
}
|
|
148
113
|
|
|
114
|
+
// src/bindingSidecar.ts
|
|
115
|
+
var fs3 = __toESM(require("fs"), 1);
|
|
116
|
+
var path4 = __toESM(require("path"), 1);
|
|
117
|
+
|
|
149
118
|
// src/projectBinding.ts
|
|
150
|
-
var
|
|
119
|
+
var fs = __toESM(require("fs/promises"), 1);
|
|
151
120
|
var path2 = __toESM(require("path"), 1);
|
|
152
121
|
var uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
153
122
|
function normalizeEnvironment(raw) {
|
|
@@ -183,7 +152,7 @@ async function resolveProjectIdFromExplicitM1Path() {
|
|
|
183
152
|
}
|
|
184
153
|
const markerPath = path2.resolve(raw);
|
|
185
154
|
try {
|
|
186
|
-
const content = await
|
|
155
|
+
const content = await fs.readFile(markerPath, "utf8");
|
|
187
156
|
const { projectId, apiKey, environment } = parseAndValidateM1(content, markerPath);
|
|
188
157
|
return environment === void 0 ? { projectId, apiKey, foundAt: markerPath } : { projectId, apiKey, environment, foundAt: markerPath };
|
|
189
158
|
} catch (err) {
|
|
@@ -198,7 +167,7 @@ async function findM1WalkingUp(workspaceRoot) {
|
|
|
198
167
|
while (true) {
|
|
199
168
|
const markerPath = path2.join(current, "memoraone.m1");
|
|
200
169
|
try {
|
|
201
|
-
const content = await
|
|
170
|
+
const content = await fs.readFile(markerPath, "utf8");
|
|
202
171
|
const { projectId, apiKey, environment } = parseAndValidateM1(content, markerPath);
|
|
203
172
|
const repoRoot = path2.dirname(markerPath);
|
|
204
173
|
return environment === void 0 ? { projectId, apiKey, repoRoot, markerPath } : { projectId, apiKey, environment, repoRoot, markerPath };
|
|
@@ -252,29 +221,33 @@ function resolveApiKeyWithSource(fileApiKey) {
|
|
|
252
221
|
}
|
|
253
222
|
return { apiKey: null, apiKeySource: "none" };
|
|
254
223
|
}
|
|
255
|
-
async function resolveAuthoritativeBinding(workspaceRoot) {
|
|
256
|
-
const
|
|
257
|
-
if (
|
|
258
|
-
const
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
224
|
+
async function resolveAuthoritativeBinding(workspaceRoot, options = {}) {
|
|
225
|
+
const respectExplicitM1Path = options.respectExplicitM1Path !== false;
|
|
226
|
+
if (respectExplicitM1Path) {
|
|
227
|
+
const explicitBinding = await resolveProjectIdFromExplicitM1Path();
|
|
228
|
+
if (explicitBinding) {
|
|
229
|
+
const resolved = resolveApiKeyWithSource(explicitBinding.apiKey);
|
|
230
|
+
return {
|
|
231
|
+
projectId: explicitBinding.projectId,
|
|
232
|
+
workspaceRoot: path2.dirname(explicitBinding.foundAt),
|
|
233
|
+
m1Path: explicitBinding.foundAt,
|
|
234
|
+
apiKey: resolved.apiKey,
|
|
235
|
+
...explicitBinding.environment !== void 0 ? { environment: explicitBinding.environment } : {},
|
|
236
|
+
bindingSource: "explicit-m1-path",
|
|
237
|
+
apiKeySource: resolved.apiKeySource
|
|
238
|
+
};
|
|
239
|
+
}
|
|
268
240
|
}
|
|
269
241
|
const candidates = normalizeWorkspaceSearchRoots(workspaceRoot);
|
|
270
242
|
if (candidates.length === 0) {
|
|
271
243
|
throw new Error("Could not find memoraone.m1 in workspace.\nOpen a folder containing memoraone.m1.");
|
|
272
244
|
}
|
|
245
|
+
const bindings = [];
|
|
273
246
|
for (const root of candidates) {
|
|
274
247
|
const binding = await findM1WalkingUp(root);
|
|
275
248
|
if (binding) {
|
|
276
249
|
const resolved = resolveApiKeyWithSource(binding.apiKey);
|
|
277
|
-
|
|
250
|
+
bindings.push({
|
|
278
251
|
projectId: binding.projectId,
|
|
279
252
|
workspaceRoot: binding.repoRoot,
|
|
280
253
|
m1Path: binding.markerPath,
|
|
@@ -282,159 +255,1339 @@ async function resolveAuthoritativeBinding(workspaceRoot) {
|
|
|
282
255
|
...binding.environment !== void 0 ? { environment: binding.environment } : {},
|
|
283
256
|
bindingSource: "workspace-search",
|
|
284
257
|
apiKeySource: resolved.apiKeySource
|
|
285
|
-
};
|
|
258
|
+
});
|
|
286
259
|
}
|
|
287
260
|
}
|
|
288
|
-
|
|
261
|
+
if (bindings.length === 0) {
|
|
262
|
+
throw new Error("Could not find memoraone.m1 in workspace.\nOpen a folder containing memoraone.m1.");
|
|
263
|
+
}
|
|
264
|
+
const distinctProjectIds = new Set(bindings.map((b) => b.projectId));
|
|
265
|
+
if (distinctProjectIds.size > 1) {
|
|
266
|
+
const lines = bindings.map(
|
|
267
|
+
(b) => ` - workspace=${b.workspaceRoot} project=${b.projectId} m1=${b.m1Path}`
|
|
268
|
+
);
|
|
269
|
+
throw new Error(
|
|
270
|
+
"[memoraone-mcp] Ambiguous workspace binding: multiple open roots map to different MemoraOne projects.\n" + lines.join("\n") + "\nOpen one repo per Cursor window, or use repo-scoped .cursor/mcp.json from setup-ide-files --cursor."
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
return bindings[0];
|
|
289
274
|
}
|
|
290
275
|
function encodeResolvedBinding(binding) {
|
|
291
276
|
return Buffer.from(JSON.stringify(binding), "utf8").toString("base64");
|
|
292
277
|
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
var fs6 = __toESM(require("fs/promises"), 1);
|
|
296
|
-
var os4 = __toESM(require("os"), 1);
|
|
297
|
-
var path6 = __toESM(require("path"), 1);
|
|
298
|
-
|
|
299
|
-
// src/cleanup.ts
|
|
300
|
-
var fs3 = __toESM(require("fs/promises"), 1);
|
|
301
|
-
var path3 = __toESM(require("path"), 1);
|
|
302
|
-
var readline = __toESM(require("readline/promises"), 1);
|
|
303
|
-
var import_node_child_process = require("child_process");
|
|
304
|
-
var import_node_util = require("util");
|
|
305
|
-
var import_node_process = require("process");
|
|
306
|
-
var execFileAsync = (0, import_node_util.promisify)(import_node_child_process.execFile);
|
|
307
|
-
var DAEMON_PROJECT_ID_RE = /--project-id\s+([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/i;
|
|
308
|
-
var PROJECT_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
309
|
-
var MEMORAONE_MCP_COMMAND_RE = /memoraone-mcp|memoraOne-mcp|@memoraone\/mcp/;
|
|
310
|
-
var CLEANUP_PROJECT_ID_REQUIRED_ERROR = "Provide --project-id <id> or run from a folder containing memoraone.m1.";
|
|
311
|
-
function isMemoraoneMcpCommandLine(commandLine) {
|
|
312
|
-
return MEMORAONE_MCP_COMMAND_RE.test(commandLine);
|
|
313
|
-
}
|
|
314
|
-
function parseDaemonProjectIdFromCommandLine(commandLine) {
|
|
315
|
-
if (!commandLine.includes("--daemon")) {
|
|
278
|
+
function decodeResolvedBinding(value) {
|
|
279
|
+
if (!value) {
|
|
316
280
|
return null;
|
|
317
281
|
}
|
|
318
|
-
|
|
319
|
-
|
|
282
|
+
let parsed;
|
|
283
|
+
try {
|
|
284
|
+
parsed = JSON.parse(Buffer.from(value, "base64").toString("utf8"));
|
|
285
|
+
} catch {
|
|
286
|
+
throw new Error("[memoraone-mcp] Invalid encoded binding payload");
|
|
287
|
+
}
|
|
288
|
+
const projectId = parsed?.projectId;
|
|
289
|
+
const workspaceRoot = parsed?.workspaceRoot;
|
|
290
|
+
const m1Path = parsed?.m1Path;
|
|
291
|
+
const apiKey = parsed?.apiKey;
|
|
292
|
+
const environment = normalizeEnvironment(parsed?.environment);
|
|
293
|
+
const bindingSource = parsed?.bindingSource;
|
|
294
|
+
const apiKeySource = parsed?.apiKeySource;
|
|
295
|
+
if (!projectId || typeof projectId !== "string" || !uuidRegex.test(projectId.trim())) {
|
|
296
|
+
throw new Error("[memoraone-mcp] Invalid binding projectId");
|
|
297
|
+
}
|
|
298
|
+
if (!workspaceRoot || typeof workspaceRoot !== "string") {
|
|
299
|
+
throw new Error("[memoraone-mcp] Invalid binding workspaceRoot");
|
|
300
|
+
}
|
|
301
|
+
if (!m1Path || typeof m1Path !== "string") {
|
|
302
|
+
throw new Error("[memoraone-mcp] Invalid binding m1Path");
|
|
303
|
+
}
|
|
304
|
+
if (apiKey !== null && apiKey !== void 0 && typeof apiKey !== "string") {
|
|
305
|
+
throw new Error("[memoraone-mcp] Invalid binding apiKey");
|
|
306
|
+
}
|
|
307
|
+
if (bindingSource !== "explicit-m1-path" && bindingSource !== "workspace-search") {
|
|
308
|
+
throw new Error("[memoraone-mcp] Invalid binding source");
|
|
309
|
+
}
|
|
310
|
+
if (apiKeySource !== "env" && apiKeySource !== "memoraone.m1" && apiKeySource !== "none") {
|
|
311
|
+
throw new Error("[memoraone-mcp] Invalid binding apiKeySource");
|
|
312
|
+
}
|
|
313
|
+
return {
|
|
314
|
+
projectId: projectId.trim(),
|
|
315
|
+
workspaceRoot,
|
|
316
|
+
m1Path,
|
|
317
|
+
apiKey: typeof apiKey === "string" && apiKey.trim() !== "" ? apiKey.trim() : null,
|
|
318
|
+
...environment !== void 0 ? { environment } : {},
|
|
319
|
+
bindingSource,
|
|
320
|
+
apiKeySource
|
|
321
|
+
};
|
|
320
322
|
}
|
|
321
|
-
|
|
322
|
-
|
|
323
|
+
|
|
324
|
+
// src/socketPaths.ts
|
|
325
|
+
var os = __toESM(require("os"), 1);
|
|
326
|
+
var path3 = __toESM(require("path"), 1);
|
|
327
|
+
var fs2 = __toESM(require("fs"), 1);
|
|
328
|
+
var BASE_DIR = process.env.MEMORAONE_MCP_LOCK_DIR || path3.join(os.homedir(), ".memoraone-mcp");
|
|
329
|
+
var HASH_SOCKET_FILENAME_RE = new RegExp(
|
|
330
|
+
`^mcp-[0-9a-f]{${BINDING_SOCKET_HASH_LENGTH}}\\.sock$`,
|
|
331
|
+
"i"
|
|
332
|
+
);
|
|
333
|
+
var LEGACY_SOCKET_FILENAME_RE = /^mcp-([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?:-([0-9a-f]{12}))?(?:-(cursor|jetbrains|copilot-vscode))?\.sock$/i;
|
|
334
|
+
var LEGACY_SOCKET_PROJECT_ID_RE = LEGACY_SOCKET_FILENAME_RE;
|
|
335
|
+
function getMcpBaseDir() {
|
|
336
|
+
return BASE_DIR;
|
|
337
|
+
}
|
|
338
|
+
var IDE_TYPES = ["cursor", "copilot-vscode", "jetbrains"];
|
|
339
|
+
var IDE_TYPE_SET = new Set(IDE_TYPES);
|
|
340
|
+
function parseIdeType(value) {
|
|
341
|
+
if (value === void 0 || value.trim() === "" || !IDE_TYPE_SET.has(value)) {
|
|
323
342
|
return void 0;
|
|
324
343
|
}
|
|
325
|
-
return
|
|
344
|
+
return value;
|
|
326
345
|
}
|
|
327
|
-
function
|
|
328
|
-
|
|
329
|
-
for (const line of lines) {
|
|
330
|
-
const trimmed = line.trim();
|
|
331
|
-
if (!trimmed) continue;
|
|
332
|
-
const spaceIdx = trimmed.indexOf(" ");
|
|
333
|
-
if (spaceIdx <= 0) continue;
|
|
334
|
-
const pid = Number.parseInt(trimmed.slice(0, spaceIdx), 10);
|
|
335
|
-
if (!Number.isFinite(pid) || pid <= 0) continue;
|
|
336
|
-
const command = trimmed.slice(spaceIdx + 1);
|
|
337
|
-
if (!isMemoraoneMcpCommandLine(command)) continue;
|
|
338
|
-
const projectId = parseDaemonProjectIdFromCommandLine(command);
|
|
339
|
-
if (projectId === null) continue;
|
|
340
|
-
const ide = parseDaemonIdeFromCommandLine(command);
|
|
341
|
-
processes.push({ pid, command, projectId, ...ide !== void 0 ? { ide } : {} });
|
|
342
|
-
}
|
|
343
|
-
return processes;
|
|
346
|
+
function resolveIdeTypeFromEnv(env = process.env) {
|
|
347
|
+
return parseIdeType(env.MEMORAONE_IDE_TYPE);
|
|
344
348
|
}
|
|
345
|
-
function
|
|
346
|
-
const
|
|
347
|
-
|
|
348
|
-
|
|
349
|
+
function parseIdeTypeFromCommandLine(commandLine) {
|
|
350
|
+
const match = commandLine.match(/--ide\s+(cursor|copilot-vscode|jetbrains)(?:\s|$)/);
|
|
351
|
+
return match ? match[1] : void 0;
|
|
352
|
+
}
|
|
353
|
+
function buildDaemonSpawnArgs(scriptPath, projectId, env = process.env) {
|
|
354
|
+
const args2 = [scriptPath, "--daemon", "--project-id", projectId];
|
|
355
|
+
const ideType = resolveIdeTypeFromEnv(env);
|
|
356
|
+
if (ideType) {
|
|
357
|
+
args2.push("--ide", ideType);
|
|
349
358
|
}
|
|
350
|
-
return
|
|
359
|
+
return args2;
|
|
351
360
|
}
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
maxBuffer: 10 * 1024 * 1024
|
|
355
|
-
});
|
|
356
|
-
return parseDaemonProcessLines(stdout.split("\n"));
|
|
361
|
+
function resolveBindingIdeType(env = process.env) {
|
|
362
|
+
return resolveIdeTypeFromEnv(env) ?? "";
|
|
357
363
|
}
|
|
358
|
-
|
|
359
|
-
const
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
entries = await fs3.readdir(baseDir);
|
|
363
|
-
} catch (err) {
|
|
364
|
-
const code = err && typeof err === "object" && "code" in err ? err.code : void 0;
|
|
365
|
-
if (code === "ENOENT") {
|
|
366
|
-
return [];
|
|
367
|
-
}
|
|
368
|
-
throw err;
|
|
369
|
-
}
|
|
370
|
-
const paths = [];
|
|
371
|
-
for (const name of entries) {
|
|
372
|
-
if (projectId === null) {
|
|
373
|
-
if (isMemoraoneSocketFilename(name)) {
|
|
374
|
-
paths.push(path3.join(baseDir, name));
|
|
375
|
-
}
|
|
376
|
-
} else if (isSocketFilenameForProject(name, projectId)) {
|
|
377
|
-
paths.push(path3.join(baseDir, name));
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
return paths.sort();
|
|
364
|
+
function getBindingSocketFilename(binding, env = process.env) {
|
|
365
|
+
const ideType = resolveBindingIdeType(env);
|
|
366
|
+
const hash = hashBindingIdentity(binding.projectId, binding.workspaceRoot, ideType);
|
|
367
|
+
return `mcp-${hash}.sock`;
|
|
381
368
|
}
|
|
382
|
-
function
|
|
383
|
-
|
|
384
|
-
return socketPaths.filter(
|
|
385
|
-
(socketPath) => isSocketFilenameForProjectAndIde(path3.basename(socketPath), projectId, ide)
|
|
386
|
-
);
|
|
369
|
+
function getBindingSocketPath(binding, env = process.env) {
|
|
370
|
+
return path3.join(BASE_DIR, getBindingSocketFilename(binding, env));
|
|
387
371
|
}
|
|
388
|
-
|
|
389
|
-
|
|
372
|
+
function ensureBaseDir() {
|
|
373
|
+
fs2.mkdirSync(BASE_DIR, { recursive: true });
|
|
374
|
+
return BASE_DIR;
|
|
390
375
|
}
|
|
391
|
-
|
|
392
|
-
|
|
376
|
+
function isHashSocketFilename(filename) {
|
|
377
|
+
return HASH_SOCKET_FILENAME_RE.test(path3.basename(filename));
|
|
393
378
|
}
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
379
|
+
function isLegacySocketFilename(filename) {
|
|
380
|
+
return LEGACY_SOCKET_FILENAME_RE.test(path3.basename(filename));
|
|
381
|
+
}
|
|
382
|
+
function isMemoraoneSocketFilename(filename) {
|
|
383
|
+
const base = path3.basename(filename);
|
|
384
|
+
return HASH_SOCKET_FILENAME_RE.test(base) || LEGACY_SOCKET_PROJECT_ID_RE.test(base);
|
|
385
|
+
}
|
|
386
|
+
function extractProjectIdFromSocketFilename(filename) {
|
|
387
|
+
const match = path3.basename(filename).match(LEGACY_SOCKET_PROJECT_ID_RE);
|
|
388
|
+
return match ? match[1].toLowerCase() : null;
|
|
389
|
+
}
|
|
390
|
+
function isSocketFilenameForProject(filename, projectId) {
|
|
391
|
+
const extracted = extractProjectIdFromSocketFilename(filename);
|
|
392
|
+
return extracted !== null && extracted === projectId.trim().toLowerCase();
|
|
393
|
+
}
|
|
394
|
+
function extractIdeTypeFromSocketFilename(filename) {
|
|
395
|
+
const match = path3.basename(filename).match(LEGACY_SOCKET_FILENAME_RE);
|
|
396
|
+
if (!match) return null;
|
|
397
|
+
const ide = match[3];
|
|
398
|
+
if (ide === void 0) return "legacy";
|
|
399
|
+
if (IDE_TYPE_SET.has(ide)) return ide;
|
|
400
|
+
return null;
|
|
401
|
+
}
|
|
402
|
+
function isSocketFilenameForProjectAndIde(filename, projectId, ide) {
|
|
403
|
+
if (!isSocketFilenameForProject(filename, projectId)) return false;
|
|
404
|
+
if (ide === void 0) return true;
|
|
405
|
+
const socketIde = extractIdeTypeFromSocketFilename(filename);
|
|
406
|
+
if (socketIde === null) return false;
|
|
407
|
+
if (ide === "cursor") {
|
|
408
|
+
return socketIde === "cursor" || socketIde === "legacy";
|
|
397
409
|
}
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
410
|
+
return socketIde === ide;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// src/bindingSidecar.ts
|
|
414
|
+
function bindingSidecarPath(socketPath) {
|
|
415
|
+
if (socketPath.endsWith(".sock")) {
|
|
416
|
+
return `${socketPath.slice(0, -".sock".length)}.binding.json`;
|
|
404
417
|
}
|
|
418
|
+
return `${socketPath}.binding.json`;
|
|
405
419
|
}
|
|
406
|
-
|
|
420
|
+
function parseSidecarRecord(raw) {
|
|
407
421
|
try {
|
|
408
|
-
const
|
|
422
|
+
const parsed = JSON.parse(raw);
|
|
423
|
+
if (!parsed?.binding) {
|
|
424
|
+
return null;
|
|
425
|
+
}
|
|
426
|
+
const binding = decodeResolvedBinding(parsed.binding);
|
|
409
427
|
return {
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
projectId: binding.projectId
|
|
428
|
+
v: typeof parsed.v === "number" ? parsed.v : 1,
|
|
429
|
+
...parsed.ideType ? { ideType: parsed.ideType } : {},
|
|
430
|
+
projectId: parsed.projectId ?? binding.projectId,
|
|
431
|
+
workspaceRoot: parsed.workspaceRoot ?? binding.workspaceRoot,
|
|
432
|
+
m1Path: parsed.m1Path ?? binding.m1Path,
|
|
433
|
+
binding: parsed.binding
|
|
413
434
|
};
|
|
414
435
|
} catch {
|
|
415
|
-
return
|
|
436
|
+
return null;
|
|
416
437
|
}
|
|
417
438
|
}
|
|
418
|
-
function
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
const skipped = [];
|
|
425
|
-
for (const proc of processes) {
|
|
426
|
-
if (proc.projectId === normalized) {
|
|
427
|
-
matching.push(proc);
|
|
428
|
-
} else {
|
|
429
|
-
skipped.push(proc);
|
|
430
|
-
}
|
|
439
|
+
function readBindingSidecarRecord(socketPath) {
|
|
440
|
+
try {
|
|
441
|
+
const raw = fs3.readFileSync(bindingSidecarPath(socketPath), "utf8");
|
|
442
|
+
return parseSidecarRecord(raw);
|
|
443
|
+
} catch {
|
|
444
|
+
return null;
|
|
431
445
|
}
|
|
432
|
-
|
|
446
|
+
}
|
|
447
|
+
function formatBindingMismatchError(socketPath, sidecar, expected, detail) {
|
|
448
|
+
const lines = [
|
|
449
|
+
`[memoraone-mcp] Daemon socket binding mismatch at ${path4.basename(socketPath)}.`,
|
|
450
|
+
` socket: project=${sidecar.projectId} workspace=${sidecar.workspaceRoot} m1=${sidecar.m1Path}`,
|
|
451
|
+
` session: project=${expected.projectId} workspace=${expected.workspaceRoot} m1=${expected.m1Path}`
|
|
452
|
+
];
|
|
453
|
+
if (detail) {
|
|
454
|
+
lines.push(` ${detail}`);
|
|
455
|
+
}
|
|
456
|
+
lines.push("Reload MCP in this IDE window or run memoraone-mcp cleanup for the stale socket.");
|
|
457
|
+
return lines.join("\n");
|
|
458
|
+
}
|
|
459
|
+
function verifyDaemonSidecarBinding(socketPath, expected, env = process.env) {
|
|
460
|
+
const record = readBindingSidecarRecord(socketPath);
|
|
461
|
+
if (!record) {
|
|
462
|
+
return null;
|
|
463
|
+
}
|
|
464
|
+
const sidecar = decodeResolvedBinding(record.binding);
|
|
465
|
+
if (!bindingsMatch(sidecar, expected)) {
|
|
466
|
+
throw new Error(formatBindingMismatchError(socketPath, sidecar, expected));
|
|
467
|
+
}
|
|
468
|
+
const expectedIdeType = resolveBindingIdeType(env);
|
|
469
|
+
if (record.ideType !== void 0 && record.ideType !== expectedIdeType) {
|
|
470
|
+
throw new Error(
|
|
471
|
+
formatBindingMismatchError(
|
|
472
|
+
socketPath,
|
|
473
|
+
sidecar,
|
|
474
|
+
expected,
|
|
475
|
+
`ideType: socket=${record.ideType} session=${expectedIdeType || "(none)"}`
|
|
476
|
+
)
|
|
477
|
+
);
|
|
478
|
+
}
|
|
479
|
+
return sidecar;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// src/bridgeClientRoots.ts
|
|
483
|
+
var readline = __toESM(require("readline"), 1);
|
|
484
|
+
var TRUTHY = /* @__PURE__ */ new Set(["1", "true", "yes", "on"]);
|
|
485
|
+
function isInitializeDebugEnabled(env = process.env) {
|
|
486
|
+
return TRUTHY.has(String(env.MEMORAONE_DEBUG_INIT ?? "").trim().toLowerCase());
|
|
487
|
+
}
|
|
488
|
+
function logInitializeDebug(log, env, msg) {
|
|
489
|
+
if (isInitializeDebugEnabled(env)) {
|
|
490
|
+
log(`[init-debug] ${msg}`);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
function clientDeclaresRootsCapability(params) {
|
|
494
|
+
const capabilities = params?.capabilities;
|
|
495
|
+
if (capabilities === null || typeof capabilities !== "object") {
|
|
496
|
+
return false;
|
|
497
|
+
}
|
|
498
|
+
return Object.prototype.hasOwnProperty.call(capabilities, "roots");
|
|
499
|
+
}
|
|
500
|
+
function summarizeInitializeParamsForDebug(params) {
|
|
501
|
+
if (!params) {
|
|
502
|
+
return "(no params)";
|
|
503
|
+
}
|
|
504
|
+
const folders = Array.isArray(params.workspaceFolders) ? params.workspaceFolders.map((folder) => {
|
|
505
|
+
if (folder && typeof folder === "object") {
|
|
506
|
+
const entry = folder;
|
|
507
|
+
return { name: entry.name, uri: entry.uri };
|
|
508
|
+
}
|
|
509
|
+
return folder;
|
|
510
|
+
}) : params.workspaceFolders;
|
|
511
|
+
const capabilityKeys = params.capabilities && typeof params.capabilities === "object" ? Object.keys(params.capabilities) : [];
|
|
512
|
+
return JSON.stringify({
|
|
513
|
+
rootUri: params.rootUri,
|
|
514
|
+
workspaceFolders: folders,
|
|
515
|
+
clientInfo: params.clientInfo,
|
|
516
|
+
capabilityKeys
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
function extractRootsUrisFromListResult(result) {
|
|
520
|
+
if (result === null || typeof result !== "object") {
|
|
521
|
+
return [];
|
|
522
|
+
}
|
|
523
|
+
const roots = result.roots;
|
|
524
|
+
if (!Array.isArray(roots)) {
|
|
525
|
+
return [];
|
|
526
|
+
}
|
|
527
|
+
const uris = [];
|
|
528
|
+
for (const root of roots) {
|
|
529
|
+
if (root && typeof root === "object") {
|
|
530
|
+
const uri = root.uri;
|
|
531
|
+
if (typeof uri === "string" && uri.trim() !== "") {
|
|
532
|
+
uris.push(uri);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
return uris;
|
|
537
|
+
}
|
|
538
|
+
var StdioLineReader = class {
|
|
539
|
+
constructor(input2) {
|
|
540
|
+
this.queue = [];
|
|
541
|
+
this.waiters = [];
|
|
542
|
+
this.closed = false;
|
|
543
|
+
this.rl = readline.createInterface({ input: input2, crlfDelay: Infinity });
|
|
544
|
+
this.rl.on("line", (line) => {
|
|
545
|
+
if (this.waiters.length > 0) {
|
|
546
|
+
this.waiters.shift()(line);
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
this.queue.push(line);
|
|
550
|
+
});
|
|
551
|
+
this.rl.on("close", () => {
|
|
552
|
+
this.closed = true;
|
|
553
|
+
while (this.waiters.length > 0) {
|
|
554
|
+
this.waiters.shift()(null);
|
|
555
|
+
}
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
async readLine() {
|
|
559
|
+
if (this.queue.length > 0) {
|
|
560
|
+
return this.queue.shift() ?? null;
|
|
561
|
+
}
|
|
562
|
+
if (this.closed) {
|
|
563
|
+
return null;
|
|
564
|
+
}
|
|
565
|
+
return new Promise((resolve8) => {
|
|
566
|
+
this.waiters.push(resolve8);
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
/** Re-queue lines read during an intermediate protocol step (e.g. roots/list) for the main bridge loop. */
|
|
570
|
+
prependLines(lines) {
|
|
571
|
+
if (lines.length === 0) {
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
575
|
+
this.queue.unshift(lines[i]);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
close() {
|
|
579
|
+
this.rl.close();
|
|
580
|
+
}
|
|
581
|
+
};
|
|
582
|
+
var nextBridgeRequestId = 1e5;
|
|
583
|
+
async function requestClientRootsListUris(options) {
|
|
584
|
+
const env = options.env ?? process.env;
|
|
585
|
+
const log = options.log ?? (() => {
|
|
586
|
+
});
|
|
587
|
+
const requestId = nextBridgeRequestId++;
|
|
588
|
+
const deferredLines = [];
|
|
589
|
+
const request = {
|
|
590
|
+
jsonrpc: "2.0",
|
|
591
|
+
id: requestId,
|
|
592
|
+
method: "roots/list",
|
|
593
|
+
params: {}
|
|
594
|
+
};
|
|
595
|
+
logInitializeDebug(
|
|
596
|
+
log,
|
|
597
|
+
env,
|
|
598
|
+
`sending roots/list id=${requestId} clientDeclaresRoots=${clientDeclaresRootsCapability(options.initializeParams)}`
|
|
599
|
+
);
|
|
600
|
+
options.stdout.write(`${JSON.stringify(request)}
|
|
601
|
+
`);
|
|
602
|
+
while (true) {
|
|
603
|
+
const line = await options.lineReader.readLine();
|
|
604
|
+
if (line === null) {
|
|
605
|
+
throw new Error("[memoraone-mcp] Client closed stdin before roots/list response");
|
|
606
|
+
}
|
|
607
|
+
const trimmed = line.trim();
|
|
608
|
+
if (trimmed === "") {
|
|
609
|
+
continue;
|
|
610
|
+
}
|
|
611
|
+
let message;
|
|
612
|
+
try {
|
|
613
|
+
message = JSON.parse(trimmed);
|
|
614
|
+
} catch (err) {
|
|
615
|
+
logInitializeDebug(log, env, `ignored non-JSON line while waiting for roots/list: ${String(err)}`);
|
|
616
|
+
continue;
|
|
617
|
+
}
|
|
618
|
+
if (message.id !== requestId) {
|
|
619
|
+
logInitializeDebug(
|
|
620
|
+
log,
|
|
621
|
+
env,
|
|
622
|
+
`deferred JSON-RPC while waiting for roots/list id=${requestId}: ${trimmed.slice(0, 200)}`
|
|
623
|
+
);
|
|
624
|
+
deferredLines.push(trimmed);
|
|
625
|
+
continue;
|
|
626
|
+
}
|
|
627
|
+
if (message.error) {
|
|
628
|
+
throw new Error(
|
|
629
|
+
`[memoraone-mcp] roots/list failed: ${JSON.stringify(message.error)}`
|
|
630
|
+
);
|
|
631
|
+
}
|
|
632
|
+
const uris = extractRootsUrisFromListResult(message.result);
|
|
633
|
+
logInitializeDebug(log, env, `roots/list id=${requestId} returned ${uris.length}: ${JSON.stringify(uris)}`);
|
|
634
|
+
return { uris, deferredLines };
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
// src/initializeBinding.ts
|
|
639
|
+
var path5 = __toESM(require("path"), 1);
|
|
640
|
+
var import_node_url = require("url");
|
|
641
|
+
var MEMORAONE_WORKSPACE_ROOT_ENV = "MEMORAONE_WORKSPACE_ROOT";
|
|
642
|
+
function getBridgeBindingResolveOptions(env = process.env) {
|
|
643
|
+
const ideType = resolveIdeTypeFromEnv(env);
|
|
644
|
+
if (ideType === "cursor") {
|
|
645
|
+
return { respectExplicitM1Path: false, allowEnvWorkspaceFallback: false };
|
|
646
|
+
}
|
|
647
|
+
return { respectExplicitM1Path: true, allowEnvWorkspaceFallback: true };
|
|
648
|
+
}
|
|
649
|
+
function uriToPath(uri) {
|
|
650
|
+
if (uri.startsWith("file://")) {
|
|
651
|
+
return (0, import_node_url.fileURLToPath)(uri);
|
|
652
|
+
}
|
|
653
|
+
return uri;
|
|
654
|
+
}
|
|
655
|
+
function getEnvWorkspaceRootCandidates() {
|
|
656
|
+
const raw = process.env.WORKSPACE_FOLDER_PATHS;
|
|
657
|
+
const parts = [];
|
|
658
|
+
if (raw !== void 0 && raw.trim() !== "") {
|
|
659
|
+
for (const p of raw.split(path5.delimiter).map((s) => s.trim()).filter(Boolean)) {
|
|
660
|
+
parts.push(path5.resolve(p));
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
parts.push(process.cwd());
|
|
664
|
+
const seen = /* @__PURE__ */ new Set();
|
|
665
|
+
const deduped = [];
|
|
666
|
+
for (const p of parts) {
|
|
667
|
+
if (!seen.has(p)) {
|
|
668
|
+
seen.add(p);
|
|
669
|
+
deduped.push(p);
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
return deduped;
|
|
673
|
+
}
|
|
674
|
+
function extractWorkspaceRootsFromInitialize(params) {
|
|
675
|
+
if (!params) {
|
|
676
|
+
return [];
|
|
677
|
+
}
|
|
678
|
+
const seen = /* @__PURE__ */ new Set();
|
|
679
|
+
const roots = [];
|
|
680
|
+
const addRoot = (uri) => {
|
|
681
|
+
if (uri === void 0 || uri.trim() === "") {
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
684
|
+
const resolved = path5.resolve(uriToPath(uri));
|
|
685
|
+
if (!seen.has(resolved)) {
|
|
686
|
+
seen.add(resolved);
|
|
687
|
+
roots.push(resolved);
|
|
688
|
+
}
|
|
689
|
+
};
|
|
690
|
+
if (Array.isArray(params.workspaceFolders) && params.workspaceFolders.length > 0) {
|
|
691
|
+
for (const folder of params.workspaceFolders) {
|
|
692
|
+
addRoot(folder?.uri);
|
|
693
|
+
}
|
|
694
|
+
return roots;
|
|
695
|
+
}
|
|
696
|
+
if (params.rootUri) {
|
|
697
|
+
addRoot(params.rootUri);
|
|
698
|
+
}
|
|
699
|
+
return roots;
|
|
700
|
+
}
|
|
701
|
+
function getRepoScopedWorkspaceHint(env = process.env) {
|
|
702
|
+
const raw = env[MEMORAONE_WORKSPACE_ROOT_ENV];
|
|
703
|
+
if (raw === void 0 || raw.trim() === "") {
|
|
704
|
+
return null;
|
|
705
|
+
}
|
|
706
|
+
return path5.resolve(raw.trim());
|
|
707
|
+
}
|
|
708
|
+
function formatWorkspaceAmbiguityError(bindings) {
|
|
709
|
+
const lines = bindings.map(
|
|
710
|
+
(b) => ` - workspace=${b.workspaceRoot} project=${b.projectId} m1=${b.m1Path}`
|
|
711
|
+
);
|
|
712
|
+
return "[memoraone-mcp] Ambiguous workspace binding: multiple open roots map to different MemoraOne projects.\n" + lines.join("\n") + `
|
|
713
|
+
Open one repo per Cursor window, or ensure this repo's managed .cursor/mcp.json includes ${MEMORAONE_WORKSPACE_ROOT_ENV} from setup-ide-files --cursor.`;
|
|
714
|
+
}
|
|
715
|
+
function formatRepoHintInitializeMismatchError(repoHintRoot, initializeBinding) {
|
|
716
|
+
return `[memoraone-mcp] Repo-scoped workspace hint conflicts with MCP initialize workspace.
|
|
717
|
+
${MEMORAONE_WORKSPACE_ROOT_ENV}: ${repoHintRoot}
|
|
718
|
+
initialize: project=${initializeBinding.projectId} workspace=${initializeBinding.workspaceRoot} m1=${initializeBinding.m1Path}
|
|
719
|
+
Reload MCP in the Cursor window for this repo, or re-run setup-ide-files --cursor here if the hint is stale.`;
|
|
720
|
+
}
|
|
721
|
+
function formatRepoHintNotInRootsListError(repoHintRoot, rootsListPaths) {
|
|
722
|
+
return `[memoraone-mcp] Repo-scoped workspace hint does not match any Cursor roots/list entry.
|
|
723
|
+
${MEMORAONE_WORKSPACE_ROOT_ENV}: ${repoHintRoot}
|
|
724
|
+
roots/list paths: ${rootsListPaths.join(", ")}
|
|
725
|
+
Reload MCP in the Cursor window for this repo, or re-run setup-ide-files --cursor if the hint is stale.`;
|
|
726
|
+
}
|
|
727
|
+
async function resolveBindingFromWorkspaceRoots(workspaceRoots, options = {}) {
|
|
728
|
+
if (workspaceRoots.length === 0) {
|
|
729
|
+
throw new Error("Could not find memoraone.m1 in workspace.\nOpen a folder containing memoraone.m1.");
|
|
730
|
+
}
|
|
731
|
+
const bindings = [];
|
|
732
|
+
for (const root of workspaceRoots) {
|
|
733
|
+
try {
|
|
734
|
+
bindings.push(
|
|
735
|
+
await resolveAuthoritativeBinding(root, {
|
|
736
|
+
respectExplicitM1Path: options.respectExplicitM1Path
|
|
737
|
+
})
|
|
738
|
+
);
|
|
739
|
+
} catch (err) {
|
|
740
|
+
if (err instanceof Error && err.message.includes("Could not find memoraone.m1")) {
|
|
741
|
+
continue;
|
|
742
|
+
}
|
|
743
|
+
throw err;
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
if (bindings.length === 0) {
|
|
747
|
+
throw new Error("Could not find memoraone.m1 in workspace.\nOpen a folder containing memoraone.m1.");
|
|
748
|
+
}
|
|
749
|
+
const distinctProjectIds = new Set(bindings.map((b) => b.projectId));
|
|
750
|
+
if (distinctProjectIds.size > 1) {
|
|
751
|
+
throw new Error(formatWorkspaceAmbiguityError(bindings));
|
|
752
|
+
}
|
|
753
|
+
return bindings[0];
|
|
754
|
+
}
|
|
755
|
+
async function resolveBindingFromInitializeParams(params, options = {}) {
|
|
756
|
+
const env = options.env ?? process.env;
|
|
757
|
+
const resolveOpts = {
|
|
758
|
+
respectExplicitM1Path: options.respectExplicitM1Path,
|
|
759
|
+
allowEnvWorkspaceFallback: options.allowEnvWorkspaceFallback
|
|
760
|
+
};
|
|
761
|
+
const repoHint = getRepoScopedWorkspaceHint(env);
|
|
762
|
+
const initializeRoots = extractWorkspaceRootsFromInitialize(params);
|
|
763
|
+
if (initializeRoots.length > 0) {
|
|
764
|
+
const binding = await resolveBindingFromWorkspaceRoots(initializeRoots, resolveOpts);
|
|
765
|
+
if (repoHint !== null) {
|
|
766
|
+
const hintBinding = await resolveAuthoritativeBinding(repoHint, {
|
|
767
|
+
respectExplicitM1Path: false
|
|
768
|
+
});
|
|
769
|
+
if (!bindingsMatch(binding, hintBinding)) {
|
|
770
|
+
throw new Error(formatRepoHintInitializeMismatchError(repoHint, binding));
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
return binding;
|
|
774
|
+
}
|
|
775
|
+
const rootsListUris = options.rootsListUris ?? [];
|
|
776
|
+
const rootsListPaths = rootsListUris.map((uri) => uriToPath(uri)).filter(Boolean);
|
|
777
|
+
if (rootsListPaths.length > 1 && repoHint !== null) {
|
|
778
|
+
const hintResolved = path5.resolve(repoHint);
|
|
779
|
+
const matchingRoot = rootsListPaths.find((root) => path5.resolve(root) === hintResolved);
|
|
780
|
+
if (!matchingRoot) {
|
|
781
|
+
throw new Error(formatRepoHintNotInRootsListError(repoHint, rootsListPaths));
|
|
782
|
+
}
|
|
783
|
+
return resolveAuthoritativeBinding(repoHint, { respectExplicitM1Path: false });
|
|
784
|
+
}
|
|
785
|
+
if (rootsListPaths.length > 0) {
|
|
786
|
+
return resolveBindingFromWorkspaceRoots(rootsListPaths, resolveOpts);
|
|
787
|
+
}
|
|
788
|
+
if (repoHint !== null) {
|
|
789
|
+
return resolveAuthoritativeBinding(repoHint, { respectExplicitM1Path: false });
|
|
790
|
+
}
|
|
791
|
+
if (options.allowEnvWorkspaceFallback === false) {
|
|
792
|
+
throw new Error(
|
|
793
|
+
formatMissingInitializeWorkspaceError({
|
|
794
|
+
rootsListAttempted: options.rootsListAttempted === true,
|
|
795
|
+
rootsListUris
|
|
796
|
+
})
|
|
797
|
+
);
|
|
798
|
+
}
|
|
799
|
+
const fallbackRoots = options.fallbackWorkspaceRoots ?? getEnvWorkspaceRootCandidates();
|
|
800
|
+
return resolveAuthoritativeBinding(fallbackRoots, {
|
|
801
|
+
respectExplicitM1Path: options.respectExplicitM1Path
|
|
802
|
+
});
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
// src/bridgeProxy.ts
|
|
806
|
+
var defaultLog = (msg) => {
|
|
807
|
+
process.stderr.write(`[memoraone-mcp][bridge] ${msg}
|
|
808
|
+
`);
|
|
809
|
+
};
|
|
810
|
+
function summarizeJsonRpcMethod(line) {
|
|
811
|
+
try {
|
|
812
|
+
const message = JSON.parse(line.trim());
|
|
813
|
+
if (typeof message.method === "string") {
|
|
814
|
+
return message.method;
|
|
815
|
+
}
|
|
816
|
+
if (message.id !== void 0) {
|
|
817
|
+
return `response:id=${String(message.id)}`;
|
|
818
|
+
}
|
|
819
|
+
return "jsonrpc";
|
|
820
|
+
} catch {
|
|
821
|
+
return "invalid-json";
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
function connectWithRetry(socketPath, log, maxRetries, retryDelayMs, connect2) {
|
|
825
|
+
return new Promise((resolve8, reject) => {
|
|
826
|
+
const tryConnect = (attempt) => {
|
|
827
|
+
connect2(socketPath).then(resolve8).catch((err) => {
|
|
828
|
+
if (attempt >= maxRetries) {
|
|
829
|
+
reject(err);
|
|
830
|
+
return;
|
|
831
|
+
}
|
|
832
|
+
log(`connect attempt ${attempt + 1} failed, retrying in ${retryDelayMs}ms: ${String(err)}`);
|
|
833
|
+
setTimeout(() => tryConnect(attempt + 1), retryDelayMs);
|
|
834
|
+
});
|
|
835
|
+
};
|
|
836
|
+
tryConnect(0);
|
|
837
|
+
});
|
|
838
|
+
}
|
|
839
|
+
async function resolveBridgeSessionBinding(params, env = process.env, options = {}) {
|
|
840
|
+
const bridgeOptions = getBridgeBindingResolveOptions(env);
|
|
841
|
+
return resolveBindingFromInitializeParams(params, {
|
|
842
|
+
env,
|
|
843
|
+
fallbackWorkspaceRoots: getEnvWorkspaceRootCandidates(),
|
|
844
|
+
rootsListUris: options.rootsListUris,
|
|
845
|
+
rootsListAttempted: options.rootsListAttempted,
|
|
846
|
+
...bridgeOptions
|
|
847
|
+
});
|
|
848
|
+
}
|
|
849
|
+
async function connectOrSpawnDaemonForBinding(binding, opts) {
|
|
850
|
+
const socketPath = getBindingSocketPath(binding, opts.env);
|
|
851
|
+
opts.log(
|
|
852
|
+
`target daemon socket=${socketPath} project=${binding.projectId} workspace=${binding.workspaceRoot}`
|
|
853
|
+
);
|
|
854
|
+
let socket;
|
|
855
|
+
try {
|
|
856
|
+
socket = await connectWithRetry(
|
|
857
|
+
socketPath,
|
|
858
|
+
opts.log,
|
|
859
|
+
opts.maxRetries,
|
|
860
|
+
opts.retryDelayMs,
|
|
861
|
+
opts.connect
|
|
862
|
+
);
|
|
863
|
+
verifyDaemonSidecarBinding(socketPath, binding, opts.env);
|
|
864
|
+
opts.log("reusing running daemon for session binding");
|
|
865
|
+
return socket;
|
|
866
|
+
} catch (err) {
|
|
867
|
+
if (err instanceof Error && err.message.includes("Daemon socket binding mismatch")) {
|
|
868
|
+
throw err;
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
opts.log("daemon not running, spawning...");
|
|
872
|
+
await opts.spawnDaemon(binding, socketPath);
|
|
873
|
+
await new Promise((r) => setTimeout(r, opts.retryDelayMs));
|
|
874
|
+
socket = await connectWithRetry(
|
|
875
|
+
socketPath,
|
|
876
|
+
opts.log,
|
|
877
|
+
opts.maxRetries,
|
|
878
|
+
opts.retryDelayMs,
|
|
879
|
+
opts.connect
|
|
880
|
+
);
|
|
881
|
+
verifyDaemonSidecarBinding(socketPath, binding, opts.env);
|
|
882
|
+
return socket;
|
|
883
|
+
}
|
|
884
|
+
var BridgeDaemonRouter = class {
|
|
885
|
+
constructor(options) {
|
|
886
|
+
this.activeSocket = null;
|
|
887
|
+
this.activeBinding = null;
|
|
888
|
+
this.socketLineReader = null;
|
|
889
|
+
this.lastInitializeLine = null;
|
|
890
|
+
this.pendingDeferredClientLines = [];
|
|
891
|
+
this.handshakeDeferredClientLines = [];
|
|
892
|
+
this.clientInitializeSeen = false;
|
|
893
|
+
this.env = options.env ?? process.env;
|
|
894
|
+
this.stdout = options.stdout ?? process.stdout;
|
|
895
|
+
this.log = options.log ?? defaultLog;
|
|
896
|
+
this.cliPath = options.cliPath;
|
|
897
|
+
this.maxRetries = options.maxRetries ?? 5;
|
|
898
|
+
this.retryDelayMs = options.retryDelayMs ?? 200;
|
|
899
|
+
this.lineReader = options.lineReader ?? null;
|
|
900
|
+
this.connectImpl = options.connect ?? ((socketPath) => new Promise((resolve8, reject) => {
|
|
901
|
+
const socket = net.connect(socketPath, () => resolve8(socket));
|
|
902
|
+
socket.on("error", reject);
|
|
903
|
+
}));
|
|
904
|
+
this.spawnDaemonImpl = options.spawnDaemon ?? (async (binding) => {
|
|
905
|
+
const child = (0, import_node_child_process.spawn)(
|
|
906
|
+
process.execPath,
|
|
907
|
+
buildDaemonSpawnArgs(this.cliPath, binding.projectId, this.env),
|
|
908
|
+
{
|
|
909
|
+
detached: true,
|
|
910
|
+
stdio: "ignore",
|
|
911
|
+
env: {
|
|
912
|
+
...this.env,
|
|
913
|
+
MEMORAONE_DAEMON_BINDING_B64: encodeResolvedBinding(binding)
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
);
|
|
917
|
+
child.on("exit", (code, signal) => {
|
|
918
|
+
logInitializeDebug(
|
|
919
|
+
this.log,
|
|
920
|
+
this.env,
|
|
921
|
+
`spawned daemon exit code=${code ?? "null"} signal=${signal ?? "null"} project=${binding.projectId}`
|
|
922
|
+
);
|
|
923
|
+
});
|
|
924
|
+
child.unref();
|
|
925
|
+
});
|
|
926
|
+
}
|
|
927
|
+
getActiveBinding() {
|
|
928
|
+
return this.activeBinding;
|
|
929
|
+
}
|
|
930
|
+
hasClientInitialize() {
|
|
931
|
+
return this.clientInitializeSeen;
|
|
932
|
+
}
|
|
933
|
+
async ensureDaemonForInitialize(params) {
|
|
934
|
+
logInitializeDebug(
|
|
935
|
+
this.log,
|
|
936
|
+
this.env,
|
|
937
|
+
`initialize payload: ${summarizeInitializeParamsForDebug(params)}`
|
|
938
|
+
);
|
|
939
|
+
const bridgeOptions = getBridgeBindingResolveOptions(this.env);
|
|
940
|
+
const initializeRoots = extractWorkspaceRootsFromInitialize(params);
|
|
941
|
+
let rootsListUris;
|
|
942
|
+
let rootsListAttempted = false;
|
|
943
|
+
const repoHint = getRepoScopedWorkspaceHint(this.env);
|
|
944
|
+
if (initializeRoots.length === 0 && bridgeOptions.allowEnvWorkspaceFallback === false && repoHint === null) {
|
|
945
|
+
if (!this.lineReader) {
|
|
946
|
+
throw new Error(
|
|
947
|
+
"[memoraone-mcp] Internal error: Cursor workspace binding requires stdin line reader for roots/list"
|
|
948
|
+
);
|
|
949
|
+
}
|
|
950
|
+
rootsListAttempted = true;
|
|
951
|
+
this.log("initialize lacks workspace roots; requesting roots/list from Cursor before binding");
|
|
952
|
+
const rootsListResult = await requestClientRootsListUris({
|
|
953
|
+
lineReader: this.lineReader,
|
|
954
|
+
stdout: this.stdout,
|
|
955
|
+
log: this.log,
|
|
956
|
+
env: this.env,
|
|
957
|
+
initializeParams: params
|
|
958
|
+
});
|
|
959
|
+
rootsListUris = rootsListResult.uris;
|
|
960
|
+
if (rootsListResult.deferredLines.length > 0) {
|
|
961
|
+
this.pendingDeferredClientLines = rootsListResult.deferredLines.slice();
|
|
962
|
+
logInitializeDebug(
|
|
963
|
+
this.log,
|
|
964
|
+
this.env,
|
|
965
|
+
`queued ${this.pendingDeferredClientLines.length} deferred client line(s) for replay after initialize`
|
|
966
|
+
);
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
const binding = await resolveBridgeSessionBinding(params, this.env, {
|
|
970
|
+
rootsListUris,
|
|
971
|
+
rootsListAttempted
|
|
972
|
+
});
|
|
973
|
+
const environmentLog = binding.environment !== void 0 ? ` environment=${binding.environment}` : "";
|
|
974
|
+
this.log(
|
|
975
|
+
`session binding project=${binding.projectId} workspace=${binding.workspaceRoot} m1=${binding.m1Path} source=${binding.bindingSource} apiKeySource=${binding.apiKeySource}${environmentLog}`
|
|
976
|
+
);
|
|
977
|
+
if (this.activeBinding && bindingsMatch(this.activeBinding, binding) && this.activeSocket) {
|
|
978
|
+
return;
|
|
979
|
+
}
|
|
980
|
+
if (this.activeBinding && !bindingsMatch(this.activeBinding, binding)) {
|
|
981
|
+
this.log(
|
|
982
|
+
`session binding changed from workspace=${this.activeBinding.workspaceRoot} to workspace=${binding.workspaceRoot}; reconnecting daemon`
|
|
983
|
+
);
|
|
984
|
+
this.resetSessionState();
|
|
985
|
+
this.detachSocketReader();
|
|
986
|
+
this.activeSocket?.destroy();
|
|
987
|
+
this.activeSocket = null;
|
|
988
|
+
}
|
|
989
|
+
this.activeBinding = binding;
|
|
990
|
+
await this.connectActiveDaemon();
|
|
991
|
+
this.log("bridge connected");
|
|
992
|
+
}
|
|
993
|
+
recordClientInitialize(line) {
|
|
994
|
+
this.lastInitializeLine = line;
|
|
995
|
+
this.clientInitializeSeen = true;
|
|
996
|
+
}
|
|
997
|
+
async forwardInitializeToDaemon(line) {
|
|
998
|
+
this.recordClientInitialize(line);
|
|
999
|
+
await this.writeToDaemon(line);
|
|
1000
|
+
logInitializeDebug(this.log, this.env, "initialize replay to daemon");
|
|
1001
|
+
this.log("forwarding active");
|
|
1002
|
+
}
|
|
1003
|
+
async replayDeferredClientMessages() {
|
|
1004
|
+
if (this.pendingDeferredClientLines.length === 0) {
|
|
1005
|
+
return;
|
|
1006
|
+
}
|
|
1007
|
+
const lines = this.pendingDeferredClientLines.slice();
|
|
1008
|
+
this.pendingDeferredClientLines = [];
|
|
1009
|
+
this.handshakeDeferredClientLines = lines.slice();
|
|
1010
|
+
const types = lines.map((line) => summarizeJsonRpcMethod(line));
|
|
1011
|
+
logInitializeDebug(
|
|
1012
|
+
this.log,
|
|
1013
|
+
this.env,
|
|
1014
|
+
`deferred message replay count=${lines.length} types=${JSON.stringify(types)}`
|
|
1015
|
+
);
|
|
1016
|
+
for (const line of lines) {
|
|
1017
|
+
await this.writeToDaemon(line);
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
async writeToDaemon(line) {
|
|
1021
|
+
await this.ensureActiveDaemonSocket();
|
|
1022
|
+
this.activeSocket.write(`${line}
|
|
1023
|
+
`);
|
|
1024
|
+
}
|
|
1025
|
+
resetSessionState() {
|
|
1026
|
+
this.lastInitializeLine = null;
|
|
1027
|
+
this.pendingDeferredClientLines = [];
|
|
1028
|
+
this.handshakeDeferredClientLines = [];
|
|
1029
|
+
this.clientInitializeSeen = false;
|
|
1030
|
+
this.activeBinding = null;
|
|
1031
|
+
}
|
|
1032
|
+
async connectActiveDaemon() {
|
|
1033
|
+
if (!this.activeBinding) {
|
|
1034
|
+
throw new Error("[memoraone-mcp] Internal error: connectActiveDaemon without active binding");
|
|
1035
|
+
}
|
|
1036
|
+
const socket = await connectOrSpawnDaemonForBinding(this.activeBinding, {
|
|
1037
|
+
env: this.env,
|
|
1038
|
+
cliPath: this.cliPath,
|
|
1039
|
+
log: this.log,
|
|
1040
|
+
maxRetries: this.maxRetries,
|
|
1041
|
+
retryDelayMs: this.retryDelayMs,
|
|
1042
|
+
connect: this.connectImpl,
|
|
1043
|
+
spawnDaemon: this.spawnDaemonImpl
|
|
1044
|
+
});
|
|
1045
|
+
this.activeSocket = socket;
|
|
1046
|
+
this.attachSocketReader(socket);
|
|
1047
|
+
}
|
|
1048
|
+
async ensureActiveDaemonSocket() {
|
|
1049
|
+
if (this.activeSocket && !this.activeSocket.destroyed) {
|
|
1050
|
+
return;
|
|
1051
|
+
}
|
|
1052
|
+
if (!this.clientInitializeSeen || !this.lastInitializeLine || !this.activeBinding) {
|
|
1053
|
+
throw new Error("[memoraone-mcp] MCP request before initialize");
|
|
1054
|
+
}
|
|
1055
|
+
this.log("daemon socket unavailable; reconnecting for session binding");
|
|
1056
|
+
await this.connectActiveDaemon();
|
|
1057
|
+
logInitializeDebug(this.log, this.env, "initialize replay to daemon after reconnect");
|
|
1058
|
+
this.activeSocket.write(`${this.lastInitializeLine}
|
|
1059
|
+
`);
|
|
1060
|
+
if (this.handshakeDeferredClientLines.length > 0) {
|
|
1061
|
+
const types = this.handshakeDeferredClientLines.map(
|
|
1062
|
+
(deferredLine) => summarizeJsonRpcMethod(deferredLine)
|
|
1063
|
+
);
|
|
1064
|
+
logInitializeDebug(
|
|
1065
|
+
this.log,
|
|
1066
|
+
this.env,
|
|
1067
|
+
`deferred message replay after reconnect count=${this.handshakeDeferredClientLines.length} types=${JSON.stringify(types)}`
|
|
1068
|
+
);
|
|
1069
|
+
for (const deferredLine of this.handshakeDeferredClientLines) {
|
|
1070
|
+
this.activeSocket.write(`${deferredLine}
|
|
1071
|
+
`);
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
attachSocketReader(socket) {
|
|
1076
|
+
this.detachSocketReader();
|
|
1077
|
+
this.socketLineReader = readline2.createInterface({ input: socket, crlfDelay: Infinity });
|
|
1078
|
+
this.socketLineReader.on("line", (line) => {
|
|
1079
|
+
this.stdout.write(`${line}
|
|
1080
|
+
`);
|
|
1081
|
+
});
|
|
1082
|
+
socket.on("close", (hadError) => {
|
|
1083
|
+
if (this.activeSocket === socket) {
|
|
1084
|
+
logInitializeDebug(
|
|
1085
|
+
this.log,
|
|
1086
|
+
this.env,
|
|
1087
|
+
`daemon socket closed hadError=${String(hadError)}`
|
|
1088
|
+
);
|
|
1089
|
+
this.log("daemon socket closed; bridge stays alive for reconnect");
|
|
1090
|
+
this.detachSocketReader();
|
|
1091
|
+
this.activeSocket = null;
|
|
1092
|
+
}
|
|
1093
|
+
});
|
|
1094
|
+
socket.on("error", (err) => {
|
|
1095
|
+
this.log(`socket error: ${String(err)}`);
|
|
1096
|
+
logInitializeDebug(this.log, this.env, `daemon socket error: ${String(err)}`);
|
|
1097
|
+
if (this.activeSocket === socket) {
|
|
1098
|
+
this.detachSocketReader();
|
|
1099
|
+
this.activeSocket = null;
|
|
1100
|
+
}
|
|
1101
|
+
});
|
|
1102
|
+
}
|
|
1103
|
+
detachSocketReader() {
|
|
1104
|
+
if (this.socketLineReader) {
|
|
1105
|
+
this.socketLineReader.close();
|
|
1106
|
+
this.socketLineReader = null;
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
};
|
|
1110
|
+
async function runBridgeProxy(options) {
|
|
1111
|
+
ensureBaseDir();
|
|
1112
|
+
const stdin = options.stdin ?? process.stdin;
|
|
1113
|
+
const stdout = options.stdout ?? process.stdout;
|
|
1114
|
+
const log = options.log ?? defaultLog;
|
|
1115
|
+
const lineReader = options.lineReader ?? new StdioLineReader(stdin);
|
|
1116
|
+
const router = new BridgeDaemonRouter({ ...options, stdout, lineReader });
|
|
1117
|
+
while (true) {
|
|
1118
|
+
const line = await lineReader.readLine();
|
|
1119
|
+
if (line === null) {
|
|
1120
|
+
break;
|
|
1121
|
+
}
|
|
1122
|
+
const trimmed = line.trim();
|
|
1123
|
+
if (trimmed === "") {
|
|
1124
|
+
continue;
|
|
1125
|
+
}
|
|
1126
|
+
let message;
|
|
1127
|
+
try {
|
|
1128
|
+
message = JSON.parse(trimmed);
|
|
1129
|
+
} catch (err) {
|
|
1130
|
+
throw new Error(`[memoraone-mcp] Invalid JSON-RPC on stdin: ${String(err)}`);
|
|
1131
|
+
}
|
|
1132
|
+
if (message.method === "initialize") {
|
|
1133
|
+
log("resolve binding from initialize request before daemon connect");
|
|
1134
|
+
const params = message.params ?? {};
|
|
1135
|
+
await router.ensureDaemonForInitialize(params);
|
|
1136
|
+
await router.forwardInitializeToDaemon(trimmed);
|
|
1137
|
+
await router.replayDeferredClientMessages();
|
|
1138
|
+
continue;
|
|
1139
|
+
}
|
|
1140
|
+
await router.writeToDaemon(trimmed);
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
// src/setupIdeFiles.ts
|
|
1145
|
+
var fs7 = __toESM(require("fs/promises"), 1);
|
|
1146
|
+
var os4 = __toESM(require("os"), 1);
|
|
1147
|
+
var path9 = __toESM(require("path"), 1);
|
|
1148
|
+
|
|
1149
|
+
// src/cleanup.ts
|
|
1150
|
+
var fs5 = __toESM(require("fs/promises"), 1);
|
|
1151
|
+
var path7 = __toESM(require("path"), 1);
|
|
1152
|
+
var readline3 = __toESM(require("readline/promises"), 1);
|
|
1153
|
+
var import_node_child_process3 = require("child_process");
|
|
1154
|
+
var import_node_util2 = require("util");
|
|
1155
|
+
var import_node_process = require("process");
|
|
1156
|
+
|
|
1157
|
+
// src/cursorGlobalMcpConfig.ts
|
|
1158
|
+
var fs4 = __toESM(require("fs/promises"), 1);
|
|
1159
|
+
var os2 = __toESM(require("os"), 1);
|
|
1160
|
+
var path6 = __toESM(require("path"), 1);
|
|
1161
|
+
var import_node_child_process2 = require("child_process");
|
|
1162
|
+
var import_node_util = require("util");
|
|
1163
|
+
var execFileAsync = (0, import_node_util.promisify)(import_node_child_process2.execFile);
|
|
1164
|
+
function buildMemoraoneCursorMcpServer(npxPath, workspaceRoot) {
|
|
1165
|
+
const env = {
|
|
1166
|
+
MEMORAONE_API_URL: "https://api.memoraone.com",
|
|
1167
|
+
MEMORAONE_IDE_TYPE: "cursor"
|
|
1168
|
+
};
|
|
1169
|
+
if (workspaceRoot !== void 0) {
|
|
1170
|
+
env[MEMORAONE_WORKSPACE_ROOT_ENV] = path6.resolve(workspaceRoot);
|
|
1171
|
+
}
|
|
1172
|
+
return {
|
|
1173
|
+
command: npxPath,
|
|
1174
|
+
args: ["-y", "@memoraone/mcp@latest"],
|
|
1175
|
+
env
|
|
1176
|
+
};
|
|
1177
|
+
}
|
|
1178
|
+
async function pathExists(filePath) {
|
|
1179
|
+
try {
|
|
1180
|
+
await fs4.access(filePath);
|
|
1181
|
+
return true;
|
|
1182
|
+
} catch {
|
|
1183
|
+
return false;
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
function stripLeadingLineComments(text) {
|
|
1187
|
+
return text.split("\n").filter((line) => !/^\s*\/\//.test(line)).join("\n");
|
|
1188
|
+
}
|
|
1189
|
+
function getKnownCursorGlobalMcpConfigCandidates(homeDir) {
|
|
1190
|
+
return [path6.join(homeDir, ".cursor", "mcp.json")];
|
|
1191
|
+
}
|
|
1192
|
+
async function detectCursorGlobalMcpConfig(options) {
|
|
1193
|
+
if (options?.explicitPath) {
|
|
1194
|
+
return { ok: true, path: options.explicitPath, detectedExisting: await pathExists(options.explicitPath) };
|
|
1195
|
+
}
|
|
1196
|
+
const homeDir = options?.homeDir ?? os2.homedir();
|
|
1197
|
+
const candidates = getKnownCursorGlobalMcpConfigCandidates(homeDir);
|
|
1198
|
+
const existing = [];
|
|
1199
|
+
for (const candidate of candidates) {
|
|
1200
|
+
if (await pathExists(candidate)) existing.push(candidate);
|
|
1201
|
+
}
|
|
1202
|
+
if (existing.length > 1) {
|
|
1203
|
+
return {
|
|
1204
|
+
ok: false,
|
|
1205
|
+
error: "[setup-ide-files] Multiple Cursor global MCP config paths found. Specify one explicitly.",
|
|
1206
|
+
candidates: existing
|
|
1207
|
+
};
|
|
1208
|
+
}
|
|
1209
|
+
if (existing.length === 1) {
|
|
1210
|
+
return { ok: true, path: existing[0], detectedExisting: true };
|
|
1211
|
+
}
|
|
1212
|
+
const defaultPath = candidates[0];
|
|
1213
|
+
if (!defaultPath) {
|
|
1214
|
+
return {
|
|
1215
|
+
ok: false,
|
|
1216
|
+
error: "[setup-ide-files] No known Cursor global MCP config path.",
|
|
1217
|
+
candidates: []
|
|
1218
|
+
};
|
|
1219
|
+
}
|
|
1220
|
+
return { ok: true, path: defaultPath, detectedExisting: false };
|
|
1221
|
+
}
|
|
1222
|
+
function formatBackupTimestamp(d = /* @__PURE__ */ new Date()) {
|
|
1223
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
1224
|
+
return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
|
|
1225
|
+
}
|
|
1226
|
+
async function isWorkingNpx(npxPath) {
|
|
1227
|
+
try {
|
|
1228
|
+
if (!await pathExists(npxPath)) return false;
|
|
1229
|
+
if (process.platform !== "win32") {
|
|
1230
|
+
try {
|
|
1231
|
+
await fs4.access(npxPath, fs4.constants.X_OK);
|
|
1232
|
+
} catch {
|
|
1233
|
+
return false;
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
await execFileAsync(npxPath, ["--version"], { timeout: 1e4 });
|
|
1237
|
+
return true;
|
|
1238
|
+
} catch {
|
|
1239
|
+
return false;
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
async function resolveNpxPath() {
|
|
1243
|
+
const npxName = process.platform === "win32" ? "npx.cmd" : "npx";
|
|
1244
|
+
const candidates = [];
|
|
1245
|
+
if (process.platform === "darwin") {
|
|
1246
|
+
candidates.push("/opt/homebrew/bin/npx", "/usr/local/bin/npx");
|
|
1247
|
+
} else if (process.platform === "linux") {
|
|
1248
|
+
candidates.push("/usr/local/bin/npx");
|
|
1249
|
+
}
|
|
1250
|
+
const pathSep = process.platform === "win32" ? ";" : ":";
|
|
1251
|
+
for (const dir of (process.env.PATH ?? "").split(pathSep)) {
|
|
1252
|
+
if (!dir) continue;
|
|
1253
|
+
candidates.push(path6.join(dir, npxName));
|
|
1254
|
+
}
|
|
1255
|
+
try {
|
|
1256
|
+
const lookupCmd = process.platform === "win32" ? "where" : "which";
|
|
1257
|
+
const { stdout } = await execFileAsync(lookupCmd, [npxName], { timeout: 5e3 });
|
|
1258
|
+
const first = stdout.trim().split(/\r?\n/).map((line) => line.trim()).find(Boolean);
|
|
1259
|
+
if (first) candidates.unshift(first);
|
|
1260
|
+
} catch {
|
|
1261
|
+
}
|
|
1262
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1263
|
+
for (const candidate of candidates) {
|
|
1264
|
+
const abs = path6.isAbsolute(candidate) ? candidate : path6.resolve(candidate);
|
|
1265
|
+
const key = process.platform === "win32" ? abs.toLowerCase() : abs;
|
|
1266
|
+
if (seen.has(key)) continue;
|
|
1267
|
+
seen.add(key);
|
|
1268
|
+
if (await isWorkingNpx(abs)) return abs;
|
|
1269
|
+
}
|
|
1270
|
+
return null;
|
|
1271
|
+
}
|
|
1272
|
+
function mergeCursorRepoMcpConfigObject(existing, npxPath, repoRoot) {
|
|
1273
|
+
const base = existing && typeof existing === "object" ? { ...existing } : { mcpServers: {} };
|
|
1274
|
+
const mcpServers = typeof base.mcpServers === "object" && base.mcpServers !== null && !Array.isArray(base.mcpServers) ? { ...base.mcpServers } : {};
|
|
1275
|
+
mcpServers.memoraone = buildMemoraoneCursorMcpServer(npxPath, repoRoot);
|
|
1276
|
+
return { ...base, mcpServers };
|
|
1277
|
+
}
|
|
1278
|
+
function isManagedMemoraoneCursorServer(server) {
|
|
1279
|
+
if (!server || typeof server !== "object") return false;
|
|
1280
|
+
const s = server;
|
|
1281
|
+
if (!Array.isArray(s.args) || s.args.length !== 2) return false;
|
|
1282
|
+
if (s.args[0] !== "-y" || s.args[1] !== "@memoraone/mcp@latest") return false;
|
|
1283
|
+
const env = s.env;
|
|
1284
|
+
if (!env || typeof env !== "object") return false;
|
|
1285
|
+
return memoraoneEnvMatchesBase(env);
|
|
1286
|
+
}
|
|
1287
|
+
function cursorConfigHasManagedMemoraone(parsed) {
|
|
1288
|
+
if (!parsed || typeof parsed !== "object") return false;
|
|
1289
|
+
const mcpServers = parsed.mcpServers;
|
|
1290
|
+
if (!mcpServers || typeof mcpServers !== "object" || Array.isArray(mcpServers)) return false;
|
|
1291
|
+
return isManagedMemoraoneCursorServer(mcpServers.memoraone);
|
|
1292
|
+
}
|
|
1293
|
+
function memoraoneEnvMatchesBase(env) {
|
|
1294
|
+
return env.MEMORAONE_API_URL === "https://api.memoraone.com" && env.MEMORAONE_IDE_TYPE === "cursor";
|
|
1295
|
+
}
|
|
1296
|
+
function getCursorRepoMcpConfigPath(repoRoot) {
|
|
1297
|
+
return path6.join(repoRoot, ".cursor", "mcp.json");
|
|
1298
|
+
}
|
|
1299
|
+
async function readCursorMcpConfigObject(configPath) {
|
|
1300
|
+
try {
|
|
1301
|
+
const raw = await fs4.readFile(configPath, "utf8");
|
|
1302
|
+
return JSON.parse(stripLeadingLineComments(raw));
|
|
1303
|
+
} catch (err) {
|
|
1304
|
+
const code = err && typeof err === "object" && "code" in err ? err.code : void 0;
|
|
1305
|
+
if (code === "ENOENT") return null;
|
|
1306
|
+
throw err;
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
async function removeMemoraoneFromCursorGlobalConfig(options) {
|
|
1310
|
+
const { configPath, dryRun } = options;
|
|
1311
|
+
const parsed = await readCursorMcpConfigObject(configPath);
|
|
1312
|
+
if (!parsed || !cursorConfigHasManagedMemoraone(parsed)) {
|
|
1313
|
+
return { changed: false };
|
|
1314
|
+
}
|
|
1315
|
+
if (dryRun) {
|
|
1316
|
+
return { changed: true, backupPath: `${configPath}.backup-<timestamp>` };
|
|
1317
|
+
}
|
|
1318
|
+
const backupPath = `${configPath}.backup-${formatBackupTimestamp()}`;
|
|
1319
|
+
await fs4.copyFile(configPath, backupPath);
|
|
1320
|
+
const mcpServers = typeof parsed.mcpServers === "object" && parsed.mcpServers !== null && !Array.isArray(parsed.mcpServers) ? { ...parsed.mcpServers } : {};
|
|
1321
|
+
delete mcpServers.memoraone;
|
|
1322
|
+
const hasOtherServers = Object.keys(mcpServers).length > 0;
|
|
1323
|
+
if (!hasOtherServers) {
|
|
1324
|
+
await fs4.unlink(configPath);
|
|
1325
|
+
return { changed: true, backupPath };
|
|
1326
|
+
}
|
|
1327
|
+
const next = { ...parsed, mcpServers };
|
|
1328
|
+
await fs4.mkdir(path6.dirname(configPath), { recursive: true });
|
|
1329
|
+
await fs4.writeFile(configPath, JSON.stringify(next, null, 2) + "\n", "utf8");
|
|
1330
|
+
return { changed: true, backupPath };
|
|
1331
|
+
}
|
|
1332
|
+
async function auditCursorMcpConfig(options) {
|
|
1333
|
+
const repoRoot = path6.resolve(options?.repoRoot ?? process.cwd());
|
|
1334
|
+
const repoConfigPath = getCursorRepoMcpConfigPath(repoRoot);
|
|
1335
|
+
const globalDetection = await detectCursorGlobalMcpConfig({
|
|
1336
|
+
homeDir: options?.homeDir,
|
|
1337
|
+
explicitPath: options?.explicitGlobalPath
|
|
1338
|
+
});
|
|
1339
|
+
const globalConfigPath = globalDetection.ok ? globalDetection.path : getKnownCursorGlobalMcpConfigCandidates(
|
|
1340
|
+
options?.homeDir ?? os2.homedir()
|
|
1341
|
+
)[0];
|
|
1342
|
+
let repoHasManagedMemoraone = false;
|
|
1343
|
+
try {
|
|
1344
|
+
const repoParsed = await readCursorMcpConfigObject(repoConfigPath);
|
|
1345
|
+
repoHasManagedMemoraone = cursorConfigHasManagedMemoraone(repoParsed);
|
|
1346
|
+
} catch {
|
|
1347
|
+
repoHasManagedMemoraone = false;
|
|
1348
|
+
}
|
|
1349
|
+
let globalHasManagedMemoraone = false;
|
|
1350
|
+
if (globalDetection.ok) {
|
|
1351
|
+
try {
|
|
1352
|
+
const globalParsed = await readCursorMcpConfigObject(globalConfigPath);
|
|
1353
|
+
globalHasManagedMemoraone = cursorConfigHasManagedMemoraone(globalParsed);
|
|
1354
|
+
} catch {
|
|
1355
|
+
globalHasManagedMemoraone = false;
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
return {
|
|
1359
|
+
repoConfigPath,
|
|
1360
|
+
repoHasManagedMemoraone,
|
|
1361
|
+
globalConfigPath,
|
|
1362
|
+
globalHasManagedMemoraone,
|
|
1363
|
+
conflict: repoHasManagedMemoraone && globalHasManagedMemoraone
|
|
1364
|
+
};
|
|
1365
|
+
}
|
|
1366
|
+
function logCursorMcpConfigAudit(prefix, audit) {
|
|
1367
|
+
console.log(`${prefix} Cursor MCP config audit:`);
|
|
1368
|
+
console.log(
|
|
1369
|
+
`${prefix} repo ${audit.repoConfigPath}: managed memoraone=${audit.repoHasManagedMemoraone}`
|
|
1370
|
+
);
|
|
1371
|
+
console.log(
|
|
1372
|
+
`${prefix} global ${audit.globalConfigPath}: managed memoraone=${audit.globalHasManagedMemoraone}`
|
|
1373
|
+
);
|
|
1374
|
+
if (audit.conflict) {
|
|
1375
|
+
console.warn(
|
|
1376
|
+
`${prefix} WARNING: Both repo and global Cursor MCP define memoraone. Global shared MCP cannot bind per-window repos; remove global memoraone and use repo .cursor/mcp.json only.`
|
|
1377
|
+
);
|
|
1378
|
+
} else if (audit.globalHasManagedMemoraone && !audit.repoHasManagedMemoraone) {
|
|
1379
|
+
console.warn(
|
|
1380
|
+
`${prefix} WARNING: Cursor global MCP has memoraone but this repo lacks .cursor/mcp.json. Global MCP shares one process across windows (first-window-wins roots). Run setup-ide-files --cursor in this repo.`
|
|
1381
|
+
);
|
|
1382
|
+
} else if (audit.repoHasManagedMemoraone && !audit.globalHasManagedMemoraone) {
|
|
1383
|
+
console.log(
|
|
1384
|
+
`${prefix} Cursor MCP is repo-scoped (.cursor/mcp.json) with no global memoraone entry (recommended for multi-repo windows).`
|
|
1385
|
+
);
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
function logCursorMcpCliSummary(info, dryRun) {
|
|
1389
|
+
const { repoConfigPath, repoOutcome, npxPath, repoBackupPath, globalConfigPath, globalMemoraoneRemoved, globalBackupPath } = info;
|
|
1390
|
+
console.log(`[setup-ide-files] Cursor repo MCP config: ${repoConfigPath}`);
|
|
1391
|
+
console.log(`[setup-ide-files] Resolved npx: ${npxPath}`);
|
|
1392
|
+
if (repoBackupPath) {
|
|
1393
|
+
console.log(`[setup-ide-files] Cursor repo MCP config backup: ${repoBackupPath}`);
|
|
1394
|
+
}
|
|
1395
|
+
if (repoOutcome === "created") {
|
|
1396
|
+
console.log(
|
|
1397
|
+
dryRun ? `[setup-ide-files] Cursor repo MCP config would be created: ${repoConfigPath}` : `[setup-ide-files] Cursor repo MCP config created: ${repoConfigPath}`
|
|
1398
|
+
);
|
|
1399
|
+
} else if (repoOutcome === "updated") {
|
|
1400
|
+
console.log(
|
|
1401
|
+
dryRun ? `[setup-ide-files] Cursor repo MCP config would be updated: ${repoConfigPath}` : `[setup-ide-files] Cursor repo MCP config updated: ${repoConfigPath}`
|
|
1402
|
+
);
|
|
1403
|
+
} else if (repoOutcome === "skipped") {
|
|
1404
|
+
console.log(`[setup-ide-files] Cursor repo MCP config unchanged: ${repoConfigPath}`);
|
|
1405
|
+
}
|
|
1406
|
+
if (globalMemoraoneRemoved && globalConfigPath) {
|
|
1407
|
+
console.log(
|
|
1408
|
+
dryRun ? `[setup-ide-files] Would remove memoraone from Cursor global MCP config: ${globalConfigPath}` : `[setup-ide-files] Removed memoraone from Cursor global MCP config: ${globalConfigPath}`
|
|
1409
|
+
);
|
|
1410
|
+
if (globalBackupPath) {
|
|
1411
|
+
console.log(`[setup-ide-files] Cursor global MCP config backup: ${globalBackupPath}`);
|
|
1412
|
+
}
|
|
1413
|
+
} else if (globalConfigPath) {
|
|
1414
|
+
console.log(
|
|
1415
|
+
`[setup-ide-files] Cursor global MCP config unchanged (no managed memoraone to remove): ${globalConfigPath}`
|
|
1416
|
+
);
|
|
1417
|
+
}
|
|
1418
|
+
console.log(
|
|
1419
|
+
"[setup-ide-files] Each Cursor window uses this repo\u2019s .cursor/mcp.json (separate MCP process per repo)."
|
|
1420
|
+
);
|
|
1421
|
+
console.log(
|
|
1422
|
+
"[setup-ide-files] Fully quit Cursor and reopen this repo for MCP changes to take effect."
|
|
1423
|
+
);
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
// src/cleanup.ts
|
|
1427
|
+
var execFileAsync2 = (0, import_node_util2.promisify)(import_node_child_process3.execFile);
|
|
1428
|
+
var DAEMON_PROJECT_ID_RE = /--project-id\s+([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/i;
|
|
1429
|
+
var PROJECT_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
1430
|
+
var MEMORAONE_MCP_COMMAND_RE = /memoraone-mcp|memoraOne-mcp|@memoraone\/mcp/;
|
|
1431
|
+
var CLEANUP_PROJECT_ID_REQUIRED_ERROR = "Provide --project-id <id> or run from a folder containing memoraone.m1.";
|
|
1432
|
+
function isMemoraoneMcpCommandLine(commandLine) {
|
|
1433
|
+
return MEMORAONE_MCP_COMMAND_RE.test(commandLine);
|
|
1434
|
+
}
|
|
1435
|
+
function parseDaemonProjectIdFromCommandLine(commandLine) {
|
|
1436
|
+
if (!commandLine.includes("--daemon")) {
|
|
1437
|
+
return null;
|
|
1438
|
+
}
|
|
1439
|
+
const match = commandLine.match(DAEMON_PROJECT_ID_RE);
|
|
1440
|
+
return match ? match[1].toLowerCase() : null;
|
|
1441
|
+
}
|
|
1442
|
+
function parseDaemonIdeFromCommandLine(commandLine) {
|
|
1443
|
+
if (!commandLine.includes("--daemon")) {
|
|
1444
|
+
return void 0;
|
|
1445
|
+
}
|
|
1446
|
+
return parseIdeTypeFromCommandLine(commandLine);
|
|
1447
|
+
}
|
|
1448
|
+
function parseDaemonProcessLines(lines) {
|
|
1449
|
+
const processes = [];
|
|
1450
|
+
for (const line of lines) {
|
|
1451
|
+
const trimmed = line.trim();
|
|
1452
|
+
if (!trimmed) continue;
|
|
1453
|
+
const spaceIdx = trimmed.indexOf(" ");
|
|
1454
|
+
if (spaceIdx <= 0) continue;
|
|
1455
|
+
const pid = Number.parseInt(trimmed.slice(0, spaceIdx), 10);
|
|
1456
|
+
if (!Number.isFinite(pid) || pid <= 0) continue;
|
|
1457
|
+
const command = trimmed.slice(spaceIdx + 1);
|
|
1458
|
+
if (!isMemoraoneMcpCommandLine(command)) continue;
|
|
1459
|
+
const projectId = parseDaemonProjectIdFromCommandLine(command);
|
|
1460
|
+
if (projectId === null) continue;
|
|
1461
|
+
const ide = parseDaemonIdeFromCommandLine(command);
|
|
1462
|
+
processes.push({ pid, command, projectId, ...ide !== void 0 ? { ide } : {} });
|
|
1463
|
+
}
|
|
1464
|
+
return processes;
|
|
1465
|
+
}
|
|
1466
|
+
function normalizeCleanupProjectId(projectId) {
|
|
1467
|
+
const trimmed = projectId.trim();
|
|
1468
|
+
if (!PROJECT_ID_RE.test(trimmed)) {
|
|
1469
|
+
return { error: `Invalid project id: ${projectId}` };
|
|
1470
|
+
}
|
|
1471
|
+
return trimmed.toLowerCase();
|
|
1472
|
+
}
|
|
1473
|
+
async function defaultListDaemonProcesses() {
|
|
1474
|
+
const { stdout } = await execFileAsync2("ps", ["-eo", "pid=,args="], {
|
|
1475
|
+
maxBuffer: 10 * 1024 * 1024
|
|
1476
|
+
});
|
|
1477
|
+
return parseDaemonProcessLines(stdout.split("\n"));
|
|
1478
|
+
}
|
|
1479
|
+
async function defaultListSocketPaths(projectId) {
|
|
1480
|
+
const baseDir = getMcpBaseDir();
|
|
1481
|
+
let entries;
|
|
1482
|
+
try {
|
|
1483
|
+
entries = await fs5.readdir(baseDir);
|
|
1484
|
+
} catch (err) {
|
|
1485
|
+
const code = err && typeof err === "object" && "code" in err ? err.code : void 0;
|
|
1486
|
+
if (code === "ENOENT") {
|
|
1487
|
+
return [];
|
|
1488
|
+
}
|
|
1489
|
+
throw err;
|
|
1490
|
+
}
|
|
1491
|
+
const paths = [];
|
|
1492
|
+
for (const name of entries) {
|
|
1493
|
+
if (!name.endsWith(".sock") || !isMemoraoneSocketFilename(name)) {
|
|
1494
|
+
continue;
|
|
1495
|
+
}
|
|
1496
|
+
const socketPath = path7.join(baseDir, name);
|
|
1497
|
+
if (projectId === null) {
|
|
1498
|
+
paths.push(socketPath);
|
|
1499
|
+
continue;
|
|
1500
|
+
}
|
|
1501
|
+
const normalizedProjectId = projectId.trim().toLowerCase();
|
|
1502
|
+
if (isLegacySocketFilename(name) && isSocketFilenameForProject(name, normalizedProjectId)) {
|
|
1503
|
+
paths.push(socketPath);
|
|
1504
|
+
continue;
|
|
1505
|
+
}
|
|
1506
|
+
if (isHashSocketFilename(name)) {
|
|
1507
|
+
const record = readBindingSidecarRecord(socketPath);
|
|
1508
|
+
if (record?.projectId.trim().toLowerCase() === normalizedProjectId) {
|
|
1509
|
+
paths.push(socketPath);
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
return paths.sort();
|
|
1514
|
+
}
|
|
1515
|
+
async function filterSocketPathsByIde(socketPaths, projectId, ide) {
|
|
1516
|
+
if (ide === void 0) return socketPaths;
|
|
1517
|
+
const normalizedProjectId = projectId.trim().toLowerCase();
|
|
1518
|
+
const filtered = [];
|
|
1519
|
+
for (const socketPath of socketPaths) {
|
|
1520
|
+
const basename4 = path7.basename(socketPath);
|
|
1521
|
+
if (isLegacySocketFilename(basename4)) {
|
|
1522
|
+
if (isSocketFilenameForProjectAndIde(basename4, normalizedProjectId, ide)) {
|
|
1523
|
+
filtered.push(socketPath);
|
|
1524
|
+
}
|
|
1525
|
+
continue;
|
|
1526
|
+
}
|
|
1527
|
+
if (isHashSocketFilename(basename4)) {
|
|
1528
|
+
const record = readBindingSidecarRecord(socketPath);
|
|
1529
|
+
if (record?.projectId.trim().toLowerCase() === normalizedProjectId && record.ideType === ide) {
|
|
1530
|
+
filtered.push(socketPath);
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
return filtered;
|
|
1535
|
+
}
|
|
1536
|
+
async function defaultKillProcess(pid) {
|
|
1537
|
+
process.kill(pid, "SIGTERM");
|
|
1538
|
+
}
|
|
1539
|
+
async function defaultRemoveSocket(socketPath) {
|
|
1540
|
+
await fs5.unlink(socketPath);
|
|
1541
|
+
try {
|
|
1542
|
+
await fs5.unlink(bindingSidecarPath(socketPath));
|
|
1543
|
+
} catch {
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
async function defaultConfirm(message) {
|
|
1547
|
+
if (!import_node_process.stdin.isTTY) {
|
|
1548
|
+
return false;
|
|
1549
|
+
}
|
|
1550
|
+
const rl = readline3.createInterface({ input: import_node_process.stdin, output: import_node_process.stdout });
|
|
1551
|
+
try {
|
|
1552
|
+
const answer = await rl.question(`${message} [y/N] `);
|
|
1553
|
+
return /^y(es)?$/i.test(answer.trim());
|
|
1554
|
+
} finally {
|
|
1555
|
+
rl.close();
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
async function resolveCleanupTarget(cwd) {
|
|
1559
|
+
try {
|
|
1560
|
+
const binding = await resolveAuthoritativeBinding([path7.resolve(cwd)]);
|
|
1561
|
+
return {
|
|
1562
|
+
workspaceRoot: binding.workspaceRoot,
|
|
1563
|
+
m1Path: binding.m1Path,
|
|
1564
|
+
projectId: binding.projectId
|
|
1565
|
+
};
|
|
1566
|
+
} catch {
|
|
1567
|
+
return { error: CLEANUP_PROJECT_ID_REQUIRED_ERROR };
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
function filterProcessesForScope(processes, projectId) {
|
|
1571
|
+
if (projectId === null) {
|
|
1572
|
+
return { matching: processes, skipped: [] };
|
|
1573
|
+
}
|
|
1574
|
+
const normalized = projectId.toLowerCase();
|
|
1575
|
+
const matching = [];
|
|
1576
|
+
const skipped = [];
|
|
1577
|
+
for (const proc of processes) {
|
|
1578
|
+
if (proc.projectId === normalized) {
|
|
1579
|
+
matching.push(proc);
|
|
1580
|
+
} else {
|
|
1581
|
+
skipped.push(proc);
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
return { matching, skipped };
|
|
433
1585
|
}
|
|
434
1586
|
function logPrefix(dryRun) {
|
|
435
1587
|
return dryRun ? "[cleanup][dry-run]" : "[cleanup]";
|
|
436
1588
|
}
|
|
437
|
-
function logReconnectNotice(prefix, ide) {
|
|
1589
|
+
function logReconnectNotice(opts, prefix, ide) {
|
|
1590
|
+
if (opts.quiet) return;
|
|
438
1591
|
if (ide) {
|
|
439
1592
|
console.log(
|
|
440
1593
|
`${prefix} Note: Valid ${ide} connections for this project may disconnect temporarily; they should reconnect automatically. Stale connections will remain cleared.`
|
|
@@ -445,6 +1598,16 @@ function logReconnectNotice(prefix, ide) {
|
|
|
445
1598
|
);
|
|
446
1599
|
}
|
|
447
1600
|
}
|
|
1601
|
+
function cleanupLog(opts, message) {
|
|
1602
|
+
if (!opts.quiet) {
|
|
1603
|
+
console.log(message);
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
function cleanupWarn(opts, message) {
|
|
1607
|
+
if (!opts.quiet) {
|
|
1608
|
+
console.warn(message);
|
|
1609
|
+
}
|
|
1610
|
+
}
|
|
448
1611
|
async function runCleanup(opts) {
|
|
449
1612
|
const listProcesses = opts.listProcesses ?? defaultListDaemonProcesses;
|
|
450
1613
|
const listSocketPaths = opts.listSocketPaths ?? defaultListSocketPaths;
|
|
@@ -465,8 +1628,9 @@ async function runCleanup(opts) {
|
|
|
465
1628
|
error: "Cannot combine --all-projects with --project-id."
|
|
466
1629
|
};
|
|
467
1630
|
}
|
|
468
|
-
|
|
469
|
-
|
|
1631
|
+
cleanupLog(opts, `${prefix} Mode: all projects (--all-projects)`);
|
|
1632
|
+
cleanupWarn(
|
|
1633
|
+
opts,
|
|
470
1634
|
`${prefix} WARNING: This stops every MemoraOne MCP daemon and removes all project sockets under ${getMcpBaseDir()}.`
|
|
471
1635
|
);
|
|
472
1636
|
} else if (opts.projectId) {
|
|
@@ -475,9 +1639,9 @@ async function runCleanup(opts) {
|
|
|
475
1639
|
return { exitCode: 1, killedPids: [], removedSockets: [], skippedProcesses: [], error: normalized.error };
|
|
476
1640
|
}
|
|
477
1641
|
targetProjectId = normalized;
|
|
478
|
-
|
|
1642
|
+
cleanupLog(opts, `${prefix} Project id: ${targetProjectId}`);
|
|
479
1643
|
if (opts.ide) {
|
|
480
|
-
|
|
1644
|
+
cleanupLog(opts, `${prefix} IDE filter: ${opts.ide}`);
|
|
481
1645
|
}
|
|
482
1646
|
} else {
|
|
483
1647
|
const target = await resolveCleanupTarget(opts.cwd);
|
|
@@ -487,15 +1651,23 @@ async function runCleanup(opts) {
|
|
|
487
1651
|
targetProjectId = target.projectId;
|
|
488
1652
|
workspaceRoot = target.workspaceRoot;
|
|
489
1653
|
m1Path = target.m1Path;
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
1654
|
+
cleanupLog(opts, `${prefix} Workspace root: ${workspaceRoot}`);
|
|
1655
|
+
cleanupLog(opts, `${prefix} memoraone.m1: ${m1Path}`);
|
|
1656
|
+
cleanupLog(opts, `${prefix} Project id: ${targetProjectId}`);
|
|
493
1657
|
if (opts.ide) {
|
|
494
|
-
|
|
1658
|
+
cleanupLog(opts, `${prefix} IDE filter: ${opts.ide}`);
|
|
1659
|
+
}
|
|
1660
|
+
if (workspaceRoot && (!opts.ide || opts.ide === "cursor")) {
|
|
1661
|
+
try {
|
|
1662
|
+
const cursorAudit = await auditCursorMcpConfig({ repoRoot: workspaceRoot });
|
|
1663
|
+
logCursorMcpConfigAudit(prefix, cursorAudit);
|
|
1664
|
+
} catch (err) {
|
|
1665
|
+
cleanupWarn(opts, `${prefix} Cursor MCP config audit failed: ${String(err)}`);
|
|
1666
|
+
}
|
|
495
1667
|
}
|
|
496
1668
|
}
|
|
497
1669
|
if (targetProjectId !== null || opts.ide) {
|
|
498
|
-
logReconnectNotice(prefix, opts.ide);
|
|
1670
|
+
logReconnectNotice(opts, prefix, opts.ide);
|
|
499
1671
|
}
|
|
500
1672
|
const allDaemonProcesses = await listProcesses();
|
|
501
1673
|
const { matching: projectProcesses, skipped: skippedProcesses } = filterProcessesForScope(
|
|
@@ -511,392 +1683,198 @@ async function runCleanup(opts) {
|
|
|
511
1683
|
processesToStop.push(proc);
|
|
512
1684
|
} else if (proc.ide === void 0) {
|
|
513
1685
|
ideSkippedProcesses.push(proc);
|
|
514
|
-
|
|
1686
|
+
cleanupLog(
|
|
1687
|
+
opts,
|
|
515
1688
|
`${prefix} Skipped daemon pid=${proc.pid} because IDE could not be safely determined.`
|
|
516
1689
|
);
|
|
517
1690
|
} else {
|
|
518
1691
|
ideSkippedProcesses.push(proc);
|
|
519
|
-
|
|
1692
|
+
cleanupLog(
|
|
1693
|
+
opts,
|
|
520
1694
|
`${prefix} Skipped daemon pid=${proc.pid} (IDE ${proc.ide} does not match filter ${opts.ide}).`
|
|
521
1695
|
);
|
|
522
1696
|
}
|
|
523
1697
|
}
|
|
524
1698
|
}
|
|
525
1699
|
const allSocketPaths = await listSocketPaths(targetProjectId);
|
|
526
|
-
const socketPaths = targetProjectId === null ? allSocketPaths : filterSocketPathsByIde(allSocketPaths, targetProjectId, opts.ide);
|
|
1700
|
+
const socketPaths = targetProjectId === null ? allSocketPaths : await filterSocketPathsByIde(allSocketPaths, targetProjectId, opts.ide);
|
|
527
1701
|
if (opts.allProjects) {
|
|
528
1702
|
const projectIds = /* @__PURE__ */ new Set();
|
|
529
1703
|
for (const proc of processesToStop) {
|
|
530
1704
|
projectIds.add(proc.projectId);
|
|
531
1705
|
}
|
|
532
1706
|
for (const socketPath of socketPaths) {
|
|
533
|
-
const id = extractProjectIdFromSocketFilename(
|
|
534
|
-
if (id)
|
|
1707
|
+
const id = extractProjectIdFromSocketFilename(path7.basename(socketPath));
|
|
1708
|
+
if (id) {
|
|
1709
|
+
projectIds.add(id);
|
|
1710
|
+
continue;
|
|
1711
|
+
}
|
|
1712
|
+
const record = readBindingSidecarRecord(socketPath);
|
|
1713
|
+
if (record) {
|
|
1714
|
+
projectIds.add(record.projectId.trim().toLowerCase());
|
|
1715
|
+
}
|
|
535
1716
|
}
|
|
536
|
-
|
|
1717
|
+
cleanupLog(
|
|
1718
|
+
opts,
|
|
1719
|
+
`${prefix} Projects affected: ${projectIds.size ? [...projectIds].sort().join(", ") : "(none found)"}`
|
|
1720
|
+
);
|
|
537
1721
|
}
|
|
538
1722
|
if (processesToStop.length) {
|
|
539
|
-
|
|
1723
|
+
cleanupLog(opts, `${prefix} Daemon processes to stop:`);
|
|
540
1724
|
for (const proc of processesToStop) {
|
|
541
1725
|
const ideLabel = proc.ide ? ` ide=${proc.ide}` : "";
|
|
542
|
-
|
|
543
|
-
}
|
|
544
|
-
} else if (ideSkippedProcesses.length) {
|
|
545
|
-
|
|
546
|
-
} else {
|
|
547
|
-
|
|
548
|
-
}
|
|
549
|
-
if (socketPaths.length) {
|
|
550
|
-
console.log(`${prefix} Sockets to remove:`);
|
|
551
|
-
for (const socketPath of socketPaths) {
|
|
552
|
-
console.log(`${prefix} ${socketPath}`);
|
|
553
|
-
}
|
|
554
|
-
} else {
|
|
555
|
-
console.log(`${prefix} No matching sockets found under ${getMcpBaseDir()}.`);
|
|
556
|
-
}
|
|
557
|
-
if (skippedProcesses.length) {
|
|
558
|
-
console.log(`${prefix} Skipped unrelated daemon processes:`);
|
|
559
|
-
for (const proc of skippedProcesses) {
|
|
560
|
-
console.log(`${prefix} pid=${proc.pid} project=${proc.projectId}`);
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
if (opts.allProjects && !opts.dryRun) {
|
|
564
|
-
const ok = opts.assumeYes ? true : await confirm(`${prefix} Proceed with cleanup for ALL projects?`);
|
|
565
|
-
if (!ok) {
|
|
566
|
-
console.log(`${prefix} Aborted.`);
|
|
567
|
-
return {
|
|
568
|
-
exitCode: 1,
|
|
569
|
-
workspaceRoot,
|
|
570
|
-
m1Path,
|
|
571
|
-
projectId: targetProjectId ?? void 0,
|
|
572
|
-
killedPids: [],
|
|
573
|
-
removedSockets: [],
|
|
574
|
-
skippedProcesses: [...skippedProcesses, ...ideSkippedProcesses],
|
|
575
|
-
error: opts.assumeYes ? void 0 : "Aborted (--all requires --yes in non-interactive mode)"
|
|
576
|
-
};
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
const killedPids = [];
|
|
580
|
-
const removedSockets = [];
|
|
581
|
-
if (opts.dryRun) {
|
|
582
|
-
console.log(`${prefix} Dry run complete \u2014 no processes stopped, no sockets removed.`);
|
|
583
|
-
return {
|
|
584
|
-
exitCode: 0,
|
|
585
|
-
workspaceRoot,
|
|
586
|
-
m1Path,
|
|
587
|
-
projectId: targetProjectId ?? void 0,
|
|
588
|
-
killedPids: processesToStop.map((p) => p.pid),
|
|
589
|
-
removedSockets: socketPaths,
|
|
590
|
-
skippedProcesses: [...skippedProcesses, ...ideSkippedProcesses]
|
|
591
|
-
};
|
|
592
|
-
}
|
|
593
|
-
for (const proc of processesToStop) {
|
|
594
|
-
try {
|
|
595
|
-
await killProcess(proc.pid);
|
|
596
|
-
killedPids.push(proc.pid);
|
|
597
|
-
console.log(`${prefix} Stopped daemon pid=${proc.pid} project=${proc.projectId}`);
|
|
598
|
-
} catch (err) {
|
|
599
|
-
console.warn(`${prefix} Could not stop pid=${proc.pid}: ${String(err)}`);
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
if (killedPids.length) {
|
|
603
|
-
await new Promise((r) => setTimeout(r, 300));
|
|
604
|
-
}
|
|
605
|
-
for (const socketPath of socketPaths) {
|
|
606
|
-
try {
|
|
607
|
-
await removeSocket(socketPath);
|
|
608
|
-
removedSockets.push(socketPath);
|
|
609
|
-
console.log(`${prefix} Removed socket ${socketPath}`);
|
|
610
|
-
} catch (err) {
|
|
611
|
-
const code = err && typeof err === "object" && "code" in err ? err.code : void 0;
|
|
612
|
-
if (code !== "ENOENT") {
|
|
613
|
-
console.warn(`${prefix} Could not remove socket ${socketPath}: ${String(err)}`);
|
|
614
|
-
}
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
console.log(`${prefix} Done. stopped=${killedPids.length} socketsRemoved=${removedSockets.length}`);
|
|
618
|
-
return {
|
|
619
|
-
exitCode: 0,
|
|
620
|
-
workspaceRoot,
|
|
621
|
-
m1Path,
|
|
622
|
-
projectId: targetProjectId ?? void 0,
|
|
623
|
-
killedPids,
|
|
624
|
-
removedSockets,
|
|
625
|
-
skippedProcesses: [...skippedProcesses, ...ideSkippedProcesses]
|
|
626
|
-
};
|
|
627
|
-
}
|
|
628
|
-
function parseCleanupFlags(argv) {
|
|
629
|
-
let dryRun = false;
|
|
630
|
-
let allProjects = false;
|
|
631
|
-
let assumeYes = false;
|
|
632
|
-
let projectId;
|
|
633
|
-
let ide;
|
|
634
|
-
let invalidIde;
|
|
635
|
-
const unknown = [];
|
|
636
|
-
for (let i = 0; i < argv.length; i++) {
|
|
637
|
-
const arg = argv[i];
|
|
638
|
-
if (arg === "--dry-run") dryRun = true;
|
|
639
|
-
else if (arg === "--all-projects" || arg === "--all") allProjects = true;
|
|
640
|
-
else if (arg === "--yes" || arg === "-y") assumeYes = true;
|
|
641
|
-
else if (arg === "--project-id") {
|
|
642
|
-
if (i + 1 >= argv.length) {
|
|
643
|
-
unknown.push("--project-id (missing value)");
|
|
644
|
-
} else {
|
|
645
|
-
projectId = argv[++i];
|
|
646
|
-
}
|
|
647
|
-
} else if (arg === "--ide") {
|
|
648
|
-
if (i + 1 >= argv.length) {
|
|
649
|
-
unknown.push("--ide (missing value)");
|
|
650
|
-
} else {
|
|
651
|
-
const value = argv[++i];
|
|
652
|
-
if (IDE_TYPES.includes(value)) {
|
|
653
|
-
ide = value;
|
|
654
|
-
} else {
|
|
655
|
-
invalidIde = value;
|
|
656
|
-
}
|
|
657
|
-
}
|
|
658
|
-
} else if (arg.startsWith("-")) unknown.push(arg);
|
|
659
|
-
else unknown.push(arg);
|
|
660
|
-
}
|
|
661
|
-
return { dryRun, allProjects, assumeYes, projectId, ide, invalidIde, unknown };
|
|
662
|
-
}
|
|
663
|
-
async function cliCleanup(argv) {
|
|
664
|
-
const { dryRun, allProjects, assumeYes, projectId, ide, invalidIde, unknown } = parseCleanupFlags(argv);
|
|
665
|
-
if (invalidIde) {
|
|
666
|
-
console.error(
|
|
667
|
-
`[cleanup] Invalid --ide value: ${invalidIde}. Expected one of: ${IDE_TYPES.join(", ")}.`
|
|
668
|
-
);
|
|
669
|
-
return 1;
|
|
670
|
-
}
|
|
671
|
-
if (unknown.length) {
|
|
672
|
-
console.error(`[cleanup] Unknown option(s): ${unknown.join(", ")}`);
|
|
673
|
-
return 1;
|
|
674
|
-
}
|
|
675
|
-
const result = await runCleanup({
|
|
676
|
-
cwd: process.cwd(),
|
|
677
|
-
dryRun,
|
|
678
|
-
allProjects,
|
|
679
|
-
assumeYes,
|
|
680
|
-
projectId,
|
|
681
|
-
ide
|
|
682
|
-
});
|
|
683
|
-
if (result.error) {
|
|
684
|
-
console.error(`[cleanup] ${result.error}`);
|
|
685
|
-
}
|
|
686
|
-
return result.exitCode;
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
// src/cursorGlobalMcpConfig.ts
|
|
690
|
-
var fs4 = __toESM(require("fs/promises"), 1);
|
|
691
|
-
var os2 = __toESM(require("os"), 1);
|
|
692
|
-
var path4 = __toESM(require("path"), 1);
|
|
693
|
-
var import_node_child_process2 = require("child_process");
|
|
694
|
-
var import_node_util2 = require("util");
|
|
695
|
-
var execFileAsync2 = (0, import_node_util2.promisify)(import_node_child_process2.execFile);
|
|
696
|
-
function buildMemoraoneCursorMcpServer(npxPath) {
|
|
697
|
-
return {
|
|
698
|
-
command: npxPath,
|
|
699
|
-
args: ["-y", "@memoraone/mcp@latest"],
|
|
700
|
-
env: {
|
|
701
|
-
MEMORAONE_API_URL: "https://api.memoraone.com",
|
|
702
|
-
MEMORAONE_IDE_TYPE: "cursor"
|
|
703
|
-
}
|
|
704
|
-
};
|
|
705
|
-
}
|
|
706
|
-
async function pathExists(filePath) {
|
|
707
|
-
try {
|
|
708
|
-
await fs4.access(filePath);
|
|
709
|
-
return true;
|
|
710
|
-
} catch {
|
|
711
|
-
return false;
|
|
712
|
-
}
|
|
713
|
-
}
|
|
714
|
-
function stripLeadingLineComments(text) {
|
|
715
|
-
return text.split("\n").filter((line) => !/^\s*\/\//.test(line)).join("\n");
|
|
716
|
-
}
|
|
717
|
-
function getKnownCursorGlobalMcpConfigCandidates(homeDir) {
|
|
718
|
-
return [path4.join(homeDir, ".cursor", "mcp.json")];
|
|
719
|
-
}
|
|
720
|
-
async function detectCursorGlobalMcpConfig(options) {
|
|
721
|
-
if (options?.explicitPath) {
|
|
722
|
-
return { ok: true, path: options.explicitPath, detectedExisting: await pathExists(options.explicitPath) };
|
|
723
|
-
}
|
|
724
|
-
const homeDir = options?.homeDir ?? os2.homedir();
|
|
725
|
-
const candidates = getKnownCursorGlobalMcpConfigCandidates(homeDir);
|
|
726
|
-
const existing = [];
|
|
727
|
-
for (const candidate of candidates) {
|
|
728
|
-
if (await pathExists(candidate)) existing.push(candidate);
|
|
729
|
-
}
|
|
730
|
-
if (existing.length > 1) {
|
|
731
|
-
return {
|
|
732
|
-
ok: false,
|
|
733
|
-
error: "[setup-ide-files] Multiple Cursor global MCP config paths found. Specify one explicitly.",
|
|
734
|
-
candidates: existing
|
|
735
|
-
};
|
|
736
|
-
}
|
|
737
|
-
if (existing.length === 1) {
|
|
738
|
-
return { ok: true, path: existing[0], detectedExisting: true };
|
|
739
|
-
}
|
|
740
|
-
const defaultPath = candidates[0];
|
|
741
|
-
if (!defaultPath) {
|
|
742
|
-
return {
|
|
743
|
-
ok: false,
|
|
744
|
-
error: "[setup-ide-files] No known Cursor global MCP config path.",
|
|
745
|
-
candidates: []
|
|
746
|
-
};
|
|
747
|
-
}
|
|
748
|
-
return { ok: true, path: defaultPath, detectedExisting: false };
|
|
749
|
-
}
|
|
750
|
-
function formatBackupTimestamp(d = /* @__PURE__ */ new Date()) {
|
|
751
|
-
const pad = (n) => String(n).padStart(2, "0");
|
|
752
|
-
return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
|
|
753
|
-
}
|
|
754
|
-
async function isWorkingNpx(npxPath) {
|
|
755
|
-
try {
|
|
756
|
-
if (!await pathExists(npxPath)) return false;
|
|
757
|
-
if (process.platform !== "win32") {
|
|
758
|
-
try {
|
|
759
|
-
await fs4.access(npxPath, fs4.constants.X_OK);
|
|
760
|
-
} catch {
|
|
761
|
-
return false;
|
|
762
|
-
}
|
|
763
|
-
}
|
|
764
|
-
await execFileAsync2(npxPath, ["--version"], { timeout: 1e4 });
|
|
765
|
-
return true;
|
|
766
|
-
} catch {
|
|
767
|
-
return false;
|
|
768
|
-
}
|
|
769
|
-
}
|
|
770
|
-
async function resolveNpxPath() {
|
|
771
|
-
const npxName = process.platform === "win32" ? "npx.cmd" : "npx";
|
|
772
|
-
const candidates = [];
|
|
773
|
-
if (process.platform === "darwin") {
|
|
774
|
-
candidates.push("/opt/homebrew/bin/npx", "/usr/local/bin/npx");
|
|
775
|
-
} else if (process.platform === "linux") {
|
|
776
|
-
candidates.push("/usr/local/bin/npx");
|
|
777
|
-
}
|
|
778
|
-
const pathSep = process.platform === "win32" ? ";" : ":";
|
|
779
|
-
for (const dir of (process.env.PATH ?? "").split(pathSep)) {
|
|
780
|
-
if (!dir) continue;
|
|
781
|
-
candidates.push(path4.join(dir, npxName));
|
|
782
|
-
}
|
|
783
|
-
try {
|
|
784
|
-
const lookupCmd = process.platform === "win32" ? "where" : "which";
|
|
785
|
-
const { stdout } = await execFileAsync2(lookupCmd, [npxName], { timeout: 5e3 });
|
|
786
|
-
const first = stdout.trim().split(/\r?\n/).map((line) => line.trim()).find(Boolean);
|
|
787
|
-
if (first) candidates.unshift(first);
|
|
788
|
-
} catch {
|
|
789
|
-
}
|
|
790
|
-
const seen = /* @__PURE__ */ new Set();
|
|
791
|
-
for (const candidate of candidates) {
|
|
792
|
-
const abs = path4.isAbsolute(candidate) ? candidate : path4.resolve(candidate);
|
|
793
|
-
const key = process.platform === "win32" ? abs.toLowerCase() : abs;
|
|
794
|
-
if (seen.has(key)) continue;
|
|
795
|
-
seen.add(key);
|
|
796
|
-
if (await isWorkingNpx(abs)) return abs;
|
|
797
|
-
}
|
|
798
|
-
return null;
|
|
799
|
-
}
|
|
800
|
-
function mergeCursorGlobalMcpConfigObject(existing, npxPath) {
|
|
801
|
-
const base = existing && typeof existing === "object" ? { ...existing } : { mcpServers: {} };
|
|
802
|
-
const mcpServers = typeof base.mcpServers === "object" && base.mcpServers !== null && !Array.isArray(base.mcpServers) ? { ...base.mcpServers } : {};
|
|
803
|
-
mcpServers.memoraone = buildMemoraoneCursorMcpServer(npxPath);
|
|
804
|
-
return { ...base, mcpServers };
|
|
805
|
-
}
|
|
806
|
-
function memoraoneServerMatches(server, npxPath) {
|
|
807
|
-
if (!server || typeof server !== "object") return false;
|
|
808
|
-
const s = server;
|
|
809
|
-
if (s.command !== npxPath) return false;
|
|
810
|
-
if (!Array.isArray(s.args) || s.args.length !== 2) return false;
|
|
811
|
-
if (s.args[0] !== "-y" || s.args[1] !== "@memoraone/mcp@latest") return false;
|
|
812
|
-
const env = s.env;
|
|
813
|
-
if (!env || typeof env !== "object") return false;
|
|
814
|
-
const e = env;
|
|
815
|
-
return e.MEMORAONE_API_URL === "https://api.memoraone.com" && e.MEMORAONE_IDE_TYPE === "cursor";
|
|
816
|
-
}
|
|
817
|
-
function validateCursorGlobalMcpConfig(parsed, npxPath) {
|
|
818
|
-
if (!parsed || typeof parsed !== "object") {
|
|
819
|
-
throw new Error("[setup-ide-files] Cursor global MCP config must be a JSON object.");
|
|
1726
|
+
cleanupLog(opts, `${prefix} pid=${proc.pid} project=${proc.projectId}${ideLabel}`);
|
|
1727
|
+
}
|
|
1728
|
+
} else if (ideSkippedProcesses.length) {
|
|
1729
|
+
cleanupLog(opts, `${prefix} No matching daemon processes for IDE filter ${opts.ide}.`);
|
|
1730
|
+
} else {
|
|
1731
|
+
cleanupLog(opts, `${prefix} No matching daemon processes found.`);
|
|
820
1732
|
}
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
1733
|
+
if (socketPaths.length) {
|
|
1734
|
+
cleanupLog(opts, `${prefix} Sockets to remove:`);
|
|
1735
|
+
for (const socketPath of socketPaths) {
|
|
1736
|
+
cleanupLog(opts, `${prefix} ${socketPath}`);
|
|
1737
|
+
}
|
|
1738
|
+
} else {
|
|
1739
|
+
cleanupLog(opts, `${prefix} No matching sockets found under ${getMcpBaseDir()}.`);
|
|
824
1740
|
}
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
1741
|
+
if (skippedProcesses.length) {
|
|
1742
|
+
cleanupLog(opts, `${prefix} Skipped unrelated daemon processes:`);
|
|
1743
|
+
for (const proc of skippedProcesses) {
|
|
1744
|
+
cleanupLog(opts, `${prefix} pid=${proc.pid} project=${proc.projectId}`);
|
|
1745
|
+
}
|
|
830
1746
|
}
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
1747
|
+
if (opts.allProjects && !opts.dryRun) {
|
|
1748
|
+
const ok = opts.assumeYes ? true : await confirm(`${prefix} Proceed with cleanup for ALL projects?`);
|
|
1749
|
+
if (!ok) {
|
|
1750
|
+
cleanupLog(opts, `${prefix} Aborted.`);
|
|
1751
|
+
return {
|
|
1752
|
+
exitCode: 1,
|
|
1753
|
+
workspaceRoot,
|
|
1754
|
+
m1Path,
|
|
1755
|
+
projectId: targetProjectId ?? void 0,
|
|
1756
|
+
killedPids: [],
|
|
1757
|
+
removedSockets: [],
|
|
1758
|
+
skippedProcesses: [...skippedProcesses, ...ideSkippedProcesses],
|
|
1759
|
+
error: opts.assumeYes ? void 0 : "Aborted (--all requires --yes in non-interactive mode)"
|
|
1760
|
+
};
|
|
844
1761
|
}
|
|
845
1762
|
}
|
|
846
|
-
const
|
|
847
|
-
const
|
|
848
|
-
if (
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
1763
|
+
const killedPids = [];
|
|
1764
|
+
const removedSockets = [];
|
|
1765
|
+
if (opts.dryRun) {
|
|
1766
|
+
cleanupLog(opts, `${prefix} Dry run complete \u2014 no processes stopped, no sockets removed.`);
|
|
1767
|
+
return {
|
|
1768
|
+
exitCode: 0,
|
|
1769
|
+
workspaceRoot,
|
|
1770
|
+
m1Path,
|
|
1771
|
+
projectId: targetProjectId ?? void 0,
|
|
1772
|
+
killedPids: processesToStop.map((p) => p.pid),
|
|
1773
|
+
removedSockets: socketPaths,
|
|
1774
|
+
skippedProcesses: [...skippedProcesses, ...ideSkippedProcesses]
|
|
1775
|
+
};
|
|
1776
|
+
}
|
|
1777
|
+
for (const proc of processesToStop) {
|
|
1778
|
+
try {
|
|
1779
|
+
await killProcess(proc.pid);
|
|
1780
|
+
killedPids.push(proc.pid);
|
|
1781
|
+
cleanupLog(opts, `${prefix} Stopped daemon pid=${proc.pid} project=${proc.projectId}`);
|
|
1782
|
+
} catch (err) {
|
|
1783
|
+
cleanupWarn(opts, `${prefix} Could not stop pid=${proc.pid}: ${String(err)}`);
|
|
852
1784
|
}
|
|
853
1785
|
}
|
|
854
|
-
if (
|
|
855
|
-
|
|
1786
|
+
if (killedPids.length) {
|
|
1787
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
856
1788
|
}
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
}
|
|
869
|
-
function logCursorGlobalMcpCliSummary(info, dryRun) {
|
|
870
|
-
const { configPath, npxPath, backupPath, outcome } = info;
|
|
871
|
-
console.log(`[setup-ide-files] Cursor global MCP config: ${configPath}`);
|
|
872
|
-
console.log(`[setup-ide-files] Resolved npx: ${npxPath}`);
|
|
873
|
-
if (backupPath) {
|
|
874
|
-
console.log(`[setup-ide-files] Cursor global MCP config backup: ${backupPath}`);
|
|
1789
|
+
for (const socketPath of socketPaths) {
|
|
1790
|
+
try {
|
|
1791
|
+
await removeSocket(socketPath);
|
|
1792
|
+
removedSockets.push(socketPath);
|
|
1793
|
+
cleanupLog(opts, `${prefix} Removed socket ${socketPath}`);
|
|
1794
|
+
} catch (err) {
|
|
1795
|
+
const code = err && typeof err === "object" && "code" in err ? err.code : void 0;
|
|
1796
|
+
if (code !== "ENOENT") {
|
|
1797
|
+
cleanupWarn(opts, `${prefix} Could not remove socket ${socketPath}: ${String(err)}`);
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
875
1800
|
}
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
1801
|
+
cleanupLog(opts, `${prefix} Done. stopped=${killedPids.length} socketsRemoved=${removedSockets.length}`);
|
|
1802
|
+
return {
|
|
1803
|
+
exitCode: 0,
|
|
1804
|
+
workspaceRoot,
|
|
1805
|
+
m1Path,
|
|
1806
|
+
projectId: targetProjectId ?? void 0,
|
|
1807
|
+
killedPids,
|
|
1808
|
+
removedSockets,
|
|
1809
|
+
skippedProcesses: [...skippedProcesses, ...ideSkippedProcesses]
|
|
1810
|
+
};
|
|
1811
|
+
}
|
|
1812
|
+
function parseCleanupFlags(argv) {
|
|
1813
|
+
let dryRun = false;
|
|
1814
|
+
let allProjects = false;
|
|
1815
|
+
let assumeYes = false;
|
|
1816
|
+
let projectId;
|
|
1817
|
+
let ide;
|
|
1818
|
+
let invalidIde;
|
|
1819
|
+
const unknown = [];
|
|
1820
|
+
for (let i = 0; i < argv.length; i++) {
|
|
1821
|
+
const arg = argv[i];
|
|
1822
|
+
if (arg === "--dry-run") dryRun = true;
|
|
1823
|
+
else if (arg === "--all-projects" || arg === "--all") allProjects = true;
|
|
1824
|
+
else if (arg === "--yes" || arg === "-y") assumeYes = true;
|
|
1825
|
+
else if (arg === "--project-id") {
|
|
1826
|
+
if (i + 1 >= argv.length) {
|
|
1827
|
+
unknown.push("--project-id (missing value)");
|
|
1828
|
+
} else {
|
|
1829
|
+
projectId = argv[++i];
|
|
1830
|
+
}
|
|
1831
|
+
} else if (arg === "--ide") {
|
|
1832
|
+
if (i + 1 >= argv.length) {
|
|
1833
|
+
unknown.push("--ide (missing value)");
|
|
1834
|
+
} else {
|
|
1835
|
+
const value = argv[++i];
|
|
1836
|
+
if (IDE_TYPES.includes(value)) {
|
|
1837
|
+
ide = value;
|
|
1838
|
+
} else {
|
|
1839
|
+
invalidIde = value;
|
|
1840
|
+
}
|
|
1841
|
+
}
|
|
1842
|
+
} else if (arg.startsWith("-")) unknown.push(arg);
|
|
1843
|
+
else unknown.push(arg);
|
|
1844
|
+
}
|
|
1845
|
+
return { dryRun, allProjects, assumeYes, projectId, ide, invalidIde, unknown };
|
|
1846
|
+
}
|
|
1847
|
+
async function cliCleanup(argv) {
|
|
1848
|
+
const { dryRun, allProjects, assumeYes, projectId, ide, invalidIde, unknown } = parseCleanupFlags(argv);
|
|
1849
|
+
if (invalidIde) {
|
|
1850
|
+
console.error(
|
|
1851
|
+
`[cleanup] Invalid --ide value: ${invalidIde}. Expected one of: ${IDE_TYPES.join(", ")}.`
|
|
883
1852
|
);
|
|
884
|
-
|
|
885
|
-
console.log(`[setup-ide-files] Cursor global MCP config unchanged: ${configPath}`);
|
|
1853
|
+
return 1;
|
|
886
1854
|
}
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
1855
|
+
if (unknown.length) {
|
|
1856
|
+
console.error(`[cleanup] Unknown option(s): ${unknown.join(", ")}`);
|
|
1857
|
+
return 1;
|
|
1858
|
+
}
|
|
1859
|
+
const result = await runCleanup({
|
|
1860
|
+
cwd: process.cwd(),
|
|
1861
|
+
dryRun,
|
|
1862
|
+
allProjects,
|
|
1863
|
+
assumeYes,
|
|
1864
|
+
projectId,
|
|
1865
|
+
ide
|
|
1866
|
+
});
|
|
1867
|
+
if (result.error) {
|
|
1868
|
+
console.error(`[cleanup] ${result.error}`);
|
|
1869
|
+
}
|
|
1870
|
+
return result.exitCode;
|
|
893
1871
|
}
|
|
894
1872
|
|
|
895
1873
|
// src/jetbrainsMcpConfig.ts
|
|
896
|
-
var
|
|
1874
|
+
var fs6 = __toESM(require("fs/promises"), 1);
|
|
897
1875
|
var os3 = __toESM(require("os"), 1);
|
|
898
|
-
var
|
|
899
|
-
var
|
|
1876
|
+
var path8 = __toESM(require("path"), 1);
|
|
1877
|
+
var import_node_child_process4 = require("child_process");
|
|
900
1878
|
|
|
901
1879
|
// src/configUtils.ts
|
|
902
1880
|
var DEV_API_URL = "http://localhost:3001";
|
|
@@ -914,7 +1892,7 @@ function stripLeadingLineComments2(text) {
|
|
|
914
1892
|
}
|
|
915
1893
|
async function pathExists2(filePath) {
|
|
916
1894
|
try {
|
|
917
|
-
await
|
|
1895
|
+
await fs6.access(filePath);
|
|
918
1896
|
return true;
|
|
919
1897
|
} catch {
|
|
920
1898
|
return false;
|
|
@@ -925,12 +1903,12 @@ function formatJetBrainsBackupTimestamp(d = /* @__PURE__ */ new Date()) {
|
|
|
925
1903
|
return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
|
|
926
1904
|
}
|
|
927
1905
|
function getJetBrainsGlobalMcpConfigPath(homeDir) {
|
|
928
|
-
return
|
|
1906
|
+
return path8.join(homeDir, ".ai", "mcp", "mcp.json");
|
|
929
1907
|
}
|
|
930
1908
|
function getJetBrainsProjectMcpConfigPaths(repoRoot) {
|
|
931
1909
|
return [
|
|
932
|
-
{ kind: "project-ai", path:
|
|
933
|
-
{ kind: "project-ij", path:
|
|
1910
|
+
{ kind: "project-ai", path: path8.join(repoRoot, ".ai", "mcp", "mcp.json") },
|
|
1911
|
+
{ kind: "project-ij", path: path8.join(repoRoot, ".ij", "mcp", "mcp.json") }
|
|
934
1912
|
];
|
|
935
1913
|
}
|
|
936
1914
|
function getKnownJetBrainsMcpConfigLocations(homeDir, repoRoot) {
|
|
@@ -941,7 +1919,7 @@ function getKnownJetBrainsMcpConfigLocations(homeDir, repoRoot) {
|
|
|
941
1919
|
}
|
|
942
1920
|
async function isZeroByteConfigFile(filePath) {
|
|
943
1921
|
if (!await pathExists2(filePath)) return false;
|
|
944
|
-
const stat2 = await
|
|
1922
|
+
const stat2 = await fs6.stat(filePath);
|
|
945
1923
|
return stat2.size === 0;
|
|
946
1924
|
}
|
|
947
1925
|
function buildMemoraoneJetBrainsMcpServer(options) {
|
|
@@ -965,7 +1943,7 @@ function mergeJetBrainsMcpConfigObject(existing, memoraone) {
|
|
|
965
1943
|
mcpServers.memoraone = memoraone;
|
|
966
1944
|
return { ...base, mcpServers };
|
|
967
1945
|
}
|
|
968
|
-
function
|
|
1946
|
+
function memoraoneServerMatches(server, expected) {
|
|
969
1947
|
if (!server || typeof server !== "object") return false;
|
|
970
1948
|
const s = server;
|
|
971
1949
|
if (s.command !== expected.command) return false;
|
|
@@ -993,20 +1971,20 @@ function validateJetBrainsMcpConfig(parsed, expected) {
|
|
|
993
1971
|
throw new Error("[setup-ide-files] JetBrains MCP config missing mcpServers object.");
|
|
994
1972
|
}
|
|
995
1973
|
const memoraone = mcpServers.memoraone;
|
|
996
|
-
if (!
|
|
1974
|
+
if (!memoraoneServerMatches(memoraone, expected)) {
|
|
997
1975
|
throw new Error(
|
|
998
1976
|
"[setup-ide-files] JetBrains MCP config mcpServers.memoraone is missing or invalid."
|
|
999
1977
|
);
|
|
1000
1978
|
}
|
|
1001
1979
|
}
|
|
1002
1980
|
async function readJsonConfig(filePath) {
|
|
1003
|
-
const raw = await
|
|
1981
|
+
const raw = await fs6.readFile(filePath, "utf8");
|
|
1004
1982
|
if (raw.trim() === "") return null;
|
|
1005
1983
|
return JSON.parse(stripLeadingLineComments2(raw));
|
|
1006
1984
|
}
|
|
1007
1985
|
async function backupConfigFile(filePath) {
|
|
1008
1986
|
const backupPath = `${filePath}.bak-${formatJetBrainsBackupTimestamp()}`;
|
|
1009
|
-
await
|
|
1987
|
+
await fs6.copyFile(filePath, backupPath);
|
|
1010
1988
|
return backupPath;
|
|
1011
1989
|
}
|
|
1012
1990
|
async function repairZeroByteConfigFile(filePath, dryRun) {
|
|
@@ -1017,7 +1995,7 @@ async function repairZeroByteConfigFile(filePath, dryRun) {
|
|
|
1017
1995
|
return { repaired: true, backupPath: `${filePath}.bak-<timestamp>` };
|
|
1018
1996
|
}
|
|
1019
1997
|
const backupPath = await backupConfigFile(filePath);
|
|
1020
|
-
await
|
|
1998
|
+
await fs6.unlink(filePath);
|
|
1021
1999
|
return { repaired: true, backupPath };
|
|
1022
2000
|
}
|
|
1023
2001
|
function configHasMemoraone(parsed) {
|
|
@@ -1048,24 +2026,24 @@ async function removeMemoraoneFromProjectConfig(options) {
|
|
|
1048
2026
|
delete mcpServers.memoraone;
|
|
1049
2027
|
const hasOtherServers = Object.keys(mcpServers).length > 0;
|
|
1050
2028
|
if (!hasOtherServers) {
|
|
1051
|
-
await
|
|
2029
|
+
await fs6.unlink(configPath);
|
|
1052
2030
|
return { changed: true, backupPath };
|
|
1053
2031
|
}
|
|
1054
2032
|
const next = { ...parsed, mcpServers };
|
|
1055
|
-
await
|
|
1056
|
-
await
|
|
2033
|
+
await fs6.mkdir(path8.dirname(configPath), { recursive: true });
|
|
2034
|
+
await fs6.writeFile(configPath, JSON.stringify(next, null, 2) + "\n", "utf8");
|
|
1057
2035
|
return { changed: true, backupPath };
|
|
1058
2036
|
}
|
|
1059
2037
|
async function resolveLocalCliPathAsync() {
|
|
1060
|
-
const here = process.argv[1] ?
|
|
2038
|
+
const here = process.argv[1] ? path8.dirname(path8.resolve(process.argv[1])) : process.cwd();
|
|
1061
2039
|
const candidates = [
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
2040
|
+
path8.join(here, "cli.cjs"),
|
|
2041
|
+
path8.join(here, "..", "dist", "cli.cjs"),
|
|
2042
|
+
path8.join(here, "..", "..", "dist", "cli.cjs")
|
|
1065
2043
|
];
|
|
1066
2044
|
for (const candidate of candidates) {
|
|
1067
2045
|
if (await pathExists2(candidate)) {
|
|
1068
|
-
return
|
|
2046
|
+
return path8.resolve(candidate);
|
|
1069
2047
|
}
|
|
1070
2048
|
}
|
|
1071
2049
|
return null;
|
|
@@ -1107,7 +2085,7 @@ async function buildJetBrainsMemoraoneServer(options) {
|
|
|
1107
2085
|
async function verifyJetBrainsMcpHandshake(options) {
|
|
1108
2086
|
const timeoutMs = options.timeoutMs ?? 15e3;
|
|
1109
2087
|
const { server } = options;
|
|
1110
|
-
return new Promise((
|
|
2088
|
+
return new Promise((resolve8) => {
|
|
1111
2089
|
let settled = false;
|
|
1112
2090
|
const finish = (ok, detail) => {
|
|
1113
2091
|
if (settled) return;
|
|
@@ -1117,9 +2095,9 @@ async function verifyJetBrainsMcpHandshake(options) {
|
|
|
1117
2095
|
child.kill();
|
|
1118
2096
|
} catch {
|
|
1119
2097
|
}
|
|
1120
|
-
|
|
2098
|
+
resolve8({ ok, detail });
|
|
1121
2099
|
};
|
|
1122
|
-
const child = (0,
|
|
2100
|
+
const child = (0, import_node_child_process4.spawn)(server.command, [...server.args], {
|
|
1123
2101
|
env: { ...process.env, ...server.env },
|
|
1124
2102
|
stdio: ["pipe", "pipe", "pipe"]
|
|
1125
2103
|
});
|
|
@@ -1186,7 +2164,7 @@ async function verifyJetBrainsMcpHandshake(options) {
|
|
|
1186
2164
|
async function setupJetBrainsMcpConfig(options) {
|
|
1187
2165
|
const homeDir = options.homeDir ?? os3.homedir();
|
|
1188
2166
|
const globalPath = options.globalConfigPath ?? getJetBrainsGlobalMcpConfigPath(homeDir);
|
|
1189
|
-
const m1Path =
|
|
2167
|
+
const m1Path = path8.join(path8.resolve(options.repoRoot), "memoraone.m1");
|
|
1190
2168
|
const repairActions = [];
|
|
1191
2169
|
const allLocations = getKnownJetBrainsMcpConfigLocations(homeDir, options.repoRoot);
|
|
1192
2170
|
for (const location of allLocations) {
|
|
@@ -1241,7 +2219,7 @@ async function setupJetBrainsMcpConfig(options) {
|
|
|
1241
2219
|
path: globalPath,
|
|
1242
2220
|
backupPath: backupPath2
|
|
1243
2221
|
});
|
|
1244
|
-
await
|
|
2222
|
+
await fs6.unlink(globalPath);
|
|
1245
2223
|
existing = null;
|
|
1246
2224
|
}
|
|
1247
2225
|
}
|
|
@@ -1250,7 +2228,7 @@ async function setupJetBrainsMcpConfig(options) {
|
|
|
1250
2228
|
const body = JSON.stringify(merged, null, 2) + "\n";
|
|
1251
2229
|
if (existed && existing) {
|
|
1252
2230
|
const currentMemoraone = existing.mcpServers && typeof existing.mcpServers === "object" && !Array.isArray(existing.mcpServers) ? existing.mcpServers.memoraone : void 0;
|
|
1253
|
-
if (
|
|
2231
|
+
if (memoraoneServerMatches(currentMemoraone, memoraone)) {
|
|
1254
2232
|
repairActions.push({ type: "wrote-global-config", path: globalPath, outcome: "skipped" });
|
|
1255
2233
|
return { outcome: "skipped", repairActions, memoraone };
|
|
1256
2234
|
}
|
|
@@ -1264,9 +2242,9 @@ async function setupJetBrainsMcpConfig(options) {
|
|
|
1264
2242
|
if (existed) {
|
|
1265
2243
|
backupPath = await backupConfigFile(globalPath);
|
|
1266
2244
|
}
|
|
1267
|
-
await
|
|
1268
|
-
await
|
|
1269
|
-
const verifyRaw = await
|
|
2245
|
+
await fs6.mkdir(path8.dirname(globalPath), { recursive: true });
|
|
2246
|
+
await fs6.writeFile(globalPath, body, "utf8");
|
|
2247
|
+
const verifyRaw = await fs6.readFile(globalPath, "utf8");
|
|
1270
2248
|
const verifyParsed = JSON.parse(stripLeadingLineComments2(verifyRaw));
|
|
1271
2249
|
validateJetBrainsMcpConfig(verifyParsed, memoraone);
|
|
1272
2250
|
const outcome = existed ? "updated" : "created";
|
|
@@ -1336,15 +2314,15 @@ function buildMemoraoneMcpServer(ideType, command = "npx") {
|
|
|
1336
2314
|
};
|
|
1337
2315
|
}
|
|
1338
2316
|
function assertUnderRepoRoot(repoRoot, absPath) {
|
|
1339
|
-
const normRoot =
|
|
1340
|
-
const normPath =
|
|
1341
|
-
if (normPath !==
|
|
2317
|
+
const normRoot = path9.resolve(repoRoot) + path9.sep;
|
|
2318
|
+
const normPath = path9.resolve(absPath);
|
|
2319
|
+
if (normPath !== path9.resolve(repoRoot) && !normPath.startsWith(normRoot)) {
|
|
1342
2320
|
throw new Error(`[setup-ide-files] Refusing to write outside repo root: ${absPath}`);
|
|
1343
2321
|
}
|
|
1344
2322
|
}
|
|
1345
2323
|
async function pathExists3(filePath) {
|
|
1346
2324
|
try {
|
|
1347
|
-
await
|
|
2325
|
+
await fs7.access(filePath);
|
|
1348
2326
|
return true;
|
|
1349
2327
|
} catch {
|
|
1350
2328
|
return false;
|
|
@@ -1365,12 +2343,12 @@ ${GITIGNORE_MEMORAONE_ENTRY}
|
|
|
1365
2343
|
}
|
|
1366
2344
|
async function ensureGitignoreMemoraone(repoRoot, opts) {
|
|
1367
2345
|
if (opts.noGitignore) return "skipped";
|
|
1368
|
-
const abs =
|
|
2346
|
+
const abs = path9.join(repoRoot, ".gitignore");
|
|
1369
2347
|
assertUnderRepoRoot(repoRoot, abs);
|
|
1370
2348
|
let prior = "";
|
|
1371
2349
|
let existed = false;
|
|
1372
2350
|
try {
|
|
1373
|
-
prior = await
|
|
2351
|
+
prior = await fs7.readFile(abs, "utf8");
|
|
1374
2352
|
existed = true;
|
|
1375
2353
|
} catch (err) {
|
|
1376
2354
|
const code = err && typeof err === "object" && "code" in err ? err.code : void 0;
|
|
@@ -1381,22 +2359,22 @@ async function ensureGitignoreMemoraone(repoRoot, opts) {
|
|
|
1381
2359
|
const separator = existed && prior.length > 0 ? prior.endsWith("\n") ? "\n" : "\n\n" : "";
|
|
1382
2360
|
const next = (existed ? prior : "") + separator + block;
|
|
1383
2361
|
if (opts.dryRun) return existed ? "updated" : "created";
|
|
1384
|
-
await
|
|
2362
|
+
await fs7.writeFile(abs, next, "utf8");
|
|
1385
2363
|
return existed ? "updated" : "created";
|
|
1386
2364
|
}
|
|
1387
2365
|
async function findRepoRoot(startDir) {
|
|
1388
|
-
let current =
|
|
1389
|
-
const root =
|
|
2366
|
+
let current = path9.resolve(startDir);
|
|
2367
|
+
const root = path9.parse(current).root;
|
|
1390
2368
|
while (true) {
|
|
1391
|
-
const gitPath =
|
|
1392
|
-
const m1Path =
|
|
2369
|
+
const gitPath = path9.join(current, ".git");
|
|
2370
|
+
const m1Path = path9.join(current, "memoraone.m1");
|
|
1393
2371
|
if (await pathExists3(gitPath) || await pathExists3(m1Path)) {
|
|
1394
2372
|
return current;
|
|
1395
2373
|
}
|
|
1396
2374
|
if (current === root) {
|
|
1397
2375
|
return null;
|
|
1398
2376
|
}
|
|
1399
|
-
current =
|
|
2377
|
+
current = path9.dirname(current);
|
|
1400
2378
|
}
|
|
1401
2379
|
}
|
|
1402
2380
|
function stripLeadingLineComments3(text) {
|
|
@@ -1407,7 +2385,7 @@ function cursorRuleBody() {
|
|
|
1407
2385
|
|
|
1408
2386
|
## MemoraOne MCP (IDE agent only)
|
|
1409
2387
|
|
|
1410
|
-
This repository uses **MemoraOne** via the MCP server named **
|
|
2388
|
+
This repository uses **MemoraOne** via the MCP server named **memoraone** (repo-level .cursor/mcp.json). This guidance applies to the **IDE coding agent** only \u2014 not the MemoraOne Studio runtime path.
|
|
1411
2389
|
|
|
1412
2390
|
### Tools
|
|
1413
2391
|
|
|
@@ -1449,43 +2427,47 @@ function buildVscodeMcpJsonBody(existing) {
|
|
|
1449
2427
|
const merged = { ...base, servers };
|
|
1450
2428
|
return mcpJsonHeader() + JSON.stringify(merged, null, 2) + "\n";
|
|
1451
2429
|
}
|
|
2430
|
+
function buildCursorMcpJsonBody(existing, npxPath, repoRoot) {
|
|
2431
|
+
const merged = mergeCursorRepoMcpConfigObject(existing, npxPath, repoRoot);
|
|
2432
|
+
return mcpJsonHeader() + JSON.stringify(merged, null, 2) + "\n";
|
|
2433
|
+
}
|
|
1452
2434
|
async function writeManagedMarkdown(repoRoot, relPath, fullContent, opts) {
|
|
1453
|
-
const abs =
|
|
2435
|
+
const abs = path9.join(repoRoot, relPath);
|
|
1454
2436
|
assertUnderRepoRoot(repoRoot, abs);
|
|
1455
2437
|
let prior = "";
|
|
1456
2438
|
let existed = false;
|
|
1457
2439
|
try {
|
|
1458
|
-
prior = await
|
|
2440
|
+
prior = await fs7.readFile(abs, "utf8");
|
|
1459
2441
|
existed = true;
|
|
1460
2442
|
} catch (err) {
|
|
1461
2443
|
if (err?.code !== "ENOENT") throw err;
|
|
1462
2444
|
}
|
|
1463
2445
|
if (!existed) {
|
|
1464
2446
|
if (opts.dryRun) return "created";
|
|
1465
|
-
await
|
|
1466
|
-
await
|
|
2447
|
+
await fs7.mkdir(path9.dirname(abs), { recursive: true });
|
|
2448
|
+
await fs7.writeFile(abs, fullContent, "utf8");
|
|
1467
2449
|
return "created";
|
|
1468
2450
|
}
|
|
1469
2451
|
if (prior.includes(MANAGED_MARKER)) {
|
|
1470
2452
|
if (prior === fullContent) return "skipped";
|
|
1471
2453
|
if (opts.dryRun) return "updated";
|
|
1472
|
-
await
|
|
1473
|
-
await
|
|
2454
|
+
await fs7.mkdir(path9.dirname(abs), { recursive: true });
|
|
2455
|
+
await fs7.writeFile(abs, fullContent, "utf8");
|
|
1474
2456
|
return "updated";
|
|
1475
2457
|
}
|
|
1476
2458
|
if (!opts.force) return "skipped-untracked";
|
|
1477
2459
|
if (opts.dryRun) return "updated";
|
|
1478
|
-
await
|
|
1479
|
-
await
|
|
2460
|
+
await fs7.mkdir(path9.dirname(abs), { recursive: true });
|
|
2461
|
+
await fs7.writeFile(abs, fullContent, "utf8");
|
|
1480
2462
|
return "updated";
|
|
1481
2463
|
}
|
|
1482
2464
|
async function writeIdeMcpJson(repoRoot, relPath, buildBody, opts) {
|
|
1483
|
-
const abs =
|
|
2465
|
+
const abs = path9.join(repoRoot, relPath);
|
|
1484
2466
|
assertUnderRepoRoot(repoRoot, abs);
|
|
1485
2467
|
let raw = "";
|
|
1486
2468
|
let existed = false;
|
|
1487
2469
|
try {
|
|
1488
|
-
raw = await
|
|
2470
|
+
raw = await fs7.readFile(abs, "utf8");
|
|
1489
2471
|
existed = true;
|
|
1490
2472
|
} catch (err) {
|
|
1491
2473
|
if (err?.code !== "ENOENT") throw err;
|
|
@@ -1493,8 +2475,8 @@ async function writeIdeMcpJson(repoRoot, relPath, buildBody, opts) {
|
|
|
1493
2475
|
if (!existed) {
|
|
1494
2476
|
const body = buildBody(null);
|
|
1495
2477
|
if (opts.dryRun) return "created";
|
|
1496
|
-
await
|
|
1497
|
-
await
|
|
2478
|
+
await fs7.mkdir(path9.dirname(abs), { recursive: true });
|
|
2479
|
+
await fs7.writeFile(abs, body, "utf8");
|
|
1498
2480
|
return "created";
|
|
1499
2481
|
}
|
|
1500
2482
|
const managed = raw.includes(MANAGED_MARKER);
|
|
@@ -1509,8 +2491,8 @@ async function writeIdeMcpJson(repoRoot, relPath, buildBody, opts) {
|
|
|
1509
2491
|
const next = buildBody(parsed);
|
|
1510
2492
|
if (managed && next === raw) return "skipped";
|
|
1511
2493
|
if (opts.dryRun) return "updated";
|
|
1512
|
-
await
|
|
1513
|
-
await
|
|
2494
|
+
await fs7.mkdir(path9.dirname(abs), { recursive: true });
|
|
2495
|
+
await fs7.writeFile(abs, next, "utf8");
|
|
1514
2496
|
return "updated";
|
|
1515
2497
|
}
|
|
1516
2498
|
function parseSetupIdeFlags(argv) {
|
|
@@ -1567,9 +2549,144 @@ function summarizeOutcomes(outcomes) {
|
|
|
1567
2549
|
}
|
|
1568
2550
|
console.log(lines.join("\n"));
|
|
1569
2551
|
}
|
|
2552
|
+
function ideTypesFromSetupTargets(targets) {
|
|
2553
|
+
const ides = [];
|
|
2554
|
+
if (targets.cursor) ides.push("cursor");
|
|
2555
|
+
if (targets.vscode) ides.push("copilot-vscode");
|
|
2556
|
+
if (targets.jetbrains) ides.push("jetbrains");
|
|
2557
|
+
return ides;
|
|
2558
|
+
}
|
|
2559
|
+
function setupTargetsAllIdes(targets) {
|
|
2560
|
+
return targets.cursor && targets.vscode && targets.jetbrains;
|
|
2561
|
+
}
|
|
2562
|
+
function aggregateCleanupResults(results) {
|
|
2563
|
+
const killedPids = /* @__PURE__ */ new Set();
|
|
2564
|
+
const removedSockets = /* @__PURE__ */ new Set();
|
|
2565
|
+
const skippedUnrelated = /* @__PURE__ */ new Map();
|
|
2566
|
+
let foundDaemonCount = 0;
|
|
2567
|
+
let error;
|
|
2568
|
+
for (const result of results) {
|
|
2569
|
+
if (result.error) error = result.error;
|
|
2570
|
+
for (const pid of result.killedPids) {
|
|
2571
|
+
killedPids.add(pid);
|
|
2572
|
+
foundDaemonCount += 1;
|
|
2573
|
+
}
|
|
2574
|
+
for (const socketPath of result.removedSockets) {
|
|
2575
|
+
removedSockets.add(socketPath);
|
|
2576
|
+
}
|
|
2577
|
+
for (const proc of result.skippedProcesses) {
|
|
2578
|
+
if (proc.projectId !== result.projectId) {
|
|
2579
|
+
skippedUnrelated.set(proc.pid, proc);
|
|
2580
|
+
}
|
|
2581
|
+
}
|
|
2582
|
+
}
|
|
2583
|
+
return {
|
|
2584
|
+
foundDaemonCount,
|
|
2585
|
+
stoppedDaemonCount: killedPids.size,
|
|
2586
|
+
removedSocketCount: removedSockets.size,
|
|
2587
|
+
skippedUnrelatedDaemonCount: skippedUnrelated.size,
|
|
2588
|
+
error
|
|
2589
|
+
};
|
|
2590
|
+
}
|
|
2591
|
+
function logSetupIdeCleanupSummary(cleanup) {
|
|
2592
|
+
if (cleanup.skipped) return;
|
|
2593
|
+
console.log(`[setup-ide-files] Project id: ${cleanup.projectId}`);
|
|
2594
|
+
if (cleanup.foundDaemonCount > 0) {
|
|
2595
|
+
console.log(`[setup-ide-files] Found ${cleanup.foundDaemonCount} stale daemon(s)`);
|
|
2596
|
+
if (cleanup.dryRun) {
|
|
2597
|
+
console.log(`[setup-ide-files] Would stop ${cleanup.foundDaemonCount} stale daemon(s)`);
|
|
2598
|
+
} else if (cleanup.stoppedDaemonCount > 0) {
|
|
2599
|
+
console.log(`[setup-ide-files] Stopped ${cleanup.stoppedDaemonCount} stale daemon(s)`);
|
|
2600
|
+
}
|
|
2601
|
+
} else {
|
|
2602
|
+
console.log("[setup-ide-files] No stale daemons found for this project and IDE target(s).");
|
|
2603
|
+
}
|
|
2604
|
+
if (cleanup.removedSocketCount > 0) {
|
|
2605
|
+
if (cleanup.dryRun) {
|
|
2606
|
+
console.log(`[setup-ide-files] Would remove ${cleanup.removedSocketCount} stale socket(s)`);
|
|
2607
|
+
} else {
|
|
2608
|
+
console.log(`[setup-ide-files] Removed ${cleanup.removedSocketCount} stale socket(s)`);
|
|
2609
|
+
}
|
|
2610
|
+
}
|
|
2611
|
+
if (cleanup.skippedUnrelatedDaemonCount > 0) {
|
|
2612
|
+
console.log(
|
|
2613
|
+
`[setup-ide-files] Skipped ${cleanup.skippedUnrelatedDaemonCount} unrelated project daemon(s)`
|
|
2614
|
+
);
|
|
2615
|
+
}
|
|
2616
|
+
}
|
|
2617
|
+
async function runSetupIdeDaemonCleanup(opts) {
|
|
2618
|
+
const ides = ideTypesFromSetupTargets(opts.targets);
|
|
2619
|
+
if (ides.length === 0) {
|
|
2620
|
+
return {
|
|
2621
|
+
skipped: true,
|
|
2622
|
+
skipReason: "no-targets",
|
|
2623
|
+
foundDaemonCount: 0,
|
|
2624
|
+
stoppedDaemonCount: 0,
|
|
2625
|
+
removedSocketCount: 0,
|
|
2626
|
+
skippedUnrelatedDaemonCount: 0,
|
|
2627
|
+
dryRun: opts.dryRun
|
|
2628
|
+
};
|
|
2629
|
+
}
|
|
2630
|
+
const target = await resolveCleanupTarget(opts.repoRoot);
|
|
2631
|
+
if ("error" in target) {
|
|
2632
|
+
return {
|
|
2633
|
+
skipped: true,
|
|
2634
|
+
skipReason: "no-m1",
|
|
2635
|
+
foundDaemonCount: 0,
|
|
2636
|
+
stoppedDaemonCount: 0,
|
|
2637
|
+
removedSocketCount: 0,
|
|
2638
|
+
skippedUnrelatedDaemonCount: 0,
|
|
2639
|
+
dryRun: opts.dryRun
|
|
2640
|
+
};
|
|
2641
|
+
}
|
|
2642
|
+
const baseCleanupOpts = {
|
|
2643
|
+
cwd: opts.repoRoot,
|
|
2644
|
+
dryRun: opts.dryRun,
|
|
2645
|
+
allProjects: false,
|
|
2646
|
+
assumeYes: true,
|
|
2647
|
+
projectId: target.projectId,
|
|
2648
|
+
quiet: true,
|
|
2649
|
+
listProcesses: opts.listProcesses,
|
|
2650
|
+
listSocketPaths: opts.listSocketPaths,
|
|
2651
|
+
killProcess: opts.killProcess,
|
|
2652
|
+
removeSocket: opts.removeSocket
|
|
2653
|
+
};
|
|
2654
|
+
const results = [];
|
|
2655
|
+
if (setupTargetsAllIdes(opts.targets)) {
|
|
2656
|
+
results.push(await runCleanup(baseCleanupOpts));
|
|
2657
|
+
} else {
|
|
2658
|
+
for (const ide of ides) {
|
|
2659
|
+
results.push(await runCleanup({ ...baseCleanupOpts, ide }));
|
|
2660
|
+
}
|
|
2661
|
+
}
|
|
2662
|
+
const aggregated = aggregateCleanupResults(results);
|
|
2663
|
+
const exitError = results.find((r) => r.exitCode !== 0)?.error ?? aggregated.error;
|
|
2664
|
+
return {
|
|
2665
|
+
skipped: false,
|
|
2666
|
+
projectId: target.projectId,
|
|
2667
|
+
foundDaemonCount: aggregated.foundDaemonCount,
|
|
2668
|
+
stoppedDaemonCount: opts.dryRun ? 0 : aggregated.stoppedDaemonCount,
|
|
2669
|
+
removedSocketCount: aggregated.removedSocketCount,
|
|
2670
|
+
skippedUnrelatedDaemonCount: aggregated.skippedUnrelatedDaemonCount,
|
|
2671
|
+
dryRun: opts.dryRun,
|
|
2672
|
+
error: exitError
|
|
2673
|
+
};
|
|
2674
|
+
}
|
|
2675
|
+
function restartIdeInstruction(targets) {
|
|
2676
|
+
const names = [];
|
|
2677
|
+
if (targets.cursor) names.push("Cursor");
|
|
2678
|
+
if (targets.vscode) names.push("VS Code");
|
|
2679
|
+
if (targets.jetbrains) names.push("JetBrains IDE");
|
|
2680
|
+
if (names.length === 0) return "Fully quit your IDE and reopen this repo for MCP changes to take effect.";
|
|
2681
|
+
if (names.length === 1) {
|
|
2682
|
+
return `Fully quit ${names[0]} and reopen this repo for MCP changes to take effect.`;
|
|
2683
|
+
}
|
|
2684
|
+
const last = names.pop();
|
|
2685
|
+
return `Fully quit ${names.join(", ")} and ${last}, then reopen this repo for MCP changes to take effect.`;
|
|
2686
|
+
}
|
|
1570
2687
|
async function runSetupIdeFiles(o) {
|
|
1571
2688
|
const outcomes = {};
|
|
1572
|
-
let
|
|
2689
|
+
let cursorMcp;
|
|
1573
2690
|
let jetbrainsMcp;
|
|
1574
2691
|
const repoRoot = await findRepoRoot(o.cwd);
|
|
1575
2692
|
if (!repoRoot) {
|
|
@@ -1580,6 +2697,27 @@ async function runSetupIdeFiles(o) {
|
|
|
1580
2697
|
error: "[setup-ide-files] No repo root found (looked for .git or memoraone.m1)."
|
|
1581
2698
|
};
|
|
1582
2699
|
}
|
|
2700
|
+
let daemonCleanup;
|
|
2701
|
+
if (!o.skipDaemonCleanup) {
|
|
2702
|
+
daemonCleanup = await runSetupIdeDaemonCleanup({
|
|
2703
|
+
repoRoot,
|
|
2704
|
+
targets: o.targets,
|
|
2705
|
+
dryRun: o.dryRun,
|
|
2706
|
+
listProcesses: o.listDaemonProcesses,
|
|
2707
|
+
listSocketPaths: o.listCleanupSocketPaths,
|
|
2708
|
+
killProcess: o.killDaemonProcess,
|
|
2709
|
+
removeSocket: o.removeCleanupSocket
|
|
2710
|
+
});
|
|
2711
|
+
if (daemonCleanup.error && !daemonCleanup.skipped) {
|
|
2712
|
+
return {
|
|
2713
|
+
exitCode: 1,
|
|
2714
|
+
repoRoot,
|
|
2715
|
+
outcomes,
|
|
2716
|
+
daemonCleanup,
|
|
2717
|
+
error: `[setup-ide-files] Daemon cleanup failed: ${daemonCleanup.error}`
|
|
2718
|
+
};
|
|
2719
|
+
}
|
|
2720
|
+
}
|
|
1583
2721
|
outcomes[".gitignore"] = await ensureGitignoreMemoraone(repoRoot, {
|
|
1584
2722
|
dryRun: o.dryRun,
|
|
1585
2723
|
noGitignore: o.noGitignore ?? false
|
|
@@ -1590,19 +2728,6 @@ description: MemoraOne MCP \u2014 IDE agent instructions
|
|
|
1590
2728
|
|
|
1591
2729
|
` + cursorRuleBody();
|
|
1592
2730
|
if (o.targets.cursor) {
|
|
1593
|
-
const detection = await detectCursorGlobalMcpConfig({
|
|
1594
|
-
homeDir: o.homeDir,
|
|
1595
|
-
explicitPath: o.cursorGlobalMcpConfigPath
|
|
1596
|
-
});
|
|
1597
|
-
if (!detection.ok) {
|
|
1598
|
-
return {
|
|
1599
|
-
exitCode: 1,
|
|
1600
|
-
repoRoot,
|
|
1601
|
-
outcomes,
|
|
1602
|
-
error: `${detection.error}
|
|
1603
|
-
${detection.candidates.join("\n ")}`
|
|
1604
|
-
};
|
|
1605
|
-
}
|
|
1606
2731
|
let npxPath;
|
|
1607
2732
|
if (o.npxPathOverride !== void 0) {
|
|
1608
2733
|
npxPath = o.npxPathOverride;
|
|
@@ -1614,7 +2739,7 @@ description: MemoraOne MCP \u2014 IDE agent instructions
|
|
|
1614
2739
|
exitCode: 1,
|
|
1615
2740
|
repoRoot,
|
|
1616
2741
|
outcomes,
|
|
1617
|
-
error: "[setup-ide-files] Could not resolve a working npx executable. Install Node.js/npm or ensure npx is on PATH before configuring Cursor
|
|
2742
|
+
error: "[setup-ide-files] Could not resolve a working npx executable. Install Node.js/npm or ensure npx is on PATH before configuring Cursor MCP."
|
|
1618
2743
|
};
|
|
1619
2744
|
}
|
|
1620
2745
|
outcomes[".cursor/rules/memoraone-mcp.mdc"] = await writeManagedMarkdown(
|
|
@@ -1623,29 +2748,58 @@ description: MemoraOne MCP \u2014 IDE agent instructions
|
|
|
1623
2748
|
cursorContent,
|
|
1624
2749
|
{ force: o.force, dryRun: o.dryRun }
|
|
1625
2750
|
);
|
|
2751
|
+
outcomes[".cursor/mcp.json"] = await writeIdeMcpJson(
|
|
2752
|
+
repoRoot,
|
|
2753
|
+
".cursor/mcp.json",
|
|
2754
|
+
(existing) => buildCursorMcpJsonBody(existing, npxPath, repoRoot),
|
|
2755
|
+
{ force: o.force, dryRun: o.dryRun }
|
|
2756
|
+
);
|
|
2757
|
+
const repoConfigPath = getCursorRepoMcpConfigPath(repoRoot);
|
|
2758
|
+
const repoOutcome = outcomes[".cursor/mcp.json"] ?? "skipped";
|
|
2759
|
+
let globalConfigPath;
|
|
2760
|
+
let globalMemoraoneRemoved = false;
|
|
2761
|
+
let globalBackupPath;
|
|
2762
|
+
const globalDetection = await detectCursorGlobalMcpConfig({
|
|
2763
|
+
homeDir: o.homeDir,
|
|
2764
|
+
explicitPath: o.cursorGlobalMcpConfigPath
|
|
2765
|
+
});
|
|
2766
|
+
if (!globalDetection.ok) {
|
|
2767
|
+
return {
|
|
2768
|
+
exitCode: 1,
|
|
2769
|
+
repoRoot,
|
|
2770
|
+
outcomes,
|
|
2771
|
+
error: `${globalDetection.error}
|
|
2772
|
+
${globalDetection.candidates.join("\n ")}`
|
|
2773
|
+
};
|
|
2774
|
+
}
|
|
2775
|
+
globalConfigPath = globalDetection.path;
|
|
1626
2776
|
try {
|
|
1627
|
-
const
|
|
1628
|
-
configPath:
|
|
1629
|
-
npxPath,
|
|
2777
|
+
const removal = await removeMemoraoneFromCursorGlobalConfig({
|
|
2778
|
+
configPath: globalDetection.path,
|
|
1630
2779
|
dryRun: o.dryRun
|
|
1631
2780
|
});
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
};
|
|
1638
|
-
outcomes[`cursor-global:${detection.path}`] = globalSetup.outcome;
|
|
2781
|
+
if (removal.changed) {
|
|
2782
|
+
globalMemoraoneRemoved = true;
|
|
2783
|
+
globalBackupPath = removal.backupPath;
|
|
2784
|
+
outcomes[`cursor-global-removed:${globalDetection.path}`] = o.dryRun ? "updated" : "updated";
|
|
2785
|
+
}
|
|
1639
2786
|
} catch (err) {
|
|
1640
2787
|
const message = err instanceof Error ? err.message : String(err);
|
|
1641
2788
|
return {
|
|
1642
2789
|
exitCode: 1,
|
|
1643
2790
|
repoRoot,
|
|
1644
2791
|
outcomes,
|
|
1645
|
-
cursorGlobalMcp: { configPath: detection.path, outcome: "skipped", npxPath },
|
|
1646
2792
|
error: message
|
|
1647
2793
|
};
|
|
1648
2794
|
}
|
|
2795
|
+
cursorMcp = {
|
|
2796
|
+
repoConfigPath,
|
|
2797
|
+
repoOutcome,
|
|
2798
|
+
npxPath,
|
|
2799
|
+
globalConfigPath,
|
|
2800
|
+
globalMemoraoneRemoved,
|
|
2801
|
+
globalBackupPath
|
|
2802
|
+
};
|
|
1649
2803
|
}
|
|
1650
2804
|
if (o.targets.vscode) {
|
|
1651
2805
|
outcomes[".vscode/mcp.json"] = await writeIdeMcpJson(
|
|
@@ -1701,12 +2855,12 @@ description: MemoraOne MCP \u2014 IDE agent instructions
|
|
|
1701
2855
|
exitCode: 1,
|
|
1702
2856
|
repoRoot,
|
|
1703
2857
|
outcomes,
|
|
1704
|
-
|
|
2858
|
+
cursorMcp,
|
|
1705
2859
|
error: message
|
|
1706
2860
|
};
|
|
1707
2861
|
}
|
|
1708
2862
|
}
|
|
1709
|
-
return { exitCode: 0, repoRoot, outcomes,
|
|
2863
|
+
return { exitCode: 0, repoRoot, outcomes, cursorMcp, jetbrainsMcp, daemonCleanup };
|
|
1710
2864
|
}
|
|
1711
2865
|
async function cliSetupIdeFiles(argv) {
|
|
1712
2866
|
const { targets, force, dryRun, noGitignore, cleanup, devMode, repair, unknown } = parseSetupIdeFlags(argv);
|
|
@@ -1734,8 +2888,11 @@ async function cliSetupIdeFiles(argv) {
|
|
|
1734
2888
|
if (result.repoRoot) {
|
|
1735
2889
|
console.log(`[setup-ide-files] Repo root: ${result.repoRoot}`);
|
|
1736
2890
|
}
|
|
1737
|
-
if (
|
|
1738
|
-
|
|
2891
|
+
if (result.daemonCleanup && !result.daemonCleanup.skipped) {
|
|
2892
|
+
logSetupIdeCleanupSummary(result.daemonCleanup);
|
|
2893
|
+
}
|
|
2894
|
+
if (targets.cursor && result.cursorMcp) {
|
|
2895
|
+
logCursorMcpCliSummary(result.cursorMcp, dryRun);
|
|
1739
2896
|
}
|
|
1740
2897
|
if (targets.jetbrains && result.jetbrainsMcp) {
|
|
1741
2898
|
logJetBrainsMcpCliSummary(result.jetbrainsMcp, dryRun);
|
|
@@ -1743,9 +2900,13 @@ async function cliSetupIdeFiles(argv) {
|
|
|
1743
2900
|
summarizeOutcomes(result.outcomes);
|
|
1744
2901
|
if (dryRun) {
|
|
1745
2902
|
console.log("[setup-ide-files] Dry run: no files written.");
|
|
2903
|
+
if (result.daemonCleanup && !result.daemonCleanup.skipped) {
|
|
2904
|
+
console.log("[setup-ide-files] Dry run: no daemons stopped, no sockets removed.");
|
|
2905
|
+
}
|
|
1746
2906
|
}
|
|
2907
|
+
console.log(`[setup-ide-files] ${restartIdeInstruction(targets)}`);
|
|
1747
2908
|
if (cleanup) {
|
|
1748
|
-
console.log("[setup-ide-files] Running project
|
|
2909
|
+
console.log("[setup-ide-files] Running additional full-project cleanup (--cleanup)...");
|
|
1749
2910
|
const cleanupResult = await runCleanup({
|
|
1750
2911
|
cwd: process.cwd(),
|
|
1751
2912
|
dryRun,
|
|
@@ -1756,13 +2917,6 @@ async function cliSetupIdeFiles(argv) {
|
|
|
1756
2917
|
console.error(`[setup-ide-files] cleanup failed: ${cleanupResult.error}`);
|
|
1757
2918
|
return cleanupResult.exitCode;
|
|
1758
2919
|
}
|
|
1759
|
-
} else {
|
|
1760
|
-
console.log(
|
|
1761
|
-
"[setup-ide-files] If Studio shows stale connections, run: npx -y @memoraone/mcp@latest cleanup --project-id <projectId>"
|
|
1762
|
-
);
|
|
1763
|
-
console.log(
|
|
1764
|
-
"[setup-ide-files] From this repo (uses memoraone.m1): npx -y @memoraone/mcp@latest cleanup"
|
|
1765
|
-
);
|
|
1766
2920
|
}
|
|
1767
2921
|
return 0;
|
|
1768
2922
|
}
|
|
@@ -1799,92 +2953,9 @@ if (args[0] === "cleanup") {
|
|
|
1799
2953
|
process.exit(1);
|
|
1800
2954
|
});
|
|
1801
2955
|
} else {
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
const parts = [];
|
|
1805
|
-
if (raw !== void 0 && raw.trim() !== "") {
|
|
1806
|
-
for (const p of raw.split(path7.delimiter).map((s) => s.trim()).filter(Boolean)) {
|
|
1807
|
-
parts.push(path7.resolve(p));
|
|
1808
|
-
}
|
|
1809
|
-
}
|
|
1810
|
-
parts.push(process.cwd());
|
|
1811
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1812
|
-
const deduped = [];
|
|
1813
|
-
for (const p of parts) {
|
|
1814
|
-
if (!seen.has(p)) {
|
|
1815
|
-
seen.add(p);
|
|
1816
|
-
deduped.push(p);
|
|
1817
|
-
}
|
|
1818
|
-
}
|
|
1819
|
-
return deduped;
|
|
1820
|
-
}, connectWithRetry = function(socketPath) {
|
|
1821
|
-
return new Promise((resolve7, reject) => {
|
|
1822
|
-
const tryConnect = (attempt) => {
|
|
1823
|
-
const socket = net.connect(socketPath, () => {
|
|
1824
|
-
resolve7(socket);
|
|
1825
|
-
});
|
|
1826
|
-
socket.on("error", (err) => {
|
|
1827
|
-
if (attempt >= MAX_RETRIES) {
|
|
1828
|
-
reject(err);
|
|
1829
|
-
return;
|
|
1830
|
-
}
|
|
1831
|
-
log(`connect attempt ${attempt + 1} failed, retrying in ${RETRY_DELAY_MS}ms: ${String(err)}`);
|
|
1832
|
-
setTimeout(() => tryConnect(attempt + 1), RETRY_DELAY_MS);
|
|
1833
|
-
});
|
|
1834
|
-
};
|
|
1835
|
-
tryConnect(0);
|
|
1836
|
-
});
|
|
1837
|
-
};
|
|
1838
|
-
const log = (msg) => {
|
|
1839
|
-
process.stderr.write(`[memoraone-mcp][bridge] ${msg}
|
|
2956
|
+
runBridgeProxy({ cliPath: process.argv[1] }).catch((err) => {
|
|
2957
|
+
process.stderr.write(`[memoraone-mcp][bridge] fatal: ${String(err)}
|
|
1840
2958
|
`);
|
|
1841
|
-
};
|
|
1842
|
-
const MAX_RETRIES = 5;
|
|
1843
|
-
const RETRY_DELAY_MS = 200;
|
|
1844
|
-
async function resolveBinding() {
|
|
1845
|
-
return resolveAuthoritativeBinding(getWorkspaceRootCandidates());
|
|
1846
|
-
}
|
|
1847
|
-
async function runBridge() {
|
|
1848
|
-
ensureBaseDir();
|
|
1849
|
-
const binding = await resolveBinding();
|
|
1850
|
-
const socketPath = getDaemonSocketPath(binding.projectId);
|
|
1851
|
-
const environmentLog = binding.environment !== void 0 ? ` environment=${binding.environment}` : "";
|
|
1852
|
-
log(
|
|
1853
|
-
`authoritative binding project=${binding.projectId} workspace=${binding.workspaceRoot} m1=${binding.m1Path} source=${binding.bindingSource} apiKeySource=${binding.apiKeySource}${environmentLog}`
|
|
1854
|
-
);
|
|
1855
|
-
let socket;
|
|
1856
|
-
try {
|
|
1857
|
-
socket = await connectWithRetry(socketPath);
|
|
1858
|
-
} catch {
|
|
1859
|
-
log("daemon not running, spawning...");
|
|
1860
|
-
const child = (0, import_node_child_process4.spawn)(
|
|
1861
|
-
process.execPath,
|
|
1862
|
-
buildDaemonSpawnArgs(process.argv[1], binding.projectId),
|
|
1863
|
-
{
|
|
1864
|
-
detached: true,
|
|
1865
|
-
stdio: "ignore",
|
|
1866
|
-
env: {
|
|
1867
|
-
...process.env,
|
|
1868
|
-
MEMORAONE_DAEMON_BINDING_B64: encodeResolvedBinding(binding)
|
|
1869
|
-
}
|
|
1870
|
-
}
|
|
1871
|
-
);
|
|
1872
|
-
child.unref();
|
|
1873
|
-
await new Promise((r) => setTimeout(r, RETRY_DELAY_MS));
|
|
1874
|
-
socket = await connectWithRetry(socketPath);
|
|
1875
|
-
}
|
|
1876
|
-
log("bridge connected");
|
|
1877
|
-
log("forwarding active");
|
|
1878
|
-
process.stdin.pipe(socket);
|
|
1879
|
-
socket.pipe(process.stdout);
|
|
1880
|
-
socket.on("close", () => process.exit(0));
|
|
1881
|
-
socket.on("error", (err) => {
|
|
1882
|
-
log(`socket error: ${String(err)}`);
|
|
1883
|
-
process.exit(1);
|
|
1884
|
-
});
|
|
1885
|
-
}
|
|
1886
|
-
runBridge().catch((err) => {
|
|
1887
|
-
log(`fatal: ${String(err)}`);
|
|
1888
2959
|
process.exit(1);
|
|
1889
2960
|
});
|
|
1890
2961
|
}
|