@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.
Files changed (4) hide show
  1. package/dist/cli.cjs +2040 -547
  2. package/dist/daemon.cjs +1730 -1114
  3. package/dist/index.cjs +1654 -959
  4. 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.35",
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 import_node_child_process3 = require("child_process");
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(projectId, workspaceRoot, ideType) {
79
+ function hashBindingIdentity(repositoryBindingId, workspaceRoot, ideType) {
79
80
  const input2 = [
80
- projectId.trim().toLowerCase(),
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) && path.resolve(a.m1Path) === path.resolve(b.m1Path) && (a.apiKey ?? null) === (b.apiKey ?? null) && envA === envB;
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 fs3 = __toESM(require("fs"), 1);
118
- var path4 = __toESM(require("path"), 1);
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 uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
124
- var CANONICAL_M1_FILENAME = "memoraone.m1";
125
- function isCanonicalM1Path(m1Path) {
126
- return path2.basename(m1Path) === CANONICAL_M1_FILENAME;
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
- parsed = JSON.parse(content);
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 resolveProjectIdFromExplicitM1Path() {
155
- const raw = process.env.MEMORAONE_M1_PATH;
156
- if (raw === void 0 || raw.trim() === "") {
157
- return null;
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
- const markerPath = path2.resolve(raw);
168
+ }
169
+ async function readJsonFile(filePath) {
160
170
  try {
161
- const content = await fs.readFile(markerPath, "utf8");
162
- const { projectId, apiKey, environment } = parseAndValidateM1(content, markerPath);
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 findM1WalkingUp(workspaceRoot) {
172
- let current = path2.resolve(workspaceRoot);
173
- while (true) {
174
- const markerPath = path2.join(current, CANONICAL_M1_FILENAME);
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
- const content = await fs.readFile(markerPath, "utf8");
177
- const { projectId, apiKey, environment } = parseAndValidateM1(content, markerPath);
178
- const repoRoot = path2.dirname(markerPath);
179
- return environment === void 0 ? { projectId, apiKey, repoRoot, markerPath } : { projectId, apiKey, environment, repoRoot, markerPath };
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 !== "ENOENT") {
182
- throw err;
362
+ if (err?.code === "EEXIST") {
363
+ retries += 1;
364
+ if (retries > maxRetries) {
365
+ throw new Error(
366
+ `[memoraone-mcp] Failed to acquire lock ${lockName} after ${maxRetries} retries`
367
+ );
368
+ }
369
+ await new Promise((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
- return null;
375
+ throw new Error(`[memoraone-mcp] Failed to acquire lock ${lockName}`);
192
376
  }
193
- function normalizeWorkspaceSearchRoots(workspaceRoot) {
194
- if (workspaceRoot === void 0) {
195
- return [];
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 raw of list) {
201
- if (raw === void 0) {
202
- continue;
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
- const trimmed = String(raw).trim();
205
- if (trimmed === "") {
206
- continue;
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
- const resolved = path2.resolve(trimmed);
209
- if (!seen.has(resolved)) {
210
- seen.add(resolved);
211
- out.push(resolved);
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
- return out;
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
- function resolveApiKeyWithSource(fileApiKey) {
217
- const envApiKey = process.env.MEMORAONE_API_KEY?.trim();
218
- if (envApiKey) {
219
- return { apiKey: envApiKey, apiKeySource: "env" };
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
- const aliasEnvApiKey = process.env.MEMORA_API_KEY?.trim();
222
- if (aliasEnvApiKey) {
223
- return { apiKey: aliasEnvApiKey, apiKeySource: "env" };
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
- if (fileApiKey) {
226
- return { apiKey: fileApiKey, apiKeySource: "memoraone.m1" };
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
- return { apiKey: null, apiKeySource: "none" };
692
+ throw new Error(`[memoraone-mcp] Unsupported platform for device ID: ${platform2}`);
229
693
  }
230
- async function resolveAuthoritativeBinding(workspaceRoot, options = {}) {
231
- const respectExplicitM1Path = options.respectExplicitM1Path !== false;
232
- if (respectExplicitM1Path) {
233
- const explicitBinding = await resolveProjectIdFromExplicitM1Path();
234
- if (explicitBinding) {
235
- const resolved = resolveApiKeyWithSource(explicitBinding.apiKey);
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
- projectId: explicitBinding.projectId,
238
- workspaceRoot: path2.dirname(explicitBinding.foundAt),
239
- m1Path: explicitBinding.foundAt,
240
- apiKey: resolved.apiKey,
241
- ...explicitBinding.environment !== void 0 ? { environment: explicitBinding.environment } : {},
242
- bindingSource: "explicit-m1-path",
243
- apiKeySource: resolved.apiKeySource
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 Error("Could not find memoraone.m1 in workspace.\nOpen a folder containing memoraone.m1.");
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
- const binding = await findM1WalkingUp(root);
254
- if (binding) {
255
- const resolved = resolveApiKeyWithSource(binding.apiKey);
256
- bindings.push({
257
- projectId: binding.projectId,
258
- workspaceRoot: binding.repoRoot,
259
- m1Path: binding.markerPath,
260
- apiKey: resolved.apiKey,
261
- ...binding.environment !== void 0 ? { environment: binding.environment } : {},
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 Error("Could not find memoraone.m1 in workspace.\nOpen a folder containing memoraone.m1.");
1129
+ throw new ReconnectRequiredError(
1130
+ "[memoraone-mcp] No local MemoraOne binding for this workspace.\nRun: memoraone-mcp connect <code>"
1131
+ );
269
1132
  }
270
- const distinctProjectIds = new Set(bindings.map((b) => b.projectId));
271
- if (distinctProjectIds.size > 1) {
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} project=${b.projectId} m1=${b.m1Path}`
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 MemoraOne projects.\n" + lines.join("\n") + "\nOpen one repo per Cursor window, or use repo-scoped .cursor/mcp.json from setup-ide-files --cursor."
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() && path2.resolve(a.workspaceRoot) === path2.resolve(b.workspaceRoot) && path2.resolve(a.m1Path) === path2.resolve(b.m1Path) && (a.apiKey ?? null) === (b.apiKey ?? null) && envA === envB;
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 m1Path = path2.resolve(cached.m1Path);
288
- if (cached.bindingSource !== "explicit-m1-path" && !isCanonicalM1Path(m1Path)) {
289
- throw new Error(
290
- `[memoraone-mcp] Cached binding m1Path is not the canonical ${CANONICAL_M1_FILENAME}: ${m1Path}`
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
- let content;
294
- try {
295
- content = await fs.readFile(m1Path, "utf8");
296
- } catch (err) {
297
- if (err?.code === "ENOENT") {
298
- throw new Error(
299
- `[memoraone-mcp] Cached binding file missing at ${m1Path}. Open a folder containing ${CANONICAL_M1_FILENAME}.`
300
- );
301
- }
302
- throw err;
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
- projectId: parsed.projectId,
308
- workspaceRoot: path2.resolve(path2.dirname(m1Path)),
309
- m1Path,
310
- apiKey: resolved.apiKey,
311
- ...parsed.environment !== void 0 ? { environment: parsed.environment } : {},
312
- bindingSource: cached.bindingSource,
313
- apiKeySource: resolved.apiKeySource
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
- return Buffer.from(JSON.stringify(binding), "utf8").toString("base64");
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 parsed;
1218
+ let parsed2;
328
1219
  try {
329
- parsed = JSON.parse(Buffer.from(value, "base64").toString("utf8"));
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
- const projectId = parsed?.projectId;
334
- const workspaceRoot = parsed?.workspaceRoot;
335
- const m1Path = parsed?.m1Path;
336
- const apiKey = parsed?.apiKey;
337
- const environment = normalizeEnvironment(parsed?.environment);
338
- const bindingSource = parsed?.bindingSource;
339
- const apiKeySource = parsed?.apiKeySource;
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 (!m1Path || typeof m1Path !== "string") {
347
- throw new Error("[memoraone-mcp] Invalid binding m1Path");
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 !== "explicit-m1-path" && bindingSource !== "workspace-search") {
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
- m1Path,
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
- apiKeySource
1255
+ bindingSource: "local-binding",
1256
+ status
366
1257
  };
367
1258
  }
368
1259
 
369
1260
  // src/socketPaths.ts
370
- var os = __toESM(require("os"), 1);
371
- var path3 = __toESM(require("path"), 1);
372
- var fs2 = __toESM(require("fs"), 1);
373
- var BASE_DIR = process.env.MEMORAONE_MCP_LOCK_DIR || path3.join(os.homedir(), ".memoraone-mcp");
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(env = process.env) {
392
- return parseIdeType(env.MEMORAONE_IDE_TYPE);
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, projectId, env = process.env) {
399
- const args2 = [scriptPath, "--daemon", "--project-id", projectId];
400
- const ideType = resolveIdeTypeFromEnv(env);
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(env = process.env) {
407
- return resolveIdeTypeFromEnv(env) ?? "";
1297
+ function resolveBindingIdeType(env2 = process.env) {
1298
+ return resolveIdeTypeFromEnv(env2) ?? "";
408
1299
  }
409
- function getBindingSocketFilename(binding, env = process.env) {
410
- const ideType = resolveBindingIdeType(env);
411
- const hash = hashBindingIdentity(binding.projectId, binding.workspaceRoot, ideType);
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, env = process.env) {
415
- return path3.join(BASE_DIR, getBindingSocketFilename(binding, env));
1305
+ function getBindingSocketPath(binding, env2 = process.env) {
1306
+ return path9.join(BASE_DIR, getBindingSocketFilename(binding, env2));
416
1307
  }
417
1308
  function ensureBaseDir() {
418
- fs2.mkdirSync(BASE_DIR, { recursive: true });
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(path3.basename(filename));
1313
+ return HASH_SOCKET_FILENAME_RE.test(path9.basename(filename));
423
1314
  }
424
1315
  function isLegacySocketFilename(filename) {
425
- return LEGACY_SOCKET_FILENAME_RE.test(path3.basename(filename));
1316
+ return LEGACY_SOCKET_FILENAME_RE.test(path9.basename(filename));
426
1317
  }
427
1318
  function isMemoraoneSocketFilename(filename) {
428
- const base = path3.basename(filename);
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 = path3.basename(filename).match(LEGACY_SOCKET_PROJECT_ID_RE);
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 = path3.basename(filename).match(LEGACY_SOCKET_FILENAME_RE);
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 parsed = JSON.parse(raw);
468
- if (!parsed?.binding) {
1358
+ const parsed2 = JSON.parse(raw);
1359
+ if (!parsed2?.binding) {
469
1360
  return null;
470
1361
  }
471
- const binding = decodeResolvedBinding(parsed.binding);
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 parsed.v === "number" ? parsed.v : 1,
474
- ...parsed.ideType ? { ideType: parsed.ideType } : {},
475
- projectId: parsed.projectId ?? binding.projectId,
476
- workspaceRoot: parsed.workspaceRoot ?? binding.workspaceRoot,
477
- m1Path: parsed.m1Path ?? binding.m1Path,
478
- binding: parsed.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 = fs3.readFileSync(bindingSidecarPath(socketPath), "utf8");
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
- fs3.unlinkSync(bindingSidecarPath(socketPath));
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
- fs3.unlinkSync(socketPath);
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 ${path4.basename(socketPath)}.`,
518
- ` socket: project=${sidecar.projectId} workspace=${sidecar.workspaceRoot} m1=${sidecar.m1Path}`,
519
- ` session: project=${expected.projectId} workspace=${expected.workspaceRoot} m1=${expected.m1Path}`
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 the current memoraone.m1.");
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, env = process.env) {
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(env);
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(env = process.env) {
554
- return TRUTHY.has(String(env.MEMORAONE_DEBUG_INIT ?? "").trim().toLowerCase());
1451
+ function isInitializeDebugEnabled(env2 = process.env) {
1452
+ return TRUTHY.has(String(env2.MEMORAONE_DEBUG_INIT ?? "").trim().toLowerCase());
555
1453
  }
556
- function logInitializeDebug(log, env, msg) {
557
- if (isInitializeDebugEnabled(env)) {
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((resolve9) => {
634
- this.waiters.push(resolve9);
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 env = options.env ?? process.env;
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
- env,
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, env, `ignored non-JSON line while waiting for roots/list: ${String(err)}`);
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
- env,
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, env, `roots/list id=${requestId} returned ${uris.length}: ${JSON.stringify(uris)}`);
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 fs5 = __toESM(require("fs/promises"), 1);
708
- var path7 = __toESM(require("path"), 1);
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 import_node_child_process2 = require("child_process");
711
- var import_node_util2 = require("util");
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 fs4 = __toESM(require("fs/promises"), 1);
716
- var os2 = __toESM(require("os"), 1);
717
- var path6 = __toESM(require("path"), 1);
718
- var import_node_child_process = require("child_process");
719
- var import_node_util = require("util");
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 path5 = __toESM(require("path"), 1);
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(env = process.env) {
726
- const ideType = resolveIdeTypeFromEnv(env);
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(path5.delimiter).map((s) => s.trim()).filter(Boolean)) {
743
- parts.push(path5.resolve(p));
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 = path5.resolve(uriToPath(uri));
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(env = process.env) {
785
- const raw = env[MEMORAONE_WORKSPACE_ROOT_ENV];
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 path5.resolve(raw.trim());
1687
+ return path11.resolve(raw.trim());
790
1688
  }
791
1689
  function formatWorkspaceAmbiguityError(bindings) {
792
1690
  const lines = bindings.map(
793
- (b) => ` - workspace=${b.workspaceRoot} project=${b.projectId} m1=${b.m1Path}`
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: project=${initializeBinding.projectId} workspace=${initializeBinding.workspaceRoot} m1=${initializeBinding.m1Path}
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("Could not find memoraone.m1 in workspace.\nOpen a folder containing memoraone.m1.");
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("Could not find memoraone.m1")) {
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("Could not find memoraone.m1 in workspace.\nOpen a folder containing memoraone.m1.");
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 env = options.env ?? process.env;
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(env);
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 = path5.resolve(repoHint);
862
- const matchingRoot = rootsListPaths.find((root) => path5.resolve(root) === hintResolved);
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 execFileAsync = (0, import_node_util.promisify)(import_node_child_process.execFile);
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 cursorMcpApiUrl(environment) {
895
- if (environment === "local") return MEMORAONE_LOCAL_API_URL;
896
- if (environment === "staging") return MEMORAONE_STAGING_API_URL;
897
- return MEMORAONE_PROD_API_URL;
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 env = {
901
- MEMORAONE_API_URL: cursorMcpApiUrl(options.environment),
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
- env[MEMORAONE_WORKSPACE_ROOT_ENV] = path6.resolve(options.workspaceRoot);
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 fs4.access(filePath);
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 [path6.join(homeDir, ".cursor", "mcp.json")];
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 ?? os2.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 fs4.access(npxPath, fs4.constants.X_OK);
1888
+ await fs9.access(npxPath, fs9.constants.X_OK);
980
1889
  } catch {
981
1890
  return false;
982
1891
  }
983
1892
  }
984
- await execFileAsync(npxPath, ["--version"], { timeout: 1e4 });
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(path6.join(dir, npxName));
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 execFileAsync(lookupCmd, [npxName], { timeout: 5e3 });
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 = path6.isAbsolute(candidate) ? candidate : path6.resolve(candidate);
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 env = s.env;
1044
- if (!env || typeof env !== "object") return false;
1045
- return memoraoneEnvMatchesManagedCleanupShape(env);
1953
+ const env2 = s.env;
1954
+ if (!env2 || typeof env2 !== "object") return false;
1955
+ return memoraoneEnvMatchesManagedCleanupShape(env2);
1046
1956
  }
1047
- function cursorConfigHasManagedMemoraone(parsed) {
1048
- if (!parsed || typeof parsed !== "object") return false;
1049
- const mcpServers = parsed.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(env) {
1054
- return env.MEMORAONE_IDE_TYPE === "cursor" && isMemoraoneManagedApiUrl(env.MEMORAONE_API_URL);
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 path6.join(repoRoot, ".cursor", "mcp.json");
1967
+ return path12.join(repoRoot, ".cursor", "mcp.json");
1058
1968
  }
1059
1969
  async function readCursorMcpConfigObject(configPath) {
1060
1970
  try {
1061
- const raw = await fs4.readFile(configPath, "utf8");
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 parsed = await readCursorMcpConfigObject(configPath);
1072
- if (!parsed || !cursorConfigHasManagedMemoraone(parsed)) {
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 fs4.copyFile(configPath, backupPath);
1080
- const mcpServers = typeof parsed.mcpServers === "object" && parsed.mcpServers !== null && !Array.isArray(parsed.mcpServers) ? { ...parsed.mcpServers } : {};
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 fs4.unlink(configPath);
1994
+ await fs9.unlink(configPath);
1085
1995
  return { changed: true, backupPath };
1086
1996
  }
1087
- const next = { ...parsed, mcpServers };
1088
- await fs4.mkdir(path6.dirname(configPath), { recursive: true });
1089
- await fs4.writeFile(configPath, JSON.stringify(next, null, 2) + "\n", "utf8");
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 = path6.resolve(options?.repoRoot ?? process.cwd());
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 ?? os2.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 execFileAsync2 = (0, import_node_util2.promisify)(import_node_child_process2.execFile);
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 folder containing memoraone.m1.";
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 execFileAsync2("ps", ["-eo", "pid=,args="], {
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 fs5.readdir(baseDir);
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 = path7.join(baseDir, name);
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 defaultListSocketPathsForM1Path(m1Path) {
1303
- const resolvedM1 = path7.resolve(m1Path);
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 fs5.readdir(baseDir);
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 = path7.join(baseDir, name);
2230
+ const socketPath = path13.join(baseDir, name);
1321
2231
  const record = readBindingSidecarRecord(socketPath);
1322
- if (!record?.m1Path) continue;
1323
- if (path7.resolve(record.m1Path) === resolvedM1) {
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, m1Path) {
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 resolvedM1 = m1Path ? path7.resolve(m1Path) : null;
2242
+ const resolvedRoot = workspaceRoot ? path13.resolve(workspaceRoot) : null;
1333
2243
  const filtered = [];
1334
2244
  for (const socketPath of socketPaths) {
1335
- const basename5 = path7.basename(socketPath);
1336
- if (isLegacySocketFilename(basename5)) {
1337
- if (isSocketFilenameForProjectAndIde(basename5, normalizedProjectId, ide)) {
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(basename5)) {
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 sameM1 = resolvedM1 !== null && path7.resolve(record.m1Path) === resolvedM1;
1347
- if (sameProject || sameM1) {
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 fs5.unlink(socketPath);
2268
+ await fs10.unlink(socketPath);
1359
2269
  try {
1360
- await fs5.unlink(bindingSidecarPath(socketPath));
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(cwd) {
2286
+ async function resolveCleanupTarget(cwd2) {
1377
2287
  try {
1378
- const binding = await resolveAuthoritativeBinding([path7.resolve(cwd)]);
2288
+ const binding = await resolveAuthoritativeBinding([path13.resolve(cwd2)]);
1379
2289
  return {
1380
2290
  workspaceRoot: binding.workspaceRoot,
1381
- m1Path: binding.m1Path,
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 m1Path;
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
- m1Path = target.m1Path;
2381
+ repositoryBindingId = target.repositoryBindingId;
1472
2382
  cleanupLog(opts, `${prefix} Workspace root: ${workspaceRoot}`);
1473
- cleanupLog(opts, `${prefix} memoraone.m1: ${m1Path}`);
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 (m1Path && !opts.allProjects) {
1519
- const m1Sockets = await defaultListSocketPathsForM1Path(m1Path);
2428
+ if (workspaceRoot && !opts.allProjects) {
2429
+ const workspaceSockets = await defaultListSocketPathsForWorkspaceRoot(workspaceRoot);
1520
2430
  const seen = new Set(allSocketPaths);
1521
- for (const socketPath of m1Sockets) {
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 m1=${m1Path})`
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(allSocketPaths, targetProjectId, opts.ide, m1Path);
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(path7.basename(socketPath));
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
- m1Path,
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
- m1Path,
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
- m1Path,
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((resolve9, reject) => {
2652
+ return new Promise((resolve16, reject) => {
1738
2653
  const tryConnect = (attempt) => {
1739
- connect2(socketPath).then(resolve9).catch((err) => {
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, env = process.env, options = {}) {
1752
- const bridgeOptions = getBridgeBindingResolveOptions(env);
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, env, log) {
1762
- const ideType = resolveBindingIdeType(env);
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 from ${sessionBinding.m1Path}: project=${sessionBinding.projectId}`
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 memoraone.m1");
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((resolve9, reject) => {
1856
- const socket = net.connect(socketPath, () => resolve9(socket));
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, import_node_child_process3.spawn)(
2775
+ const child = (0, import_node_child_process4.spawn)(
1861
2776
  process.execPath,
1862
- buildDaemonSpawnArgs(this.cliPath, binding.projectId, this.env),
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 project=${binding.projectId} workspace=${binding.workspaceRoot} m1=${binding.m1Path} source=${binding.bindingSource} apiKeySource=${binding.apiKeySource}${environmentLog}`
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
- while (true) {
2074
- const line = await lineReader.readLine();
2075
- if (line === null) {
2076
- break;
2077
- }
2078
- const trimmed = line.trim();
2079
- if (trimmed === "") {
2080
- continue;
2081
- }
2082
- let message;
2083
- try {
2084
- message = JSON.parse(trimmed);
2085
- } catch (err) {
2086
- throw new Error(`[memoraone-mcp] Invalid JSON-RPC on stdin: ${String(err)}`);
2087
- }
2088
- if (message.method === "initialize") {
2089
- log("resolve binding from initialize request before daemon connect");
2090
- const params = message.params ?? {};
2091
- await router.ensureDaemonForInitialize(params);
2092
- await router.forwardInitializeToDaemon(trimmed);
2093
- await router.replayDeferredClientMessages();
2094
- continue;
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
- await router.writeToDaemon(trimmed);
3027
+ } finally {
3028
+ router.close();
2097
3029
  }
2098
3030
  }
2099
3031
 
2100
3032
  // src/setupIdeFiles.ts
2101
- var fs8 = __toESM(require("fs/promises"), 1);
2102
- var os4 = __toESM(require("os"), 1);
2103
- var path10 = __toESM(require("path"), 1);
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 fs6 = __toESM(require("fs/promises"), 1);
2107
- var os3 = __toESM(require("os"), 1);
2108
- var path8 = __toESM(require("path"), 1);
2109
- var import_node_child_process4 = require("child_process");
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 fs6.access(filePath);
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 path8.join(homeDir, ".ai", "mcp", "mcp.json");
3085
+ return path14.join(homeDir, ".ai", "mcp", "mcp.json");
2139
3086
  }
2140
3087
  function getJetBrainsProjectMcpConfigPaths(repoRoot) {
2141
3088
  return [
2142
- { kind: "project-ai", path: path8.join(repoRoot, ".ai", "mcp", "mcp.json") },
2143
- { kind: "project-ij", path: path8.join(repoRoot, ".ij", "mcp", "mcp.json") }
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 stat2 = await fs6.stat(filePath);
2155
- return stat2.size === 0;
3101
+ const stat4 = await fs11.stat(filePath);
3102
+ return stat4.size === 0;
2156
3103
  }
2157
3104
  function buildMemoraoneJetBrainsMcpServer(options) {
2158
- const env = {
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
- MEMORAONE_M1_PATH: options.m1Path
3108
+ [MEMORAONE_WORKSPACE_ROOT_ENV]: path14.resolve(options.workspaceRoot)
2162
3109
  };
2163
3110
  if (options.devMode) {
2164
- env.MEMORAONE_DEV_MODE = "1";
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 env = s.env;
2187
- if (!env || typeof env !== "object") return false;
2188
- const e = env;
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(parsed, expected) {
2198
- if (!parsed || typeof parsed !== "object") {
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 = parsed.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 fs6.readFile(filePath, "utf8");
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 fs6.copyFile(filePath, backupPath);
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 fs6.unlink(filePath);
3180
+ await fs11.unlink(filePath);
2231
3181
  return { repaired: true, backupPath };
2232
3182
  }
2233
- function configHasMemoraone(parsed) {
2234
- if (!parsed) return false;
2235
- const mcpServers = parsed.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 parsed = null;
3194
+ let parsed2 = null;
2245
3195
  try {
2246
- parsed = await readJsonConfig(configPath);
3196
+ parsed2 = await readJsonConfig(configPath);
2247
3197
  } catch {
2248
3198
  return { changed: false };
2249
3199
  }
2250
- if (!configHasMemoraone(parsed)) {
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 = parsed && typeof parsed.mcpServers === "object" && parsed.mcpServers !== null && !Array.isArray(parsed.mcpServers) ? { ...parsed.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 fs6.unlink(configPath);
3211
+ await fs11.unlink(configPath);
2262
3212
  return { changed: true, backupPath };
2263
3213
  }
2264
- const next = { ...parsed, mcpServers };
2265
- await fs6.mkdir(path8.dirname(configPath), { recursive: true });
2266
- await fs6.writeFile(configPath, JSON.stringify(next, null, 2) + "\n", "utf8");
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] ? path8.dirname(path8.resolve(process.argv[1])) : process.cwd();
3220
+ const here = process.argv[1] ? path14.dirname(path14.resolve(process.argv[1])) : process.cwd();
2271
3221
  const candidates = [
2272
- path8.join(here, "cli.cjs"),
2273
- path8.join(here, "..", "dist", "cli.cjs"),
2274
- path8.join(here, "..", "..", "dist", "cli.cjs")
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 path8.resolve(candidate);
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
- m1Path: options.m1Path,
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
- m1Path: options.m1Path,
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((resolve9) => {
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
- resolve9({ ok, detail });
3281
+ resolve16({ ok, detail });
2331
3282
  };
2332
- const child = (0, import_node_child_process4.spawn)(server.command, [...server.args], {
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 ?? os3.homedir();
3348
+ const homeDir = options.homeDir ?? os5.homedir();
2398
3349
  const globalPath = options.globalConfigPath ?? getJetBrainsGlobalMcpConfigPath(homeDir);
2399
- const m1Path = path8.join(path8.resolve(options.repoRoot), "memoraone.m1");
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
- m1Path,
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 fs6.unlink(globalPath);
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 fs6.mkdir(path8.dirname(globalPath), { recursive: true });
2478
- await fs6.writeFile(globalPath, body, "utf8");
2479
- const verifyRaw = await fs6.readFile(globalPath, "utf8");
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 fs7 = __toESM(require("fs/promises"), 1);
2536
- var path9 = __toESM(require("path"), 1);
2537
- var MONOREPO_CLI_REL = path9.join("packages", "mcp", "dist", "cli.cjs");
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 fs7.access(filePath);
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 = path9.resolve(startDir);
2548
- const root = path9.parse(current).root;
3499
+ let current = path15.resolve(startDir);
3500
+ const root = path15.parse(current).root;
2549
3501
  while (true) {
2550
- const candidate = path9.join(current, MONOREPO_CLI_REL);
3502
+ const candidate = path15.join(current, MONOREPO_CLI_REL);
2551
3503
  if (await pathExists3(candidate)) {
2552
- return path9.resolve(candidate);
3504
+ return path15.resolve(candidate);
2553
3505
  }
2554
3506
  if (current === root) break;
2555
- current = path9.dirname(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 = path9.resolve(dir);
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
- const here = process.argv[1] ? path9.dirname(path9.resolve(process.argv[1])) : process.cwd();
2575
- const candidates = [
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 import_node_child_process5 = require("child_process");
3558
+ var import_node_child_process6 = require("child_process");
2590
3559
  var readline4 = __toESM(require("readline/promises"), 1);
2591
- var import_node_util3 = require("util");
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(env = process.env) {
2603
- if (env.CI === "true" || env.CI === "1") return true;
2604
- if (env.GITHUB_ACTIONS === "true" || env.GITHUB_ACTIONS === "1") return true;
2605
- if (env.GITLAB_CI === "true" || env.GITLAB_CI === "1") return true;
2606
- if (env.CIRCLECI === "true" || env.CIRCLECI === "1") return true;
2607
- if (env.BUILDKITE === "true" || env.BUILDKITE === "1") return true;
2608
- if (typeof env.CI === "string" && env.CI.trim() !== "" && env.CI !== "0" && env.CI !== "false") {
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 env = opts.env ?? process.env;
2616
- if (env.NO_COLOR !== void 0) return false;
2617
- if (isCiLikeEnv(env)) return false;
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 env = opts.env ?? process.env;
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 (env.TERM === "dumb") return false;
3595
+ if (env2.TERM === "dumb") return false;
2627
3596
  if (process.platform === "win32") {
2628
3597
  return Boolean(
2629
- env.WT_SESSION || env.WT_PROFILE_ID || env.ConEmuANSI === "ON" || env.TERM_PROGRAM === "vscode" || env.TERM_PROGRAM === "cursor" || typeof env.TERM === "string" && env.TERM !== "" && env.TERM !== "dumb"
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 execFileAsync3 = (0, import_node_util3.promisify)(import_node_child_process5.execFile);
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 = execFileAsync3) {
3728
+ async function openCursorMcpSettingsViaOsascript(execFileImpl = execFileAsync4) {
2760
3729
  await execFileImpl("osascript", ["-e", macosOpenCursorMcpSettingsAppleScript()], {
2761
3730
  timeout: 3e4
2762
3731
  });
2763
3732
  }
2764
- function manualCursorMcpSettingsSteps(platform) {
2765
- const chord = platform === "darwin" ? "Command + Shift + P" : "Ctrl + Shift + P";
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(platform, println = console.log) {
2777
- for (const line of manualCursorMcpSettingsSteps(platform)) {
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 platform = deps.platform ?? process.platform;
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(platform, println);
3762
+ printManualCursorMcpSettingsSteps(platform2, println);
2794
3763
  return;
2795
3764
  }
2796
- if (platform === "darwin") {
3765
+ if (platform2 === "darwin") {
2797
3766
  println(tp.warningLine("macOS may request Automation or Accessibility permission."));
2798
3767
  try {
2799
- const open = deps.openViaOsascript ?? (() => openCursorMcpSettingsViaOsascript(deps.execFile ?? execFileAsync3));
2800
- await open();
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(platform, println);
3782
+ printManualCursorMcpSettingsSteps(platform2, println);
2814
3783
  }
2815
3784
 
2816
3785
  // src/setupIdeFiles.ts
2817
3786
  var MANAGED_MARKER = "<!-- MemoraOne managed IDE helper -->";
2818
- var GITIGNORE_MEMORAONE_COMMENT = "# MemoraOne local project binding / API key";
2819
- var GITIGNORE_MEMORAONE_ENTRY = "memoraone.m1";
2820
- function buildMemoraoneMcpServer(ideType, command = "npx") {
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 = path10.resolve(repoRoot) + path10.sep;
2832
- const normPath = path10.resolve(absPath);
2833
- if (normPath !== path10.resolve(repoRoot) && !normPath.startsWith(normRoot)) {
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 fs8.access(filePath);
3821
+ await fs13.access(filePath);
2840
3822
  return true;
2841
3823
  } catch {
2842
3824
  return false;
2843
3825
  }
2844
3826
  }
2845
- function gitignoreAlreadyIgnoresMemoraoneM1(content) {
2846
- for (const line of content.split(/\r?\n/)) {
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 = path10.resolve(startDir);
2881
- const root = path10.parse(current).root;
3831
+ let current = path16.resolve(startDir);
3832
+ const root = path16.parse(current).root;
2882
3833
  while (true) {
2883
- const gitPath = path10.join(current, ".git");
2884
- const m1Path = path10.join(current, "memoraone.m1");
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 = path10.dirname(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 = path10.join(repoRoot, relPath);
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 fs8.readFile(abs, "utf8");
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 fs8.mkdir(path10.dirname(abs), { recursive: true });
2962
- await fs8.writeFile(abs, fullContent, "utf8");
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 fs8.mkdir(path10.dirname(abs), { recursive: true });
2969
- await fs8.writeFile(abs, fullContent, "utf8");
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 fs8.mkdir(path10.dirname(abs), { recursive: true });
2975
- await fs8.writeFile(abs, fullContent, "utf8");
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 = path10.join(repoRoot, relPath);
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 fs8.readFile(abs, "utf8");
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 fs8.mkdir(path10.dirname(abs), { recursive: true });
2993
- await fs8.writeFile(abs, body, "utf8");
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 parsed = null;
3949
+ let parsed2 = null;
2999
3950
  try {
3000
- parsed = JSON.parse(stripLeadingLineComments3(raw));
3951
+ parsed2 = JSON.parse(stripLeadingLineComments3(raw));
3001
3952
  } catch {
3002
- parsed = null;
3953
+ parsed2 = null;
3003
3954
  }
3004
- if (!parsed && !opts.force) return "skipped-untracked";
3005
- const next = buildBody(parsed);
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 fs8.mkdir(path10.dirname(abs), { recursive: true });
3009
- await fs8.writeFile(abs, next, "utf8");
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
- for (const a of argv) {
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.startsWith("-")) unknown.push(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
- let flagError;
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-m1",
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({ searchFrom: [repoRoot, o.cwd] });
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 ?? os4.homedir();
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 cwd = options.cwd ?? process.cwd();
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 env = openDeps.env ?? process.env;
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 --project-id <uuid> [--ide cursor|copilot-vscode|jetbrains]]\n memoraone-mcp setup-ide-files [--all|--cursor|--vscode|--jetbrains] [--force] [--dry-run] [--no-gitignore] [--cleanup] [--dev] [--repair]\n Cursor API environment (with --cursor or --all): --local (node + built cli.cjs + localhost) | --staging (npx + staging API)\n memoraone-mcp cleanup [--project-id <uuid>] [--ide cursor|copilot-vscode|jetbrains] [--dry-run] [--all-projects] [--yes]"
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] }).catch((err) => {
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);