@memoraone/mcp 0.1.35 → 0.1.37
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 +2330 -637
- package/dist/daemon.cjs +1730 -1114
- package/dist/index.cjs +1654 -959
- package/package.json +12 -10
package/dist/cli.cjs
CHANGED
|
@@ -30,7 +30,7 @@ var require_package = __commonJS({
|
|
|
30
30
|
"package.json"(exports2, module2) {
|
|
31
31
|
module2.exports = {
|
|
32
32
|
name: "@memoraone/mcp",
|
|
33
|
-
version: "0.1.
|
|
33
|
+
version: "0.1.37",
|
|
34
34
|
type: "module",
|
|
35
35
|
main: "dist/index.cjs",
|
|
36
36
|
bin: {
|
|
@@ -54,6 +54,7 @@ var require_package = __commonJS({
|
|
|
54
54
|
},
|
|
55
55
|
dependencies: {
|
|
56
56
|
"@modelcontextprotocol/sdk": "^1.25.1",
|
|
57
|
+
"@napi-rs/keyring": "1.3.0",
|
|
57
58
|
dotenv: "^16.4.5",
|
|
58
59
|
zod: "^4.0.0"
|
|
59
60
|
},
|
|
@@ -69,15 +70,15 @@ var require_package = __commonJS({
|
|
|
69
70
|
// src/bridgeProxy.ts
|
|
70
71
|
var net = __toESM(require("net"), 1);
|
|
71
72
|
var readline3 = __toESM(require("readline"), 1);
|
|
72
|
-
var
|
|
73
|
+
var import_node_child_process4 = require("child_process");
|
|
73
74
|
|
|
74
75
|
// src/bindingIdentity.ts
|
|
75
76
|
var crypto = __toESM(require("crypto"), 1);
|
|
76
77
|
var path = __toESM(require("path"), 1);
|
|
77
78
|
var BINDING_SOCKET_HASH_LENGTH = 16;
|
|
78
|
-
function hashBindingIdentity(
|
|
79
|
+
function hashBindingIdentity(repositoryBindingId, workspaceRoot, ideType) {
|
|
79
80
|
const input2 = [
|
|
80
|
-
|
|
81
|
+
repositoryBindingId.trim(),
|
|
81
82
|
path.resolve(workspaceRoot),
|
|
82
83
|
ideType
|
|
83
84
|
].join("|");
|
|
@@ -86,7 +87,7 @@ function hashBindingIdentity(projectId, workspaceRoot, ideType) {
|
|
|
86
87
|
function bindingsMatch(a, b) {
|
|
87
88
|
const envA = a.environment ?? void 0;
|
|
88
89
|
const envB = b.environment ?? void 0;
|
|
89
|
-
return a.projectId.trim().toLowerCase() === b.projectId.trim().toLowerCase() && path.resolve(a.workspaceRoot) === path.resolve(b.workspaceRoot) &&
|
|
90
|
+
return a.repositoryBindingId === b.repositoryBindingId && a.projectId.trim().toLowerCase() === b.projectId.trim().toLowerCase() && path.resolve(a.workspaceRoot) === path.resolve(b.workspaceRoot) && (a.installationPublicId ?? void 0) === (b.installationPublicId ?? void 0) && envA === envB && a.status === b.status;
|
|
90
91
|
}
|
|
91
92
|
function formatMissingInitializeWorkspaceError(options) {
|
|
92
93
|
const lines = [
|
|
@@ -114,203 +115,1084 @@ function formatMissingInitializeWorkspaceError(options) {
|
|
|
114
115
|
}
|
|
115
116
|
|
|
116
117
|
// src/bindingSidecar.ts
|
|
117
|
-
var
|
|
118
|
-
var
|
|
118
|
+
var fs8 = __toESM(require("fs"), 1);
|
|
119
|
+
var path10 = __toESM(require("path"), 1);
|
|
119
120
|
|
|
120
121
|
// src/projectBinding.ts
|
|
122
|
+
var fs6 = __toESM(require("fs/promises"), 1);
|
|
123
|
+
var path8 = __toESM(require("path"), 1);
|
|
124
|
+
|
|
125
|
+
// src/localState/resolveLocalBinding.ts
|
|
126
|
+
var fs5 = __toESM(require("fs/promises"), 1);
|
|
127
|
+
var path7 = __toESM(require("path"), 1);
|
|
128
|
+
|
|
129
|
+
// src/localState/bindingStore.ts
|
|
130
|
+
var fs3 = __toESM(require("fs/promises"), 1);
|
|
131
|
+
var path4 = __toESM(require("path"), 1);
|
|
132
|
+
|
|
133
|
+
// src/localState/atomicFs.ts
|
|
121
134
|
var fs = __toESM(require("fs/promises"), 1);
|
|
122
135
|
var path2 = __toESM(require("path"), 1);
|
|
123
|
-
var
|
|
124
|
-
var
|
|
125
|
-
|
|
126
|
-
|
|
136
|
+
var import_node_crypto = require("crypto");
|
|
137
|
+
var STATE_DIR_MODE = 448;
|
|
138
|
+
var STATE_FILE_MODE = 384;
|
|
139
|
+
async function ensurePrivateDir(dirPath) {
|
|
140
|
+
await fs.mkdir(dirPath, { recursive: true, mode: STATE_DIR_MODE });
|
|
141
|
+
try {
|
|
142
|
+
await fs.chmod(dirPath, STATE_DIR_MODE);
|
|
143
|
+
} catch {
|
|
144
|
+
}
|
|
127
145
|
}
|
|
128
|
-
function
|
|
129
|
-
|
|
130
|
-
|
|
146
|
+
async function writeFileAtomic(filePath, content, options = {}) {
|
|
147
|
+
const mode = options.mode ?? STATE_FILE_MODE;
|
|
148
|
+
const dir = path2.dirname(filePath);
|
|
149
|
+
await ensurePrivateDir(dir);
|
|
150
|
+
const tmpPath = path2.join(
|
|
151
|
+
dir,
|
|
152
|
+
`.${path2.basename(filePath)}.${process.pid}.${(0, import_node_crypto.randomBytes)(8).toString("hex")}.tmp`
|
|
153
|
+
);
|
|
154
|
+
try {
|
|
155
|
+
await fs.writeFile(tmpPath, content, { encoding: "utf8", mode });
|
|
156
|
+
await fs.rename(tmpPath, filePath);
|
|
157
|
+
try {
|
|
158
|
+
await fs.chmod(filePath, mode);
|
|
159
|
+
} catch {
|
|
160
|
+
}
|
|
161
|
+
} catch (err) {
|
|
162
|
+
try {
|
|
163
|
+
await fs.unlink(tmpPath);
|
|
164
|
+
} catch {
|
|
165
|
+
}
|
|
166
|
+
throw err;
|
|
131
167
|
}
|
|
132
|
-
const trimmed = raw.trim();
|
|
133
|
-
return trimmed === "" ? void 0 : trimmed;
|
|
134
168
|
}
|
|
135
|
-
function
|
|
136
|
-
let parsed;
|
|
169
|
+
async function readJsonFile(filePath) {
|
|
137
170
|
try {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
171
|
+
const raw = await fs.readFile(filePath, "utf8");
|
|
172
|
+
return JSON.parse(raw);
|
|
173
|
+
} catch (err) {
|
|
174
|
+
if (err?.code === "ENOENT") {
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
if (err instanceof SyntaxError) {
|
|
178
|
+
throw new Error(`[memoraone-mcp] Corrupt JSON at ${filePath}`);
|
|
179
|
+
}
|
|
180
|
+
throw err;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
async function writeJsonAtomic(filePath, value, options = {}) {
|
|
184
|
+
await writeFileAtomic(filePath, `${JSON.stringify(value, null, 2)}
|
|
185
|
+
`, options);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// src/localState/repositoryBindingId.ts
|
|
189
|
+
var import_node_crypto2 = require("crypto");
|
|
190
|
+
var MRB_PREFIX = "mrb_";
|
|
191
|
+
var MRB_RE = /^mrb_[A-Za-z0-9_-]{43}$/;
|
|
192
|
+
function generateRepositoryBindingId(random = () => (0, import_node_crypto2.randomBytes)(32)) {
|
|
193
|
+
const bytes = random();
|
|
194
|
+
if (bytes.length !== 32) {
|
|
195
|
+
throw new Error("[memoraone-mcp] repository binding id requires exactly 32 random bytes");
|
|
141
196
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
197
|
+
return `${MRB_PREFIX}${bytes.toString("base64url")}`;
|
|
198
|
+
}
|
|
199
|
+
function isRepositoryBindingId(value) {
|
|
200
|
+
return MRB_RE.test(value);
|
|
201
|
+
}
|
|
202
|
+
function assertRepositoryBindingId(value) {
|
|
203
|
+
if (!isRepositoryBindingId(value)) {
|
|
204
|
+
throw new Error(`[memoraone-mcp] Invalid repository_binding_id: ${value}`);
|
|
145
205
|
}
|
|
146
|
-
|
|
147
|
-
|
|
206
|
+
return value;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// src/localState/bindingRecord.ts
|
|
210
|
+
var BINDING_RECORD_VERSION = 1;
|
|
211
|
+
var SECRET_KEYS = [
|
|
212
|
+
"accessToken",
|
|
213
|
+
"refreshToken",
|
|
214
|
+
"access_token",
|
|
215
|
+
"refresh_token",
|
|
216
|
+
"apiKey",
|
|
217
|
+
"api_key",
|
|
218
|
+
"MEMORAONE_API_KEY",
|
|
219
|
+
"clientRedeemKey",
|
|
220
|
+
"client_redeem_key",
|
|
221
|
+
"clientRefreshKey",
|
|
222
|
+
"client_refresh_key",
|
|
223
|
+
"code",
|
|
224
|
+
"connectCode",
|
|
225
|
+
"connect_code"
|
|
226
|
+
];
|
|
227
|
+
function assertNoSecretsInBindingRecord(record) {
|
|
228
|
+
for (const key of SECRET_KEYS) {
|
|
229
|
+
if (key in record && record[key] != null && record[key] !== "") {
|
|
230
|
+
throw new Error(`[memoraone-mcp] Binding record must not contain secret field: ${key}`);
|
|
231
|
+
}
|
|
148
232
|
}
|
|
149
|
-
const apiKeyRaw = parsed?.MEMORAONE_API_KEY ?? parsed?.api_key;
|
|
150
|
-
const apiKey = apiKeyRaw !== void 0 && apiKeyRaw !== null && typeof apiKeyRaw === "string" && apiKeyRaw.trim() !== "" ? apiKeyRaw.trim() : null;
|
|
151
|
-
const environment = normalizeEnvironment(parsed?.environment);
|
|
152
|
-
return environment === void 0 ? { projectId: projectId.trim(), apiKey } : { projectId: projectId.trim(), apiKey, environment };
|
|
153
233
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
234
|
+
function parseBindingRecord(raw) {
|
|
235
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
236
|
+
throw new Error("[memoraone-mcp] Corrupt binding record");
|
|
237
|
+
}
|
|
238
|
+
const obj = raw;
|
|
239
|
+
assertNoSecretsInBindingRecord(obj);
|
|
240
|
+
if (obj.v !== BINDING_RECORD_VERSION) {
|
|
241
|
+
throw new Error(`[memoraone-mcp] Unsupported binding record version: ${String(obj.v)}`);
|
|
242
|
+
}
|
|
243
|
+
const repositoryBindingId = assertRepositoryBindingId(String(obj.repositoryBindingId ?? ""));
|
|
244
|
+
const workspaceRoot = typeof obj.workspaceRoot === "string" ? obj.workspaceRoot : "";
|
|
245
|
+
if (!workspaceRoot) {
|
|
246
|
+
throw new Error("[memoraone-mcp] Binding record missing workspaceRoot");
|
|
247
|
+
}
|
|
248
|
+
const fsId = obj.filesystemIdentity;
|
|
249
|
+
if (!fsId || typeof fsId !== "object" || Array.isArray(fsId)) {
|
|
250
|
+
throw new Error("[memoraone-mcp] Binding record missing filesystemIdentity");
|
|
251
|
+
}
|
|
252
|
+
const identity = fsId;
|
|
253
|
+
const birthtimeMs = Number(identity.birthtimeMs);
|
|
254
|
+
if (typeof identity.platform !== "string" || typeof identity.deviceId !== "string" || typeof identity.inode !== "string" || !Number.isFinite(birthtimeMs) || birthtimeMs <= 0) {
|
|
255
|
+
throw new Error("[memoraone-mcp] Binding record has invalid filesystemIdentity");
|
|
256
|
+
}
|
|
257
|
+
const status = obj.status;
|
|
258
|
+
if (status !== "connected" && status !== "reconnect_required" && status !== "pending") {
|
|
259
|
+
throw new Error("[memoraone-mcp] Binding record has invalid status");
|
|
260
|
+
}
|
|
261
|
+
const record = {
|
|
262
|
+
v: BINDING_RECORD_VERSION,
|
|
263
|
+
repositoryBindingId,
|
|
264
|
+
workspaceRoot,
|
|
265
|
+
filesystemIdentity: {
|
|
266
|
+
platform: identity.platform,
|
|
267
|
+
deviceId: identity.deviceId,
|
|
268
|
+
inode: identity.inode,
|
|
269
|
+
birthtimeMs
|
|
270
|
+
},
|
|
271
|
+
rootFingerprint: typeof obj.rootFingerprint === "string" ? obj.rootFingerprint : "",
|
|
272
|
+
displayName: typeof obj.displayName === "string" ? obj.displayName : "",
|
|
273
|
+
environment: typeof obj.environment === "string" ? obj.environment : "local",
|
|
274
|
+
normalizedGitRemote: obj.normalizedGitRemote === null || typeof obj.normalizedGitRemote === "string" ? obj.normalizedGitRemote : null,
|
|
275
|
+
status,
|
|
276
|
+
createdAt: typeof obj.createdAt === "string" ? obj.createdAt : (/* @__PURE__ */ new Date()).toISOString(),
|
|
277
|
+
updatedAt: typeof obj.updatedAt === "string" ? obj.updatedAt : (/* @__PURE__ */ new Date()).toISOString()
|
|
278
|
+
};
|
|
279
|
+
if (typeof obj.apiUrl === "string" && obj.apiUrl.trim()) {
|
|
280
|
+
record.apiUrl = obj.apiUrl.trim().replace(/\/+$/, "");
|
|
281
|
+
}
|
|
282
|
+
if (typeof obj.installationPublicId === "string" && obj.installationPublicId) {
|
|
283
|
+
record.installationPublicId = obj.installationPublicId;
|
|
284
|
+
}
|
|
285
|
+
if (typeof obj.projectId === "string" && obj.projectId) {
|
|
286
|
+
record.projectId = obj.projectId;
|
|
287
|
+
}
|
|
288
|
+
if (obj.packageVersion === null || typeof obj.packageVersion === "string") {
|
|
289
|
+
record.packageVersion = obj.packageVersion;
|
|
290
|
+
}
|
|
291
|
+
if (obj.ideType === null || typeof obj.ideType === "string") {
|
|
292
|
+
record.ideType = obj.ideType;
|
|
293
|
+
}
|
|
294
|
+
return record;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// src/localState/localLocks.ts
|
|
298
|
+
var fs2 = __toESM(require("fs/promises"), 1);
|
|
299
|
+
|
|
300
|
+
// src/localState/statePaths.ts
|
|
301
|
+
var os = __toESM(require("os"), 1);
|
|
302
|
+
var path3 = __toESM(require("path"), 1);
|
|
303
|
+
var MEMORAONE_STATE_DIRNAME = ".memoraone";
|
|
304
|
+
function getMemoraoneStateDir(homeDir = os.homedir()) {
|
|
305
|
+
return path3.join(homeDir, MEMORAONE_STATE_DIRNAME);
|
|
306
|
+
}
|
|
307
|
+
function getPathIndexPath(homeDir = os.homedir()) {
|
|
308
|
+
return path3.join(getMemoraoneStateDir(homeDir), "path-index.json");
|
|
309
|
+
}
|
|
310
|
+
function getBindingsDir(homeDir = os.homedir()) {
|
|
311
|
+
return path3.join(getMemoraoneStateDir(homeDir), "bindings");
|
|
312
|
+
}
|
|
313
|
+
function getBindingFilePath(repositoryBindingId, homeDir = os.homedir()) {
|
|
314
|
+
return path3.join(getBindingsDir(homeDir), `${repositoryBindingId}.json`);
|
|
315
|
+
}
|
|
316
|
+
function getLocksDir(homeDir = os.homedir()) {
|
|
317
|
+
return path3.join(getMemoraoneStateDir(homeDir), "locks");
|
|
318
|
+
}
|
|
319
|
+
function getLockPath(lockName, homeDir = os.homedir()) {
|
|
320
|
+
return path3.join(getLocksDir(homeDir), `${lockName}.lock`);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// src/localState/localLocks.ts
|
|
324
|
+
async function acquireLocalLock(lockName, options = {}) {
|
|
325
|
+
const homeDir = options.homeDir;
|
|
326
|
+
const maxRetries = options.maxRetries ?? 40;
|
|
327
|
+
const retryDelayMs = options.retryDelayMs ?? 50;
|
|
328
|
+
const maxLockAgeMs = options.maxLockAgeMs ?? 15e3;
|
|
329
|
+
const lockPath = getLockPath(lockName, homeDir);
|
|
330
|
+
await ensurePrivateDir(getLocksDir(homeDir));
|
|
331
|
+
let retries = 0;
|
|
332
|
+
while (retries <= maxRetries) {
|
|
333
|
+
try {
|
|
334
|
+
try {
|
|
335
|
+
const stat4 = await fs2.stat(lockPath);
|
|
336
|
+
if (Date.now() - stat4.mtimeMs > maxLockAgeMs) {
|
|
337
|
+
await fs2.unlink(lockPath);
|
|
338
|
+
}
|
|
339
|
+
} catch (err) {
|
|
340
|
+
if (err?.code !== "ENOENT") {
|
|
341
|
+
throw err;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
const fd = await fs2.open(lockPath, "wx");
|
|
345
|
+
await fd.writeFile(
|
|
346
|
+
JSON.stringify({
|
|
347
|
+
pid: process.pid,
|
|
348
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
349
|
+
}),
|
|
350
|
+
"utf8"
|
|
351
|
+
);
|
|
352
|
+
await fd.close();
|
|
353
|
+
return async () => {
|
|
354
|
+
try {
|
|
355
|
+
await fs2.unlink(lockPath);
|
|
356
|
+
} catch (err) {
|
|
357
|
+
if (err?.code !== "ENOENT") {
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
} catch (err) {
|
|
362
|
+
if (err?.code === "EEXIST") {
|
|
363
|
+
retries += 1;
|
|
364
|
+
if (retries > maxRetries) {
|
|
365
|
+
throw new Error(
|
|
366
|
+
`[memoraone-mcp] Failed to acquire lock ${lockName} after ${maxRetries} retries`
|
|
367
|
+
);
|
|
368
|
+
}
|
|
369
|
+
await new Promise((resolve17) => setTimeout(resolve17, retryDelayMs));
|
|
370
|
+
continue;
|
|
371
|
+
}
|
|
372
|
+
throw err;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
throw new Error(`[memoraone-mcp] Failed to acquire lock ${lockName}`);
|
|
376
|
+
}
|
|
377
|
+
async function withLocalLock(lockName, fn, options = {}) {
|
|
378
|
+
const release = await acquireLocalLock(lockName, options);
|
|
379
|
+
try {
|
|
380
|
+
return await fn();
|
|
381
|
+
} finally {
|
|
382
|
+
await release();
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// src/localState/bindingStore.ts
|
|
387
|
+
async function ensureBindingsDir(homeDir) {
|
|
388
|
+
const dir = getBindingsDir(homeDir);
|
|
389
|
+
await ensurePrivateDir(dir);
|
|
390
|
+
return dir;
|
|
391
|
+
}
|
|
392
|
+
async function readBindingRecord(repositoryBindingId, homeDir) {
|
|
393
|
+
const id = assertRepositoryBindingId(repositoryBindingId);
|
|
394
|
+
const filePath = getBindingFilePath(id, homeDir);
|
|
395
|
+
const raw = await readJsonFile(filePath);
|
|
396
|
+
if (raw == null) {
|
|
157
397
|
return null;
|
|
158
398
|
}
|
|
159
|
-
|
|
399
|
+
return parseBindingRecord(raw);
|
|
400
|
+
}
|
|
401
|
+
async function writeBindingRecord(record, homeDir) {
|
|
402
|
+
assertNoSecretsInBindingRecord(record);
|
|
403
|
+
const id = assertRepositoryBindingId(record.repositoryBindingId);
|
|
404
|
+
await ensureBindingsDir(homeDir);
|
|
405
|
+
const next = {
|
|
406
|
+
...record,
|
|
407
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
408
|
+
};
|
|
409
|
+
await withLocalLock(
|
|
410
|
+
`binding-${id}`,
|
|
411
|
+
async () => {
|
|
412
|
+
await writeJsonAtomic(getBindingFilePath(id, homeDir), next);
|
|
413
|
+
},
|
|
414
|
+
{ homeDir }
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
async function listBindingRecords(homeDir) {
|
|
418
|
+
const dir = await ensureBindingsDir(homeDir);
|
|
419
|
+
let names;
|
|
160
420
|
try {
|
|
161
|
-
|
|
162
|
-
const { projectId, apiKey, environment } = parseAndValidateM1(content, markerPath);
|
|
163
|
-
return environment === void 0 ? { projectId, apiKey, foundAt: markerPath } : { projectId, apiKey, environment, foundAt: markerPath };
|
|
421
|
+
names = await fs3.readdir(dir);
|
|
164
422
|
} catch (err) {
|
|
165
|
-
if (err?.code === "ENOENT")
|
|
423
|
+
if (err?.code === "ENOENT") return [];
|
|
424
|
+
throw err;
|
|
425
|
+
}
|
|
426
|
+
const out = [];
|
|
427
|
+
for (const name of names) {
|
|
428
|
+
if (!name.endsWith(".json") || name.startsWith(".")) continue;
|
|
429
|
+
const id = name.slice(0, -".json".length);
|
|
430
|
+
try {
|
|
431
|
+
const record = await readBindingRecord(id, homeDir);
|
|
432
|
+
if (record) out.push(record);
|
|
433
|
+
} catch {
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
return out;
|
|
437
|
+
}
|
|
438
|
+
async function findBindingRecordByWorkspaceRoot(workspaceRoot, homeDir) {
|
|
439
|
+
const resolved = path4.resolve(workspaceRoot);
|
|
440
|
+
const records = await listBindingRecords(homeDir);
|
|
441
|
+
return records.find((r) => path4.resolve(r.workspaceRoot) === resolved) ?? null;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// src/localState/installationCredentials.ts
|
|
445
|
+
var import_node_crypto3 = require("crypto");
|
|
446
|
+
|
|
447
|
+
// src/localState/keyringStore.ts
|
|
448
|
+
var KEYRING_SERVICE = "MemoraOne Local MCP";
|
|
449
|
+
function keyringAccountForBinding(repositoryBindingId) {
|
|
450
|
+
return `binding:${assertRepositoryBindingId(repositoryBindingId)}`;
|
|
451
|
+
}
|
|
452
|
+
var KeyringUnavailableError = class extends Error {
|
|
453
|
+
constructor(message, cause) {
|
|
454
|
+
super(message);
|
|
455
|
+
this.name = "KeyringUnavailableError";
|
|
456
|
+
if (cause !== void 0) {
|
|
457
|
+
this.cause = cause;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
};
|
|
461
|
+
var KeyringOperationError = class extends Error {
|
|
462
|
+
constructor(message, cause) {
|
|
463
|
+
super(message);
|
|
464
|
+
this.name = "KeyringOperationError";
|
|
465
|
+
if (cause !== void 0) {
|
|
466
|
+
this.cause = cause;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
};
|
|
470
|
+
var cachedModule;
|
|
471
|
+
var loadError;
|
|
472
|
+
async function loadKeyringModule(loader = defaultKeyringLoader) {
|
|
473
|
+
if (cachedModule) {
|
|
474
|
+
return cachedModule;
|
|
475
|
+
}
|
|
476
|
+
if (cachedModule === null) {
|
|
477
|
+
throw new KeyringUnavailableError(
|
|
478
|
+
"[memoraone-mcp] OS keyring unavailable; cannot store or load credentials. No plaintext fallback.",
|
|
479
|
+
loadError
|
|
480
|
+
);
|
|
481
|
+
}
|
|
482
|
+
try {
|
|
483
|
+
cachedModule = await loader();
|
|
484
|
+
return cachedModule;
|
|
485
|
+
} catch (err) {
|
|
486
|
+
cachedModule = null;
|
|
487
|
+
loadError = err;
|
|
488
|
+
throw new KeyringUnavailableError(
|
|
489
|
+
"[memoraone-mcp] OS keyring unavailable; cannot store or load credentials. No plaintext fallback.",
|
|
490
|
+
err
|
|
491
|
+
);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
async function defaultKeyringLoader() {
|
|
495
|
+
const mod = await import("@napi-rs/keyring");
|
|
496
|
+
if (!mod?.Entry) {
|
|
497
|
+
throw new Error("Entry export missing from @napi-rs/keyring");
|
|
498
|
+
}
|
|
499
|
+
return mod;
|
|
500
|
+
}
|
|
501
|
+
async function keyringSetPassword(repositoryBindingId, password, options = {}) {
|
|
502
|
+
const mod = await loadKeyringModule(options.loader);
|
|
503
|
+
const account = keyringAccountForBinding(repositoryBindingId);
|
|
504
|
+
try {
|
|
505
|
+
const entry = new mod.Entry(KEYRING_SERVICE, account);
|
|
506
|
+
entry.setPassword(password);
|
|
507
|
+
} catch (err) {
|
|
508
|
+
if (err instanceof KeyringUnavailableError) throw err;
|
|
509
|
+
throw new KeyringOperationError(
|
|
510
|
+
`[memoraone-mcp] Failed to write credentials to OS keyring for ${account}`,
|
|
511
|
+
err
|
|
512
|
+
);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
async function keyringGetPassword(repositoryBindingId, options = {}) {
|
|
516
|
+
const mod = await loadKeyringModule(options.loader);
|
|
517
|
+
const account = keyringAccountForBinding(repositoryBindingId);
|
|
518
|
+
try {
|
|
519
|
+
const entry = new mod.Entry(KEYRING_SERVICE, account);
|
|
520
|
+
return entry.getPassword();
|
|
521
|
+
} catch (err) {
|
|
522
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
523
|
+
if (/NoEntry|not found|no entry/i.test(message)) {
|
|
166
524
|
return null;
|
|
167
525
|
}
|
|
526
|
+
if (err instanceof KeyringUnavailableError) throw err;
|
|
527
|
+
throw new KeyringOperationError(
|
|
528
|
+
`[memoraone-mcp] Failed to read credentials from OS keyring for ${account}`,
|
|
529
|
+
err
|
|
530
|
+
);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// src/localState/installationCredentials.ts
|
|
535
|
+
function parsePayload(raw, repositoryBindingId) {
|
|
536
|
+
let parsed2;
|
|
537
|
+
try {
|
|
538
|
+
parsed2 = JSON.parse(raw);
|
|
539
|
+
} catch {
|
|
540
|
+
throw new Error("[memoraone-mcp] Corrupt keyring credential payload");
|
|
541
|
+
}
|
|
542
|
+
if (!parsed2 || typeof parsed2 !== "object" || Array.isArray(parsed2)) {
|
|
543
|
+
throw new Error("[memoraone-mcp] Corrupt keyring credential payload");
|
|
544
|
+
}
|
|
545
|
+
const obj = parsed2;
|
|
546
|
+
const id = assertRepositoryBindingId(String(obj.repositoryBindingId ?? repositoryBindingId));
|
|
547
|
+
if (id !== repositoryBindingId) {
|
|
548
|
+
throw new Error("[memoraone-mcp] Keyring credential payload binding id mismatch");
|
|
549
|
+
}
|
|
550
|
+
const payload = {
|
|
551
|
+
repositoryBindingId: id,
|
|
552
|
+
updatedAt: typeof obj.updatedAt === "string" ? obj.updatedAt : (/* @__PURE__ */ new Date()).toISOString()
|
|
553
|
+
};
|
|
554
|
+
const optionalString = (key) => {
|
|
555
|
+
const value = obj[key];
|
|
556
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
557
|
+
payload[key] = value;
|
|
558
|
+
}
|
|
559
|
+
};
|
|
560
|
+
optionalString("installationPublicId");
|
|
561
|
+
optionalString("projectId");
|
|
562
|
+
optionalString("accessToken");
|
|
563
|
+
optionalString("refreshToken");
|
|
564
|
+
optionalString("accessTokenExpiresAt");
|
|
565
|
+
optionalString("refreshTokenExpiresAt");
|
|
566
|
+
optionalString("clientRedeemKey");
|
|
567
|
+
optionalString("clientRefreshKey");
|
|
568
|
+
return payload;
|
|
569
|
+
}
|
|
570
|
+
async function readInstallationCredentials(repositoryBindingId, options = {}) {
|
|
571
|
+
const id = assertRepositoryBindingId(repositoryBindingId);
|
|
572
|
+
const raw = await keyringGetPassword(id, options);
|
|
573
|
+
if (raw == null || raw.trim() === "") {
|
|
574
|
+
return null;
|
|
575
|
+
}
|
|
576
|
+
return parsePayload(raw, id);
|
|
577
|
+
}
|
|
578
|
+
async function writeInstallationCredentials(payload, options = {}) {
|
|
579
|
+
const id = assertRepositoryBindingId(payload.repositoryBindingId);
|
|
580
|
+
const next = {
|
|
581
|
+
...payload,
|
|
582
|
+
repositoryBindingId: id,
|
|
583
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
584
|
+
};
|
|
585
|
+
await keyringSetPassword(id, JSON.stringify(next), options);
|
|
586
|
+
return next;
|
|
587
|
+
}
|
|
588
|
+
async function updateInstallationCredentials(repositoryBindingId, patch, options = {}) {
|
|
589
|
+
const id = assertRepositoryBindingId(repositoryBindingId);
|
|
590
|
+
const existing = await readInstallationCredentials(id, options) ?? {
|
|
591
|
+
repositoryBindingId: id,
|
|
592
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
593
|
+
};
|
|
594
|
+
const merged = {
|
|
595
|
+
...existing,
|
|
596
|
+
...Object.fromEntries(
|
|
597
|
+
Object.entries(patch).filter(([, value]) => value !== void 0)
|
|
598
|
+
),
|
|
599
|
+
repositoryBindingId: id
|
|
600
|
+
};
|
|
601
|
+
for (const key of options.clearKeys ?? []) {
|
|
602
|
+
if (key !== "repositoryBindingId" && key !== "updatedAt") {
|
|
603
|
+
delete merged[key];
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
return writeInstallationCredentials(merged, options);
|
|
607
|
+
}
|
|
608
|
+
async function ensureClientRedeemKey(repositoryBindingId, options = {}) {
|
|
609
|
+
const existing = await readInstallationCredentials(repositoryBindingId, options);
|
|
610
|
+
if (existing?.clientRedeemKey) {
|
|
611
|
+
return { payload: existing, clientRedeemKey: existing.clientRedeemKey, created: false };
|
|
612
|
+
}
|
|
613
|
+
const clientRedeemKey = (0, import_node_crypto3.randomUUID)();
|
|
614
|
+
const payload = await updateInstallationCredentials(
|
|
615
|
+
repositoryBindingId,
|
|
616
|
+
{ clientRedeemKey },
|
|
617
|
+
options
|
|
618
|
+
);
|
|
619
|
+
return { payload, clientRedeemKey, created: true };
|
|
620
|
+
}
|
|
621
|
+
function hasUsableAccessToken(payload) {
|
|
622
|
+
return Boolean(payload?.accessToken && payload.accessToken.startsWith("mia_"));
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
// src/localState/pathIndex.ts
|
|
626
|
+
var path6 = __toESM(require("path"), 1);
|
|
627
|
+
|
|
628
|
+
// src/localState/rootFilesystemIdentity.ts
|
|
629
|
+
var fs4 = __toESM(require("fs/promises"), 1);
|
|
630
|
+
var os2 = __toESM(require("os"), 1);
|
|
631
|
+
var path5 = __toESM(require("path"), 1);
|
|
632
|
+
var import_node_child_process = require("child_process");
|
|
633
|
+
var import_node_util = require("util");
|
|
634
|
+
var execFileAsync = (0, import_node_util.promisify)(import_node_child_process.execFile);
|
|
635
|
+
function filesystemIdentityKey(identity) {
|
|
636
|
+
return [
|
|
637
|
+
identity.platform,
|
|
638
|
+
identity.deviceId,
|
|
639
|
+
identity.inode,
|
|
640
|
+
String(identity.birthtimeMs)
|
|
641
|
+
].join("|");
|
|
642
|
+
}
|
|
643
|
+
function identitiesMatch(a, b) {
|
|
644
|
+
return filesystemIdentityKey(a) === filesystemIdentityKey(b);
|
|
645
|
+
}
|
|
646
|
+
async function readDarwinDeviceId() {
|
|
647
|
+
const { stdout } = await execFileAsync("ioreg", ["-rd1", "-c", "IOPlatformExpertDevice"]);
|
|
648
|
+
const match = stdout.match(/"IOPlatformUUID"\s*=\s*"([^"]+)"/);
|
|
649
|
+
if (!match?.[1]) {
|
|
650
|
+
throw new Error("[memoraone-mcp] Unable to read macOS IOPlatformUUID");
|
|
651
|
+
}
|
|
652
|
+
return match[1].trim();
|
|
653
|
+
}
|
|
654
|
+
async function readLinuxDeviceId() {
|
|
655
|
+
try {
|
|
656
|
+
const content = await fs4.readFile("/etc/machine-id", "utf8");
|
|
657
|
+
const id = content.trim();
|
|
658
|
+
if (id) return id;
|
|
659
|
+
} catch {
|
|
660
|
+
}
|
|
661
|
+
try {
|
|
662
|
+
const content = await fs4.readFile("/var/lib/dbus/machine-id", "utf8");
|
|
663
|
+
const id = content.trim();
|
|
664
|
+
if (id) return id;
|
|
665
|
+
} catch {
|
|
666
|
+
}
|
|
667
|
+
throw new Error("[memoraone-mcp] Unable to read Linux machine-id");
|
|
668
|
+
}
|
|
669
|
+
async function readWindowsDeviceId() {
|
|
670
|
+
const { stdout } = await execFileAsync("reg", [
|
|
671
|
+
"query",
|
|
672
|
+
"HKLM\\SOFTWARE\\Microsoft\\Cryptography",
|
|
673
|
+
"/v",
|
|
674
|
+
"MachineGuid"
|
|
675
|
+
]);
|
|
676
|
+
const match = stdout.match(/MachineGuid\s+REG_SZ\s+(.+)/i);
|
|
677
|
+
if (!match?.[1]) {
|
|
678
|
+
throw new Error("[memoraone-mcp] Unable to read Windows MachineGuid");
|
|
679
|
+
}
|
|
680
|
+
return match[1].trim();
|
|
681
|
+
}
|
|
682
|
+
async function resolveDeviceId(platform2 = os2.platform()) {
|
|
683
|
+
if (platform2 === "darwin") return readDarwinDeviceId();
|
|
684
|
+
if (platform2 === "linux") return readLinuxDeviceId();
|
|
685
|
+
if (platform2 === "win32") return readWindowsDeviceId();
|
|
686
|
+
try {
|
|
687
|
+
const { stdout } = await execFileAsync("hostid", []);
|
|
688
|
+
const id = stdout.trim();
|
|
689
|
+
if (id) return id;
|
|
690
|
+
} catch {
|
|
691
|
+
}
|
|
692
|
+
throw new Error(`[memoraone-mcp] Unsupported platform for device ID: ${platform2}`);
|
|
693
|
+
}
|
|
694
|
+
async function captureRootFilesystemIdentity(rootPath, deps = {}) {
|
|
695
|
+
const resolved = path5.resolve(rootPath);
|
|
696
|
+
const platform2 = deps.platform ?? os2.platform();
|
|
697
|
+
const statRoot = deps.statRoot ?? (async (p) => {
|
|
698
|
+
const st2 = await fs4.stat(p);
|
|
699
|
+
return {
|
|
700
|
+
ino: st2.ino,
|
|
701
|
+
dev: st2.dev,
|
|
702
|
+
birthtimeMs: st2.birthtimeMs,
|
|
703
|
+
isDirectory: () => st2.isDirectory()
|
|
704
|
+
};
|
|
705
|
+
});
|
|
706
|
+
const readDeviceId = deps.readDeviceId ?? (() => resolveDeviceId(platform2));
|
|
707
|
+
const st = await statRoot(resolved);
|
|
708
|
+
if (!st.isDirectory()) {
|
|
709
|
+
throw new Error(`[memoraone-mcp] Workspace root is not a directory: ${resolved}`);
|
|
710
|
+
}
|
|
711
|
+
const birthtimeMs = Number(st.birthtimeMs);
|
|
712
|
+
if (!Number.isFinite(birthtimeMs) || birthtimeMs <= 0) {
|
|
713
|
+
throw new Error(
|
|
714
|
+
"[memoraone-mcp] Root birth time unavailable; cannot bind this working tree. Reconnect required."
|
|
715
|
+
);
|
|
716
|
+
}
|
|
717
|
+
const inode = typeof st.ino === "bigint" ? st.ino.toString() : String(st.ino);
|
|
718
|
+
const deviceId = await readDeviceId();
|
|
719
|
+
if (!deviceId || !inode) {
|
|
720
|
+
throw new Error(
|
|
721
|
+
"[memoraone-mcp] Ambiguous filesystem identity; cannot bind this working tree. Reconnect required."
|
|
722
|
+
);
|
|
723
|
+
}
|
|
724
|
+
return {
|
|
725
|
+
platform: platform2,
|
|
726
|
+
deviceId,
|
|
727
|
+
inode,
|
|
728
|
+
birthtimeMs
|
|
729
|
+
};
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
// src/localState/pathIndex.ts
|
|
733
|
+
var PATH_INDEX_VERSION = 1;
|
|
734
|
+
function emptyIndex() {
|
|
735
|
+
return {
|
|
736
|
+
v: PATH_INDEX_VERSION,
|
|
737
|
+
byPath: {},
|
|
738
|
+
byIdentity: {},
|
|
739
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
function parsePathIndex(raw) {
|
|
743
|
+
if (raw == null) {
|
|
744
|
+
return emptyIndex();
|
|
745
|
+
}
|
|
746
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
747
|
+
throw new Error("[memoraone-mcp] Corrupt path-index.json");
|
|
748
|
+
}
|
|
749
|
+
const obj = raw;
|
|
750
|
+
if (obj.v !== PATH_INDEX_VERSION) {
|
|
751
|
+
throw new Error(`[memoraone-mcp] Unsupported path-index version: ${String(obj.v)}`);
|
|
752
|
+
}
|
|
753
|
+
const byPath = obj.byPath && typeof obj.byPath === "object" && !Array.isArray(obj.byPath) ? obj.byPath : {};
|
|
754
|
+
const byIdentity = obj.byIdentity && typeof obj.byIdentity === "object" && !Array.isArray(obj.byIdentity) ? obj.byIdentity : {};
|
|
755
|
+
return {
|
|
756
|
+
v: PATH_INDEX_VERSION,
|
|
757
|
+
byPath: { ...byPath },
|
|
758
|
+
byIdentity: { ...byIdentity },
|
|
759
|
+
updatedAt: typeof obj.updatedAt === "string" ? obj.updatedAt : (/* @__PURE__ */ new Date()).toISOString()
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
async function loadPathIndex(homeDir) {
|
|
763
|
+
const filePath = getPathIndexPath(homeDir);
|
|
764
|
+
try {
|
|
765
|
+
const raw = await readJsonFile(filePath);
|
|
766
|
+
return parsePathIndex(raw);
|
|
767
|
+
} catch (err) {
|
|
768
|
+
if (err instanceof Error && err.message.includes("Corrupt")) {
|
|
769
|
+
throw err;
|
|
770
|
+
}
|
|
168
771
|
throw err;
|
|
169
772
|
}
|
|
170
773
|
}
|
|
171
|
-
async function
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
774
|
+
async function savePathIndex(index, homeDir) {
|
|
775
|
+
const next = {
|
|
776
|
+
...index,
|
|
777
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
778
|
+
};
|
|
779
|
+
await writeJsonAtomic(getPathIndexPath(homeDir), next);
|
|
780
|
+
}
|
|
781
|
+
function lookupPathIndex(index, workspaceRoot, identity) {
|
|
782
|
+
const resolved = path6.resolve(workspaceRoot);
|
|
783
|
+
const byPathId = index.byPath[resolved];
|
|
784
|
+
if (byPathId) {
|
|
785
|
+
return { kind: "path", repositoryBindingId: assertRepositoryBindingId(byPathId) };
|
|
786
|
+
}
|
|
787
|
+
const identityKey = filesystemIdentityKey(identity);
|
|
788
|
+
const byIdentityId = index.byIdentity[identityKey];
|
|
789
|
+
if (!byIdentityId) {
|
|
790
|
+
return { kind: "none" };
|
|
791
|
+
}
|
|
792
|
+
const previousPath = Object.entries(index.byPath).find(([, id]) => id === byIdentityId)?.[0];
|
|
793
|
+
if (!previousPath) {
|
|
794
|
+
return {
|
|
795
|
+
kind: "identity-rename",
|
|
796
|
+
repositoryBindingId: assertRepositoryBindingId(byIdentityId),
|
|
797
|
+
previousPath: resolved
|
|
798
|
+
};
|
|
799
|
+
}
|
|
800
|
+
return {
|
|
801
|
+
kind: "identity-rename",
|
|
802
|
+
repositoryBindingId: assertRepositoryBindingId(byIdentityId),
|
|
803
|
+
previousPath
|
|
804
|
+
};
|
|
805
|
+
}
|
|
806
|
+
async function upsertPathIndexEntry(options) {
|
|
807
|
+
const repositoryBindingId = assertRepositoryBindingId(options.repositoryBindingId);
|
|
808
|
+
const resolved = path6.resolve(options.workspaceRoot);
|
|
809
|
+
const identityKey = filesystemIdentityKey(options.identity);
|
|
810
|
+
return withLocalLock(
|
|
811
|
+
"path-index",
|
|
812
|
+
async () => {
|
|
813
|
+
const index = await loadPathIndex(options.homeDir);
|
|
814
|
+
if (options.previousPath && path6.resolve(options.previousPath) !== resolved) {
|
|
815
|
+
delete index.byPath[path6.resolve(options.previousPath)];
|
|
816
|
+
}
|
|
817
|
+
for (const [p, id] of Object.entries(index.byPath)) {
|
|
818
|
+
if (id === repositoryBindingId && p !== resolved) {
|
|
819
|
+
delete index.byPath[p];
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
for (const [key, id] of Object.entries(index.byIdentity)) {
|
|
823
|
+
if (id === repositoryBindingId && key !== identityKey) {
|
|
824
|
+
delete index.byIdentity[key];
|
|
825
|
+
}
|
|
183
826
|
}
|
|
827
|
+
index.byPath[resolved] = repositoryBindingId;
|
|
828
|
+
index.byIdentity[identityKey] = repositoryBindingId;
|
|
829
|
+
await savePathIndex(index, options.homeDir);
|
|
830
|
+
return index;
|
|
831
|
+
},
|
|
832
|
+
{ homeDir: options.homeDir }
|
|
833
|
+
);
|
|
834
|
+
}
|
|
835
|
+
function identityMatchesStored(stored, current) {
|
|
836
|
+
return identitiesMatch(stored, current);
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
// src/client/memoraClient.ts
|
|
840
|
+
var parseBooleanFlag = (value) => {
|
|
841
|
+
if (!value) {
|
|
842
|
+
return false;
|
|
843
|
+
}
|
|
844
|
+
const normalized = value.trim().toLowerCase();
|
|
845
|
+
return ["1", "true", "yes", "on"].includes(normalized);
|
|
846
|
+
};
|
|
847
|
+
var debugEnabled = parseBooleanFlag(process.env.MEMORAONE_DEV_MODE);
|
|
848
|
+
var MemoraOneHttpError = class extends Error {
|
|
849
|
+
constructor(status, statusText, body) {
|
|
850
|
+
super(`MemoraOne request failed: ${status} ${statusText}`);
|
|
851
|
+
this.name = "MemoraOneHttpError";
|
|
852
|
+
this.status = status;
|
|
853
|
+
this.body = body;
|
|
854
|
+
}
|
|
855
|
+
};
|
|
856
|
+
|
|
857
|
+
// src/localState/localConnectClient.ts
|
|
858
|
+
async function requestJson(baseUrl, method, path21, options = {}) {
|
|
859
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
860
|
+
const url = `${baseUrl.replace(/\/+$/, "")}${path21.startsWith("/") ? path21 : `/${path21}`}`;
|
|
861
|
+
const res = await fetchImpl(url, {
|
|
862
|
+
method,
|
|
863
|
+
headers: {
|
|
864
|
+
"content-type": "application/json",
|
|
865
|
+
...options.headers ?? {}
|
|
866
|
+
},
|
|
867
|
+
body: method === "GET" ? void 0 : JSON.stringify(options.body ?? {})
|
|
868
|
+
});
|
|
869
|
+
const text = await res.text();
|
|
870
|
+
let json = null;
|
|
871
|
+
if (text) {
|
|
872
|
+
try {
|
|
873
|
+
json = JSON.parse(text);
|
|
874
|
+
} catch {
|
|
875
|
+
json = text;
|
|
184
876
|
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
877
|
+
}
|
|
878
|
+
if (!res.ok && !options.acceptStatuses?.includes(res.status)) {
|
|
879
|
+
throw new MemoraOneHttpError(res.status, res.statusText, json);
|
|
880
|
+
}
|
|
881
|
+
return { status: res.status, statusText: res.statusText, ok: res.ok, json };
|
|
882
|
+
}
|
|
883
|
+
function assertSafeRedeemBody(body) {
|
|
884
|
+
if ("canonical_root" in body) {
|
|
885
|
+
throw new Error("[memoraone-mcp] redeem body must not include canonical_root");
|
|
886
|
+
}
|
|
887
|
+
for (const key of [
|
|
888
|
+
"workspace_root",
|
|
889
|
+
"absolute_path",
|
|
890
|
+
"filesystem_identity",
|
|
891
|
+
"device_id",
|
|
892
|
+
"inode",
|
|
893
|
+
"birthtime_ms",
|
|
894
|
+
"birthtimeMs"
|
|
895
|
+
]) {
|
|
896
|
+
if (key in body) {
|
|
897
|
+
throw new Error(`[memoraone-mcp] redeem body must not include ${key}`);
|
|
188
898
|
}
|
|
189
|
-
current = parent;
|
|
190
899
|
}
|
|
191
|
-
return null;
|
|
192
900
|
}
|
|
193
|
-
function
|
|
194
|
-
|
|
195
|
-
|
|
901
|
+
async function redeemLocalConnectCode(apiUrl, body, options = {}) {
|
|
902
|
+
assertSafeRedeemBody(body);
|
|
903
|
+
const res = await requestJson(apiUrl, "POST", "/v1/local-connect/redeem", {
|
|
904
|
+
body,
|
|
905
|
+
fetchImpl: options.fetchImpl
|
|
906
|
+
});
|
|
907
|
+
const data = res.json;
|
|
908
|
+
const accessToken = data?.access_token;
|
|
909
|
+
const refreshToken = data?.refresh_token;
|
|
910
|
+
const installationPublicId = data?.installation_public_id;
|
|
911
|
+
const projectId = data?.project_id;
|
|
912
|
+
const repositoryBindingId = data?.repository_binding_id;
|
|
913
|
+
if (typeof accessToken !== "string" || typeof refreshToken !== "string" || typeof installationPublicId !== "string" || typeof projectId !== "string" || typeof repositoryBindingId !== "string") {
|
|
914
|
+
throw new Error("[memoraone-mcp] Invalid redeem response");
|
|
915
|
+
}
|
|
916
|
+
let sourceId = null;
|
|
917
|
+
if (data.source_id === null) {
|
|
918
|
+
sourceId = null;
|
|
919
|
+
} else if (typeof data.source_id === "string") {
|
|
920
|
+
sourceId = data.source_id;
|
|
196
921
|
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
922
|
+
return {
|
|
923
|
+
access_token: accessToken,
|
|
924
|
+
refresh_token: refreshToken,
|
|
925
|
+
access_token_expires_at: typeof data.access_token_expires_at === "string" ? data.access_token_expires_at : null,
|
|
926
|
+
refresh_token_expires_at: typeof data.refresh_token_expires_at === "string" ? data.refresh_token_expires_at : null,
|
|
927
|
+
installation_public_id: installationPublicId,
|
|
928
|
+
project_id: projectId,
|
|
929
|
+
repository_binding_id: repositoryBindingId,
|
|
930
|
+
source_id: sourceId,
|
|
931
|
+
recovered: Boolean(data.recovered)
|
|
932
|
+
};
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
// src/localState/tokenRefreshCoordinator.ts
|
|
936
|
+
var ReconnectRequiredError = class extends Error {
|
|
937
|
+
constructor(message) {
|
|
938
|
+
super(message);
|
|
939
|
+
this.name = "ReconnectRequiredError";
|
|
940
|
+
}
|
|
941
|
+
};
|
|
942
|
+
|
|
943
|
+
// src/localState/resolveLocalBinding.ts
|
|
944
|
+
async function detectLegacyM1Warning(workspaceRoot) {
|
|
945
|
+
const candidate = path7.join(path7.resolve(workspaceRoot), CANONICAL_M1_FILENAME);
|
|
946
|
+
try {
|
|
947
|
+
await fs5.access(candidate);
|
|
948
|
+
return candidate;
|
|
949
|
+
} catch {
|
|
950
|
+
return void 0;
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
async function ensureRepositoryBindingForRoot(workspaceRoot, options = {}) {
|
|
954
|
+
const resolved = path7.resolve(workspaceRoot);
|
|
955
|
+
const identity = await captureRootFilesystemIdentity(resolved, options.identityDeps);
|
|
956
|
+
const legacyM1WarningPath = await detectLegacyM1Warning(resolved);
|
|
957
|
+
const index = await loadPathIndex(options.homeDir);
|
|
958
|
+
const lookup = lookupPathIndex(index, resolved, identity);
|
|
959
|
+
if (lookup.kind === "path") {
|
|
960
|
+
const record = await readBindingRecord(lookup.repositoryBindingId, options.homeDir);
|
|
961
|
+
if (record && identityMatchesStored(record.filesystemIdentity, identity)) {
|
|
962
|
+
if (path7.resolve(record.workspaceRoot) !== resolved) {
|
|
963
|
+
const updated = {
|
|
964
|
+
...record,
|
|
965
|
+
workspaceRoot: resolved,
|
|
966
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
967
|
+
};
|
|
968
|
+
await writeBindingRecord(updated, options.homeDir);
|
|
969
|
+
await upsertPathIndexEntry({
|
|
970
|
+
repositoryBindingId: record.repositoryBindingId,
|
|
971
|
+
workspaceRoot: resolved,
|
|
972
|
+
identity,
|
|
973
|
+
homeDir: options.homeDir,
|
|
974
|
+
previousPath: record.workspaceRoot
|
|
975
|
+
});
|
|
976
|
+
}
|
|
977
|
+
return {
|
|
978
|
+
repositoryBindingId: record.repositoryBindingId,
|
|
979
|
+
identity,
|
|
980
|
+
created: false,
|
|
981
|
+
legacyM1WarningPath
|
|
982
|
+
};
|
|
207
983
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
984
|
+
}
|
|
985
|
+
if (lookup.kind === "identity-rename") {
|
|
986
|
+
const record = await readBindingRecord(lookup.repositoryBindingId, options.homeDir);
|
|
987
|
+
if (record && identityMatchesStored(record.filesystemIdentity, identity)) {
|
|
988
|
+
const updated = {
|
|
989
|
+
...record,
|
|
990
|
+
workspaceRoot: resolved,
|
|
991
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
992
|
+
};
|
|
993
|
+
await writeBindingRecord(updated, options.homeDir);
|
|
994
|
+
await upsertPathIndexEntry({
|
|
995
|
+
repositoryBindingId: record.repositoryBindingId,
|
|
996
|
+
workspaceRoot: resolved,
|
|
997
|
+
identity,
|
|
998
|
+
homeDir: options.homeDir,
|
|
999
|
+
previousPath: lookup.previousPath
|
|
1000
|
+
});
|
|
1001
|
+
return {
|
|
1002
|
+
repositoryBindingId: record.repositoryBindingId,
|
|
1003
|
+
identity,
|
|
1004
|
+
created: false,
|
|
1005
|
+
renamedFrom: lookup.previousPath,
|
|
1006
|
+
legacyM1WarningPath
|
|
1007
|
+
};
|
|
212
1008
|
}
|
|
213
1009
|
}
|
|
214
|
-
|
|
1010
|
+
if (!options.createIfMissing) {
|
|
1011
|
+
throw new ReconnectRequiredError(
|
|
1012
|
+
`[memoraone-mcp] No local binding for workspace ${resolved}. Run: memoraone-mcp connect <code>`
|
|
1013
|
+
);
|
|
1014
|
+
}
|
|
1015
|
+
const repositoryBindingId = generateRepositoryBindingId();
|
|
1016
|
+
return {
|
|
1017
|
+
repositoryBindingId,
|
|
1018
|
+
identity,
|
|
1019
|
+
created: true,
|
|
1020
|
+
legacyM1WarningPath
|
|
1021
|
+
};
|
|
215
1022
|
}
|
|
216
|
-
function
|
|
217
|
-
const
|
|
218
|
-
|
|
219
|
-
|
|
1023
|
+
async function resolveLocalBinding(workspaceRoot, options = {}) {
|
|
1024
|
+
const resolved = path7.resolve(workspaceRoot);
|
|
1025
|
+
const { repositoryBindingId, legacyM1WarningPath } = await ensureRepositoryBindingForRoot(
|
|
1026
|
+
resolved,
|
|
1027
|
+
{ ...options, createIfMissing: false }
|
|
1028
|
+
);
|
|
1029
|
+
const record = await readBindingRecord(repositoryBindingId, options.homeDir);
|
|
1030
|
+
if (!record) {
|
|
1031
|
+
throw new ReconnectRequiredError(
|
|
1032
|
+
`[memoraone-mcp] Binding metadata missing for ${repositoryBindingId}. Run: memoraone-mcp connect <code>`
|
|
1033
|
+
);
|
|
1034
|
+
}
|
|
1035
|
+
const identity = await captureRootFilesystemIdentity(resolved, options.identityDeps);
|
|
1036
|
+
if (!identityMatchesStored(record.filesystemIdentity, identity)) {
|
|
1037
|
+
throw new ReconnectRequiredError(
|
|
1038
|
+
"[memoraone-mcp] Workspace filesystem identity changed. Run: memoraone-mcp connect <code>"
|
|
1039
|
+
);
|
|
1040
|
+
}
|
|
1041
|
+
if (record.status === "reconnect_required") {
|
|
1042
|
+
throw new ReconnectRequiredError(
|
|
1043
|
+
"[memoraone-mcp] Installation requires reconnect. Run: memoraone-mcp connect <code>"
|
|
1044
|
+
);
|
|
220
1045
|
}
|
|
221
|
-
const
|
|
222
|
-
|
|
223
|
-
|
|
1046
|
+
const creds = await readInstallationCredentials(
|
|
1047
|
+
repositoryBindingId,
|
|
1048
|
+
options.credentialOptions
|
|
1049
|
+
);
|
|
1050
|
+
if (!hasUsableAccessToken(creds) || !creds?.refreshToken) {
|
|
1051
|
+
throw new ReconnectRequiredError(
|
|
1052
|
+
"[memoraone-mcp] Installation credentials missing. Run: memoraone-mcp connect <code>"
|
|
1053
|
+
);
|
|
224
1054
|
}
|
|
225
|
-
|
|
226
|
-
|
|
1055
|
+
const projectId = record.projectId ?? creds.projectId;
|
|
1056
|
+
if (!projectId) {
|
|
1057
|
+
throw new ReconnectRequiredError(
|
|
1058
|
+
"[memoraone-mcp] Binding missing project id. Run: memoraone-mcp connect <code>"
|
|
1059
|
+
);
|
|
1060
|
+
}
|
|
1061
|
+
return {
|
|
1062
|
+
repositoryBindingId,
|
|
1063
|
+
projectId,
|
|
1064
|
+
workspaceRoot: resolved,
|
|
1065
|
+
installationPublicId: record.installationPublicId ?? creds.installationPublicId,
|
|
1066
|
+
environment: record.environment,
|
|
1067
|
+
bindingSource: "local-binding",
|
|
1068
|
+
status: record.status,
|
|
1069
|
+
legacyM1WarningPath
|
|
1070
|
+
};
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
// src/projectBinding.ts
|
|
1074
|
+
var uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
1075
|
+
var CANONICAL_M1_FILENAME = "memoraone.m1";
|
|
1076
|
+
function normalizeEnvironment(raw) {
|
|
1077
|
+
if (raw === void 0 || raw === null || typeof raw !== "string") {
|
|
1078
|
+
return void 0;
|
|
1079
|
+
}
|
|
1080
|
+
const trimmed = raw.trim();
|
|
1081
|
+
return trimmed === "" ? void 0 : trimmed;
|
|
1082
|
+
}
|
|
1083
|
+
function toResolvedBinding(local) {
|
|
1084
|
+
return {
|
|
1085
|
+
repositoryBindingId: local.repositoryBindingId,
|
|
1086
|
+
projectId: local.projectId,
|
|
1087
|
+
workspaceRoot: local.workspaceRoot,
|
|
1088
|
+
installationPublicId: local.installationPublicId,
|
|
1089
|
+
environment: local.environment,
|
|
1090
|
+
bindingSource: "local-binding",
|
|
1091
|
+
status: local.status,
|
|
1092
|
+
legacyM1WarningPath: local.legacyM1WarningPath
|
|
1093
|
+
};
|
|
1094
|
+
}
|
|
1095
|
+
async function warnLegacyM1IfPresent(workspaceRoot) {
|
|
1096
|
+
const candidate = path8.join(path8.resolve(workspaceRoot), CANONICAL_M1_FILENAME);
|
|
1097
|
+
try {
|
|
1098
|
+
await fs6.access(candidate);
|
|
1099
|
+
process.stderr.write(
|
|
1100
|
+
`[memoraone-mcp] warning: ignoring legacy ${CANONICAL_M1_FILENAME} (not used for credentials or binding)
|
|
1101
|
+
`
|
|
1102
|
+
);
|
|
1103
|
+
return candidate;
|
|
1104
|
+
} catch {
|
|
1105
|
+
return void 0;
|
|
227
1106
|
}
|
|
228
|
-
return { apiKey: null, apiKeySource: "none" };
|
|
229
1107
|
}
|
|
230
|
-
async function resolveAuthoritativeBinding(workspaceRoot,
|
|
231
|
-
const respectExplicitM1Path = options.respectExplicitM1Path !== false;
|
|
232
|
-
if (respectExplicitM1Path) {
|
|
233
|
-
const explicitBinding = await resolveProjectIdFromExplicitM1Path();
|
|
234
|
-
if (explicitBinding) {
|
|
235
|
-
const resolved = resolveApiKeyWithSource(explicitBinding.apiKey);
|
|
236
|
-
return {
|
|
237
|
-
projectId: explicitBinding.projectId,
|
|
238
|
-
workspaceRoot: path2.dirname(explicitBinding.foundAt),
|
|
239
|
-
m1Path: explicitBinding.foundAt,
|
|
240
|
-
apiKey: resolved.apiKey,
|
|
241
|
-
...explicitBinding.environment !== void 0 ? { environment: explicitBinding.environment } : {},
|
|
242
|
-
bindingSource: "explicit-m1-path",
|
|
243
|
-
apiKeySource: resolved.apiKeySource
|
|
244
|
-
};
|
|
245
|
-
}
|
|
246
|
-
}
|
|
1108
|
+
async function resolveAuthoritativeBinding(workspaceRoot, _options = {}) {
|
|
247
1109
|
const candidates = normalizeWorkspaceSearchRoots(workspaceRoot);
|
|
248
1110
|
if (candidates.length === 0) {
|
|
249
|
-
throw new
|
|
1111
|
+
throw new ReconnectRequiredError(
|
|
1112
|
+
"[memoraone-mcp] Could not resolve workspace root. Open a connected repository folder.\nRun: memoraone-mcp connect <code>"
|
|
1113
|
+
);
|
|
250
1114
|
}
|
|
251
1115
|
const bindings = [];
|
|
252
1116
|
for (const root of candidates) {
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
const
|
|
256
|
-
bindings.push(
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
bindingSource: "workspace-search",
|
|
263
|
-
apiKeySource: resolved.apiKeySource
|
|
264
|
-
});
|
|
1117
|
+
await warnLegacyM1IfPresent(root);
|
|
1118
|
+
try {
|
|
1119
|
+
const local = await resolveLocalBinding(root);
|
|
1120
|
+
bindings.push(toResolvedBinding(local));
|
|
1121
|
+
} catch (err) {
|
|
1122
|
+
if (err instanceof ReconnectRequiredError) {
|
|
1123
|
+
continue;
|
|
1124
|
+
}
|
|
1125
|
+
throw err;
|
|
265
1126
|
}
|
|
266
1127
|
}
|
|
267
1128
|
if (bindings.length === 0) {
|
|
268
|
-
throw new
|
|
1129
|
+
throw new ReconnectRequiredError(
|
|
1130
|
+
"[memoraone-mcp] No local MemoraOne binding for this workspace.\nRun: memoraone-mcp connect <code>"
|
|
1131
|
+
);
|
|
269
1132
|
}
|
|
270
|
-
const
|
|
271
|
-
if (
|
|
1133
|
+
const distinctIds = new Set(bindings.map((b) => b.repositoryBindingId));
|
|
1134
|
+
if (distinctIds.size > 1) {
|
|
272
1135
|
const lines = bindings.map(
|
|
273
|
-
(b) => ` - workspace=${b.workspaceRoot}
|
|
1136
|
+
(b) => ` - workspace=${b.workspaceRoot} binding=${b.repositoryBindingId} project=${b.projectId}`
|
|
274
1137
|
);
|
|
275
1138
|
throw new Error(
|
|
276
|
-
"[memoraone-mcp] Ambiguous workspace binding: multiple open roots map to different
|
|
1139
|
+
"[memoraone-mcp] Ambiguous workspace binding: multiple open roots map to different repository bindings.\n" + lines.join("\n") + "\nOpen one repo per window."
|
|
277
1140
|
);
|
|
278
1141
|
}
|
|
279
1142
|
return bindings[0];
|
|
280
1143
|
}
|
|
1144
|
+
function normalizeWorkspaceSearchRoots(workspaceRoot) {
|
|
1145
|
+
if (workspaceRoot === void 0) {
|
|
1146
|
+
return [];
|
|
1147
|
+
}
|
|
1148
|
+
const list = Array.isArray(workspaceRoot) ? workspaceRoot : [workspaceRoot];
|
|
1149
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1150
|
+
const out = [];
|
|
1151
|
+
for (const raw of list) {
|
|
1152
|
+
if (raw === void 0) continue;
|
|
1153
|
+
const trimmed = String(raw).trim();
|
|
1154
|
+
if (trimmed === "") continue;
|
|
1155
|
+
const resolved = path8.resolve(trimmed);
|
|
1156
|
+
if (!seen.has(resolved)) {
|
|
1157
|
+
seen.add(resolved);
|
|
1158
|
+
out.push(resolved);
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
return out;
|
|
1162
|
+
}
|
|
281
1163
|
function bindingRelevantValuesMatch(a, b) {
|
|
282
1164
|
const envA = a.environment ?? void 0;
|
|
283
1165
|
const envB = b.environment ?? void 0;
|
|
284
|
-
return a.projectId.trim().toLowerCase() === b.projectId.trim().toLowerCase() &&
|
|
1166
|
+
return a.repositoryBindingId === b.repositoryBindingId && a.projectId.trim().toLowerCase() === b.projectId.trim().toLowerCase() && path8.resolve(a.workspaceRoot) === path8.resolve(b.workspaceRoot) && (a.installationPublicId ?? void 0) === (b.installationPublicId ?? void 0) && envA === envB && a.status === b.status;
|
|
285
1167
|
}
|
|
286
1168
|
async function reconcileResolvedBindingWithDisk(cached) {
|
|
287
|
-
const
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
1169
|
+
const repositoryBindingId = assertRepositoryBindingId(cached.repositoryBindingId);
|
|
1170
|
+
const record = await readBindingRecord(repositoryBindingId);
|
|
1171
|
+
if (!record) {
|
|
1172
|
+
throw new ReconnectRequiredError(
|
|
1173
|
+
`[memoraone-mcp] Cached binding missing for ${repositoryBindingId}. Run: memoraone-mcp connect <code>`
|
|
291
1174
|
);
|
|
292
1175
|
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
1176
|
+
const workspaceRoot = path8.resolve(record.workspaceRoot);
|
|
1177
|
+
const identity = await captureRootFilesystemIdentity(workspaceRoot);
|
|
1178
|
+
if (!identitiesMatch(record.filesystemIdentity, identity)) {
|
|
1179
|
+
throw new ReconnectRequiredError(
|
|
1180
|
+
"[memoraone-mcp] Workspace filesystem identity changed. Run: memoraone-mcp connect <code>"
|
|
1181
|
+
);
|
|
1182
|
+
}
|
|
1183
|
+
if (record.status === "reconnect_required" || !record.projectId) {
|
|
1184
|
+
throw new ReconnectRequiredError(
|
|
1185
|
+
"[memoraone-mcp] Installation requires reconnect. Run: memoraone-mcp connect <code>"
|
|
1186
|
+
);
|
|
303
1187
|
}
|
|
304
|
-
const parsed = parseAndValidateM1(content, m1Path);
|
|
305
|
-
const resolved = resolveApiKeyWithSource(parsed.apiKey);
|
|
306
1188
|
const fresh = {
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
bindingSource:
|
|
313
|
-
|
|
1189
|
+
repositoryBindingId,
|
|
1190
|
+
projectId: record.projectId,
|
|
1191
|
+
workspaceRoot,
|
|
1192
|
+
installationPublicId: record.installationPublicId,
|
|
1193
|
+
environment: record.environment,
|
|
1194
|
+
bindingSource: "local-binding",
|
|
1195
|
+
status: record.status
|
|
314
1196
|
};
|
|
315
1197
|
if (bindingRelevantValuesMatch(cached, fresh)) {
|
|
316
1198
|
return { binding: fresh, cacheRefreshed: false };
|
|
@@ -318,59 +1200,68 @@ async function reconcileResolvedBindingWithDisk(cached) {
|
|
|
318
1200
|
return { binding: fresh, cacheRefreshed: true };
|
|
319
1201
|
}
|
|
320
1202
|
function encodeResolvedBinding(binding) {
|
|
321
|
-
|
|
1203
|
+
const payload = {
|
|
1204
|
+
repositoryBindingId: binding.repositoryBindingId,
|
|
1205
|
+
projectId: binding.projectId,
|
|
1206
|
+
workspaceRoot: binding.workspaceRoot,
|
|
1207
|
+
installationPublicId: binding.installationPublicId,
|
|
1208
|
+
environment: binding.environment,
|
|
1209
|
+
bindingSource: binding.bindingSource,
|
|
1210
|
+
status: binding.status
|
|
1211
|
+
};
|
|
1212
|
+
return Buffer.from(JSON.stringify(payload), "utf8").toString("base64");
|
|
322
1213
|
}
|
|
323
1214
|
function decodeResolvedBinding(value) {
|
|
324
1215
|
if (!value) {
|
|
325
1216
|
return null;
|
|
326
1217
|
}
|
|
327
|
-
let
|
|
1218
|
+
let parsed2;
|
|
328
1219
|
try {
|
|
329
|
-
|
|
1220
|
+
parsed2 = JSON.parse(Buffer.from(value, "base64").toString("utf8"));
|
|
330
1221
|
} catch {
|
|
331
1222
|
throw new Error("[memoraone-mcp] Invalid encoded binding payload");
|
|
332
1223
|
}
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
const
|
|
339
|
-
const
|
|
1224
|
+
if (parsed2?.apiKey || parsed2?.accessToken || parsed2?.refreshToken || parsed2?.m1Path) {
|
|
1225
|
+
throw new Error(
|
|
1226
|
+
"[memoraone-mcp] Rejected legacy daemon binding payload containing secrets or .m1 path"
|
|
1227
|
+
);
|
|
1228
|
+
}
|
|
1229
|
+
const repositoryBindingId = parsed2?.repositoryBindingId;
|
|
1230
|
+
const projectId = parsed2?.projectId;
|
|
1231
|
+
const workspaceRoot = parsed2?.workspaceRoot;
|
|
1232
|
+
const environment = normalizeEnvironment(parsed2?.environment);
|
|
1233
|
+
const status = parsed2?.status;
|
|
1234
|
+
if (!repositoryBindingId || !isRepositoryBindingId(String(repositoryBindingId))) {
|
|
1235
|
+
throw new Error("[memoraone-mcp] Invalid binding repositoryBindingId");
|
|
1236
|
+
}
|
|
340
1237
|
if (!projectId || typeof projectId !== "string" || !uuidRegex.test(projectId.trim())) {
|
|
341
1238
|
throw new Error("[memoraone-mcp] Invalid binding projectId");
|
|
342
1239
|
}
|
|
343
1240
|
if (!workspaceRoot || typeof workspaceRoot !== "string") {
|
|
344
1241
|
throw new Error("[memoraone-mcp] Invalid binding workspaceRoot");
|
|
345
1242
|
}
|
|
346
|
-
if (
|
|
347
|
-
throw new Error("[memoraone-mcp] Invalid binding
|
|
348
|
-
}
|
|
349
|
-
if (apiKey !== null && apiKey !== void 0 && typeof apiKey !== "string") {
|
|
350
|
-
throw new Error("[memoraone-mcp] Invalid binding apiKey");
|
|
1243
|
+
if (status !== "connected" && status !== "reconnect_required" && status !== "pending") {
|
|
1244
|
+
throw new Error("[memoraone-mcp] Invalid binding status");
|
|
351
1245
|
}
|
|
352
|
-
if (bindingSource !== "
|
|
1246
|
+
if (parsed2?.bindingSource !== "local-binding") {
|
|
353
1247
|
throw new Error("[memoraone-mcp] Invalid binding source");
|
|
354
1248
|
}
|
|
355
|
-
if (apiKeySource !== "env" && apiKeySource !== "memoraone.m1" && apiKeySource !== "none") {
|
|
356
|
-
throw new Error("[memoraone-mcp] Invalid binding apiKeySource");
|
|
357
|
-
}
|
|
358
1249
|
return {
|
|
1250
|
+
repositoryBindingId: String(repositoryBindingId),
|
|
359
1251
|
projectId: projectId.trim(),
|
|
360
1252
|
workspaceRoot,
|
|
361
|
-
|
|
362
|
-
apiKey: typeof apiKey === "string" && apiKey.trim() !== "" ? apiKey.trim() : null,
|
|
1253
|
+
installationPublicId: typeof parsed2?.installationPublicId === "string" ? parsed2.installationPublicId : void 0,
|
|
363
1254
|
...environment !== void 0 ? { environment } : {},
|
|
364
|
-
bindingSource,
|
|
365
|
-
|
|
1255
|
+
bindingSource: "local-binding",
|
|
1256
|
+
status
|
|
366
1257
|
};
|
|
367
1258
|
}
|
|
368
1259
|
|
|
369
1260
|
// src/socketPaths.ts
|
|
370
|
-
var
|
|
371
|
-
var
|
|
372
|
-
var
|
|
373
|
-
var BASE_DIR = process.env.MEMORAONE_MCP_LOCK_DIR ||
|
|
1261
|
+
var os3 = __toESM(require("os"), 1);
|
|
1262
|
+
var path9 = __toESM(require("path"), 1);
|
|
1263
|
+
var fs7 = __toESM(require("fs"), 1);
|
|
1264
|
+
var BASE_DIR = process.env.MEMORAONE_MCP_LOCK_DIR || path9.join(os3.homedir(), ".memoraone-mcp");
|
|
374
1265
|
var HASH_SOCKET_FILENAME_RE = new RegExp(
|
|
375
1266
|
`^mcp-[0-9a-f]{${BINDING_SOCKET_HASH_LENGTH}}\\.sock$`,
|
|
376
1267
|
"i"
|
|
@@ -388,48 +1279,48 @@ function parseIdeType(value) {
|
|
|
388
1279
|
}
|
|
389
1280
|
return value;
|
|
390
1281
|
}
|
|
391
|
-
function resolveIdeTypeFromEnv(
|
|
392
|
-
return parseIdeType(
|
|
1282
|
+
function resolveIdeTypeFromEnv(env2 = process.env) {
|
|
1283
|
+
return parseIdeType(env2.MEMORAONE_IDE_TYPE);
|
|
393
1284
|
}
|
|
394
1285
|
function parseIdeTypeFromCommandLine(commandLine) {
|
|
395
1286
|
const match = commandLine.match(/--ide\s+(cursor|copilot-vscode|jetbrains)(?:\s|$)/);
|
|
396
1287
|
return match ? match[1] : void 0;
|
|
397
1288
|
}
|
|
398
|
-
function buildDaemonSpawnArgs(scriptPath,
|
|
399
|
-
const args2 = [scriptPath, "--daemon", "--
|
|
400
|
-
const ideType = resolveIdeTypeFromEnv(
|
|
1289
|
+
function buildDaemonSpawnArgs(scriptPath, repositoryBindingId, env2 = process.env) {
|
|
1290
|
+
const args2 = [scriptPath, "--daemon", "--binding-id", repositoryBindingId];
|
|
1291
|
+
const ideType = resolveIdeTypeFromEnv(env2);
|
|
401
1292
|
if (ideType) {
|
|
402
1293
|
args2.push("--ide", ideType);
|
|
403
1294
|
}
|
|
404
1295
|
return args2;
|
|
405
1296
|
}
|
|
406
|
-
function resolveBindingIdeType(
|
|
407
|
-
return resolveIdeTypeFromEnv(
|
|
1297
|
+
function resolveBindingIdeType(env2 = process.env) {
|
|
1298
|
+
return resolveIdeTypeFromEnv(env2) ?? "";
|
|
408
1299
|
}
|
|
409
|
-
function getBindingSocketFilename(binding,
|
|
410
|
-
const ideType = resolveBindingIdeType(
|
|
411
|
-
const hash = hashBindingIdentity(binding.
|
|
1300
|
+
function getBindingSocketFilename(binding, env2 = process.env) {
|
|
1301
|
+
const ideType = resolveBindingIdeType(env2);
|
|
1302
|
+
const hash = hashBindingIdentity(binding.repositoryBindingId, binding.workspaceRoot, ideType);
|
|
412
1303
|
return `mcp-${hash}.sock`;
|
|
413
1304
|
}
|
|
414
|
-
function getBindingSocketPath(binding,
|
|
415
|
-
return
|
|
1305
|
+
function getBindingSocketPath(binding, env2 = process.env) {
|
|
1306
|
+
return path9.join(BASE_DIR, getBindingSocketFilename(binding, env2));
|
|
416
1307
|
}
|
|
417
1308
|
function ensureBaseDir() {
|
|
418
|
-
|
|
1309
|
+
fs7.mkdirSync(BASE_DIR, { recursive: true });
|
|
419
1310
|
return BASE_DIR;
|
|
420
1311
|
}
|
|
421
1312
|
function isHashSocketFilename(filename) {
|
|
422
|
-
return HASH_SOCKET_FILENAME_RE.test(
|
|
1313
|
+
return HASH_SOCKET_FILENAME_RE.test(path9.basename(filename));
|
|
423
1314
|
}
|
|
424
1315
|
function isLegacySocketFilename(filename) {
|
|
425
|
-
return LEGACY_SOCKET_FILENAME_RE.test(
|
|
1316
|
+
return LEGACY_SOCKET_FILENAME_RE.test(path9.basename(filename));
|
|
426
1317
|
}
|
|
427
1318
|
function isMemoraoneSocketFilename(filename) {
|
|
428
|
-
const base =
|
|
1319
|
+
const base = path9.basename(filename);
|
|
429
1320
|
return HASH_SOCKET_FILENAME_RE.test(base) || LEGACY_SOCKET_PROJECT_ID_RE.test(base);
|
|
430
1321
|
}
|
|
431
1322
|
function extractProjectIdFromSocketFilename(filename) {
|
|
432
|
-
const match =
|
|
1323
|
+
const match = path9.basename(filename).match(LEGACY_SOCKET_PROJECT_ID_RE);
|
|
433
1324
|
return match ? match[1].toLowerCase() : null;
|
|
434
1325
|
}
|
|
435
1326
|
function isSocketFilenameForProject(filename, projectId) {
|
|
@@ -437,7 +1328,7 @@ function isSocketFilenameForProject(filename, projectId) {
|
|
|
437
1328
|
return extracted !== null && extracted === projectId.trim().toLowerCase();
|
|
438
1329
|
}
|
|
439
1330
|
function extractIdeTypeFromSocketFilename(filename) {
|
|
440
|
-
const match =
|
|
1331
|
+
const match = path9.basename(filename).match(LEGACY_SOCKET_FILENAME_RE);
|
|
441
1332
|
if (!match) return null;
|
|
442
1333
|
const ide = match[3];
|
|
443
1334
|
if (ide === void 0) return "legacy";
|
|
@@ -464,18 +1355,22 @@ function bindingSidecarPath(socketPath) {
|
|
|
464
1355
|
}
|
|
465
1356
|
function parseSidecarRecord(raw) {
|
|
466
1357
|
try {
|
|
467
|
-
const
|
|
468
|
-
if (!
|
|
1358
|
+
const parsed2 = JSON.parse(raw);
|
|
1359
|
+
if (!parsed2?.binding) {
|
|
469
1360
|
return null;
|
|
470
1361
|
}
|
|
471
|
-
|
|
1362
|
+
if (parsed2.m1Path) {
|
|
1363
|
+
throw new Error("[memoraone-mcp] Rejected legacy sidecar with m1Path");
|
|
1364
|
+
}
|
|
1365
|
+
const binding = decodeResolvedBinding(parsed2.binding);
|
|
1366
|
+
if (!binding) return null;
|
|
472
1367
|
return {
|
|
473
|
-
v: typeof
|
|
474
|
-
...
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
binding:
|
|
1368
|
+
v: typeof parsed2.v === "number" ? parsed2.v : 3,
|
|
1369
|
+
...parsed2.ideType ? { ideType: parsed2.ideType } : {},
|
|
1370
|
+
repositoryBindingId: parsed2.repositoryBindingId ?? binding.repositoryBindingId,
|
|
1371
|
+
projectId: parsed2.projectId ?? binding.projectId,
|
|
1372
|
+
workspaceRoot: parsed2.workspaceRoot ?? binding.workspaceRoot,
|
|
1373
|
+
binding: parsed2.binding
|
|
479
1374
|
};
|
|
480
1375
|
} catch {
|
|
481
1376
|
return null;
|
|
@@ -483,7 +1378,7 @@ function parseSidecarRecord(raw) {
|
|
|
483
1378
|
}
|
|
484
1379
|
function readBindingSidecarRecord(socketPath) {
|
|
485
1380
|
try {
|
|
486
|
-
const raw =
|
|
1381
|
+
const raw = fs8.readFileSync(bindingSidecarPath(socketPath), "utf8");
|
|
487
1382
|
return parseSidecarRecord(raw);
|
|
488
1383
|
} catch {
|
|
489
1384
|
return null;
|
|
@@ -498,14 +1393,14 @@ function readBindingSidecar(socketPath) {
|
|
|
498
1393
|
}
|
|
499
1394
|
function removeBindingSidecar(socketPath) {
|
|
500
1395
|
try {
|
|
501
|
-
|
|
1396
|
+
fs8.unlinkSync(bindingSidecarPath(socketPath));
|
|
502
1397
|
} catch {
|
|
503
1398
|
}
|
|
504
1399
|
}
|
|
505
1400
|
function removeDaemonSocketArtifacts(socketPath) {
|
|
506
1401
|
removeBindingSidecar(socketPath);
|
|
507
1402
|
try {
|
|
508
|
-
|
|
1403
|
+
fs8.unlinkSync(socketPath);
|
|
509
1404
|
} catch {
|
|
510
1405
|
}
|
|
511
1406
|
}
|
|
@@ -514,26 +1409,29 @@ function isDaemonBindingMismatchError(err) {
|
|
|
514
1409
|
}
|
|
515
1410
|
function formatBindingMismatchError(socketPath, sidecar, expected, detail) {
|
|
516
1411
|
const lines = [
|
|
517
|
-
`[memoraone-mcp] Daemon socket binding mismatch at ${
|
|
518
|
-
` socket:
|
|
519
|
-
` session:
|
|
1412
|
+
`[memoraone-mcp] Daemon socket binding mismatch at ${path10.basename(socketPath)}.`,
|
|
1413
|
+
` socket: binding=${sidecar.repositoryBindingId} project=${sidecar.projectId} workspace=${sidecar.workspaceRoot}`,
|
|
1414
|
+
` session: binding=${expected.repositoryBindingId} project=${expected.projectId} workspace=${expected.workspaceRoot}`
|
|
520
1415
|
];
|
|
521
1416
|
if (detail) {
|
|
522
1417
|
lines.push(` ${detail}`);
|
|
523
1418
|
}
|
|
524
|
-
lines.push("The bridge will replace this stale daemon automatically from
|
|
1419
|
+
lines.push("The bridge will replace this stale daemon automatically from local binding state.");
|
|
525
1420
|
return lines.join("\n");
|
|
526
1421
|
}
|
|
527
|
-
function verifyDaemonSidecarBinding(socketPath, expected,
|
|
1422
|
+
function verifyDaemonSidecarBinding(socketPath, expected, env2 = process.env) {
|
|
528
1423
|
const record = readBindingSidecarRecord(socketPath);
|
|
529
1424
|
if (!record) {
|
|
530
1425
|
return null;
|
|
531
1426
|
}
|
|
532
1427
|
const sidecar = decodeResolvedBinding(record.binding);
|
|
1428
|
+
if (!sidecar) {
|
|
1429
|
+
return null;
|
|
1430
|
+
}
|
|
533
1431
|
if (!bindingsMatch(sidecar, expected)) {
|
|
534
1432
|
throw new Error(formatBindingMismatchError(socketPath, sidecar, expected));
|
|
535
1433
|
}
|
|
536
|
-
const expectedIdeType = resolveBindingIdeType(
|
|
1434
|
+
const expectedIdeType = resolveBindingIdeType(env2);
|
|
537
1435
|
if (record.ideType !== void 0 && record.ideType !== expectedIdeType) {
|
|
538
1436
|
throw new Error(
|
|
539
1437
|
formatBindingMismatchError(
|
|
@@ -550,11 +1448,11 @@ function verifyDaemonSidecarBinding(socketPath, expected, env = process.env) {
|
|
|
550
1448
|
// src/bridgeClientRoots.ts
|
|
551
1449
|
var readline = __toESM(require("readline"), 1);
|
|
552
1450
|
var TRUTHY = /* @__PURE__ */ new Set(["1", "true", "yes", "on"]);
|
|
553
|
-
function isInitializeDebugEnabled(
|
|
554
|
-
return TRUTHY.has(String(
|
|
1451
|
+
function isInitializeDebugEnabled(env2 = process.env) {
|
|
1452
|
+
return TRUTHY.has(String(env2.MEMORAONE_DEBUG_INIT ?? "").trim().toLowerCase());
|
|
555
1453
|
}
|
|
556
|
-
function logInitializeDebug(log,
|
|
557
|
-
if (isInitializeDebugEnabled(
|
|
1454
|
+
function logInitializeDebug(log, env2, msg) {
|
|
1455
|
+
if (isInitializeDebugEnabled(env2)) {
|
|
558
1456
|
log(`[init-debug] ${msg}`);
|
|
559
1457
|
}
|
|
560
1458
|
}
|
|
@@ -630,8 +1528,8 @@ var StdioLineReader = class {
|
|
|
630
1528
|
if (this.closed) {
|
|
631
1529
|
return null;
|
|
632
1530
|
}
|
|
633
|
-
return new Promise((
|
|
634
|
-
this.waiters.push(
|
|
1531
|
+
return new Promise((resolve17) => {
|
|
1532
|
+
this.waiters.push(resolve17);
|
|
635
1533
|
});
|
|
636
1534
|
}
|
|
637
1535
|
/** Re-queue lines read during an intermediate protocol step (e.g. roots/list) for the main bridge loop. */
|
|
@@ -649,7 +1547,7 @@ var StdioLineReader = class {
|
|
|
649
1547
|
};
|
|
650
1548
|
var nextBridgeRequestId = 1e5;
|
|
651
1549
|
async function requestClientRootsListUris(options) {
|
|
652
|
-
const
|
|
1550
|
+
const env2 = options.env ?? process.env;
|
|
653
1551
|
const log = options.log ?? (() => {
|
|
654
1552
|
});
|
|
655
1553
|
const requestId = nextBridgeRequestId++;
|
|
@@ -662,7 +1560,7 @@ async function requestClientRootsListUris(options) {
|
|
|
662
1560
|
};
|
|
663
1561
|
logInitializeDebug(
|
|
664
1562
|
log,
|
|
665
|
-
|
|
1563
|
+
env2,
|
|
666
1564
|
`sending roots/list id=${requestId} clientDeclaresRoots=${clientDeclaresRootsCapability(options.initializeParams)}`
|
|
667
1565
|
);
|
|
668
1566
|
options.stdout.write(`${JSON.stringify(request)}
|
|
@@ -680,13 +1578,13 @@ async function requestClientRootsListUris(options) {
|
|
|
680
1578
|
try {
|
|
681
1579
|
message = JSON.parse(trimmed);
|
|
682
1580
|
} catch (err) {
|
|
683
|
-
logInitializeDebug(log,
|
|
1581
|
+
logInitializeDebug(log, env2, `ignored non-JSON line while waiting for roots/list: ${String(err)}`);
|
|
684
1582
|
continue;
|
|
685
1583
|
}
|
|
686
1584
|
if (message.id !== requestId) {
|
|
687
1585
|
logInitializeDebug(
|
|
688
1586
|
log,
|
|
689
|
-
|
|
1587
|
+
env2,
|
|
690
1588
|
`deferred JSON-RPC while waiting for roots/list id=${requestId}: ${trimmed.slice(0, 200)}`
|
|
691
1589
|
);
|
|
692
1590
|
deferredLines.push(trimmed);
|
|
@@ -698,32 +1596,32 @@ async function requestClientRootsListUris(options) {
|
|
|
698
1596
|
);
|
|
699
1597
|
}
|
|
700
1598
|
const uris = extractRootsUrisFromListResult(message.result);
|
|
701
|
-
logInitializeDebug(log,
|
|
1599
|
+
logInitializeDebug(log, env2, `roots/list id=${requestId} returned ${uris.length}: ${JSON.stringify(uris)}`);
|
|
702
1600
|
return { uris, deferredLines };
|
|
703
1601
|
}
|
|
704
1602
|
}
|
|
705
1603
|
|
|
706
1604
|
// src/cleanup.ts
|
|
707
|
-
var
|
|
708
|
-
var
|
|
1605
|
+
var fs11 = __toESM(require("fs/promises"), 1);
|
|
1606
|
+
var path15 = __toESM(require("path"), 1);
|
|
709
1607
|
var readline2 = __toESM(require("readline/promises"), 1);
|
|
710
|
-
var
|
|
711
|
-
var
|
|
1608
|
+
var import_node_child_process3 = require("child_process");
|
|
1609
|
+
var import_node_util3 = require("util");
|
|
712
1610
|
var import_node_process = require("process");
|
|
713
1611
|
|
|
714
1612
|
// src/cursorGlobalMcpConfig.ts
|
|
715
|
-
var
|
|
716
|
-
var
|
|
717
|
-
var
|
|
718
|
-
var
|
|
719
|
-
var
|
|
1613
|
+
var fs10 = __toESM(require("fs/promises"), 1);
|
|
1614
|
+
var os4 = __toESM(require("os"), 1);
|
|
1615
|
+
var path14 = __toESM(require("path"), 1);
|
|
1616
|
+
var import_node_child_process2 = require("child_process");
|
|
1617
|
+
var import_node_util2 = require("util");
|
|
720
1618
|
|
|
721
1619
|
// src/initializeBinding.ts
|
|
722
|
-
var
|
|
1620
|
+
var path11 = __toESM(require("path"), 1);
|
|
723
1621
|
var import_node_url = require("url");
|
|
724
1622
|
var MEMORAONE_WORKSPACE_ROOT_ENV = "MEMORAONE_WORKSPACE_ROOT";
|
|
725
|
-
function getBridgeBindingResolveOptions(
|
|
726
|
-
const ideType = resolveIdeTypeFromEnv(
|
|
1623
|
+
function getBridgeBindingResolveOptions(env2 = process.env) {
|
|
1624
|
+
const ideType = resolveIdeTypeFromEnv(env2);
|
|
727
1625
|
if (ideType === "cursor") {
|
|
728
1626
|
return { respectExplicitM1Path: false, allowEnvWorkspaceFallback: false };
|
|
729
1627
|
}
|
|
@@ -739,8 +1637,8 @@ function getEnvWorkspaceRootCandidates() {
|
|
|
739
1637
|
const raw = process.env.WORKSPACE_FOLDER_PATHS;
|
|
740
1638
|
const parts = [];
|
|
741
1639
|
if (raw !== void 0 && raw.trim() !== "") {
|
|
742
|
-
for (const p of raw.split(
|
|
743
|
-
parts.push(
|
|
1640
|
+
for (const p of raw.split(path11.delimiter).map((s) => s.trim()).filter(Boolean)) {
|
|
1641
|
+
parts.push(path11.resolve(p));
|
|
744
1642
|
}
|
|
745
1643
|
}
|
|
746
1644
|
parts.push(process.cwd());
|
|
@@ -764,7 +1662,7 @@ function extractWorkspaceRootsFromInitialize(params) {
|
|
|
764
1662
|
if (uri === void 0 || uri.trim() === "") {
|
|
765
1663
|
return;
|
|
766
1664
|
}
|
|
767
|
-
const resolved =
|
|
1665
|
+
const resolved = path11.resolve(uriToPath(uri));
|
|
768
1666
|
if (!seen.has(resolved)) {
|
|
769
1667
|
seen.add(resolved);
|
|
770
1668
|
roots.push(resolved);
|
|
@@ -781,16 +1679,16 @@ function extractWorkspaceRootsFromInitialize(params) {
|
|
|
781
1679
|
}
|
|
782
1680
|
return roots;
|
|
783
1681
|
}
|
|
784
|
-
function getRepoScopedWorkspaceHint(
|
|
785
|
-
const raw =
|
|
1682
|
+
function getRepoScopedWorkspaceHint(env2 = process.env) {
|
|
1683
|
+
const raw = env2[MEMORAONE_WORKSPACE_ROOT_ENV];
|
|
786
1684
|
if (raw === void 0 || raw.trim() === "") {
|
|
787
1685
|
return null;
|
|
788
1686
|
}
|
|
789
|
-
return
|
|
1687
|
+
return path11.resolve(raw.trim());
|
|
790
1688
|
}
|
|
791
1689
|
function formatWorkspaceAmbiguityError(bindings) {
|
|
792
1690
|
const lines = bindings.map(
|
|
793
|
-
(b) => ` - workspace=${b.workspaceRoot}
|
|
1691
|
+
(b) => ` - workspace=${b.workspaceRoot} binding=${b.repositoryBindingId} project=${b.projectId}`
|
|
794
1692
|
);
|
|
795
1693
|
return "[memoraone-mcp] Ambiguous workspace binding: multiple open roots map to different MemoraOne projects.\n" + lines.join("\n") + `
|
|
796
1694
|
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.`;
|
|
@@ -798,7 +1696,7 @@ Open one repo per Cursor window, or ensure this repo's managed .cursor/mcp.json
|
|
|
798
1696
|
function formatRepoHintInitializeMismatchError(repoHintRoot, initializeBinding) {
|
|
799
1697
|
return `[memoraone-mcp] Repo-scoped workspace hint conflicts with MCP initialize workspace.
|
|
800
1698
|
${MEMORAONE_WORKSPACE_ROOT_ENV}: ${repoHintRoot}
|
|
801
|
-
initialize:
|
|
1699
|
+
initialize: binding=${initializeBinding.repositoryBindingId} project=${initializeBinding.projectId} workspace=${initializeBinding.workspaceRoot}
|
|
802
1700
|
Reload MCP in the Cursor window for this repo, or re-run setup-ide-files --cursor here if the hint is stale.`;
|
|
803
1701
|
}
|
|
804
1702
|
function formatRepoHintNotInRootsListError(repoHintRoot, rootsListPaths) {
|
|
@@ -809,7 +1707,9 @@ Reload MCP in the Cursor window for this repo, or re-run setup-ide-files --curso
|
|
|
809
1707
|
}
|
|
810
1708
|
async function resolveBindingFromWorkspaceRoots(workspaceRoots, options = {}) {
|
|
811
1709
|
if (workspaceRoots.length === 0) {
|
|
812
|
-
throw new Error(
|
|
1710
|
+
throw new Error(
|
|
1711
|
+
"[memoraone-mcp] No local MemoraOne binding for this workspace.\nRun: memoraone-mcp connect <code>"
|
|
1712
|
+
);
|
|
813
1713
|
}
|
|
814
1714
|
const bindings = [];
|
|
815
1715
|
for (const root of workspaceRoots) {
|
|
@@ -820,14 +1720,16 @@ async function resolveBindingFromWorkspaceRoots(workspaceRoots, options = {}) {
|
|
|
820
1720
|
})
|
|
821
1721
|
);
|
|
822
1722
|
} catch (err) {
|
|
823
|
-
if (err instanceof Error && err.message.includes("
|
|
1723
|
+
if (err instanceof Error && (err.message.includes("No local MemoraOne binding") || err.message.includes("memoraone-mcp connect") || err.name === "ReconnectRequiredError")) {
|
|
824
1724
|
continue;
|
|
825
1725
|
}
|
|
826
1726
|
throw err;
|
|
827
1727
|
}
|
|
828
1728
|
}
|
|
829
1729
|
if (bindings.length === 0) {
|
|
830
|
-
throw new Error(
|
|
1730
|
+
throw new Error(
|
|
1731
|
+
"[memoraone-mcp] No local MemoraOne binding for this workspace.\nRun: memoraone-mcp connect <code>"
|
|
1732
|
+
);
|
|
831
1733
|
}
|
|
832
1734
|
const distinctProjectIds = new Set(bindings.map((b) => b.projectId));
|
|
833
1735
|
if (distinctProjectIds.size > 1) {
|
|
@@ -836,12 +1738,12 @@ async function resolveBindingFromWorkspaceRoots(workspaceRoots, options = {}) {
|
|
|
836
1738
|
return bindings[0];
|
|
837
1739
|
}
|
|
838
1740
|
async function resolveBindingFromInitializeParams(params, options = {}) {
|
|
839
|
-
const
|
|
1741
|
+
const env2 = options.env ?? process.env;
|
|
840
1742
|
const resolveOpts = {
|
|
841
1743
|
respectExplicitM1Path: options.respectExplicitM1Path,
|
|
842
1744
|
allowEnvWorkspaceFallback: options.allowEnvWorkspaceFallback
|
|
843
1745
|
};
|
|
844
|
-
const repoHint = getRepoScopedWorkspaceHint(
|
|
1746
|
+
const repoHint = getRepoScopedWorkspaceHint(env2);
|
|
845
1747
|
const initializeRoots = extractWorkspaceRootsFromInitialize(params);
|
|
846
1748
|
if (initializeRoots.length > 0) {
|
|
847
1749
|
const binding = await resolveBindingFromWorkspaceRoots(initializeRoots, resolveOpts);
|
|
@@ -858,8 +1760,8 @@ async function resolveBindingFromInitializeParams(params, options = {}) {
|
|
|
858
1760
|
const rootsListUris = options.rootsListUris ?? [];
|
|
859
1761
|
const rootsListPaths = rootsListUris.map((uri) => uriToPath(uri)).filter(Boolean);
|
|
860
1762
|
if (rootsListPaths.length > 1 && repoHint !== null) {
|
|
861
|
-
const hintResolved =
|
|
862
|
-
const matchingRoot = rootsListPaths.find((root) =>
|
|
1763
|
+
const hintResolved = path11.resolve(repoHint);
|
|
1764
|
+
const matchingRoot = rootsListPaths.find((root) => path11.resolve(root) === hintResolved);
|
|
863
1765
|
if (!matchingRoot) {
|
|
864
1766
|
throw new Error(formatRepoHintNotInRootsListError(repoHint, rootsListPaths));
|
|
865
1767
|
}
|
|
@@ -885,24 +1787,206 @@ async function resolveBindingFromInitializeParams(params, options = {}) {
|
|
|
885
1787
|
});
|
|
886
1788
|
}
|
|
887
1789
|
|
|
1790
|
+
// src/packageExecutionMode.ts
|
|
1791
|
+
var path13 = __toESM(require("path"), 1);
|
|
1792
|
+
|
|
1793
|
+
// src/resolveBuiltCliPath.ts
|
|
1794
|
+
var fs9 = __toESM(require("fs/promises"), 1);
|
|
1795
|
+
var path12 = __toESM(require("path"), 1);
|
|
1796
|
+
var MONOREPO_CLI_REL = path12.join("packages", "mcp", "dist", "cli.cjs");
|
|
1797
|
+
async function pathExists(filePath) {
|
|
1798
|
+
try {
|
|
1799
|
+
await fs9.access(filePath);
|
|
1800
|
+
return true;
|
|
1801
|
+
} catch {
|
|
1802
|
+
return false;
|
|
1803
|
+
}
|
|
1804
|
+
}
|
|
1805
|
+
async function findMonorepoCliFrom(startDir) {
|
|
1806
|
+
let current = path12.resolve(startDir);
|
|
1807
|
+
const root = path12.parse(current).root;
|
|
1808
|
+
while (true) {
|
|
1809
|
+
const candidate = path12.join(current, MONOREPO_CLI_REL);
|
|
1810
|
+
if (await pathExists(candidate)) {
|
|
1811
|
+
return path12.resolve(candidate);
|
|
1812
|
+
}
|
|
1813
|
+
if (current === root) break;
|
|
1814
|
+
current = path12.dirname(current);
|
|
1815
|
+
}
|
|
1816
|
+
return null;
|
|
1817
|
+
}
|
|
1818
|
+
async function resolveFromRunningScript() {
|
|
1819
|
+
if (!process.argv[1]) return null;
|
|
1820
|
+
const script = path12.resolve(process.argv[1]);
|
|
1821
|
+
const base = path12.basename(script);
|
|
1822
|
+
if ((base === "cli.cjs" || base === "cli.ts" || base === "memoraone-mcp.cjs") && await pathExists(script)) {
|
|
1823
|
+
return script;
|
|
1824
|
+
}
|
|
1825
|
+
const here = path12.dirname(script);
|
|
1826
|
+
const candidates = [
|
|
1827
|
+
path12.join(here, "cli.cjs"),
|
|
1828
|
+
path12.join(here, "..", "dist", "cli.cjs"),
|
|
1829
|
+
path12.join(here, "..", "..", "dist", "cli.cjs")
|
|
1830
|
+
];
|
|
1831
|
+
for (const candidate of candidates) {
|
|
1832
|
+
if (await pathExists(candidate)) {
|
|
1833
|
+
return path12.resolve(candidate);
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
return null;
|
|
1837
|
+
}
|
|
1838
|
+
async function resolveBuiltCliPathAsync(options) {
|
|
1839
|
+
const preferRunning = options?.preferRunningScript !== false;
|
|
1840
|
+
if (preferRunning) {
|
|
1841
|
+
const fromRunning = await resolveFromRunningScript();
|
|
1842
|
+
if (fromRunning) return fromRunning;
|
|
1843
|
+
}
|
|
1844
|
+
const searchDirs = [];
|
|
1845
|
+
if (options?.searchFrom !== void 0) {
|
|
1846
|
+
const dirs = Array.isArray(options.searchFrom) ? options.searchFrom : [options.searchFrom];
|
|
1847
|
+
searchDirs.push(...dirs);
|
|
1848
|
+
}
|
|
1849
|
+
searchDirs.push(process.cwd());
|
|
1850
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1851
|
+
for (const dir of searchDirs) {
|
|
1852
|
+
const key = path12.resolve(dir);
|
|
1853
|
+
if (seen.has(key)) continue;
|
|
1854
|
+
seen.add(key);
|
|
1855
|
+
const found = await findMonorepoCliFrom(key);
|
|
1856
|
+
if (found) return found;
|
|
1857
|
+
}
|
|
1858
|
+
if (!preferRunning) {
|
|
1859
|
+
return resolveFromRunningScript();
|
|
1860
|
+
}
|
|
1861
|
+
return null;
|
|
1862
|
+
}
|
|
1863
|
+
|
|
1864
|
+
// src/packageExecutionMode.ts
|
|
1865
|
+
var PROD_API_URL = "https://api.memoraone.com";
|
|
1866
|
+
var STAGING_API_URL = "https://memora-api-staging-phbtrzocjq-uk.a.run.app";
|
|
1867
|
+
var STAGING_API_URL_PREFIX = "https://memora-api-staging-";
|
|
1868
|
+
function normalizeApiUrl(url) {
|
|
1869
|
+
return url.trim().replace(/\/+$/, "");
|
|
1870
|
+
}
|
|
1871
|
+
function memoraoneNpmPackageSpec(channel) {
|
|
1872
|
+
return channel === "staging" ? "@memoraone/mcp@staging" : "@memoraone/mcp@latest";
|
|
1873
|
+
}
|
|
1874
|
+
function isMemoraoneNpmPackageSpec(value) {
|
|
1875
|
+
return value === "@memoraone/mcp@staging" || value === "@memoraone/mcp@latest";
|
|
1876
|
+
}
|
|
1877
|
+
function npmPackageChannelFromEnvironment(environment) {
|
|
1878
|
+
return environment === "staging" ? "staging" : "latest";
|
|
1879
|
+
}
|
|
1880
|
+
function cursorEnvironmentFromPackageExecutionMode(mode) {
|
|
1881
|
+
if (mode.kind === "local") return "local";
|
|
1882
|
+
return mode.channel === "staging" ? "staging" : "production";
|
|
1883
|
+
}
|
|
1884
|
+
function npmPackageChannelFromApiUrl(apiUrl) {
|
|
1885
|
+
if (!apiUrl) return "latest";
|
|
1886
|
+
const normalized = normalizeApiUrl(apiUrl);
|
|
1887
|
+
if (normalized === STAGING_API_URL || normalized.startsWith(STAGING_API_URL_PREFIX)) {
|
|
1888
|
+
return "staging";
|
|
1889
|
+
}
|
|
1890
|
+
if (normalized === PROD_API_URL) return "latest";
|
|
1891
|
+
return "latest";
|
|
1892
|
+
}
|
|
1893
|
+
function looksLikeLocalMonorepoCliPath(scriptPath) {
|
|
1894
|
+
const normalized = scriptPath.replace(/\\/g, "/");
|
|
1895
|
+
if (normalized.includes("/node_modules/@memoraone/mcp/")) return false;
|
|
1896
|
+
if (normalized.includes("/.npm/_npx/")) return false;
|
|
1897
|
+
if (normalized.includes("/_npx/")) return false;
|
|
1898
|
+
const base = path13.basename(normalized);
|
|
1899
|
+
if (base === "cli.ts" || base === "cli.cjs" || base === "memoraone-mcp.cjs") {
|
|
1900
|
+
if (normalized.includes("/packages/mcp/dist/") || normalized.includes("/packages/mcp/dist-bin/") || normalized.includes("/packages/mcp/src/")) {
|
|
1901
|
+
return true;
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
return false;
|
|
1905
|
+
}
|
|
1906
|
+
function looksLikePublishedPackagePath(scriptPath) {
|
|
1907
|
+
const normalized = scriptPath.replace(/\\/g, "/");
|
|
1908
|
+
return normalized.includes("/node_modules/@memoraone/mcp/") || normalized.includes("/.npm/_npx/") || /\/_npx\//.test(normalized);
|
|
1909
|
+
}
|
|
1910
|
+
function channelFromEnv(env2) {
|
|
1911
|
+
const explicit = env2.MEMORAONE_NPM_CHANNEL?.trim().toLowerCase() || env2.npm_config_tag?.trim().toLowerCase();
|
|
1912
|
+
if (explicit === "staging") return "staging";
|
|
1913
|
+
if (explicit === "latest") return "latest";
|
|
1914
|
+
return void 0;
|
|
1915
|
+
}
|
|
1916
|
+
async function resolvePackageExecutionMode(options = {}) {
|
|
1917
|
+
if (options.executionMode) return options.executionMode;
|
|
1918
|
+
const env2 = options.env ?? process.env;
|
|
1919
|
+
const scriptPath = options.scriptPath !== void 0 ? options.scriptPath : process.argv[1] ? path13.resolve(process.argv[1]) : null;
|
|
1920
|
+
if (scriptPath && looksLikePublishedPackagePath(scriptPath)) {
|
|
1921
|
+
const channel2 = channelFromEnv(env2) ?? npmPackageChannelFromApiUrl(options.apiUrl);
|
|
1922
|
+
return { kind: "published", channel: channel2 };
|
|
1923
|
+
}
|
|
1924
|
+
if (scriptPath && looksLikeLocalMonorepoCliPath(scriptPath)) {
|
|
1925
|
+
return { kind: "local", cliPath: scriptPath };
|
|
1926
|
+
}
|
|
1927
|
+
if (options.cliPath) {
|
|
1928
|
+
return { kind: "local", cliPath: path13.resolve(options.cliPath) };
|
|
1929
|
+
}
|
|
1930
|
+
const resolveCli = options.resolveBuiltCliPath ?? resolveBuiltCliPathAsync;
|
|
1931
|
+
const builtCli = await resolveCli();
|
|
1932
|
+
if (builtCli && looksLikeLocalMonorepoCliPath(builtCli) && !(scriptPath && looksLikePublishedPackagePath(scriptPath))) {
|
|
1933
|
+
return { kind: "local", cliPath: builtCli };
|
|
1934
|
+
}
|
|
1935
|
+
const channel = channelFromEnv(env2) ?? npmPackageChannelFromApiUrl(options.apiUrl);
|
|
1936
|
+
return { kind: "published", channel };
|
|
1937
|
+
}
|
|
1938
|
+
function setupOptionsFromPackageExecutionMode(mode) {
|
|
1939
|
+
if (mode.kind === "local") {
|
|
1940
|
+
return {
|
|
1941
|
+
cursorEnvironment: "local",
|
|
1942
|
+
devMode: true,
|
|
1943
|
+
npmPackageChannel: "latest",
|
|
1944
|
+
cursorLocalCliPathOverride: mode.cliPath,
|
|
1945
|
+
cliPathOverride: mode.cliPath
|
|
1946
|
+
};
|
|
1947
|
+
}
|
|
1948
|
+
return {
|
|
1949
|
+
cursorEnvironment: cursorEnvironmentFromPackageExecutionMode(mode),
|
|
1950
|
+
devMode: false,
|
|
1951
|
+
npmPackageChannel: mode.channel,
|
|
1952
|
+
cursorLocalCliPathOverride: null,
|
|
1953
|
+
cliPathOverride: null
|
|
1954
|
+
};
|
|
1955
|
+
}
|
|
1956
|
+
function formatIdeSetupRepairHint(workspaceRoot, mode) {
|
|
1957
|
+
if (mode.kind === "local") {
|
|
1958
|
+
return `Repair with (no new connect code): node ${mode.cliPath} setup-ide-files --all --local --dev --force --workspace-root ${workspaceRoot}`;
|
|
1959
|
+
}
|
|
1960
|
+
const spec = memoraoneNpmPackageSpec(mode.channel);
|
|
1961
|
+
const stagingFlag = mode.channel === "staging" ? " --staging" : "";
|
|
1962
|
+
return `Repair with (no new connect code): npx -y ${spec} setup-ide-files --all${stagingFlag} --force --workspace-root ${workspaceRoot}`;
|
|
1963
|
+
}
|
|
1964
|
+
|
|
888
1965
|
// src/cursorGlobalMcpConfig.ts
|
|
889
|
-
var
|
|
1966
|
+
var execFileAsync2 = (0, import_node_util2.promisify)(import_node_child_process2.execFile);
|
|
890
1967
|
var MEMORAONE_PROD_API_URL = "https://api.memoraone.com";
|
|
891
1968
|
var MEMORAONE_LOCAL_API_URL = "http://localhost:3001";
|
|
892
1969
|
var MEMORAONE_STAGING_API_URL = "https://memora-api-staging-phbtrzocjq-uk.a.run.app";
|
|
893
1970
|
var MEMORAONE_STAGING_API_URL_PREFIX = "https://memora-api-staging-";
|
|
894
|
-
function
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
1971
|
+
function normalizeApiUrl2(url) {
|
|
1972
|
+
return url.trim().replace(/\/+$/, "");
|
|
1973
|
+
}
|
|
1974
|
+
function resolveIdeApiUrl(options) {
|
|
1975
|
+
if (options.apiUrl) return normalizeApiUrl2(options.apiUrl);
|
|
1976
|
+
if (options.environment === "staging") return MEMORAONE_STAGING_API_URL;
|
|
1977
|
+
if (options.environment === "production") return MEMORAONE_PROD_API_URL;
|
|
1978
|
+
return MEMORAONE_LOCAL_API_URL;
|
|
898
1979
|
}
|
|
899
1980
|
function buildMemoraoneCursorMcpServer(options) {
|
|
900
|
-
const
|
|
901
|
-
MEMORAONE_API_URL:
|
|
1981
|
+
const env2 = {
|
|
1982
|
+
MEMORAONE_API_URL: resolveIdeApiUrl({
|
|
1983
|
+
environment: options.environment,
|
|
1984
|
+
apiUrl: options.apiUrl
|
|
1985
|
+
}),
|
|
902
1986
|
MEMORAONE_IDE_TYPE: "cursor"
|
|
903
1987
|
};
|
|
904
1988
|
if (options.workspaceRoot !== void 0) {
|
|
905
|
-
|
|
1989
|
+
env2[MEMORAONE_WORKSPACE_ROOT_ENV] = path14.resolve(options.workspaceRoot);
|
|
906
1990
|
}
|
|
907
1991
|
if (options.environment === "local") {
|
|
908
1992
|
if (!options.cliPath) {
|
|
@@ -911,21 +1995,22 @@ function buildMemoraoneCursorMcpServer(options) {
|
|
|
911
1995
|
return {
|
|
912
1996
|
command: "node",
|
|
913
1997
|
args: [options.cliPath],
|
|
914
|
-
env
|
|
1998
|
+
env: env2
|
|
915
1999
|
};
|
|
916
2000
|
}
|
|
917
2001
|
if (!options.npxPath) {
|
|
918
2002
|
throw new Error("[setup-ide-files] Cursor MCP config requires a resolved npx path.");
|
|
919
2003
|
}
|
|
2004
|
+
const channel = options.npmPackageChannel ?? npmPackageChannelFromEnvironment(options.environment);
|
|
920
2005
|
return {
|
|
921
2006
|
command: options.npxPath,
|
|
922
|
-
args: ["-y",
|
|
923
|
-
env
|
|
2007
|
+
args: ["-y", memoraoneNpmPackageSpec(channel)],
|
|
2008
|
+
env: env2
|
|
924
2009
|
};
|
|
925
2010
|
}
|
|
926
|
-
async function
|
|
2011
|
+
async function pathExists2(filePath) {
|
|
927
2012
|
try {
|
|
928
|
-
await
|
|
2013
|
+
await fs10.access(filePath);
|
|
929
2014
|
return true;
|
|
930
2015
|
} catch {
|
|
931
2016
|
return false;
|
|
@@ -935,17 +2020,17 @@ function stripLeadingLineComments(text) {
|
|
|
935
2020
|
return text.split("\n").filter((line) => !/^\s*\/\//.test(line)).join("\n");
|
|
936
2021
|
}
|
|
937
2022
|
function getKnownCursorGlobalMcpConfigCandidates(homeDir) {
|
|
938
|
-
return [
|
|
2023
|
+
return [path14.join(homeDir, ".cursor", "mcp.json")];
|
|
939
2024
|
}
|
|
940
2025
|
async function detectCursorGlobalMcpConfig(options) {
|
|
941
2026
|
if (options?.explicitPath) {
|
|
942
|
-
return { ok: true, path: options.explicitPath, detectedExisting: await
|
|
2027
|
+
return { ok: true, path: options.explicitPath, detectedExisting: await pathExists2(options.explicitPath) };
|
|
943
2028
|
}
|
|
944
|
-
const homeDir = options?.homeDir ??
|
|
2029
|
+
const homeDir = options?.homeDir ?? os4.homedir();
|
|
945
2030
|
const candidates = getKnownCursorGlobalMcpConfigCandidates(homeDir);
|
|
946
2031
|
const existing = [];
|
|
947
2032
|
for (const candidate of candidates) {
|
|
948
|
-
if (await
|
|
2033
|
+
if (await pathExists2(candidate)) existing.push(candidate);
|
|
949
2034
|
}
|
|
950
2035
|
if (existing.length > 1) {
|
|
951
2036
|
return {
|
|
@@ -973,15 +2058,15 @@ function formatBackupTimestamp(d = /* @__PURE__ */ new Date()) {
|
|
|
973
2058
|
}
|
|
974
2059
|
async function isWorkingNpx(npxPath) {
|
|
975
2060
|
try {
|
|
976
|
-
if (!await
|
|
2061
|
+
if (!await pathExists2(npxPath)) return false;
|
|
977
2062
|
if (process.platform !== "win32") {
|
|
978
2063
|
try {
|
|
979
|
-
await
|
|
2064
|
+
await fs10.access(npxPath, fs10.constants.X_OK);
|
|
980
2065
|
} catch {
|
|
981
2066
|
return false;
|
|
982
2067
|
}
|
|
983
2068
|
}
|
|
984
|
-
await
|
|
2069
|
+
await execFileAsync2(npxPath, ["--version"], { timeout: 1e4 });
|
|
985
2070
|
return true;
|
|
986
2071
|
} catch {
|
|
987
2072
|
return false;
|
|
@@ -998,18 +2083,18 @@ async function resolveNpxPath() {
|
|
|
998
2083
|
const pathSep = process.platform === "win32" ? ";" : ":";
|
|
999
2084
|
for (const dir of (process.env.PATH ?? "").split(pathSep)) {
|
|
1000
2085
|
if (!dir) continue;
|
|
1001
|
-
candidates.push(
|
|
2086
|
+
candidates.push(path14.join(dir, npxName));
|
|
1002
2087
|
}
|
|
1003
2088
|
try {
|
|
1004
2089
|
const lookupCmd = process.platform === "win32" ? "where" : "which";
|
|
1005
|
-
const { stdout } = await
|
|
2090
|
+
const { stdout } = await execFileAsync2(lookupCmd, [npxName], { timeout: 5e3 });
|
|
1006
2091
|
const first = stdout.trim().split(/\r?\n/).map((line) => line.trim()).find(Boolean);
|
|
1007
2092
|
if (first) candidates.unshift(first);
|
|
1008
2093
|
} catch {
|
|
1009
2094
|
}
|
|
1010
2095
|
const seen = /* @__PURE__ */ new Set();
|
|
1011
2096
|
for (const candidate of candidates) {
|
|
1012
|
-
const abs =
|
|
2097
|
+
const abs = path14.isAbsolute(candidate) ? candidate : path14.resolve(candidate);
|
|
1013
2098
|
const key = process.platform === "win32" ? abs.toLowerCase() : abs;
|
|
1014
2099
|
if (seen.has(key)) continue;
|
|
1015
2100
|
seen.add(key);
|
|
@@ -1021,11 +2106,14 @@ function mergeCursorRepoMcpConfigObject(existing, writeOptions) {
|
|
|
1021
2106
|
const environment = writeOptions.environment ?? "production";
|
|
1022
2107
|
const base = existing && typeof existing === "object" ? { ...existing } : { mcpServers: {} };
|
|
1023
2108
|
const mcpServers = typeof base.mcpServers === "object" && base.mcpServers !== null && !Array.isArray(base.mcpServers) ? { ...base.mcpServers } : {};
|
|
2109
|
+
delete mcpServers.memoraone;
|
|
1024
2110
|
mcpServers.memoraone = buildMemoraoneCursorMcpServer({
|
|
1025
2111
|
environment,
|
|
1026
2112
|
npxPath: writeOptions.npxPath,
|
|
1027
2113
|
cliPath: writeOptions.cliPath,
|
|
1028
|
-
workspaceRoot: writeOptions.repoRoot
|
|
2114
|
+
workspaceRoot: writeOptions.repoRoot,
|
|
2115
|
+
apiUrl: writeOptions.apiUrl,
|
|
2116
|
+
npmPackageChannel: writeOptions.npmPackageChannel
|
|
1029
2117
|
});
|
|
1030
2118
|
return { ...base, mcpServers };
|
|
1031
2119
|
}
|
|
@@ -1039,26 +2127,26 @@ function isManagedMemoraoneCursorServer(server) {
|
|
|
1039
2127
|
if (!server || typeof server !== "object") return false;
|
|
1040
2128
|
const s = server;
|
|
1041
2129
|
if (!Array.isArray(s.args) || s.args.length !== 2) return false;
|
|
1042
|
-
if (s.args[0] !== "-y" || s.args[1]
|
|
1043
|
-
const
|
|
1044
|
-
if (!
|
|
1045
|
-
return memoraoneEnvMatchesManagedCleanupShape(
|
|
1046
|
-
}
|
|
1047
|
-
function cursorConfigHasManagedMemoraone(
|
|
1048
|
-
if (!
|
|
1049
|
-
const mcpServers =
|
|
2130
|
+
if (s.args[0] !== "-y" || !isMemoraoneNpmPackageSpec(s.args[1])) return false;
|
|
2131
|
+
const env2 = s.env;
|
|
2132
|
+
if (!env2 || typeof env2 !== "object") return false;
|
|
2133
|
+
return memoraoneEnvMatchesManagedCleanupShape(env2);
|
|
2134
|
+
}
|
|
2135
|
+
function cursorConfigHasManagedMemoraone(parsed2) {
|
|
2136
|
+
if (!parsed2 || typeof parsed2 !== "object") return false;
|
|
2137
|
+
const mcpServers = parsed2.mcpServers;
|
|
1050
2138
|
if (!mcpServers || typeof mcpServers !== "object" || Array.isArray(mcpServers)) return false;
|
|
1051
2139
|
return isManagedMemoraoneCursorServer(mcpServers.memoraone);
|
|
1052
2140
|
}
|
|
1053
|
-
function memoraoneEnvMatchesManagedCleanupShape(
|
|
1054
|
-
return
|
|
2141
|
+
function memoraoneEnvMatchesManagedCleanupShape(env2) {
|
|
2142
|
+
return env2.MEMORAONE_IDE_TYPE === "cursor" && isMemoraoneManagedApiUrl(env2.MEMORAONE_API_URL);
|
|
1055
2143
|
}
|
|
1056
2144
|
function getCursorRepoMcpConfigPath(repoRoot) {
|
|
1057
|
-
return
|
|
2145
|
+
return path14.join(repoRoot, ".cursor", "mcp.json");
|
|
1058
2146
|
}
|
|
1059
2147
|
async function readCursorMcpConfigObject(configPath) {
|
|
1060
2148
|
try {
|
|
1061
|
-
const raw = await
|
|
2149
|
+
const raw = await fs10.readFile(configPath, "utf8");
|
|
1062
2150
|
return JSON.parse(stripLeadingLineComments(raw));
|
|
1063
2151
|
} catch (err) {
|
|
1064
2152
|
const code = err && typeof err === "object" && "code" in err ? err.code : void 0;
|
|
@@ -1068,36 +2156,36 @@ async function readCursorMcpConfigObject(configPath) {
|
|
|
1068
2156
|
}
|
|
1069
2157
|
async function removeMemoraoneFromCursorGlobalConfig(options) {
|
|
1070
2158
|
const { configPath, dryRun } = options;
|
|
1071
|
-
const
|
|
1072
|
-
if (!
|
|
2159
|
+
const parsed2 = await readCursorMcpConfigObject(configPath);
|
|
2160
|
+
if (!parsed2 || !cursorConfigHasManagedMemoraone(parsed2)) {
|
|
1073
2161
|
return { changed: false };
|
|
1074
2162
|
}
|
|
1075
2163
|
if (dryRun) {
|
|
1076
2164
|
return { changed: true, backupPath: `${configPath}.backup-<timestamp>` };
|
|
1077
2165
|
}
|
|
1078
2166
|
const backupPath = `${configPath}.backup-${formatBackupTimestamp()}`;
|
|
1079
|
-
await
|
|
1080
|
-
const mcpServers = typeof
|
|
2167
|
+
await fs10.copyFile(configPath, backupPath);
|
|
2168
|
+
const mcpServers = typeof parsed2.mcpServers === "object" && parsed2.mcpServers !== null && !Array.isArray(parsed2.mcpServers) ? { ...parsed2.mcpServers } : {};
|
|
1081
2169
|
delete mcpServers.memoraone;
|
|
1082
2170
|
const hasOtherServers = Object.keys(mcpServers).length > 0;
|
|
1083
2171
|
if (!hasOtherServers) {
|
|
1084
|
-
await
|
|
2172
|
+
await fs10.unlink(configPath);
|
|
1085
2173
|
return { changed: true, backupPath };
|
|
1086
2174
|
}
|
|
1087
|
-
const next = { ...
|
|
1088
|
-
await
|
|
1089
|
-
await
|
|
2175
|
+
const next = { ...parsed2, mcpServers };
|
|
2176
|
+
await fs10.mkdir(path14.dirname(configPath), { recursive: true });
|
|
2177
|
+
await fs10.writeFile(configPath, JSON.stringify(next, null, 2) + "\n", "utf8");
|
|
1090
2178
|
return { changed: true, backupPath };
|
|
1091
2179
|
}
|
|
1092
2180
|
async function auditCursorMcpConfig(options) {
|
|
1093
|
-
const repoRoot =
|
|
2181
|
+
const repoRoot = path14.resolve(options?.repoRoot ?? process.cwd());
|
|
1094
2182
|
const repoConfigPath = getCursorRepoMcpConfigPath(repoRoot);
|
|
1095
2183
|
const globalDetection = await detectCursorGlobalMcpConfig({
|
|
1096
2184
|
homeDir: options?.homeDir,
|
|
1097
2185
|
explicitPath: options?.explicitGlobalPath
|
|
1098
2186
|
});
|
|
1099
2187
|
const globalConfigPath = globalDetection.ok ? globalDetection.path : getKnownCursorGlobalMcpConfigCandidates(
|
|
1100
|
-
options?.homeDir ??
|
|
2188
|
+
options?.homeDir ?? os4.homedir()
|
|
1101
2189
|
)[0];
|
|
1102
2190
|
let repoHasManagedMemoraone = false;
|
|
1103
2191
|
try {
|
|
@@ -1193,11 +2281,11 @@ function logCursorMcpCliSummary(info, dryRun, opts) {
|
|
|
1193
2281
|
}
|
|
1194
2282
|
|
|
1195
2283
|
// src/cleanup.ts
|
|
1196
|
-
var
|
|
2284
|
+
var execFileAsync3 = (0, import_node_util3.promisify)(import_node_child_process3.execFile);
|
|
1197
2285
|
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;
|
|
1198
2286
|
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;
|
|
1199
2287
|
var MEMORAONE_MCP_COMMAND_RE = /memoraone-mcp|memoraOne-mcp|@memoraone\/mcp/;
|
|
1200
|
-
var CLEANUP_PROJECT_ID_REQUIRED_ERROR = "Provide --project-id <id> or run from a
|
|
2288
|
+
var CLEANUP_PROJECT_ID_REQUIRED_ERROR = "Provide --project-id <id> or run from a connected workspace (memoraone-mcp connect).";
|
|
1201
2289
|
function isMemoraoneMcpCommandLine(commandLine) {
|
|
1202
2290
|
return MEMORAONE_MCP_COMMAND_RE.test(commandLine);
|
|
1203
2291
|
}
|
|
@@ -1258,7 +2346,7 @@ function normalizeCleanupProjectId(projectId) {
|
|
|
1258
2346
|
return trimmed.toLowerCase();
|
|
1259
2347
|
}
|
|
1260
2348
|
async function defaultListDaemonProcesses() {
|
|
1261
|
-
const { stdout } = await
|
|
2349
|
+
const { stdout } = await execFileAsync3("ps", ["-eo", "pid=,args="], {
|
|
1262
2350
|
maxBuffer: 10 * 1024 * 1024
|
|
1263
2351
|
});
|
|
1264
2352
|
return parseDaemonProcessLines(stdout.split("\n"));
|
|
@@ -1267,7 +2355,7 @@ async function defaultListSocketPaths(projectId) {
|
|
|
1267
2355
|
const baseDir = getMcpBaseDir();
|
|
1268
2356
|
let entries;
|
|
1269
2357
|
try {
|
|
1270
|
-
entries = await
|
|
2358
|
+
entries = await fs11.readdir(baseDir);
|
|
1271
2359
|
} catch (err) {
|
|
1272
2360
|
const code = err && typeof err === "object" && "code" in err ? err.code : void 0;
|
|
1273
2361
|
if (code === "ENOENT") {
|
|
@@ -1280,7 +2368,7 @@ async function defaultListSocketPaths(projectId) {
|
|
|
1280
2368
|
if (!name.endsWith(".sock") || !isMemoraoneSocketFilename(name)) {
|
|
1281
2369
|
continue;
|
|
1282
2370
|
}
|
|
1283
|
-
const socketPath =
|
|
2371
|
+
const socketPath = path15.join(baseDir, name);
|
|
1284
2372
|
if (projectId === null) {
|
|
1285
2373
|
paths.push(socketPath);
|
|
1286
2374
|
continue;
|
|
@@ -1299,12 +2387,12 @@ async function defaultListSocketPaths(projectId) {
|
|
|
1299
2387
|
}
|
|
1300
2388
|
return paths.sort();
|
|
1301
2389
|
}
|
|
1302
|
-
async function
|
|
1303
|
-
const
|
|
2390
|
+
async function defaultListSocketPathsForWorkspaceRoot(workspaceRoot) {
|
|
2391
|
+
const resolvedRoot = path15.resolve(workspaceRoot);
|
|
1304
2392
|
const baseDir = getMcpBaseDir();
|
|
1305
2393
|
let entries;
|
|
1306
2394
|
try {
|
|
1307
|
-
entries = await
|
|
2395
|
+
entries = await fs11.readdir(baseDir);
|
|
1308
2396
|
} catch (err) {
|
|
1309
2397
|
const code = err && typeof err === "object" && "code" in err ? err.code : void 0;
|
|
1310
2398
|
if (code === "ENOENT") {
|
|
@@ -1317,34 +2405,34 @@ async function defaultListSocketPathsForM1Path(m1Path) {
|
|
|
1317
2405
|
if (!name.endsWith(".sock") || !isHashSocketFilename(name)) {
|
|
1318
2406
|
continue;
|
|
1319
2407
|
}
|
|
1320
|
-
const socketPath =
|
|
2408
|
+
const socketPath = path15.join(baseDir, name);
|
|
1321
2409
|
const record = readBindingSidecarRecord(socketPath);
|
|
1322
|
-
if (!record?.
|
|
1323
|
-
if (
|
|
2410
|
+
if (!record?.workspaceRoot) continue;
|
|
2411
|
+
if (path15.resolve(record.workspaceRoot) === resolvedRoot) {
|
|
1324
2412
|
paths.push(socketPath);
|
|
1325
2413
|
}
|
|
1326
2414
|
}
|
|
1327
2415
|
return paths.sort();
|
|
1328
2416
|
}
|
|
1329
|
-
async function filterSocketPathsByIdeForCleanup(socketPaths, projectId, ide,
|
|
2417
|
+
async function filterSocketPathsByIdeForCleanup(socketPaths, projectId, ide, workspaceRoot) {
|
|
1330
2418
|
if (ide === void 0) return socketPaths;
|
|
1331
2419
|
const normalizedProjectId = projectId.trim().toLowerCase();
|
|
1332
|
-
const
|
|
2420
|
+
const resolvedRoot = workspaceRoot ? path15.resolve(workspaceRoot) : null;
|
|
1333
2421
|
const filtered = [];
|
|
1334
2422
|
for (const socketPath of socketPaths) {
|
|
1335
|
-
const
|
|
1336
|
-
if (isLegacySocketFilename(
|
|
1337
|
-
if (isSocketFilenameForProjectAndIde(
|
|
2423
|
+
const basename11 = path15.basename(socketPath);
|
|
2424
|
+
if (isLegacySocketFilename(basename11)) {
|
|
2425
|
+
if (isSocketFilenameForProjectAndIde(basename11, normalizedProjectId, ide)) {
|
|
1338
2426
|
filtered.push(socketPath);
|
|
1339
2427
|
}
|
|
1340
2428
|
continue;
|
|
1341
2429
|
}
|
|
1342
|
-
if (isHashSocketFilename(
|
|
2430
|
+
if (isHashSocketFilename(basename11)) {
|
|
1343
2431
|
const record = readBindingSidecarRecord(socketPath);
|
|
1344
2432
|
if (!record || record.ideType !== ide) continue;
|
|
1345
2433
|
const sameProject = record.projectId.trim().toLowerCase() === normalizedProjectId;
|
|
1346
|
-
const
|
|
1347
|
-
if (sameProject ||
|
|
2434
|
+
const sameWorkspace = resolvedRoot !== null && path15.resolve(record.workspaceRoot) === resolvedRoot;
|
|
2435
|
+
if (sameProject || sameWorkspace) {
|
|
1348
2436
|
filtered.push(socketPath);
|
|
1349
2437
|
}
|
|
1350
2438
|
}
|
|
@@ -1355,9 +2443,9 @@ async function defaultKillProcess(pid) {
|
|
|
1355
2443
|
process.kill(pid, "SIGTERM");
|
|
1356
2444
|
}
|
|
1357
2445
|
async function defaultRemoveSocket(socketPath) {
|
|
1358
|
-
await
|
|
2446
|
+
await fs11.unlink(socketPath);
|
|
1359
2447
|
try {
|
|
1360
|
-
await
|
|
2448
|
+
await fs11.unlink(bindingSidecarPath(socketPath));
|
|
1361
2449
|
} catch {
|
|
1362
2450
|
}
|
|
1363
2451
|
}
|
|
@@ -1373,12 +2461,12 @@ async function defaultConfirm(message) {
|
|
|
1373
2461
|
rl.close();
|
|
1374
2462
|
}
|
|
1375
2463
|
}
|
|
1376
|
-
async function resolveCleanupTarget(
|
|
2464
|
+
async function resolveCleanupTarget(cwd2) {
|
|
1377
2465
|
try {
|
|
1378
|
-
const binding = await resolveAuthoritativeBinding([
|
|
2466
|
+
const binding = await resolveAuthoritativeBinding([path15.resolve(cwd2)]);
|
|
1379
2467
|
return {
|
|
1380
2468
|
workspaceRoot: binding.workspaceRoot,
|
|
1381
|
-
|
|
2469
|
+
repositoryBindingId: binding.repositoryBindingId,
|
|
1382
2470
|
projectId: binding.projectId
|
|
1383
2471
|
};
|
|
1384
2472
|
} catch {
|
|
@@ -1435,7 +2523,7 @@ async function runCleanup(opts) {
|
|
|
1435
2523
|
const prefix = logPrefix(opts.dryRun);
|
|
1436
2524
|
let targetProjectId = null;
|
|
1437
2525
|
let workspaceRoot;
|
|
1438
|
-
let
|
|
2526
|
+
let repositoryBindingId;
|
|
1439
2527
|
if (opts.allProjects) {
|
|
1440
2528
|
if (opts.projectId) {
|
|
1441
2529
|
return {
|
|
@@ -1468,9 +2556,9 @@ async function runCleanup(opts) {
|
|
|
1468
2556
|
}
|
|
1469
2557
|
targetProjectId = target.projectId;
|
|
1470
2558
|
workspaceRoot = target.workspaceRoot;
|
|
1471
|
-
|
|
2559
|
+
repositoryBindingId = target.repositoryBindingId;
|
|
1472
2560
|
cleanupLog(opts, `${prefix} Workspace root: ${workspaceRoot}`);
|
|
1473
|
-
cleanupLog(opts, `${prefix}
|
|
2561
|
+
cleanupLog(opts, `${prefix} Repository binding: ${repositoryBindingId}`);
|
|
1474
2562
|
cleanupLog(opts, `${prefix} Project id: ${targetProjectId}`);
|
|
1475
2563
|
if (opts.ide) {
|
|
1476
2564
|
cleanupLog(opts, `${prefix} IDE filter: ${opts.ide}`);
|
|
@@ -1515,10 +2603,10 @@ async function runCleanup(opts) {
|
|
|
1515
2603
|
}
|
|
1516
2604
|
}
|
|
1517
2605
|
let allSocketPaths = await listSocketPaths(targetProjectId);
|
|
1518
|
-
if (
|
|
1519
|
-
const
|
|
2606
|
+
if (workspaceRoot && !opts.allProjects) {
|
|
2607
|
+
const workspaceSockets = await defaultListSocketPathsForWorkspaceRoot(workspaceRoot);
|
|
1520
2608
|
const seen = new Set(allSocketPaths);
|
|
1521
|
-
for (const socketPath of
|
|
2609
|
+
for (const socketPath of workspaceSockets) {
|
|
1522
2610
|
if (!seen.has(socketPath)) {
|
|
1523
2611
|
seen.add(socketPath);
|
|
1524
2612
|
allSocketPaths.push(socketPath);
|
|
@@ -1533,7 +2621,7 @@ async function runCleanup(opts) {
|
|
|
1533
2621
|
processesToStop.push(proc);
|
|
1534
2622
|
cleanupLog(
|
|
1535
2623
|
opts,
|
|
1536
|
-
`${prefix} Including stale daemon pid=${proc.pid} project=${staleProjectId} (sidecar
|
|
2624
|
+
`${prefix} Including stale daemon pid=${proc.pid} project=${staleProjectId} (sidecar workspace=${workspaceRoot})`
|
|
1537
2625
|
);
|
|
1538
2626
|
}
|
|
1539
2627
|
}
|
|
@@ -1541,14 +2629,19 @@ async function runCleanup(opts) {
|
|
|
1541
2629
|
}
|
|
1542
2630
|
allSocketPaths = [...seen].sort();
|
|
1543
2631
|
}
|
|
1544
|
-
const socketPaths = targetProjectId === null ? allSocketPaths : await filterSocketPathsByIdeForCleanup(
|
|
2632
|
+
const socketPaths = targetProjectId === null ? allSocketPaths : await filterSocketPathsByIdeForCleanup(
|
|
2633
|
+
allSocketPaths,
|
|
2634
|
+
targetProjectId,
|
|
2635
|
+
opts.ide,
|
|
2636
|
+
workspaceRoot
|
|
2637
|
+
);
|
|
1545
2638
|
if (opts.allProjects) {
|
|
1546
2639
|
const projectIds = /* @__PURE__ */ new Set();
|
|
1547
2640
|
for (const proc of processesToStop) {
|
|
1548
2641
|
projectIds.add(proc.projectId);
|
|
1549
2642
|
}
|
|
1550
2643
|
for (const socketPath of socketPaths) {
|
|
1551
|
-
const id = extractProjectIdFromSocketFilename(
|
|
2644
|
+
const id = extractProjectIdFromSocketFilename(path15.basename(socketPath));
|
|
1552
2645
|
if (id) {
|
|
1553
2646
|
projectIds.add(id);
|
|
1554
2647
|
continue;
|
|
@@ -1595,7 +2688,7 @@ async function runCleanup(opts) {
|
|
|
1595
2688
|
return {
|
|
1596
2689
|
exitCode: 1,
|
|
1597
2690
|
workspaceRoot,
|
|
1598
|
-
|
|
2691
|
+
repositoryBindingId,
|
|
1599
2692
|
projectId: targetProjectId ?? void 0,
|
|
1600
2693
|
killedPids: [],
|
|
1601
2694
|
removedSockets: [],
|
|
@@ -1611,7 +2704,7 @@ async function runCleanup(opts) {
|
|
|
1611
2704
|
return {
|
|
1612
2705
|
exitCode: 0,
|
|
1613
2706
|
workspaceRoot,
|
|
1614
|
-
|
|
2707
|
+
repositoryBindingId,
|
|
1615
2708
|
projectId: targetProjectId ?? void 0,
|
|
1616
2709
|
killedPids: processesToStop.map((p) => p.pid),
|
|
1617
2710
|
removedSockets: socketPaths,
|
|
@@ -1646,7 +2739,7 @@ async function runCleanup(opts) {
|
|
|
1646
2739
|
return {
|
|
1647
2740
|
exitCode: 0,
|
|
1648
2741
|
workspaceRoot,
|
|
1649
|
-
|
|
2742
|
+
repositoryBindingId,
|
|
1650
2743
|
projectId: targetProjectId ?? void 0,
|
|
1651
2744
|
killedPids,
|
|
1652
2745
|
removedSockets,
|
|
@@ -1734,9 +2827,9 @@ function summarizeJsonRpcMethod(line) {
|
|
|
1734
2827
|
}
|
|
1735
2828
|
}
|
|
1736
2829
|
function connectWithRetry(socketPath, log, maxRetries, retryDelayMs, connect2) {
|
|
1737
|
-
return new Promise((
|
|
2830
|
+
return new Promise((resolve17, reject) => {
|
|
1738
2831
|
const tryConnect = (attempt) => {
|
|
1739
|
-
connect2(socketPath).then(
|
|
2832
|
+
connect2(socketPath).then(resolve17).catch((err) => {
|
|
1740
2833
|
if (attempt >= maxRetries) {
|
|
1741
2834
|
reject(err);
|
|
1742
2835
|
return;
|
|
@@ -1748,18 +2841,18 @@ function connectWithRetry(socketPath, log, maxRetries, retryDelayMs, connect2) {
|
|
|
1748
2841
|
tryConnect(0);
|
|
1749
2842
|
});
|
|
1750
2843
|
}
|
|
1751
|
-
async function resolveBridgeSessionBinding(params,
|
|
1752
|
-
const bridgeOptions = getBridgeBindingResolveOptions(
|
|
2844
|
+
async function resolveBridgeSessionBinding(params, env2 = process.env, options = {}) {
|
|
2845
|
+
const bridgeOptions = getBridgeBindingResolveOptions(env2);
|
|
1753
2846
|
return resolveBindingFromInitializeParams(params, {
|
|
1754
|
-
env,
|
|
2847
|
+
env: env2,
|
|
1755
2848
|
fallbackWorkspaceRoots: getEnvWorkspaceRootCandidates(),
|
|
1756
2849
|
rootsListUris: options.rootsListUris,
|
|
1757
2850
|
rootsListAttempted: options.rootsListAttempted,
|
|
1758
2851
|
...bridgeOptions
|
|
1759
2852
|
});
|
|
1760
2853
|
}
|
|
1761
|
-
async function stopDaemonsForStaleBinding(stale,
|
|
1762
|
-
const ideType = resolveBindingIdeType(
|
|
2854
|
+
async function stopDaemonsForStaleBinding(stale, env2, log) {
|
|
2855
|
+
const ideType = resolveBindingIdeType(env2);
|
|
1763
2856
|
let processes;
|
|
1764
2857
|
try {
|
|
1765
2858
|
processes = await defaultListDaemonProcesses();
|
|
@@ -1783,7 +2876,7 @@ async function connectOrSpawnDaemonForBinding(binding, opts) {
|
|
|
1783
2876
|
const sessionBinding = reconciled.binding;
|
|
1784
2877
|
if (reconciled.cacheRefreshed) {
|
|
1785
2878
|
opts.log(
|
|
1786
|
-
`refreshed stale binding
|
|
2879
|
+
`refreshed stale binding ${sessionBinding.repositoryBindingId}: project=${sessionBinding.projectId}`
|
|
1787
2880
|
);
|
|
1788
2881
|
}
|
|
1789
2882
|
const socketPath = getBindingSocketPath(sessionBinding, opts.env);
|
|
@@ -1811,7 +2904,7 @@ async function connectOrSpawnDaemonForBinding(binding, opts) {
|
|
|
1811
2904
|
socket = void 0;
|
|
1812
2905
|
}
|
|
1813
2906
|
if (isDaemonBindingMismatchError(err)) {
|
|
1814
|
-
opts.log("stale daemon binding detected; replacing daemon for current
|
|
2907
|
+
opts.log("stale daemon binding detected; replacing daemon for current local binding");
|
|
1815
2908
|
const staleSidecar = readBindingSidecar(socketPath);
|
|
1816
2909
|
if (staleSidecar) {
|
|
1817
2910
|
await stopDaemonsForStaleBinding(staleSidecar, opts.env, opts.log);
|
|
@@ -1852,19 +2945,20 @@ var BridgeDaemonRouter = class {
|
|
|
1852
2945
|
this.maxRetries = options.maxRetries ?? 5;
|
|
1853
2946
|
this.retryDelayMs = options.retryDelayMs ?? 200;
|
|
1854
2947
|
this.lineReader = options.lineReader ?? null;
|
|
1855
|
-
this.connectImpl = options.connect ?? ((socketPath) => new Promise((
|
|
1856
|
-
const socket = net.connect(socketPath, () =>
|
|
2948
|
+
this.connectImpl = options.connect ?? ((socketPath) => new Promise((resolve17, reject) => {
|
|
2949
|
+
const socket = net.connect(socketPath, () => resolve17(socket));
|
|
1857
2950
|
socket.on("error", reject);
|
|
1858
2951
|
}));
|
|
1859
2952
|
this.spawnDaemonImpl = options.spawnDaemon ?? (async (binding) => {
|
|
1860
|
-
const child = (0,
|
|
2953
|
+
const child = (0, import_node_child_process4.spawn)(
|
|
1861
2954
|
process.execPath,
|
|
1862
|
-
buildDaemonSpawnArgs(this.cliPath, binding.
|
|
2955
|
+
buildDaemonSpawnArgs(this.cliPath, binding.repositoryBindingId, this.env),
|
|
1863
2956
|
{
|
|
1864
2957
|
detached: true,
|
|
1865
2958
|
stdio: "ignore",
|
|
1866
2959
|
env: {
|
|
1867
2960
|
...this.env,
|
|
2961
|
+
// Secret-free handoff only; tokens stay in the OS keyring.
|
|
1868
2962
|
MEMORAONE_DAEMON_BINDING_B64: encodeResolvedBinding(binding)
|
|
1869
2963
|
}
|
|
1870
2964
|
}
|
|
@@ -1927,7 +3021,7 @@ var BridgeDaemonRouter = class {
|
|
|
1927
3021
|
});
|
|
1928
3022
|
const environmentLog = binding.environment !== void 0 ? ` environment=${binding.environment}` : "";
|
|
1929
3023
|
this.log(
|
|
1930
|
-
`session binding
|
|
3024
|
+
`session binding binding=${binding.repositoryBindingId} project=${binding.projectId} workspace=${binding.workspaceRoot} source=${binding.bindingSource}${environmentLog}`
|
|
1931
3025
|
);
|
|
1932
3026
|
if (this.activeBinding && bindingsMatch(this.activeBinding, binding) && this.activeSocket) {
|
|
1933
3027
|
return;
|
|
@@ -2062,6 +3156,17 @@ var BridgeDaemonRouter = class {
|
|
|
2062
3156
|
this.socketLineReader = null;
|
|
2063
3157
|
}
|
|
2064
3158
|
}
|
|
3159
|
+
/** Tear down the daemon socket when the IDE stdio transport ends. */
|
|
3160
|
+
close() {
|
|
3161
|
+
this.detachSocketReader();
|
|
3162
|
+
if (this.activeSocket) {
|
|
3163
|
+
try {
|
|
3164
|
+
this.activeSocket.destroy();
|
|
3165
|
+
} catch {
|
|
3166
|
+
}
|
|
3167
|
+
this.activeSocket = null;
|
|
3168
|
+
}
|
|
3169
|
+
}
|
|
2065
3170
|
};
|
|
2066
3171
|
async function runBridgeProxy(options) {
|
|
2067
3172
|
ensureBaseDir();
|
|
@@ -2070,49 +3175,70 @@ async function runBridgeProxy(options) {
|
|
|
2070
3175
|
const log = options.log ?? defaultLog;
|
|
2071
3176
|
const lineReader = options.lineReader ?? new StdioLineReader(stdin);
|
|
2072
3177
|
const router = new BridgeDaemonRouter({ ...options, stdout, lineReader });
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
message
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
3178
|
+
try {
|
|
3179
|
+
while (true) {
|
|
3180
|
+
const line = await lineReader.readLine();
|
|
3181
|
+
if (line === null) {
|
|
3182
|
+
log("stdin EOF; closing daemon socket");
|
|
3183
|
+
break;
|
|
3184
|
+
}
|
|
3185
|
+
const trimmed = line.trim();
|
|
3186
|
+
if (trimmed === "") {
|
|
3187
|
+
continue;
|
|
3188
|
+
}
|
|
3189
|
+
let message;
|
|
3190
|
+
try {
|
|
3191
|
+
message = JSON.parse(trimmed);
|
|
3192
|
+
} catch (err) {
|
|
3193
|
+
throw new Error(`[memoraone-mcp] Invalid JSON-RPC on stdin: ${String(err)}`);
|
|
3194
|
+
}
|
|
3195
|
+
if (message.method === "initialize") {
|
|
3196
|
+
log("resolve binding from initialize request before daemon connect");
|
|
3197
|
+
const params = message.params ?? {};
|
|
3198
|
+
await router.ensureDaemonForInitialize(params);
|
|
3199
|
+
await router.forwardInitializeToDaemon(trimmed);
|
|
3200
|
+
await router.replayDeferredClientMessages();
|
|
3201
|
+
continue;
|
|
3202
|
+
}
|
|
3203
|
+
await router.writeToDaemon(trimmed);
|
|
2095
3204
|
}
|
|
2096
|
-
|
|
3205
|
+
} finally {
|
|
3206
|
+
router.close();
|
|
2097
3207
|
}
|
|
2098
3208
|
}
|
|
2099
3209
|
|
|
2100
3210
|
// src/setupIdeFiles.ts
|
|
2101
|
-
var
|
|
2102
|
-
var
|
|
2103
|
-
var
|
|
3211
|
+
var fs13 = __toESM(require("fs/promises"), 1);
|
|
3212
|
+
var os6 = __toESM(require("os"), 1);
|
|
3213
|
+
var path17 = __toESM(require("path"), 1);
|
|
3214
|
+
var import_node_crypto5 = require("crypto");
|
|
2104
3215
|
|
|
2105
3216
|
// src/jetbrainsMcpConfig.ts
|
|
2106
|
-
var
|
|
2107
|
-
var
|
|
2108
|
-
var
|
|
2109
|
-
var
|
|
3217
|
+
var fs12 = __toESM(require("fs/promises"), 1);
|
|
3218
|
+
var os5 = __toESM(require("os"), 1);
|
|
3219
|
+
var path16 = __toESM(require("path"), 1);
|
|
3220
|
+
var import_node_crypto4 = require("crypto");
|
|
3221
|
+
var import_node_child_process5 = require("child_process");
|
|
2110
3222
|
|
|
2111
3223
|
// src/configUtils.ts
|
|
3224
|
+
var DEFAULT_API_URL = "http://localhost:3001";
|
|
2112
3225
|
var DEV_API_URL = "http://localhost:3001";
|
|
3226
|
+
function resolveApiUrl(env2) {
|
|
3227
|
+
const explicitUrl = env2.MEMORAONE_API_URL?.trim();
|
|
3228
|
+
if (explicitUrl) {
|
|
3229
|
+
return explicitUrl;
|
|
3230
|
+
}
|
|
3231
|
+
const aliasUrl = env2.MEMORA_API_URL?.trim();
|
|
3232
|
+
if (aliasUrl) {
|
|
3233
|
+
return aliasUrl;
|
|
3234
|
+
}
|
|
3235
|
+
if (env2.MEMORAONE_DEV_MODE === "1") {
|
|
3236
|
+
return DEV_API_URL;
|
|
3237
|
+
}
|
|
3238
|
+
return DEFAULT_API_URL;
|
|
3239
|
+
}
|
|
2113
3240
|
|
|
2114
3241
|
// src/jetbrainsMcpConfig.ts
|
|
2115
|
-
var PROD_API_URL = "https://api.memoraone.com";
|
|
2116
3242
|
var JETBRAINS_DEBUG_ENV_VARS = [
|
|
2117
3243
|
"MEMORAONE_DEBUG_INIT",
|
|
2118
3244
|
"MEMORAONE_DEBUG_MINIMAL_TOOLS",
|
|
@@ -2122,9 +3248,9 @@ var JETBRAINS_DEBUG_ENV_VARS = [
|
|
|
2122
3248
|
function stripLeadingLineComments2(text) {
|
|
2123
3249
|
return text.split("\n").filter((line) => !/^\s*\/\//.test(line)).join("\n");
|
|
2124
3250
|
}
|
|
2125
|
-
async function
|
|
3251
|
+
async function pathExists3(filePath) {
|
|
2126
3252
|
try {
|
|
2127
|
-
await
|
|
3253
|
+
await fs12.access(filePath);
|
|
2128
3254
|
return true;
|
|
2129
3255
|
} catch {
|
|
2130
3256
|
return false;
|
|
@@ -2135,12 +3261,12 @@ function formatJetBrainsBackupTimestamp(d = /* @__PURE__ */ new Date()) {
|
|
|
2135
3261
|
return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
|
|
2136
3262
|
}
|
|
2137
3263
|
function getJetBrainsGlobalMcpConfigPath(homeDir) {
|
|
2138
|
-
return
|
|
3264
|
+
return path16.join(homeDir, ".ai", "mcp", "mcp.json");
|
|
2139
3265
|
}
|
|
2140
3266
|
function getJetBrainsProjectMcpConfigPaths(repoRoot) {
|
|
2141
3267
|
return [
|
|
2142
|
-
{ kind: "project-ai", path:
|
|
2143
|
-
{ kind: "project-ij", path:
|
|
3268
|
+
{ kind: "project-ai", path: path16.join(repoRoot, ".ai", "mcp", "mcp.json") },
|
|
3269
|
+
{ kind: "project-ij", path: path16.join(repoRoot, ".ij", "mcp", "mcp.json") }
|
|
2144
3270
|
];
|
|
2145
3271
|
}
|
|
2146
3272
|
function getKnownJetBrainsMcpConfigLocations(homeDir, repoRoot) {
|
|
@@ -2150,31 +3276,57 @@ function getKnownJetBrainsMcpConfigLocations(homeDir, repoRoot) {
|
|
|
2150
3276
|
];
|
|
2151
3277
|
}
|
|
2152
3278
|
async function isZeroByteConfigFile(filePath) {
|
|
2153
|
-
if (!await
|
|
2154
|
-
const
|
|
2155
|
-
return
|
|
3279
|
+
if (!await pathExists3(filePath)) return false;
|
|
3280
|
+
const stat4 = await fs12.stat(filePath);
|
|
3281
|
+
return stat4.size === 0;
|
|
2156
3282
|
}
|
|
2157
3283
|
function buildMemoraoneJetBrainsMcpServer(options) {
|
|
2158
|
-
const
|
|
2159
|
-
|
|
3284
|
+
const environment = options.environment ?? (options.devMode ? "local" : "production");
|
|
3285
|
+
const env2 = {
|
|
3286
|
+
MEMORAONE_API_URL: resolveIdeApiUrl({
|
|
3287
|
+
environment,
|
|
3288
|
+
apiUrl: options.apiUrl ?? (environment === "local" ? DEV_API_URL : void 0)
|
|
3289
|
+
}),
|
|
2160
3290
|
MEMORAONE_IDE_TYPE: "jetbrains",
|
|
2161
|
-
|
|
3291
|
+
[MEMORAONE_WORKSPACE_ROOT_ENV]: path16.resolve(options.workspaceRoot)
|
|
2162
3292
|
};
|
|
2163
|
-
if (options.devMode) {
|
|
2164
|
-
|
|
3293
|
+
if (environment === "local" || options.devMode) {
|
|
3294
|
+
env2.MEMORAONE_DEV_MODE = "1";
|
|
3295
|
+
}
|
|
3296
|
+
if ("MEMORAONE_M1_PATH" in env2 || "MEMORAONE_API_KEY" in env2) {
|
|
3297
|
+
throw new Error("[setup-ide-files] JetBrains MCP config must not include credentials or .m1 paths");
|
|
2165
3298
|
}
|
|
2166
3299
|
return {
|
|
2167
3300
|
command: options.command,
|
|
2168
3301
|
args: options.args,
|
|
2169
|
-
env
|
|
3302
|
+
env: env2
|
|
2170
3303
|
};
|
|
2171
3304
|
}
|
|
2172
3305
|
function mergeJetBrainsMcpConfigObject(existing, memoraone) {
|
|
2173
3306
|
const base = existing && typeof existing === "object" ? { ...existing } : { mcpServers: {} };
|
|
2174
3307
|
const mcpServers = typeof base.mcpServers === "object" && base.mcpServers !== null && !Array.isArray(base.mcpServers) ? { ...base.mcpServers } : {};
|
|
3308
|
+
delete mcpServers.memoraone;
|
|
2175
3309
|
mcpServers.memoraone = memoraone;
|
|
2176
3310
|
return { ...base, mcpServers };
|
|
2177
3311
|
}
|
|
3312
|
+
async function writeJetBrainsMcpJsonAtomic(filePath, content) {
|
|
3313
|
+
const dir = path16.dirname(filePath);
|
|
3314
|
+
await fs12.mkdir(dir, { recursive: true });
|
|
3315
|
+
const tmpPath = path16.join(
|
|
3316
|
+
dir,
|
|
3317
|
+
`.${path16.basename(filePath)}.${process.pid}.${(0, import_node_crypto4.randomBytes)(8).toString("hex")}.tmp`
|
|
3318
|
+
);
|
|
3319
|
+
try {
|
|
3320
|
+
await fs12.writeFile(tmpPath, content, "utf8");
|
|
3321
|
+
await fs12.rename(tmpPath, filePath);
|
|
3322
|
+
} catch (err) {
|
|
3323
|
+
try {
|
|
3324
|
+
await fs12.unlink(tmpPath);
|
|
3325
|
+
} catch {
|
|
3326
|
+
}
|
|
3327
|
+
throw err;
|
|
3328
|
+
}
|
|
3329
|
+
}
|
|
2178
3330
|
function memoraoneServerMatches(server, expected) {
|
|
2179
3331
|
if (!server || typeof server !== "object") return false;
|
|
2180
3332
|
const s = server;
|
|
@@ -2183,9 +3335,9 @@ function memoraoneServerMatches(server, expected) {
|
|
|
2183
3335
|
for (let i = 0; i < expected.args.length; i += 1) {
|
|
2184
3336
|
if (s.args[i] !== expected.args[i]) return false;
|
|
2185
3337
|
}
|
|
2186
|
-
const
|
|
2187
|
-
if (!
|
|
2188
|
-
const e =
|
|
3338
|
+
const env2 = s.env;
|
|
3339
|
+
if (!env2 || typeof env2 !== "object") return false;
|
|
3340
|
+
const e = env2;
|
|
2189
3341
|
for (const [key, value] of Object.entries(expected.env)) {
|
|
2190
3342
|
if (e[key] !== value) return false;
|
|
2191
3343
|
}
|
|
@@ -2194,11 +3346,11 @@ function memoraoneServerMatches(server, expected) {
|
|
|
2194
3346
|
}
|
|
2195
3347
|
return true;
|
|
2196
3348
|
}
|
|
2197
|
-
function validateJetBrainsMcpConfig(
|
|
2198
|
-
if (!
|
|
3349
|
+
function validateJetBrainsMcpConfig(parsed2, expected) {
|
|
3350
|
+
if (!parsed2 || typeof parsed2 !== "object") {
|
|
2199
3351
|
throw new Error("[setup-ide-files] JetBrains MCP config must be a JSON object.");
|
|
2200
3352
|
}
|
|
2201
|
-
const mcpServers =
|
|
3353
|
+
const mcpServers = parsed2.mcpServers;
|
|
2202
3354
|
if (!mcpServers || typeof mcpServers !== "object" || Array.isArray(mcpServers)) {
|
|
2203
3355
|
throw new Error("[setup-ide-files] JetBrains MCP config missing mcpServers object.");
|
|
2204
3356
|
}
|
|
@@ -2210,13 +3362,13 @@ function validateJetBrainsMcpConfig(parsed, expected) {
|
|
|
2210
3362
|
}
|
|
2211
3363
|
}
|
|
2212
3364
|
async function readJsonConfig(filePath) {
|
|
2213
|
-
const raw = await
|
|
3365
|
+
const raw = await fs12.readFile(filePath, "utf8");
|
|
2214
3366
|
if (raw.trim() === "") return null;
|
|
2215
3367
|
return JSON.parse(stripLeadingLineComments2(raw));
|
|
2216
3368
|
}
|
|
2217
3369
|
async function backupConfigFile(filePath) {
|
|
2218
3370
|
const backupPath = `${filePath}.bak-${formatJetBrainsBackupTimestamp()}`;
|
|
2219
|
-
await
|
|
3371
|
+
await fs12.copyFile(filePath, backupPath);
|
|
2220
3372
|
return backupPath;
|
|
2221
3373
|
}
|
|
2222
3374
|
async function repairZeroByteConfigFile(filePath, dryRun) {
|
|
@@ -2227,61 +3379,62 @@ async function repairZeroByteConfigFile(filePath, dryRun) {
|
|
|
2227
3379
|
return { repaired: true, backupPath: `${filePath}.bak-<timestamp>` };
|
|
2228
3380
|
}
|
|
2229
3381
|
const backupPath = await backupConfigFile(filePath);
|
|
2230
|
-
await
|
|
3382
|
+
await fs12.unlink(filePath);
|
|
2231
3383
|
return { repaired: true, backupPath };
|
|
2232
3384
|
}
|
|
2233
|
-
function configHasMemoraone(
|
|
2234
|
-
if (!
|
|
2235
|
-
const mcpServers =
|
|
3385
|
+
function configHasMemoraone(parsed2) {
|
|
3386
|
+
if (!parsed2) return false;
|
|
3387
|
+
const mcpServers = parsed2.mcpServers;
|
|
2236
3388
|
if (!mcpServers || typeof mcpServers !== "object" || Array.isArray(mcpServers)) return false;
|
|
2237
3389
|
return Boolean(mcpServers.memoraone);
|
|
2238
3390
|
}
|
|
2239
3391
|
async function removeMemoraoneFromProjectConfig(options) {
|
|
2240
3392
|
const { configPath, dryRun } = options;
|
|
2241
|
-
if (!await
|
|
3393
|
+
if (!await pathExists3(configPath)) {
|
|
2242
3394
|
return { changed: false };
|
|
2243
3395
|
}
|
|
2244
|
-
let
|
|
3396
|
+
let parsed2 = null;
|
|
2245
3397
|
try {
|
|
2246
|
-
|
|
3398
|
+
parsed2 = await readJsonConfig(configPath);
|
|
2247
3399
|
} catch {
|
|
2248
3400
|
return { changed: false };
|
|
2249
3401
|
}
|
|
2250
|
-
if (!configHasMemoraone(
|
|
3402
|
+
if (!configHasMemoraone(parsed2)) {
|
|
2251
3403
|
return { changed: false };
|
|
2252
3404
|
}
|
|
2253
3405
|
if (dryRun) {
|
|
2254
3406
|
return { changed: true, backupPath: `${configPath}.bak-<timestamp>` };
|
|
2255
3407
|
}
|
|
2256
3408
|
const backupPath = await backupConfigFile(configPath);
|
|
2257
|
-
const mcpServers =
|
|
3409
|
+
const mcpServers = parsed2 && typeof parsed2.mcpServers === "object" && parsed2.mcpServers !== null && !Array.isArray(parsed2.mcpServers) ? { ...parsed2.mcpServers } : {};
|
|
2258
3410
|
delete mcpServers.memoraone;
|
|
2259
3411
|
const hasOtherServers = Object.keys(mcpServers).length > 0;
|
|
2260
3412
|
if (!hasOtherServers) {
|
|
2261
|
-
await
|
|
3413
|
+
await fs12.unlink(configPath);
|
|
2262
3414
|
return { changed: true, backupPath };
|
|
2263
3415
|
}
|
|
2264
|
-
const next = { ...
|
|
2265
|
-
await
|
|
2266
|
-
await
|
|
3416
|
+
const next = { ...parsed2, mcpServers };
|
|
3417
|
+
await fs12.mkdir(path16.dirname(configPath), { recursive: true });
|
|
3418
|
+
await fs12.writeFile(configPath, JSON.stringify(next, null, 2) + "\n", "utf8");
|
|
2267
3419
|
return { changed: true, backupPath };
|
|
2268
3420
|
}
|
|
2269
3421
|
async function resolveLocalCliPathAsync() {
|
|
2270
|
-
const here = process.argv[1] ?
|
|
3422
|
+
const here = process.argv[1] ? path16.dirname(path16.resolve(process.argv[1])) : process.cwd();
|
|
2271
3423
|
const candidates = [
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
3424
|
+
path16.join(here, "cli.cjs"),
|
|
3425
|
+
path16.join(here, "..", "dist", "cli.cjs"),
|
|
3426
|
+
path16.join(here, "..", "..", "dist", "cli.cjs")
|
|
2275
3427
|
];
|
|
2276
3428
|
for (const candidate of candidates) {
|
|
2277
|
-
if (await
|
|
2278
|
-
return
|
|
3429
|
+
if (await pathExists3(candidate)) {
|
|
3430
|
+
return path16.resolve(candidate);
|
|
2279
3431
|
}
|
|
2280
3432
|
}
|
|
2281
3433
|
return null;
|
|
2282
3434
|
}
|
|
2283
3435
|
async function buildJetBrainsMemoraoneServer(options) {
|
|
2284
|
-
|
|
3436
|
+
const environment = options.environment ?? (options.devMode ? "local" : "production");
|
|
3437
|
+
if (environment === "local" || options.devMode) {
|
|
2285
3438
|
let cliPath = options.cliPathOverride;
|
|
2286
3439
|
if (cliPath === void 0) {
|
|
2287
3440
|
cliPath = await resolveLocalCliPathAsync();
|
|
@@ -2294,8 +3447,10 @@ async function buildJetBrainsMemoraoneServer(options) {
|
|
|
2294
3447
|
return buildMemoraoneJetBrainsMcpServer({
|
|
2295
3448
|
command: process.execPath,
|
|
2296
3449
|
args: [cliPath],
|
|
2297
|
-
|
|
2298
|
-
|
|
3450
|
+
workspaceRoot: options.workspaceRoot,
|
|
3451
|
+
environment: "local",
|
|
3452
|
+
devMode: true,
|
|
3453
|
+
apiUrl: options.apiUrl
|
|
2299
3454
|
});
|
|
2300
3455
|
}
|
|
2301
3456
|
let npxPath = options.npxPathOverride;
|
|
@@ -2307,17 +3462,20 @@ async function buildJetBrainsMemoraoneServer(options) {
|
|
|
2307
3462
|
"[setup-ide-files] Could not resolve a working npx executable. Install Node.js/npm or ensure npx is on PATH before configuring JetBrains MCP."
|
|
2308
3463
|
);
|
|
2309
3464
|
}
|
|
3465
|
+
const channel = options.npmPackageChannel ?? npmPackageChannelFromEnvironment(environment);
|
|
2310
3466
|
return buildMemoraoneJetBrainsMcpServer({
|
|
2311
3467
|
command: npxPath,
|
|
2312
|
-
args: ["-y",
|
|
2313
|
-
|
|
2314
|
-
|
|
3468
|
+
args: ["-y", memoraoneNpmPackageSpec(channel)],
|
|
3469
|
+
workspaceRoot: options.workspaceRoot,
|
|
3470
|
+
environment,
|
|
3471
|
+
devMode: false,
|
|
3472
|
+
apiUrl: options.apiUrl
|
|
2315
3473
|
});
|
|
2316
3474
|
}
|
|
2317
3475
|
async function verifyJetBrainsMcpHandshake(options) {
|
|
2318
3476
|
const timeoutMs = options.timeoutMs ?? 15e3;
|
|
2319
3477
|
const { server } = options;
|
|
2320
|
-
return new Promise((
|
|
3478
|
+
return new Promise((resolve17) => {
|
|
2321
3479
|
let settled = false;
|
|
2322
3480
|
const finish = (ok, detail) => {
|
|
2323
3481
|
if (settled) return;
|
|
@@ -2327,9 +3485,9 @@ async function verifyJetBrainsMcpHandshake(options) {
|
|
|
2327
3485
|
child.kill();
|
|
2328
3486
|
} catch {
|
|
2329
3487
|
}
|
|
2330
|
-
|
|
3488
|
+
resolve17({ ok, detail });
|
|
2331
3489
|
};
|
|
2332
|
-
const child = (0,
|
|
3490
|
+
const child = (0, import_node_child_process5.spawn)(server.command, [...server.args], {
|
|
2333
3491
|
env: { ...process.env, ...server.env },
|
|
2334
3492
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2335
3493
|
});
|
|
@@ -2394,13 +3552,13 @@ async function verifyJetBrainsMcpHandshake(options) {
|
|
|
2394
3552
|
});
|
|
2395
3553
|
}
|
|
2396
3554
|
async function setupJetBrainsMcpConfig(options) {
|
|
2397
|
-
const homeDir = options.homeDir ??
|
|
3555
|
+
const homeDir = options.homeDir ?? os5.homedir();
|
|
2398
3556
|
const globalPath = options.globalConfigPath ?? getJetBrainsGlobalMcpConfigPath(homeDir);
|
|
2399
|
-
const
|
|
3557
|
+
const workspaceRoot = path16.resolve(options.repoRoot);
|
|
2400
3558
|
const repairActions = [];
|
|
2401
3559
|
const allLocations = getKnownJetBrainsMcpConfigLocations(homeDir, options.repoRoot);
|
|
2402
3560
|
for (const location of allLocations) {
|
|
2403
|
-
if (await
|
|
3561
|
+
if (await pathExists3(location.path)) {
|
|
2404
3562
|
repairActions.push({ type: "found-config", location });
|
|
2405
3563
|
}
|
|
2406
3564
|
}
|
|
@@ -2415,8 +3573,11 @@ async function setupJetBrainsMcpConfig(options) {
|
|
|
2415
3573
|
}
|
|
2416
3574
|
}
|
|
2417
3575
|
const memoraone = await buildJetBrainsMemoraoneServer({
|
|
2418
|
-
|
|
3576
|
+
workspaceRoot,
|
|
2419
3577
|
devMode: options.devMode,
|
|
3578
|
+
environment: options.environment,
|
|
3579
|
+
npmPackageChannel: options.npmPackageChannel,
|
|
3580
|
+
apiUrl: options.apiUrl,
|
|
2420
3581
|
npxPathOverride: options.npxPathOverride,
|
|
2421
3582
|
cliPathOverride: options.cliPathOverride
|
|
2422
3583
|
});
|
|
@@ -2436,24 +3597,15 @@ async function setupJetBrainsMcpConfig(options) {
|
|
|
2436
3597
|
repairActions.push({ type: "removed-project-memoraone", path: location.path });
|
|
2437
3598
|
}
|
|
2438
3599
|
}
|
|
2439
|
-
const existed = await
|
|
3600
|
+
const existed = await pathExists3(globalPath);
|
|
2440
3601
|
let existing = null;
|
|
2441
3602
|
if (existed) {
|
|
2442
3603
|
try {
|
|
2443
3604
|
existing = await readJsonConfig(globalPath);
|
|
2444
3605
|
} catch {
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
const backupPath2 = await backupConfigFile(globalPath);
|
|
2449
|
-
repairActions.push({
|
|
2450
|
-
type: "repaired-zero-byte",
|
|
2451
|
-
path: globalPath,
|
|
2452
|
-
backupPath: backupPath2
|
|
2453
|
-
});
|
|
2454
|
-
await fs6.unlink(globalPath);
|
|
2455
|
-
existing = null;
|
|
2456
|
-
}
|
|
3606
|
+
throw new Error(
|
|
3607
|
+
`[setup-ide-files] Invalid JSON in shared JetBrains MCP config (file preserved, not modified): ${globalPath}. Fix or remove the file, then re-run setup-ide-files.`
|
|
3608
|
+
);
|
|
2457
3609
|
}
|
|
2458
3610
|
}
|
|
2459
3611
|
const merged = mergeJetBrainsMcpConfigObject(existing, memoraone);
|
|
@@ -2474,9 +3626,8 @@ async function setupJetBrainsMcpConfig(options) {
|
|
|
2474
3626
|
if (existed) {
|
|
2475
3627
|
backupPath = await backupConfigFile(globalPath);
|
|
2476
3628
|
}
|
|
2477
|
-
await
|
|
2478
|
-
await
|
|
2479
|
-
const verifyRaw = await fs6.readFile(globalPath, "utf8");
|
|
3629
|
+
await writeJetBrainsMcpJsonAtomic(globalPath, body);
|
|
3630
|
+
const verifyRaw = await fs12.readFile(globalPath, "utf8");
|
|
2480
3631
|
const verifyParsed = JSON.parse(stripLeadingLineComments2(verifyRaw));
|
|
2481
3632
|
validateJetBrainsMcpConfig(verifyParsed, memoraone);
|
|
2482
3633
|
const outcome = existed ? "updated" : "created";
|
|
@@ -2519,76 +3670,22 @@ function logJetBrainsMcpCliSummary(info, dryRun) {
|
|
|
2519
3670
|
} else {
|
|
2520
3671
|
console.log(`[setup-ide-files] JetBrains global MCP config unchanged: ${info.activeConfigPath}`);
|
|
2521
3672
|
}
|
|
2522
|
-
if (info.backupPath) {
|
|
2523
|
-
console.log(`[setup-ide-files] JetBrains global MCP config backup: ${info.backupPath}`);
|
|
2524
|
-
}
|
|
2525
|
-
if (info.npxPath) {
|
|
2526
|
-
console.log(`[setup-ide-files] Resolved npx: ${info.npxPath}`);
|
|
2527
|
-
}
|
|
2528
|
-
console.log(`[setup-ide-files] Final active JetBrains MCP config: ${info.activeConfigPath}`);
|
|
2529
|
-
console.log(
|
|
2530
|
-
"[setup-ide-files] Fully quit JetBrains IDE and reopen this repo for MCP changes to take effect."
|
|
2531
|
-
);
|
|
2532
|
-
}
|
|
2533
|
-
|
|
2534
|
-
// src/resolveBuiltCliPath.ts
|
|
2535
|
-
var fs7 = __toESM(require("fs/promises"), 1);
|
|
2536
|
-
var path9 = __toESM(require("path"), 1);
|
|
2537
|
-
var MONOREPO_CLI_REL = path9.join("packages", "mcp", "dist", "cli.cjs");
|
|
2538
|
-
async function pathExists3(filePath) {
|
|
2539
|
-
try {
|
|
2540
|
-
await fs7.access(filePath);
|
|
2541
|
-
return true;
|
|
2542
|
-
} catch {
|
|
2543
|
-
return false;
|
|
2544
|
-
}
|
|
2545
|
-
}
|
|
2546
|
-
async function findMonorepoCliFrom(startDir) {
|
|
2547
|
-
let current = path9.resolve(startDir);
|
|
2548
|
-
const root = path9.parse(current).root;
|
|
2549
|
-
while (true) {
|
|
2550
|
-
const candidate = path9.join(current, MONOREPO_CLI_REL);
|
|
2551
|
-
if (await pathExists3(candidate)) {
|
|
2552
|
-
return path9.resolve(candidate);
|
|
2553
|
-
}
|
|
2554
|
-
if (current === root) break;
|
|
2555
|
-
current = path9.dirname(current);
|
|
2556
|
-
}
|
|
2557
|
-
return null;
|
|
2558
|
-
}
|
|
2559
|
-
async function resolveBuiltCliPathAsync(options) {
|
|
2560
|
-
const searchDirs = [];
|
|
2561
|
-
if (options?.searchFrom !== void 0) {
|
|
2562
|
-
const dirs = Array.isArray(options.searchFrom) ? options.searchFrom : [options.searchFrom];
|
|
2563
|
-
searchDirs.push(...dirs);
|
|
2564
|
-
}
|
|
2565
|
-
searchDirs.push(process.cwd());
|
|
2566
|
-
const seen = /* @__PURE__ */ new Set();
|
|
2567
|
-
for (const dir of searchDirs) {
|
|
2568
|
-
const key = path9.resolve(dir);
|
|
2569
|
-
if (seen.has(key)) continue;
|
|
2570
|
-
seen.add(key);
|
|
2571
|
-
const found = await findMonorepoCliFrom(key);
|
|
2572
|
-
if (found) return found;
|
|
3673
|
+
if (info.backupPath) {
|
|
3674
|
+
console.log(`[setup-ide-files] JetBrains global MCP config backup: ${info.backupPath}`);
|
|
2573
3675
|
}
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
path9.join(here, "cli.cjs"),
|
|
2577
|
-
path9.join(here, "..", "dist", "cli.cjs"),
|
|
2578
|
-
path9.join(here, "..", "..", "dist", "cli.cjs")
|
|
2579
|
-
];
|
|
2580
|
-
for (const candidate of candidates) {
|
|
2581
|
-
if (await pathExists3(candidate)) {
|
|
2582
|
-
return path9.resolve(candidate);
|
|
2583
|
-
}
|
|
3676
|
+
if (info.npxPath) {
|
|
3677
|
+
console.log(`[setup-ide-files] Resolved npx: ${info.npxPath}`);
|
|
2584
3678
|
}
|
|
2585
|
-
|
|
3679
|
+
console.log(`[setup-ide-files] Final active JetBrains MCP config: ${info.activeConfigPath}`);
|
|
3680
|
+
console.log(
|
|
3681
|
+
"[setup-ide-files] Fully quit JetBrains IDE and reopen this repo for MCP changes to take effect."
|
|
3682
|
+
);
|
|
2586
3683
|
}
|
|
2587
3684
|
|
|
2588
3685
|
// src/openCursorMcpSettings.ts
|
|
2589
|
-
var
|
|
3686
|
+
var import_node_child_process6 = require("child_process");
|
|
2590
3687
|
var readline4 = __toESM(require("readline/promises"), 1);
|
|
2591
|
-
var
|
|
3688
|
+
var import_node_util4 = require("util");
|
|
2592
3689
|
|
|
2593
3690
|
// src/terminalPresentation.ts
|
|
2594
3691
|
var ANSI = {
|
|
@@ -2599,34 +3696,34 @@ var ANSI = {
|
|
|
2599
3696
|
yellow: "\x1B[33m",
|
|
2600
3697
|
cyan: "\x1B[36m"
|
|
2601
3698
|
};
|
|
2602
|
-
function isCiLikeEnv(
|
|
2603
|
-
if (
|
|
2604
|
-
if (
|
|
2605
|
-
if (
|
|
2606
|
-
if (
|
|
2607
|
-
if (
|
|
2608
|
-
if (typeof
|
|
3699
|
+
function isCiLikeEnv(env2 = process.env) {
|
|
3700
|
+
if (env2.CI === "true" || env2.CI === "1") return true;
|
|
3701
|
+
if (env2.GITHUB_ACTIONS === "true" || env2.GITHUB_ACTIONS === "1") return true;
|
|
3702
|
+
if (env2.GITLAB_CI === "true" || env2.GITLAB_CI === "1") return true;
|
|
3703
|
+
if (env2.CIRCLECI === "true" || env2.CIRCLECI === "1") return true;
|
|
3704
|
+
if (env2.BUILDKITE === "true" || env2.BUILDKITE === "1") return true;
|
|
3705
|
+
if (typeof env2.CI === "string" && env2.CI.trim() !== "" && env2.CI !== "0" && env2.CI !== "false") {
|
|
2609
3706
|
return true;
|
|
2610
3707
|
}
|
|
2611
3708
|
return false;
|
|
2612
3709
|
}
|
|
2613
3710
|
function shouldEnableAnsiColor(opts = {}) {
|
|
2614
3711
|
if (typeof opts.color === "boolean") return opts.color;
|
|
2615
|
-
const
|
|
2616
|
-
if (
|
|
2617
|
-
if (isCiLikeEnv(
|
|
3712
|
+
const env2 = opts.env ?? process.env;
|
|
3713
|
+
if (env2.NO_COLOR !== void 0) return false;
|
|
3714
|
+
if (isCiLikeEnv(env2)) return false;
|
|
2618
3715
|
const tty = opts.stdoutIsTty ?? process.stdout.isTTY === true;
|
|
2619
3716
|
return tty;
|
|
2620
3717
|
}
|
|
2621
3718
|
function shouldUseUnicodeSymbols(opts = {}) {
|
|
2622
3719
|
if (typeof opts.unicode === "boolean") return opts.unicode;
|
|
2623
|
-
const
|
|
3720
|
+
const env2 = opts.env ?? process.env;
|
|
2624
3721
|
const tty = opts.stdoutIsTty ?? process.stdout.isTTY === true;
|
|
2625
3722
|
if (!tty) return false;
|
|
2626
|
-
if (
|
|
3723
|
+
if (env2.TERM === "dumb") return false;
|
|
2627
3724
|
if (process.platform === "win32") {
|
|
2628
3725
|
return Boolean(
|
|
2629
|
-
|
|
3726
|
+
env2.WT_SESSION || env2.WT_PROFILE_ID || env2.ConEmuANSI === "ON" || env2.TERM_PROGRAM === "vscode" || env2.TERM_PROGRAM === "cursor" || typeof env2.TERM === "string" && env2.TERM !== "" && env2.TERM !== "dumb"
|
|
2630
3727
|
);
|
|
2631
3728
|
}
|
|
2632
3729
|
return true;
|
|
@@ -2661,7 +3758,7 @@ function createTerminalPresentation(opts = {}) {
|
|
|
2661
3758
|
}
|
|
2662
3759
|
|
|
2663
3760
|
// src/openCursorMcpSettings.ts
|
|
2664
|
-
var
|
|
3761
|
+
var execFileAsync4 = (0, import_node_util4.promisify)(import_node_child_process6.execFile);
|
|
2665
3762
|
var OPEN_CURSOR_MCP_SETTINGS_PROMPT = "Open Cursor MCP settings now? [Y/n] ";
|
|
2666
3763
|
function resolvePresentation(deps) {
|
|
2667
3764
|
if (deps.presentation) return deps.presentation;
|
|
@@ -2756,13 +3853,13 @@ function macosOpenCursorMcpSettingsAppleScript() {
|
|
|
2756
3853
|
"end tell"
|
|
2757
3854
|
].join("\n");
|
|
2758
3855
|
}
|
|
2759
|
-
async function openCursorMcpSettingsViaOsascript(execFileImpl =
|
|
3856
|
+
async function openCursorMcpSettingsViaOsascript(execFileImpl = execFileAsync4) {
|
|
2760
3857
|
await execFileImpl("osascript", ["-e", macosOpenCursorMcpSettingsAppleScript()], {
|
|
2761
3858
|
timeout: 3e4
|
|
2762
3859
|
});
|
|
2763
3860
|
}
|
|
2764
|
-
function manualCursorMcpSettingsSteps(
|
|
2765
|
-
const chord =
|
|
3861
|
+
function manualCursorMcpSettingsSteps(platform2) {
|
|
3862
|
+
const chord = platform2 === "darwin" ? "Command + Shift + P" : "Ctrl + Shift + P";
|
|
2766
3863
|
return [
|
|
2767
3864
|
"To finish setup manually:",
|
|
2768
3865
|
"1. Open this repository in Cursor.",
|
|
@@ -2773,8 +3870,8 @@ function manualCursorMcpSettingsSteps(platform) {
|
|
|
2773
3870
|
"6. Return to MemoraOne Studio and refresh Sources."
|
|
2774
3871
|
];
|
|
2775
3872
|
}
|
|
2776
|
-
function printManualCursorMcpSettingsSteps(
|
|
2777
|
-
for (const line of manualCursorMcpSettingsSteps(
|
|
3873
|
+
function printManualCursorMcpSettingsSteps(platform2, println = console.log) {
|
|
3874
|
+
for (const line of manualCursorMcpSettingsSteps(platform2)) {
|
|
2778
3875
|
println(line);
|
|
2779
3876
|
}
|
|
2780
3877
|
}
|
|
@@ -2782,7 +3879,7 @@ function formatOpenCursorMcpSettingsPrompt(presentation = createTerminalPresenta
|
|
|
2782
3879
|
return `${presentation.indent(OPEN_CURSOR_MCP_SETTINGS_PROMPT.trimEnd())} `;
|
|
2783
3880
|
}
|
|
2784
3881
|
async function runOpenCursorMcpSettingsFlow(deps = {}) {
|
|
2785
|
-
const
|
|
3882
|
+
const platform2 = deps.platform ?? process.platform;
|
|
2786
3883
|
const println = deps.println ?? console.log;
|
|
2787
3884
|
const tp = resolvePresentation(deps);
|
|
2788
3885
|
const confirm = deps.confirm ?? ((question) => confirmYesDefault(question));
|
|
@@ -2790,14 +3887,14 @@ async function runOpenCursorMcpSettingsFlow(deps = {}) {
|
|
|
2790
3887
|
println(tp.heading("Next"));
|
|
2791
3888
|
const yes = await confirm(formatOpenCursorMcpSettingsPrompt(tp));
|
|
2792
3889
|
if (!yes) {
|
|
2793
|
-
printManualCursorMcpSettingsSteps(
|
|
3890
|
+
printManualCursorMcpSettingsSteps(platform2, println);
|
|
2794
3891
|
return;
|
|
2795
3892
|
}
|
|
2796
|
-
if (
|
|
3893
|
+
if (platform2 === "darwin") {
|
|
2797
3894
|
println(tp.warningLine("macOS may request Automation or Accessibility permission."));
|
|
2798
3895
|
try {
|
|
2799
|
-
const
|
|
2800
|
-
await
|
|
3896
|
+
const open2 = deps.openViaOsascript ?? (() => openCursorMcpSettingsViaOsascript(deps.execFile ?? execFileAsync4));
|
|
3897
|
+
await open2();
|
|
2801
3898
|
println(tp.successLine('Confirm "memoraone" is enabled and green'));
|
|
2802
3899
|
println(tp.nextActionLine("Return to MemoraOne Studio and refresh Sources"));
|
|
2803
3900
|
} catch (err) {
|
|
@@ -2810,85 +3907,68 @@ async function runOpenCursorMcpSettingsFlow(deps = {}) {
|
|
|
2810
3907
|
}
|
|
2811
3908
|
return;
|
|
2812
3909
|
}
|
|
2813
|
-
printManualCursorMcpSettingsSteps(
|
|
3910
|
+
printManualCursorMcpSettingsSteps(platform2, println);
|
|
2814
3911
|
}
|
|
2815
3912
|
|
|
2816
3913
|
// src/setupIdeFiles.ts
|
|
2817
3914
|
var MANAGED_MARKER = "<!-- MemoraOne managed IDE helper -->";
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
3915
|
+
function buildMemoraoneMcpServer(ideType, options = {}) {
|
|
3916
|
+
const environment = options.environment ?? "production";
|
|
3917
|
+
const env2 = {
|
|
3918
|
+
MEMORAONE_API_URL: resolveIdeApiUrl({ environment, apiUrl: options.apiUrl }),
|
|
3919
|
+
MEMORAONE_IDE_TYPE: ideType
|
|
3920
|
+
};
|
|
3921
|
+
if (environment === "local") {
|
|
3922
|
+
if (!options.cliPath) {
|
|
3923
|
+
throw new Error("[setup-ide-files] Local VS Code MCP config requires a built CLI path.");
|
|
3924
|
+
}
|
|
3925
|
+
if (options.workspaceRoot !== void 0) {
|
|
3926
|
+
env2[MEMORAONE_WORKSPACE_ROOT_ENV] = path17.resolve(options.workspaceRoot);
|
|
2827
3927
|
}
|
|
3928
|
+
return {
|
|
3929
|
+
command: "node",
|
|
3930
|
+
args: [options.cliPath],
|
|
3931
|
+
env: env2
|
|
3932
|
+
};
|
|
3933
|
+
}
|
|
3934
|
+
const channel = options.npmPackageChannel ?? npmPackageChannelFromEnvironment(environment);
|
|
3935
|
+
return {
|
|
3936
|
+
command: options.command ?? "npx",
|
|
3937
|
+
args: ["-y", memoraoneNpmPackageSpec(channel)],
|
|
3938
|
+
env: env2
|
|
2828
3939
|
};
|
|
2829
3940
|
}
|
|
2830
3941
|
function assertUnderRepoRoot(repoRoot, absPath) {
|
|
2831
|
-
const normRoot =
|
|
2832
|
-
const normPath =
|
|
2833
|
-
if (normPath !==
|
|
3942
|
+
const normRoot = path17.resolve(repoRoot) + path17.sep;
|
|
3943
|
+
const normPath = path17.resolve(absPath);
|
|
3944
|
+
if (normPath !== path17.resolve(repoRoot) && !normPath.startsWith(normRoot)) {
|
|
2834
3945
|
throw new Error(`[setup-ide-files] Refusing to write outside repo root: ${absPath}`);
|
|
2835
3946
|
}
|
|
2836
3947
|
}
|
|
2837
3948
|
async function pathExists4(filePath) {
|
|
2838
3949
|
try {
|
|
2839
|
-
await
|
|
3950
|
+
await fs13.access(filePath);
|
|
2840
3951
|
return true;
|
|
2841
3952
|
} catch {
|
|
2842
3953
|
return false;
|
|
2843
3954
|
}
|
|
2844
3955
|
}
|
|
2845
|
-
function
|
|
2846
|
-
|
|
2847
|
-
const trimmed = line.trim();
|
|
2848
|
-
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
2849
|
-
if (/^\/?memoraone\.m1\s*$/.test(trimmed)) return true;
|
|
2850
|
-
}
|
|
2851
|
-
return false;
|
|
2852
|
-
}
|
|
2853
|
-
function memoraoneGitignoreBlock() {
|
|
2854
|
-
return `${GITIGNORE_MEMORAONE_COMMENT}
|
|
2855
|
-
${GITIGNORE_MEMORAONE_ENTRY}
|
|
2856
|
-
`;
|
|
2857
|
-
}
|
|
2858
|
-
async function ensureGitignoreMemoraone(repoRoot, opts) {
|
|
2859
|
-
if (opts.noGitignore) return "skipped";
|
|
2860
|
-
const abs = path10.join(repoRoot, ".gitignore");
|
|
2861
|
-
assertUnderRepoRoot(repoRoot, abs);
|
|
2862
|
-
let prior = "";
|
|
2863
|
-
let existed = false;
|
|
2864
|
-
try {
|
|
2865
|
-
prior = await fs8.readFile(abs, "utf8");
|
|
2866
|
-
existed = true;
|
|
2867
|
-
} catch (err) {
|
|
2868
|
-
const code = err && typeof err === "object" && "code" in err ? err.code : void 0;
|
|
2869
|
-
if (code !== "ENOENT") throw err;
|
|
2870
|
-
}
|
|
2871
|
-
if (gitignoreAlreadyIgnoresMemoraoneM1(prior)) return "skipped";
|
|
2872
|
-
const block = memoraoneGitignoreBlock();
|
|
2873
|
-
const separator = existed && prior.length > 0 ? prior.endsWith("\n") ? "\n" : "\n\n" : "";
|
|
2874
|
-
const next = (existed ? prior : "") + separator + block;
|
|
2875
|
-
if (opts.dryRun) return existed ? "updated" : "created";
|
|
2876
|
-
await fs8.writeFile(abs, next, "utf8");
|
|
2877
|
-
return existed ? "updated" : "created";
|
|
3956
|
+
async function ensureGitignoreMemoraone(_repoRoot, _opts) {
|
|
3957
|
+
return "skipped";
|
|
2878
3958
|
}
|
|
2879
3959
|
async function findRepoRoot(startDir) {
|
|
2880
|
-
let current =
|
|
2881
|
-
const root =
|
|
3960
|
+
let current = path17.resolve(startDir);
|
|
3961
|
+
const root = path17.parse(current).root;
|
|
2882
3962
|
while (true) {
|
|
2883
|
-
const gitPath =
|
|
2884
|
-
const m1Path =
|
|
3963
|
+
const gitPath = path17.join(current, ".git");
|
|
3964
|
+
const m1Path = path17.join(current, "memoraone.m1");
|
|
2885
3965
|
if (await pathExists4(gitPath) || await pathExists4(m1Path)) {
|
|
2886
3966
|
return current;
|
|
2887
3967
|
}
|
|
2888
3968
|
if (current === root) {
|
|
2889
3969
|
return null;
|
|
2890
3970
|
}
|
|
2891
|
-
current =
|
|
3971
|
+
current = path17.dirname(current);
|
|
2892
3972
|
}
|
|
2893
3973
|
}
|
|
2894
3974
|
function stripLeadingLineComments3(text) {
|
|
@@ -2934,54 +4014,82 @@ function mcpJsonHeader() {
|
|
|
2934
4014
|
return `// ${MANAGED_MARKER}
|
|
2935
4015
|
`;
|
|
2936
4016
|
}
|
|
2937
|
-
function buildVscodeMcpJsonBody(existing) {
|
|
4017
|
+
function buildVscodeMcpJsonBody(existing, options = {}) {
|
|
2938
4018
|
const base = existing && typeof existing === "object" ? { ...existing } : { servers: {} };
|
|
2939
4019
|
const servers = typeof base.servers === "object" && base.servers !== null && !Array.isArray(base.servers) ? { ...base.servers } : {};
|
|
2940
|
-
servers.memoraone
|
|
4020
|
+
delete servers.memoraone;
|
|
4021
|
+
servers.memoraone = buildMemoraoneMcpServer("copilot-vscode", options);
|
|
2941
4022
|
const merged = { ...base, servers };
|
|
2942
4023
|
return mcpJsonHeader() + JSON.stringify(merged, null, 2) + "\n";
|
|
2943
4024
|
}
|
|
4025
|
+
async function writeSharedMcpJsonAtomic(filePath, content) {
|
|
4026
|
+
const dir = path17.dirname(filePath);
|
|
4027
|
+
await fs13.mkdir(dir, { recursive: true });
|
|
4028
|
+
const tmpPath = path17.join(
|
|
4029
|
+
dir,
|
|
4030
|
+
`.${path17.basename(filePath)}.${process.pid}.${(0, import_node_crypto5.randomBytes)(8).toString("hex")}.tmp`
|
|
4031
|
+
);
|
|
4032
|
+
try {
|
|
4033
|
+
await fs13.writeFile(tmpPath, content, "utf8");
|
|
4034
|
+
await fs13.rename(tmpPath, filePath);
|
|
4035
|
+
} catch (err) {
|
|
4036
|
+
try {
|
|
4037
|
+
await fs13.unlink(tmpPath);
|
|
4038
|
+
} catch {
|
|
4039
|
+
}
|
|
4040
|
+
throw err;
|
|
4041
|
+
}
|
|
4042
|
+
}
|
|
4043
|
+
var SharedMcpJsonParseError = class extends Error {
|
|
4044
|
+
constructor(configPath) {
|
|
4045
|
+
super(
|
|
4046
|
+
`[setup-ide-files] Invalid JSON in shared MCP config (file preserved, not modified): ${configPath}. Fix or remove the file, then re-run setup-ide-files.`
|
|
4047
|
+
);
|
|
4048
|
+
this.name = "SharedMcpJsonParseError";
|
|
4049
|
+
this.configPath = configPath;
|
|
4050
|
+
}
|
|
4051
|
+
};
|
|
2944
4052
|
function buildCursorMcpJsonBody(existing, writeOptions) {
|
|
2945
4053
|
const merged = mergeCursorRepoMcpConfigObject(existing, writeOptions);
|
|
2946
4054
|
return mcpJsonHeader() + JSON.stringify(merged, null, 2) + "\n";
|
|
2947
4055
|
}
|
|
2948
4056
|
async function writeManagedMarkdown(repoRoot, relPath, fullContent, opts) {
|
|
2949
|
-
const abs =
|
|
4057
|
+
const abs = path17.join(repoRoot, relPath);
|
|
2950
4058
|
assertUnderRepoRoot(repoRoot, abs);
|
|
2951
4059
|
let prior = "";
|
|
2952
4060
|
let existed = false;
|
|
2953
4061
|
try {
|
|
2954
|
-
prior = await
|
|
4062
|
+
prior = await fs13.readFile(abs, "utf8");
|
|
2955
4063
|
existed = true;
|
|
2956
4064
|
} catch (err) {
|
|
2957
4065
|
if (err?.code !== "ENOENT") throw err;
|
|
2958
4066
|
}
|
|
2959
4067
|
if (!existed) {
|
|
2960
4068
|
if (opts.dryRun) return "created";
|
|
2961
|
-
await
|
|
2962
|
-
await
|
|
4069
|
+
await fs13.mkdir(path17.dirname(abs), { recursive: true });
|
|
4070
|
+
await fs13.writeFile(abs, fullContent, "utf8");
|
|
2963
4071
|
return "created";
|
|
2964
4072
|
}
|
|
2965
4073
|
if (prior.includes(MANAGED_MARKER)) {
|
|
2966
4074
|
if (prior === fullContent) return "skipped";
|
|
2967
4075
|
if (opts.dryRun) return "updated";
|
|
2968
|
-
await
|
|
2969
|
-
await
|
|
4076
|
+
await fs13.mkdir(path17.dirname(abs), { recursive: true });
|
|
4077
|
+
await fs13.writeFile(abs, fullContent, "utf8");
|
|
2970
4078
|
return "updated";
|
|
2971
4079
|
}
|
|
2972
4080
|
if (!opts.force) return "skipped-untracked";
|
|
2973
4081
|
if (opts.dryRun) return "updated";
|
|
2974
|
-
await
|
|
2975
|
-
await
|
|
4082
|
+
await fs13.mkdir(path17.dirname(abs), { recursive: true });
|
|
4083
|
+
await fs13.writeFile(abs, fullContent, "utf8");
|
|
2976
4084
|
return "updated";
|
|
2977
4085
|
}
|
|
2978
4086
|
async function writeIdeMcpJson(repoRoot, relPath, buildBody, opts) {
|
|
2979
|
-
const abs =
|
|
4087
|
+
const abs = path17.join(repoRoot, relPath);
|
|
2980
4088
|
assertUnderRepoRoot(repoRoot, abs);
|
|
2981
4089
|
let raw = "";
|
|
2982
4090
|
let existed = false;
|
|
2983
4091
|
try {
|
|
2984
|
-
raw = await
|
|
4092
|
+
raw = await fs13.readFile(abs, "utf8");
|
|
2985
4093
|
existed = true;
|
|
2986
4094
|
} catch (err) {
|
|
2987
4095
|
if (err?.code !== "ENOENT") throw err;
|
|
@@ -2989,24 +4097,24 @@ async function writeIdeMcpJson(repoRoot, relPath, buildBody, opts) {
|
|
|
2989
4097
|
if (!existed) {
|
|
2990
4098
|
const body = buildBody(null);
|
|
2991
4099
|
if (opts.dryRun) return "created";
|
|
2992
|
-
await
|
|
2993
|
-
await fs8.writeFile(abs, body, "utf8");
|
|
4100
|
+
await writeSharedMcpJsonAtomic(abs, body);
|
|
2994
4101
|
return "created";
|
|
2995
4102
|
}
|
|
2996
4103
|
const managed = raw.includes(MANAGED_MARKER);
|
|
2997
4104
|
if (!managed && !opts.force) return "skipped-untracked";
|
|
2998
|
-
let
|
|
4105
|
+
let parsed2;
|
|
2999
4106
|
try {
|
|
3000
|
-
|
|
4107
|
+
parsed2 = JSON.parse(stripLeadingLineComments3(raw));
|
|
3001
4108
|
} catch {
|
|
3002
|
-
|
|
4109
|
+
throw new SharedMcpJsonParseError(abs);
|
|
3003
4110
|
}
|
|
3004
|
-
if (!
|
|
3005
|
-
|
|
4111
|
+
if (!parsed2 || typeof parsed2 !== "object" || Array.isArray(parsed2)) {
|
|
4112
|
+
throw new SharedMcpJsonParseError(abs);
|
|
4113
|
+
}
|
|
4114
|
+
const next = buildBody(parsed2);
|
|
3006
4115
|
if (managed && next === raw) return "skipped";
|
|
3007
4116
|
if (opts.dryRun) return "updated";
|
|
3008
|
-
await
|
|
3009
|
-
await fs8.writeFile(abs, next, "utf8");
|
|
4117
|
+
await writeSharedMcpJsonAtomic(abs, next);
|
|
3010
4118
|
return "updated";
|
|
3011
4119
|
}
|
|
3012
4120
|
function parseSetupIdeFlags(argv) {
|
|
@@ -3022,8 +4130,12 @@ function parseSetupIdeFlags(argv) {
|
|
|
3022
4130
|
let repair = false;
|
|
3023
4131
|
let local = false;
|
|
3024
4132
|
let staging = false;
|
|
4133
|
+
let workspaceRoot;
|
|
4134
|
+
let apiUrl;
|
|
3025
4135
|
const unknown = [];
|
|
3026
|
-
|
|
4136
|
+
let flagError;
|
|
4137
|
+
for (let i = 0; i < argv.length; i++) {
|
|
4138
|
+
const a = argv[i];
|
|
3027
4139
|
if (a === "--cursor") cursor = true;
|
|
3028
4140
|
else if (a === "--vscode") vscode = true;
|
|
3029
4141
|
else if (a === "--jetbrains") jetbrains = true;
|
|
@@ -3036,7 +4148,35 @@ function parseSetupIdeFlags(argv) {
|
|
|
3036
4148
|
else if (a === "--repair") repair = true;
|
|
3037
4149
|
else if (a === "--local") local = true;
|
|
3038
4150
|
else if (a === "--staging") staging = true;
|
|
3039
|
-
else if (a
|
|
4151
|
+
else if (a === "--workspace-root") {
|
|
4152
|
+
const value = argv[++i];
|
|
4153
|
+
if (!value || value.startsWith("-")) {
|
|
4154
|
+
flagError = "[setup-ide-files] --workspace-root requires a path argument.";
|
|
4155
|
+
} else {
|
|
4156
|
+
workspaceRoot = value;
|
|
4157
|
+
}
|
|
4158
|
+
} else if (a.startsWith("--workspace-root=")) {
|
|
4159
|
+
const value = a.slice("--workspace-root=".length);
|
|
4160
|
+
if (!value) {
|
|
4161
|
+
flagError = "[setup-ide-files] --workspace-root requires a path argument.";
|
|
4162
|
+
} else {
|
|
4163
|
+
workspaceRoot = value;
|
|
4164
|
+
}
|
|
4165
|
+
} else if (a === "--api-url") {
|
|
4166
|
+
const value = argv[++i];
|
|
4167
|
+
if (!value || value.startsWith("-")) {
|
|
4168
|
+
flagError = "[setup-ide-files] --api-url requires a URL argument.";
|
|
4169
|
+
} else {
|
|
4170
|
+
apiUrl = value;
|
|
4171
|
+
}
|
|
4172
|
+
} else if (a.startsWith("--api-url=")) {
|
|
4173
|
+
const value = a.slice("--api-url=".length);
|
|
4174
|
+
if (!value) {
|
|
4175
|
+
flagError = "[setup-ide-files] --api-url requires a URL argument.";
|
|
4176
|
+
} else {
|
|
4177
|
+
apiUrl = value;
|
|
4178
|
+
}
|
|
4179
|
+
} else if (a.startsWith("-")) unknown.push(a);
|
|
3040
4180
|
}
|
|
3041
4181
|
const specific = cursor || vscode || jetbrains;
|
|
3042
4182
|
let targets;
|
|
@@ -3045,10 +4185,12 @@ function parseSetupIdeFlags(argv) {
|
|
|
3045
4185
|
} else {
|
|
3046
4186
|
targets = { cursor, vscode, jetbrains };
|
|
3047
4187
|
}
|
|
3048
|
-
|
|
3049
|
-
if (local && staging) {
|
|
4188
|
+
if (!flagError && local && staging) {
|
|
3050
4189
|
flagError = "[setup-ide-files] --local and --staging are mutually exclusive.";
|
|
3051
4190
|
}
|
|
4191
|
+
if (!flagError && apiUrl && !local && !devMode && !staging) {
|
|
4192
|
+
flagError = "[setup-ide-files] --api-url is developer-only and requires --local, --dev, or --staging.";
|
|
4193
|
+
}
|
|
3052
4194
|
return {
|
|
3053
4195
|
targets,
|
|
3054
4196
|
force,
|
|
@@ -3060,11 +4202,34 @@ function parseSetupIdeFlags(argv) {
|
|
|
3060
4202
|
local,
|
|
3061
4203
|
staging,
|
|
3062
4204
|
all,
|
|
4205
|
+
workspaceRoot,
|
|
4206
|
+
apiUrl,
|
|
3063
4207
|
explicitCursor: cursor,
|
|
3064
4208
|
unknown,
|
|
3065
4209
|
flagError
|
|
3066
4210
|
};
|
|
3067
4211
|
}
|
|
4212
|
+
async function resolveSetupApiUrl(o, repoRoot) {
|
|
4213
|
+
if (o.apiUrl) return normalizeApiUrl2(o.apiUrl);
|
|
4214
|
+
const binding = await findBindingRecordByWorkspaceRoot(repoRoot, o.homeDir);
|
|
4215
|
+
if (binding?.apiUrl) return normalizeApiUrl2(binding.apiUrl);
|
|
4216
|
+
return void 0;
|
|
4217
|
+
}
|
|
4218
|
+
async function persistBindingApiUrl(repoRoot, apiUrl, homeDir, dryRun) {
|
|
4219
|
+
if (dryRun) return;
|
|
4220
|
+
const binding = await findBindingRecordByWorkspaceRoot(repoRoot, homeDir);
|
|
4221
|
+
if (!binding) return;
|
|
4222
|
+
const normalized = normalizeApiUrl2(apiUrl);
|
|
4223
|
+
if (binding.apiUrl === normalized) return;
|
|
4224
|
+
await writeBindingRecord(
|
|
4225
|
+
{
|
|
4226
|
+
...binding,
|
|
4227
|
+
apiUrl: normalized,
|
|
4228
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4229
|
+
},
|
|
4230
|
+
homeDir
|
|
4231
|
+
);
|
|
4232
|
+
}
|
|
3068
4233
|
function cursorEnvironmentFromFlags(local, staging) {
|
|
3069
4234
|
if (local) return "local";
|
|
3070
4235
|
if (staging) return "staging";
|
|
@@ -3172,7 +4337,7 @@ async function runSetupIdeDaemonCleanup(opts) {
|
|
|
3172
4337
|
if ("error" in target) {
|
|
3173
4338
|
return {
|
|
3174
4339
|
skipped: true,
|
|
3175
|
-
skipReason: "no-
|
|
4340
|
+
skipReason: "no-binding",
|
|
3176
4341
|
foundDaemonCount: 0,
|
|
3177
4342
|
stoppedDaemonCount: 0,
|
|
3178
4343
|
removedSocketCount: 0,
|
|
@@ -3229,13 +4394,13 @@ async function runSetupIdeFiles(o) {
|
|
|
3229
4394
|
const outcomes = {};
|
|
3230
4395
|
let cursorMcp;
|
|
3231
4396
|
let jetbrainsMcp;
|
|
3232
|
-
const repoRoot = await findRepoRoot(o.cwd);
|
|
4397
|
+
const repoRoot = o.workspaceRoot !== void 0 && o.workspaceRoot !== "" ? path17.resolve(o.workspaceRoot) : await findRepoRoot(o.cwd);
|
|
3233
4398
|
if (!repoRoot) {
|
|
3234
4399
|
return {
|
|
3235
4400
|
exitCode: 1,
|
|
3236
4401
|
repoRoot: null,
|
|
3237
4402
|
outcomes,
|
|
3238
|
-
error: "[setup-ide-files] No repo root found (looked for .git or memoraone.m1)."
|
|
4403
|
+
error: "[setup-ide-files] No repo root found (looked for .git or memoraone.m1). Pass --workspace-root <path> for a fileless bound workspace."
|
|
3239
4404
|
};
|
|
3240
4405
|
}
|
|
3241
4406
|
let daemonCleanup;
|
|
@@ -3263,19 +4428,29 @@ async function runSetupIdeFiles(o) {
|
|
|
3263
4428
|
dryRun: o.dryRun,
|
|
3264
4429
|
noGitignore: o.noGitignore ?? false
|
|
3265
4430
|
});
|
|
4431
|
+
const cursorEnvironment = o.cursorEnvironment ?? "production";
|
|
4432
|
+
const localOrDev = cursorEnvironment === "local" || Boolean(o.devMode);
|
|
4433
|
+
const npmPackageChannel = o.npmPackageChannel ?? npmPackageChannelFromEnvironment(cursorEnvironment);
|
|
4434
|
+
const resolvedApiUrl2 = await resolveSetupApiUrl(o, repoRoot);
|
|
4435
|
+
const effectiveApiUrl = resolveIdeApiUrl({
|
|
4436
|
+
environment: localOrDev ? "local" : cursorEnvironment,
|
|
4437
|
+
apiUrl: resolvedApiUrl2
|
|
4438
|
+
});
|
|
4439
|
+
if (!o.dryRun && resolvedApiUrl2) {
|
|
4440
|
+
await persistBindingApiUrl(repoRoot, effectiveApiUrl, o.homeDir, o.dryRun);
|
|
4441
|
+
}
|
|
3266
4442
|
const cursorContent = `---
|
|
3267
4443
|
description: MemoraOne MCP \u2014 IDE agent instructions
|
|
3268
4444
|
---
|
|
3269
4445
|
|
|
3270
4446
|
` + cursorRuleBody();
|
|
3271
4447
|
if (o.targets.cursor) {
|
|
3272
|
-
const cursorEnvironment = o.cursorEnvironment ?? "production";
|
|
3273
4448
|
let npxPath = null;
|
|
3274
4449
|
let cliPath;
|
|
3275
4450
|
if (cursorEnvironment === "local") {
|
|
3276
4451
|
let resolvedCliPath = o.cursorLocalCliPathOverride;
|
|
3277
4452
|
if (resolvedCliPath === void 0) {
|
|
3278
|
-
resolvedCliPath = await resolveBuiltCliPathAsync(
|
|
4453
|
+
resolvedCliPath = await resolveBuiltCliPathAsync();
|
|
3279
4454
|
}
|
|
3280
4455
|
if (!resolvedCliPath) {
|
|
3281
4456
|
return {
|
|
@@ -3305,7 +4480,9 @@ description: MemoraOne MCP \u2014 IDE agent instructions
|
|
|
3305
4480
|
environment: cursorEnvironment,
|
|
3306
4481
|
npxPath: npxPath ?? void 0,
|
|
3307
4482
|
cliPath,
|
|
3308
|
-
repoRoot
|
|
4483
|
+
repoRoot,
|
|
4484
|
+
apiUrl: resolvedApiUrl2,
|
|
4485
|
+
npmPackageChannel
|
|
3309
4486
|
};
|
|
3310
4487
|
outcomes[".cursor/rules/memoraone-mcp.mdc"] = await writeManagedMarkdown(
|
|
3311
4488
|
repoRoot,
|
|
@@ -3313,12 +4490,22 @@ description: MemoraOne MCP \u2014 IDE agent instructions
|
|
|
3313
4490
|
cursorContent,
|
|
3314
4491
|
{ force: o.force, dryRun: o.dryRun }
|
|
3315
4492
|
);
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
4493
|
+
try {
|
|
4494
|
+
outcomes[".cursor/mcp.json"] = await writeIdeMcpJson(
|
|
4495
|
+
repoRoot,
|
|
4496
|
+
".cursor/mcp.json",
|
|
4497
|
+
(existing) => buildCursorMcpJsonBody(existing, cursorWriteOptions),
|
|
4498
|
+
{ force: o.force, dryRun: o.dryRun }
|
|
4499
|
+
);
|
|
4500
|
+
} catch (err) {
|
|
4501
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
4502
|
+
return {
|
|
4503
|
+
exitCode: 1,
|
|
4504
|
+
repoRoot,
|
|
4505
|
+
outcomes,
|
|
4506
|
+
error: message
|
|
4507
|
+
};
|
|
4508
|
+
}
|
|
3322
4509
|
const repoConfigPath = getCursorRepoMcpConfigPath(repoRoot);
|
|
3323
4510
|
const repoOutcome = outcomes[".cursor/mcp.json"] ?? "skipped";
|
|
3324
4511
|
let globalConfigPath;
|
|
@@ -3367,15 +4554,53 @@ description: MemoraOne MCP \u2014 IDE agent instructions
|
|
|
3367
4554
|
};
|
|
3368
4555
|
}
|
|
3369
4556
|
if (o.targets.vscode) {
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
{
|
|
3375
|
-
|
|
3376
|
-
dryRun: o.dryRun
|
|
4557
|
+
const vscodeEnvironment = localOrDev ? "local" : cursorEnvironment === "staging" ? "staging" : "production";
|
|
4558
|
+
let vscodeCliPath;
|
|
4559
|
+
if (vscodeEnvironment === "local") {
|
|
4560
|
+
let resolvedCliPath = o.cursorLocalCliPathOverride;
|
|
4561
|
+
if (resolvedCliPath === void 0) {
|
|
4562
|
+
resolvedCliPath = o.cliPathOverride;
|
|
3377
4563
|
}
|
|
3378
|
-
|
|
4564
|
+
if (resolvedCliPath === void 0) {
|
|
4565
|
+
resolvedCliPath = await resolveBuiltCliPathAsync();
|
|
4566
|
+
}
|
|
4567
|
+
if (!resolvedCliPath) {
|
|
4568
|
+
return {
|
|
4569
|
+
exitCode: 1,
|
|
4570
|
+
repoRoot,
|
|
4571
|
+
outcomes,
|
|
4572
|
+
cursorMcp,
|
|
4573
|
+
error: "[setup-ide-files] Local mode requires a built CLI at packages/mcp/dist/cli.cjs. Run pnpm build first."
|
|
4574
|
+
};
|
|
4575
|
+
}
|
|
4576
|
+
vscodeCliPath = resolvedCliPath;
|
|
4577
|
+
}
|
|
4578
|
+
try {
|
|
4579
|
+
outcomes[".vscode/mcp.json"] = await writeIdeMcpJson(
|
|
4580
|
+
repoRoot,
|
|
4581
|
+
".vscode/mcp.json",
|
|
4582
|
+
(existing) => buildVscodeMcpJsonBody(existing, {
|
|
4583
|
+
environment: vscodeEnvironment,
|
|
4584
|
+
apiUrl: resolvedApiUrl2,
|
|
4585
|
+
cliPath: vscodeCliPath,
|
|
4586
|
+
workspaceRoot: vscodeEnvironment === "local" ? repoRoot : void 0,
|
|
4587
|
+
npmPackageChannel
|
|
4588
|
+
}),
|
|
4589
|
+
{
|
|
4590
|
+
force: o.force,
|
|
4591
|
+
dryRun: o.dryRun
|
|
4592
|
+
}
|
|
4593
|
+
);
|
|
4594
|
+
} catch (err) {
|
|
4595
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
4596
|
+
return {
|
|
4597
|
+
exitCode: 1,
|
|
4598
|
+
repoRoot,
|
|
4599
|
+
outcomes,
|
|
4600
|
+
cursorMcp,
|
|
4601
|
+
error: message
|
|
4602
|
+
};
|
|
4603
|
+
}
|
|
3379
4604
|
outcomes[".github/copilot-instructions.md"] = await writeManagedMarkdown(
|
|
3380
4605
|
repoRoot,
|
|
3381
4606
|
".github/copilot-instructions.md",
|
|
@@ -3391,14 +4616,19 @@ description: MemoraOne MCP \u2014 IDE agent instructions
|
|
|
3391
4616
|
{ force: o.force, dryRun: o.dryRun }
|
|
3392
4617
|
);
|
|
3393
4618
|
try {
|
|
3394
|
-
const homeDir = o.jetbrainsHomeDir ??
|
|
4619
|
+
const homeDir = o.jetbrainsHomeDir ?? os6.homedir();
|
|
3395
4620
|
const activePath = o.jetbrainsGlobalMcpConfigPath ?? getJetBrainsGlobalMcpConfigPath(homeDir);
|
|
3396
4621
|
const jetbrainsSetup = await setupJetBrainsMcpConfig({
|
|
3397
4622
|
homeDir,
|
|
3398
4623
|
repoRoot,
|
|
3399
4624
|
globalConfigPath: activePath,
|
|
3400
4625
|
dryRun: o.dryRun,
|
|
3401
|
-
|
|
4626
|
+
// Local dogfood: --dev / local environment → node + built CLI.
|
|
4627
|
+
// Published staging/production: npx + channel-specific package spec.
|
|
4628
|
+
devMode: localOrDev,
|
|
4629
|
+
environment: localOrDev ? "local" : cursorEnvironment,
|
|
4630
|
+
npmPackageChannel,
|
|
4631
|
+
apiUrl: resolvedApiUrl2,
|
|
3402
4632
|
repair: o.repair ?? false,
|
|
3403
4633
|
verify: o.verifyHandshake ?? !o.dryRun,
|
|
3404
4634
|
npxPathOverride: o.npxPathOverride,
|
|
@@ -3439,6 +4669,8 @@ async function cliSetupIdeFiles(argv, options = {}) {
|
|
|
3439
4669
|
local,
|
|
3440
4670
|
staging,
|
|
3441
4671
|
all,
|
|
4672
|
+
workspaceRoot,
|
|
4673
|
+
apiUrl,
|
|
3442
4674
|
explicitCursor,
|
|
3443
4675
|
unknown,
|
|
3444
4676
|
flagError
|
|
@@ -3451,26 +4683,30 @@ async function cliSetupIdeFiles(argv, options = {}) {
|
|
|
3451
4683
|
console.error(`[setup-ide-files] Unknown option(s): ${unknown.join(", ")}`);
|
|
3452
4684
|
return 1;
|
|
3453
4685
|
}
|
|
3454
|
-
const
|
|
4686
|
+
const cwd2 = options.cwd ?? process.cwd();
|
|
3455
4687
|
const openDeps = options.openCursorMcpSettings ?? {};
|
|
3456
4688
|
const stdinIsTty = openDeps.stdinIsTty ?? process.stdin.isTTY === true;
|
|
3457
|
-
const
|
|
4689
|
+
const env2 = openDeps.env ?? process.env;
|
|
3458
4690
|
const promptOpenCursorSettings = shouldPromptOpenCursorMcpSettings({
|
|
3459
4691
|
explicitCursor,
|
|
3460
4692
|
all,
|
|
3461
4693
|
dryRun,
|
|
3462
4694
|
stdinIsTty,
|
|
3463
|
-
env
|
|
4695
|
+
env: env2
|
|
3464
4696
|
});
|
|
4697
|
+
const cursorEnvironment = cursorEnvironmentFromFlags(local, staging);
|
|
3465
4698
|
const result = await runSetupIdeFiles({
|
|
3466
|
-
cwd,
|
|
4699
|
+
cwd: cwd2,
|
|
4700
|
+
workspaceRoot,
|
|
3467
4701
|
targets,
|
|
3468
4702
|
force,
|
|
3469
4703
|
dryRun,
|
|
3470
4704
|
noGitignore: options.setupOverrides?.noGitignore ?? noGitignore,
|
|
3471
4705
|
devMode,
|
|
3472
4706
|
repair,
|
|
3473
|
-
cursorEnvironment
|
|
4707
|
+
cursorEnvironment,
|
|
4708
|
+
npmPackageChannel: npmPackageChannelFromEnvironment(cursorEnvironment),
|
|
4709
|
+
apiUrl,
|
|
3474
4710
|
...options.setupOverrides
|
|
3475
4711
|
});
|
|
3476
4712
|
if (result.error) {
|
|
@@ -3497,7 +4733,7 @@ async function cliSetupIdeFiles(argv, options = {}) {
|
|
|
3497
4733
|
}
|
|
3498
4734
|
if (promptOpenCursorSettings && result.repoRoot) {
|
|
3499
4735
|
const presentation = openDeps.presentation ?? createTerminalPresentation({
|
|
3500
|
-
env,
|
|
4736
|
+
env: env2,
|
|
3501
4737
|
stdoutIsTty: openDeps.stdoutIsTty ?? process.stdout.isTTY === true,
|
|
3502
4738
|
color: openDeps.color,
|
|
3503
4739
|
unicode: openDeps.unicode
|
|
@@ -3510,7 +4746,7 @@ async function cliSetupIdeFiles(argv, options = {}) {
|
|
|
3510
4746
|
await runOpenCursorMcpSettingsFlow({
|
|
3511
4747
|
...openDeps,
|
|
3512
4748
|
stdinIsTty,
|
|
3513
|
-
env,
|
|
4749
|
+
env: env2,
|
|
3514
4750
|
presentation
|
|
3515
4751
|
});
|
|
3516
4752
|
} else {
|
|
@@ -3526,7 +4762,7 @@ async function cliSetupIdeFiles(argv, options = {}) {
|
|
|
3526
4762
|
if (cleanup) {
|
|
3527
4763
|
console.log("[setup-ide-files] Running additional full-project cleanup (--cleanup)...");
|
|
3528
4764
|
const cleanupResult = await runCleanup({
|
|
3529
|
-
cwd,
|
|
4765
|
+
cwd: cwd2,
|
|
3530
4766
|
dryRun,
|
|
3531
4767
|
allProjects: false,
|
|
3532
4768
|
assumeYes: true
|
|
@@ -3539,6 +4775,455 @@ async function cliSetupIdeFiles(argv, options = {}) {
|
|
|
3539
4775
|
return 0;
|
|
3540
4776
|
}
|
|
3541
4777
|
|
|
4778
|
+
// src/localState/connectCommand.ts
|
|
4779
|
+
var path20 = __toESM(require("path"), 1);
|
|
4780
|
+
var os7 = __toESM(require("os"), 1);
|
|
4781
|
+
|
|
4782
|
+
// src/config.ts
|
|
4783
|
+
var process2 = __toESM(require("process"), 1);
|
|
4784
|
+
var fs14 = __toESM(require("fs"), 1);
|
|
4785
|
+
var path18 = __toESM(require("path"), 1);
|
|
4786
|
+
var dotenv = __toESM(require("dotenv"), 1);
|
|
4787
|
+
var import_v4 = require("zod/v4");
|
|
4788
|
+
var dotenvPath = path18.resolve(process2.cwd(), ".env");
|
|
4789
|
+
if (fs14.existsSync(dotenvPath)) {
|
|
4790
|
+
try {
|
|
4791
|
+
dotenv.config({ path: dotenvPath });
|
|
4792
|
+
} catch (err) {
|
|
4793
|
+
process2.stderr.write("[memoraone-mcp] Failed to load .env: " + String(err) + "\n");
|
|
4794
|
+
}
|
|
4795
|
+
}
|
|
4796
|
+
var EnvSchema = import_v4.z.object({
|
|
4797
|
+
MEMORAONE_API_URL: import_v4.z.string().url().optional(),
|
|
4798
|
+
MEMORAONE_API_KEY: import_v4.z.string().min(1).optional(),
|
|
4799
|
+
MEMORAONE_DEV_MODE: import_v4.z.string().min(1).optional(),
|
|
4800
|
+
MEMORAONE_AGENT_NAME: import_v4.z.string().min(1).optional(),
|
|
4801
|
+
MEMORAONE_AGENT_TYPE: import_v4.z.string().min(1).optional(),
|
|
4802
|
+
MEMORAONE_SOURCE: import_v4.z.string().min(1).optional(),
|
|
4803
|
+
MEMORAONE_IDE_TYPE: import_v4.z.enum(["cursor", "copilot-vscode", "jetbrains"]).optional(),
|
|
4804
|
+
MEMORAONE_WORKLOG: import_v4.z.string().min(1).optional(),
|
|
4805
|
+
MEMORAONE_HEARTBEAT: import_v4.z.string().min(1).optional(),
|
|
4806
|
+
MEMORAONE_HEARTBEAT_INTERVAL_MS: import_v4.z.string().min(1).optional()
|
|
4807
|
+
});
|
|
4808
|
+
var requiredEnvVars = [];
|
|
4809
|
+
var missingEnvVars = requiredEnvVars.filter((key) => {
|
|
4810
|
+
const value = process2.env[key];
|
|
4811
|
+
return value === void 0 || value.trim() === "";
|
|
4812
|
+
});
|
|
4813
|
+
if (missingEnvVars.length > 0) {
|
|
4814
|
+
for (const key of missingEnvVars) {
|
|
4815
|
+
process2.stderr.write(`Missing ${key}
|
|
4816
|
+
`);
|
|
4817
|
+
}
|
|
4818
|
+
process2.exit(1);
|
|
4819
|
+
}
|
|
4820
|
+
var parsed = EnvSchema.safeParse(process2.env);
|
|
4821
|
+
var resolvedApiUrl = resolveApiUrl(process2.env);
|
|
4822
|
+
if (!parsed.success) {
|
|
4823
|
+
const formatted = parsed.error.format();
|
|
4824
|
+
process2.stderr.write(
|
|
4825
|
+
"[memoraone-mcp] Invalid environment variables " + JSON.stringify(formatted) + "\n"
|
|
4826
|
+
);
|
|
4827
|
+
throw new Error("Config validation failed");
|
|
4828
|
+
}
|
|
4829
|
+
var parseBooleanFlag2 = (value, defaultValue) => {
|
|
4830
|
+
if (value === void 0) {
|
|
4831
|
+
return defaultValue;
|
|
4832
|
+
}
|
|
4833
|
+
const normalized = value.trim().toLowerCase();
|
|
4834
|
+
if (["1", "true", "yes", "on"].includes(normalized)) {
|
|
4835
|
+
return true;
|
|
4836
|
+
}
|
|
4837
|
+
if (["0", "false", "no", "off"].includes(normalized)) {
|
|
4838
|
+
return false;
|
|
4839
|
+
}
|
|
4840
|
+
return defaultValue;
|
|
4841
|
+
};
|
|
4842
|
+
var config2 = {
|
|
4843
|
+
apiUrl: resolvedApiUrl.replace(/\/+$/, ""),
|
|
4844
|
+
apiKey: parsed.data.MEMORAONE_API_KEY,
|
|
4845
|
+
agentName: parsed.data.MEMORAONE_AGENT_NAME ?? "cursor",
|
|
4846
|
+
agentType: parsed.data.MEMORAONE_AGENT_TYPE ?? "agent",
|
|
4847
|
+
source: parsed.data.MEMORAONE_SOURCE ?? "cursor",
|
|
4848
|
+
ideType: parsed.data.MEMORAONE_IDE_TYPE,
|
|
4849
|
+
devMode: parseBooleanFlag2(parsed.data.MEMORAONE_DEV_MODE, false),
|
|
4850
|
+
worklogEnabled: parseBooleanFlag2(parsed.data.MEMORAONE_WORKLOG, true),
|
|
4851
|
+
heartbeatEnabled: parseBooleanFlag2(parsed.data.MEMORAONE_HEARTBEAT, true),
|
|
4852
|
+
heartbeatIntervalMs: Number.parseInt(parsed.data.MEMORAONE_HEARTBEAT_INTERVAL_MS ?? "30000", 10)
|
|
4853
|
+
};
|
|
4854
|
+
|
|
4855
|
+
// src/repoFingerprint.ts
|
|
4856
|
+
var fs15 = __toESM(require("fs"), 1);
|
|
4857
|
+
var path19 = __toESM(require("path"), 1);
|
|
4858
|
+
var crypto2 = __toESM(require("crypto"), 1);
|
|
4859
|
+
var parseBooleanFlag3 = (value) => {
|
|
4860
|
+
if (!value) {
|
|
4861
|
+
return false;
|
|
4862
|
+
}
|
|
4863
|
+
const normalized = value.trim().toLowerCase();
|
|
4864
|
+
return ["1", "true", "yes", "on"].includes(normalized);
|
|
4865
|
+
};
|
|
4866
|
+
var debugEnabled2 = parseBooleanFlag3(process.env.MEMORAONE_DEV_MODE);
|
|
4867
|
+
var debugLog = (message) => {
|
|
4868
|
+
if (!debugEnabled2) {
|
|
4869
|
+
return;
|
|
4870
|
+
}
|
|
4871
|
+
process.stderr.write(`[memoraone-mcp][debug] ${message}
|
|
4872
|
+
`);
|
|
4873
|
+
};
|
|
4874
|
+
var normalizeRemoteUrl = (remoteUrl) => {
|
|
4875
|
+
let normalized = remoteUrl.trim();
|
|
4876
|
+
normalized = normalized.replace(/^[a-z]+:\/\//i, "");
|
|
4877
|
+
normalized = normalized.replace(/^git@([^:]+):/i, "$1/");
|
|
4878
|
+
normalized = normalized.replace(/\.git$/i, "");
|
|
4879
|
+
normalized = normalized.replace(/\/+$/, "");
|
|
4880
|
+
return normalized.toLowerCase();
|
|
4881
|
+
};
|
|
4882
|
+
var sha256 = (value) => {
|
|
4883
|
+
return crypto2.createHash("sha256").update(value).digest("hex");
|
|
4884
|
+
};
|
|
4885
|
+
var resolveGitDir = (gitPath) => {
|
|
4886
|
+
try {
|
|
4887
|
+
const stat4 = fs15.statSync(gitPath);
|
|
4888
|
+
if (stat4.isDirectory()) {
|
|
4889
|
+
return gitPath;
|
|
4890
|
+
}
|
|
4891
|
+
if (stat4.isFile()) {
|
|
4892
|
+
const content = fs15.readFileSync(gitPath, "utf8");
|
|
4893
|
+
const match = content.match(/^gitdir:\s*(.+)$/m);
|
|
4894
|
+
if (match) {
|
|
4895
|
+
const gitDir = match[1].trim();
|
|
4896
|
+
return path19.resolve(path19.dirname(gitPath), gitDir);
|
|
4897
|
+
}
|
|
4898
|
+
}
|
|
4899
|
+
} catch {
|
|
4900
|
+
return null;
|
|
4901
|
+
}
|
|
4902
|
+
return null;
|
|
4903
|
+
};
|
|
4904
|
+
var findGitRoot = (start) => {
|
|
4905
|
+
let current = path19.resolve(start);
|
|
4906
|
+
while (true) {
|
|
4907
|
+
const gitPath = path19.join(current, ".git");
|
|
4908
|
+
if (fs15.existsSync(gitPath)) {
|
|
4909
|
+
const gitDir = resolveGitDir(gitPath);
|
|
4910
|
+
if (gitDir) {
|
|
4911
|
+
return { gitRoot: current, gitDir };
|
|
4912
|
+
}
|
|
4913
|
+
}
|
|
4914
|
+
const parent = path19.dirname(current);
|
|
4915
|
+
if (parent === current) {
|
|
4916
|
+
break;
|
|
4917
|
+
}
|
|
4918
|
+
current = parent;
|
|
4919
|
+
}
|
|
4920
|
+
return null;
|
|
4921
|
+
};
|
|
4922
|
+
var readOriginRemote = (gitDir) => {
|
|
4923
|
+
const configPath = path19.join(gitDir, "config");
|
|
4924
|
+
try {
|
|
4925
|
+
const content = fs15.readFileSync(configPath, "utf8");
|
|
4926
|
+
const lines = content.split(/\r?\n/);
|
|
4927
|
+
let inOrigin = false;
|
|
4928
|
+
for (const line of lines) {
|
|
4929
|
+
const sectionMatch = line.match(/^\s*\[(.+)]\s*$/);
|
|
4930
|
+
if (sectionMatch) {
|
|
4931
|
+
inOrigin = sectionMatch[1].trim() === 'remote "origin"';
|
|
4932
|
+
continue;
|
|
4933
|
+
}
|
|
4934
|
+
if (inOrigin) {
|
|
4935
|
+
const urlMatch = line.match(/^\s*url\s*=\s*(.+)\s*$/);
|
|
4936
|
+
if (urlMatch) {
|
|
4937
|
+
return urlMatch[1].trim();
|
|
4938
|
+
}
|
|
4939
|
+
}
|
|
4940
|
+
}
|
|
4941
|
+
} catch {
|
|
4942
|
+
return null;
|
|
4943
|
+
}
|
|
4944
|
+
return null;
|
|
4945
|
+
};
|
|
4946
|
+
function resolveRepoFingerprint(cwd2) {
|
|
4947
|
+
const found = findGitRoot(cwd2);
|
|
4948
|
+
if (!found) {
|
|
4949
|
+
const fallbackPath = path19.resolve(cwd2);
|
|
4950
|
+
const fingerprint2 = sha256(fallbackPath);
|
|
4951
|
+
debugLog(`repo fingerprint=${fingerprint2} source=path-fallback`);
|
|
4952
|
+
return {
|
|
4953
|
+
fingerprint: fingerprint2,
|
|
4954
|
+
gitRoot: fallbackPath,
|
|
4955
|
+
source: "path-fallback"
|
|
4956
|
+
};
|
|
4957
|
+
}
|
|
4958
|
+
const { gitRoot, gitDir } = found;
|
|
4959
|
+
const remoteUrl = readOriginRemote(gitDir);
|
|
4960
|
+
if (remoteUrl) {
|
|
4961
|
+
const normalized = normalizeRemoteUrl(remoteUrl);
|
|
4962
|
+
const fingerprint2 = sha256(normalized);
|
|
4963
|
+
debugLog(`repo fingerprint=${fingerprint2} source=git-remote`);
|
|
4964
|
+
return {
|
|
4965
|
+
fingerprint: fingerprint2,
|
|
4966
|
+
gitRoot,
|
|
4967
|
+
remoteUrl,
|
|
4968
|
+
source: "git-remote"
|
|
4969
|
+
};
|
|
4970
|
+
}
|
|
4971
|
+
const fingerprint = sha256(path19.resolve(gitRoot));
|
|
4972
|
+
debugLog(`repo fingerprint=${fingerprint} source=path-fallback`);
|
|
4973
|
+
return {
|
|
4974
|
+
fingerprint,
|
|
4975
|
+
gitRoot,
|
|
4976
|
+
source: "path-fallback"
|
|
4977
|
+
};
|
|
4978
|
+
}
|
|
4979
|
+
|
|
4980
|
+
// src/localState/connectCommand.ts
|
|
4981
|
+
function normalizeConnectCode(code) {
|
|
4982
|
+
const trimmed = code.trim();
|
|
4983
|
+
if (!trimmed.startsWith("mcc_")) {
|
|
4984
|
+
throw new Error("[memoraone-mcp] Connect code must start with mcc_");
|
|
4985
|
+
}
|
|
4986
|
+
return trimmed;
|
|
4987
|
+
}
|
|
4988
|
+
function normalizeGitRemote(remoteUrl) {
|
|
4989
|
+
if (!remoteUrl) return null;
|
|
4990
|
+
let normalized = remoteUrl.trim();
|
|
4991
|
+
normalized = normalized.replace(/^[a-z]+:\/\//i, "");
|
|
4992
|
+
normalized = normalized.replace(/^git@([^:]+):/i, "$1/");
|
|
4993
|
+
normalized = normalized.replace(/\.git$/i, "");
|
|
4994
|
+
normalized = normalized.replace(/\/+$/, "");
|
|
4995
|
+
return normalized.toLowerCase() || null;
|
|
4996
|
+
}
|
|
4997
|
+
async function runConnectCommand(options) {
|
|
4998
|
+
const code = normalizeConnectCode(options.code);
|
|
4999
|
+
const cwd2 = path20.resolve(options.cwd ?? process.cwd());
|
|
5000
|
+
const apiUrl = (options.apiUrl ?? config2.apiUrl).replace(/\/+$/, "");
|
|
5001
|
+
const homeDir = options.homeDir ?? os7.homedir();
|
|
5002
|
+
const environment = "local";
|
|
5003
|
+
const executionMode = await resolvePackageExecutionMode({
|
|
5004
|
+
executionMode: options.executionMode,
|
|
5005
|
+
// Only honor an explicit caller cliPath; never auto-resolve before mode detection
|
|
5006
|
+
// (published installs must not be forced into local monorepo mode).
|
|
5007
|
+
cliPath: options.cliPath,
|
|
5008
|
+
apiUrl,
|
|
5009
|
+
scriptPath: options.scriptPath,
|
|
5010
|
+
env: options.env,
|
|
5011
|
+
resolveBuiltCliPath: resolveBuiltCliPathAsync
|
|
5012
|
+
});
|
|
5013
|
+
let resolvedMode = executionMode;
|
|
5014
|
+
if (resolvedMode.kind === "local" && !resolvedMode.cliPath) {
|
|
5015
|
+
const cliPath = await resolveBuiltCliPathAsync();
|
|
5016
|
+
if (!cliPath) {
|
|
5017
|
+
throw new Error(
|
|
5018
|
+
"[memoraone-mcp] Local connect requires a built CLI at packages/mcp/dist/cli.cjs. Run pnpm build first."
|
|
5019
|
+
);
|
|
5020
|
+
}
|
|
5021
|
+
resolvedMode = { kind: "local", cliPath };
|
|
5022
|
+
}
|
|
5023
|
+
const ensured = await ensureRepositoryBindingForRoot(cwd2, {
|
|
5024
|
+
homeDir,
|
|
5025
|
+
identityDeps: options.identityDeps,
|
|
5026
|
+
createIfMissing: true
|
|
5027
|
+
});
|
|
5028
|
+
if (ensured.legacyM1WarningPath) {
|
|
5029
|
+
process.stderr.write(
|
|
5030
|
+
`[memoraone-mcp] warning: ignoring legacy ${path20.basename(ensured.legacyM1WarningPath)} at ${ensured.legacyM1WarningPath} (credentials and binding are package-managed)
|
|
5031
|
+
`
|
|
5032
|
+
);
|
|
5033
|
+
}
|
|
5034
|
+
const fingerprint = resolveRepoFingerprint(cwd2);
|
|
5035
|
+
const displayName = cwd2;
|
|
5036
|
+
const normalizedGitRemote = normalizeGitRemote(fingerprint.remoteUrl);
|
|
5037
|
+
const { clientRedeemKey } = await ensureClientRedeemKey(
|
|
5038
|
+
ensured.repositoryBindingId,
|
|
5039
|
+
options.credentialOptions
|
|
5040
|
+
);
|
|
5041
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5042
|
+
const pendingRecord = {
|
|
5043
|
+
v: 1,
|
|
5044
|
+
repositoryBindingId: ensured.repositoryBindingId,
|
|
5045
|
+
workspaceRoot: cwd2,
|
|
5046
|
+
filesystemIdentity: ensured.identity,
|
|
5047
|
+
rootFingerprint: fingerprint.fingerprint,
|
|
5048
|
+
displayName,
|
|
5049
|
+
environment,
|
|
5050
|
+
apiUrl,
|
|
5051
|
+
normalizedGitRemote,
|
|
5052
|
+
status: "pending",
|
|
5053
|
+
createdAt: now,
|
|
5054
|
+
updatedAt: now,
|
|
5055
|
+
packageVersion: options.packageVersion ?? null,
|
|
5056
|
+
ideType: options.ideType ?? null
|
|
5057
|
+
};
|
|
5058
|
+
await writeBindingRecord(pendingRecord, homeDir);
|
|
5059
|
+
await upsertPathIndexEntry({
|
|
5060
|
+
repositoryBindingId: ensured.repositoryBindingId,
|
|
5061
|
+
workspaceRoot: cwd2,
|
|
5062
|
+
identity: ensured.identity,
|
|
5063
|
+
homeDir,
|
|
5064
|
+
previousPath: ensured.renamedFrom
|
|
5065
|
+
});
|
|
5066
|
+
const redeemed = await redeemLocalConnectCode(
|
|
5067
|
+
apiUrl,
|
|
5068
|
+
{
|
|
5069
|
+
code,
|
|
5070
|
+
client_redeem_key: clientRedeemKey,
|
|
5071
|
+
repository_binding_id: ensured.repositoryBindingId,
|
|
5072
|
+
root_fingerprint: fingerprint.fingerprint,
|
|
5073
|
+
display_name: displayName,
|
|
5074
|
+
environment,
|
|
5075
|
+
normalized_git_remote: normalizedGitRemote,
|
|
5076
|
+
platform: ensured.identity.platform,
|
|
5077
|
+
package_version: options.packageVersion ?? null,
|
|
5078
|
+
ide_type: options.ideType ?? null
|
|
5079
|
+
},
|
|
5080
|
+
{ fetchImpl: options.fetchImpl }
|
|
5081
|
+
);
|
|
5082
|
+
await updateInstallationCredentials(
|
|
5083
|
+
ensured.repositoryBindingId,
|
|
5084
|
+
{
|
|
5085
|
+
accessToken: redeemed.access_token,
|
|
5086
|
+
refreshToken: redeemed.refresh_token,
|
|
5087
|
+
accessTokenExpiresAt: redeemed.access_token_expires_at ?? void 0,
|
|
5088
|
+
refreshTokenExpiresAt: redeemed.refresh_token_expires_at ?? void 0,
|
|
5089
|
+
installationPublicId: redeemed.installation_public_id,
|
|
5090
|
+
projectId: redeemed.project_id
|
|
5091
|
+
},
|
|
5092
|
+
{ ...options.credentialOptions, clearKeys: ["clientRedeemKey"] }
|
|
5093
|
+
);
|
|
5094
|
+
const connectedRecord = {
|
|
5095
|
+
...pendingRecord,
|
|
5096
|
+
installationPublicId: redeemed.installation_public_id,
|
|
5097
|
+
projectId: redeemed.project_id,
|
|
5098
|
+
status: "connected",
|
|
5099
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
5100
|
+
};
|
|
5101
|
+
await writeBindingRecord(connectedRecord, homeDir);
|
|
5102
|
+
const baseSuccess = `Connected repository binding ${ensured.repositoryBindingId}` + (redeemed.recovered ? " (recovered)" : "") + ` to project ${redeemed.project_id}.`;
|
|
5103
|
+
if (options.configureIdes !== false) {
|
|
5104
|
+
const targets = { cursor: true, vscode: true, jetbrains: true };
|
|
5105
|
+
const setup = options.setupIdeFiles ?? runSetupIdeFiles;
|
|
5106
|
+
const modeSetup = setupOptionsFromPackageExecutionMode(resolvedMode);
|
|
5107
|
+
let setupResult;
|
|
5108
|
+
try {
|
|
5109
|
+
setupResult = await setup({
|
|
5110
|
+
cwd: cwd2,
|
|
5111
|
+
// Use the exact bound workspace root — do not rediscover via .git / .m1.
|
|
5112
|
+
workspaceRoot: cwd2,
|
|
5113
|
+
targets,
|
|
5114
|
+
force: true,
|
|
5115
|
+
dryRun: false,
|
|
5116
|
+
noGitignore: true,
|
|
5117
|
+
skipDaemonCleanup: true,
|
|
5118
|
+
homeDir,
|
|
5119
|
+
// Propagate the redeemed binding API URL (backend only — not execution mode).
|
|
5120
|
+
apiUrl,
|
|
5121
|
+
...modeSetup,
|
|
5122
|
+
...options.setupIdeOptions
|
|
5123
|
+
});
|
|
5124
|
+
} catch (err) {
|
|
5125
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
5126
|
+
setupResult = {
|
|
5127
|
+
exitCode: 1,
|
|
5128
|
+
repoRoot: cwd2,
|
|
5129
|
+
outcomes: {},
|
|
5130
|
+
error: message
|
|
5131
|
+
};
|
|
5132
|
+
}
|
|
5133
|
+
if (setupResult.exitCode !== 0) {
|
|
5134
|
+
const detail = setupResult.error ?? "unknown IDE setup error";
|
|
5135
|
+
const repair = formatIdeSetupRepairHint(cwd2, resolvedMode);
|
|
5136
|
+
return {
|
|
5137
|
+
exitCode: 1,
|
|
5138
|
+
repositoryBindingId: ensured.repositoryBindingId,
|
|
5139
|
+
projectId: redeemed.project_id,
|
|
5140
|
+
installationPublicId: redeemed.installation_public_id,
|
|
5141
|
+
recovered: redeemed.recovered,
|
|
5142
|
+
createdBinding: ensured.created,
|
|
5143
|
+
legacyM1WarningPath: ensured.legacyM1WarningPath,
|
|
5144
|
+
ideSetupError: detail,
|
|
5145
|
+
executionMode: resolvedMode,
|
|
5146
|
+
message: `Repository connection exists for binding ${ensured.repositoryBindingId} (project ${redeemed.project_id}), but IDE configuration failed: ${detail}. Credentials and binding were kept. ${repair}`
|
|
5147
|
+
};
|
|
5148
|
+
}
|
|
5149
|
+
}
|
|
5150
|
+
return {
|
|
5151
|
+
exitCode: 0,
|
|
5152
|
+
repositoryBindingId: ensured.repositoryBindingId,
|
|
5153
|
+
projectId: redeemed.project_id,
|
|
5154
|
+
installationPublicId: redeemed.installation_public_id,
|
|
5155
|
+
recovered: redeemed.recovered,
|
|
5156
|
+
createdBinding: ensured.created,
|
|
5157
|
+
legacyM1WarningPath: ensured.legacyM1WarningPath,
|
|
5158
|
+
executionMode: resolvedMode,
|
|
5159
|
+
message: baseSuccess
|
|
5160
|
+
};
|
|
5161
|
+
}
|
|
5162
|
+
function parseConnectArgv(argv) {
|
|
5163
|
+
let code;
|
|
5164
|
+
let apiUrl;
|
|
5165
|
+
for (let i = 0; i < argv.length; i++) {
|
|
5166
|
+
const a = argv[i];
|
|
5167
|
+
if (a === "--api-url") {
|
|
5168
|
+
const value = argv[++i];
|
|
5169
|
+
if (!value || value.startsWith("-")) {
|
|
5170
|
+
return { error: "Usage: memoraone-mcp connect <code> [--api-url <url>]" };
|
|
5171
|
+
}
|
|
5172
|
+
apiUrl = value;
|
|
5173
|
+
continue;
|
|
5174
|
+
}
|
|
5175
|
+
if (a.startsWith("--api-url=")) {
|
|
5176
|
+
const value = a.slice("--api-url=".length);
|
|
5177
|
+
if (!value) {
|
|
5178
|
+
return { error: "Usage: memoraone-mcp connect <code> [--api-url <url>]" };
|
|
5179
|
+
}
|
|
5180
|
+
apiUrl = value;
|
|
5181
|
+
continue;
|
|
5182
|
+
}
|
|
5183
|
+
if (a.startsWith("-")) {
|
|
5184
|
+
return { error: `Unknown connect option: ${a}` };
|
|
5185
|
+
}
|
|
5186
|
+
if (!code) {
|
|
5187
|
+
code = a;
|
|
5188
|
+
continue;
|
|
5189
|
+
}
|
|
5190
|
+
return { error: "Usage: memoraone-mcp connect <code> [--api-url <url>]" };
|
|
5191
|
+
}
|
|
5192
|
+
return { code, apiUrl };
|
|
5193
|
+
}
|
|
5194
|
+
async function cliConnect(argv) {
|
|
5195
|
+
const parsed2 = parseConnectArgv(argv);
|
|
5196
|
+
if (parsed2.error) {
|
|
5197
|
+
process.stderr.write(`${parsed2.error}
|
|
5198
|
+
`);
|
|
5199
|
+
return 1;
|
|
5200
|
+
}
|
|
5201
|
+
if (!parsed2.code) {
|
|
5202
|
+
process.stderr.write("Usage: memoraone-mcp connect <code> [--api-url <url>]\n");
|
|
5203
|
+
return 1;
|
|
5204
|
+
}
|
|
5205
|
+
try {
|
|
5206
|
+
const result = await runConnectCommand({
|
|
5207
|
+
code: parsed2.code,
|
|
5208
|
+
cwd: process.cwd(),
|
|
5209
|
+
apiUrl: parsed2.apiUrl,
|
|
5210
|
+
packageVersion: process.env.npm_package_version ?? null
|
|
5211
|
+
});
|
|
5212
|
+
if (result.exitCode === 0) {
|
|
5213
|
+
process.stdout.write(`${result.message}
|
|
5214
|
+
`);
|
|
5215
|
+
} else {
|
|
5216
|
+
process.stderr.write(`[memoraone-mcp] ${result.message}
|
|
5217
|
+
`);
|
|
5218
|
+
}
|
|
5219
|
+
return result.exitCode;
|
|
5220
|
+
} catch (err) {
|
|
5221
|
+
process.stderr.write(`[memoraone-mcp] connect failed: ${String(err)}
|
|
5222
|
+
`);
|
|
5223
|
+
return 1;
|
|
5224
|
+
}
|
|
5225
|
+
}
|
|
5226
|
+
|
|
3542
5227
|
// src/cli.ts
|
|
3543
5228
|
var { version } = require_package();
|
|
3544
5229
|
var args = process.argv.slice(2);
|
|
@@ -3548,7 +5233,7 @@ if (args.includes("--version") || args.includes("-v")) {
|
|
|
3548
5233
|
}
|
|
3549
5234
|
if (args.includes("--help") || args.includes("-h")) {
|
|
3550
5235
|
console.log(
|
|
3551
|
-
"Usage: memoraone-mcp [--version] [--help] [--daemon --
|
|
5236
|
+
"Usage: memoraone-mcp [--version] [--help]\n memoraone-mcp connect <code> [--api-url <url>]\n memoraone-mcp [--daemon --binding-id <mrb_\u2026> [--ide cursor|copilot-vscode|jetbrains]]\n memoraone-mcp setup-ide-files [--all|--cursor|--vscode|--jetbrains] [--force] [--dry-run] [--no-gitignore] [--cleanup] [--dev] [--repair] [--workspace-root <path>] [--api-url <url>]\n Cursor API environment (with --cursor or --all): --local (node + built cli.cjs + local API) | --staging (npx + staging API)\n --workspace-root: configure an explicit bound workspace (skips .git discovery; for fileless Local MCP repair)\n --api-url: developer-only local/dev API endpoint (defaults from binding or http://localhost:3001; never Studio :3000)\n memoraone-mcp cleanup [--project-id <uuid>] [--ide cursor|copilot-vscode|jetbrains] [--dry-run] [--all-projects] [--yes]"
|
|
3552
5237
|
);
|
|
3553
5238
|
process.exit(0);
|
|
3554
5239
|
}
|
|
@@ -3558,6 +5243,12 @@ if (args[0] === "cleanup") {
|
|
|
3558
5243
|
`);
|
|
3559
5244
|
process.exit(1);
|
|
3560
5245
|
});
|
|
5246
|
+
} else if (args[0] === "connect") {
|
|
5247
|
+
cliConnect(args.slice(1)).then((code) => process.exit(code)).catch((err) => {
|
|
5248
|
+
process.stderr.write(`[memoraone-mcp] connect fatal: ${String(err)}
|
|
5249
|
+
`);
|
|
5250
|
+
process.exit(1);
|
|
5251
|
+
});
|
|
3561
5252
|
} else if (args[0] === "setup-ide-files") {
|
|
3562
5253
|
cliSetupIdeFiles(args.slice(1)).then((code) => process.exit(code)).catch((err) => {
|
|
3563
5254
|
process.stderr.write(`[memoraone-mcp] setup-ide-files fatal: ${String(err)}
|
|
@@ -3571,7 +5262,9 @@ if (args[0] === "cleanup") {
|
|
|
3571
5262
|
process.exit(1);
|
|
3572
5263
|
});
|
|
3573
5264
|
} else {
|
|
3574
|
-
runBridgeProxy({ cliPath: process.argv[1] }).
|
|
5265
|
+
runBridgeProxy({ cliPath: process.argv[1] }).then(() => {
|
|
5266
|
+
process.exit(0);
|
|
5267
|
+
}).catch((err) => {
|
|
3575
5268
|
process.stderr.write(`[memoraone-mcp][bridge] fatal: ${String(err)}
|
|
3576
5269
|
`);
|
|
3577
5270
|
process.exit(1);
|