@memoraone/mcp 0.1.35 → 0.1.36
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 +2040 -547
- 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.36",
|
|
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
|
-
|
|
127
|
-
}
|
|
128
|
-
function normalizeEnvironment(raw) {
|
|
129
|
-
if (raw === void 0 || raw === null || typeof raw !== "string") {
|
|
130
|
-
return void 0;
|
|
131
|
-
}
|
|
132
|
-
const trimmed = raw.trim();
|
|
133
|
-
return trimmed === "" ? void 0 : trimmed;
|
|
134
|
-
}
|
|
135
|
-
function parseAndValidateM1(content, markerPath) {
|
|
136
|
-
let parsed;
|
|
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 });
|
|
137
141
|
try {
|
|
138
|
-
|
|
142
|
+
await fs.chmod(dirPath, STATE_DIR_MODE);
|
|
139
143
|
} catch {
|
|
140
|
-
throw new Error(`[memoraone-mcp] Invalid memoraone.m1 JSON at ${markerPath}`);
|
|
141
144
|
}
|
|
142
|
-
const projectId = parsed?.projectId ?? parsed?.project_id;
|
|
143
|
-
if (!projectId || typeof projectId !== "string") {
|
|
144
|
-
throw new Error(`[memoraone-mcp] memoraone.m1 missing projectId at ${markerPath}`);
|
|
145
|
-
}
|
|
146
|
-
if (!uuidRegex.test(projectId.trim())) {
|
|
147
|
-
throw new Error(`[memoraone-mcp] memoraone.m1 projectId is not a UUID at ${markerPath}`);
|
|
148
|
-
}
|
|
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
145
|
}
|
|
154
|
-
async function
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
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;
|
|
158
167
|
}
|
|
159
|
-
|
|
168
|
+
}
|
|
169
|
+
async function readJsonFile(filePath) {
|
|
160
170
|
try {
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
return environment === void 0 ? { projectId, apiKey, foundAt: markerPath } : { projectId, apiKey, environment, foundAt: markerPath };
|
|
171
|
+
const raw = await fs.readFile(filePath, "utf8");
|
|
172
|
+
return JSON.parse(raw);
|
|
164
173
|
} catch (err) {
|
|
165
174
|
if (err?.code === "ENOENT") {
|
|
166
175
|
return null;
|
|
167
176
|
}
|
|
177
|
+
if (err instanceof SyntaxError) {
|
|
178
|
+
throw new Error(`[memoraone-mcp] Corrupt JSON at ${filePath}`);
|
|
179
|
+
}
|
|
168
180
|
throw err;
|
|
169
181
|
}
|
|
170
182
|
}
|
|
171
|
-
async function
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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");
|
|
196
|
+
}
|
|
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}`);
|
|
205
|
+
}
|
|
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
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
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) {
|
|
175
333
|
try {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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
|
+
};
|
|
180
361
|
} catch (err) {
|
|
181
|
-
if (err?.code
|
|
182
|
-
|
|
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((resolve16) => setTimeout(resolve16, retryDelayMs));
|
|
370
|
+
continue;
|
|
183
371
|
}
|
|
372
|
+
throw err;
|
|
184
373
|
}
|
|
185
|
-
const parent = path2.dirname(current);
|
|
186
|
-
if (parent === current) {
|
|
187
|
-
break;
|
|
188
|
-
}
|
|
189
|
-
current = parent;
|
|
190
374
|
}
|
|
191
|
-
|
|
375
|
+
throw new Error(`[memoraone-mcp] Failed to acquire lock ${lockName}`);
|
|
192
376
|
}
|
|
193
|
-
function
|
|
194
|
-
|
|
195
|
-
|
|
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) {
|
|
397
|
+
return null;
|
|
398
|
+
}
|
|
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;
|
|
420
|
+
try {
|
|
421
|
+
names = await fs3.readdir(dir);
|
|
422
|
+
} catch (err) {
|
|
423
|
+
if (err?.code === "ENOENT") return [];
|
|
424
|
+
throw err;
|
|
196
425
|
}
|
|
197
|
-
const list = Array.isArray(workspaceRoot) ? workspaceRoot : [workspaceRoot];
|
|
198
|
-
const seen = /* @__PURE__ */ new Set();
|
|
199
426
|
const out = [];
|
|
200
|
-
for (const
|
|
201
|
-
if (
|
|
202
|
-
|
|
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 {
|
|
203
434
|
}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
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;
|
|
207
458
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
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;
|
|
212
467
|
}
|
|
213
468
|
}
|
|
214
|
-
|
|
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)) {
|
|
524
|
+
return null;
|
|
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_"));
|
|
215
623
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
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 {
|
|
220
660
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
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 {
|
|
224
666
|
}
|
|
225
|
-
|
|
226
|
-
|
|
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 {
|
|
227
691
|
}
|
|
228
|
-
|
|
692
|
+
throw new Error(`[memoraone-mcp] Unsupported platform for device ID: ${platform2}`);
|
|
229
693
|
}
|
|
230
|
-
async function
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
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
|
+
}
|
|
771
|
+
throw err;
|
|
772
|
+
}
|
|
773
|
+
}
|
|
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
|
+
}
|
|
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, path20, options = {}) {
|
|
859
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
860
|
+
const url = `${baseUrl.replace(/\/+$/, "")}${path20.startsWith("/") ? path20 : `/${path20}`}`;
|
|
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;
|
|
876
|
+
}
|
|
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}`);
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
}
|
|
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;
|
|
921
|
+
}
|
|
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
|
+
}
|
|
236
977
|
return {
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
...explicitBinding.environment !== void 0 ? { environment: explicitBinding.environment } : {},
|
|
242
|
-
bindingSource: "explicit-m1-path",
|
|
243
|
-
apiKeySource: resolved.apiKeySource
|
|
978
|
+
repositoryBindingId: record.repositoryBindingId,
|
|
979
|
+
identity,
|
|
980
|
+
created: false,
|
|
981
|
+
legacyM1WarningPath
|
|
244
982
|
};
|
|
245
983
|
}
|
|
246
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
|
+
};
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
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
|
+
};
|
|
1022
|
+
}
|
|
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
|
+
);
|
|
1045
|
+
}
|
|
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
|
+
);
|
|
1054
|
+
}
|
|
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;
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
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((resolve16) => {
|
|
1532
|
+
this.waiters.push(resolve16);
|
|
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 fs10 = __toESM(require("fs/promises"), 1);
|
|
1606
|
+
var path13 = __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 fs9 = __toESM(require("fs/promises"), 1);
|
|
1614
|
+
var os4 = __toESM(require("os"), 1);
|
|
1615
|
+
var path12 = __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
|
}
|
|
@@ -886,23 +1788,30 @@ async function resolveBindingFromInitializeParams(params, options = {}) {
|
|
|
886
1788
|
}
|
|
887
1789
|
|
|
888
1790
|
// src/cursorGlobalMcpConfig.ts
|
|
889
|
-
var
|
|
1791
|
+
var execFileAsync2 = (0, import_node_util2.promisify)(import_node_child_process2.execFile);
|
|
890
1792
|
var MEMORAONE_PROD_API_URL = "https://api.memoraone.com";
|
|
891
1793
|
var MEMORAONE_LOCAL_API_URL = "http://localhost:3001";
|
|
892
1794
|
var MEMORAONE_STAGING_API_URL = "https://memora-api-staging-phbtrzocjq-uk.a.run.app";
|
|
893
1795
|
var MEMORAONE_STAGING_API_URL_PREFIX = "https://memora-api-staging-";
|
|
894
|
-
function
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
1796
|
+
function normalizeApiUrl(url) {
|
|
1797
|
+
return url.trim().replace(/\/+$/, "");
|
|
1798
|
+
}
|
|
1799
|
+
function resolveIdeApiUrl(options) {
|
|
1800
|
+
if (options.environment === "staging") return MEMORAONE_STAGING_API_URL;
|
|
1801
|
+
if (options.environment === "production") return MEMORAONE_PROD_API_URL;
|
|
1802
|
+
if (options.apiUrl) return normalizeApiUrl(options.apiUrl);
|
|
1803
|
+
return MEMORAONE_LOCAL_API_URL;
|
|
898
1804
|
}
|
|
899
1805
|
function buildMemoraoneCursorMcpServer(options) {
|
|
900
|
-
const
|
|
901
|
-
MEMORAONE_API_URL:
|
|
1806
|
+
const env2 = {
|
|
1807
|
+
MEMORAONE_API_URL: resolveIdeApiUrl({
|
|
1808
|
+
environment: options.environment,
|
|
1809
|
+
apiUrl: options.apiUrl
|
|
1810
|
+
}),
|
|
902
1811
|
MEMORAONE_IDE_TYPE: "cursor"
|
|
903
1812
|
};
|
|
904
1813
|
if (options.workspaceRoot !== void 0) {
|
|
905
|
-
|
|
1814
|
+
env2[MEMORAONE_WORKSPACE_ROOT_ENV] = path12.resolve(options.workspaceRoot);
|
|
906
1815
|
}
|
|
907
1816
|
if (options.environment === "local") {
|
|
908
1817
|
if (!options.cliPath) {
|
|
@@ -911,7 +1820,7 @@ function buildMemoraoneCursorMcpServer(options) {
|
|
|
911
1820
|
return {
|
|
912
1821
|
command: "node",
|
|
913
1822
|
args: [options.cliPath],
|
|
914
|
-
env
|
|
1823
|
+
env: env2
|
|
915
1824
|
};
|
|
916
1825
|
}
|
|
917
1826
|
if (!options.npxPath) {
|
|
@@ -920,12 +1829,12 @@ function buildMemoraoneCursorMcpServer(options) {
|
|
|
920
1829
|
return {
|
|
921
1830
|
command: options.npxPath,
|
|
922
1831
|
args: ["-y", "@memoraone/mcp@latest"],
|
|
923
|
-
env
|
|
1832
|
+
env: env2
|
|
924
1833
|
};
|
|
925
1834
|
}
|
|
926
1835
|
async function pathExists(filePath) {
|
|
927
1836
|
try {
|
|
928
|
-
await
|
|
1837
|
+
await fs9.access(filePath);
|
|
929
1838
|
return true;
|
|
930
1839
|
} catch {
|
|
931
1840
|
return false;
|
|
@@ -935,13 +1844,13 @@ function stripLeadingLineComments(text) {
|
|
|
935
1844
|
return text.split("\n").filter((line) => !/^\s*\/\//.test(line)).join("\n");
|
|
936
1845
|
}
|
|
937
1846
|
function getKnownCursorGlobalMcpConfigCandidates(homeDir) {
|
|
938
|
-
return [
|
|
1847
|
+
return [path12.join(homeDir, ".cursor", "mcp.json")];
|
|
939
1848
|
}
|
|
940
1849
|
async function detectCursorGlobalMcpConfig(options) {
|
|
941
1850
|
if (options?.explicitPath) {
|
|
942
1851
|
return { ok: true, path: options.explicitPath, detectedExisting: await pathExists(options.explicitPath) };
|
|
943
1852
|
}
|
|
944
|
-
const homeDir = options?.homeDir ??
|
|
1853
|
+
const homeDir = options?.homeDir ?? os4.homedir();
|
|
945
1854
|
const candidates = getKnownCursorGlobalMcpConfigCandidates(homeDir);
|
|
946
1855
|
const existing = [];
|
|
947
1856
|
for (const candidate of candidates) {
|
|
@@ -976,12 +1885,12 @@ async function isWorkingNpx(npxPath) {
|
|
|
976
1885
|
if (!await pathExists(npxPath)) return false;
|
|
977
1886
|
if (process.platform !== "win32") {
|
|
978
1887
|
try {
|
|
979
|
-
await
|
|
1888
|
+
await fs9.access(npxPath, fs9.constants.X_OK);
|
|
980
1889
|
} catch {
|
|
981
1890
|
return false;
|
|
982
1891
|
}
|
|
983
1892
|
}
|
|
984
|
-
await
|
|
1893
|
+
await execFileAsync2(npxPath, ["--version"], { timeout: 1e4 });
|
|
985
1894
|
return true;
|
|
986
1895
|
} catch {
|
|
987
1896
|
return false;
|
|
@@ -998,18 +1907,18 @@ async function resolveNpxPath() {
|
|
|
998
1907
|
const pathSep = process.platform === "win32" ? ";" : ":";
|
|
999
1908
|
for (const dir of (process.env.PATH ?? "").split(pathSep)) {
|
|
1000
1909
|
if (!dir) continue;
|
|
1001
|
-
candidates.push(
|
|
1910
|
+
candidates.push(path12.join(dir, npxName));
|
|
1002
1911
|
}
|
|
1003
1912
|
try {
|
|
1004
1913
|
const lookupCmd = process.platform === "win32" ? "where" : "which";
|
|
1005
|
-
const { stdout } = await
|
|
1914
|
+
const { stdout } = await execFileAsync2(lookupCmd, [npxName], { timeout: 5e3 });
|
|
1006
1915
|
const first = stdout.trim().split(/\r?\n/).map((line) => line.trim()).find(Boolean);
|
|
1007
1916
|
if (first) candidates.unshift(first);
|
|
1008
1917
|
} catch {
|
|
1009
1918
|
}
|
|
1010
1919
|
const seen = /* @__PURE__ */ new Set();
|
|
1011
1920
|
for (const candidate of candidates) {
|
|
1012
|
-
const abs =
|
|
1921
|
+
const abs = path12.isAbsolute(candidate) ? candidate : path12.resolve(candidate);
|
|
1013
1922
|
const key = process.platform === "win32" ? abs.toLowerCase() : abs;
|
|
1014
1923
|
if (seen.has(key)) continue;
|
|
1015
1924
|
seen.add(key);
|
|
@@ -1025,7 +1934,8 @@ function mergeCursorRepoMcpConfigObject(existing, writeOptions) {
|
|
|
1025
1934
|
environment,
|
|
1026
1935
|
npxPath: writeOptions.npxPath,
|
|
1027
1936
|
cliPath: writeOptions.cliPath,
|
|
1028
|
-
workspaceRoot: writeOptions.repoRoot
|
|
1937
|
+
workspaceRoot: writeOptions.repoRoot,
|
|
1938
|
+
apiUrl: writeOptions.apiUrl
|
|
1029
1939
|
});
|
|
1030
1940
|
return { ...base, mcpServers };
|
|
1031
1941
|
}
|
|
@@ -1040,25 +1950,25 @@ function isManagedMemoraoneCursorServer(server) {
|
|
|
1040
1950
|
const s = server;
|
|
1041
1951
|
if (!Array.isArray(s.args) || s.args.length !== 2) return false;
|
|
1042
1952
|
if (s.args[0] !== "-y" || s.args[1] !== "@memoraone/mcp@latest") return false;
|
|
1043
|
-
const
|
|
1044
|
-
if (!
|
|
1045
|
-
return memoraoneEnvMatchesManagedCleanupShape(
|
|
1953
|
+
const env2 = s.env;
|
|
1954
|
+
if (!env2 || typeof env2 !== "object") return false;
|
|
1955
|
+
return memoraoneEnvMatchesManagedCleanupShape(env2);
|
|
1046
1956
|
}
|
|
1047
|
-
function cursorConfigHasManagedMemoraone(
|
|
1048
|
-
if (!
|
|
1049
|
-
const mcpServers =
|
|
1957
|
+
function cursorConfigHasManagedMemoraone(parsed2) {
|
|
1958
|
+
if (!parsed2 || typeof parsed2 !== "object") return false;
|
|
1959
|
+
const mcpServers = parsed2.mcpServers;
|
|
1050
1960
|
if (!mcpServers || typeof mcpServers !== "object" || Array.isArray(mcpServers)) return false;
|
|
1051
1961
|
return isManagedMemoraoneCursorServer(mcpServers.memoraone);
|
|
1052
1962
|
}
|
|
1053
|
-
function memoraoneEnvMatchesManagedCleanupShape(
|
|
1054
|
-
return
|
|
1963
|
+
function memoraoneEnvMatchesManagedCleanupShape(env2) {
|
|
1964
|
+
return env2.MEMORAONE_IDE_TYPE === "cursor" && isMemoraoneManagedApiUrl(env2.MEMORAONE_API_URL);
|
|
1055
1965
|
}
|
|
1056
1966
|
function getCursorRepoMcpConfigPath(repoRoot) {
|
|
1057
|
-
return
|
|
1967
|
+
return path12.join(repoRoot, ".cursor", "mcp.json");
|
|
1058
1968
|
}
|
|
1059
1969
|
async function readCursorMcpConfigObject(configPath) {
|
|
1060
1970
|
try {
|
|
1061
|
-
const raw = await
|
|
1971
|
+
const raw = await fs9.readFile(configPath, "utf8");
|
|
1062
1972
|
return JSON.parse(stripLeadingLineComments(raw));
|
|
1063
1973
|
} catch (err) {
|
|
1064
1974
|
const code = err && typeof err === "object" && "code" in err ? err.code : void 0;
|
|
@@ -1068,36 +1978,36 @@ async function readCursorMcpConfigObject(configPath) {
|
|
|
1068
1978
|
}
|
|
1069
1979
|
async function removeMemoraoneFromCursorGlobalConfig(options) {
|
|
1070
1980
|
const { configPath, dryRun } = options;
|
|
1071
|
-
const
|
|
1072
|
-
if (!
|
|
1981
|
+
const parsed2 = await readCursorMcpConfigObject(configPath);
|
|
1982
|
+
if (!parsed2 || !cursorConfigHasManagedMemoraone(parsed2)) {
|
|
1073
1983
|
return { changed: false };
|
|
1074
1984
|
}
|
|
1075
1985
|
if (dryRun) {
|
|
1076
1986
|
return { changed: true, backupPath: `${configPath}.backup-<timestamp>` };
|
|
1077
1987
|
}
|
|
1078
1988
|
const backupPath = `${configPath}.backup-${formatBackupTimestamp()}`;
|
|
1079
|
-
await
|
|
1080
|
-
const mcpServers = typeof
|
|
1989
|
+
await fs9.copyFile(configPath, backupPath);
|
|
1990
|
+
const mcpServers = typeof parsed2.mcpServers === "object" && parsed2.mcpServers !== null && !Array.isArray(parsed2.mcpServers) ? { ...parsed2.mcpServers } : {};
|
|
1081
1991
|
delete mcpServers.memoraone;
|
|
1082
1992
|
const hasOtherServers = Object.keys(mcpServers).length > 0;
|
|
1083
1993
|
if (!hasOtherServers) {
|
|
1084
|
-
await
|
|
1994
|
+
await fs9.unlink(configPath);
|
|
1085
1995
|
return { changed: true, backupPath };
|
|
1086
1996
|
}
|
|
1087
|
-
const next = { ...
|
|
1088
|
-
await
|
|
1089
|
-
await
|
|
1997
|
+
const next = { ...parsed2, mcpServers };
|
|
1998
|
+
await fs9.mkdir(path12.dirname(configPath), { recursive: true });
|
|
1999
|
+
await fs9.writeFile(configPath, JSON.stringify(next, null, 2) + "\n", "utf8");
|
|
1090
2000
|
return { changed: true, backupPath };
|
|
1091
2001
|
}
|
|
1092
2002
|
async function auditCursorMcpConfig(options) {
|
|
1093
|
-
const repoRoot =
|
|
2003
|
+
const repoRoot = path12.resolve(options?.repoRoot ?? process.cwd());
|
|
1094
2004
|
const repoConfigPath = getCursorRepoMcpConfigPath(repoRoot);
|
|
1095
2005
|
const globalDetection = await detectCursorGlobalMcpConfig({
|
|
1096
2006
|
homeDir: options?.homeDir,
|
|
1097
2007
|
explicitPath: options?.explicitGlobalPath
|
|
1098
2008
|
});
|
|
1099
2009
|
const globalConfigPath = globalDetection.ok ? globalDetection.path : getKnownCursorGlobalMcpConfigCandidates(
|
|
1100
|
-
options?.homeDir ??
|
|
2010
|
+
options?.homeDir ?? os4.homedir()
|
|
1101
2011
|
)[0];
|
|
1102
2012
|
let repoHasManagedMemoraone = false;
|
|
1103
2013
|
try {
|
|
@@ -1193,11 +2103,11 @@ function logCursorMcpCliSummary(info, dryRun, opts) {
|
|
|
1193
2103
|
}
|
|
1194
2104
|
|
|
1195
2105
|
// src/cleanup.ts
|
|
1196
|
-
var
|
|
2106
|
+
var execFileAsync3 = (0, import_node_util3.promisify)(import_node_child_process3.execFile);
|
|
1197
2107
|
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
2108
|
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
2109
|
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
|
|
2110
|
+
var CLEANUP_PROJECT_ID_REQUIRED_ERROR = "Provide --project-id <id> or run from a connected workspace (memoraone-mcp connect).";
|
|
1201
2111
|
function isMemoraoneMcpCommandLine(commandLine) {
|
|
1202
2112
|
return MEMORAONE_MCP_COMMAND_RE.test(commandLine);
|
|
1203
2113
|
}
|
|
@@ -1258,7 +2168,7 @@ function normalizeCleanupProjectId(projectId) {
|
|
|
1258
2168
|
return trimmed.toLowerCase();
|
|
1259
2169
|
}
|
|
1260
2170
|
async function defaultListDaemonProcesses() {
|
|
1261
|
-
const { stdout } = await
|
|
2171
|
+
const { stdout } = await execFileAsync3("ps", ["-eo", "pid=,args="], {
|
|
1262
2172
|
maxBuffer: 10 * 1024 * 1024
|
|
1263
2173
|
});
|
|
1264
2174
|
return parseDaemonProcessLines(stdout.split("\n"));
|
|
@@ -1267,7 +2177,7 @@ async function defaultListSocketPaths(projectId) {
|
|
|
1267
2177
|
const baseDir = getMcpBaseDir();
|
|
1268
2178
|
let entries;
|
|
1269
2179
|
try {
|
|
1270
|
-
entries = await
|
|
2180
|
+
entries = await fs10.readdir(baseDir);
|
|
1271
2181
|
} catch (err) {
|
|
1272
2182
|
const code = err && typeof err === "object" && "code" in err ? err.code : void 0;
|
|
1273
2183
|
if (code === "ENOENT") {
|
|
@@ -1280,7 +2190,7 @@ async function defaultListSocketPaths(projectId) {
|
|
|
1280
2190
|
if (!name.endsWith(".sock") || !isMemoraoneSocketFilename(name)) {
|
|
1281
2191
|
continue;
|
|
1282
2192
|
}
|
|
1283
|
-
const socketPath =
|
|
2193
|
+
const socketPath = path13.join(baseDir, name);
|
|
1284
2194
|
if (projectId === null) {
|
|
1285
2195
|
paths.push(socketPath);
|
|
1286
2196
|
continue;
|
|
@@ -1299,12 +2209,12 @@ async function defaultListSocketPaths(projectId) {
|
|
|
1299
2209
|
}
|
|
1300
2210
|
return paths.sort();
|
|
1301
2211
|
}
|
|
1302
|
-
async function
|
|
1303
|
-
const
|
|
2212
|
+
async function defaultListSocketPathsForWorkspaceRoot(workspaceRoot) {
|
|
2213
|
+
const resolvedRoot = path13.resolve(workspaceRoot);
|
|
1304
2214
|
const baseDir = getMcpBaseDir();
|
|
1305
2215
|
let entries;
|
|
1306
2216
|
try {
|
|
1307
|
-
entries = await
|
|
2217
|
+
entries = await fs10.readdir(baseDir);
|
|
1308
2218
|
} catch (err) {
|
|
1309
2219
|
const code = err && typeof err === "object" && "code" in err ? err.code : void 0;
|
|
1310
2220
|
if (code === "ENOENT") {
|
|
@@ -1317,34 +2227,34 @@ async function defaultListSocketPathsForM1Path(m1Path) {
|
|
|
1317
2227
|
if (!name.endsWith(".sock") || !isHashSocketFilename(name)) {
|
|
1318
2228
|
continue;
|
|
1319
2229
|
}
|
|
1320
|
-
const socketPath =
|
|
2230
|
+
const socketPath = path13.join(baseDir, name);
|
|
1321
2231
|
const record = readBindingSidecarRecord(socketPath);
|
|
1322
|
-
if (!record?.
|
|
1323
|
-
if (
|
|
2232
|
+
if (!record?.workspaceRoot) continue;
|
|
2233
|
+
if (path13.resolve(record.workspaceRoot) === resolvedRoot) {
|
|
1324
2234
|
paths.push(socketPath);
|
|
1325
2235
|
}
|
|
1326
2236
|
}
|
|
1327
2237
|
return paths.sort();
|
|
1328
2238
|
}
|
|
1329
|
-
async function filterSocketPathsByIdeForCleanup(socketPaths, projectId, ide,
|
|
2239
|
+
async function filterSocketPathsByIdeForCleanup(socketPaths, projectId, ide, workspaceRoot) {
|
|
1330
2240
|
if (ide === void 0) return socketPaths;
|
|
1331
2241
|
const normalizedProjectId = projectId.trim().toLowerCase();
|
|
1332
|
-
const
|
|
2242
|
+
const resolvedRoot = workspaceRoot ? path13.resolve(workspaceRoot) : null;
|
|
1333
2243
|
const filtered = [];
|
|
1334
2244
|
for (const socketPath of socketPaths) {
|
|
1335
|
-
const
|
|
1336
|
-
if (isLegacySocketFilename(
|
|
1337
|
-
if (isSocketFilenameForProjectAndIde(
|
|
2245
|
+
const basename8 = path13.basename(socketPath);
|
|
2246
|
+
if (isLegacySocketFilename(basename8)) {
|
|
2247
|
+
if (isSocketFilenameForProjectAndIde(basename8, normalizedProjectId, ide)) {
|
|
1338
2248
|
filtered.push(socketPath);
|
|
1339
2249
|
}
|
|
1340
2250
|
continue;
|
|
1341
2251
|
}
|
|
1342
|
-
if (isHashSocketFilename(
|
|
2252
|
+
if (isHashSocketFilename(basename8)) {
|
|
1343
2253
|
const record = readBindingSidecarRecord(socketPath);
|
|
1344
2254
|
if (!record || record.ideType !== ide) continue;
|
|
1345
2255
|
const sameProject = record.projectId.trim().toLowerCase() === normalizedProjectId;
|
|
1346
|
-
const
|
|
1347
|
-
if (sameProject ||
|
|
2256
|
+
const sameWorkspace = resolvedRoot !== null && path13.resolve(record.workspaceRoot) === resolvedRoot;
|
|
2257
|
+
if (sameProject || sameWorkspace) {
|
|
1348
2258
|
filtered.push(socketPath);
|
|
1349
2259
|
}
|
|
1350
2260
|
}
|
|
@@ -1355,9 +2265,9 @@ async function defaultKillProcess(pid) {
|
|
|
1355
2265
|
process.kill(pid, "SIGTERM");
|
|
1356
2266
|
}
|
|
1357
2267
|
async function defaultRemoveSocket(socketPath) {
|
|
1358
|
-
await
|
|
2268
|
+
await fs10.unlink(socketPath);
|
|
1359
2269
|
try {
|
|
1360
|
-
await
|
|
2270
|
+
await fs10.unlink(bindingSidecarPath(socketPath));
|
|
1361
2271
|
} catch {
|
|
1362
2272
|
}
|
|
1363
2273
|
}
|
|
@@ -1373,12 +2283,12 @@ async function defaultConfirm(message) {
|
|
|
1373
2283
|
rl.close();
|
|
1374
2284
|
}
|
|
1375
2285
|
}
|
|
1376
|
-
async function resolveCleanupTarget(
|
|
2286
|
+
async function resolveCleanupTarget(cwd2) {
|
|
1377
2287
|
try {
|
|
1378
|
-
const binding = await resolveAuthoritativeBinding([
|
|
2288
|
+
const binding = await resolveAuthoritativeBinding([path13.resolve(cwd2)]);
|
|
1379
2289
|
return {
|
|
1380
2290
|
workspaceRoot: binding.workspaceRoot,
|
|
1381
|
-
|
|
2291
|
+
repositoryBindingId: binding.repositoryBindingId,
|
|
1382
2292
|
projectId: binding.projectId
|
|
1383
2293
|
};
|
|
1384
2294
|
} catch {
|
|
@@ -1435,7 +2345,7 @@ async function runCleanup(opts) {
|
|
|
1435
2345
|
const prefix = logPrefix(opts.dryRun);
|
|
1436
2346
|
let targetProjectId = null;
|
|
1437
2347
|
let workspaceRoot;
|
|
1438
|
-
let
|
|
2348
|
+
let repositoryBindingId;
|
|
1439
2349
|
if (opts.allProjects) {
|
|
1440
2350
|
if (opts.projectId) {
|
|
1441
2351
|
return {
|
|
@@ -1468,9 +2378,9 @@ async function runCleanup(opts) {
|
|
|
1468
2378
|
}
|
|
1469
2379
|
targetProjectId = target.projectId;
|
|
1470
2380
|
workspaceRoot = target.workspaceRoot;
|
|
1471
|
-
|
|
2381
|
+
repositoryBindingId = target.repositoryBindingId;
|
|
1472
2382
|
cleanupLog(opts, `${prefix} Workspace root: ${workspaceRoot}`);
|
|
1473
|
-
cleanupLog(opts, `${prefix}
|
|
2383
|
+
cleanupLog(opts, `${prefix} Repository binding: ${repositoryBindingId}`);
|
|
1474
2384
|
cleanupLog(opts, `${prefix} Project id: ${targetProjectId}`);
|
|
1475
2385
|
if (opts.ide) {
|
|
1476
2386
|
cleanupLog(opts, `${prefix} IDE filter: ${opts.ide}`);
|
|
@@ -1515,10 +2425,10 @@ async function runCleanup(opts) {
|
|
|
1515
2425
|
}
|
|
1516
2426
|
}
|
|
1517
2427
|
let allSocketPaths = await listSocketPaths(targetProjectId);
|
|
1518
|
-
if (
|
|
1519
|
-
const
|
|
2428
|
+
if (workspaceRoot && !opts.allProjects) {
|
|
2429
|
+
const workspaceSockets = await defaultListSocketPathsForWorkspaceRoot(workspaceRoot);
|
|
1520
2430
|
const seen = new Set(allSocketPaths);
|
|
1521
|
-
for (const socketPath of
|
|
2431
|
+
for (const socketPath of workspaceSockets) {
|
|
1522
2432
|
if (!seen.has(socketPath)) {
|
|
1523
2433
|
seen.add(socketPath);
|
|
1524
2434
|
allSocketPaths.push(socketPath);
|
|
@@ -1533,7 +2443,7 @@ async function runCleanup(opts) {
|
|
|
1533
2443
|
processesToStop.push(proc);
|
|
1534
2444
|
cleanupLog(
|
|
1535
2445
|
opts,
|
|
1536
|
-
`${prefix} Including stale daemon pid=${proc.pid} project=${staleProjectId} (sidecar
|
|
2446
|
+
`${prefix} Including stale daemon pid=${proc.pid} project=${staleProjectId} (sidecar workspace=${workspaceRoot})`
|
|
1537
2447
|
);
|
|
1538
2448
|
}
|
|
1539
2449
|
}
|
|
@@ -1541,14 +2451,19 @@ async function runCleanup(opts) {
|
|
|
1541
2451
|
}
|
|
1542
2452
|
allSocketPaths = [...seen].sort();
|
|
1543
2453
|
}
|
|
1544
|
-
const socketPaths = targetProjectId === null ? allSocketPaths : await filterSocketPathsByIdeForCleanup(
|
|
2454
|
+
const socketPaths = targetProjectId === null ? allSocketPaths : await filterSocketPathsByIdeForCleanup(
|
|
2455
|
+
allSocketPaths,
|
|
2456
|
+
targetProjectId,
|
|
2457
|
+
opts.ide,
|
|
2458
|
+
workspaceRoot
|
|
2459
|
+
);
|
|
1545
2460
|
if (opts.allProjects) {
|
|
1546
2461
|
const projectIds = /* @__PURE__ */ new Set();
|
|
1547
2462
|
for (const proc of processesToStop) {
|
|
1548
2463
|
projectIds.add(proc.projectId);
|
|
1549
2464
|
}
|
|
1550
2465
|
for (const socketPath of socketPaths) {
|
|
1551
|
-
const id = extractProjectIdFromSocketFilename(
|
|
2466
|
+
const id = extractProjectIdFromSocketFilename(path13.basename(socketPath));
|
|
1552
2467
|
if (id) {
|
|
1553
2468
|
projectIds.add(id);
|
|
1554
2469
|
continue;
|
|
@@ -1595,7 +2510,7 @@ async function runCleanup(opts) {
|
|
|
1595
2510
|
return {
|
|
1596
2511
|
exitCode: 1,
|
|
1597
2512
|
workspaceRoot,
|
|
1598
|
-
|
|
2513
|
+
repositoryBindingId,
|
|
1599
2514
|
projectId: targetProjectId ?? void 0,
|
|
1600
2515
|
killedPids: [],
|
|
1601
2516
|
removedSockets: [],
|
|
@@ -1611,7 +2526,7 @@ async function runCleanup(opts) {
|
|
|
1611
2526
|
return {
|
|
1612
2527
|
exitCode: 0,
|
|
1613
2528
|
workspaceRoot,
|
|
1614
|
-
|
|
2529
|
+
repositoryBindingId,
|
|
1615
2530
|
projectId: targetProjectId ?? void 0,
|
|
1616
2531
|
killedPids: processesToStop.map((p) => p.pid),
|
|
1617
2532
|
removedSockets: socketPaths,
|
|
@@ -1646,7 +2561,7 @@ async function runCleanup(opts) {
|
|
|
1646
2561
|
return {
|
|
1647
2562
|
exitCode: 0,
|
|
1648
2563
|
workspaceRoot,
|
|
1649
|
-
|
|
2564
|
+
repositoryBindingId,
|
|
1650
2565
|
projectId: targetProjectId ?? void 0,
|
|
1651
2566
|
killedPids,
|
|
1652
2567
|
removedSockets,
|
|
@@ -1734,9 +2649,9 @@ function summarizeJsonRpcMethod(line) {
|
|
|
1734
2649
|
}
|
|
1735
2650
|
}
|
|
1736
2651
|
function connectWithRetry(socketPath, log, maxRetries, retryDelayMs, connect2) {
|
|
1737
|
-
return new Promise((
|
|
2652
|
+
return new Promise((resolve16, reject) => {
|
|
1738
2653
|
const tryConnect = (attempt) => {
|
|
1739
|
-
connect2(socketPath).then(
|
|
2654
|
+
connect2(socketPath).then(resolve16).catch((err) => {
|
|
1740
2655
|
if (attempt >= maxRetries) {
|
|
1741
2656
|
reject(err);
|
|
1742
2657
|
return;
|
|
@@ -1748,18 +2663,18 @@ function connectWithRetry(socketPath, log, maxRetries, retryDelayMs, connect2) {
|
|
|
1748
2663
|
tryConnect(0);
|
|
1749
2664
|
});
|
|
1750
2665
|
}
|
|
1751
|
-
async function resolveBridgeSessionBinding(params,
|
|
1752
|
-
const bridgeOptions = getBridgeBindingResolveOptions(
|
|
2666
|
+
async function resolveBridgeSessionBinding(params, env2 = process.env, options = {}) {
|
|
2667
|
+
const bridgeOptions = getBridgeBindingResolveOptions(env2);
|
|
1753
2668
|
return resolveBindingFromInitializeParams(params, {
|
|
1754
|
-
env,
|
|
2669
|
+
env: env2,
|
|
1755
2670
|
fallbackWorkspaceRoots: getEnvWorkspaceRootCandidates(),
|
|
1756
2671
|
rootsListUris: options.rootsListUris,
|
|
1757
2672
|
rootsListAttempted: options.rootsListAttempted,
|
|
1758
2673
|
...bridgeOptions
|
|
1759
2674
|
});
|
|
1760
2675
|
}
|
|
1761
|
-
async function stopDaemonsForStaleBinding(stale,
|
|
1762
|
-
const ideType = resolveBindingIdeType(
|
|
2676
|
+
async function stopDaemonsForStaleBinding(stale, env2, log) {
|
|
2677
|
+
const ideType = resolveBindingIdeType(env2);
|
|
1763
2678
|
let processes;
|
|
1764
2679
|
try {
|
|
1765
2680
|
processes = await defaultListDaemonProcesses();
|
|
@@ -1783,7 +2698,7 @@ async function connectOrSpawnDaemonForBinding(binding, opts) {
|
|
|
1783
2698
|
const sessionBinding = reconciled.binding;
|
|
1784
2699
|
if (reconciled.cacheRefreshed) {
|
|
1785
2700
|
opts.log(
|
|
1786
|
-
`refreshed stale binding
|
|
2701
|
+
`refreshed stale binding ${sessionBinding.repositoryBindingId}: project=${sessionBinding.projectId}`
|
|
1787
2702
|
);
|
|
1788
2703
|
}
|
|
1789
2704
|
const socketPath = getBindingSocketPath(sessionBinding, opts.env);
|
|
@@ -1811,7 +2726,7 @@ async function connectOrSpawnDaemonForBinding(binding, opts) {
|
|
|
1811
2726
|
socket = void 0;
|
|
1812
2727
|
}
|
|
1813
2728
|
if (isDaemonBindingMismatchError(err)) {
|
|
1814
|
-
opts.log("stale daemon binding detected; replacing daemon for current
|
|
2729
|
+
opts.log("stale daemon binding detected; replacing daemon for current local binding");
|
|
1815
2730
|
const staleSidecar = readBindingSidecar(socketPath);
|
|
1816
2731
|
if (staleSidecar) {
|
|
1817
2732
|
await stopDaemonsForStaleBinding(staleSidecar, opts.env, opts.log);
|
|
@@ -1852,19 +2767,20 @@ var BridgeDaemonRouter = class {
|
|
|
1852
2767
|
this.maxRetries = options.maxRetries ?? 5;
|
|
1853
2768
|
this.retryDelayMs = options.retryDelayMs ?? 200;
|
|
1854
2769
|
this.lineReader = options.lineReader ?? null;
|
|
1855
|
-
this.connectImpl = options.connect ?? ((socketPath) => new Promise((
|
|
1856
|
-
const socket = net.connect(socketPath, () =>
|
|
2770
|
+
this.connectImpl = options.connect ?? ((socketPath) => new Promise((resolve16, reject) => {
|
|
2771
|
+
const socket = net.connect(socketPath, () => resolve16(socket));
|
|
1857
2772
|
socket.on("error", reject);
|
|
1858
2773
|
}));
|
|
1859
2774
|
this.spawnDaemonImpl = options.spawnDaemon ?? (async (binding) => {
|
|
1860
|
-
const child = (0,
|
|
2775
|
+
const child = (0, import_node_child_process4.spawn)(
|
|
1861
2776
|
process.execPath,
|
|
1862
|
-
buildDaemonSpawnArgs(this.cliPath, binding.
|
|
2777
|
+
buildDaemonSpawnArgs(this.cliPath, binding.repositoryBindingId, this.env),
|
|
1863
2778
|
{
|
|
1864
2779
|
detached: true,
|
|
1865
2780
|
stdio: "ignore",
|
|
1866
2781
|
env: {
|
|
1867
2782
|
...this.env,
|
|
2783
|
+
// Secret-free handoff only; tokens stay in the OS keyring.
|
|
1868
2784
|
MEMORAONE_DAEMON_BINDING_B64: encodeResolvedBinding(binding)
|
|
1869
2785
|
}
|
|
1870
2786
|
}
|
|
@@ -1927,7 +2843,7 @@ var BridgeDaemonRouter = class {
|
|
|
1927
2843
|
});
|
|
1928
2844
|
const environmentLog = binding.environment !== void 0 ? ` environment=${binding.environment}` : "";
|
|
1929
2845
|
this.log(
|
|
1930
|
-
`session binding
|
|
2846
|
+
`session binding binding=${binding.repositoryBindingId} project=${binding.projectId} workspace=${binding.workspaceRoot} source=${binding.bindingSource}${environmentLog}`
|
|
1931
2847
|
);
|
|
1932
2848
|
if (this.activeBinding && bindingsMatch(this.activeBinding, binding) && this.activeSocket) {
|
|
1933
2849
|
return;
|
|
@@ -2062,6 +2978,17 @@ var BridgeDaemonRouter = class {
|
|
|
2062
2978
|
this.socketLineReader = null;
|
|
2063
2979
|
}
|
|
2064
2980
|
}
|
|
2981
|
+
/** Tear down the daemon socket when the IDE stdio transport ends. */
|
|
2982
|
+
close() {
|
|
2983
|
+
this.detachSocketReader();
|
|
2984
|
+
if (this.activeSocket) {
|
|
2985
|
+
try {
|
|
2986
|
+
this.activeSocket.destroy();
|
|
2987
|
+
} catch {
|
|
2988
|
+
}
|
|
2989
|
+
this.activeSocket = null;
|
|
2990
|
+
}
|
|
2991
|
+
}
|
|
2065
2992
|
};
|
|
2066
2993
|
async function runBridgeProxy(options) {
|
|
2067
2994
|
ensureBaseDir();
|
|
@@ -2070,46 +2997,66 @@ async function runBridgeProxy(options) {
|
|
|
2070
2997
|
const log = options.log ?? defaultLog;
|
|
2071
2998
|
const lineReader = options.lineReader ?? new StdioLineReader(stdin);
|
|
2072
2999
|
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
|
-
|
|
3000
|
+
try {
|
|
3001
|
+
while (true) {
|
|
3002
|
+
const line = await lineReader.readLine();
|
|
3003
|
+
if (line === null) {
|
|
3004
|
+
log("stdin EOF; closing daemon socket");
|
|
3005
|
+
break;
|
|
3006
|
+
}
|
|
3007
|
+
const trimmed = line.trim();
|
|
3008
|
+
if (trimmed === "") {
|
|
3009
|
+
continue;
|
|
3010
|
+
}
|
|
3011
|
+
let message;
|
|
3012
|
+
try {
|
|
3013
|
+
message = JSON.parse(trimmed);
|
|
3014
|
+
} catch (err) {
|
|
3015
|
+
throw new Error(`[memoraone-mcp] Invalid JSON-RPC on stdin: ${String(err)}`);
|
|
3016
|
+
}
|
|
3017
|
+
if (message.method === "initialize") {
|
|
3018
|
+
log("resolve binding from initialize request before daemon connect");
|
|
3019
|
+
const params = message.params ?? {};
|
|
3020
|
+
await router.ensureDaemonForInitialize(params);
|
|
3021
|
+
await router.forwardInitializeToDaemon(trimmed);
|
|
3022
|
+
await router.replayDeferredClientMessages();
|
|
3023
|
+
continue;
|
|
3024
|
+
}
|
|
3025
|
+
await router.writeToDaemon(trimmed);
|
|
2095
3026
|
}
|
|
2096
|
-
|
|
3027
|
+
} finally {
|
|
3028
|
+
router.close();
|
|
2097
3029
|
}
|
|
2098
3030
|
}
|
|
2099
3031
|
|
|
2100
3032
|
// src/setupIdeFiles.ts
|
|
2101
|
-
var
|
|
2102
|
-
var
|
|
2103
|
-
var
|
|
3033
|
+
var fs13 = __toESM(require("fs/promises"), 1);
|
|
3034
|
+
var os6 = __toESM(require("os"), 1);
|
|
3035
|
+
var path16 = __toESM(require("path"), 1);
|
|
2104
3036
|
|
|
2105
3037
|
// src/jetbrainsMcpConfig.ts
|
|
2106
|
-
var
|
|
2107
|
-
var
|
|
2108
|
-
var
|
|
2109
|
-
var
|
|
3038
|
+
var fs11 = __toESM(require("fs/promises"), 1);
|
|
3039
|
+
var os5 = __toESM(require("os"), 1);
|
|
3040
|
+
var path14 = __toESM(require("path"), 1);
|
|
3041
|
+
var import_node_child_process5 = require("child_process");
|
|
2110
3042
|
|
|
2111
3043
|
// src/configUtils.ts
|
|
3044
|
+
var DEFAULT_API_URL = "http://localhost:3001";
|
|
2112
3045
|
var DEV_API_URL = "http://localhost:3001";
|
|
3046
|
+
function resolveApiUrl(env2) {
|
|
3047
|
+
const explicitUrl = env2.MEMORAONE_API_URL?.trim();
|
|
3048
|
+
if (explicitUrl) {
|
|
3049
|
+
return explicitUrl;
|
|
3050
|
+
}
|
|
3051
|
+
const aliasUrl = env2.MEMORA_API_URL?.trim();
|
|
3052
|
+
if (aliasUrl) {
|
|
3053
|
+
return aliasUrl;
|
|
3054
|
+
}
|
|
3055
|
+
if (env2.MEMORAONE_DEV_MODE === "1") {
|
|
3056
|
+
return DEV_API_URL;
|
|
3057
|
+
}
|
|
3058
|
+
return DEFAULT_API_URL;
|
|
3059
|
+
}
|
|
2113
3060
|
|
|
2114
3061
|
// src/jetbrainsMcpConfig.ts
|
|
2115
3062
|
var PROD_API_URL = "https://api.memoraone.com";
|
|
@@ -2124,7 +3071,7 @@ function stripLeadingLineComments2(text) {
|
|
|
2124
3071
|
}
|
|
2125
3072
|
async function pathExists2(filePath) {
|
|
2126
3073
|
try {
|
|
2127
|
-
await
|
|
3074
|
+
await fs11.access(filePath);
|
|
2128
3075
|
return true;
|
|
2129
3076
|
} catch {
|
|
2130
3077
|
return false;
|
|
@@ -2135,12 +3082,12 @@ function formatJetBrainsBackupTimestamp(d = /* @__PURE__ */ new Date()) {
|
|
|
2135
3082
|
return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
|
|
2136
3083
|
}
|
|
2137
3084
|
function getJetBrainsGlobalMcpConfigPath(homeDir) {
|
|
2138
|
-
return
|
|
3085
|
+
return path14.join(homeDir, ".ai", "mcp", "mcp.json");
|
|
2139
3086
|
}
|
|
2140
3087
|
function getJetBrainsProjectMcpConfigPaths(repoRoot) {
|
|
2141
3088
|
return [
|
|
2142
|
-
{ kind: "project-ai", path:
|
|
2143
|
-
{ kind: "project-ij", path:
|
|
3089
|
+
{ kind: "project-ai", path: path14.join(repoRoot, ".ai", "mcp", "mcp.json") },
|
|
3090
|
+
{ kind: "project-ij", path: path14.join(repoRoot, ".ij", "mcp", "mcp.json") }
|
|
2144
3091
|
];
|
|
2145
3092
|
}
|
|
2146
3093
|
function getKnownJetBrainsMcpConfigLocations(homeDir, repoRoot) {
|
|
@@ -2151,22 +3098,25 @@ function getKnownJetBrainsMcpConfigLocations(homeDir, repoRoot) {
|
|
|
2151
3098
|
}
|
|
2152
3099
|
async function isZeroByteConfigFile(filePath) {
|
|
2153
3100
|
if (!await pathExists2(filePath)) return false;
|
|
2154
|
-
const
|
|
2155
|
-
return
|
|
3101
|
+
const stat4 = await fs11.stat(filePath);
|
|
3102
|
+
return stat4.size === 0;
|
|
2156
3103
|
}
|
|
2157
3104
|
function buildMemoraoneJetBrainsMcpServer(options) {
|
|
2158
|
-
const
|
|
2159
|
-
MEMORAONE_API_URL: options.devMode ? DEV_API_URL : PROD_API_URL,
|
|
3105
|
+
const env2 = {
|
|
3106
|
+
MEMORAONE_API_URL: options.devMode ? options.apiUrl ? normalizeApiUrl(options.apiUrl) : DEV_API_URL : PROD_API_URL,
|
|
2160
3107
|
MEMORAONE_IDE_TYPE: "jetbrains",
|
|
2161
|
-
|
|
3108
|
+
[MEMORAONE_WORKSPACE_ROOT_ENV]: path14.resolve(options.workspaceRoot)
|
|
2162
3109
|
};
|
|
2163
3110
|
if (options.devMode) {
|
|
2164
|
-
|
|
3111
|
+
env2.MEMORAONE_DEV_MODE = "1";
|
|
3112
|
+
}
|
|
3113
|
+
if ("MEMORAONE_M1_PATH" in env2 || "MEMORAONE_API_KEY" in env2) {
|
|
3114
|
+
throw new Error("[setup-ide-files] JetBrains MCP config must not include credentials or .m1 paths");
|
|
2165
3115
|
}
|
|
2166
3116
|
return {
|
|
2167
3117
|
command: options.command,
|
|
2168
3118
|
args: options.args,
|
|
2169
|
-
env
|
|
3119
|
+
env: env2
|
|
2170
3120
|
};
|
|
2171
3121
|
}
|
|
2172
3122
|
function mergeJetBrainsMcpConfigObject(existing, memoraone) {
|
|
@@ -2183,9 +3133,9 @@ function memoraoneServerMatches(server, expected) {
|
|
|
2183
3133
|
for (let i = 0; i < expected.args.length; i += 1) {
|
|
2184
3134
|
if (s.args[i] !== expected.args[i]) return false;
|
|
2185
3135
|
}
|
|
2186
|
-
const
|
|
2187
|
-
if (!
|
|
2188
|
-
const e =
|
|
3136
|
+
const env2 = s.env;
|
|
3137
|
+
if (!env2 || typeof env2 !== "object") return false;
|
|
3138
|
+
const e = env2;
|
|
2189
3139
|
for (const [key, value] of Object.entries(expected.env)) {
|
|
2190
3140
|
if (e[key] !== value) return false;
|
|
2191
3141
|
}
|
|
@@ -2194,11 +3144,11 @@ function memoraoneServerMatches(server, expected) {
|
|
|
2194
3144
|
}
|
|
2195
3145
|
return true;
|
|
2196
3146
|
}
|
|
2197
|
-
function validateJetBrainsMcpConfig(
|
|
2198
|
-
if (!
|
|
3147
|
+
function validateJetBrainsMcpConfig(parsed2, expected) {
|
|
3148
|
+
if (!parsed2 || typeof parsed2 !== "object") {
|
|
2199
3149
|
throw new Error("[setup-ide-files] JetBrains MCP config must be a JSON object.");
|
|
2200
3150
|
}
|
|
2201
|
-
const mcpServers =
|
|
3151
|
+
const mcpServers = parsed2.mcpServers;
|
|
2202
3152
|
if (!mcpServers || typeof mcpServers !== "object" || Array.isArray(mcpServers)) {
|
|
2203
3153
|
throw new Error("[setup-ide-files] JetBrains MCP config missing mcpServers object.");
|
|
2204
3154
|
}
|
|
@@ -2210,13 +3160,13 @@ function validateJetBrainsMcpConfig(parsed, expected) {
|
|
|
2210
3160
|
}
|
|
2211
3161
|
}
|
|
2212
3162
|
async function readJsonConfig(filePath) {
|
|
2213
|
-
const raw = await
|
|
3163
|
+
const raw = await fs11.readFile(filePath, "utf8");
|
|
2214
3164
|
if (raw.trim() === "") return null;
|
|
2215
3165
|
return JSON.parse(stripLeadingLineComments2(raw));
|
|
2216
3166
|
}
|
|
2217
3167
|
async function backupConfigFile(filePath) {
|
|
2218
3168
|
const backupPath = `${filePath}.bak-${formatJetBrainsBackupTimestamp()}`;
|
|
2219
|
-
await
|
|
3169
|
+
await fs11.copyFile(filePath, backupPath);
|
|
2220
3170
|
return backupPath;
|
|
2221
3171
|
}
|
|
2222
3172
|
async function repairZeroByteConfigFile(filePath, dryRun) {
|
|
@@ -2227,12 +3177,12 @@ async function repairZeroByteConfigFile(filePath, dryRun) {
|
|
|
2227
3177
|
return { repaired: true, backupPath: `${filePath}.bak-<timestamp>` };
|
|
2228
3178
|
}
|
|
2229
3179
|
const backupPath = await backupConfigFile(filePath);
|
|
2230
|
-
await
|
|
3180
|
+
await fs11.unlink(filePath);
|
|
2231
3181
|
return { repaired: true, backupPath };
|
|
2232
3182
|
}
|
|
2233
|
-
function configHasMemoraone(
|
|
2234
|
-
if (!
|
|
2235
|
-
const mcpServers =
|
|
3183
|
+
function configHasMemoraone(parsed2) {
|
|
3184
|
+
if (!parsed2) return false;
|
|
3185
|
+
const mcpServers = parsed2.mcpServers;
|
|
2236
3186
|
if (!mcpServers || typeof mcpServers !== "object" || Array.isArray(mcpServers)) return false;
|
|
2237
3187
|
return Boolean(mcpServers.memoraone);
|
|
2238
3188
|
}
|
|
@@ -2241,41 +3191,41 @@ async function removeMemoraoneFromProjectConfig(options) {
|
|
|
2241
3191
|
if (!await pathExists2(configPath)) {
|
|
2242
3192
|
return { changed: false };
|
|
2243
3193
|
}
|
|
2244
|
-
let
|
|
3194
|
+
let parsed2 = null;
|
|
2245
3195
|
try {
|
|
2246
|
-
|
|
3196
|
+
parsed2 = await readJsonConfig(configPath);
|
|
2247
3197
|
} catch {
|
|
2248
3198
|
return { changed: false };
|
|
2249
3199
|
}
|
|
2250
|
-
if (!configHasMemoraone(
|
|
3200
|
+
if (!configHasMemoraone(parsed2)) {
|
|
2251
3201
|
return { changed: false };
|
|
2252
3202
|
}
|
|
2253
3203
|
if (dryRun) {
|
|
2254
3204
|
return { changed: true, backupPath: `${configPath}.bak-<timestamp>` };
|
|
2255
3205
|
}
|
|
2256
3206
|
const backupPath = await backupConfigFile(configPath);
|
|
2257
|
-
const mcpServers =
|
|
3207
|
+
const mcpServers = parsed2 && typeof parsed2.mcpServers === "object" && parsed2.mcpServers !== null && !Array.isArray(parsed2.mcpServers) ? { ...parsed2.mcpServers } : {};
|
|
2258
3208
|
delete mcpServers.memoraone;
|
|
2259
3209
|
const hasOtherServers = Object.keys(mcpServers).length > 0;
|
|
2260
3210
|
if (!hasOtherServers) {
|
|
2261
|
-
await
|
|
3211
|
+
await fs11.unlink(configPath);
|
|
2262
3212
|
return { changed: true, backupPath };
|
|
2263
3213
|
}
|
|
2264
|
-
const next = { ...
|
|
2265
|
-
await
|
|
2266
|
-
await
|
|
3214
|
+
const next = { ...parsed2, mcpServers };
|
|
3215
|
+
await fs11.mkdir(path14.dirname(configPath), { recursive: true });
|
|
3216
|
+
await fs11.writeFile(configPath, JSON.stringify(next, null, 2) + "\n", "utf8");
|
|
2267
3217
|
return { changed: true, backupPath };
|
|
2268
3218
|
}
|
|
2269
3219
|
async function resolveLocalCliPathAsync() {
|
|
2270
|
-
const here = process.argv[1] ?
|
|
3220
|
+
const here = process.argv[1] ? path14.dirname(path14.resolve(process.argv[1])) : process.cwd();
|
|
2271
3221
|
const candidates = [
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
3222
|
+
path14.join(here, "cli.cjs"),
|
|
3223
|
+
path14.join(here, "..", "dist", "cli.cjs"),
|
|
3224
|
+
path14.join(here, "..", "..", "dist", "cli.cjs")
|
|
2275
3225
|
];
|
|
2276
3226
|
for (const candidate of candidates) {
|
|
2277
3227
|
if (await pathExists2(candidate)) {
|
|
2278
|
-
return
|
|
3228
|
+
return path14.resolve(candidate);
|
|
2279
3229
|
}
|
|
2280
3230
|
}
|
|
2281
3231
|
return null;
|
|
@@ -2294,8 +3244,9 @@ async function buildJetBrainsMemoraoneServer(options) {
|
|
|
2294
3244
|
return buildMemoraoneJetBrainsMcpServer({
|
|
2295
3245
|
command: process.execPath,
|
|
2296
3246
|
args: [cliPath],
|
|
2297
|
-
|
|
2298
|
-
devMode: true
|
|
3247
|
+
workspaceRoot: options.workspaceRoot,
|
|
3248
|
+
devMode: true,
|
|
3249
|
+
apiUrl: options.apiUrl
|
|
2299
3250
|
});
|
|
2300
3251
|
}
|
|
2301
3252
|
let npxPath = options.npxPathOverride;
|
|
@@ -2310,14 +3261,14 @@ async function buildJetBrainsMemoraoneServer(options) {
|
|
|
2310
3261
|
return buildMemoraoneJetBrainsMcpServer({
|
|
2311
3262
|
command: npxPath,
|
|
2312
3263
|
args: ["-y", "@memoraone/mcp@latest"],
|
|
2313
|
-
|
|
3264
|
+
workspaceRoot: options.workspaceRoot,
|
|
2314
3265
|
devMode: false
|
|
2315
3266
|
});
|
|
2316
3267
|
}
|
|
2317
3268
|
async function verifyJetBrainsMcpHandshake(options) {
|
|
2318
3269
|
const timeoutMs = options.timeoutMs ?? 15e3;
|
|
2319
3270
|
const { server } = options;
|
|
2320
|
-
return new Promise((
|
|
3271
|
+
return new Promise((resolve16) => {
|
|
2321
3272
|
let settled = false;
|
|
2322
3273
|
const finish = (ok, detail) => {
|
|
2323
3274
|
if (settled) return;
|
|
@@ -2327,9 +3278,9 @@ async function verifyJetBrainsMcpHandshake(options) {
|
|
|
2327
3278
|
child.kill();
|
|
2328
3279
|
} catch {
|
|
2329
3280
|
}
|
|
2330
|
-
|
|
3281
|
+
resolve16({ ok, detail });
|
|
2331
3282
|
};
|
|
2332
|
-
const child = (0,
|
|
3283
|
+
const child = (0, import_node_child_process5.spawn)(server.command, [...server.args], {
|
|
2333
3284
|
env: { ...process.env, ...server.env },
|
|
2334
3285
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2335
3286
|
});
|
|
@@ -2394,9 +3345,9 @@ async function verifyJetBrainsMcpHandshake(options) {
|
|
|
2394
3345
|
});
|
|
2395
3346
|
}
|
|
2396
3347
|
async function setupJetBrainsMcpConfig(options) {
|
|
2397
|
-
const homeDir = options.homeDir ??
|
|
3348
|
+
const homeDir = options.homeDir ?? os5.homedir();
|
|
2398
3349
|
const globalPath = options.globalConfigPath ?? getJetBrainsGlobalMcpConfigPath(homeDir);
|
|
2399
|
-
const
|
|
3350
|
+
const workspaceRoot = path14.resolve(options.repoRoot);
|
|
2400
3351
|
const repairActions = [];
|
|
2401
3352
|
const allLocations = getKnownJetBrainsMcpConfigLocations(homeDir, options.repoRoot);
|
|
2402
3353
|
for (const location of allLocations) {
|
|
@@ -2415,8 +3366,9 @@ async function setupJetBrainsMcpConfig(options) {
|
|
|
2415
3366
|
}
|
|
2416
3367
|
}
|
|
2417
3368
|
const memoraone = await buildJetBrainsMemoraoneServer({
|
|
2418
|
-
|
|
3369
|
+
workspaceRoot,
|
|
2419
3370
|
devMode: options.devMode,
|
|
3371
|
+
apiUrl: options.apiUrl,
|
|
2420
3372
|
npxPathOverride: options.npxPathOverride,
|
|
2421
3373
|
cliPathOverride: options.cliPathOverride
|
|
2422
3374
|
});
|
|
@@ -2451,7 +3403,7 @@ async function setupJetBrainsMcpConfig(options) {
|
|
|
2451
3403
|
path: globalPath,
|
|
2452
3404
|
backupPath: backupPath2
|
|
2453
3405
|
});
|
|
2454
|
-
await
|
|
3406
|
+
await fs11.unlink(globalPath);
|
|
2455
3407
|
existing = null;
|
|
2456
3408
|
}
|
|
2457
3409
|
}
|
|
@@ -2474,9 +3426,9 @@ async function setupJetBrainsMcpConfig(options) {
|
|
|
2474
3426
|
if (existed) {
|
|
2475
3427
|
backupPath = await backupConfigFile(globalPath);
|
|
2476
3428
|
}
|
|
2477
|
-
await
|
|
2478
|
-
await
|
|
2479
|
-
const verifyRaw = await
|
|
3429
|
+
await fs11.mkdir(path14.dirname(globalPath), { recursive: true });
|
|
3430
|
+
await fs11.writeFile(globalPath, body, "utf8");
|
|
3431
|
+
const verifyRaw = await fs11.readFile(globalPath, "utf8");
|
|
2480
3432
|
const verifyParsed = JSON.parse(stripLeadingLineComments2(verifyRaw));
|
|
2481
3433
|
validateJetBrainsMcpConfig(verifyParsed, memoraone);
|
|
2482
3434
|
const outcome = existed ? "updated" : "created";
|
|
@@ -2532,31 +3484,56 @@ function logJetBrainsMcpCliSummary(info, dryRun) {
|
|
|
2532
3484
|
}
|
|
2533
3485
|
|
|
2534
3486
|
// src/resolveBuiltCliPath.ts
|
|
2535
|
-
var
|
|
2536
|
-
var
|
|
2537
|
-
var MONOREPO_CLI_REL =
|
|
3487
|
+
var fs12 = __toESM(require("fs/promises"), 1);
|
|
3488
|
+
var path15 = __toESM(require("path"), 1);
|
|
3489
|
+
var MONOREPO_CLI_REL = path15.join("packages", "mcp", "dist", "cli.cjs");
|
|
2538
3490
|
async function pathExists3(filePath) {
|
|
2539
3491
|
try {
|
|
2540
|
-
await
|
|
3492
|
+
await fs12.access(filePath);
|
|
2541
3493
|
return true;
|
|
2542
3494
|
} catch {
|
|
2543
3495
|
return false;
|
|
2544
3496
|
}
|
|
2545
3497
|
}
|
|
2546
3498
|
async function findMonorepoCliFrom(startDir) {
|
|
2547
|
-
let current =
|
|
2548
|
-
const root =
|
|
3499
|
+
let current = path15.resolve(startDir);
|
|
3500
|
+
const root = path15.parse(current).root;
|
|
2549
3501
|
while (true) {
|
|
2550
|
-
const candidate =
|
|
3502
|
+
const candidate = path15.join(current, MONOREPO_CLI_REL);
|
|
2551
3503
|
if (await pathExists3(candidate)) {
|
|
2552
|
-
return
|
|
3504
|
+
return path15.resolve(candidate);
|
|
2553
3505
|
}
|
|
2554
3506
|
if (current === root) break;
|
|
2555
|
-
current =
|
|
3507
|
+
current = path15.dirname(current);
|
|
3508
|
+
}
|
|
3509
|
+
return null;
|
|
3510
|
+
}
|
|
3511
|
+
async function resolveFromRunningScript() {
|
|
3512
|
+
if (!process.argv[1]) return null;
|
|
3513
|
+
const script = path15.resolve(process.argv[1]);
|
|
3514
|
+
const base = path15.basename(script);
|
|
3515
|
+
if ((base === "cli.cjs" || base === "cli.ts" || base === "memoraone-mcp.cjs") && await pathExists3(script)) {
|
|
3516
|
+
return script;
|
|
3517
|
+
}
|
|
3518
|
+
const here = path15.dirname(script);
|
|
3519
|
+
const candidates = [
|
|
3520
|
+
path15.join(here, "cli.cjs"),
|
|
3521
|
+
path15.join(here, "..", "dist", "cli.cjs"),
|
|
3522
|
+
path15.join(here, "..", "..", "dist", "cli.cjs")
|
|
3523
|
+
];
|
|
3524
|
+
for (const candidate of candidates) {
|
|
3525
|
+
if (await pathExists3(candidate)) {
|
|
3526
|
+
return path15.resolve(candidate);
|
|
3527
|
+
}
|
|
2556
3528
|
}
|
|
2557
3529
|
return null;
|
|
2558
3530
|
}
|
|
2559
3531
|
async function resolveBuiltCliPathAsync(options) {
|
|
3532
|
+
const preferRunning = options?.preferRunningScript !== false;
|
|
3533
|
+
if (preferRunning) {
|
|
3534
|
+
const fromRunning = await resolveFromRunningScript();
|
|
3535
|
+
if (fromRunning) return fromRunning;
|
|
3536
|
+
}
|
|
2560
3537
|
const searchDirs = [];
|
|
2561
3538
|
if (options?.searchFrom !== void 0) {
|
|
2562
3539
|
const dirs = Array.isArray(options.searchFrom) ? options.searchFrom : [options.searchFrom];
|
|
@@ -2565,30 +3542,22 @@ async function resolveBuiltCliPathAsync(options) {
|
|
|
2565
3542
|
searchDirs.push(process.cwd());
|
|
2566
3543
|
const seen = /* @__PURE__ */ new Set();
|
|
2567
3544
|
for (const dir of searchDirs) {
|
|
2568
|
-
const key =
|
|
3545
|
+
const key = path15.resolve(dir);
|
|
2569
3546
|
if (seen.has(key)) continue;
|
|
2570
3547
|
seen.add(key);
|
|
2571
3548
|
const found = await findMonorepoCliFrom(key);
|
|
2572
3549
|
if (found) return found;
|
|
2573
3550
|
}
|
|
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
|
-
}
|
|
3551
|
+
if (!preferRunning) {
|
|
3552
|
+
return resolveFromRunningScript();
|
|
2584
3553
|
}
|
|
2585
3554
|
return null;
|
|
2586
3555
|
}
|
|
2587
3556
|
|
|
2588
3557
|
// src/openCursorMcpSettings.ts
|
|
2589
|
-
var
|
|
3558
|
+
var import_node_child_process6 = require("child_process");
|
|
2590
3559
|
var readline4 = __toESM(require("readline/promises"), 1);
|
|
2591
|
-
var
|
|
3560
|
+
var import_node_util4 = require("util");
|
|
2592
3561
|
|
|
2593
3562
|
// src/terminalPresentation.ts
|
|
2594
3563
|
var ANSI = {
|
|
@@ -2599,34 +3568,34 @@ var ANSI = {
|
|
|
2599
3568
|
yellow: "\x1B[33m",
|
|
2600
3569
|
cyan: "\x1B[36m"
|
|
2601
3570
|
};
|
|
2602
|
-
function isCiLikeEnv(
|
|
2603
|
-
if (
|
|
2604
|
-
if (
|
|
2605
|
-
if (
|
|
2606
|
-
if (
|
|
2607
|
-
if (
|
|
2608
|
-
if (typeof
|
|
3571
|
+
function isCiLikeEnv(env2 = process.env) {
|
|
3572
|
+
if (env2.CI === "true" || env2.CI === "1") return true;
|
|
3573
|
+
if (env2.GITHUB_ACTIONS === "true" || env2.GITHUB_ACTIONS === "1") return true;
|
|
3574
|
+
if (env2.GITLAB_CI === "true" || env2.GITLAB_CI === "1") return true;
|
|
3575
|
+
if (env2.CIRCLECI === "true" || env2.CIRCLECI === "1") return true;
|
|
3576
|
+
if (env2.BUILDKITE === "true" || env2.BUILDKITE === "1") return true;
|
|
3577
|
+
if (typeof env2.CI === "string" && env2.CI.trim() !== "" && env2.CI !== "0" && env2.CI !== "false") {
|
|
2609
3578
|
return true;
|
|
2610
3579
|
}
|
|
2611
3580
|
return false;
|
|
2612
3581
|
}
|
|
2613
3582
|
function shouldEnableAnsiColor(opts = {}) {
|
|
2614
3583
|
if (typeof opts.color === "boolean") return opts.color;
|
|
2615
|
-
const
|
|
2616
|
-
if (
|
|
2617
|
-
if (isCiLikeEnv(
|
|
3584
|
+
const env2 = opts.env ?? process.env;
|
|
3585
|
+
if (env2.NO_COLOR !== void 0) return false;
|
|
3586
|
+
if (isCiLikeEnv(env2)) return false;
|
|
2618
3587
|
const tty = opts.stdoutIsTty ?? process.stdout.isTTY === true;
|
|
2619
3588
|
return tty;
|
|
2620
3589
|
}
|
|
2621
3590
|
function shouldUseUnicodeSymbols(opts = {}) {
|
|
2622
3591
|
if (typeof opts.unicode === "boolean") return opts.unicode;
|
|
2623
|
-
const
|
|
3592
|
+
const env2 = opts.env ?? process.env;
|
|
2624
3593
|
const tty = opts.stdoutIsTty ?? process.stdout.isTTY === true;
|
|
2625
3594
|
if (!tty) return false;
|
|
2626
|
-
if (
|
|
3595
|
+
if (env2.TERM === "dumb") return false;
|
|
2627
3596
|
if (process.platform === "win32") {
|
|
2628
3597
|
return Boolean(
|
|
2629
|
-
|
|
3598
|
+
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
3599
|
);
|
|
2631
3600
|
}
|
|
2632
3601
|
return true;
|
|
@@ -2661,7 +3630,7 @@ function createTerminalPresentation(opts = {}) {
|
|
|
2661
3630
|
}
|
|
2662
3631
|
|
|
2663
3632
|
// src/openCursorMcpSettings.ts
|
|
2664
|
-
var
|
|
3633
|
+
var execFileAsync4 = (0, import_node_util4.promisify)(import_node_child_process6.execFile);
|
|
2665
3634
|
var OPEN_CURSOR_MCP_SETTINGS_PROMPT = "Open Cursor MCP settings now? [Y/n] ";
|
|
2666
3635
|
function resolvePresentation(deps) {
|
|
2667
3636
|
if (deps.presentation) return deps.presentation;
|
|
@@ -2756,13 +3725,13 @@ function macosOpenCursorMcpSettingsAppleScript() {
|
|
|
2756
3725
|
"end tell"
|
|
2757
3726
|
].join("\n");
|
|
2758
3727
|
}
|
|
2759
|
-
async function openCursorMcpSettingsViaOsascript(execFileImpl =
|
|
3728
|
+
async function openCursorMcpSettingsViaOsascript(execFileImpl = execFileAsync4) {
|
|
2760
3729
|
await execFileImpl("osascript", ["-e", macosOpenCursorMcpSettingsAppleScript()], {
|
|
2761
3730
|
timeout: 3e4
|
|
2762
3731
|
});
|
|
2763
3732
|
}
|
|
2764
|
-
function manualCursorMcpSettingsSteps(
|
|
2765
|
-
const chord =
|
|
3733
|
+
function manualCursorMcpSettingsSteps(platform2) {
|
|
3734
|
+
const chord = platform2 === "darwin" ? "Command + Shift + P" : "Ctrl + Shift + P";
|
|
2766
3735
|
return [
|
|
2767
3736
|
"To finish setup manually:",
|
|
2768
3737
|
"1. Open this repository in Cursor.",
|
|
@@ -2773,8 +3742,8 @@ function manualCursorMcpSettingsSteps(platform) {
|
|
|
2773
3742
|
"6. Return to MemoraOne Studio and refresh Sources."
|
|
2774
3743
|
];
|
|
2775
3744
|
}
|
|
2776
|
-
function printManualCursorMcpSettingsSteps(
|
|
2777
|
-
for (const line of manualCursorMcpSettingsSteps(
|
|
3745
|
+
function printManualCursorMcpSettingsSteps(platform2, println = console.log) {
|
|
3746
|
+
for (const line of manualCursorMcpSettingsSteps(platform2)) {
|
|
2778
3747
|
println(line);
|
|
2779
3748
|
}
|
|
2780
3749
|
}
|
|
@@ -2782,7 +3751,7 @@ function formatOpenCursorMcpSettingsPrompt(presentation = createTerminalPresenta
|
|
|
2782
3751
|
return `${presentation.indent(OPEN_CURSOR_MCP_SETTINGS_PROMPT.trimEnd())} `;
|
|
2783
3752
|
}
|
|
2784
3753
|
async function runOpenCursorMcpSettingsFlow(deps = {}) {
|
|
2785
|
-
const
|
|
3754
|
+
const platform2 = deps.platform ?? process.platform;
|
|
2786
3755
|
const println = deps.println ?? console.log;
|
|
2787
3756
|
const tp = resolvePresentation(deps);
|
|
2788
3757
|
const confirm = deps.confirm ?? ((question) => confirmYesDefault(question));
|
|
@@ -2790,14 +3759,14 @@ async function runOpenCursorMcpSettingsFlow(deps = {}) {
|
|
|
2790
3759
|
println(tp.heading("Next"));
|
|
2791
3760
|
const yes = await confirm(formatOpenCursorMcpSettingsPrompt(tp));
|
|
2792
3761
|
if (!yes) {
|
|
2793
|
-
printManualCursorMcpSettingsSteps(
|
|
3762
|
+
printManualCursorMcpSettingsSteps(platform2, println);
|
|
2794
3763
|
return;
|
|
2795
3764
|
}
|
|
2796
|
-
if (
|
|
3765
|
+
if (platform2 === "darwin") {
|
|
2797
3766
|
println(tp.warningLine("macOS may request Automation or Accessibility permission."));
|
|
2798
3767
|
try {
|
|
2799
|
-
const
|
|
2800
|
-
await
|
|
3768
|
+
const open2 = deps.openViaOsascript ?? (() => openCursorMcpSettingsViaOsascript(deps.execFile ?? execFileAsync4));
|
|
3769
|
+
await open2();
|
|
2801
3770
|
println(tp.successLine('Confirm "memoraone" is enabled and green'));
|
|
2802
3771
|
println(tp.nextActionLine("Return to MemoraOne Studio and refresh Sources"));
|
|
2803
3772
|
} catch (err) {
|
|
@@ -2810,85 +3779,67 @@ async function runOpenCursorMcpSettingsFlow(deps = {}) {
|
|
|
2810
3779
|
}
|
|
2811
3780
|
return;
|
|
2812
3781
|
}
|
|
2813
|
-
printManualCursorMcpSettingsSteps(
|
|
3782
|
+
printManualCursorMcpSettingsSteps(platform2, println);
|
|
2814
3783
|
}
|
|
2815
3784
|
|
|
2816
3785
|
// src/setupIdeFiles.ts
|
|
2817
3786
|
var MANAGED_MARKER = "<!-- MemoraOne managed IDE helper -->";
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
3787
|
+
function buildMemoraoneMcpServer(ideType, options = {}) {
|
|
3788
|
+
const environment = options.environment ?? "production";
|
|
3789
|
+
const env2 = {
|
|
3790
|
+
MEMORAONE_API_URL: resolveIdeApiUrl({ environment, apiUrl: options.apiUrl }),
|
|
3791
|
+
MEMORAONE_IDE_TYPE: ideType
|
|
3792
|
+
};
|
|
3793
|
+
if (environment === "local") {
|
|
3794
|
+
if (!options.cliPath) {
|
|
3795
|
+
throw new Error("[setup-ide-files] Local VS Code MCP config requires a built CLI path.");
|
|
3796
|
+
}
|
|
3797
|
+
if (options.workspaceRoot !== void 0) {
|
|
3798
|
+
env2[MEMORAONE_WORKSPACE_ROOT_ENV] = path16.resolve(options.workspaceRoot);
|
|
3799
|
+
}
|
|
3800
|
+
return {
|
|
3801
|
+
command: "node",
|
|
3802
|
+
args: [options.cliPath],
|
|
3803
|
+
env: env2
|
|
3804
|
+
};
|
|
3805
|
+
}
|
|
2821
3806
|
return {
|
|
2822
|
-
command,
|
|
3807
|
+
command: options.command ?? "npx",
|
|
2823
3808
|
args: ["-y", "@memoraone/mcp@latest"],
|
|
2824
|
-
env:
|
|
2825
|
-
MEMORAONE_API_URL: "https://api.memoraone.com",
|
|
2826
|
-
MEMORAONE_IDE_TYPE: ideType
|
|
2827
|
-
}
|
|
3809
|
+
env: env2
|
|
2828
3810
|
};
|
|
2829
3811
|
}
|
|
2830
3812
|
function assertUnderRepoRoot(repoRoot, absPath) {
|
|
2831
|
-
const normRoot =
|
|
2832
|
-
const normPath =
|
|
2833
|
-
if (normPath !==
|
|
3813
|
+
const normRoot = path16.resolve(repoRoot) + path16.sep;
|
|
3814
|
+
const normPath = path16.resolve(absPath);
|
|
3815
|
+
if (normPath !== path16.resolve(repoRoot) && !normPath.startsWith(normRoot)) {
|
|
2834
3816
|
throw new Error(`[setup-ide-files] Refusing to write outside repo root: ${absPath}`);
|
|
2835
3817
|
}
|
|
2836
3818
|
}
|
|
2837
3819
|
async function pathExists4(filePath) {
|
|
2838
3820
|
try {
|
|
2839
|
-
await
|
|
3821
|
+
await fs13.access(filePath);
|
|
2840
3822
|
return true;
|
|
2841
3823
|
} catch {
|
|
2842
3824
|
return false;
|
|
2843
3825
|
}
|
|
2844
3826
|
}
|
|
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";
|
|
3827
|
+
async function ensureGitignoreMemoraone(_repoRoot, _opts) {
|
|
3828
|
+
return "skipped";
|
|
2878
3829
|
}
|
|
2879
3830
|
async function findRepoRoot(startDir) {
|
|
2880
|
-
let current =
|
|
2881
|
-
const root =
|
|
3831
|
+
let current = path16.resolve(startDir);
|
|
3832
|
+
const root = path16.parse(current).root;
|
|
2882
3833
|
while (true) {
|
|
2883
|
-
const gitPath =
|
|
2884
|
-
const m1Path =
|
|
3834
|
+
const gitPath = path16.join(current, ".git");
|
|
3835
|
+
const m1Path = path16.join(current, "memoraone.m1");
|
|
2885
3836
|
if (await pathExists4(gitPath) || await pathExists4(m1Path)) {
|
|
2886
3837
|
return current;
|
|
2887
3838
|
}
|
|
2888
3839
|
if (current === root) {
|
|
2889
3840
|
return null;
|
|
2890
3841
|
}
|
|
2891
|
-
current =
|
|
3842
|
+
current = path16.dirname(current);
|
|
2892
3843
|
}
|
|
2893
3844
|
}
|
|
2894
3845
|
function stripLeadingLineComments3(text) {
|
|
@@ -2934,10 +3885,10 @@ function mcpJsonHeader() {
|
|
|
2934
3885
|
return `// ${MANAGED_MARKER}
|
|
2935
3886
|
`;
|
|
2936
3887
|
}
|
|
2937
|
-
function buildVscodeMcpJsonBody(existing) {
|
|
3888
|
+
function buildVscodeMcpJsonBody(existing, options = {}) {
|
|
2938
3889
|
const base = existing && typeof existing === "object" ? { ...existing } : { servers: {} };
|
|
2939
3890
|
const servers = typeof base.servers === "object" && base.servers !== null && !Array.isArray(base.servers) ? { ...base.servers } : {};
|
|
2940
|
-
servers.memoraone = buildMemoraoneMcpServer("copilot-vscode");
|
|
3891
|
+
servers.memoraone = buildMemoraoneMcpServer("copilot-vscode", options);
|
|
2941
3892
|
const merged = { ...base, servers };
|
|
2942
3893
|
return mcpJsonHeader() + JSON.stringify(merged, null, 2) + "\n";
|
|
2943
3894
|
}
|
|
@@ -2946,42 +3897,42 @@ function buildCursorMcpJsonBody(existing, writeOptions) {
|
|
|
2946
3897
|
return mcpJsonHeader() + JSON.stringify(merged, null, 2) + "\n";
|
|
2947
3898
|
}
|
|
2948
3899
|
async function writeManagedMarkdown(repoRoot, relPath, fullContent, opts) {
|
|
2949
|
-
const abs =
|
|
3900
|
+
const abs = path16.join(repoRoot, relPath);
|
|
2950
3901
|
assertUnderRepoRoot(repoRoot, abs);
|
|
2951
3902
|
let prior = "";
|
|
2952
3903
|
let existed = false;
|
|
2953
3904
|
try {
|
|
2954
|
-
prior = await
|
|
3905
|
+
prior = await fs13.readFile(abs, "utf8");
|
|
2955
3906
|
existed = true;
|
|
2956
3907
|
} catch (err) {
|
|
2957
3908
|
if (err?.code !== "ENOENT") throw err;
|
|
2958
3909
|
}
|
|
2959
3910
|
if (!existed) {
|
|
2960
3911
|
if (opts.dryRun) return "created";
|
|
2961
|
-
await
|
|
2962
|
-
await
|
|
3912
|
+
await fs13.mkdir(path16.dirname(abs), { recursive: true });
|
|
3913
|
+
await fs13.writeFile(abs, fullContent, "utf8");
|
|
2963
3914
|
return "created";
|
|
2964
3915
|
}
|
|
2965
3916
|
if (prior.includes(MANAGED_MARKER)) {
|
|
2966
3917
|
if (prior === fullContent) return "skipped";
|
|
2967
3918
|
if (opts.dryRun) return "updated";
|
|
2968
|
-
await
|
|
2969
|
-
await
|
|
3919
|
+
await fs13.mkdir(path16.dirname(abs), { recursive: true });
|
|
3920
|
+
await fs13.writeFile(abs, fullContent, "utf8");
|
|
2970
3921
|
return "updated";
|
|
2971
3922
|
}
|
|
2972
3923
|
if (!opts.force) return "skipped-untracked";
|
|
2973
3924
|
if (opts.dryRun) return "updated";
|
|
2974
|
-
await
|
|
2975
|
-
await
|
|
3925
|
+
await fs13.mkdir(path16.dirname(abs), { recursive: true });
|
|
3926
|
+
await fs13.writeFile(abs, fullContent, "utf8");
|
|
2976
3927
|
return "updated";
|
|
2977
3928
|
}
|
|
2978
3929
|
async function writeIdeMcpJson(repoRoot, relPath, buildBody, opts) {
|
|
2979
|
-
const abs =
|
|
3930
|
+
const abs = path16.join(repoRoot, relPath);
|
|
2980
3931
|
assertUnderRepoRoot(repoRoot, abs);
|
|
2981
3932
|
let raw = "";
|
|
2982
3933
|
let existed = false;
|
|
2983
3934
|
try {
|
|
2984
|
-
raw = await
|
|
3935
|
+
raw = await fs13.readFile(abs, "utf8");
|
|
2985
3936
|
existed = true;
|
|
2986
3937
|
} catch (err) {
|
|
2987
3938
|
if (err?.code !== "ENOENT") throw err;
|
|
@@ -2989,24 +3940,24 @@ async function writeIdeMcpJson(repoRoot, relPath, buildBody, opts) {
|
|
|
2989
3940
|
if (!existed) {
|
|
2990
3941
|
const body = buildBody(null);
|
|
2991
3942
|
if (opts.dryRun) return "created";
|
|
2992
|
-
await
|
|
2993
|
-
await
|
|
3943
|
+
await fs13.mkdir(path16.dirname(abs), { recursive: true });
|
|
3944
|
+
await fs13.writeFile(abs, body, "utf8");
|
|
2994
3945
|
return "created";
|
|
2995
3946
|
}
|
|
2996
3947
|
const managed = raw.includes(MANAGED_MARKER);
|
|
2997
3948
|
if (!managed && !opts.force) return "skipped-untracked";
|
|
2998
|
-
let
|
|
3949
|
+
let parsed2 = null;
|
|
2999
3950
|
try {
|
|
3000
|
-
|
|
3951
|
+
parsed2 = JSON.parse(stripLeadingLineComments3(raw));
|
|
3001
3952
|
} catch {
|
|
3002
|
-
|
|
3953
|
+
parsed2 = null;
|
|
3003
3954
|
}
|
|
3004
|
-
if (!
|
|
3005
|
-
const next = buildBody(
|
|
3955
|
+
if (!parsed2 && !opts.force) return "skipped-untracked";
|
|
3956
|
+
const next = buildBody(parsed2);
|
|
3006
3957
|
if (managed && next === raw) return "skipped";
|
|
3007
3958
|
if (opts.dryRun) return "updated";
|
|
3008
|
-
await
|
|
3009
|
-
await
|
|
3959
|
+
await fs13.mkdir(path16.dirname(abs), { recursive: true });
|
|
3960
|
+
await fs13.writeFile(abs, next, "utf8");
|
|
3010
3961
|
return "updated";
|
|
3011
3962
|
}
|
|
3012
3963
|
function parseSetupIdeFlags(argv) {
|
|
@@ -3022,8 +3973,12 @@ function parseSetupIdeFlags(argv) {
|
|
|
3022
3973
|
let repair = false;
|
|
3023
3974
|
let local = false;
|
|
3024
3975
|
let staging = false;
|
|
3976
|
+
let workspaceRoot;
|
|
3977
|
+
let apiUrl;
|
|
3025
3978
|
const unknown = [];
|
|
3026
|
-
|
|
3979
|
+
let flagError;
|
|
3980
|
+
for (let i = 0; i < argv.length; i++) {
|
|
3981
|
+
const a = argv[i];
|
|
3027
3982
|
if (a === "--cursor") cursor = true;
|
|
3028
3983
|
else if (a === "--vscode") vscode = true;
|
|
3029
3984
|
else if (a === "--jetbrains") jetbrains = true;
|
|
@@ -3036,7 +3991,35 @@ function parseSetupIdeFlags(argv) {
|
|
|
3036
3991
|
else if (a === "--repair") repair = true;
|
|
3037
3992
|
else if (a === "--local") local = true;
|
|
3038
3993
|
else if (a === "--staging") staging = true;
|
|
3039
|
-
else if (a
|
|
3994
|
+
else if (a === "--workspace-root") {
|
|
3995
|
+
const value = argv[++i];
|
|
3996
|
+
if (!value || value.startsWith("-")) {
|
|
3997
|
+
flagError = "[setup-ide-files] --workspace-root requires a path argument.";
|
|
3998
|
+
} else {
|
|
3999
|
+
workspaceRoot = value;
|
|
4000
|
+
}
|
|
4001
|
+
} else if (a.startsWith("--workspace-root=")) {
|
|
4002
|
+
const value = a.slice("--workspace-root=".length);
|
|
4003
|
+
if (!value) {
|
|
4004
|
+
flagError = "[setup-ide-files] --workspace-root requires a path argument.";
|
|
4005
|
+
} else {
|
|
4006
|
+
workspaceRoot = value;
|
|
4007
|
+
}
|
|
4008
|
+
} else if (a === "--api-url") {
|
|
4009
|
+
const value = argv[++i];
|
|
4010
|
+
if (!value || value.startsWith("-")) {
|
|
4011
|
+
flagError = "[setup-ide-files] --api-url requires a URL argument.";
|
|
4012
|
+
} else {
|
|
4013
|
+
apiUrl = value;
|
|
4014
|
+
}
|
|
4015
|
+
} else if (a.startsWith("--api-url=")) {
|
|
4016
|
+
const value = a.slice("--api-url=".length);
|
|
4017
|
+
if (!value) {
|
|
4018
|
+
flagError = "[setup-ide-files] --api-url requires a URL argument.";
|
|
4019
|
+
} else {
|
|
4020
|
+
apiUrl = value;
|
|
4021
|
+
}
|
|
4022
|
+
} else if (a.startsWith("-")) unknown.push(a);
|
|
3040
4023
|
}
|
|
3041
4024
|
const specific = cursor || vscode || jetbrains;
|
|
3042
4025
|
let targets;
|
|
@@ -3045,10 +4028,12 @@ function parseSetupIdeFlags(argv) {
|
|
|
3045
4028
|
} else {
|
|
3046
4029
|
targets = { cursor, vscode, jetbrains };
|
|
3047
4030
|
}
|
|
3048
|
-
|
|
3049
|
-
if (local && staging) {
|
|
4031
|
+
if (!flagError && local && staging) {
|
|
3050
4032
|
flagError = "[setup-ide-files] --local and --staging are mutually exclusive.";
|
|
3051
4033
|
}
|
|
4034
|
+
if (!flagError && apiUrl && !local && !devMode && !staging) {
|
|
4035
|
+
flagError = "[setup-ide-files] --api-url is developer-only and requires --local, --dev, or --staging.";
|
|
4036
|
+
}
|
|
3052
4037
|
return {
|
|
3053
4038
|
targets,
|
|
3054
4039
|
force,
|
|
@@ -3060,11 +4045,35 @@ function parseSetupIdeFlags(argv) {
|
|
|
3060
4045
|
local,
|
|
3061
4046
|
staging,
|
|
3062
4047
|
all,
|
|
4048
|
+
workspaceRoot,
|
|
4049
|
+
apiUrl,
|
|
3063
4050
|
explicitCursor: cursor,
|
|
3064
4051
|
unknown,
|
|
3065
4052
|
flagError
|
|
3066
4053
|
};
|
|
3067
4054
|
}
|
|
4055
|
+
async function resolveSetupApiUrl(o, repoRoot, localOrDev) {
|
|
4056
|
+
if (o.apiUrl) return normalizeApiUrl(o.apiUrl);
|
|
4057
|
+
if (!localOrDev) return void 0;
|
|
4058
|
+
const binding = await findBindingRecordByWorkspaceRoot(repoRoot, o.homeDir);
|
|
4059
|
+
if (binding?.apiUrl) return normalizeApiUrl(binding.apiUrl);
|
|
4060
|
+
return void 0;
|
|
4061
|
+
}
|
|
4062
|
+
async function persistBindingApiUrl(repoRoot, apiUrl, homeDir, dryRun) {
|
|
4063
|
+
if (dryRun) return;
|
|
4064
|
+
const binding = await findBindingRecordByWorkspaceRoot(repoRoot, homeDir);
|
|
4065
|
+
if (!binding) return;
|
|
4066
|
+
const normalized = normalizeApiUrl(apiUrl);
|
|
4067
|
+
if (binding.apiUrl === normalized) return;
|
|
4068
|
+
await writeBindingRecord(
|
|
4069
|
+
{
|
|
4070
|
+
...binding,
|
|
4071
|
+
apiUrl: normalized,
|
|
4072
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4073
|
+
},
|
|
4074
|
+
homeDir
|
|
4075
|
+
);
|
|
4076
|
+
}
|
|
3068
4077
|
function cursorEnvironmentFromFlags(local, staging) {
|
|
3069
4078
|
if (local) return "local";
|
|
3070
4079
|
if (staging) return "staging";
|
|
@@ -3172,7 +4181,7 @@ async function runSetupIdeDaemonCleanup(opts) {
|
|
|
3172
4181
|
if ("error" in target) {
|
|
3173
4182
|
return {
|
|
3174
4183
|
skipped: true,
|
|
3175
|
-
skipReason: "no-
|
|
4184
|
+
skipReason: "no-binding",
|
|
3176
4185
|
foundDaemonCount: 0,
|
|
3177
4186
|
stoppedDaemonCount: 0,
|
|
3178
4187
|
removedSocketCount: 0,
|
|
@@ -3229,13 +4238,13 @@ async function runSetupIdeFiles(o) {
|
|
|
3229
4238
|
const outcomes = {};
|
|
3230
4239
|
let cursorMcp;
|
|
3231
4240
|
let jetbrainsMcp;
|
|
3232
|
-
const repoRoot = await findRepoRoot(o.cwd);
|
|
4241
|
+
const repoRoot = o.workspaceRoot !== void 0 && o.workspaceRoot !== "" ? path16.resolve(o.workspaceRoot) : await findRepoRoot(o.cwd);
|
|
3233
4242
|
if (!repoRoot) {
|
|
3234
4243
|
return {
|
|
3235
4244
|
exitCode: 1,
|
|
3236
4245
|
repoRoot: null,
|
|
3237
4246
|
outcomes,
|
|
3238
|
-
error: "[setup-ide-files] No repo root found (looked for .git or memoraone.m1)."
|
|
4247
|
+
error: "[setup-ide-files] No repo root found (looked for .git or memoraone.m1). Pass --workspace-root <path> for a fileless bound workspace."
|
|
3239
4248
|
};
|
|
3240
4249
|
}
|
|
3241
4250
|
let daemonCleanup;
|
|
@@ -3263,19 +4272,28 @@ async function runSetupIdeFiles(o) {
|
|
|
3263
4272
|
dryRun: o.dryRun,
|
|
3264
4273
|
noGitignore: o.noGitignore ?? false
|
|
3265
4274
|
});
|
|
4275
|
+
const cursorEnvironment = o.cursorEnvironment ?? "production";
|
|
4276
|
+
const localOrDev = cursorEnvironment === "local" || Boolean(o.devMode);
|
|
4277
|
+
const resolvedApiUrl2 = await resolveSetupApiUrl(o, repoRoot, localOrDev);
|
|
4278
|
+
const effectiveApiUrl = resolveIdeApiUrl({
|
|
4279
|
+
environment: localOrDev ? "local" : cursorEnvironment,
|
|
4280
|
+
apiUrl: resolvedApiUrl2
|
|
4281
|
+
});
|
|
4282
|
+
if (localOrDev && !o.dryRun) {
|
|
4283
|
+
await persistBindingApiUrl(repoRoot, effectiveApiUrl, o.homeDir, o.dryRun);
|
|
4284
|
+
}
|
|
3266
4285
|
const cursorContent = `---
|
|
3267
4286
|
description: MemoraOne MCP \u2014 IDE agent instructions
|
|
3268
4287
|
---
|
|
3269
4288
|
|
|
3270
4289
|
` + cursorRuleBody();
|
|
3271
4290
|
if (o.targets.cursor) {
|
|
3272
|
-
const cursorEnvironment = o.cursorEnvironment ?? "production";
|
|
3273
4291
|
let npxPath = null;
|
|
3274
4292
|
let cliPath;
|
|
3275
4293
|
if (cursorEnvironment === "local") {
|
|
3276
4294
|
let resolvedCliPath = o.cursorLocalCliPathOverride;
|
|
3277
4295
|
if (resolvedCliPath === void 0) {
|
|
3278
|
-
resolvedCliPath = await resolveBuiltCliPathAsync(
|
|
4296
|
+
resolvedCliPath = await resolveBuiltCliPathAsync();
|
|
3279
4297
|
}
|
|
3280
4298
|
if (!resolvedCliPath) {
|
|
3281
4299
|
return {
|
|
@@ -3305,7 +4323,8 @@ description: MemoraOne MCP \u2014 IDE agent instructions
|
|
|
3305
4323
|
environment: cursorEnvironment,
|
|
3306
4324
|
npxPath: npxPath ?? void 0,
|
|
3307
4325
|
cliPath,
|
|
3308
|
-
repoRoot
|
|
4326
|
+
repoRoot,
|
|
4327
|
+
apiUrl: resolvedApiUrl2
|
|
3309
4328
|
};
|
|
3310
4329
|
outcomes[".cursor/rules/memoraone-mcp.mdc"] = await writeManagedMarkdown(
|
|
3311
4330
|
repoRoot,
|
|
@@ -3367,10 +4386,36 @@ description: MemoraOne MCP \u2014 IDE agent instructions
|
|
|
3367
4386
|
};
|
|
3368
4387
|
}
|
|
3369
4388
|
if (o.targets.vscode) {
|
|
4389
|
+
const vscodeEnvironment = localOrDev ? "local" : "production";
|
|
4390
|
+
let vscodeCliPath;
|
|
4391
|
+
if (vscodeEnvironment === "local") {
|
|
4392
|
+
let resolvedCliPath = o.cursorLocalCliPathOverride;
|
|
4393
|
+
if (resolvedCliPath === void 0) {
|
|
4394
|
+
resolvedCliPath = o.cliPathOverride;
|
|
4395
|
+
}
|
|
4396
|
+
if (resolvedCliPath === void 0) {
|
|
4397
|
+
resolvedCliPath = await resolveBuiltCliPathAsync();
|
|
4398
|
+
}
|
|
4399
|
+
if (!resolvedCliPath) {
|
|
4400
|
+
return {
|
|
4401
|
+
exitCode: 1,
|
|
4402
|
+
repoRoot,
|
|
4403
|
+
outcomes,
|
|
4404
|
+
cursorMcp,
|
|
4405
|
+
error: "[setup-ide-files] Local mode requires a built CLI at packages/mcp/dist/cli.cjs. Run pnpm build first."
|
|
4406
|
+
};
|
|
4407
|
+
}
|
|
4408
|
+
vscodeCliPath = resolvedCliPath;
|
|
4409
|
+
}
|
|
3370
4410
|
outcomes[".vscode/mcp.json"] = await writeIdeMcpJson(
|
|
3371
4411
|
repoRoot,
|
|
3372
4412
|
".vscode/mcp.json",
|
|
3373
|
-
buildVscodeMcpJsonBody,
|
|
4413
|
+
(existing) => buildVscodeMcpJsonBody(existing, {
|
|
4414
|
+
environment: vscodeEnvironment,
|
|
4415
|
+
apiUrl: resolvedApiUrl2,
|
|
4416
|
+
cliPath: vscodeCliPath,
|
|
4417
|
+
workspaceRoot: vscodeEnvironment === "local" ? repoRoot : void 0
|
|
4418
|
+
}),
|
|
3374
4419
|
{
|
|
3375
4420
|
force: o.force,
|
|
3376
4421
|
dryRun: o.dryRun
|
|
@@ -3391,14 +4436,16 @@ description: MemoraOne MCP \u2014 IDE agent instructions
|
|
|
3391
4436
|
{ force: o.force, dryRun: o.dryRun }
|
|
3392
4437
|
);
|
|
3393
4438
|
try {
|
|
3394
|
-
const homeDir = o.jetbrainsHomeDir ??
|
|
4439
|
+
const homeDir = o.jetbrainsHomeDir ?? os6.homedir();
|
|
3395
4440
|
const activePath = o.jetbrainsGlobalMcpConfigPath ?? getJetBrainsGlobalMcpConfigPath(homeDir);
|
|
3396
4441
|
const jetbrainsSetup = await setupJetBrainsMcpConfig({
|
|
3397
4442
|
homeDir,
|
|
3398
4443
|
repoRoot,
|
|
3399
4444
|
globalConfigPath: activePath,
|
|
3400
4445
|
dryRun: o.dryRun,
|
|
4446
|
+
// Connect/local repair passes --dev so JetBrains gets the same backend URL + local CLI.
|
|
3401
4447
|
devMode: o.devMode,
|
|
4448
|
+
apiUrl: resolvedApiUrl2,
|
|
3402
4449
|
repair: o.repair ?? false,
|
|
3403
4450
|
verify: o.verifyHandshake ?? !o.dryRun,
|
|
3404
4451
|
npxPathOverride: o.npxPathOverride,
|
|
@@ -3439,6 +4486,8 @@ async function cliSetupIdeFiles(argv, options = {}) {
|
|
|
3439
4486
|
local,
|
|
3440
4487
|
staging,
|
|
3441
4488
|
all,
|
|
4489
|
+
workspaceRoot,
|
|
4490
|
+
apiUrl,
|
|
3442
4491
|
explicitCursor,
|
|
3443
4492
|
unknown,
|
|
3444
4493
|
flagError
|
|
@@ -3451,19 +4500,20 @@ async function cliSetupIdeFiles(argv, options = {}) {
|
|
|
3451
4500
|
console.error(`[setup-ide-files] Unknown option(s): ${unknown.join(", ")}`);
|
|
3452
4501
|
return 1;
|
|
3453
4502
|
}
|
|
3454
|
-
const
|
|
4503
|
+
const cwd2 = options.cwd ?? process.cwd();
|
|
3455
4504
|
const openDeps = options.openCursorMcpSettings ?? {};
|
|
3456
4505
|
const stdinIsTty = openDeps.stdinIsTty ?? process.stdin.isTTY === true;
|
|
3457
|
-
const
|
|
4506
|
+
const env2 = openDeps.env ?? process.env;
|
|
3458
4507
|
const promptOpenCursorSettings = shouldPromptOpenCursorMcpSettings({
|
|
3459
4508
|
explicitCursor,
|
|
3460
4509
|
all,
|
|
3461
4510
|
dryRun,
|
|
3462
4511
|
stdinIsTty,
|
|
3463
|
-
env
|
|
4512
|
+
env: env2
|
|
3464
4513
|
});
|
|
3465
4514
|
const result = await runSetupIdeFiles({
|
|
3466
|
-
cwd,
|
|
4515
|
+
cwd: cwd2,
|
|
4516
|
+
workspaceRoot,
|
|
3467
4517
|
targets,
|
|
3468
4518
|
force,
|
|
3469
4519
|
dryRun,
|
|
@@ -3471,6 +4521,7 @@ async function cliSetupIdeFiles(argv, options = {}) {
|
|
|
3471
4521
|
devMode,
|
|
3472
4522
|
repair,
|
|
3473
4523
|
cursorEnvironment: cursorEnvironmentFromFlags(local, staging),
|
|
4524
|
+
apiUrl,
|
|
3474
4525
|
...options.setupOverrides
|
|
3475
4526
|
});
|
|
3476
4527
|
if (result.error) {
|
|
@@ -3497,7 +4548,7 @@ async function cliSetupIdeFiles(argv, options = {}) {
|
|
|
3497
4548
|
}
|
|
3498
4549
|
if (promptOpenCursorSettings && result.repoRoot) {
|
|
3499
4550
|
const presentation = openDeps.presentation ?? createTerminalPresentation({
|
|
3500
|
-
env,
|
|
4551
|
+
env: env2,
|
|
3501
4552
|
stdoutIsTty: openDeps.stdoutIsTty ?? process.stdout.isTTY === true,
|
|
3502
4553
|
color: openDeps.color,
|
|
3503
4554
|
unicode: openDeps.unicode
|
|
@@ -3510,7 +4561,7 @@ async function cliSetupIdeFiles(argv, options = {}) {
|
|
|
3510
4561
|
await runOpenCursorMcpSettingsFlow({
|
|
3511
4562
|
...openDeps,
|
|
3512
4563
|
stdinIsTty,
|
|
3513
|
-
env,
|
|
4564
|
+
env: env2,
|
|
3514
4565
|
presentation
|
|
3515
4566
|
});
|
|
3516
4567
|
} else {
|
|
@@ -3526,7 +4577,7 @@ async function cliSetupIdeFiles(argv, options = {}) {
|
|
|
3526
4577
|
if (cleanup) {
|
|
3527
4578
|
console.log("[setup-ide-files] Running additional full-project cleanup (--cleanup)...");
|
|
3528
4579
|
const cleanupResult = await runCleanup({
|
|
3529
|
-
cwd,
|
|
4580
|
+
cwd: cwd2,
|
|
3530
4581
|
dryRun,
|
|
3531
4582
|
allProjects: false,
|
|
3532
4583
|
assumeYes: true
|
|
@@ -3539,6 +4590,440 @@ async function cliSetupIdeFiles(argv, options = {}) {
|
|
|
3539
4590
|
return 0;
|
|
3540
4591
|
}
|
|
3541
4592
|
|
|
4593
|
+
// src/localState/connectCommand.ts
|
|
4594
|
+
var path19 = __toESM(require("path"), 1);
|
|
4595
|
+
var os7 = __toESM(require("os"), 1);
|
|
4596
|
+
|
|
4597
|
+
// src/config.ts
|
|
4598
|
+
var process2 = __toESM(require("process"), 1);
|
|
4599
|
+
var fs14 = __toESM(require("fs"), 1);
|
|
4600
|
+
var path17 = __toESM(require("path"), 1);
|
|
4601
|
+
var dotenv = __toESM(require("dotenv"), 1);
|
|
4602
|
+
var import_v4 = require("zod/v4");
|
|
4603
|
+
var dotenvPath = path17.resolve(process2.cwd(), ".env");
|
|
4604
|
+
if (fs14.existsSync(dotenvPath)) {
|
|
4605
|
+
try {
|
|
4606
|
+
dotenv.config({ path: dotenvPath });
|
|
4607
|
+
} catch (err) {
|
|
4608
|
+
process2.stderr.write("[memoraone-mcp] Failed to load .env: " + String(err) + "\n");
|
|
4609
|
+
}
|
|
4610
|
+
}
|
|
4611
|
+
var EnvSchema = import_v4.z.object({
|
|
4612
|
+
MEMORAONE_API_URL: import_v4.z.string().url().optional(),
|
|
4613
|
+
MEMORAONE_API_KEY: import_v4.z.string().min(1).optional(),
|
|
4614
|
+
MEMORAONE_DEV_MODE: import_v4.z.string().min(1).optional(),
|
|
4615
|
+
MEMORAONE_AGENT_NAME: import_v4.z.string().min(1).optional(),
|
|
4616
|
+
MEMORAONE_AGENT_TYPE: import_v4.z.string().min(1).optional(),
|
|
4617
|
+
MEMORAONE_SOURCE: import_v4.z.string().min(1).optional(),
|
|
4618
|
+
MEMORAONE_IDE_TYPE: import_v4.z.enum(["cursor", "copilot-vscode", "jetbrains"]).optional(),
|
|
4619
|
+
MEMORAONE_WORKLOG: import_v4.z.string().min(1).optional(),
|
|
4620
|
+
MEMORAONE_HEARTBEAT: import_v4.z.string().min(1).optional(),
|
|
4621
|
+
MEMORAONE_HEARTBEAT_INTERVAL_MS: import_v4.z.string().min(1).optional()
|
|
4622
|
+
});
|
|
4623
|
+
var requiredEnvVars = [];
|
|
4624
|
+
var missingEnvVars = requiredEnvVars.filter((key) => {
|
|
4625
|
+
const value = process2.env[key];
|
|
4626
|
+
return value === void 0 || value.trim() === "";
|
|
4627
|
+
});
|
|
4628
|
+
if (missingEnvVars.length > 0) {
|
|
4629
|
+
for (const key of missingEnvVars) {
|
|
4630
|
+
process2.stderr.write(`Missing ${key}
|
|
4631
|
+
`);
|
|
4632
|
+
}
|
|
4633
|
+
process2.exit(1);
|
|
4634
|
+
}
|
|
4635
|
+
var parsed = EnvSchema.safeParse(process2.env);
|
|
4636
|
+
var resolvedApiUrl = resolveApiUrl(process2.env);
|
|
4637
|
+
if (!parsed.success) {
|
|
4638
|
+
const formatted = parsed.error.format();
|
|
4639
|
+
process2.stderr.write(
|
|
4640
|
+
"[memoraone-mcp] Invalid environment variables " + JSON.stringify(formatted) + "\n"
|
|
4641
|
+
);
|
|
4642
|
+
throw new Error("Config validation failed");
|
|
4643
|
+
}
|
|
4644
|
+
var parseBooleanFlag2 = (value, defaultValue) => {
|
|
4645
|
+
if (value === void 0) {
|
|
4646
|
+
return defaultValue;
|
|
4647
|
+
}
|
|
4648
|
+
const normalized = value.trim().toLowerCase();
|
|
4649
|
+
if (["1", "true", "yes", "on"].includes(normalized)) {
|
|
4650
|
+
return true;
|
|
4651
|
+
}
|
|
4652
|
+
if (["0", "false", "no", "off"].includes(normalized)) {
|
|
4653
|
+
return false;
|
|
4654
|
+
}
|
|
4655
|
+
return defaultValue;
|
|
4656
|
+
};
|
|
4657
|
+
var config2 = {
|
|
4658
|
+
apiUrl: resolvedApiUrl.replace(/\/+$/, ""),
|
|
4659
|
+
apiKey: parsed.data.MEMORAONE_API_KEY,
|
|
4660
|
+
agentName: parsed.data.MEMORAONE_AGENT_NAME ?? "cursor",
|
|
4661
|
+
agentType: parsed.data.MEMORAONE_AGENT_TYPE ?? "agent",
|
|
4662
|
+
source: parsed.data.MEMORAONE_SOURCE ?? "cursor",
|
|
4663
|
+
ideType: parsed.data.MEMORAONE_IDE_TYPE,
|
|
4664
|
+
devMode: parseBooleanFlag2(parsed.data.MEMORAONE_DEV_MODE, false),
|
|
4665
|
+
worklogEnabled: parseBooleanFlag2(parsed.data.MEMORAONE_WORKLOG, true),
|
|
4666
|
+
heartbeatEnabled: parseBooleanFlag2(parsed.data.MEMORAONE_HEARTBEAT, true),
|
|
4667
|
+
heartbeatIntervalMs: Number.parseInt(parsed.data.MEMORAONE_HEARTBEAT_INTERVAL_MS ?? "30000", 10)
|
|
4668
|
+
};
|
|
4669
|
+
|
|
4670
|
+
// src/repoFingerprint.ts
|
|
4671
|
+
var fs15 = __toESM(require("fs"), 1);
|
|
4672
|
+
var path18 = __toESM(require("path"), 1);
|
|
4673
|
+
var crypto2 = __toESM(require("crypto"), 1);
|
|
4674
|
+
var parseBooleanFlag3 = (value) => {
|
|
4675
|
+
if (!value) {
|
|
4676
|
+
return false;
|
|
4677
|
+
}
|
|
4678
|
+
const normalized = value.trim().toLowerCase();
|
|
4679
|
+
return ["1", "true", "yes", "on"].includes(normalized);
|
|
4680
|
+
};
|
|
4681
|
+
var debugEnabled2 = parseBooleanFlag3(process.env.MEMORAONE_DEV_MODE);
|
|
4682
|
+
var debugLog = (message) => {
|
|
4683
|
+
if (!debugEnabled2) {
|
|
4684
|
+
return;
|
|
4685
|
+
}
|
|
4686
|
+
process.stderr.write(`[memoraone-mcp][debug] ${message}
|
|
4687
|
+
`);
|
|
4688
|
+
};
|
|
4689
|
+
var normalizeRemoteUrl = (remoteUrl) => {
|
|
4690
|
+
let normalized = remoteUrl.trim();
|
|
4691
|
+
normalized = normalized.replace(/^[a-z]+:\/\//i, "");
|
|
4692
|
+
normalized = normalized.replace(/^git@([^:]+):/i, "$1/");
|
|
4693
|
+
normalized = normalized.replace(/\.git$/i, "");
|
|
4694
|
+
normalized = normalized.replace(/\/+$/, "");
|
|
4695
|
+
return normalized.toLowerCase();
|
|
4696
|
+
};
|
|
4697
|
+
var sha256 = (value) => {
|
|
4698
|
+
return crypto2.createHash("sha256").update(value).digest("hex");
|
|
4699
|
+
};
|
|
4700
|
+
var resolveGitDir = (gitPath) => {
|
|
4701
|
+
try {
|
|
4702
|
+
const stat4 = fs15.statSync(gitPath);
|
|
4703
|
+
if (stat4.isDirectory()) {
|
|
4704
|
+
return gitPath;
|
|
4705
|
+
}
|
|
4706
|
+
if (stat4.isFile()) {
|
|
4707
|
+
const content = fs15.readFileSync(gitPath, "utf8");
|
|
4708
|
+
const match = content.match(/^gitdir:\s*(.+)$/m);
|
|
4709
|
+
if (match) {
|
|
4710
|
+
const gitDir = match[1].trim();
|
|
4711
|
+
return path18.resolve(path18.dirname(gitPath), gitDir);
|
|
4712
|
+
}
|
|
4713
|
+
}
|
|
4714
|
+
} catch {
|
|
4715
|
+
return null;
|
|
4716
|
+
}
|
|
4717
|
+
return null;
|
|
4718
|
+
};
|
|
4719
|
+
var findGitRoot = (start) => {
|
|
4720
|
+
let current = path18.resolve(start);
|
|
4721
|
+
while (true) {
|
|
4722
|
+
const gitPath = path18.join(current, ".git");
|
|
4723
|
+
if (fs15.existsSync(gitPath)) {
|
|
4724
|
+
const gitDir = resolveGitDir(gitPath);
|
|
4725
|
+
if (gitDir) {
|
|
4726
|
+
return { gitRoot: current, gitDir };
|
|
4727
|
+
}
|
|
4728
|
+
}
|
|
4729
|
+
const parent = path18.dirname(current);
|
|
4730
|
+
if (parent === current) {
|
|
4731
|
+
break;
|
|
4732
|
+
}
|
|
4733
|
+
current = parent;
|
|
4734
|
+
}
|
|
4735
|
+
return null;
|
|
4736
|
+
};
|
|
4737
|
+
var readOriginRemote = (gitDir) => {
|
|
4738
|
+
const configPath = path18.join(gitDir, "config");
|
|
4739
|
+
try {
|
|
4740
|
+
const content = fs15.readFileSync(configPath, "utf8");
|
|
4741
|
+
const lines = content.split(/\r?\n/);
|
|
4742
|
+
let inOrigin = false;
|
|
4743
|
+
for (const line of lines) {
|
|
4744
|
+
const sectionMatch = line.match(/^\s*\[(.+)]\s*$/);
|
|
4745
|
+
if (sectionMatch) {
|
|
4746
|
+
inOrigin = sectionMatch[1].trim() === 'remote "origin"';
|
|
4747
|
+
continue;
|
|
4748
|
+
}
|
|
4749
|
+
if (inOrigin) {
|
|
4750
|
+
const urlMatch = line.match(/^\s*url\s*=\s*(.+)\s*$/);
|
|
4751
|
+
if (urlMatch) {
|
|
4752
|
+
return urlMatch[1].trim();
|
|
4753
|
+
}
|
|
4754
|
+
}
|
|
4755
|
+
}
|
|
4756
|
+
} catch {
|
|
4757
|
+
return null;
|
|
4758
|
+
}
|
|
4759
|
+
return null;
|
|
4760
|
+
};
|
|
4761
|
+
function resolveRepoFingerprint(cwd2) {
|
|
4762
|
+
const found = findGitRoot(cwd2);
|
|
4763
|
+
if (!found) {
|
|
4764
|
+
const fallbackPath = path18.resolve(cwd2);
|
|
4765
|
+
const fingerprint2 = sha256(fallbackPath);
|
|
4766
|
+
debugLog(`repo fingerprint=${fingerprint2} source=path-fallback`);
|
|
4767
|
+
return {
|
|
4768
|
+
fingerprint: fingerprint2,
|
|
4769
|
+
gitRoot: fallbackPath,
|
|
4770
|
+
source: "path-fallback"
|
|
4771
|
+
};
|
|
4772
|
+
}
|
|
4773
|
+
const { gitRoot, gitDir } = found;
|
|
4774
|
+
const remoteUrl = readOriginRemote(gitDir);
|
|
4775
|
+
if (remoteUrl) {
|
|
4776
|
+
const normalized = normalizeRemoteUrl(remoteUrl);
|
|
4777
|
+
const fingerprint2 = sha256(normalized);
|
|
4778
|
+
debugLog(`repo fingerprint=${fingerprint2} source=git-remote`);
|
|
4779
|
+
return {
|
|
4780
|
+
fingerprint: fingerprint2,
|
|
4781
|
+
gitRoot,
|
|
4782
|
+
remoteUrl,
|
|
4783
|
+
source: "git-remote"
|
|
4784
|
+
};
|
|
4785
|
+
}
|
|
4786
|
+
const fingerprint = sha256(path18.resolve(gitRoot));
|
|
4787
|
+
debugLog(`repo fingerprint=${fingerprint} source=path-fallback`);
|
|
4788
|
+
return {
|
|
4789
|
+
fingerprint,
|
|
4790
|
+
gitRoot,
|
|
4791
|
+
source: "path-fallback"
|
|
4792
|
+
};
|
|
4793
|
+
}
|
|
4794
|
+
|
|
4795
|
+
// src/localState/connectCommand.ts
|
|
4796
|
+
function normalizeConnectCode(code) {
|
|
4797
|
+
const trimmed = code.trim();
|
|
4798
|
+
if (!trimmed.startsWith("mcc_")) {
|
|
4799
|
+
throw new Error("[memoraone-mcp] Connect code must start with mcc_");
|
|
4800
|
+
}
|
|
4801
|
+
return trimmed;
|
|
4802
|
+
}
|
|
4803
|
+
function normalizeGitRemote(remoteUrl) {
|
|
4804
|
+
if (!remoteUrl) return null;
|
|
4805
|
+
let normalized = remoteUrl.trim();
|
|
4806
|
+
normalized = normalized.replace(/^[a-z]+:\/\//i, "");
|
|
4807
|
+
normalized = normalized.replace(/^git@([^:]+):/i, "$1/");
|
|
4808
|
+
normalized = normalized.replace(/\.git$/i, "");
|
|
4809
|
+
normalized = normalized.replace(/\/+$/, "");
|
|
4810
|
+
return normalized.toLowerCase() || null;
|
|
4811
|
+
}
|
|
4812
|
+
function formatIdeSetupRepairHint(workspaceRoot, cliPath) {
|
|
4813
|
+
const cli = cliPath ?? "<path-to-packages/mcp/dist/cli.cjs>";
|
|
4814
|
+
return `Repair with (no new connect code): node ${cli} setup-ide-files --all --local --dev --force --workspace-root ${workspaceRoot}`;
|
|
4815
|
+
}
|
|
4816
|
+
async function runConnectCommand(options) {
|
|
4817
|
+
const code = normalizeConnectCode(options.code);
|
|
4818
|
+
const cwd2 = path19.resolve(options.cwd ?? process.cwd());
|
|
4819
|
+
const apiUrl = (options.apiUrl ?? config2.apiUrl).replace(/\/+$/, "");
|
|
4820
|
+
const homeDir = options.homeDir ?? os7.homedir();
|
|
4821
|
+
const environment = "local";
|
|
4822
|
+
const ensured = await ensureRepositoryBindingForRoot(cwd2, {
|
|
4823
|
+
homeDir,
|
|
4824
|
+
identityDeps: options.identityDeps,
|
|
4825
|
+
createIfMissing: true
|
|
4826
|
+
});
|
|
4827
|
+
if (ensured.legacyM1WarningPath) {
|
|
4828
|
+
process.stderr.write(
|
|
4829
|
+
`[memoraone-mcp] warning: ignoring legacy ${path19.basename(ensured.legacyM1WarningPath)} at ${ensured.legacyM1WarningPath} (credentials and binding are package-managed)
|
|
4830
|
+
`
|
|
4831
|
+
);
|
|
4832
|
+
}
|
|
4833
|
+
const fingerprint = resolveRepoFingerprint(cwd2);
|
|
4834
|
+
const displayName = cwd2;
|
|
4835
|
+
const normalizedGitRemote = normalizeGitRemote(fingerprint.remoteUrl);
|
|
4836
|
+
const { clientRedeemKey } = await ensureClientRedeemKey(
|
|
4837
|
+
ensured.repositoryBindingId,
|
|
4838
|
+
options.credentialOptions
|
|
4839
|
+
);
|
|
4840
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4841
|
+
const pendingRecord = {
|
|
4842
|
+
v: 1,
|
|
4843
|
+
repositoryBindingId: ensured.repositoryBindingId,
|
|
4844
|
+
workspaceRoot: cwd2,
|
|
4845
|
+
filesystemIdentity: ensured.identity,
|
|
4846
|
+
rootFingerprint: fingerprint.fingerprint,
|
|
4847
|
+
displayName,
|
|
4848
|
+
environment,
|
|
4849
|
+
apiUrl,
|
|
4850
|
+
normalizedGitRemote,
|
|
4851
|
+
status: "pending",
|
|
4852
|
+
createdAt: now,
|
|
4853
|
+
updatedAt: now,
|
|
4854
|
+
packageVersion: options.packageVersion ?? null,
|
|
4855
|
+
ideType: options.ideType ?? null
|
|
4856
|
+
};
|
|
4857
|
+
await writeBindingRecord(pendingRecord, homeDir);
|
|
4858
|
+
await upsertPathIndexEntry({
|
|
4859
|
+
repositoryBindingId: ensured.repositoryBindingId,
|
|
4860
|
+
workspaceRoot: cwd2,
|
|
4861
|
+
identity: ensured.identity,
|
|
4862
|
+
homeDir,
|
|
4863
|
+
previousPath: ensured.renamedFrom
|
|
4864
|
+
});
|
|
4865
|
+
const redeemed = await redeemLocalConnectCode(
|
|
4866
|
+
apiUrl,
|
|
4867
|
+
{
|
|
4868
|
+
code,
|
|
4869
|
+
client_redeem_key: clientRedeemKey,
|
|
4870
|
+
repository_binding_id: ensured.repositoryBindingId,
|
|
4871
|
+
root_fingerprint: fingerprint.fingerprint,
|
|
4872
|
+
display_name: displayName,
|
|
4873
|
+
environment,
|
|
4874
|
+
normalized_git_remote: normalizedGitRemote,
|
|
4875
|
+
platform: ensured.identity.platform,
|
|
4876
|
+
package_version: options.packageVersion ?? null,
|
|
4877
|
+
ide_type: options.ideType ?? null
|
|
4878
|
+
},
|
|
4879
|
+
{ fetchImpl: options.fetchImpl }
|
|
4880
|
+
);
|
|
4881
|
+
await updateInstallationCredentials(
|
|
4882
|
+
ensured.repositoryBindingId,
|
|
4883
|
+
{
|
|
4884
|
+
accessToken: redeemed.access_token,
|
|
4885
|
+
refreshToken: redeemed.refresh_token,
|
|
4886
|
+
accessTokenExpiresAt: redeemed.access_token_expires_at ?? void 0,
|
|
4887
|
+
refreshTokenExpiresAt: redeemed.refresh_token_expires_at ?? void 0,
|
|
4888
|
+
installationPublicId: redeemed.installation_public_id,
|
|
4889
|
+
projectId: redeemed.project_id
|
|
4890
|
+
},
|
|
4891
|
+
{ ...options.credentialOptions, clearKeys: ["clientRedeemKey"] }
|
|
4892
|
+
);
|
|
4893
|
+
const connectedRecord = {
|
|
4894
|
+
...pendingRecord,
|
|
4895
|
+
installationPublicId: redeemed.installation_public_id,
|
|
4896
|
+
projectId: redeemed.project_id,
|
|
4897
|
+
status: "connected",
|
|
4898
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4899
|
+
};
|
|
4900
|
+
await writeBindingRecord(connectedRecord, homeDir);
|
|
4901
|
+
const baseSuccess = `Connected repository binding ${ensured.repositoryBindingId}` + (redeemed.recovered ? " (recovered)" : "") + ` to project ${redeemed.project_id}.`;
|
|
4902
|
+
if (options.configureIdes !== false) {
|
|
4903
|
+
const targets = { cursor: true, vscode: true, jetbrains: true };
|
|
4904
|
+
const setup = options.setupIdeFiles ?? runSetupIdeFiles;
|
|
4905
|
+
const cliPath = options.cliPath !== void 0 ? options.cliPath : await resolveBuiltCliPathAsync();
|
|
4906
|
+
let setupResult;
|
|
4907
|
+
try {
|
|
4908
|
+
setupResult = await setup({
|
|
4909
|
+
cwd: cwd2,
|
|
4910
|
+
// Use the exact bound workspace root — do not rediscover via .git / .m1.
|
|
4911
|
+
workspaceRoot: cwd2,
|
|
4912
|
+
targets,
|
|
4913
|
+
force: true,
|
|
4914
|
+
dryRun: false,
|
|
4915
|
+
noGitignore: true,
|
|
4916
|
+
skipDaemonCleanup: true,
|
|
4917
|
+
homeDir,
|
|
4918
|
+
cursorEnvironment: "local",
|
|
4919
|
+
devMode: true,
|
|
4920
|
+
// Propagate the same backend endpoint used for redeem into all IDE configs.
|
|
4921
|
+
apiUrl,
|
|
4922
|
+
cursorLocalCliPathOverride: cliPath,
|
|
4923
|
+
cliPathOverride: cliPath,
|
|
4924
|
+
...options.setupIdeOptions
|
|
4925
|
+
});
|
|
4926
|
+
} catch (err) {
|
|
4927
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
4928
|
+
setupResult = {
|
|
4929
|
+
exitCode: 1,
|
|
4930
|
+
repoRoot: cwd2,
|
|
4931
|
+
outcomes: {},
|
|
4932
|
+
error: message
|
|
4933
|
+
};
|
|
4934
|
+
}
|
|
4935
|
+
if (setupResult.exitCode !== 0) {
|
|
4936
|
+
const detail = setupResult.error ?? "unknown IDE setup error";
|
|
4937
|
+
const repair = formatIdeSetupRepairHint(cwd2, cliPath);
|
|
4938
|
+
return {
|
|
4939
|
+
exitCode: 1,
|
|
4940
|
+
repositoryBindingId: ensured.repositoryBindingId,
|
|
4941
|
+
projectId: redeemed.project_id,
|
|
4942
|
+
installationPublicId: redeemed.installation_public_id,
|
|
4943
|
+
recovered: redeemed.recovered,
|
|
4944
|
+
createdBinding: ensured.created,
|
|
4945
|
+
legacyM1WarningPath: ensured.legacyM1WarningPath,
|
|
4946
|
+
ideSetupError: detail,
|
|
4947
|
+
message: `Repository connection exists for binding ${ensured.repositoryBindingId} (project ${redeemed.project_id}), but IDE configuration failed: ${detail}. Credentials and binding were kept. ${repair}`
|
|
4948
|
+
};
|
|
4949
|
+
}
|
|
4950
|
+
}
|
|
4951
|
+
return {
|
|
4952
|
+
exitCode: 0,
|
|
4953
|
+
repositoryBindingId: ensured.repositoryBindingId,
|
|
4954
|
+
projectId: redeemed.project_id,
|
|
4955
|
+
installationPublicId: redeemed.installation_public_id,
|
|
4956
|
+
recovered: redeemed.recovered,
|
|
4957
|
+
createdBinding: ensured.created,
|
|
4958
|
+
legacyM1WarningPath: ensured.legacyM1WarningPath,
|
|
4959
|
+
message: baseSuccess
|
|
4960
|
+
};
|
|
4961
|
+
}
|
|
4962
|
+
function parseConnectArgv(argv) {
|
|
4963
|
+
let code;
|
|
4964
|
+
let apiUrl;
|
|
4965
|
+
for (let i = 0; i < argv.length; i++) {
|
|
4966
|
+
const a = argv[i];
|
|
4967
|
+
if (a === "--api-url") {
|
|
4968
|
+
const value = argv[++i];
|
|
4969
|
+
if (!value || value.startsWith("-")) {
|
|
4970
|
+
return { error: "Usage: memoraone-mcp connect <code> [--api-url <url>]" };
|
|
4971
|
+
}
|
|
4972
|
+
apiUrl = value;
|
|
4973
|
+
continue;
|
|
4974
|
+
}
|
|
4975
|
+
if (a.startsWith("--api-url=")) {
|
|
4976
|
+
const value = a.slice("--api-url=".length);
|
|
4977
|
+
if (!value) {
|
|
4978
|
+
return { error: "Usage: memoraone-mcp connect <code> [--api-url <url>]" };
|
|
4979
|
+
}
|
|
4980
|
+
apiUrl = value;
|
|
4981
|
+
continue;
|
|
4982
|
+
}
|
|
4983
|
+
if (a.startsWith("-")) {
|
|
4984
|
+
return { error: `Unknown connect option: ${a}` };
|
|
4985
|
+
}
|
|
4986
|
+
if (!code) {
|
|
4987
|
+
code = a;
|
|
4988
|
+
continue;
|
|
4989
|
+
}
|
|
4990
|
+
return { error: "Usage: memoraone-mcp connect <code> [--api-url <url>]" };
|
|
4991
|
+
}
|
|
4992
|
+
return { code, apiUrl };
|
|
4993
|
+
}
|
|
4994
|
+
async function cliConnect(argv) {
|
|
4995
|
+
const parsed2 = parseConnectArgv(argv);
|
|
4996
|
+
if (parsed2.error) {
|
|
4997
|
+
process.stderr.write(`${parsed2.error}
|
|
4998
|
+
`);
|
|
4999
|
+
return 1;
|
|
5000
|
+
}
|
|
5001
|
+
if (!parsed2.code) {
|
|
5002
|
+
process.stderr.write("Usage: memoraone-mcp connect <code> [--api-url <url>]\n");
|
|
5003
|
+
return 1;
|
|
5004
|
+
}
|
|
5005
|
+
try {
|
|
5006
|
+
const result = await runConnectCommand({
|
|
5007
|
+
code: parsed2.code,
|
|
5008
|
+
cwd: process.cwd(),
|
|
5009
|
+
apiUrl: parsed2.apiUrl,
|
|
5010
|
+
packageVersion: process.env.npm_package_version ?? null
|
|
5011
|
+
});
|
|
5012
|
+
if (result.exitCode === 0) {
|
|
5013
|
+
process.stdout.write(`${result.message}
|
|
5014
|
+
`);
|
|
5015
|
+
} else {
|
|
5016
|
+
process.stderr.write(`[memoraone-mcp] ${result.message}
|
|
5017
|
+
`);
|
|
5018
|
+
}
|
|
5019
|
+
return result.exitCode;
|
|
5020
|
+
} catch (err) {
|
|
5021
|
+
process.stderr.write(`[memoraone-mcp] connect failed: ${String(err)}
|
|
5022
|
+
`);
|
|
5023
|
+
return 1;
|
|
5024
|
+
}
|
|
5025
|
+
}
|
|
5026
|
+
|
|
3542
5027
|
// src/cli.ts
|
|
3543
5028
|
var { version } = require_package();
|
|
3544
5029
|
var args = process.argv.slice(2);
|
|
@@ -3548,7 +5033,7 @@ if (args.includes("--version") || args.includes("-v")) {
|
|
|
3548
5033
|
}
|
|
3549
5034
|
if (args.includes("--help") || args.includes("-h")) {
|
|
3550
5035
|
console.log(
|
|
3551
|
-
"Usage: memoraone-mcp [--version] [--help] [--daemon --
|
|
5036
|
+
"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
5037
|
);
|
|
3553
5038
|
process.exit(0);
|
|
3554
5039
|
}
|
|
@@ -3558,6 +5043,12 @@ if (args[0] === "cleanup") {
|
|
|
3558
5043
|
`);
|
|
3559
5044
|
process.exit(1);
|
|
3560
5045
|
});
|
|
5046
|
+
} else if (args[0] === "connect") {
|
|
5047
|
+
cliConnect(args.slice(1)).then((code) => process.exit(code)).catch((err) => {
|
|
5048
|
+
process.stderr.write(`[memoraone-mcp] connect fatal: ${String(err)}
|
|
5049
|
+
`);
|
|
5050
|
+
process.exit(1);
|
|
5051
|
+
});
|
|
3561
5052
|
} else if (args[0] === "setup-ide-files") {
|
|
3562
5053
|
cliSetupIdeFiles(args.slice(1)).then((code) => process.exit(code)).catch((err) => {
|
|
3563
5054
|
process.stderr.write(`[memoraone-mcp] setup-ide-files fatal: ${String(err)}
|
|
@@ -3571,7 +5062,9 @@ if (args[0] === "cleanup") {
|
|
|
3571
5062
|
process.exit(1);
|
|
3572
5063
|
});
|
|
3573
5064
|
} else {
|
|
3574
|
-
runBridgeProxy({ cliPath: process.argv[1] }).
|
|
5065
|
+
runBridgeProxy({ cliPath: process.argv[1] }).then(() => {
|
|
5066
|
+
process.exit(0);
|
|
5067
|
+
}).catch((err) => {
|
|
3575
5068
|
process.stderr.write(`[memoraone-mcp][bridge] fatal: ${String(err)}
|
|
3576
5069
|
`);
|
|
3577
5070
|
process.exit(1);
|