@memoraone/mcp 0.1.35 → 0.1.37

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