@memoraone/mcp 0.1.38 → 0.1.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.cjs +2091 -398
- package/dist/daemon.cjs +526 -89
- package/dist/index.cjs +525 -88
- package/package.json +11 -11
package/dist/cli.cjs
CHANGED
|
@@ -30,7 +30,7 @@ var require_package = __commonJS({
|
|
|
30
30
|
"package.json"(exports2, module2) {
|
|
31
31
|
module2.exports = {
|
|
32
32
|
name: "@memoraone/mcp",
|
|
33
|
-
version: "0.1.
|
|
33
|
+
version: "0.1.39",
|
|
34
34
|
type: "module",
|
|
35
35
|
main: "dist/index.cjs",
|
|
36
36
|
bin: {
|
|
@@ -56,6 +56,7 @@ var require_package = __commonJS({
|
|
|
56
56
|
"@modelcontextprotocol/sdk": "^1.25.1",
|
|
57
57
|
"@napi-rs/keyring": "1.3.0",
|
|
58
58
|
dotenv: "^16.4.5",
|
|
59
|
+
"smol-toml": "^1.7.1",
|
|
59
60
|
zod: "^4.0.0"
|
|
60
61
|
},
|
|
61
62
|
devDependencies: {
|
|
@@ -366,7 +367,7 @@ async function acquireLocalLock(lockName, options = {}) {
|
|
|
366
367
|
`[memoraone-mcp] Failed to acquire lock ${lockName} after ${maxRetries} retries`
|
|
367
368
|
);
|
|
368
369
|
}
|
|
369
|
-
await new Promise((
|
|
370
|
+
await new Promise((resolve21) => setTimeout(resolve21, retryDelayMs));
|
|
370
371
|
continue;
|
|
371
372
|
}
|
|
372
373
|
throw err;
|
|
@@ -530,6 +531,24 @@ async function keyringGetPassword(repositoryBindingId, options = {}) {
|
|
|
530
531
|
);
|
|
531
532
|
}
|
|
532
533
|
}
|
|
534
|
+
async function keyringDeletePassword(repositoryBindingId, options = {}) {
|
|
535
|
+
const mod = await loadKeyringModule(options.loader);
|
|
536
|
+
const account = keyringAccountForBinding(repositoryBindingId);
|
|
537
|
+
try {
|
|
538
|
+
const entry = new mod.Entry(KEYRING_SERVICE, account);
|
|
539
|
+
entry.deletePassword();
|
|
540
|
+
} catch (err) {
|
|
541
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
542
|
+
if (/NoEntry|not found|no entry/i.test(message)) {
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
if (err instanceof KeyringUnavailableError) throw err;
|
|
546
|
+
throw new KeyringOperationError(
|
|
547
|
+
`[memoraone-mcp] Failed to delete credentials from OS keyring for ${account}`,
|
|
548
|
+
err
|
|
549
|
+
);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
533
552
|
|
|
534
553
|
// src/localState/installationCredentials.ts
|
|
535
554
|
function parsePayload(raw, repositoryBindingId) {
|
|
@@ -618,6 +637,9 @@ async function ensureClientRedeemKey(repositoryBindingId, options = {}) {
|
|
|
618
637
|
);
|
|
619
638
|
return { payload, clientRedeemKey, created: true };
|
|
620
639
|
}
|
|
640
|
+
async function deleteInstallationCredentials(repositoryBindingId, options = {}) {
|
|
641
|
+
await keyringDeletePassword(repositoryBindingId, options);
|
|
642
|
+
}
|
|
621
643
|
function hasUsableAccessToken(payload) {
|
|
622
644
|
return Boolean(payload?.accessToken && payload.accessToken.startsWith("mia_"));
|
|
623
645
|
}
|
|
@@ -855,9 +877,9 @@ var MemoraOneHttpError = class extends Error {
|
|
|
855
877
|
};
|
|
856
878
|
|
|
857
879
|
// src/localState/localConnectClient.ts
|
|
858
|
-
async function requestJson(baseUrl, method,
|
|
880
|
+
async function requestJson(baseUrl, method, path25, options = {}) {
|
|
859
881
|
const fetchImpl = options.fetchImpl ?? fetch;
|
|
860
|
-
const url = `${baseUrl.replace(/\/+$/, "")}${
|
|
882
|
+
const url = `${baseUrl.replace(/\/+$/, "")}${path25.startsWith("/") ? path25 : `/${path25}`}`;
|
|
861
883
|
const res = await fetchImpl(url, {
|
|
862
884
|
method,
|
|
863
885
|
headers: {
|
|
@@ -1266,12 +1288,20 @@ var HASH_SOCKET_FILENAME_RE = new RegExp(
|
|
|
1266
1288
|
`^mcp-[0-9a-f]{${BINDING_SOCKET_HASH_LENGTH}}\\.sock$`,
|
|
1267
1289
|
"i"
|
|
1268
1290
|
);
|
|
1269
|
-
var LEGACY_SOCKET_FILENAME_RE = /^mcp-([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?:-([0-9a-f]{12}))?(?:-(cursor|jetbrains|copilot-vscode))?\.sock$/i;
|
|
1291
|
+
var LEGACY_SOCKET_FILENAME_RE = /^mcp-([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?:-([0-9a-f]{12}))?(?:-(cursor|jetbrains|copilot-vscode|claude-code|windsurf|opencode|codex))?\.sock$/i;
|
|
1270
1292
|
var LEGACY_SOCKET_PROJECT_ID_RE = LEGACY_SOCKET_FILENAME_RE;
|
|
1271
1293
|
function getMcpBaseDir() {
|
|
1272
1294
|
return BASE_DIR;
|
|
1273
1295
|
}
|
|
1274
|
-
var IDE_TYPES = [
|
|
1296
|
+
var IDE_TYPES = [
|
|
1297
|
+
"cursor",
|
|
1298
|
+
"copilot-vscode",
|
|
1299
|
+
"jetbrains",
|
|
1300
|
+
"claude-code",
|
|
1301
|
+
"windsurf",
|
|
1302
|
+
"opencode",
|
|
1303
|
+
"codex"
|
|
1304
|
+
];
|
|
1275
1305
|
var IDE_TYPE_SET = new Set(IDE_TYPES);
|
|
1276
1306
|
function parseIdeType(value) {
|
|
1277
1307
|
if (value === void 0 || value.trim() === "" || !IDE_TYPE_SET.has(value)) {
|
|
@@ -1283,27 +1313,32 @@ function resolveIdeTypeFromEnv(env2 = process.env) {
|
|
|
1283
1313
|
return parseIdeType(env2.MEMORAONE_IDE_TYPE);
|
|
1284
1314
|
}
|
|
1285
1315
|
function parseIdeTypeFromCommandLine(commandLine) {
|
|
1286
|
-
const match = commandLine.match(
|
|
1316
|
+
const match = commandLine.match(
|
|
1317
|
+
/--ide\s+(cursor|copilot-vscode|jetbrains|claude-code|windsurf|opencode|codex)(?:\s|$)/
|
|
1318
|
+
);
|
|
1287
1319
|
return match ? match[1] : void 0;
|
|
1288
1320
|
}
|
|
1289
|
-
function buildDaemonSpawnArgs(scriptPath, repositoryBindingId, env2 = process.env) {
|
|
1321
|
+
function buildDaemonSpawnArgs(scriptPath, repositoryBindingId, env2 = process.env, ideTypeOverride) {
|
|
1290
1322
|
const args2 = [scriptPath, "--daemon", "--binding-id", repositoryBindingId];
|
|
1291
|
-
const ideType = resolveIdeTypeFromEnv(env2);
|
|
1323
|
+
const ideType = ideTypeOverride ?? resolveIdeTypeFromEnv(env2);
|
|
1292
1324
|
if (ideType) {
|
|
1293
1325
|
args2.push("--ide", ideType);
|
|
1294
1326
|
}
|
|
1295
1327
|
return args2;
|
|
1296
1328
|
}
|
|
1297
|
-
function resolveBindingIdeType(env2 = process.env) {
|
|
1329
|
+
function resolveBindingIdeType(env2 = process.env, ideTypeOverride) {
|
|
1330
|
+
if (ideTypeOverride !== void 0) {
|
|
1331
|
+
return ideTypeOverride;
|
|
1332
|
+
}
|
|
1298
1333
|
return resolveIdeTypeFromEnv(env2) ?? "";
|
|
1299
1334
|
}
|
|
1300
|
-
function getBindingSocketFilename(binding, env2 = process.env) {
|
|
1301
|
-
const ideType = resolveBindingIdeType(env2);
|
|
1335
|
+
function getBindingSocketFilename(binding, env2 = process.env, ideTypeOverride) {
|
|
1336
|
+
const ideType = resolveBindingIdeType(env2, ideTypeOverride);
|
|
1302
1337
|
const hash = hashBindingIdentity(binding.repositoryBindingId, binding.workspaceRoot, ideType);
|
|
1303
1338
|
return `mcp-${hash}.sock`;
|
|
1304
1339
|
}
|
|
1305
|
-
function getBindingSocketPath(binding, env2 = process.env) {
|
|
1306
|
-
return path9.join(BASE_DIR, getBindingSocketFilename(binding, env2));
|
|
1340
|
+
function getBindingSocketPath(binding, env2 = process.env, ideTypeOverride) {
|
|
1341
|
+
return path9.join(BASE_DIR, getBindingSocketFilename(binding, env2, ideTypeOverride));
|
|
1307
1342
|
}
|
|
1308
1343
|
function ensureBaseDir() {
|
|
1309
1344
|
fs7.mkdirSync(BASE_DIR, { recursive: true });
|
|
@@ -1419,7 +1454,7 @@ function formatBindingMismatchError(socketPath, sidecar, expected, detail) {
|
|
|
1419
1454
|
lines.push("The bridge will replace this stale daemon automatically from local binding state.");
|
|
1420
1455
|
return lines.join("\n");
|
|
1421
1456
|
}
|
|
1422
|
-
function verifyDaemonSidecarBinding(socketPath, expected, env2 = process.env) {
|
|
1457
|
+
function verifyDaemonSidecarBinding(socketPath, expected, env2 = process.env, ideTypeOverride) {
|
|
1423
1458
|
const record = readBindingSidecarRecord(socketPath);
|
|
1424
1459
|
if (!record) {
|
|
1425
1460
|
return null;
|
|
@@ -1431,7 +1466,7 @@ function verifyDaemonSidecarBinding(socketPath, expected, env2 = process.env) {
|
|
|
1431
1466
|
if (!bindingsMatch(sidecar, expected)) {
|
|
1432
1467
|
throw new Error(formatBindingMismatchError(socketPath, sidecar, expected));
|
|
1433
1468
|
}
|
|
1434
|
-
const expectedIdeType = resolveBindingIdeType(env2);
|
|
1469
|
+
const expectedIdeType = resolveBindingIdeType(env2, ideTypeOverride);
|
|
1435
1470
|
if (record.ideType !== void 0 && record.ideType !== expectedIdeType) {
|
|
1436
1471
|
throw new Error(
|
|
1437
1472
|
formatBindingMismatchError(
|
|
@@ -1528,8 +1563,8 @@ var StdioLineReader = class {
|
|
|
1528
1563
|
if (this.closed) {
|
|
1529
1564
|
return null;
|
|
1530
1565
|
}
|
|
1531
|
-
return new Promise((
|
|
1532
|
-
this.waiters.push(
|
|
1566
|
+
return new Promise((resolve21) => {
|
|
1567
|
+
this.waiters.push(resolve21);
|
|
1533
1568
|
});
|
|
1534
1569
|
}
|
|
1535
1570
|
/** Re-queue lines read during an intermediate protocol step (e.g. roots/list) for the main bridge loop. */
|
|
@@ -1978,19 +2013,24 @@ function resolveIdeApiUrl(options) {
|
|
|
1978
2013
|
return MEMORAONE_LOCAL_API_URL;
|
|
1979
2014
|
}
|
|
1980
2015
|
function buildMemoraoneCursorMcpServer(options) {
|
|
2016
|
+
const ideType = options.ideType ?? "cursor";
|
|
1981
2017
|
const env2 = {
|
|
1982
2018
|
MEMORAONE_API_URL: resolveIdeApiUrl({
|
|
1983
2019
|
environment: options.environment,
|
|
1984
2020
|
apiUrl: options.apiUrl
|
|
1985
|
-
})
|
|
1986
|
-
MEMORAONE_IDE_TYPE: "cursor"
|
|
2021
|
+
})
|
|
1987
2022
|
};
|
|
2023
|
+
if (ideType !== "claude-code") {
|
|
2024
|
+
env2.MEMORAONE_IDE_TYPE = ideType;
|
|
2025
|
+
}
|
|
1988
2026
|
if (options.workspaceRoot !== void 0) {
|
|
1989
2027
|
env2[MEMORAONE_WORKSPACE_ROOT_ENV] = path14.resolve(options.workspaceRoot);
|
|
1990
2028
|
}
|
|
1991
2029
|
if (options.environment === "local") {
|
|
1992
2030
|
if (!options.cliPath) {
|
|
1993
|
-
throw new Error(
|
|
2031
|
+
throw new Error(
|
|
2032
|
+
`[setup-ide-files] Local ${ideType} MCP config requires a built CLI path.`
|
|
2033
|
+
);
|
|
1994
2034
|
}
|
|
1995
2035
|
return {
|
|
1996
2036
|
command: "node",
|
|
@@ -1999,7 +2039,7 @@ function buildMemoraoneCursorMcpServer(options) {
|
|
|
1999
2039
|
};
|
|
2000
2040
|
}
|
|
2001
2041
|
if (!options.npxPath) {
|
|
2002
|
-
throw new Error(
|
|
2042
|
+
throw new Error(`[setup-ide-files] ${ideType} MCP config requires a resolved npx path.`);
|
|
2003
2043
|
}
|
|
2004
2044
|
const channel = options.npmPackageChannel ?? npmPackageChannelFromEnvironment(options.environment);
|
|
2005
2045
|
return {
|
|
@@ -2113,10 +2153,30 @@ function mergeCursorRepoMcpConfigObject(existing, writeOptions) {
|
|
|
2113
2153
|
cliPath: writeOptions.cliPath,
|
|
2114
2154
|
workspaceRoot: writeOptions.repoRoot,
|
|
2115
2155
|
apiUrl: writeOptions.apiUrl,
|
|
2116
|
-
npmPackageChannel: writeOptions.npmPackageChannel
|
|
2156
|
+
npmPackageChannel: writeOptions.npmPackageChannel,
|
|
2157
|
+
ideType: "cursor"
|
|
2158
|
+
});
|
|
2159
|
+
return { ...base, mcpServers };
|
|
2160
|
+
}
|
|
2161
|
+
function mergeClaudeCodeMcpConfigObject(existing, writeOptions) {
|
|
2162
|
+
const environment = writeOptions.environment ?? "production";
|
|
2163
|
+
const base = existing && typeof existing === "object" ? { ...existing } : { mcpServers: {} };
|
|
2164
|
+
const mcpServers = typeof base.mcpServers === "object" && base.mcpServers !== null && !Array.isArray(base.mcpServers) ? { ...base.mcpServers } : {};
|
|
2165
|
+
delete mcpServers.memoraone;
|
|
2166
|
+
mcpServers.memoraone = buildMemoraoneCursorMcpServer({
|
|
2167
|
+
environment,
|
|
2168
|
+
npxPath: writeOptions.npxPath,
|
|
2169
|
+
cliPath: writeOptions.cliPath,
|
|
2170
|
+
workspaceRoot: writeOptions.repoRoot,
|
|
2171
|
+
apiUrl: writeOptions.apiUrl,
|
|
2172
|
+
npmPackageChannel: writeOptions.npmPackageChannel,
|
|
2173
|
+
ideType: "claude-code"
|
|
2117
2174
|
});
|
|
2118
2175
|
return { ...base, mcpServers };
|
|
2119
2176
|
}
|
|
2177
|
+
function getClaudeCodeMcpConfigPath(repoRoot) {
|
|
2178
|
+
return path14.join(path14.resolve(repoRoot), ".mcp.json");
|
|
2179
|
+
}
|
|
2120
2180
|
function isMemoraoneManagedApiUrl(url) {
|
|
2121
2181
|
if (typeof url !== "string" || url.length === 0) return false;
|
|
2122
2182
|
if (url === MEMORAONE_PROD_API_URL) return true;
|
|
@@ -2421,14 +2481,14 @@ async function filterSocketPathsByIdeForCleanup(socketPaths, projectId, ide, wor
|
|
|
2421
2481
|
const resolvedRoot = workspaceRoot ? path15.resolve(workspaceRoot) : null;
|
|
2422
2482
|
const filtered = [];
|
|
2423
2483
|
for (const socketPath of socketPaths) {
|
|
2424
|
-
const
|
|
2425
|
-
if (isLegacySocketFilename(
|
|
2426
|
-
if (isSocketFilenameForProjectAndIde(
|
|
2484
|
+
const basename14 = path15.basename(socketPath);
|
|
2485
|
+
if (isLegacySocketFilename(basename14)) {
|
|
2486
|
+
if (isSocketFilenameForProjectAndIde(basename14, normalizedProjectId, ide)) {
|
|
2427
2487
|
filtered.push(socketPath);
|
|
2428
2488
|
}
|
|
2429
2489
|
continue;
|
|
2430
2490
|
}
|
|
2431
|
-
if (isHashSocketFilename(
|
|
2491
|
+
if (isHashSocketFilename(basename14)) {
|
|
2432
2492
|
const record = readBindingSidecarRecord(socketPath);
|
|
2433
2493
|
if (!record || record.ideType !== ide) continue;
|
|
2434
2494
|
const sameProject = record.projectId.trim().toLowerCase() === normalizedProjectId;
|
|
@@ -2808,6 +2868,179 @@ async function cliCleanup(argv) {
|
|
|
2808
2868
|
return result.exitCode;
|
|
2809
2869
|
}
|
|
2810
2870
|
|
|
2871
|
+
// src/config.ts
|
|
2872
|
+
var process2 = __toESM(require("process"), 1);
|
|
2873
|
+
var fs12 = __toESM(require("fs"), 1);
|
|
2874
|
+
var path16 = __toESM(require("path"), 1);
|
|
2875
|
+
var dotenv = __toESM(require("dotenv"), 1);
|
|
2876
|
+
var import_v4 = require("zod/v4");
|
|
2877
|
+
|
|
2878
|
+
// src/configUtils.ts
|
|
2879
|
+
var DEFAULT_API_URL = "http://localhost:3001";
|
|
2880
|
+
var DEV_API_URL = "http://localhost:3001";
|
|
2881
|
+
function resolveApiUrl(env2) {
|
|
2882
|
+
const explicitUrl = env2.MEMORAONE_API_URL?.trim();
|
|
2883
|
+
if (explicitUrl) {
|
|
2884
|
+
return explicitUrl;
|
|
2885
|
+
}
|
|
2886
|
+
const aliasUrl = env2.MEMORA_API_URL?.trim();
|
|
2887
|
+
if (aliasUrl) {
|
|
2888
|
+
return aliasUrl;
|
|
2889
|
+
}
|
|
2890
|
+
if (env2.MEMORAONE_DEV_MODE === "1") {
|
|
2891
|
+
return DEV_API_URL;
|
|
2892
|
+
}
|
|
2893
|
+
return DEFAULT_API_URL;
|
|
2894
|
+
}
|
|
2895
|
+
|
|
2896
|
+
// src/config.ts
|
|
2897
|
+
var dotenvPath = path16.resolve(process2.cwd(), ".env");
|
|
2898
|
+
if (fs12.existsSync(dotenvPath)) {
|
|
2899
|
+
try {
|
|
2900
|
+
dotenv.config({ path: dotenvPath });
|
|
2901
|
+
} catch (err) {
|
|
2902
|
+
process2.stderr.write("[memoraone-mcp] Failed to load .env: " + String(err) + "\n");
|
|
2903
|
+
}
|
|
2904
|
+
}
|
|
2905
|
+
var EnvSchema = import_v4.z.object({
|
|
2906
|
+
MEMORAONE_API_URL: import_v4.z.string().url().optional(),
|
|
2907
|
+
MEMORAONE_API_KEY: import_v4.z.string().min(1).optional(),
|
|
2908
|
+
MEMORAONE_DEV_MODE: import_v4.z.string().min(1).optional(),
|
|
2909
|
+
MEMORAONE_AGENT_NAME: import_v4.z.string().min(1).optional(),
|
|
2910
|
+
MEMORAONE_AGENT_TYPE: import_v4.z.string().min(1).optional(),
|
|
2911
|
+
MEMORAONE_SOURCE: import_v4.z.string().min(1).optional(),
|
|
2912
|
+
MEMORAONE_IDE_TYPE: import_v4.z.enum(["cursor", "copilot-vscode", "jetbrains", "claude-code", "windsurf", "opencode", "codex"]).optional(),
|
|
2913
|
+
MEMORAONE_WORKLOG: import_v4.z.string().min(1).optional(),
|
|
2914
|
+
MEMORAONE_HEARTBEAT: import_v4.z.string().min(1).optional(),
|
|
2915
|
+
MEMORAONE_HEARTBEAT_INTERVAL_MS: import_v4.z.string().min(1).optional()
|
|
2916
|
+
});
|
|
2917
|
+
var requiredEnvVars = [];
|
|
2918
|
+
var missingEnvVars = requiredEnvVars.filter((key) => {
|
|
2919
|
+
const value = process2.env[key];
|
|
2920
|
+
return value === void 0 || value.trim() === "";
|
|
2921
|
+
});
|
|
2922
|
+
if (missingEnvVars.length > 0) {
|
|
2923
|
+
for (const key of missingEnvVars) {
|
|
2924
|
+
process2.stderr.write(`Missing ${key}
|
|
2925
|
+
`);
|
|
2926
|
+
}
|
|
2927
|
+
process2.exit(1);
|
|
2928
|
+
}
|
|
2929
|
+
var parsed = EnvSchema.safeParse(process2.env);
|
|
2930
|
+
var resolvedApiUrl = resolveApiUrl(process2.env);
|
|
2931
|
+
if (!parsed.success) {
|
|
2932
|
+
const formatted = parsed.error.format();
|
|
2933
|
+
process2.stderr.write(
|
|
2934
|
+
"[memoraone-mcp] Invalid environment variables " + JSON.stringify(formatted) + "\n"
|
|
2935
|
+
);
|
|
2936
|
+
throw new Error("Config validation failed");
|
|
2937
|
+
}
|
|
2938
|
+
var parseBooleanFlag2 = (value, defaultValue) => {
|
|
2939
|
+
if (value === void 0) {
|
|
2940
|
+
return defaultValue;
|
|
2941
|
+
}
|
|
2942
|
+
const normalized = value.trim().toLowerCase();
|
|
2943
|
+
if (["1", "true", "yes", "on"].includes(normalized)) {
|
|
2944
|
+
return true;
|
|
2945
|
+
}
|
|
2946
|
+
if (["0", "false", "no", "off"].includes(normalized)) {
|
|
2947
|
+
return false;
|
|
2948
|
+
}
|
|
2949
|
+
return defaultValue;
|
|
2950
|
+
};
|
|
2951
|
+
var config2 = {
|
|
2952
|
+
apiUrl: resolvedApiUrl.replace(/\/+$/, ""),
|
|
2953
|
+
apiKey: parsed.data.MEMORAONE_API_KEY,
|
|
2954
|
+
agentName: parsed.data.MEMORAONE_AGENT_NAME ?? "cursor",
|
|
2955
|
+
agentType: parsed.data.MEMORAONE_AGENT_TYPE ?? "agent",
|
|
2956
|
+
source: parsed.data.MEMORAONE_SOURCE ?? "cursor",
|
|
2957
|
+
ideType: parsed.data.MEMORAONE_IDE_TYPE,
|
|
2958
|
+
devMode: parseBooleanFlag2(parsed.data.MEMORAONE_DEV_MODE, false),
|
|
2959
|
+
worklogEnabled: parseBooleanFlag2(parsed.data.MEMORAONE_WORKLOG, true),
|
|
2960
|
+
heartbeatEnabled: parseBooleanFlag2(parsed.data.MEMORAONE_HEARTBEAT, true),
|
|
2961
|
+
// Cadence is owned by LOCAL_MCP_HEARTBEAT_INTERVAL_MS in heartbeat.ts (1_000).
|
|
2962
|
+
// Env override is accepted for forward-compat but the timer path ignores it.
|
|
2963
|
+
heartbeatIntervalMs: Number.parseInt(parsed.data.MEMORAONE_HEARTBEAT_INTERVAL_MS ?? "1000", 10)
|
|
2964
|
+
};
|
|
2965
|
+
|
|
2966
|
+
// src/ideType.ts
|
|
2967
|
+
function mapReliableClientInfoName(name) {
|
|
2968
|
+
if (typeof name !== "string") {
|
|
2969
|
+
return void 0;
|
|
2970
|
+
}
|
|
2971
|
+
const normalized = name.trim().toLowerCase();
|
|
2972
|
+
if (normalized === "") {
|
|
2973
|
+
return void 0;
|
|
2974
|
+
}
|
|
2975
|
+
if (normalized === "claude-code") {
|
|
2976
|
+
return "claude-code";
|
|
2977
|
+
}
|
|
2978
|
+
if (normalized === "devin") {
|
|
2979
|
+
return "windsurf";
|
|
2980
|
+
}
|
|
2981
|
+
if (normalized === "windsurf" || /^windsurf[\s_-].+$/.test(normalized)) {
|
|
2982
|
+
return "windsurf";
|
|
2983
|
+
}
|
|
2984
|
+
return void 0;
|
|
2985
|
+
}
|
|
2986
|
+
function mapReliableHostIdentity(env2) {
|
|
2987
|
+
if (env2.WINDSURF_IDE_TYPE === "windsurf") {
|
|
2988
|
+
return "windsurf";
|
|
2989
|
+
}
|
|
2990
|
+
if (env2.ACP_BACKEND === "windsurf") {
|
|
2991
|
+
return "windsurf";
|
|
2992
|
+
}
|
|
2993
|
+
if (env2.__CFBundleIdentifier === "com.exafunction.windsurf") {
|
|
2994
|
+
return "windsurf";
|
|
2995
|
+
}
|
|
2996
|
+
return void 0;
|
|
2997
|
+
}
|
|
2998
|
+
function inferIdeType(params, options = {}) {
|
|
2999
|
+
const env2 = options.env ?? process.env;
|
|
3000
|
+
const argv = (options.argv ?? process.argv).join(" ").toLowerCase();
|
|
3001
|
+
const hasExplicitConfigOption = Object.prototype.hasOwnProperty.call(options, "configIdeType");
|
|
3002
|
+
const explicitHint = hasExplicitConfigOption ? options.configIdeType : resolveIdeTypeFromEnv(env2) ?? config2.ideType;
|
|
3003
|
+
const fromClientInfo = mapReliableClientInfoName(params?.clientInfo?.name);
|
|
3004
|
+
if (fromClientInfo) {
|
|
3005
|
+
return fromClientInfo;
|
|
3006
|
+
}
|
|
3007
|
+
const fromHost = mapReliableHostIdentity(env2);
|
|
3008
|
+
if (fromHost) {
|
|
3009
|
+
return fromHost;
|
|
3010
|
+
}
|
|
3011
|
+
if (explicitHint) {
|
|
3012
|
+
return explicitHint;
|
|
3013
|
+
}
|
|
3014
|
+
const clientInfoName = String(params?.clientInfo?.name ?? "").toLowerCase();
|
|
3015
|
+
const clientInfoVersion = String(params?.clientInfo?.version ?? "").toLowerCase();
|
|
3016
|
+
const termProgram = String(env2.TERM_PROGRAM ?? "").toLowerCase();
|
|
3017
|
+
const envKeys = Object.keys(env2);
|
|
3018
|
+
const hasCursorSignals = envKeys.some((key) => key.startsWith("CURSOR_")) || termProgram === "cursor" || clientInfoName.includes("cursor") || clientInfoVersion.includes("cursor") || argv.includes("cursor");
|
|
3019
|
+
if (hasCursorSignals) {
|
|
3020
|
+
return "cursor";
|
|
3021
|
+
}
|
|
3022
|
+
const hasJetBrainsSignals = envKeys.some(
|
|
3023
|
+
(key) => [
|
|
3024
|
+
"JETBRAINS_IDE",
|
|
3025
|
+
"IDEA_INITIAL_DIRECTORY",
|
|
3026
|
+
"JETBRAINS_REMOTE_RUN",
|
|
3027
|
+
"INTELLIJ_ENVIRONMENT_READER"
|
|
3028
|
+
].includes(key)
|
|
3029
|
+
) || String(env2.TERMINAL_EMULATOR ?? "").toLowerCase().includes("jetbrains") || /(jetbrains|intellij|pycharm|webstorm|goland|rubymine|clion|phpstorm|rider|datagrip)/.test(
|
|
3030
|
+
clientInfoName
|
|
3031
|
+
) || /(jetbrains|intellij|pycharm|webstorm|goland|rubymine|clion|phpstorm|rider|datagrip)/.test(
|
|
3032
|
+
argv
|
|
3033
|
+
);
|
|
3034
|
+
if (hasJetBrainsSignals) {
|
|
3035
|
+
return "jetbrains";
|
|
3036
|
+
}
|
|
3037
|
+
const hasVsCodeSignals = envKeys.some((key) => key.startsWith("VSCODE_")) || /(visual studio code|vscode|vs code|github copilot)/.test(clientInfoName) || /(visual studio code|vscode|vs code)/.test(argv);
|
|
3038
|
+
if (hasVsCodeSignals) {
|
|
3039
|
+
return "copilot-vscode";
|
|
3040
|
+
}
|
|
3041
|
+
return void 0;
|
|
3042
|
+
}
|
|
3043
|
+
|
|
2811
3044
|
// src/bridgeProxy.ts
|
|
2812
3045
|
var defaultLog = (msg) => {
|
|
2813
3046
|
process.stderr.write(`[memoraone-mcp][bridge] ${msg}
|
|
@@ -2828,9 +3061,9 @@ function summarizeJsonRpcMethod(line) {
|
|
|
2828
3061
|
}
|
|
2829
3062
|
}
|
|
2830
3063
|
function connectWithRetry(socketPath, log, maxRetries, retryDelayMs, connect2) {
|
|
2831
|
-
return new Promise((
|
|
3064
|
+
return new Promise((resolve21, reject) => {
|
|
2832
3065
|
const tryConnect = (attempt) => {
|
|
2833
|
-
connect2(socketPath).then(
|
|
3066
|
+
connect2(socketPath).then(resolve21).catch((err) => {
|
|
2834
3067
|
if (attempt >= maxRetries) {
|
|
2835
3068
|
reject(err);
|
|
2836
3069
|
return;
|
|
@@ -2852,8 +3085,8 @@ async function resolveBridgeSessionBinding(params, env2 = process.env, options =
|
|
|
2852
3085
|
...bridgeOptions
|
|
2853
3086
|
});
|
|
2854
3087
|
}
|
|
2855
|
-
async function stopDaemonsForStaleBinding(stale, env2, log) {
|
|
2856
|
-
const ideType = resolveBindingIdeType(env2);
|
|
3088
|
+
async function stopDaemonsForStaleBinding(stale, env2, log, ideTypeOverride) {
|
|
3089
|
+
const ideType = resolveBindingIdeType(env2, ideTypeOverride);
|
|
2857
3090
|
let processes;
|
|
2858
3091
|
try {
|
|
2859
3092
|
processes = await defaultListDaemonProcesses();
|
|
@@ -2880,9 +3113,10 @@ async function connectOrSpawnDaemonForBinding(binding, opts) {
|
|
|
2880
3113
|
`refreshed stale binding ${sessionBinding.repositoryBindingId}: project=${sessionBinding.projectId}`
|
|
2881
3114
|
);
|
|
2882
3115
|
}
|
|
2883
|
-
const
|
|
3116
|
+
const ideType = opts.ideType;
|
|
3117
|
+
const socketPath = getBindingSocketPath(sessionBinding, opts.env, ideType);
|
|
2884
3118
|
opts.log(
|
|
2885
|
-
`target daemon socket=${socketPath} project=${sessionBinding.projectId} workspace=${sessionBinding.workspaceRoot}`
|
|
3119
|
+
`target daemon socket=${socketPath} project=${sessionBinding.projectId} workspace=${sessionBinding.workspaceRoot}` + (ideType ? ` ideType=${ideType}` : "")
|
|
2886
3120
|
);
|
|
2887
3121
|
let socket;
|
|
2888
3122
|
try {
|
|
@@ -2893,7 +3127,7 @@ async function connectOrSpawnDaemonForBinding(binding, opts) {
|
|
|
2893
3127
|
opts.retryDelayMs,
|
|
2894
3128
|
opts.connect
|
|
2895
3129
|
);
|
|
2896
|
-
verifyDaemonSidecarBinding(socketPath, sessionBinding, opts.env);
|
|
3130
|
+
verifyDaemonSidecarBinding(socketPath, sessionBinding, opts.env, ideType);
|
|
2897
3131
|
opts.log("reusing running daemon for session binding");
|
|
2898
3132
|
return { socket, binding: sessionBinding, cacheRefreshed: reconciled.cacheRefreshed };
|
|
2899
3133
|
} catch (err) {
|
|
@@ -2908,11 +3142,11 @@ async function connectOrSpawnDaemonForBinding(binding, opts) {
|
|
|
2908
3142
|
opts.log("stale daemon binding detected; replacing daemon for current local binding");
|
|
2909
3143
|
const staleSidecar = readBindingSidecar(socketPath);
|
|
2910
3144
|
if (staleSidecar) {
|
|
2911
|
-
await stopDaemonsForStaleBinding(staleSidecar, opts.env, opts.log);
|
|
3145
|
+
await stopDaemonsForStaleBinding(staleSidecar, opts.env, opts.log, ideType);
|
|
2912
3146
|
}
|
|
2913
|
-
await stopDaemonsForStaleBinding(sessionBinding, opts.env, opts.log);
|
|
3147
|
+
await stopDaemonsForStaleBinding(sessionBinding, opts.env, opts.log, ideType);
|
|
2914
3148
|
if (!bindingsMatch(binding, sessionBinding)) {
|
|
2915
|
-
await stopDaemonsForStaleBinding(binding, opts.env, opts.log);
|
|
3149
|
+
await stopDaemonsForStaleBinding(binding, opts.env, opts.log, ideType);
|
|
2916
3150
|
}
|
|
2917
3151
|
removeDaemonSocketArtifacts(socketPath);
|
|
2918
3152
|
}
|
|
@@ -2927,13 +3161,14 @@ async function connectOrSpawnDaemonForBinding(binding, opts) {
|
|
|
2927
3161
|
opts.retryDelayMs,
|
|
2928
3162
|
opts.connect
|
|
2929
3163
|
);
|
|
2930
|
-
verifyDaemonSidecarBinding(socketPath, sessionBinding, opts.env);
|
|
3164
|
+
verifyDaemonSidecarBinding(socketPath, sessionBinding, opts.env, ideType);
|
|
2931
3165
|
return { socket, binding: sessionBinding, cacheRefreshed: reconciled.cacheRefreshed };
|
|
2932
3166
|
}
|
|
2933
3167
|
var BridgeDaemonRouter = class {
|
|
2934
3168
|
constructor(options) {
|
|
2935
3169
|
this.activeSocket = null;
|
|
2936
3170
|
this.activeBinding = null;
|
|
3171
|
+
this.activeIdeType = void 0;
|
|
2937
3172
|
this.socketLineReader = null;
|
|
2938
3173
|
this.lastInitializeLine = null;
|
|
2939
3174
|
this.pendingDeferredClientLines = [];
|
|
@@ -2946,19 +3181,23 @@ var BridgeDaemonRouter = class {
|
|
|
2946
3181
|
this.maxRetries = options.maxRetries ?? 5;
|
|
2947
3182
|
this.retryDelayMs = options.retryDelayMs ?? 200;
|
|
2948
3183
|
this.lineReader = options.lineReader ?? null;
|
|
2949
|
-
this.connectImpl = options.connect ?? ((socketPath) => new Promise((
|
|
2950
|
-
const socket = net.connect(socketPath, () =>
|
|
3184
|
+
this.connectImpl = options.connect ?? ((socketPath) => new Promise((resolve21, reject) => {
|
|
3185
|
+
const socket = net.connect(socketPath, () => resolve21(socket));
|
|
2951
3186
|
socket.on("error", reject);
|
|
2952
3187
|
}));
|
|
2953
3188
|
this.spawnDaemonImpl = options.spawnDaemon ?? (async (binding) => {
|
|
3189
|
+
const ideType = this.activeIdeType;
|
|
2954
3190
|
const child = (0, import_node_child_process4.spawn)(
|
|
2955
3191
|
process.execPath,
|
|
2956
|
-
buildDaemonSpawnArgs(this.cliPath, binding.repositoryBindingId, this.env),
|
|
3192
|
+
buildDaemonSpawnArgs(this.cliPath, binding.repositoryBindingId, this.env, ideType),
|
|
2957
3193
|
{
|
|
2958
3194
|
detached: true,
|
|
2959
3195
|
stdio: "ignore",
|
|
2960
3196
|
env: {
|
|
2961
3197
|
...this.env,
|
|
3198
|
+
// Align child hint with the effective live identity for this session.
|
|
3199
|
+
// Does not mutate the bridge process environment.
|
|
3200
|
+
...ideType ? { MEMORAONE_IDE_TYPE: ideType } : {},
|
|
2962
3201
|
// Secret-free handoff only; tokens stay in the OS keyring.
|
|
2963
3202
|
MEMORAONE_DAEMON_BINDING_B64: encodeResolvedBinding(binding)
|
|
2964
3203
|
}
|
|
@@ -2977,6 +3216,9 @@ var BridgeDaemonRouter = class {
|
|
|
2977
3216
|
getActiveBinding() {
|
|
2978
3217
|
return this.activeBinding;
|
|
2979
3218
|
}
|
|
3219
|
+
getActiveIdeType() {
|
|
3220
|
+
return this.activeIdeType;
|
|
3221
|
+
}
|
|
2980
3222
|
hasClientInitialize() {
|
|
2981
3223
|
return this.clientInitializeSeen;
|
|
2982
3224
|
}
|
|
@@ -2986,6 +3228,14 @@ var BridgeDaemonRouter = class {
|
|
|
2986
3228
|
this.env,
|
|
2987
3229
|
`initialize payload: ${summarizeInitializeParamsForDebug(params)}`
|
|
2988
3230
|
);
|
|
3231
|
+
const effectiveIdeType = inferIdeType(params, {
|
|
3232
|
+
env: this.env,
|
|
3233
|
+
configIdeType: resolveIdeTypeFromEnv(this.env)
|
|
3234
|
+
});
|
|
3235
|
+
this.activeIdeType = effectiveIdeType;
|
|
3236
|
+
if (effectiveIdeType) {
|
|
3237
|
+
this.log(`effective ideType=${effectiveIdeType} (initialize identity)`);
|
|
3238
|
+
}
|
|
2989
3239
|
const bridgeOptions = getBridgeBindingResolveOptions(this.env);
|
|
2990
3240
|
const initializeRoots = extractWorkspaceRootsFromInitialize(params);
|
|
2991
3241
|
let rootsListUris;
|
|
@@ -3035,6 +3285,7 @@ var BridgeDaemonRouter = class {
|
|
|
3035
3285
|
this.detachSocketReader();
|
|
3036
3286
|
this.activeSocket?.destroy();
|
|
3037
3287
|
this.activeSocket = null;
|
|
3288
|
+
this.activeIdeType = effectiveIdeType;
|
|
3038
3289
|
}
|
|
3039
3290
|
this.activeBinding = binding;
|
|
3040
3291
|
await this.connectActiveDaemon();
|
|
@@ -3078,6 +3329,7 @@ var BridgeDaemonRouter = class {
|
|
|
3078
3329
|
this.handshakeDeferredClientLines = [];
|
|
3079
3330
|
this.clientInitializeSeen = false;
|
|
3080
3331
|
this.activeBinding = null;
|
|
3332
|
+
this.activeIdeType = void 0;
|
|
3081
3333
|
}
|
|
3082
3334
|
async connectActiveDaemon() {
|
|
3083
3335
|
if (!this.activeBinding) {
|
|
@@ -3090,7 +3342,8 @@ var BridgeDaemonRouter = class {
|
|
|
3090
3342
|
maxRetries: this.maxRetries,
|
|
3091
3343
|
retryDelayMs: this.retryDelayMs,
|
|
3092
3344
|
connect: this.connectImpl,
|
|
3093
|
-
spawnDaemon: this.spawnDaemonImpl
|
|
3345
|
+
spawnDaemon: this.spawnDaemonImpl,
|
|
3346
|
+
ideType: this.activeIdeType
|
|
3094
3347
|
});
|
|
3095
3348
|
this.activeBinding = connected.binding;
|
|
3096
3349
|
this.activeSocket = connected.socket;
|
|
@@ -3209,37 +3462,17 @@ async function runBridgeProxy(options) {
|
|
|
3209
3462
|
}
|
|
3210
3463
|
|
|
3211
3464
|
// src/setupIdeFiles.ts
|
|
3212
|
-
var
|
|
3213
|
-
var
|
|
3214
|
-
var
|
|
3215
|
-
var
|
|
3465
|
+
var fs17 = __toESM(require("fs/promises"), 1);
|
|
3466
|
+
var os7 = __toESM(require("os"), 1);
|
|
3467
|
+
var path21 = __toESM(require("path"), 1);
|
|
3468
|
+
var import_node_crypto8 = require("crypto");
|
|
3216
3469
|
|
|
3217
3470
|
// src/jetbrainsMcpConfig.ts
|
|
3218
|
-
var
|
|
3471
|
+
var fs13 = __toESM(require("fs/promises"), 1);
|
|
3219
3472
|
var os5 = __toESM(require("os"), 1);
|
|
3220
|
-
var
|
|
3473
|
+
var path17 = __toESM(require("path"), 1);
|
|
3221
3474
|
var import_node_crypto4 = require("crypto");
|
|
3222
3475
|
var import_node_child_process5 = require("child_process");
|
|
3223
|
-
|
|
3224
|
-
// src/configUtils.ts
|
|
3225
|
-
var DEFAULT_API_URL = "http://localhost:3001";
|
|
3226
|
-
var DEV_API_URL = "http://localhost:3001";
|
|
3227
|
-
function resolveApiUrl(env2) {
|
|
3228
|
-
const explicitUrl = env2.MEMORAONE_API_URL?.trim();
|
|
3229
|
-
if (explicitUrl) {
|
|
3230
|
-
return explicitUrl;
|
|
3231
|
-
}
|
|
3232
|
-
const aliasUrl = env2.MEMORA_API_URL?.trim();
|
|
3233
|
-
if (aliasUrl) {
|
|
3234
|
-
return aliasUrl;
|
|
3235
|
-
}
|
|
3236
|
-
if (env2.MEMORAONE_DEV_MODE === "1") {
|
|
3237
|
-
return DEV_API_URL;
|
|
3238
|
-
}
|
|
3239
|
-
return DEFAULT_API_URL;
|
|
3240
|
-
}
|
|
3241
|
-
|
|
3242
|
-
// src/jetbrainsMcpConfig.ts
|
|
3243
3476
|
var JETBRAINS_DEBUG_ENV_VARS = [
|
|
3244
3477
|
"MEMORAONE_DEBUG_INIT",
|
|
3245
3478
|
"MEMORAONE_DEBUG_MINIMAL_TOOLS",
|
|
@@ -3251,7 +3484,7 @@ function stripLeadingLineComments2(text) {
|
|
|
3251
3484
|
}
|
|
3252
3485
|
async function pathExists3(filePath) {
|
|
3253
3486
|
try {
|
|
3254
|
-
await
|
|
3487
|
+
await fs13.access(filePath);
|
|
3255
3488
|
return true;
|
|
3256
3489
|
} catch {
|
|
3257
3490
|
return false;
|
|
@@ -3262,12 +3495,12 @@ function formatJetBrainsBackupTimestamp(d = /* @__PURE__ */ new Date()) {
|
|
|
3262
3495
|
return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
|
|
3263
3496
|
}
|
|
3264
3497
|
function getJetBrainsGlobalMcpConfigPath(homeDir) {
|
|
3265
|
-
return
|
|
3498
|
+
return path17.join(homeDir, ".ai", "mcp", "mcp.json");
|
|
3266
3499
|
}
|
|
3267
3500
|
function getJetBrainsProjectMcpConfigPaths(repoRoot) {
|
|
3268
3501
|
return [
|
|
3269
|
-
{ kind: "project-ai", path:
|
|
3270
|
-
{ kind: "project-ij", path:
|
|
3502
|
+
{ kind: "project-ai", path: path17.join(repoRoot, ".ai", "mcp", "mcp.json") },
|
|
3503
|
+
{ kind: "project-ij", path: path17.join(repoRoot, ".ij", "mcp", "mcp.json") }
|
|
3271
3504
|
];
|
|
3272
3505
|
}
|
|
3273
3506
|
function getKnownJetBrainsMcpConfigLocations(homeDir, repoRoot) {
|
|
@@ -3278,7 +3511,7 @@ function getKnownJetBrainsMcpConfigLocations(homeDir, repoRoot) {
|
|
|
3278
3511
|
}
|
|
3279
3512
|
async function isZeroByteConfigFile(filePath) {
|
|
3280
3513
|
if (!await pathExists3(filePath)) return false;
|
|
3281
|
-
const stat4 = await
|
|
3514
|
+
const stat4 = await fs13.stat(filePath);
|
|
3282
3515
|
return stat4.size === 0;
|
|
3283
3516
|
}
|
|
3284
3517
|
function buildMemoraoneJetBrainsMcpServer(options) {
|
|
@@ -3289,7 +3522,7 @@ function buildMemoraoneJetBrainsMcpServer(options) {
|
|
|
3289
3522
|
apiUrl: options.apiUrl ?? (environment === "local" ? DEV_API_URL : void 0)
|
|
3290
3523
|
}),
|
|
3291
3524
|
MEMORAONE_IDE_TYPE: "jetbrains",
|
|
3292
|
-
[MEMORAONE_WORKSPACE_ROOT_ENV]:
|
|
3525
|
+
[MEMORAONE_WORKSPACE_ROOT_ENV]: path17.resolve(options.workspaceRoot)
|
|
3293
3526
|
};
|
|
3294
3527
|
if (environment === "local" || options.devMode) {
|
|
3295
3528
|
env2.MEMORAONE_DEV_MODE = "1";
|
|
@@ -3311,18 +3544,18 @@ function mergeJetBrainsMcpConfigObject(existing, memoraone) {
|
|
|
3311
3544
|
return { ...base, mcpServers };
|
|
3312
3545
|
}
|
|
3313
3546
|
async function writeJetBrainsMcpJsonAtomic(filePath, content) {
|
|
3314
|
-
const dir =
|
|
3315
|
-
await
|
|
3316
|
-
const tmpPath =
|
|
3547
|
+
const dir = path17.dirname(filePath);
|
|
3548
|
+
await fs13.mkdir(dir, { recursive: true });
|
|
3549
|
+
const tmpPath = path17.join(
|
|
3317
3550
|
dir,
|
|
3318
|
-
`.${
|
|
3551
|
+
`.${path17.basename(filePath)}.${process.pid}.${(0, import_node_crypto4.randomBytes)(8).toString("hex")}.tmp`
|
|
3319
3552
|
);
|
|
3320
3553
|
try {
|
|
3321
|
-
await
|
|
3322
|
-
await
|
|
3554
|
+
await fs13.writeFile(tmpPath, content, "utf8");
|
|
3555
|
+
await fs13.rename(tmpPath, filePath);
|
|
3323
3556
|
} catch (err) {
|
|
3324
3557
|
try {
|
|
3325
|
-
await
|
|
3558
|
+
await fs13.unlink(tmpPath);
|
|
3326
3559
|
} catch {
|
|
3327
3560
|
}
|
|
3328
3561
|
throw err;
|
|
@@ -3363,13 +3596,13 @@ function validateJetBrainsMcpConfig(parsed2, expected) {
|
|
|
3363
3596
|
}
|
|
3364
3597
|
}
|
|
3365
3598
|
async function readJsonConfig(filePath) {
|
|
3366
|
-
const raw = await
|
|
3599
|
+
const raw = await fs13.readFile(filePath, "utf8");
|
|
3367
3600
|
if (raw.trim() === "") return null;
|
|
3368
3601
|
return JSON.parse(stripLeadingLineComments2(raw));
|
|
3369
3602
|
}
|
|
3370
3603
|
async function backupConfigFile(filePath) {
|
|
3371
3604
|
const backupPath = `${filePath}.bak-${formatJetBrainsBackupTimestamp()}`;
|
|
3372
|
-
await
|
|
3605
|
+
await fs13.copyFile(filePath, backupPath);
|
|
3373
3606
|
return backupPath;
|
|
3374
3607
|
}
|
|
3375
3608
|
async function repairZeroByteConfigFile(filePath, dryRun) {
|
|
@@ -3380,7 +3613,7 @@ async function repairZeroByteConfigFile(filePath, dryRun) {
|
|
|
3380
3613
|
return { repaired: true, backupPath: `${filePath}.bak-<timestamp>` };
|
|
3381
3614
|
}
|
|
3382
3615
|
const backupPath = await backupConfigFile(filePath);
|
|
3383
|
-
await
|
|
3616
|
+
await fs13.unlink(filePath);
|
|
3384
3617
|
return { repaired: true, backupPath };
|
|
3385
3618
|
}
|
|
3386
3619
|
function configHasMemoraone(parsed2) {
|
|
@@ -3411,24 +3644,24 @@ async function removeMemoraoneFromProjectConfig(options) {
|
|
|
3411
3644
|
delete mcpServers.memoraone;
|
|
3412
3645
|
const hasOtherServers = Object.keys(mcpServers).length > 0;
|
|
3413
3646
|
if (!hasOtherServers) {
|
|
3414
|
-
await
|
|
3647
|
+
await fs13.unlink(configPath);
|
|
3415
3648
|
return { changed: true, backupPath };
|
|
3416
3649
|
}
|
|
3417
3650
|
const next = { ...parsed2, mcpServers };
|
|
3418
|
-
await
|
|
3419
|
-
await
|
|
3651
|
+
await fs13.mkdir(path17.dirname(configPath), { recursive: true });
|
|
3652
|
+
await fs13.writeFile(configPath, JSON.stringify(next, null, 2) + "\n", "utf8");
|
|
3420
3653
|
return { changed: true, backupPath };
|
|
3421
3654
|
}
|
|
3422
3655
|
async function resolveLocalCliPathAsync() {
|
|
3423
|
-
const here = process.argv[1] ?
|
|
3656
|
+
const here = process.argv[1] ? path17.dirname(path17.resolve(process.argv[1])) : process.cwd();
|
|
3424
3657
|
const candidates = [
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3658
|
+
path17.join(here, "cli.cjs"),
|
|
3659
|
+
path17.join(here, "..", "dist", "cli.cjs"),
|
|
3660
|
+
path17.join(here, "..", "..", "dist", "cli.cjs")
|
|
3428
3661
|
];
|
|
3429
3662
|
for (const candidate of candidates) {
|
|
3430
3663
|
if (await pathExists3(candidate)) {
|
|
3431
|
-
return
|
|
3664
|
+
return path17.resolve(candidate);
|
|
3432
3665
|
}
|
|
3433
3666
|
}
|
|
3434
3667
|
return null;
|
|
@@ -3485,7 +3718,7 @@ function formatOptionalJetBrainsHandshakeUnavailableDetail(detail) {
|
|
|
3485
3718
|
async function verifyJetBrainsMcpHandshake(options) {
|
|
3486
3719
|
const timeoutMs = options.timeoutMs ?? 15e3;
|
|
3487
3720
|
const { server } = options;
|
|
3488
|
-
return new Promise((
|
|
3721
|
+
return new Promise((resolve21) => {
|
|
3489
3722
|
let settled = false;
|
|
3490
3723
|
const finish = (ok, detail, optionalUnavailable) => {
|
|
3491
3724
|
if (settled) return;
|
|
@@ -3495,7 +3728,7 @@ async function verifyJetBrainsMcpHandshake(options) {
|
|
|
3495
3728
|
child.kill();
|
|
3496
3729
|
} catch {
|
|
3497
3730
|
}
|
|
3498
|
-
|
|
3731
|
+
resolve21({ ok, detail, optionalUnavailable });
|
|
3499
3732
|
};
|
|
3500
3733
|
const child = (0, import_node_child_process5.spawn)(server.command, [...server.args], {
|
|
3501
3734
|
env: { ...process.env, ...server.env },
|
|
@@ -3569,7 +3802,7 @@ async function verifyJetBrainsMcpHandshake(options) {
|
|
|
3569
3802
|
async function setupJetBrainsMcpConfig(options) {
|
|
3570
3803
|
const homeDir = options.homeDir ?? os5.homedir();
|
|
3571
3804
|
const globalPath = options.globalConfigPath ?? getJetBrainsGlobalMcpConfigPath(homeDir);
|
|
3572
|
-
const workspaceRoot =
|
|
3805
|
+
const workspaceRoot = path17.resolve(options.repoRoot);
|
|
3573
3806
|
const repairActions = [];
|
|
3574
3807
|
const allLocations = getKnownJetBrainsMcpConfigLocations(homeDir, options.repoRoot);
|
|
3575
3808
|
for (const location of allLocations) {
|
|
@@ -3642,7 +3875,7 @@ async function setupJetBrainsMcpConfig(options) {
|
|
|
3642
3875
|
backupPath = await backupConfigFile(globalPath);
|
|
3643
3876
|
}
|
|
3644
3877
|
await writeJetBrainsMcpJsonAtomic(globalPath, body);
|
|
3645
|
-
const verifyRaw = await
|
|
3878
|
+
const verifyRaw = await fs13.readFile(globalPath, "utf8");
|
|
3646
3879
|
const verifyParsed = JSON.parse(stripLeadingLineComments2(verifyRaw));
|
|
3647
3880
|
validateJetBrainsMcpConfig(verifyParsed, memoraone);
|
|
3648
3881
|
const outcome = existed ? "updated" : "created";
|
|
@@ -3710,51 +3943,1010 @@ function logJetBrainsMcpCliSummary(info, dryRun, println = console.log) {
|
|
|
3710
3943
|
);
|
|
3711
3944
|
}
|
|
3712
3945
|
|
|
3713
|
-
// src/
|
|
3714
|
-
var
|
|
3715
|
-
var
|
|
3716
|
-
var
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
yellow: "\x1B[33m",
|
|
3725
|
-
cyan: "\x1B[36m"
|
|
3726
|
-
};
|
|
3727
|
-
function isCiLikeEnv(env2 = process.env) {
|
|
3728
|
-
if (env2.CI === "true" || env2.CI === "1") return true;
|
|
3729
|
-
if (env2.GITHUB_ACTIONS === "true" || env2.GITHUB_ACTIONS === "1") return true;
|
|
3730
|
-
if (env2.GITLAB_CI === "true" || env2.GITLAB_CI === "1") return true;
|
|
3731
|
-
if (env2.CIRCLECI === "true" || env2.CIRCLECI === "1") return true;
|
|
3732
|
-
if (env2.BUILDKITE === "true" || env2.BUILDKITE === "1") return true;
|
|
3733
|
-
if (typeof env2.CI === "string" && env2.CI.trim() !== "" && env2.CI !== "0" && env2.CI !== "false") {
|
|
3946
|
+
// src/windsurfMcpConfig.ts
|
|
3947
|
+
var fs14 = __toESM(require("fs/promises"), 1);
|
|
3948
|
+
var os6 = __toESM(require("os"), 1);
|
|
3949
|
+
var path18 = __toESM(require("path"), 1);
|
|
3950
|
+
var import_node_crypto5 = require("crypto");
|
|
3951
|
+
function stripLeadingLineComments3(text) {
|
|
3952
|
+
return text.split("\n").filter((line) => !/^\s*\/\//.test(line)).join("\n");
|
|
3953
|
+
}
|
|
3954
|
+
async function pathExists4(filePath) {
|
|
3955
|
+
try {
|
|
3956
|
+
await fs14.access(filePath);
|
|
3734
3957
|
return true;
|
|
3958
|
+
} catch {
|
|
3959
|
+
return false;
|
|
3735
3960
|
}
|
|
3736
|
-
return false;
|
|
3737
3961
|
}
|
|
3738
|
-
function
|
|
3739
|
-
|
|
3740
|
-
const env2 = opts.env ?? process.env;
|
|
3741
|
-
if (env2.NO_COLOR !== void 0) return false;
|
|
3742
|
-
if (isCiLikeEnv(env2)) return false;
|
|
3743
|
-
const tty = opts.stdoutIsTty ?? process.stdout.isTTY === true;
|
|
3744
|
-
return tty;
|
|
3962
|
+
function getWindsurfGlobalMcpConfigPath(homeDir) {
|
|
3963
|
+
return path18.join(homeDir, ".config", "devin", "mcp_config.json");
|
|
3745
3964
|
}
|
|
3746
|
-
function
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3965
|
+
function getWindsurfLegacyMcpConfigPath(homeDir) {
|
|
3966
|
+
return path18.join(homeDir, ".codeium", "windsurf", "mcp_config.json");
|
|
3967
|
+
}
|
|
3968
|
+
function formatWindsurfBackupTimestamp(d = /* @__PURE__ */ new Date()) {
|
|
3969
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
3970
|
+
return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
|
|
3971
|
+
}
|
|
3972
|
+
function buildMemoraoneWindsurfMcpServer(options) {
|
|
3973
|
+
const environment = options.environment ?? (options.devMode ? "local" : "production");
|
|
3974
|
+
const env2 = {
|
|
3975
|
+
MEMORAONE_API_URL: resolveIdeApiUrl({
|
|
3976
|
+
environment,
|
|
3977
|
+
apiUrl: options.apiUrl ?? (environment === "local" ? DEV_API_URL : void 0)
|
|
3978
|
+
}),
|
|
3979
|
+
MEMORAONE_IDE_TYPE: "windsurf",
|
|
3980
|
+
[MEMORAONE_WORKSPACE_ROOT_ENV]: path18.resolve(options.workspaceRoot)
|
|
3981
|
+
};
|
|
3982
|
+
if (environment === "local" || options.devMode) {
|
|
3983
|
+
env2.MEMORAONE_DEV_MODE = "1";
|
|
3984
|
+
}
|
|
3985
|
+
if ("MEMORAONE_M1_PATH" in env2 || "MEMORAONE_API_KEY" in env2) {
|
|
3986
|
+
throw new Error(
|
|
3987
|
+
"[setup-ide-files] Devin Desktop MCP config must not include credentials or .m1 paths"
|
|
3755
3988
|
);
|
|
3756
3989
|
}
|
|
3757
|
-
return
|
|
3990
|
+
return {
|
|
3991
|
+
command: options.command,
|
|
3992
|
+
args: options.args,
|
|
3993
|
+
env: env2
|
|
3994
|
+
};
|
|
3995
|
+
}
|
|
3996
|
+
function mergeWindsurfMcpConfigObject(existing, memoraone) {
|
|
3997
|
+
const base = existing && typeof existing === "object" ? { ...existing } : { mcpServers: {} };
|
|
3998
|
+
const mcpServers = typeof base.mcpServers === "object" && base.mcpServers !== null && !Array.isArray(base.mcpServers) ? { ...base.mcpServers } : {};
|
|
3999
|
+
delete mcpServers.memoraone;
|
|
4000
|
+
mcpServers.memoraone = memoraone;
|
|
4001
|
+
return { ...base, mcpServers };
|
|
4002
|
+
}
|
|
4003
|
+
function stripMemoraoneFromWindsurfConfigObject(existing) {
|
|
4004
|
+
const base = { ...existing };
|
|
4005
|
+
if (typeof base.mcpServers !== "object" || base.mcpServers === null || Array.isArray(base.mcpServers)) {
|
|
4006
|
+
return { next: base, removed: false };
|
|
4007
|
+
}
|
|
4008
|
+
const mcpServers = { ...base.mcpServers };
|
|
4009
|
+
if (!("memoraone" in mcpServers)) {
|
|
4010
|
+
return { next: base, removed: false };
|
|
4011
|
+
}
|
|
4012
|
+
delete mcpServers.memoraone;
|
|
4013
|
+
return { next: { ...base, mcpServers }, removed: true };
|
|
4014
|
+
}
|
|
4015
|
+
async function writeWindsurfMcpJsonAtomic(filePath, content) {
|
|
4016
|
+
const dir = path18.dirname(filePath);
|
|
4017
|
+
await fs14.mkdir(dir, { recursive: true });
|
|
4018
|
+
const tmpPath = path18.join(
|
|
4019
|
+
dir,
|
|
4020
|
+
`.${path18.basename(filePath)}.${process.pid}.${(0, import_node_crypto5.randomBytes)(8).toString("hex")}.tmp`
|
|
4021
|
+
);
|
|
4022
|
+
try {
|
|
4023
|
+
await fs14.writeFile(tmpPath, content, "utf8");
|
|
4024
|
+
await fs14.rename(tmpPath, filePath);
|
|
4025
|
+
} catch (err) {
|
|
4026
|
+
try {
|
|
4027
|
+
await fs14.unlink(tmpPath);
|
|
4028
|
+
} catch {
|
|
4029
|
+
}
|
|
4030
|
+
throw err;
|
|
4031
|
+
}
|
|
4032
|
+
}
|
|
4033
|
+
function memoraoneServerMatches2(server, expected) {
|
|
4034
|
+
if (!server || typeof server !== "object") return false;
|
|
4035
|
+
const s = server;
|
|
4036
|
+
if (s.command !== expected.command) return false;
|
|
4037
|
+
if (!Array.isArray(s.args) || s.args.length !== expected.args.length) return false;
|
|
4038
|
+
for (let i = 0; i < expected.args.length; i += 1) {
|
|
4039
|
+
if (s.args[i] !== expected.args[i]) return false;
|
|
4040
|
+
}
|
|
4041
|
+
const env2 = s.env;
|
|
4042
|
+
if (!env2 || typeof env2 !== "object") return false;
|
|
4043
|
+
const e = env2;
|
|
4044
|
+
for (const [key, value] of Object.entries(expected.env)) {
|
|
4045
|
+
if (e[key] !== value) return false;
|
|
4046
|
+
}
|
|
4047
|
+
return true;
|
|
4048
|
+
}
|
|
4049
|
+
function validateWindsurfMcpConfig(parsed2, expected) {
|
|
4050
|
+
if (!parsed2 || typeof parsed2 !== "object") {
|
|
4051
|
+
throw new Error("[setup-ide-files] Devin Desktop MCP config must be a JSON object.");
|
|
4052
|
+
}
|
|
4053
|
+
const mcpServers = parsed2.mcpServers;
|
|
4054
|
+
if (!mcpServers || typeof mcpServers !== "object" || Array.isArray(mcpServers)) {
|
|
4055
|
+
throw new Error("[setup-ide-files] Devin Desktop MCP config missing mcpServers object.");
|
|
4056
|
+
}
|
|
4057
|
+
const memoraone = mcpServers.memoraone;
|
|
4058
|
+
if (!memoraoneServerMatches2(memoraone, expected)) {
|
|
4059
|
+
throw new Error(
|
|
4060
|
+
"[setup-ide-files] Devin Desktop MCP config mcpServers.memoraone is missing or invalid."
|
|
4061
|
+
);
|
|
4062
|
+
}
|
|
4063
|
+
}
|
|
4064
|
+
async function readJsonConfig2(filePath) {
|
|
4065
|
+
const raw = await fs14.readFile(filePath, "utf8");
|
|
4066
|
+
if (raw.trim() === "") return null;
|
|
4067
|
+
return JSON.parse(stripLeadingLineComments3(raw));
|
|
4068
|
+
}
|
|
4069
|
+
async function backupConfigFile2(filePath) {
|
|
4070
|
+
const backupPath = `${filePath}.bak-${formatWindsurfBackupTimestamp()}`;
|
|
4071
|
+
await fs14.copyFile(filePath, backupPath);
|
|
4072
|
+
return backupPath;
|
|
4073
|
+
}
|
|
4074
|
+
function extractMemoraone(existing) {
|
|
4075
|
+
if (!existing || typeof existing.mcpServers !== "object" || existing.mcpServers === null || Array.isArray(existing.mcpServers)) {
|
|
4076
|
+
return void 0;
|
|
4077
|
+
}
|
|
4078
|
+
return existing.mcpServers.memoraone;
|
|
4079
|
+
}
|
|
4080
|
+
async function buildWindsurfMemoraoneServer(options) {
|
|
4081
|
+
const environment = options.environment ?? (options.devMode ? "local" : "production");
|
|
4082
|
+
if (environment === "local" || options.devMode) {
|
|
4083
|
+
let cliPath = options.cliPathOverride;
|
|
4084
|
+
if (cliPath === void 0) {
|
|
4085
|
+
cliPath = await resolveLocalCliPathAsync();
|
|
4086
|
+
}
|
|
4087
|
+
if (!cliPath) {
|
|
4088
|
+
throw new Error(
|
|
4089
|
+
"[setup-ide-files] Dev mode requires a built CLI at packages/mcp/dist/cli.cjs. Run pnpm build first."
|
|
4090
|
+
);
|
|
4091
|
+
}
|
|
4092
|
+
return buildMemoraoneWindsurfMcpServer({
|
|
4093
|
+
command: process.execPath,
|
|
4094
|
+
args: [cliPath],
|
|
4095
|
+
workspaceRoot: options.workspaceRoot,
|
|
4096
|
+
environment: "local",
|
|
4097
|
+
devMode: true,
|
|
4098
|
+
apiUrl: options.apiUrl
|
|
4099
|
+
});
|
|
4100
|
+
}
|
|
4101
|
+
let npxPath = options.npxPathOverride;
|
|
4102
|
+
if (npxPath === void 0) {
|
|
4103
|
+
npxPath = await resolveNpxPath();
|
|
4104
|
+
}
|
|
4105
|
+
if (!npxPath) {
|
|
4106
|
+
throw new Error(
|
|
4107
|
+
"[setup-ide-files] Could not resolve a working npx executable. Install Node.js/npm or ensure npx is on PATH before configuring Devin Desktop MCP."
|
|
4108
|
+
);
|
|
4109
|
+
}
|
|
4110
|
+
const channel = options.npmPackageChannel ?? npmPackageChannelFromEnvironment(environment);
|
|
4111
|
+
return buildMemoraoneWindsurfMcpServer({
|
|
4112
|
+
command: npxPath,
|
|
4113
|
+
args: ["-y", memoraoneNpmPackageSpec(channel)],
|
|
4114
|
+
workspaceRoot: options.workspaceRoot,
|
|
4115
|
+
environment,
|
|
4116
|
+
devMode: false,
|
|
4117
|
+
apiUrl: options.apiUrl
|
|
4118
|
+
});
|
|
4119
|
+
}
|
|
4120
|
+
async function setupWindsurfMcpConfig(options) {
|
|
4121
|
+
const homeDir = options.homeDir ?? os6.homedir();
|
|
4122
|
+
const activePath = options.globalConfigPath ?? getWindsurfGlobalMcpConfigPath(homeDir);
|
|
4123
|
+
const legacyPath = options.legacyConfigPath ?? getWindsurfLegacyMcpConfigPath(homeDir);
|
|
4124
|
+
const samePath = path18.resolve(activePath) === path18.resolve(legacyPath);
|
|
4125
|
+
const workspaceRoot = path18.resolve(options.repoRoot);
|
|
4126
|
+
const memoraone = await buildWindsurfMemoraoneServer({
|
|
4127
|
+
workspaceRoot,
|
|
4128
|
+
devMode: options.devMode,
|
|
4129
|
+
environment: options.environment,
|
|
4130
|
+
npmPackageChannel: options.npmPackageChannel,
|
|
4131
|
+
apiUrl: options.apiUrl,
|
|
4132
|
+
npxPathOverride: options.npxPathOverride,
|
|
4133
|
+
cliPathOverride: options.cliPathOverride
|
|
4134
|
+
});
|
|
4135
|
+
const activeExisted = await pathExists4(activePath);
|
|
4136
|
+
let activeExisting = null;
|
|
4137
|
+
if (activeExisted) {
|
|
4138
|
+
try {
|
|
4139
|
+
activeExisting = await readJsonConfig2(activePath);
|
|
4140
|
+
} catch {
|
|
4141
|
+
throw new Error(
|
|
4142
|
+
`[setup-ide-files] Invalid JSON in shared Devin Desktop MCP config (file preserved, not modified): ${activePath}. Fix or remove the file, then re-run setup-ide-files.`
|
|
4143
|
+
);
|
|
4144
|
+
}
|
|
4145
|
+
}
|
|
4146
|
+
let legacyExisting = null;
|
|
4147
|
+
let legacyMalformed = false;
|
|
4148
|
+
const legacyExisted = !samePath && await pathExists4(legacyPath);
|
|
4149
|
+
if (legacyExisted) {
|
|
4150
|
+
try {
|
|
4151
|
+
legacyExisting = await readJsonConfig2(legacyPath);
|
|
4152
|
+
} catch {
|
|
4153
|
+
legacyMalformed = true;
|
|
4154
|
+
}
|
|
4155
|
+
}
|
|
4156
|
+
if (!activeExisted && legacyMalformed) {
|
|
4157
|
+
throw new Error(
|
|
4158
|
+
`[setup-ide-files] Invalid JSON in legacy Windsurf MCP config (file preserved, not modified): ${legacyPath}. Fix or remove the file, then re-run setup-ide-files. Active Devin Desktop path was not written: ${activePath}.`
|
|
4159
|
+
);
|
|
4160
|
+
}
|
|
4161
|
+
const mergeBase = activeExisted ? activeExisting : legacyExisting;
|
|
4162
|
+
const merged = mergeWindsurfMcpConfigObject(mergeBase, memoraone);
|
|
4163
|
+
const body = JSON.stringify(merged, null, 2) + "\n";
|
|
4164
|
+
const activeMemoraone = extractMemoraone(activeExisting);
|
|
4165
|
+
const activeAlreadyCorrect = activeExisted && memoraoneServerMatches2(activeMemoraone, memoraone);
|
|
4166
|
+
let legacyNeedsCleanup = false;
|
|
4167
|
+
if (legacyExisted && !legacyMalformed && legacyExisting) {
|
|
4168
|
+
const legacyMemoraone = extractMemoraone(legacyExisting);
|
|
4169
|
+
legacyNeedsCleanup = legacyMemoraone !== void 0;
|
|
4170
|
+
}
|
|
4171
|
+
if (activeAlreadyCorrect && !legacyNeedsCleanup) {
|
|
4172
|
+
return {
|
|
4173
|
+
outcome: "skipped",
|
|
4174
|
+
memoraone,
|
|
4175
|
+
activeConfigPath: activePath,
|
|
4176
|
+
legacyConfigPath: legacyPath,
|
|
4177
|
+
legacyCleanup: legacyExisted ? legacyMalformed ? "malformed" : "unchanged" : "absent",
|
|
4178
|
+
warning: legacyMalformed ? `[setup-ide-files] Legacy Windsurf MCP config has invalid JSON and was left unchanged: ${legacyPath}` : void 0
|
|
4179
|
+
};
|
|
4180
|
+
}
|
|
4181
|
+
if (options.dryRun) {
|
|
4182
|
+
let legacyCleanup2 = "absent";
|
|
4183
|
+
if (legacyExisted) {
|
|
4184
|
+
if (legacyMalformed) legacyCleanup2 = "malformed";
|
|
4185
|
+
else if (legacyNeedsCleanup) legacyCleanup2 = "skipped-dry-run";
|
|
4186
|
+
else legacyCleanup2 = "unchanged";
|
|
4187
|
+
}
|
|
4188
|
+
return {
|
|
4189
|
+
outcome: activeExisted ? "updated" : "created",
|
|
4190
|
+
memoraone,
|
|
4191
|
+
activeConfigPath: activePath,
|
|
4192
|
+
legacyConfigPath: legacyPath,
|
|
4193
|
+
legacyCleanup: legacyCleanup2,
|
|
4194
|
+
warning: legacyMalformed ? `[setup-ide-files] Legacy Windsurf MCP config has invalid JSON and was left unchanged: ${legacyPath}` : void 0
|
|
4195
|
+
};
|
|
4196
|
+
}
|
|
4197
|
+
let backupPath;
|
|
4198
|
+
if (activeExisted && !activeAlreadyCorrect) {
|
|
4199
|
+
backupPath = await backupConfigFile2(activePath);
|
|
4200
|
+
}
|
|
4201
|
+
if (!activeAlreadyCorrect) {
|
|
4202
|
+
await writeWindsurfMcpJsonAtomic(activePath, body);
|
|
4203
|
+
const verifyRaw = await fs14.readFile(activePath, "utf8");
|
|
4204
|
+
const verifyParsed = JSON.parse(stripLeadingLineComments3(verifyRaw));
|
|
4205
|
+
validateWindsurfMcpConfig(verifyParsed, memoraone);
|
|
4206
|
+
}
|
|
4207
|
+
let legacyCleanup = "absent";
|
|
4208
|
+
let warning;
|
|
4209
|
+
if (legacyExisted) {
|
|
4210
|
+
if (legacyMalformed) {
|
|
4211
|
+
legacyCleanup = "malformed";
|
|
4212
|
+
warning = `[setup-ide-files] Legacy Windsurf MCP config has invalid JSON and was left unchanged: ${legacyPath}. Devin Desktop active config was written; fix or remove the legacy file to avoid ambiguity.`;
|
|
4213
|
+
} else if (legacyNeedsCleanup && legacyExisting) {
|
|
4214
|
+
await backupConfigFile2(legacyPath);
|
|
4215
|
+
const { next, removed } = stripMemoraoneFromWindsurfConfigObject(legacyExisting);
|
|
4216
|
+
if (removed) {
|
|
4217
|
+
const legacyBody = JSON.stringify(next, null, 2) + "\n";
|
|
4218
|
+
await writeWindsurfMcpJsonAtomic(legacyPath, legacyBody);
|
|
4219
|
+
legacyCleanup = "removed-memoraone";
|
|
4220
|
+
} else {
|
|
4221
|
+
legacyCleanup = "unchanged";
|
|
4222
|
+
}
|
|
4223
|
+
} else {
|
|
4224
|
+
legacyCleanup = "unchanged";
|
|
4225
|
+
}
|
|
4226
|
+
}
|
|
4227
|
+
return {
|
|
4228
|
+
outcome: activeExisted ? "updated" : "created",
|
|
4229
|
+
backupPath,
|
|
4230
|
+
memoraone,
|
|
4231
|
+
activeConfigPath: activePath,
|
|
4232
|
+
legacyConfigPath: legacyPath,
|
|
4233
|
+
legacyCleanup,
|
|
4234
|
+
warning
|
|
4235
|
+
};
|
|
4236
|
+
}
|
|
4237
|
+
function logWindsurfMcpCliSummary(info, dryRun, println = console.log) {
|
|
4238
|
+
const prefix = dryRun ? "would be " : "";
|
|
4239
|
+
if (info.outcome === "created") {
|
|
4240
|
+
println(
|
|
4241
|
+
`[setup-ide-files] Devin Desktop global MCP config ${prefix}created: ${info.activeConfigPath}`
|
|
4242
|
+
);
|
|
4243
|
+
} else if (info.outcome === "updated") {
|
|
4244
|
+
println(
|
|
4245
|
+
`[setup-ide-files] Devin Desktop global MCP config ${prefix}updated: ${info.activeConfigPath}`
|
|
4246
|
+
);
|
|
4247
|
+
} else {
|
|
4248
|
+
println(
|
|
4249
|
+
`[setup-ide-files] Devin Desktop global MCP config unchanged: ${info.activeConfigPath}`
|
|
4250
|
+
);
|
|
4251
|
+
}
|
|
4252
|
+
if (info.backupPath) {
|
|
4253
|
+
println(`[setup-ide-files] Devin Desktop global MCP config backup: ${info.backupPath}`);
|
|
4254
|
+
}
|
|
4255
|
+
if (info.legacyConfigPath) {
|
|
4256
|
+
println(`[setup-ide-files] Legacy Windsurf MCP config path: ${info.legacyConfigPath}`);
|
|
4257
|
+
}
|
|
4258
|
+
if (info.legacyCleanup === "removed-memoraone") {
|
|
4259
|
+
println(
|
|
4260
|
+
`[setup-ide-files] Removed mcpServers.memoraone from legacy Windsurf MCP config: ${info.legacyConfigPath}`
|
|
4261
|
+
);
|
|
4262
|
+
} else if (info.legacyCleanup === "skipped-dry-run") {
|
|
4263
|
+
println(
|
|
4264
|
+
`[setup-ide-files] Legacy Windsurf mcpServers.memoraone would be removed: ${info.legacyConfigPath}`
|
|
4265
|
+
);
|
|
4266
|
+
}
|
|
4267
|
+
if (info.warning) {
|
|
4268
|
+
println(info.warning);
|
|
4269
|
+
}
|
|
4270
|
+
if (info.npxPath) {
|
|
4271
|
+
println(`[setup-ide-files] Resolved npx: ${info.npxPath}`);
|
|
4272
|
+
}
|
|
4273
|
+
println(`[setup-ide-files] Final active Devin Desktop MCP config: ${info.activeConfigPath}`);
|
|
4274
|
+
println(
|
|
4275
|
+
"[setup-ide-files] Refresh MCP servers in Devin Desktop settings, or restart Devin Desktop, for MCP changes to take effect."
|
|
4276
|
+
);
|
|
4277
|
+
}
|
|
4278
|
+
|
|
4279
|
+
// src/opencodeMcpConfig.ts
|
|
4280
|
+
var fs15 = __toESM(require("fs/promises"), 1);
|
|
4281
|
+
var path19 = __toESM(require("path"), 1);
|
|
4282
|
+
var import_node_crypto6 = require("crypto");
|
|
4283
|
+
var OPENCODE_CONFIG_SCHEMA_URL = "https://opencode.ai/config.json";
|
|
4284
|
+
var OpenCodeMcpJsonParseError = class extends Error {
|
|
4285
|
+
constructor(configPath) {
|
|
4286
|
+
super(
|
|
4287
|
+
`[setup-ide-files] Invalid JSON in shared OpenCode MCP config (file preserved, not modified): ${configPath}. Fix or remove the file, then re-run setup-ide-files.`
|
|
4288
|
+
);
|
|
4289
|
+
this.name = "OpenCodeMcpJsonParseError";
|
|
4290
|
+
this.configPath = configPath;
|
|
4291
|
+
}
|
|
4292
|
+
};
|
|
4293
|
+
var OpenCodeJsoncOnlyError = class extends Error {
|
|
4294
|
+
constructor(configPath) {
|
|
4295
|
+
super(
|
|
4296
|
+
`[setup-ide-files] Project OpenCode config is JSONC-only (file preserved, not modified): ${configPath}. Automatic setup manages opencode.json only and cannot safely preserve JSONC comments. Add or convert to opencode.json, then re-run setup-ide-files.`
|
|
4297
|
+
);
|
|
4298
|
+
this.name = "OpenCodeJsoncOnlyError";
|
|
4299
|
+
this.configPath = configPath;
|
|
4300
|
+
}
|
|
4301
|
+
};
|
|
4302
|
+
async function pathExists5(filePath) {
|
|
4303
|
+
try {
|
|
4304
|
+
await fs15.access(filePath);
|
|
4305
|
+
return true;
|
|
4306
|
+
} catch {
|
|
4307
|
+
return false;
|
|
4308
|
+
}
|
|
4309
|
+
}
|
|
4310
|
+
function getOpenCodeProjectMcpConfigPath(repoRoot) {
|
|
4311
|
+
return path19.join(path19.resolve(repoRoot), "opencode.json");
|
|
4312
|
+
}
|
|
4313
|
+
function getOpenCodeProjectJsoncConfigPath(repoRoot) {
|
|
4314
|
+
return path19.join(path19.resolve(repoRoot), "opencode.jsonc");
|
|
4315
|
+
}
|
|
4316
|
+
function buildMemoraoneOpenCodeMcpServer(options) {
|
|
4317
|
+
const environment = options.environment ?? (options.devMode ? "local" : "production");
|
|
4318
|
+
const env2 = {
|
|
4319
|
+
MEMORAONE_API_URL: resolveIdeApiUrl({
|
|
4320
|
+
environment,
|
|
4321
|
+
apiUrl: options.apiUrl ?? (environment === "local" ? DEV_API_URL : void 0)
|
|
4322
|
+
}),
|
|
4323
|
+
MEMORAONE_IDE_TYPE: "opencode",
|
|
4324
|
+
[MEMORAONE_WORKSPACE_ROOT_ENV]: path19.resolve(options.workspaceRoot)
|
|
4325
|
+
};
|
|
4326
|
+
if (environment === "local" || options.devMode) {
|
|
4327
|
+
env2.MEMORAONE_DEV_MODE = "1";
|
|
4328
|
+
}
|
|
4329
|
+
if ("MEMORAONE_M1_PATH" in env2 || "MEMORAONE_API_KEY" in env2) {
|
|
4330
|
+
throw new Error("[setup-ide-files] OpenCode MCP config must not include credentials or .m1 paths");
|
|
4331
|
+
}
|
|
4332
|
+
return {
|
|
4333
|
+
type: "local",
|
|
4334
|
+
command: [options.command, ...options.args],
|
|
4335
|
+
environment: env2,
|
|
4336
|
+
enabled: true
|
|
4337
|
+
};
|
|
4338
|
+
}
|
|
4339
|
+
function mergeOpenCodeMcpConfigObject(existing, memoraone) {
|
|
4340
|
+
const base = existing && typeof existing === "object" ? { ...existing } : { $schema: OPENCODE_CONFIG_SCHEMA_URL };
|
|
4341
|
+
const mcp = typeof base.mcp === "object" && base.mcp !== null && !Array.isArray(base.mcp) ? { ...base.mcp } : {};
|
|
4342
|
+
delete mcp.memoraone;
|
|
4343
|
+
if (typeof mcp.servers === "object" && mcp.servers !== null && !Array.isArray(mcp.servers)) {
|
|
4344
|
+
const servers = { ...mcp.servers };
|
|
4345
|
+
delete servers.memoraone;
|
|
4346
|
+
if (Object.keys(servers).length === 0) {
|
|
4347
|
+
delete mcp.servers;
|
|
4348
|
+
} else {
|
|
4349
|
+
mcp.servers = servers;
|
|
4350
|
+
}
|
|
4351
|
+
}
|
|
4352
|
+
mcp.memoraone = memoraone;
|
|
4353
|
+
return { ...base, mcp };
|
|
4354
|
+
}
|
|
4355
|
+
async function writeOpenCodeMcpJsonAtomic(filePath, content) {
|
|
4356
|
+
const dir = path19.dirname(filePath);
|
|
4357
|
+
await fs15.mkdir(dir, { recursive: true });
|
|
4358
|
+
const tmpPath = path19.join(
|
|
4359
|
+
dir,
|
|
4360
|
+
`.${path19.basename(filePath)}.${process.pid}.${(0, import_node_crypto6.randomBytes)(8).toString("hex")}.tmp`
|
|
4361
|
+
);
|
|
4362
|
+
try {
|
|
4363
|
+
await fs15.writeFile(tmpPath, content, "utf8");
|
|
4364
|
+
await fs15.rename(tmpPath, filePath);
|
|
4365
|
+
} catch (err) {
|
|
4366
|
+
try {
|
|
4367
|
+
await fs15.unlink(tmpPath);
|
|
4368
|
+
} catch {
|
|
4369
|
+
}
|
|
4370
|
+
throw err;
|
|
4371
|
+
}
|
|
4372
|
+
}
|
|
4373
|
+
function memoraoneServerMatches3(server, expected) {
|
|
4374
|
+
if (!server || typeof server !== "object") return false;
|
|
4375
|
+
const s = server;
|
|
4376
|
+
if (s.type !== "local") return false;
|
|
4377
|
+
if (s.enabled !== true) return false;
|
|
4378
|
+
if (!Array.isArray(s.command) || s.command.length !== expected.command.length) return false;
|
|
4379
|
+
for (let i = 0; i < expected.command.length; i += 1) {
|
|
4380
|
+
if (s.command[i] !== expected.command[i]) return false;
|
|
4381
|
+
}
|
|
4382
|
+
const environment = s.environment;
|
|
4383
|
+
if (!environment || typeof environment !== "object") return false;
|
|
4384
|
+
const e = environment;
|
|
4385
|
+
for (const [key, value] of Object.entries(expected.environment)) {
|
|
4386
|
+
if (e[key] !== value) return false;
|
|
4387
|
+
}
|
|
4388
|
+
return true;
|
|
4389
|
+
}
|
|
4390
|
+
function validateOpenCodeMcpConfig(parsed2, expected) {
|
|
4391
|
+
if (!parsed2 || typeof parsed2 !== "object") {
|
|
4392
|
+
throw new Error("[setup-ide-files] OpenCode MCP config must be a JSON object.");
|
|
4393
|
+
}
|
|
4394
|
+
const mcp = parsed2.mcp;
|
|
4395
|
+
if (!mcp || typeof mcp !== "object" || Array.isArray(mcp)) {
|
|
4396
|
+
throw new Error("[setup-ide-files] OpenCode MCP config missing mcp object.");
|
|
4397
|
+
}
|
|
4398
|
+
const memoraone = mcp.memoraone;
|
|
4399
|
+
if (!memoraoneServerMatches3(memoraone, expected)) {
|
|
4400
|
+
throw new Error(
|
|
4401
|
+
"[setup-ide-files] OpenCode MCP config mcp.memoraone is missing or invalid."
|
|
4402
|
+
);
|
|
4403
|
+
}
|
|
4404
|
+
}
|
|
4405
|
+
async function readJsonConfig3(filePath) {
|
|
4406
|
+
const raw = await fs15.readFile(filePath, "utf8");
|
|
4407
|
+
if (raw.trim() === "") return null;
|
|
4408
|
+
return JSON.parse(raw);
|
|
4409
|
+
}
|
|
4410
|
+
async function buildOpenCodeMemoraoneServer(options) {
|
|
4411
|
+
const environment = options.environment ?? (options.devMode ? "local" : "production");
|
|
4412
|
+
if (environment === "local" || options.devMode) {
|
|
4413
|
+
let cliPath = options.cliPathOverride;
|
|
4414
|
+
if (cliPath === void 0) {
|
|
4415
|
+
cliPath = await resolveLocalCliPathAsync();
|
|
4416
|
+
}
|
|
4417
|
+
if (!cliPath) {
|
|
4418
|
+
throw new Error(
|
|
4419
|
+
"[setup-ide-files] Dev mode requires a built CLI at packages/mcp/dist/cli.cjs. Run pnpm build first."
|
|
4420
|
+
);
|
|
4421
|
+
}
|
|
4422
|
+
return buildMemoraoneOpenCodeMcpServer({
|
|
4423
|
+
command: process.execPath,
|
|
4424
|
+
args: [cliPath],
|
|
4425
|
+
workspaceRoot: options.workspaceRoot,
|
|
4426
|
+
environment: "local",
|
|
4427
|
+
devMode: true,
|
|
4428
|
+
apiUrl: options.apiUrl
|
|
4429
|
+
});
|
|
4430
|
+
}
|
|
4431
|
+
let npxPath = options.npxPathOverride;
|
|
4432
|
+
if (npxPath === void 0) {
|
|
4433
|
+
npxPath = await resolveNpxPath();
|
|
4434
|
+
}
|
|
4435
|
+
if (!npxPath) {
|
|
4436
|
+
throw new Error(
|
|
4437
|
+
"[setup-ide-files] Could not resolve a working npx executable. Install Node.js/npm or ensure npx is on PATH before configuring OpenCode MCP."
|
|
4438
|
+
);
|
|
4439
|
+
}
|
|
4440
|
+
const channel = options.npmPackageChannel ?? npmPackageChannelFromEnvironment(environment);
|
|
4441
|
+
return buildMemoraoneOpenCodeMcpServer({
|
|
4442
|
+
command: npxPath,
|
|
4443
|
+
args: ["-y", memoraoneNpmPackageSpec(channel)],
|
|
4444
|
+
workspaceRoot: options.workspaceRoot,
|
|
4445
|
+
environment,
|
|
4446
|
+
devMode: false,
|
|
4447
|
+
apiUrl: options.apiUrl
|
|
4448
|
+
});
|
|
4449
|
+
}
|
|
4450
|
+
async function setupOpenCodeMcpConfig(options) {
|
|
4451
|
+
const projectPath = options.projectConfigPath ?? getOpenCodeProjectMcpConfigPath(options.repoRoot);
|
|
4452
|
+
const jsoncPath = getOpenCodeProjectJsoncConfigPath(options.repoRoot);
|
|
4453
|
+
const workspaceRoot = path19.resolve(options.repoRoot);
|
|
4454
|
+
const memoraone = await buildOpenCodeMemoraoneServer({
|
|
4455
|
+
workspaceRoot,
|
|
4456
|
+
devMode: options.devMode,
|
|
4457
|
+
environment: options.environment,
|
|
4458
|
+
npmPackageChannel: options.npmPackageChannel,
|
|
4459
|
+
apiUrl: options.apiUrl,
|
|
4460
|
+
npxPathOverride: options.npxPathOverride,
|
|
4461
|
+
cliPathOverride: options.cliPathOverride
|
|
4462
|
+
});
|
|
4463
|
+
const jsonExists = await pathExists5(projectPath);
|
|
4464
|
+
const jsoncExists = await pathExists5(jsoncPath);
|
|
4465
|
+
if (!jsonExists && jsoncExists) {
|
|
4466
|
+
throw new OpenCodeJsoncOnlyError(jsoncPath);
|
|
4467
|
+
}
|
|
4468
|
+
let existing = null;
|
|
4469
|
+
if (jsonExists) {
|
|
4470
|
+
try {
|
|
4471
|
+
existing = await readJsonConfig3(projectPath);
|
|
4472
|
+
} catch {
|
|
4473
|
+
throw new OpenCodeMcpJsonParseError(projectPath);
|
|
4474
|
+
}
|
|
4475
|
+
if (existing !== null && (typeof existing !== "object" || Array.isArray(existing))) {
|
|
4476
|
+
throw new OpenCodeMcpJsonParseError(projectPath);
|
|
4477
|
+
}
|
|
4478
|
+
}
|
|
4479
|
+
const merged = mergeOpenCodeMcpConfigObject(existing, memoraone);
|
|
4480
|
+
const body = JSON.stringify(merged, null, 2) + "\n";
|
|
4481
|
+
if (jsonExists && existing) {
|
|
4482
|
+
const currentMemoraone = existing.mcp && typeof existing.mcp === "object" && !Array.isArray(existing.mcp) ? existing.mcp.memoraone : void 0;
|
|
4483
|
+
if (memoraoneServerMatches3(currentMemoraone, memoraone)) {
|
|
4484
|
+
return { outcome: "skipped", memoraone };
|
|
4485
|
+
}
|
|
4486
|
+
}
|
|
4487
|
+
if (options.dryRun) {
|
|
4488
|
+
return { outcome: jsonExists ? "updated" : "created", memoraone };
|
|
4489
|
+
}
|
|
4490
|
+
await writeOpenCodeMcpJsonAtomic(projectPath, body);
|
|
4491
|
+
const verifyRaw = await fs15.readFile(projectPath, "utf8");
|
|
4492
|
+
const verifyParsed = JSON.parse(verifyRaw);
|
|
4493
|
+
validateOpenCodeMcpConfig(verifyParsed, memoraone);
|
|
4494
|
+
return {
|
|
4495
|
+
outcome: jsonExists ? "updated" : "created",
|
|
4496
|
+
memoraone
|
|
4497
|
+
};
|
|
4498
|
+
}
|
|
4499
|
+
function logOpenCodeMcpCliSummary(info, dryRun, println = console.log) {
|
|
4500
|
+
const prefix = dryRun ? "would be " : "";
|
|
4501
|
+
if (info.outcome === "created") {
|
|
4502
|
+
println(`[setup-ide-files] OpenCode project MCP config ${prefix}created: ${info.activeConfigPath}`);
|
|
4503
|
+
} else if (info.outcome === "updated") {
|
|
4504
|
+
println(`[setup-ide-files] OpenCode project MCP config ${prefix}updated: ${info.activeConfigPath}`);
|
|
4505
|
+
} else {
|
|
4506
|
+
println(`[setup-ide-files] OpenCode project MCP config unchanged: ${info.activeConfigPath}`);
|
|
4507
|
+
}
|
|
4508
|
+
if (info.npxPath) {
|
|
4509
|
+
println(`[setup-ide-files] Resolved npx: ${info.npxPath}`);
|
|
4510
|
+
}
|
|
4511
|
+
println(`[setup-ide-files] Final active OpenCode MCP config: ${info.activeConfigPath}`);
|
|
4512
|
+
println(
|
|
4513
|
+
"[setup-ide-files] Fully quit OpenCode and reopen this repo for MCP changes to take effect."
|
|
4514
|
+
);
|
|
4515
|
+
}
|
|
4516
|
+
|
|
4517
|
+
// src/codexMcpConfig.ts
|
|
4518
|
+
var fs16 = __toESM(require("fs/promises"), 1);
|
|
4519
|
+
var path20 = __toESM(require("path"), 1);
|
|
4520
|
+
var import_node_crypto7 = require("crypto");
|
|
4521
|
+
var import_smol_toml = require("smol-toml");
|
|
4522
|
+
var CODEX_MCP_SERVER_NAME = "memoraone";
|
|
4523
|
+
var CODEX_OWNED_TABLE_PREFIX = `mcp_servers.${CODEX_MCP_SERVER_NAME}`;
|
|
4524
|
+
var CodexMcpTomlParseError = class extends Error {
|
|
4525
|
+
constructor(configPath) {
|
|
4526
|
+
super(
|
|
4527
|
+
`[setup-ide-files] Invalid TOML in shared Codex MCP config (file preserved, not modified): ${configPath}. Fix or remove the file, then re-run setup-ide-files.`
|
|
4528
|
+
);
|
|
4529
|
+
this.name = "CodexMcpTomlParseError";
|
|
4530
|
+
this.configPath = configPath;
|
|
4531
|
+
}
|
|
4532
|
+
};
|
|
4533
|
+
async function pathExists6(filePath) {
|
|
4534
|
+
try {
|
|
4535
|
+
await fs16.access(filePath);
|
|
4536
|
+
return true;
|
|
4537
|
+
} catch {
|
|
4538
|
+
return false;
|
|
4539
|
+
}
|
|
4540
|
+
}
|
|
4541
|
+
function getCodexProjectMcpConfigPath(repoRoot) {
|
|
4542
|
+
return path20.join(path20.resolve(repoRoot), ".codex", "config.toml");
|
|
4543
|
+
}
|
|
4544
|
+
function escapeTomlBasicString(value) {
|
|
4545
|
+
return '"' + value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\t/g, "\\t").replace(/\n/g, "\\n").replace(/\r/g, "\\r") + '"';
|
|
4546
|
+
}
|
|
4547
|
+
function formatMemoraoneCodexTomlTables(memoraone) {
|
|
4548
|
+
const args2 = memoraone.args.map(escapeTomlBasicString).join(", ");
|
|
4549
|
+
const envLines = Object.entries(memoraone.env).map(([key, value]) => `${key} = ${escapeTomlBasicString(value)}`).join("\n");
|
|
4550
|
+
return `[mcp_servers.${CODEX_MCP_SERVER_NAME}]
|
|
4551
|
+
command = ${escapeTomlBasicString(memoraone.command)}
|
|
4552
|
+
args = [${args2}]
|
|
4553
|
+
|
|
4554
|
+
[mcp_servers.${CODEX_MCP_SERVER_NAME}.env]
|
|
4555
|
+
${envLines}
|
|
4556
|
+
`;
|
|
4557
|
+
}
|
|
4558
|
+
function buildMemoraoneCodexMcpServer(options) {
|
|
4559
|
+
const environment = options.environment ?? (options.devMode ? "local" : "production");
|
|
4560
|
+
const env2 = {
|
|
4561
|
+
MEMORAONE_API_URL: resolveIdeApiUrl({
|
|
4562
|
+
environment,
|
|
4563
|
+
apiUrl: options.apiUrl ?? (environment === "local" ? DEV_API_URL : void 0)
|
|
4564
|
+
}),
|
|
4565
|
+
MEMORAONE_IDE_TYPE: "codex",
|
|
4566
|
+
[MEMORAONE_WORKSPACE_ROOT_ENV]: path20.resolve(options.workspaceRoot)
|
|
4567
|
+
};
|
|
4568
|
+
if (environment === "local" || options.devMode) {
|
|
4569
|
+
env2.MEMORAONE_DEV_MODE = "1";
|
|
4570
|
+
}
|
|
4571
|
+
if ("MEMORAONE_M1_PATH" in env2 || "MEMORAONE_API_KEY" in env2) {
|
|
4572
|
+
throw new Error("[setup-ide-files] Codex MCP config must not include credentials or .m1 paths");
|
|
4573
|
+
}
|
|
4574
|
+
return {
|
|
4575
|
+
command: options.command,
|
|
4576
|
+
args: [...options.args],
|
|
4577
|
+
env: env2
|
|
4578
|
+
};
|
|
4579
|
+
}
|
|
4580
|
+
function normalizeTomlTablePath(headerInner) {
|
|
4581
|
+
return headerInner.split(".").map((part) => {
|
|
4582
|
+
const trimmed = part.trim();
|
|
4583
|
+
if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) {
|
|
4584
|
+
return trimmed.slice(1, -1);
|
|
4585
|
+
}
|
|
4586
|
+
return trimmed;
|
|
4587
|
+
}).join(".");
|
|
4588
|
+
}
|
|
4589
|
+
function isMemoraoneOwnedCodexTablePath(headerInner) {
|
|
4590
|
+
const normalized = normalizeTomlTablePath(headerInner);
|
|
4591
|
+
return normalized === CODEX_OWNED_TABLE_PREFIX || normalized.startsWith(`${CODEX_OWNED_TABLE_PREFIX}.`);
|
|
4592
|
+
}
|
|
4593
|
+
var TABLE_HEADER_RE = /^\s*\[\[?([^\]]+)\]\]?\s*(?:#.*)?$/;
|
|
4594
|
+
function splitTomlTableSegments(source) {
|
|
4595
|
+
const lines = source.split(/(?<=\n)/);
|
|
4596
|
+
const segments = [];
|
|
4597
|
+
let current = { tablePath: null, arrayOfTables: false, start: 0, end: 0 };
|
|
4598
|
+
let offset = 0;
|
|
4599
|
+
for (const line of lines) {
|
|
4600
|
+
const match = line.match(TABLE_HEADER_RE);
|
|
4601
|
+
if (match) {
|
|
4602
|
+
current.end = offset;
|
|
4603
|
+
if (current.end > current.start || current.tablePath !== null) {
|
|
4604
|
+
segments.push(current);
|
|
4605
|
+
}
|
|
4606
|
+
const arrayOfTables = /^\s*\[\[/.test(line);
|
|
4607
|
+
current = {
|
|
4608
|
+
tablePath: match[1].trim(),
|
|
4609
|
+
arrayOfTables,
|
|
4610
|
+
start: offset,
|
|
4611
|
+
end: offset + line.length
|
|
4612
|
+
};
|
|
4613
|
+
} else {
|
|
4614
|
+
current.end = offset + line.length;
|
|
4615
|
+
}
|
|
4616
|
+
offset += line.length;
|
|
4617
|
+
}
|
|
4618
|
+
current.end = source.length;
|
|
4619
|
+
if (current.end > current.start || current.tablePath !== null) {
|
|
4620
|
+
segments.push(current);
|
|
4621
|
+
}
|
|
4622
|
+
return segments;
|
|
4623
|
+
}
|
|
4624
|
+
function stripMemoraoneAssignmentsFromMcpServersBody(body) {
|
|
4625
|
+
const lines = body.split(/(?<=\n)/);
|
|
4626
|
+
const kept = [];
|
|
4627
|
+
let skippingMultiline = false;
|
|
4628
|
+
let multilineTerminator = null;
|
|
4629
|
+
for (const line of lines) {
|
|
4630
|
+
if (skippingMultiline) {
|
|
4631
|
+
if (multilineTerminator && line.includes(multilineTerminator)) {
|
|
4632
|
+
skippingMultiline = false;
|
|
4633
|
+
multilineTerminator = null;
|
|
4634
|
+
}
|
|
4635
|
+
continue;
|
|
4636
|
+
}
|
|
4637
|
+
const trimmed = line.trim();
|
|
4638
|
+
if (!trimmed || trimmed.startsWith("#")) {
|
|
4639
|
+
kept.push(line);
|
|
4640
|
+
continue;
|
|
4641
|
+
}
|
|
4642
|
+
const assignMatch = trimmed.match(
|
|
4643
|
+
/^(?:memoraone|"memoraone"|'memoraone')(?:\.[A-Za-z0-9_\-"']+)*\s*=\s*(.*)$/
|
|
4644
|
+
);
|
|
4645
|
+
if (!assignMatch) {
|
|
4646
|
+
kept.push(line);
|
|
4647
|
+
continue;
|
|
4648
|
+
}
|
|
4649
|
+
const rhs = assignMatch[1] ?? "";
|
|
4650
|
+
if (rhs.startsWith('"""')) {
|
|
4651
|
+
if (!rhs.slice(3).includes('"""')) {
|
|
4652
|
+
skippingMultiline = true;
|
|
4653
|
+
multilineTerminator = '"""';
|
|
4654
|
+
}
|
|
4655
|
+
continue;
|
|
4656
|
+
}
|
|
4657
|
+
if (rhs.startsWith("'''")) {
|
|
4658
|
+
if (!rhs.slice(3).includes("'''")) {
|
|
4659
|
+
skippingMultiline = true;
|
|
4660
|
+
multilineTerminator = "'''";
|
|
4661
|
+
}
|
|
4662
|
+
continue;
|
|
4663
|
+
}
|
|
4664
|
+
}
|
|
4665
|
+
return kept.join("");
|
|
4666
|
+
}
|
|
4667
|
+
function removeMemoraoneOwnedCodexToml(source) {
|
|
4668
|
+
const segments = splitTomlTableSegments(source);
|
|
4669
|
+
let removed = false;
|
|
4670
|
+
const parts = [];
|
|
4671
|
+
for (const segment of segments) {
|
|
4672
|
+
if (segment.tablePath && isMemoraoneOwnedCodexTablePath(segment.tablePath)) {
|
|
4673
|
+
removed = true;
|
|
4674
|
+
continue;
|
|
4675
|
+
}
|
|
4676
|
+
const chunk = source.slice(segment.start, segment.end);
|
|
4677
|
+
if (segment.tablePath && normalizeTomlTablePath(segment.tablePath) === "mcp_servers" && !segment.arrayOfTables) {
|
|
4678
|
+
const headerEnd = chunk.indexOf("\n");
|
|
4679
|
+
if (headerEnd === -1) {
|
|
4680
|
+
parts.push(chunk);
|
|
4681
|
+
continue;
|
|
4682
|
+
}
|
|
4683
|
+
const header = chunk.slice(0, headerEnd + 1);
|
|
4684
|
+
const body = chunk.slice(headerEnd + 1);
|
|
4685
|
+
const stripped = stripMemoraoneAssignmentsFromMcpServersBody(body);
|
|
4686
|
+
if (stripped !== body) removed = true;
|
|
4687
|
+
const hasKeys = stripped.split(/\r?\n/).some((line) => {
|
|
4688
|
+
const t = line.trim();
|
|
4689
|
+
return t.length > 0 && !t.startsWith("#");
|
|
4690
|
+
});
|
|
4691
|
+
if (!hasKeys && removed && body !== stripped) {
|
|
4692
|
+
const otherServersRemain = segments.some(
|
|
4693
|
+
(s) => s.tablePath != null && normalizeTomlTablePath(s.tablePath).startsWith("mcp_servers.") && !isMemoraoneOwnedCodexTablePath(s.tablePath)
|
|
4694
|
+
);
|
|
4695
|
+
if (!otherServersRemain) {
|
|
4696
|
+
const hasComments = stripped.split(/\r?\n/).some((line) => line.trim().startsWith("#"));
|
|
4697
|
+
if (hasComments) {
|
|
4698
|
+
parts.push(header + stripped);
|
|
4699
|
+
}
|
|
4700
|
+
continue;
|
|
4701
|
+
}
|
|
4702
|
+
}
|
|
4703
|
+
parts.push(header + stripped);
|
|
4704
|
+
continue;
|
|
4705
|
+
}
|
|
4706
|
+
parts.push(chunk);
|
|
4707
|
+
}
|
|
4708
|
+
let remainder = parts.join("");
|
|
4709
|
+
remainder = remainder.replace(/\n{3,}$/g, "\n\n");
|
|
4710
|
+
if (remainder.length > 0 && !remainder.endsWith("\n")) {
|
|
4711
|
+
remainder += "\n";
|
|
4712
|
+
}
|
|
4713
|
+
return { remainder, removed };
|
|
4714
|
+
}
|
|
4715
|
+
function mergeCodexMcpConfigToml(existingSource, memoraone) {
|
|
4716
|
+
const ownedBlock = formatMemoraoneCodexTomlTables(memoraone);
|
|
4717
|
+
if (existingSource === null || existingSource.trim() === "") {
|
|
4718
|
+
return ownedBlock;
|
|
4719
|
+
}
|
|
4720
|
+
const { remainder } = removeMemoraoneOwnedCodexToml(existingSource);
|
|
4721
|
+
if (remainder.trim() === "") {
|
|
4722
|
+
return ownedBlock;
|
|
4723
|
+
}
|
|
4724
|
+
const sep2 = remainder.endsWith("\n\n") ? "" : remainder.endsWith("\n") ? "\n" : "\n\n";
|
|
4725
|
+
return remainder + sep2 + ownedBlock;
|
|
4726
|
+
}
|
|
4727
|
+
function asStringRecord(value) {
|
|
4728
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
4729
|
+
return value;
|
|
4730
|
+
}
|
|
4731
|
+
function extractMemoraoneFromCodexParsed(parsed2) {
|
|
4732
|
+
const root = asStringRecord(parsed2);
|
|
4733
|
+
if (!root) return null;
|
|
4734
|
+
const mcpServers = asStringRecord(root.mcp_servers);
|
|
4735
|
+
if (!mcpServers) return null;
|
|
4736
|
+
const memoraone = asStringRecord(mcpServers[CODEX_MCP_SERVER_NAME]);
|
|
4737
|
+
if (!memoraone) return null;
|
|
4738
|
+
if (typeof memoraone.command !== "string") return null;
|
|
4739
|
+
if (!Array.isArray(memoraone.args) || !memoraone.args.every((a) => typeof a === "string")) {
|
|
4740
|
+
return null;
|
|
4741
|
+
}
|
|
4742
|
+
const env2 = asStringRecord(memoraone.env);
|
|
4743
|
+
if (!env2) return null;
|
|
4744
|
+
if (typeof env2.MEMORAONE_API_URL !== "string") return null;
|
|
4745
|
+
if (env2.MEMORAONE_IDE_TYPE !== "codex") return null;
|
|
4746
|
+
if (typeof env2[MEMORAONE_WORKSPACE_ROOT_ENV] !== "string") return null;
|
|
4747
|
+
const out = {
|
|
4748
|
+
command: memoraone.command,
|
|
4749
|
+
args: memoraone.args,
|
|
4750
|
+
env: {
|
|
4751
|
+
MEMORAONE_API_URL: env2.MEMORAONE_API_URL,
|
|
4752
|
+
MEMORAONE_IDE_TYPE: "codex",
|
|
4753
|
+
[MEMORAONE_WORKSPACE_ROOT_ENV]: env2[MEMORAONE_WORKSPACE_ROOT_ENV]
|
|
4754
|
+
}
|
|
4755
|
+
};
|
|
4756
|
+
if (typeof env2.MEMORAONE_DEV_MODE === "string") {
|
|
4757
|
+
out.env.MEMORAONE_DEV_MODE = env2.MEMORAONE_DEV_MODE;
|
|
4758
|
+
}
|
|
4759
|
+
return out;
|
|
4760
|
+
}
|
|
4761
|
+
function memoraoneServerMatches4(actual, expected) {
|
|
4762
|
+
if (!actual) return false;
|
|
4763
|
+
if (actual.command !== expected.command) return false;
|
|
4764
|
+
if (actual.args.length !== expected.args.length) return false;
|
|
4765
|
+
for (let i = 0; i < expected.args.length; i += 1) {
|
|
4766
|
+
if (actual.args[i] !== expected.args[i]) return false;
|
|
4767
|
+
}
|
|
4768
|
+
for (const [key, value] of Object.entries(expected.env)) {
|
|
4769
|
+
if (actual.env[key] !== value) return false;
|
|
4770
|
+
}
|
|
4771
|
+
for (const key of Object.keys(actual.env)) {
|
|
4772
|
+
if (!(key in expected.env)) return false;
|
|
4773
|
+
}
|
|
4774
|
+
return true;
|
|
4775
|
+
}
|
|
4776
|
+
function validateCodexMcpConfig(parsed2, expected) {
|
|
4777
|
+
const actual = extractMemoraoneFromCodexParsed(parsed2);
|
|
4778
|
+
if (!memoraoneServerMatches4(actual, expected)) {
|
|
4779
|
+
throw new Error(
|
|
4780
|
+
"[setup-ide-files] Codex MCP config mcp_servers.memoraone is missing or invalid."
|
|
4781
|
+
);
|
|
4782
|
+
}
|
|
4783
|
+
}
|
|
4784
|
+
function parseCodexTomlOrThrow(source, configPath) {
|
|
4785
|
+
try {
|
|
4786
|
+
return (0, import_smol_toml.parse)(source);
|
|
4787
|
+
} catch (err) {
|
|
4788
|
+
if (err instanceof import_smol_toml.TomlError || err instanceof Error && err.name === "TomlError") {
|
|
4789
|
+
throw new CodexMcpTomlParseError(configPath);
|
|
4790
|
+
}
|
|
4791
|
+
throw new CodexMcpTomlParseError(configPath);
|
|
4792
|
+
}
|
|
4793
|
+
}
|
|
4794
|
+
async function writeCodexTomlAtomic(filePath, content) {
|
|
4795
|
+
const dir = path20.dirname(filePath);
|
|
4796
|
+
await fs16.mkdir(dir, { recursive: true });
|
|
4797
|
+
const tmpPath = path20.join(
|
|
4798
|
+
dir,
|
|
4799
|
+
`.${path20.basename(filePath)}.${process.pid}.${(0, import_node_crypto7.randomBytes)(8).toString("hex")}.tmp`
|
|
4800
|
+
);
|
|
4801
|
+
try {
|
|
4802
|
+
await fs16.writeFile(tmpPath, content, "utf8");
|
|
4803
|
+
await fs16.rename(tmpPath, filePath);
|
|
4804
|
+
} catch (err) {
|
|
4805
|
+
try {
|
|
4806
|
+
await fs16.unlink(tmpPath);
|
|
4807
|
+
} catch {
|
|
4808
|
+
}
|
|
4809
|
+
throw err;
|
|
4810
|
+
}
|
|
4811
|
+
}
|
|
4812
|
+
async function buildCodexMemoraoneServer(options) {
|
|
4813
|
+
const environment = options.environment ?? (options.devMode ? "local" : "production");
|
|
4814
|
+
if (environment === "local" || options.devMode) {
|
|
4815
|
+
let cliPath = options.cliPathOverride;
|
|
4816
|
+
if (cliPath === void 0) {
|
|
4817
|
+
cliPath = await resolveLocalCliPathAsync();
|
|
4818
|
+
}
|
|
4819
|
+
if (!cliPath) {
|
|
4820
|
+
throw new Error(
|
|
4821
|
+
"[setup-ide-files] Dev mode requires a built CLI at packages/mcp/dist/cli.cjs. Run pnpm build first."
|
|
4822
|
+
);
|
|
4823
|
+
}
|
|
4824
|
+
return buildMemoraoneCodexMcpServer({
|
|
4825
|
+
command: process.execPath,
|
|
4826
|
+
args: [cliPath],
|
|
4827
|
+
workspaceRoot: options.workspaceRoot,
|
|
4828
|
+
environment: "local",
|
|
4829
|
+
devMode: true,
|
|
4830
|
+
apiUrl: options.apiUrl
|
|
4831
|
+
});
|
|
4832
|
+
}
|
|
4833
|
+
let npxPath = options.npxPathOverride;
|
|
4834
|
+
if (npxPath === void 0) {
|
|
4835
|
+
npxPath = await resolveNpxPath();
|
|
4836
|
+
}
|
|
4837
|
+
if (!npxPath) {
|
|
4838
|
+
throw new Error(
|
|
4839
|
+
"[setup-ide-files] Could not resolve a working npx executable. Install Node.js/npm or ensure npx is on PATH before configuring Codex MCP."
|
|
4840
|
+
);
|
|
4841
|
+
}
|
|
4842
|
+
const channel = options.npmPackageChannel ?? npmPackageChannelFromEnvironment(environment);
|
|
4843
|
+
return buildMemoraoneCodexMcpServer({
|
|
4844
|
+
command: npxPath,
|
|
4845
|
+
args: ["-y", memoraoneNpmPackageSpec(channel)],
|
|
4846
|
+
workspaceRoot: options.workspaceRoot,
|
|
4847
|
+
environment,
|
|
4848
|
+
devMode: false,
|
|
4849
|
+
apiUrl: options.apiUrl
|
|
4850
|
+
});
|
|
4851
|
+
}
|
|
4852
|
+
async function setupCodexMcpConfig(options) {
|
|
4853
|
+
const projectPath = options.projectConfigPath ?? getCodexProjectMcpConfigPath(options.repoRoot);
|
|
4854
|
+
const workspaceRoot = path20.resolve(options.repoRoot);
|
|
4855
|
+
const memoraone = await buildCodexMemoraoneServer({
|
|
4856
|
+
workspaceRoot,
|
|
4857
|
+
devMode: options.devMode,
|
|
4858
|
+
environment: options.environment,
|
|
4859
|
+
npmPackageChannel: options.npmPackageChannel,
|
|
4860
|
+
apiUrl: options.apiUrl,
|
|
4861
|
+
npxPathOverride: options.npxPathOverride,
|
|
4862
|
+
cliPathOverride: options.cliPathOverride
|
|
4863
|
+
});
|
|
4864
|
+
const exists = await pathExists6(projectPath);
|
|
4865
|
+
let existingRaw = null;
|
|
4866
|
+
if (exists) {
|
|
4867
|
+
existingRaw = await fs16.readFile(projectPath, "utf8");
|
|
4868
|
+
const parsed2 = parseCodexTomlOrThrow(existingRaw, projectPath);
|
|
4869
|
+
const current = extractMemoraoneFromCodexParsed(parsed2);
|
|
4870
|
+
if (memoraoneServerMatches4(current, memoraone)) {
|
|
4871
|
+
return { outcome: "skipped", memoraone };
|
|
4872
|
+
}
|
|
4873
|
+
}
|
|
4874
|
+
const next = mergeCodexMcpConfigToml(existingRaw, memoraone);
|
|
4875
|
+
if (options.dryRun) {
|
|
4876
|
+
return { outcome: exists ? "updated" : "created", memoraone };
|
|
4877
|
+
}
|
|
4878
|
+
await writeCodexTomlAtomic(projectPath, next);
|
|
4879
|
+
const verifyRaw = await fs16.readFile(projectPath, "utf8");
|
|
4880
|
+
const verifyParsed = parseCodexTomlOrThrow(verifyRaw, projectPath);
|
|
4881
|
+
validateCodexMcpConfig(verifyParsed, memoraone);
|
|
4882
|
+
return {
|
|
4883
|
+
outcome: exists ? "updated" : "created",
|
|
4884
|
+
memoraone
|
|
4885
|
+
};
|
|
4886
|
+
}
|
|
4887
|
+
function logCodexMcpCliSummary(info, dryRun, println = console.log) {
|
|
4888
|
+
const prefix = dryRun ? "would be " : "";
|
|
4889
|
+
if (info.outcome === "created") {
|
|
4890
|
+
println(`[setup-ide-files] Codex project MCP config ${prefix}created: ${info.activeConfigPath}`);
|
|
4891
|
+
} else if (info.outcome === "updated") {
|
|
4892
|
+
println(`[setup-ide-files] Codex project MCP config ${prefix}updated: ${info.activeConfigPath}`);
|
|
4893
|
+
} else {
|
|
4894
|
+
println(`[setup-ide-files] Codex project MCP config unchanged: ${info.activeConfigPath}`);
|
|
4895
|
+
}
|
|
4896
|
+
if (info.npxPath) {
|
|
4897
|
+
println(`[setup-ide-files] Resolved npx: ${info.npxPath}`);
|
|
4898
|
+
}
|
|
4899
|
+
println(`[setup-ide-files] Final active Codex MCP config: ${info.activeConfigPath}`);
|
|
4900
|
+
println(
|
|
4901
|
+
"[setup-ide-files] Trust this project in Codex if prompted, then restart Codex."
|
|
4902
|
+
);
|
|
4903
|
+
}
|
|
4904
|
+
|
|
4905
|
+
// src/openCursorMcpSettings.ts
|
|
4906
|
+
var import_node_child_process6 = require("child_process");
|
|
4907
|
+
var readline4 = __toESM(require("readline/promises"), 1);
|
|
4908
|
+
var import_node_util4 = require("util");
|
|
4909
|
+
|
|
4910
|
+
// src/terminalPresentation.ts
|
|
4911
|
+
var ANSI = {
|
|
4912
|
+
reset: "\x1B[0m",
|
|
4913
|
+
bold: "\x1B[1m",
|
|
4914
|
+
dim: "\x1B[2m",
|
|
4915
|
+
green: "\x1B[32m",
|
|
4916
|
+
yellow: "\x1B[33m",
|
|
4917
|
+
cyan: "\x1B[36m"
|
|
4918
|
+
};
|
|
4919
|
+
function isCiLikeEnv(env2 = process.env) {
|
|
4920
|
+
if (env2.CI === "true" || env2.CI === "1") return true;
|
|
4921
|
+
if (env2.GITHUB_ACTIONS === "true" || env2.GITHUB_ACTIONS === "1") return true;
|
|
4922
|
+
if (env2.GITLAB_CI === "true" || env2.GITLAB_CI === "1") return true;
|
|
4923
|
+
if (env2.CIRCLECI === "true" || env2.CIRCLECI === "1") return true;
|
|
4924
|
+
if (env2.BUILDKITE === "true" || env2.BUILDKITE === "1") return true;
|
|
4925
|
+
if (typeof env2.CI === "string" && env2.CI.trim() !== "" && env2.CI !== "0" && env2.CI !== "false") {
|
|
4926
|
+
return true;
|
|
4927
|
+
}
|
|
4928
|
+
return false;
|
|
4929
|
+
}
|
|
4930
|
+
function shouldEnableAnsiColor(opts = {}) {
|
|
4931
|
+
if (typeof opts.color === "boolean") return opts.color;
|
|
4932
|
+
const env2 = opts.env ?? process.env;
|
|
4933
|
+
if (env2.NO_COLOR !== void 0) return false;
|
|
4934
|
+
if (isCiLikeEnv(env2)) return false;
|
|
4935
|
+
const tty = opts.stdoutIsTty ?? process.stdout.isTTY === true;
|
|
4936
|
+
return tty;
|
|
4937
|
+
}
|
|
4938
|
+
function shouldUseUnicodeSymbols(opts = {}) {
|
|
4939
|
+
if (typeof opts.unicode === "boolean") return opts.unicode;
|
|
4940
|
+
const env2 = opts.env ?? process.env;
|
|
4941
|
+
const tty = opts.stdoutIsTty ?? process.stdout.isTTY === true;
|
|
4942
|
+
if (!tty) return false;
|
|
4943
|
+
if (env2.TERM === "dumb") return false;
|
|
4944
|
+
if (process.platform === "win32") {
|
|
4945
|
+
return Boolean(
|
|
4946
|
+
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"
|
|
4947
|
+
);
|
|
4948
|
+
}
|
|
4949
|
+
return true;
|
|
3758
4950
|
}
|
|
3759
4951
|
function paint(enabled, code, text) {
|
|
3760
4952
|
if (!enabled || text === "") return text;
|
|
@@ -3902,6 +5094,10 @@ async function runOpenCursorMcpSettingsFlow(deps = {}) {
|
|
|
3902
5094
|
}
|
|
3903
5095
|
|
|
3904
5096
|
// src/setupSuccessOutput.ts
|
|
5097
|
+
var CLAUDE_CODE_APPROVAL_FOLLOW_UP = "Approve the MemoraOne MCP server in Claude Code if prompted.";
|
|
5098
|
+
var WINDSURF_REFRESH_FOLLOW_UP = "Refresh MCP servers in Devin Desktop settings, or restart Devin Desktop.";
|
|
5099
|
+
var OPENCODE_RESTART_FOLLOW_UP = "Fully quit OpenCode and reopen this repo for MCP changes to take effect.";
|
|
5100
|
+
var CODEX_TRUST_FOLLOW_UP = "Trust this project in Codex if prompted, then restart Codex.";
|
|
3905
5101
|
var RESTART_LINE = "Restart your IDEs to finish setup.";
|
|
3906
5102
|
function formatSetupSuccessLines(opts = {}) {
|
|
3907
5103
|
const tp = opts.presentation ?? createTerminalPresentation(opts.presentationOptions ?? { color: false, unicode: true });
|
|
@@ -3919,13 +5115,39 @@ function formatSetupSuccessLines(opts = {}) {
|
|
|
3919
5115
|
if (targets.jetbrains) {
|
|
3920
5116
|
lines.push(tp.checkLine("JetBrains configured"));
|
|
3921
5117
|
}
|
|
3922
|
-
|
|
5118
|
+
if (targets.claudeCode) {
|
|
5119
|
+
lines.push(tp.checkLine("Claude Code configured"));
|
|
5120
|
+
}
|
|
5121
|
+
if (targets.windsurf) {
|
|
5122
|
+
lines.push(tp.checkLine("Devin Desktop configured"));
|
|
5123
|
+
}
|
|
5124
|
+
if (targets.opencode) {
|
|
5125
|
+
lines.push(tp.checkLine("OpenCode configured"));
|
|
5126
|
+
}
|
|
5127
|
+
if (targets.codex) {
|
|
5128
|
+
lines.push(tp.checkLine("Codex configured"));
|
|
5129
|
+
}
|
|
5130
|
+
const hasIde = Boolean(
|
|
5131
|
+
targets.cursor || targets.vscode || targets.jetbrains || targets.claudeCode || targets.windsurf || targets.opencode || targets.codex
|
|
5132
|
+
);
|
|
3923
5133
|
lines.push("");
|
|
3924
5134
|
lines.push(tp.checkLine("MemoraOne is ready"));
|
|
3925
5135
|
if (hasIde) {
|
|
3926
5136
|
lines.push("");
|
|
3927
5137
|
lines.push(RESTART_LINE);
|
|
3928
5138
|
}
|
|
5139
|
+
if (targets.claudeCode) {
|
|
5140
|
+
lines.push(CLAUDE_CODE_APPROVAL_FOLLOW_UP);
|
|
5141
|
+
}
|
|
5142
|
+
if (targets.windsurf) {
|
|
5143
|
+
lines.push(WINDSURF_REFRESH_FOLLOW_UP);
|
|
5144
|
+
}
|
|
5145
|
+
if (targets.opencode) {
|
|
5146
|
+
lines.push(OPENCODE_RESTART_FOLLOW_UP);
|
|
5147
|
+
}
|
|
5148
|
+
if (targets.codex) {
|
|
5149
|
+
lines.push(CODEX_TRUST_FOLLOW_UP);
|
|
5150
|
+
}
|
|
3929
5151
|
return lines;
|
|
3930
5152
|
}
|
|
3931
5153
|
function printSetupSuccess(opts, println = console.log) {
|
|
@@ -3942,13 +5164,14 @@ function buildMemoraoneMcpServer(ideType, options = {}) {
|
|
|
3942
5164
|
MEMORAONE_API_URL: resolveIdeApiUrl({ environment, apiUrl: options.apiUrl }),
|
|
3943
5165
|
MEMORAONE_IDE_TYPE: ideType
|
|
3944
5166
|
};
|
|
5167
|
+
const includeWorkspaceRoot = options.workspaceRoot !== void 0 && (environment === "local" || ideType === "cursor" || ideType === "claude-code");
|
|
5168
|
+
if (includeWorkspaceRoot) {
|
|
5169
|
+
env2[MEMORAONE_WORKSPACE_ROOT_ENV] = path21.resolve(options.workspaceRoot);
|
|
5170
|
+
}
|
|
3945
5171
|
if (environment === "local") {
|
|
3946
5172
|
if (!options.cliPath) {
|
|
3947
5173
|
throw new Error("[setup-ide-files] Local VS Code MCP config requires a built CLI path.");
|
|
3948
5174
|
}
|
|
3949
|
-
if (options.workspaceRoot !== void 0) {
|
|
3950
|
-
env2[MEMORAONE_WORKSPACE_ROOT_ENV] = path17.resolve(options.workspaceRoot);
|
|
3951
|
-
}
|
|
3952
5175
|
return {
|
|
3953
5176
|
command: "node",
|
|
3954
5177
|
args: [options.cliPath],
|
|
@@ -3963,15 +5186,15 @@ function buildMemoraoneMcpServer(ideType, options = {}) {
|
|
|
3963
5186
|
};
|
|
3964
5187
|
}
|
|
3965
5188
|
function assertUnderRepoRoot(repoRoot, absPath) {
|
|
3966
|
-
const normRoot =
|
|
3967
|
-
const normPath =
|
|
3968
|
-
if (normPath !==
|
|
5189
|
+
const normRoot = path21.resolve(repoRoot) + path21.sep;
|
|
5190
|
+
const normPath = path21.resolve(absPath);
|
|
5191
|
+
if (normPath !== path21.resolve(repoRoot) && !normPath.startsWith(normRoot)) {
|
|
3969
5192
|
throw new Error(`[setup-ide-files] Refusing to write outside repo root: ${absPath}`);
|
|
3970
5193
|
}
|
|
3971
5194
|
}
|
|
3972
|
-
async function
|
|
5195
|
+
async function pathExists7(filePath) {
|
|
3973
5196
|
try {
|
|
3974
|
-
await
|
|
5197
|
+
await fs17.access(filePath);
|
|
3975
5198
|
return true;
|
|
3976
5199
|
} catch {
|
|
3977
5200
|
return false;
|
|
@@ -3981,21 +5204,21 @@ async function ensureGitignoreMemoraone(_repoRoot, _opts) {
|
|
|
3981
5204
|
return "skipped";
|
|
3982
5205
|
}
|
|
3983
5206
|
async function findRepoRoot(startDir) {
|
|
3984
|
-
let current =
|
|
3985
|
-
const root =
|
|
5207
|
+
let current = path21.resolve(startDir);
|
|
5208
|
+
const root = path21.parse(current).root;
|
|
3986
5209
|
while (true) {
|
|
3987
|
-
const gitPath =
|
|
3988
|
-
const m1Path =
|
|
3989
|
-
if (await
|
|
5210
|
+
const gitPath = path21.join(current, ".git");
|
|
5211
|
+
const m1Path = path21.join(current, "memoraone.m1");
|
|
5212
|
+
if (await pathExists7(gitPath) || await pathExists7(m1Path)) {
|
|
3990
5213
|
return current;
|
|
3991
5214
|
}
|
|
3992
5215
|
if (current === root) {
|
|
3993
5216
|
return null;
|
|
3994
5217
|
}
|
|
3995
|
-
current =
|
|
5218
|
+
current = path21.dirname(current);
|
|
3996
5219
|
}
|
|
3997
5220
|
}
|
|
3998
|
-
function
|
|
5221
|
+
function stripLeadingLineComments4(text) {
|
|
3999
5222
|
return text.split("\n").filter((line) => !/^\s*\/\//.test(line)).join("\n");
|
|
4000
5223
|
}
|
|
4001
5224
|
function cursorRuleBody() {
|
|
@@ -4047,18 +5270,18 @@ function buildVscodeMcpJsonBody(existing, options = {}) {
|
|
|
4047
5270
|
return mcpJsonHeader() + JSON.stringify(merged, null, 2) + "\n";
|
|
4048
5271
|
}
|
|
4049
5272
|
async function writeSharedMcpJsonAtomic(filePath, content) {
|
|
4050
|
-
const dir =
|
|
4051
|
-
await
|
|
4052
|
-
const tmpPath =
|
|
5273
|
+
const dir = path21.dirname(filePath);
|
|
5274
|
+
await fs17.mkdir(dir, { recursive: true });
|
|
5275
|
+
const tmpPath = path21.join(
|
|
4053
5276
|
dir,
|
|
4054
|
-
`.${
|
|
5277
|
+
`.${path21.basename(filePath)}.${process.pid}.${(0, import_node_crypto8.randomBytes)(8).toString("hex")}.tmp`
|
|
4055
5278
|
);
|
|
4056
5279
|
try {
|
|
4057
|
-
await
|
|
4058
|
-
await
|
|
5280
|
+
await fs17.writeFile(tmpPath, content, "utf8");
|
|
5281
|
+
await fs17.rename(tmpPath, filePath);
|
|
4059
5282
|
} catch (err) {
|
|
4060
5283
|
try {
|
|
4061
|
-
await
|
|
5284
|
+
await fs17.unlink(tmpPath);
|
|
4062
5285
|
} catch {
|
|
4063
5286
|
}
|
|
4064
5287
|
throw err;
|
|
@@ -4077,43 +5300,79 @@ function buildCursorMcpJsonBody(existing, writeOptions) {
|
|
|
4077
5300
|
const merged = mergeCursorRepoMcpConfigObject(existing, writeOptions);
|
|
4078
5301
|
return mcpJsonHeader() + JSON.stringify(merged, null, 2) + "\n";
|
|
4079
5302
|
}
|
|
5303
|
+
function buildClaudeCodeMcpJsonBody(existing, writeOptions) {
|
|
5304
|
+
const merged = mergeClaudeCodeMcpConfigObject(existing, writeOptions);
|
|
5305
|
+
return JSON.stringify(merged, null, 2) + "\n";
|
|
5306
|
+
}
|
|
5307
|
+
async function writeClaudeCodeMcpJson(repoRoot, buildBody, opts) {
|
|
5308
|
+
const abs = getClaudeCodeMcpConfigPath(repoRoot);
|
|
5309
|
+
assertUnderRepoRoot(repoRoot, abs);
|
|
5310
|
+
let raw = "";
|
|
5311
|
+
let existed = false;
|
|
5312
|
+
try {
|
|
5313
|
+
raw = await fs17.readFile(abs, "utf8");
|
|
5314
|
+
existed = true;
|
|
5315
|
+
} catch (err) {
|
|
5316
|
+
if (err?.code !== "ENOENT") throw err;
|
|
5317
|
+
}
|
|
5318
|
+
if (!existed) {
|
|
5319
|
+
const body = buildBody(null);
|
|
5320
|
+
if (opts.dryRun) return "created";
|
|
5321
|
+
await writeSharedMcpJsonAtomic(abs, body);
|
|
5322
|
+
return "created";
|
|
5323
|
+
}
|
|
5324
|
+
let parsed2;
|
|
5325
|
+
try {
|
|
5326
|
+
parsed2 = JSON.parse(stripLeadingLineComments4(raw));
|
|
5327
|
+
} catch {
|
|
5328
|
+
throw new SharedMcpJsonParseError(abs);
|
|
5329
|
+
}
|
|
5330
|
+
if (!parsed2 || typeof parsed2 !== "object" || Array.isArray(parsed2)) {
|
|
5331
|
+
throw new SharedMcpJsonParseError(abs);
|
|
5332
|
+
}
|
|
5333
|
+
const next = buildBody(parsed2);
|
|
5334
|
+
if (next === raw) return "skipped";
|
|
5335
|
+
if (opts.dryRun) return "updated";
|
|
5336
|
+
await writeSharedMcpJsonAtomic(abs, next);
|
|
5337
|
+
return "updated";
|
|
5338
|
+
}
|
|
4080
5339
|
async function writeManagedMarkdown(repoRoot, relPath, fullContent, opts) {
|
|
4081
|
-
const abs =
|
|
5340
|
+
const abs = path21.join(repoRoot, relPath);
|
|
4082
5341
|
assertUnderRepoRoot(repoRoot, abs);
|
|
4083
5342
|
let prior = "";
|
|
4084
5343
|
let existed = false;
|
|
4085
5344
|
try {
|
|
4086
|
-
prior = await
|
|
5345
|
+
prior = await fs17.readFile(abs, "utf8");
|
|
4087
5346
|
existed = true;
|
|
4088
5347
|
} catch (err) {
|
|
4089
5348
|
if (err?.code !== "ENOENT") throw err;
|
|
4090
5349
|
}
|
|
4091
5350
|
if (!existed) {
|
|
4092
5351
|
if (opts.dryRun) return "created";
|
|
4093
|
-
await
|
|
4094
|
-
await
|
|
5352
|
+
await fs17.mkdir(path21.dirname(abs), { recursive: true });
|
|
5353
|
+
await fs17.writeFile(abs, fullContent, "utf8");
|
|
4095
5354
|
return "created";
|
|
4096
5355
|
}
|
|
4097
5356
|
if (prior.includes(MANAGED_MARKER)) {
|
|
4098
5357
|
if (prior === fullContent) return "skipped";
|
|
4099
5358
|
if (opts.dryRun) return "updated";
|
|
4100
|
-
await
|
|
4101
|
-
await
|
|
5359
|
+
await fs17.mkdir(path21.dirname(abs), { recursive: true });
|
|
5360
|
+
await fs17.writeFile(abs, fullContent, "utf8");
|
|
4102
5361
|
return "updated";
|
|
4103
5362
|
}
|
|
4104
5363
|
if (!opts.force) return "skipped-untracked";
|
|
4105
5364
|
if (opts.dryRun) return "updated";
|
|
4106
|
-
await
|
|
4107
|
-
await
|
|
5365
|
+
await fs17.mkdir(path21.dirname(abs), { recursive: true });
|
|
5366
|
+
await fs17.writeFile(abs, fullContent, "utf8");
|
|
4108
5367
|
return "updated";
|
|
4109
5368
|
}
|
|
4110
5369
|
async function writeIdeMcpJson(repoRoot, relPath, buildBody, opts) {
|
|
4111
|
-
const abs =
|
|
5370
|
+
const abs = path21.join(repoRoot, relPath);
|
|
4112
5371
|
assertUnderRepoRoot(repoRoot, abs);
|
|
4113
5372
|
let raw = "";
|
|
4114
5373
|
let existed = false;
|
|
4115
5374
|
try {
|
|
4116
|
-
raw = await
|
|
5375
|
+
raw = await fs17.readFile(abs, "utf8");
|
|
4117
5376
|
existed = true;
|
|
4118
5377
|
} catch (err) {
|
|
4119
5378
|
if (err?.code !== "ENOENT") throw err;
|
|
@@ -4128,7 +5387,7 @@ async function writeIdeMcpJson(repoRoot, relPath, buildBody, opts) {
|
|
|
4128
5387
|
if (!managed && !opts.force) return "skipped-untracked";
|
|
4129
5388
|
let parsed2;
|
|
4130
5389
|
try {
|
|
4131
|
-
parsed2 = JSON.parse(
|
|
5390
|
+
parsed2 = JSON.parse(stripLeadingLineComments4(raw));
|
|
4132
5391
|
} catch {
|
|
4133
5392
|
throw new SharedMcpJsonParseError(abs);
|
|
4134
5393
|
}
|
|
@@ -4145,6 +5404,10 @@ function parseSetupIdeFlags(argv) {
|
|
|
4145
5404
|
let cursor = false;
|
|
4146
5405
|
let vscode = false;
|
|
4147
5406
|
let jetbrains = false;
|
|
5407
|
+
let claudeCode = false;
|
|
5408
|
+
let windsurf = false;
|
|
5409
|
+
let opencode = false;
|
|
5410
|
+
let codex = false;
|
|
4148
5411
|
let all = false;
|
|
4149
5412
|
let force = false;
|
|
4150
5413
|
let dryRun = false;
|
|
@@ -4164,6 +5427,10 @@ function parseSetupIdeFlags(argv) {
|
|
|
4164
5427
|
if (a === "--cursor") cursor = true;
|
|
4165
5428
|
else if (a === "--vscode") vscode = true;
|
|
4166
5429
|
else if (a === "--jetbrains") jetbrains = true;
|
|
5430
|
+
else if (a === "--claude-code") claudeCode = true;
|
|
5431
|
+
else if (a === "--windsurf") windsurf = true;
|
|
5432
|
+
else if (a === "--opencode") opencode = true;
|
|
5433
|
+
else if (a === "--codex") codex = true;
|
|
4167
5434
|
else if (a === "--all") all = true;
|
|
4168
5435
|
else if (a === "--force") force = true;
|
|
4169
5436
|
else if (a === "--dry-run") dryRun = true;
|
|
@@ -4204,12 +5471,20 @@ function parseSetupIdeFlags(argv) {
|
|
|
4204
5471
|
}
|
|
4205
5472
|
} else if (a.startsWith("-")) unknown.push(a);
|
|
4206
5473
|
}
|
|
4207
|
-
const specific = cursor || vscode || jetbrains;
|
|
5474
|
+
const specific = cursor || vscode || jetbrains || claudeCode || windsurf || opencode || codex;
|
|
4208
5475
|
let targets;
|
|
4209
5476
|
if (all || !specific) {
|
|
4210
|
-
targets = {
|
|
5477
|
+
targets = {
|
|
5478
|
+
cursor: true,
|
|
5479
|
+
vscode: true,
|
|
5480
|
+
jetbrains: true,
|
|
5481
|
+
claudeCode: true,
|
|
5482
|
+
windsurf: true,
|
|
5483
|
+
opencode: true,
|
|
5484
|
+
codex: true
|
|
5485
|
+
};
|
|
4211
5486
|
} else {
|
|
4212
|
-
targets = { cursor, vscode, jetbrains };
|
|
5487
|
+
targets = { cursor, vscode, jetbrains, claudeCode, windsurf, opencode, codex };
|
|
4213
5488
|
}
|
|
4214
5489
|
if (!flagError && local && staging) {
|
|
4215
5490
|
flagError = "[setup-ide-files] --local and --staging are mutually exclusive.";
|
|
@@ -4253,6 +5528,15 @@ function logSetupIdeFilesVerboseSuccess(opts, println = console.log) {
|
|
|
4253
5528
|
if (targets.jetbrains && result.jetbrainsMcp) {
|
|
4254
5529
|
logJetBrainsMcpCliSummary(result.jetbrainsMcp, dryRun, println);
|
|
4255
5530
|
}
|
|
5531
|
+
if (targets.windsurf && result.windsurfMcp) {
|
|
5532
|
+
logWindsurfMcpCliSummary(result.windsurfMcp, dryRun, println);
|
|
5533
|
+
}
|
|
5534
|
+
if (targets.opencode && result.opencodeMcp) {
|
|
5535
|
+
logOpenCodeMcpCliSummary(result.opencodeMcp, dryRun, println);
|
|
5536
|
+
}
|
|
5537
|
+
if (targets.codex && result.codexMcp) {
|
|
5538
|
+
logCodexMcpCliSummary(result.codexMcp, dryRun, println);
|
|
5539
|
+
}
|
|
4256
5540
|
summarizeOutcomes(result.outcomes, println);
|
|
4257
5541
|
if (dryRun) {
|
|
4258
5542
|
println("[setup-ide-files] Dry run: no files written.");
|
|
@@ -4313,10 +5597,14 @@ function ideTypesFromSetupTargets(targets) {
|
|
|
4313
5597
|
if (targets.cursor) ides.push("cursor");
|
|
4314
5598
|
if (targets.vscode) ides.push("copilot-vscode");
|
|
4315
5599
|
if (targets.jetbrains) ides.push("jetbrains");
|
|
5600
|
+
if (targets.claudeCode) ides.push("claude-code");
|
|
5601
|
+
if (targets.windsurf) ides.push("windsurf");
|
|
5602
|
+
if (targets.opencode) ides.push("opencode");
|
|
5603
|
+
if (targets.codex) ides.push("codex");
|
|
4316
5604
|
return ides;
|
|
4317
5605
|
}
|
|
4318
5606
|
function setupTargetsAllIdes(targets) {
|
|
4319
|
-
return targets.cursor && targets.vscode && targets.jetbrains;
|
|
5607
|
+
return targets.cursor && targets.vscode && targets.jetbrains && targets.claudeCode && targets.windsurf && targets.opencode && targets.codex;
|
|
4320
5608
|
}
|
|
4321
5609
|
function aggregateCleanupResults(results) {
|
|
4322
5610
|
const killedPids = /* @__PURE__ */ new Set();
|
|
@@ -4436,8 +5724,18 @@ function restartIdeInstruction(targets) {
|
|
|
4436
5724
|
if (targets.cursor) names.push("Cursor");
|
|
4437
5725
|
if (targets.vscode) names.push("VS Code");
|
|
4438
5726
|
if (targets.jetbrains) names.push("JetBrains IDE");
|
|
5727
|
+
if (targets.claudeCode) names.push("Claude Code");
|
|
5728
|
+
if (targets.windsurf) names.push("Devin Desktop");
|
|
5729
|
+
if (targets.opencode) names.push("OpenCode");
|
|
5730
|
+
if (targets.codex) names.push("Codex");
|
|
4439
5731
|
if (names.length === 0) return "Fully quit your IDE and reopen this repo for MCP changes to take effect.";
|
|
4440
5732
|
if (names.length === 1) {
|
|
5733
|
+
if (targets.windsurf) {
|
|
5734
|
+
return "Refresh MCP servers in Devin Desktop settings, or restart Devin Desktop, for MCP changes to take effect.";
|
|
5735
|
+
}
|
|
5736
|
+
if (targets.codex) {
|
|
5737
|
+
return "Trust this project in Codex if prompted, then restart Codex.";
|
|
5738
|
+
}
|
|
4441
5739
|
return `Fully quit ${names[0]} and reopen this repo for MCP changes to take effect.`;
|
|
4442
5740
|
}
|
|
4443
5741
|
const last = names.pop();
|
|
@@ -4447,7 +5745,10 @@ async function runSetupIdeFiles(o) {
|
|
|
4447
5745
|
const outcomes = {};
|
|
4448
5746
|
let cursorMcp;
|
|
4449
5747
|
let jetbrainsMcp;
|
|
4450
|
-
|
|
5748
|
+
let windsurfMcp;
|
|
5749
|
+
let opencodeMcp;
|
|
5750
|
+
let codexMcp;
|
|
5751
|
+
const repoRoot = o.workspaceRoot !== void 0 && o.workspaceRoot !== "" ? path21.resolve(o.workspaceRoot) : await findRepoRoot(o.cwd);
|
|
4451
5752
|
if (!repoRoot) {
|
|
4452
5753
|
return {
|
|
4453
5754
|
exitCode: 1,
|
|
@@ -4669,12 +5970,203 @@ description: MemoraOne MCP \u2014 IDE agent instructions
|
|
|
4669
5970
|
{ force: o.force, dryRun: o.dryRun }
|
|
4670
5971
|
);
|
|
4671
5972
|
try {
|
|
4672
|
-
const homeDir = o.jetbrainsHomeDir ??
|
|
5973
|
+
const homeDir = o.jetbrainsHomeDir ?? os7.homedir();
|
|
4673
5974
|
const activePath = o.jetbrainsGlobalMcpConfigPath ?? getJetBrainsGlobalMcpConfigPath(homeDir);
|
|
4674
5975
|
const jetbrainsSetup = await setupJetBrainsMcpConfig({
|
|
4675
5976
|
homeDir,
|
|
4676
5977
|
repoRoot,
|
|
4677
|
-
globalConfigPath: activePath,
|
|
5978
|
+
globalConfigPath: activePath,
|
|
5979
|
+
dryRun: o.dryRun,
|
|
5980
|
+
// Local dogfood: --dev / local environment → node + built CLI.
|
|
5981
|
+
// Published staging/production: npx + channel-specific package spec.
|
|
5982
|
+
devMode: localOrDev,
|
|
5983
|
+
environment: localOrDev ? "local" : cursorEnvironment,
|
|
5984
|
+
npmPackageChannel,
|
|
5985
|
+
apiUrl: resolvedApiUrl2,
|
|
5986
|
+
repair: o.repair ?? false,
|
|
5987
|
+
verify: o.verifyHandshake ?? !o.dryRun,
|
|
5988
|
+
npxPathOverride: o.npxPathOverride,
|
|
5989
|
+
cliPathOverride: o.cliPathOverride
|
|
5990
|
+
});
|
|
5991
|
+
jetbrainsMcp = {
|
|
5992
|
+
activeConfigPath: activePath,
|
|
5993
|
+
outcome: jetbrainsSetup.outcome,
|
|
5994
|
+
npxPath: jetbrainsSetup.memoraone?.command,
|
|
5995
|
+
backupPath: jetbrainsSetup.backupPath,
|
|
5996
|
+
repairActions: jetbrainsSetup.repairActions,
|
|
5997
|
+
verifyOk: jetbrainsSetup.verifyOk,
|
|
5998
|
+
verifyDetail: jetbrainsSetup.verifyDetail
|
|
5999
|
+
};
|
|
6000
|
+
outcomes[`jetbrains-global:${activePath}`] = jetbrainsSetup.outcome;
|
|
6001
|
+
} catch (err) {
|
|
6002
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
6003
|
+
return {
|
|
6004
|
+
exitCode: 1,
|
|
6005
|
+
repoRoot,
|
|
6006
|
+
outcomes,
|
|
6007
|
+
cursorMcp,
|
|
6008
|
+
error: message
|
|
6009
|
+
};
|
|
6010
|
+
}
|
|
6011
|
+
}
|
|
6012
|
+
if (o.targets.claudeCode) {
|
|
6013
|
+
let npxPath = null;
|
|
6014
|
+
let cliPath;
|
|
6015
|
+
if (cursorEnvironment === "local") {
|
|
6016
|
+
let resolvedCliPath = o.cursorLocalCliPathOverride;
|
|
6017
|
+
if (resolvedCliPath === void 0) {
|
|
6018
|
+
resolvedCliPath = o.cliPathOverride;
|
|
6019
|
+
}
|
|
6020
|
+
if (resolvedCliPath === void 0) {
|
|
6021
|
+
resolvedCliPath = await resolveBuiltCliPathAsync();
|
|
6022
|
+
}
|
|
6023
|
+
if (!resolvedCliPath) {
|
|
6024
|
+
return {
|
|
6025
|
+
exitCode: 1,
|
|
6026
|
+
repoRoot,
|
|
6027
|
+
outcomes,
|
|
6028
|
+
cursorMcp,
|
|
6029
|
+
jetbrainsMcp,
|
|
6030
|
+
daemonCleanup,
|
|
6031
|
+
error: "[setup-ide-files] Local mode requires a built CLI at packages/mcp/dist/cli.cjs. Run pnpm build first."
|
|
6032
|
+
};
|
|
6033
|
+
}
|
|
6034
|
+
cliPath = resolvedCliPath;
|
|
6035
|
+
} else {
|
|
6036
|
+
if (o.npxPathOverride !== void 0) {
|
|
6037
|
+
npxPath = o.npxPathOverride;
|
|
6038
|
+
} else {
|
|
6039
|
+
npxPath = await resolveNpxPath();
|
|
6040
|
+
}
|
|
6041
|
+
if (!npxPath) {
|
|
6042
|
+
return {
|
|
6043
|
+
exitCode: 1,
|
|
6044
|
+
repoRoot,
|
|
6045
|
+
outcomes,
|
|
6046
|
+
cursorMcp,
|
|
6047
|
+
jetbrainsMcp,
|
|
6048
|
+
daemonCleanup,
|
|
6049
|
+
error: "[setup-ide-files] Could not resolve a working npx executable. Install Node.js/npm or ensure npx is on PATH before configuring Claude Code MCP."
|
|
6050
|
+
};
|
|
6051
|
+
}
|
|
6052
|
+
}
|
|
6053
|
+
const claudeWriteOptions = {
|
|
6054
|
+
environment: cursorEnvironment,
|
|
6055
|
+
npxPath: npxPath ?? void 0,
|
|
6056
|
+
cliPath,
|
|
6057
|
+
repoRoot,
|
|
6058
|
+
apiUrl: resolvedApiUrl2,
|
|
6059
|
+
npmPackageChannel
|
|
6060
|
+
};
|
|
6061
|
+
try {
|
|
6062
|
+
outcomes[".mcp.json"] = await writeClaudeCodeMcpJson(
|
|
6063
|
+
repoRoot,
|
|
6064
|
+
(existing) => buildClaudeCodeMcpJsonBody(existing, claudeWriteOptions),
|
|
6065
|
+
{ dryRun: o.dryRun }
|
|
6066
|
+
);
|
|
6067
|
+
} catch (err) {
|
|
6068
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
6069
|
+
return {
|
|
6070
|
+
exitCode: 1,
|
|
6071
|
+
repoRoot,
|
|
6072
|
+
outcomes,
|
|
6073
|
+
cursorMcp,
|
|
6074
|
+
jetbrainsMcp,
|
|
6075
|
+
windsurfMcp,
|
|
6076
|
+
daemonCleanup,
|
|
6077
|
+
error: message
|
|
6078
|
+
};
|
|
6079
|
+
}
|
|
6080
|
+
}
|
|
6081
|
+
if (o.targets.windsurf) {
|
|
6082
|
+
try {
|
|
6083
|
+
const homeDir = o.windsurfHomeDir ?? os7.homedir();
|
|
6084
|
+
const activePath = o.windsurfGlobalMcpConfigPath ?? getWindsurfGlobalMcpConfigPath(homeDir);
|
|
6085
|
+
const legacyPath = o.windsurfLegacyMcpConfigPath ?? getWindsurfLegacyMcpConfigPath(homeDir);
|
|
6086
|
+
const windsurfSetup = await setupWindsurfMcpConfig({
|
|
6087
|
+
homeDir,
|
|
6088
|
+
repoRoot,
|
|
6089
|
+
globalConfigPath: activePath,
|
|
6090
|
+
legacyConfigPath: legacyPath,
|
|
6091
|
+
dryRun: o.dryRun,
|
|
6092
|
+
// Local dogfood: --dev / local environment → node + built CLI.
|
|
6093
|
+
// Published staging/production: npx + channel-specific package spec.
|
|
6094
|
+
devMode: localOrDev,
|
|
6095
|
+
environment: localOrDev ? "local" : cursorEnvironment,
|
|
6096
|
+
npmPackageChannel,
|
|
6097
|
+
apiUrl: resolvedApiUrl2,
|
|
6098
|
+
npxPathOverride: o.npxPathOverride,
|
|
6099
|
+
cliPathOverride: o.cliPathOverride ?? o.cursorLocalCliPathOverride
|
|
6100
|
+
});
|
|
6101
|
+
windsurfMcp = {
|
|
6102
|
+
activeConfigPath: windsurfSetup.activeConfigPath,
|
|
6103
|
+
outcome: windsurfSetup.outcome,
|
|
6104
|
+
npxPath: windsurfSetup.memoraone?.command,
|
|
6105
|
+
backupPath: windsurfSetup.backupPath,
|
|
6106
|
+
legacyConfigPath: windsurfSetup.legacyConfigPath,
|
|
6107
|
+
legacyCleanup: windsurfSetup.legacyCleanup,
|
|
6108
|
+
warning: windsurfSetup.warning
|
|
6109
|
+
};
|
|
6110
|
+
outcomes[`windsurf-global:${activePath}`] = windsurfSetup.outcome;
|
|
6111
|
+
} catch (err) {
|
|
6112
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
6113
|
+
return {
|
|
6114
|
+
exitCode: 1,
|
|
6115
|
+
repoRoot,
|
|
6116
|
+
outcomes,
|
|
6117
|
+
cursorMcp,
|
|
6118
|
+
jetbrainsMcp,
|
|
6119
|
+
windsurfMcp,
|
|
6120
|
+
opencodeMcp,
|
|
6121
|
+
daemonCleanup,
|
|
6122
|
+
error: message
|
|
6123
|
+
};
|
|
6124
|
+
}
|
|
6125
|
+
}
|
|
6126
|
+
if (o.targets.opencode) {
|
|
6127
|
+
try {
|
|
6128
|
+
const activePath = o.opencodeProjectMcpConfigPath ?? getOpenCodeProjectMcpConfigPath(repoRoot);
|
|
6129
|
+
const opencodeSetup = await setupOpenCodeMcpConfig({
|
|
6130
|
+
repoRoot,
|
|
6131
|
+
projectConfigPath: activePath,
|
|
6132
|
+
dryRun: o.dryRun,
|
|
6133
|
+
// Local dogfood: --dev / local environment → node + built CLI.
|
|
6134
|
+
// Published staging/production: npx + channel-specific package spec.
|
|
6135
|
+
devMode: localOrDev,
|
|
6136
|
+
environment: localOrDev ? "local" : cursorEnvironment,
|
|
6137
|
+
npmPackageChannel,
|
|
6138
|
+
apiUrl: resolvedApiUrl2,
|
|
6139
|
+
npxPathOverride: o.npxPathOverride,
|
|
6140
|
+
cliPathOverride: o.cliPathOverride ?? o.cursorLocalCliPathOverride
|
|
6141
|
+
});
|
|
6142
|
+
opencodeMcp = {
|
|
6143
|
+
activeConfigPath: activePath,
|
|
6144
|
+
outcome: opencodeSetup.outcome,
|
|
6145
|
+
npxPath: opencodeSetup.memoraone?.command[0]
|
|
6146
|
+
};
|
|
6147
|
+
outcomes["opencode.json"] = opencodeSetup.outcome;
|
|
6148
|
+
} catch (err) {
|
|
6149
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
6150
|
+
return {
|
|
6151
|
+
exitCode: 1,
|
|
6152
|
+
repoRoot,
|
|
6153
|
+
outcomes,
|
|
6154
|
+
cursorMcp,
|
|
6155
|
+
jetbrainsMcp,
|
|
6156
|
+
windsurfMcp,
|
|
6157
|
+
opencodeMcp,
|
|
6158
|
+
codexMcp,
|
|
6159
|
+
daemonCleanup,
|
|
6160
|
+
error: message
|
|
6161
|
+
};
|
|
6162
|
+
}
|
|
6163
|
+
}
|
|
6164
|
+
if (o.targets.codex) {
|
|
6165
|
+
try {
|
|
6166
|
+
const activePath = o.codexProjectMcpConfigPath ?? getCodexProjectMcpConfigPath(repoRoot);
|
|
6167
|
+
const codexSetup = await setupCodexMcpConfig({
|
|
6168
|
+
repoRoot,
|
|
6169
|
+
projectConfigPath: activePath,
|
|
4678
6170
|
dryRun: o.dryRun,
|
|
4679
6171
|
// Local dogfood: --dev / local environment → node + built CLI.
|
|
4680
6172
|
// Published staging/production: npx + channel-specific package spec.
|
|
@@ -4682,21 +6174,15 @@ description: MemoraOne MCP \u2014 IDE agent instructions
|
|
|
4682
6174
|
environment: localOrDev ? "local" : cursorEnvironment,
|
|
4683
6175
|
npmPackageChannel,
|
|
4684
6176
|
apiUrl: resolvedApiUrl2,
|
|
4685
|
-
repair: o.repair ?? false,
|
|
4686
|
-
verify: o.verifyHandshake ?? !o.dryRun,
|
|
4687
6177
|
npxPathOverride: o.npxPathOverride,
|
|
4688
|
-
cliPathOverride: o.cliPathOverride
|
|
6178
|
+
cliPathOverride: o.cliPathOverride ?? o.cursorLocalCliPathOverride
|
|
4689
6179
|
});
|
|
4690
|
-
|
|
6180
|
+
codexMcp = {
|
|
4691
6181
|
activeConfigPath: activePath,
|
|
4692
|
-
outcome:
|
|
4693
|
-
npxPath:
|
|
4694
|
-
backupPath: jetbrainsSetup.backupPath,
|
|
4695
|
-
repairActions: jetbrainsSetup.repairActions,
|
|
4696
|
-
verifyOk: jetbrainsSetup.verifyOk,
|
|
4697
|
-
verifyDetail: jetbrainsSetup.verifyDetail
|
|
6182
|
+
outcome: codexSetup.outcome,
|
|
6183
|
+
npxPath: codexSetup.memoraone?.command
|
|
4698
6184
|
};
|
|
4699
|
-
outcomes[
|
|
6185
|
+
outcomes[".codex/config.toml"] = codexSetup.outcome;
|
|
4700
6186
|
} catch (err) {
|
|
4701
6187
|
const message = err instanceof Error ? err.message : String(err);
|
|
4702
6188
|
return {
|
|
@@ -4704,11 +6190,26 @@ description: MemoraOne MCP \u2014 IDE agent instructions
|
|
|
4704
6190
|
repoRoot,
|
|
4705
6191
|
outcomes,
|
|
4706
6192
|
cursorMcp,
|
|
6193
|
+
jetbrainsMcp,
|
|
6194
|
+
windsurfMcp,
|
|
6195
|
+
opencodeMcp,
|
|
6196
|
+
codexMcp,
|
|
6197
|
+
daemonCleanup,
|
|
4707
6198
|
error: message
|
|
4708
6199
|
};
|
|
4709
6200
|
}
|
|
4710
6201
|
}
|
|
4711
|
-
return {
|
|
6202
|
+
return {
|
|
6203
|
+
exitCode: 0,
|
|
6204
|
+
repoRoot,
|
|
6205
|
+
outcomes,
|
|
6206
|
+
cursorMcp,
|
|
6207
|
+
jetbrainsMcp,
|
|
6208
|
+
windsurfMcp,
|
|
6209
|
+
opencodeMcp,
|
|
6210
|
+
codexMcp,
|
|
6211
|
+
daemonCleanup
|
|
6212
|
+
};
|
|
4712
6213
|
}
|
|
4713
6214
|
async function cliSetupIdeFiles(argv, options = {}) {
|
|
4714
6215
|
const {
|
|
@@ -4818,85 +6319,13 @@ async function cliSetupIdeFiles(argv, options = {}) {
|
|
|
4818
6319
|
}
|
|
4819
6320
|
|
|
4820
6321
|
// src/localState/connectCommand.ts
|
|
4821
|
-
var
|
|
4822
|
-
var
|
|
4823
|
-
|
|
4824
|
-
// src/config.ts
|
|
4825
|
-
var process2 = __toESM(require("process"), 1);
|
|
4826
|
-
var fs14 = __toESM(require("fs"), 1);
|
|
4827
|
-
var path18 = __toESM(require("path"), 1);
|
|
4828
|
-
var dotenv = __toESM(require("dotenv"), 1);
|
|
4829
|
-
var import_v4 = require("zod/v4");
|
|
4830
|
-
var dotenvPath = path18.resolve(process2.cwd(), ".env");
|
|
4831
|
-
if (fs14.existsSync(dotenvPath)) {
|
|
4832
|
-
try {
|
|
4833
|
-
dotenv.config({ path: dotenvPath });
|
|
4834
|
-
} catch (err) {
|
|
4835
|
-
process2.stderr.write("[memoraone-mcp] Failed to load .env: " + String(err) + "\n");
|
|
4836
|
-
}
|
|
4837
|
-
}
|
|
4838
|
-
var EnvSchema = import_v4.z.object({
|
|
4839
|
-
MEMORAONE_API_URL: import_v4.z.string().url().optional(),
|
|
4840
|
-
MEMORAONE_API_KEY: import_v4.z.string().min(1).optional(),
|
|
4841
|
-
MEMORAONE_DEV_MODE: import_v4.z.string().min(1).optional(),
|
|
4842
|
-
MEMORAONE_AGENT_NAME: import_v4.z.string().min(1).optional(),
|
|
4843
|
-
MEMORAONE_AGENT_TYPE: import_v4.z.string().min(1).optional(),
|
|
4844
|
-
MEMORAONE_SOURCE: import_v4.z.string().min(1).optional(),
|
|
4845
|
-
MEMORAONE_IDE_TYPE: import_v4.z.enum(["cursor", "copilot-vscode", "jetbrains"]).optional(),
|
|
4846
|
-
MEMORAONE_WORKLOG: import_v4.z.string().min(1).optional(),
|
|
4847
|
-
MEMORAONE_HEARTBEAT: import_v4.z.string().min(1).optional(),
|
|
4848
|
-
MEMORAONE_HEARTBEAT_INTERVAL_MS: import_v4.z.string().min(1).optional()
|
|
4849
|
-
});
|
|
4850
|
-
var requiredEnvVars = [];
|
|
4851
|
-
var missingEnvVars = requiredEnvVars.filter((key) => {
|
|
4852
|
-
const value = process2.env[key];
|
|
4853
|
-
return value === void 0 || value.trim() === "";
|
|
4854
|
-
});
|
|
4855
|
-
if (missingEnvVars.length > 0) {
|
|
4856
|
-
for (const key of missingEnvVars) {
|
|
4857
|
-
process2.stderr.write(`Missing ${key}
|
|
4858
|
-
`);
|
|
4859
|
-
}
|
|
4860
|
-
process2.exit(1);
|
|
4861
|
-
}
|
|
4862
|
-
var parsed = EnvSchema.safeParse(process2.env);
|
|
4863
|
-
var resolvedApiUrl = resolveApiUrl(process2.env);
|
|
4864
|
-
if (!parsed.success) {
|
|
4865
|
-
const formatted = parsed.error.format();
|
|
4866
|
-
process2.stderr.write(
|
|
4867
|
-
"[memoraone-mcp] Invalid environment variables " + JSON.stringify(formatted) + "\n"
|
|
4868
|
-
);
|
|
4869
|
-
throw new Error("Config validation failed");
|
|
4870
|
-
}
|
|
4871
|
-
var parseBooleanFlag2 = (value, defaultValue) => {
|
|
4872
|
-
if (value === void 0) {
|
|
4873
|
-
return defaultValue;
|
|
4874
|
-
}
|
|
4875
|
-
const normalized = value.trim().toLowerCase();
|
|
4876
|
-
if (["1", "true", "yes", "on"].includes(normalized)) {
|
|
4877
|
-
return true;
|
|
4878
|
-
}
|
|
4879
|
-
if (["0", "false", "no", "off"].includes(normalized)) {
|
|
4880
|
-
return false;
|
|
4881
|
-
}
|
|
4882
|
-
return defaultValue;
|
|
4883
|
-
};
|
|
4884
|
-
var config2 = {
|
|
4885
|
-
apiUrl: resolvedApiUrl.replace(/\/+$/, ""),
|
|
4886
|
-
apiKey: parsed.data.MEMORAONE_API_KEY,
|
|
4887
|
-
agentName: parsed.data.MEMORAONE_AGENT_NAME ?? "cursor",
|
|
4888
|
-
agentType: parsed.data.MEMORAONE_AGENT_TYPE ?? "agent",
|
|
4889
|
-
source: parsed.data.MEMORAONE_SOURCE ?? "cursor",
|
|
4890
|
-
ideType: parsed.data.MEMORAONE_IDE_TYPE,
|
|
4891
|
-
devMode: parseBooleanFlag2(parsed.data.MEMORAONE_DEV_MODE, false),
|
|
4892
|
-
worklogEnabled: parseBooleanFlag2(parsed.data.MEMORAONE_WORKLOG, true),
|
|
4893
|
-
heartbeatEnabled: parseBooleanFlag2(parsed.data.MEMORAONE_HEARTBEAT, true),
|
|
4894
|
-
heartbeatIntervalMs: Number.parseInt(parsed.data.MEMORAONE_HEARTBEAT_INTERVAL_MS ?? "30000", 10)
|
|
4895
|
-
};
|
|
6322
|
+
var fs20 = __toESM(require("fs/promises"), 1);
|
|
6323
|
+
var path24 = __toESM(require("path"), 1);
|
|
6324
|
+
var os8 = __toESM(require("os"), 1);
|
|
4896
6325
|
|
|
4897
6326
|
// src/repoFingerprint.ts
|
|
4898
|
-
var
|
|
4899
|
-
var
|
|
6327
|
+
var fs18 = __toESM(require("fs"), 1);
|
|
6328
|
+
var path22 = __toESM(require("path"), 1);
|
|
4900
6329
|
var crypto2 = __toESM(require("crypto"), 1);
|
|
4901
6330
|
var parseBooleanFlag3 = (value) => {
|
|
4902
6331
|
if (!value) {
|
|
@@ -4926,16 +6355,16 @@ var sha256 = (value) => {
|
|
|
4926
6355
|
};
|
|
4927
6356
|
var resolveGitDir = (gitPath) => {
|
|
4928
6357
|
try {
|
|
4929
|
-
const stat4 =
|
|
6358
|
+
const stat4 = fs18.statSync(gitPath);
|
|
4930
6359
|
if (stat4.isDirectory()) {
|
|
4931
6360
|
return gitPath;
|
|
4932
6361
|
}
|
|
4933
6362
|
if (stat4.isFile()) {
|
|
4934
|
-
const content =
|
|
6363
|
+
const content = fs18.readFileSync(gitPath, "utf8");
|
|
4935
6364
|
const match = content.match(/^gitdir:\s*(.+)$/m);
|
|
4936
6365
|
if (match) {
|
|
4937
6366
|
const gitDir = match[1].trim();
|
|
4938
|
-
return
|
|
6367
|
+
return path22.resolve(path22.dirname(gitPath), gitDir);
|
|
4939
6368
|
}
|
|
4940
6369
|
}
|
|
4941
6370
|
} catch {
|
|
@@ -4944,16 +6373,16 @@ var resolveGitDir = (gitPath) => {
|
|
|
4944
6373
|
return null;
|
|
4945
6374
|
};
|
|
4946
6375
|
var findGitRoot = (start) => {
|
|
4947
|
-
let current =
|
|
6376
|
+
let current = path22.resolve(start);
|
|
4948
6377
|
while (true) {
|
|
4949
|
-
const gitPath =
|
|
4950
|
-
if (
|
|
6378
|
+
const gitPath = path22.join(current, ".git");
|
|
6379
|
+
if (fs18.existsSync(gitPath)) {
|
|
4951
6380
|
const gitDir = resolveGitDir(gitPath);
|
|
4952
6381
|
if (gitDir) {
|
|
4953
6382
|
return { gitRoot: current, gitDir };
|
|
4954
6383
|
}
|
|
4955
6384
|
}
|
|
4956
|
-
const parent =
|
|
6385
|
+
const parent = path22.dirname(current);
|
|
4957
6386
|
if (parent === current) {
|
|
4958
6387
|
break;
|
|
4959
6388
|
}
|
|
@@ -4962,9 +6391,9 @@ var findGitRoot = (start) => {
|
|
|
4962
6391
|
return null;
|
|
4963
6392
|
};
|
|
4964
6393
|
var readOriginRemote = (gitDir) => {
|
|
4965
|
-
const configPath =
|
|
6394
|
+
const configPath = path22.join(gitDir, "config");
|
|
4966
6395
|
try {
|
|
4967
|
-
const content =
|
|
6396
|
+
const content = fs18.readFileSync(configPath, "utf8");
|
|
4968
6397
|
const lines = content.split(/\r?\n/);
|
|
4969
6398
|
let inOrigin = false;
|
|
4970
6399
|
for (const line of lines) {
|
|
@@ -4988,7 +6417,7 @@ var readOriginRemote = (gitDir) => {
|
|
|
4988
6417
|
function resolveRepoFingerprint(cwd2) {
|
|
4989
6418
|
const found = findGitRoot(cwd2);
|
|
4990
6419
|
if (!found) {
|
|
4991
|
-
const fallbackPath =
|
|
6420
|
+
const fallbackPath = path22.resolve(cwd2);
|
|
4992
6421
|
const fingerprint2 = sha256(fallbackPath);
|
|
4993
6422
|
debugLog(`repo fingerprint=${fingerprint2} source=path-fallback`);
|
|
4994
6423
|
return {
|
|
@@ -5010,7 +6439,7 @@ function resolveRepoFingerprint(cwd2) {
|
|
|
5010
6439
|
source: "git-remote"
|
|
5011
6440
|
};
|
|
5012
6441
|
}
|
|
5013
|
-
const fingerprint = sha256(
|
|
6442
|
+
const fingerprint = sha256(path22.resolve(gitRoot));
|
|
5014
6443
|
debugLog(`repo fingerprint=${fingerprint} source=path-fallback`);
|
|
5015
6444
|
return {
|
|
5016
6445
|
fingerprint,
|
|
@@ -5019,6 +6448,188 @@ function resolveRepoFingerprint(cwd2) {
|
|
|
5019
6448
|
};
|
|
5020
6449
|
}
|
|
5021
6450
|
|
|
6451
|
+
// src/localState/replaceRootLocalState.ts
|
|
6452
|
+
var fs19 = __toESM(require("fs/promises"), 1);
|
|
6453
|
+
var path23 = __toESM(require("path"), 1);
|
|
6454
|
+
var WORKSPACE_MAP_FILENAME = "workspaces.json";
|
|
6455
|
+
var FINGERPRINT_REGEX = /^[0-9a-f]{64}$/i;
|
|
6456
|
+
function canonicalizeWorkspaceRoot(workspaceRoot) {
|
|
6457
|
+
return path23.resolve(workspaceRoot);
|
|
6458
|
+
}
|
|
6459
|
+
function getWorkspaceMapPathForHome(homeDir) {
|
|
6460
|
+
return path23.join(getMemoraoneStateDir(homeDir), WORKSPACE_MAP_FILENAME);
|
|
6461
|
+
}
|
|
6462
|
+
async function loadWorkspaceMapFile(homeDir) {
|
|
6463
|
+
const filePath = getWorkspaceMapPathForHome(homeDir);
|
|
6464
|
+
const raw = await readJsonFile(filePath);
|
|
6465
|
+
if (raw == null) return {};
|
|
6466
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
6467
|
+
throw new Error(`[memoraone-mcp] Corrupt ${WORKSPACE_MAP_FILENAME}`);
|
|
6468
|
+
}
|
|
6469
|
+
return { ...raw };
|
|
6470
|
+
}
|
|
6471
|
+
async function saveWorkspaceMapFile(map, homeDir) {
|
|
6472
|
+
await writeJsonAtomic(getWorkspaceMapPathForHome(homeDir), map);
|
|
6473
|
+
}
|
|
6474
|
+
function rootsMatch(a, b) {
|
|
6475
|
+
return path23.resolve(a) === path23.resolve(b);
|
|
6476
|
+
}
|
|
6477
|
+
async function collectRootAssociatedBindingIds(workspaceRoot, options = {}) {
|
|
6478
|
+
const canonicalRoot = canonicalizeWorkspaceRoot(workspaceRoot);
|
|
6479
|
+
const identity = await captureRootFilesystemIdentity(canonicalRoot, options.identityDeps);
|
|
6480
|
+
const identityKey = filesystemIdentityKey(identity);
|
|
6481
|
+
const bindingIds = /* @__PURE__ */ new Set();
|
|
6482
|
+
const index = await loadPathIndex(options.homeDir);
|
|
6483
|
+
const byPathId = index.byPath[canonicalRoot];
|
|
6484
|
+
if (byPathId) bindingIds.add(byPathId);
|
|
6485
|
+
const byIdentityId = index.byIdentity[identityKey];
|
|
6486
|
+
if (byIdentityId) bindingIds.add(byIdentityId);
|
|
6487
|
+
for (const [p, id] of Object.entries(index.byPath)) {
|
|
6488
|
+
if (rootsMatch(p, canonicalRoot)) {
|
|
6489
|
+
bindingIds.add(id);
|
|
6490
|
+
}
|
|
6491
|
+
}
|
|
6492
|
+
const records = await listBindingRecords(options.homeDir);
|
|
6493
|
+
for (const record of records) {
|
|
6494
|
+
if (rootsMatch(record.workspaceRoot, canonicalRoot)) {
|
|
6495
|
+
bindingIds.add(record.repositoryBindingId);
|
|
6496
|
+
continue;
|
|
6497
|
+
}
|
|
6498
|
+
if (identitiesMatch(record.filesystemIdentity, identity)) {
|
|
6499
|
+
bindingIds.add(record.repositoryBindingId);
|
|
6500
|
+
}
|
|
6501
|
+
}
|
|
6502
|
+
return { canonicalRoot, identity, bindingIds };
|
|
6503
|
+
}
|
|
6504
|
+
async function snapshotAndRemoveRootLocalState(workspaceRoot, options = {}) {
|
|
6505
|
+
const { canonicalRoot, identity, bindingIds } = await collectRootAssociatedBindingIds(
|
|
6506
|
+
workspaceRoot,
|
|
6507
|
+
options
|
|
6508
|
+
);
|
|
6509
|
+
const identityKey = filesystemIdentityKey(identity);
|
|
6510
|
+
const bindingRecords = [];
|
|
6511
|
+
const credentials = [];
|
|
6512
|
+
for (const id of bindingIds) {
|
|
6513
|
+
const record = await readBindingRecord(id, options.homeDir);
|
|
6514
|
+
if (record) bindingRecords.push(record);
|
|
6515
|
+
try {
|
|
6516
|
+
const creds = await readInstallationCredentials(id, options.credentialOptions);
|
|
6517
|
+
if (creds) credentials.push(creds);
|
|
6518
|
+
} catch {
|
|
6519
|
+
}
|
|
6520
|
+
}
|
|
6521
|
+
const fingerprints = /* @__PURE__ */ new Set();
|
|
6522
|
+
for (const fp of options.extraFingerprints ?? []) {
|
|
6523
|
+
if (FINGERPRINT_REGEX.test(fp)) fingerprints.add(fp.toLowerCase());
|
|
6524
|
+
}
|
|
6525
|
+
for (const record of bindingRecords) {
|
|
6526
|
+
if (FINGERPRINT_REGEX.test(record.rootFingerprint)) {
|
|
6527
|
+
fingerprints.add(record.rootFingerprint.toLowerCase());
|
|
6528
|
+
}
|
|
6529
|
+
}
|
|
6530
|
+
const removedPathEntries = {};
|
|
6531
|
+
const removedIdentityEntries = {};
|
|
6532
|
+
await withLocalLock(
|
|
6533
|
+
"path-index",
|
|
6534
|
+
async () => {
|
|
6535
|
+
const index = await loadPathIndex(options.homeDir);
|
|
6536
|
+
for (const [p, id] of Object.entries(index.byPath)) {
|
|
6537
|
+
if (bindingIds.has(id) || rootsMatch(p, canonicalRoot)) {
|
|
6538
|
+
removedPathEntries[p] = id;
|
|
6539
|
+
delete index.byPath[p];
|
|
6540
|
+
}
|
|
6541
|
+
}
|
|
6542
|
+
for (const [key, id] of Object.entries(index.byIdentity)) {
|
|
6543
|
+
if (bindingIds.has(id) || key === identityKey) {
|
|
6544
|
+
removedIdentityEntries[key] = id;
|
|
6545
|
+
delete index.byIdentity[key];
|
|
6546
|
+
}
|
|
6547
|
+
}
|
|
6548
|
+
await savePathIndex(index, options.homeDir);
|
|
6549
|
+
},
|
|
6550
|
+
{ homeDir: options.homeDir }
|
|
6551
|
+
);
|
|
6552
|
+
const removedWorkspaceMapEntries = {};
|
|
6553
|
+
if (fingerprints.size > 0) {
|
|
6554
|
+
const map = await loadWorkspaceMapFile(options.homeDir);
|
|
6555
|
+
let changed = false;
|
|
6556
|
+
for (const fp of fingerprints) {
|
|
6557
|
+
const key = Object.keys(map).find((k) => k.toLowerCase() === fp) ?? fp;
|
|
6558
|
+
if (key in map) {
|
|
6559
|
+
removedWorkspaceMapEntries[key] = map[key];
|
|
6560
|
+
delete map[key];
|
|
6561
|
+
changed = true;
|
|
6562
|
+
}
|
|
6563
|
+
}
|
|
6564
|
+
if (changed) {
|
|
6565
|
+
await saveWorkspaceMapFile(map, options.homeDir);
|
|
6566
|
+
}
|
|
6567
|
+
}
|
|
6568
|
+
for (const id of bindingIds) {
|
|
6569
|
+
try {
|
|
6570
|
+
await deleteInstallationCredentials(id, options.credentialOptions);
|
|
6571
|
+
} catch {
|
|
6572
|
+
}
|
|
6573
|
+
try {
|
|
6574
|
+
await fs19.unlink(getBindingFilePath(id, options.homeDir));
|
|
6575
|
+
} catch (err) {
|
|
6576
|
+
if (err?.code !== "ENOENT") throw err;
|
|
6577
|
+
}
|
|
6578
|
+
}
|
|
6579
|
+
return {
|
|
6580
|
+
canonicalRoot,
|
|
6581
|
+
identity,
|
|
6582
|
+
bindingIds: [...bindingIds],
|
|
6583
|
+
bindingRecords,
|
|
6584
|
+
removedPathEntries,
|
|
6585
|
+
removedIdentityEntries,
|
|
6586
|
+
removedWorkspaceMapEntries,
|
|
6587
|
+
credentials
|
|
6588
|
+
};
|
|
6589
|
+
}
|
|
6590
|
+
async function restoreRootLocalState(snapshot, options = {}) {
|
|
6591
|
+
for (const id of options.discardBindingIds ?? []) {
|
|
6592
|
+
try {
|
|
6593
|
+
await deleteInstallationCredentials(id, options.credentialOptions);
|
|
6594
|
+
} catch {
|
|
6595
|
+
}
|
|
6596
|
+
try {
|
|
6597
|
+
await fs19.unlink(getBindingFilePath(id, options.homeDir));
|
|
6598
|
+
} catch (err) {
|
|
6599
|
+
if (err?.code !== "ENOENT") throw err;
|
|
6600
|
+
}
|
|
6601
|
+
}
|
|
6602
|
+
for (const record of snapshot.bindingRecords) {
|
|
6603
|
+
await writeBindingRecord(record, options.homeDir);
|
|
6604
|
+
}
|
|
6605
|
+
await withLocalLock(
|
|
6606
|
+
"path-index",
|
|
6607
|
+
async () => {
|
|
6608
|
+
const index = await loadPathIndex(options.homeDir);
|
|
6609
|
+
for (const id of options.discardBindingIds ?? []) {
|
|
6610
|
+
for (const [p, mappedId] of Object.entries(index.byPath)) {
|
|
6611
|
+
if (mappedId === id) delete index.byPath[p];
|
|
6612
|
+
}
|
|
6613
|
+
for (const [key, mappedId] of Object.entries(index.byIdentity)) {
|
|
6614
|
+
if (mappedId === id) delete index.byIdentity[key];
|
|
6615
|
+
}
|
|
6616
|
+
}
|
|
6617
|
+
Object.assign(index.byPath, snapshot.removedPathEntries);
|
|
6618
|
+
Object.assign(index.byIdentity, snapshot.removedIdentityEntries);
|
|
6619
|
+
await savePathIndex(index, options.homeDir);
|
|
6620
|
+
},
|
|
6621
|
+
{ homeDir: options.homeDir }
|
|
6622
|
+
);
|
|
6623
|
+
if (Object.keys(snapshot.removedWorkspaceMapEntries).length > 0) {
|
|
6624
|
+
const map = await loadWorkspaceMapFile(options.homeDir);
|
|
6625
|
+
Object.assign(map, snapshot.removedWorkspaceMapEntries);
|
|
6626
|
+
await saveWorkspaceMapFile(map, options.homeDir);
|
|
6627
|
+
}
|
|
6628
|
+
for (const creds of snapshot.credentials) {
|
|
6629
|
+
await writeInstallationCredentials(creds, options.credentialOptions);
|
|
6630
|
+
}
|
|
6631
|
+
}
|
|
6632
|
+
|
|
5022
6633
|
// src/localState/connectCommand.ts
|
|
5023
6634
|
function normalizeConnectCode(code) {
|
|
5024
6635
|
const trimmed = code.trim();
|
|
@@ -5036,12 +6647,29 @@ function normalizeGitRemote(remoteUrl) {
|
|
|
5036
6647
|
normalized = normalized.replace(/\/+$/, "");
|
|
5037
6648
|
return normalized.toLowerCase() || null;
|
|
5038
6649
|
}
|
|
6650
|
+
async function detectLegacyM1Warning2(workspaceRoot) {
|
|
6651
|
+
const candidate = path24.join(path24.resolve(workspaceRoot), CANONICAL_M1_FILENAME);
|
|
6652
|
+
try {
|
|
6653
|
+
await fs20.access(candidate);
|
|
6654
|
+
return candidate;
|
|
6655
|
+
} catch {
|
|
6656
|
+
return void 0;
|
|
6657
|
+
}
|
|
6658
|
+
}
|
|
6659
|
+
async function rollbackFailedConnect(options) {
|
|
6660
|
+
await restoreRootLocalState(options.snapshot, {
|
|
6661
|
+
homeDir: options.homeDir,
|
|
6662
|
+
credentialOptions: options.credentialOptions,
|
|
6663
|
+
discardBindingIds: [options.discardBindingId]
|
|
6664
|
+
});
|
|
6665
|
+
}
|
|
5039
6666
|
async function runConnectCommand(options) {
|
|
5040
6667
|
const code = normalizeConnectCode(options.code);
|
|
5041
|
-
const cwd2 =
|
|
6668
|
+
const cwd2 = path24.resolve(options.cwd ?? process.cwd());
|
|
5042
6669
|
const apiUrl = (options.apiUrl ?? config2.apiUrl).replace(/\/+$/, "");
|
|
5043
|
-
const homeDir = options.homeDir ??
|
|
6670
|
+
const homeDir = options.homeDir ?? os8.homedir();
|
|
5044
6671
|
const environment = "local";
|
|
6672
|
+
const verifyLocalBinding = options.verifyLocalBinding ?? resolveLocalBinding;
|
|
5045
6673
|
const executionMode = await resolvePackageExecutionMode({
|
|
5046
6674
|
executionMode: options.executionMode,
|
|
5047
6675
|
// Only honor an explicit caller cliPath; never auto-resolve before mode detection
|
|
@@ -5062,157 +6690,222 @@ async function runConnectCommand(options) {
|
|
|
5062
6690
|
}
|
|
5063
6691
|
resolvedMode = { kind: "local", cliPath };
|
|
5064
6692
|
}
|
|
5065
|
-
const
|
|
5066
|
-
|
|
5067
|
-
|
|
5068
|
-
createIfMissing: true
|
|
5069
|
-
});
|
|
5070
|
-
if (ensured.legacyM1WarningPath) {
|
|
6693
|
+
const fingerprint = resolveRepoFingerprint(cwd2);
|
|
6694
|
+
const legacyM1WarningPath = await detectLegacyM1Warning2(cwd2);
|
|
6695
|
+
if (legacyM1WarningPath) {
|
|
5071
6696
|
process.stderr.write(
|
|
5072
|
-
`[memoraone-mcp] warning: ignoring legacy ${
|
|
6697
|
+
`[memoraone-mcp] warning: ignoring legacy ${path24.basename(legacyM1WarningPath)} at ${legacyM1WarningPath} (credentials and binding are package-managed)
|
|
5073
6698
|
`
|
|
5074
6699
|
);
|
|
5075
6700
|
}
|
|
5076
|
-
const
|
|
5077
|
-
const displayName = cwd2;
|
|
5078
|
-
const normalizedGitRemote = normalizeGitRemote(fingerprint.remoteUrl);
|
|
5079
|
-
const { clientRedeemKey } = await ensureClientRedeemKey(
|
|
5080
|
-
ensured.repositoryBindingId,
|
|
5081
|
-
options.credentialOptions
|
|
5082
|
-
);
|
|
5083
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5084
|
-
const pendingRecord = {
|
|
5085
|
-
v: 1,
|
|
5086
|
-
repositoryBindingId: ensured.repositoryBindingId,
|
|
5087
|
-
workspaceRoot: cwd2,
|
|
5088
|
-
filesystemIdentity: ensured.identity,
|
|
5089
|
-
rootFingerprint: fingerprint.fingerprint,
|
|
5090
|
-
displayName,
|
|
5091
|
-
environment,
|
|
5092
|
-
apiUrl,
|
|
5093
|
-
normalizedGitRemote,
|
|
5094
|
-
status: "pending",
|
|
5095
|
-
createdAt: now,
|
|
5096
|
-
updatedAt: now,
|
|
5097
|
-
packageVersion: options.packageVersion ?? null,
|
|
5098
|
-
ideType: options.ideType ?? null
|
|
5099
|
-
};
|
|
5100
|
-
await writeBindingRecord(pendingRecord, homeDir);
|
|
5101
|
-
await upsertPathIndexEntry({
|
|
5102
|
-
repositoryBindingId: ensured.repositoryBindingId,
|
|
5103
|
-
workspaceRoot: cwd2,
|
|
5104
|
-
identity: ensured.identity,
|
|
6701
|
+
const snapshot = await snapshotAndRemoveRootLocalState(cwd2, {
|
|
5105
6702
|
homeDir,
|
|
5106
|
-
|
|
6703
|
+
identityDeps: options.identityDeps,
|
|
6704
|
+
credentialOptions: options.credentialOptions,
|
|
6705
|
+
extraFingerprints: [fingerprint.fingerprint]
|
|
5107
6706
|
});
|
|
5108
|
-
const
|
|
5109
|
-
|
|
5110
|
-
|
|
5111
|
-
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
|
|
5115
|
-
|
|
6707
|
+
const identity = await captureRootFilesystemIdentity(cwd2, options.identityDeps);
|
|
6708
|
+
const repositoryBindingId = generateRepositoryBindingId();
|
|
6709
|
+
const createdBinding = true;
|
|
6710
|
+
const displayName = cwd2;
|
|
6711
|
+
const normalizedGitRemote = normalizeGitRemote(fingerprint.remoteUrl);
|
|
6712
|
+
try {
|
|
6713
|
+
const { clientRedeemKey } = await ensureClientRedeemKey(
|
|
6714
|
+
repositoryBindingId,
|
|
6715
|
+
options.credentialOptions
|
|
6716
|
+
);
|
|
6717
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
6718
|
+
const pendingRecord = {
|
|
6719
|
+
v: 1,
|
|
6720
|
+
repositoryBindingId,
|
|
6721
|
+
workspaceRoot: cwd2,
|
|
6722
|
+
filesystemIdentity: identity,
|
|
6723
|
+
rootFingerprint: fingerprint.fingerprint,
|
|
6724
|
+
displayName,
|
|
5116
6725
|
environment,
|
|
5117
|
-
|
|
5118
|
-
|
|
5119
|
-
|
|
5120
|
-
|
|
5121
|
-
|
|
5122
|
-
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
|
|
5126
|
-
{
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
|
|
6726
|
+
apiUrl,
|
|
6727
|
+
normalizedGitRemote,
|
|
6728
|
+
status: "pending",
|
|
6729
|
+
createdAt: now,
|
|
6730
|
+
updatedAt: now,
|
|
6731
|
+
packageVersion: options.packageVersion ?? null,
|
|
6732
|
+
ideType: options.ideType ?? null
|
|
6733
|
+
};
|
|
6734
|
+
await writeBindingRecord(pendingRecord, homeDir);
|
|
6735
|
+
await upsertPathIndexEntry({
|
|
6736
|
+
repositoryBindingId,
|
|
6737
|
+
workspaceRoot: cwd2,
|
|
6738
|
+
identity,
|
|
6739
|
+
homeDir
|
|
6740
|
+
});
|
|
6741
|
+
const redeemed = await redeemLocalConnectCode(
|
|
6742
|
+
apiUrl,
|
|
6743
|
+
{
|
|
6744
|
+
code,
|
|
6745
|
+
client_redeem_key: clientRedeemKey,
|
|
6746
|
+
repository_binding_id: repositoryBindingId,
|
|
6747
|
+
root_fingerprint: fingerprint.fingerprint,
|
|
6748
|
+
display_name: displayName,
|
|
6749
|
+
environment,
|
|
6750
|
+
normalized_git_remote: normalizedGitRemote,
|
|
6751
|
+
platform: identity.platform,
|
|
6752
|
+
package_version: options.packageVersion ?? null,
|
|
6753
|
+
ide_type: options.ideType ?? null
|
|
6754
|
+
},
|
|
6755
|
+
{ fetchImpl: options.fetchImpl }
|
|
6756
|
+
);
|
|
6757
|
+
await updateInstallationCredentials(
|
|
6758
|
+
repositoryBindingId,
|
|
6759
|
+
{
|
|
6760
|
+
accessToken: redeemed.access_token,
|
|
6761
|
+
refreshToken: redeemed.refresh_token,
|
|
6762
|
+
accessTokenExpiresAt: redeemed.access_token_expires_at ?? void 0,
|
|
6763
|
+
refreshTokenExpiresAt: redeemed.refresh_token_expires_at ?? void 0,
|
|
6764
|
+
installationPublicId: redeemed.installation_public_id,
|
|
6765
|
+
projectId: redeemed.project_id
|
|
6766
|
+
},
|
|
6767
|
+
{ ...options.credentialOptions, clearKeys: ["clientRedeemKey"] }
|
|
6768
|
+
);
|
|
6769
|
+
const connectedRecord = {
|
|
6770
|
+
...pendingRecord,
|
|
5131
6771
|
installationPublicId: redeemed.installation_public_id,
|
|
5132
|
-
projectId: redeemed.project_id
|
|
5133
|
-
|
|
5134
|
-
|
|
5135
|
-
|
|
5136
|
-
|
|
5137
|
-
|
|
5138
|
-
installationPublicId: redeemed.installation_public_id,
|
|
5139
|
-
projectId: redeemed.project_id,
|
|
5140
|
-
status: "connected",
|
|
5141
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
5142
|
-
};
|
|
5143
|
-
await writeBindingRecord(connectedRecord, homeDir);
|
|
5144
|
-
const baseSuccess = `Connected repository binding ${ensured.repositoryBindingId}` + (redeemed.recovered ? " (recovered)" : "") + ` to project ${redeemed.project_id}.`;
|
|
5145
|
-
if (options.configureIdes !== false) {
|
|
5146
|
-
const targets = { cursor: true, vscode: true, jetbrains: true };
|
|
5147
|
-
const setup = options.setupIdeFiles ?? runSetupIdeFiles;
|
|
5148
|
-
const modeSetup = setupOptionsFromPackageExecutionMode(resolvedMode);
|
|
5149
|
-
let setupResult;
|
|
6772
|
+
projectId: redeemed.project_id,
|
|
6773
|
+
status: "connected",
|
|
6774
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
6775
|
+
};
|
|
6776
|
+
await writeBindingRecord(connectedRecord, homeDir);
|
|
6777
|
+
let verified;
|
|
5150
6778
|
try {
|
|
5151
|
-
|
|
5152
|
-
cwd: cwd2,
|
|
5153
|
-
// Use the exact bound workspace root — do not rediscover via .git / .m1.
|
|
5154
|
-
workspaceRoot: cwd2,
|
|
5155
|
-
targets,
|
|
5156
|
-
force: true,
|
|
5157
|
-
dryRun: false,
|
|
5158
|
-
noGitignore: true,
|
|
5159
|
-
skipDaemonCleanup: true,
|
|
6779
|
+
verified = await verifyLocalBinding(cwd2, {
|
|
5160
6780
|
homeDir,
|
|
5161
|
-
|
|
5162
|
-
|
|
5163
|
-
...modeSetup,
|
|
5164
|
-
...options.setupIdeOptions
|
|
6781
|
+
identityDeps: options.identityDeps,
|
|
6782
|
+
credentialOptions: options.credentialOptions
|
|
5165
6783
|
});
|
|
5166
6784
|
} catch (err) {
|
|
5167
|
-
const
|
|
5168
|
-
|
|
6785
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
6786
|
+
await rollbackFailedConnect({
|
|
6787
|
+
snapshot,
|
|
6788
|
+
discardBindingId: repositoryBindingId,
|
|
6789
|
+
homeDir,
|
|
6790
|
+
credentialOptions: options.credentialOptions
|
|
6791
|
+
});
|
|
6792
|
+
return {
|
|
5169
6793
|
exitCode: 1,
|
|
5170
|
-
|
|
5171
|
-
|
|
5172
|
-
|
|
6794
|
+
repositoryBindingId,
|
|
6795
|
+
createdBinding,
|
|
6796
|
+
legacyM1WarningPath,
|
|
6797
|
+
executionMode: resolvedMode,
|
|
6798
|
+
verificationError: detail,
|
|
6799
|
+
message: `Connect wrote binding ${repositoryBindingId} but runtime binding lookup failed for ${cwd2}: ${detail}. Previous root-specific local state was restored. IDE contracts were not updated. Check ~/.memoraone/path-index.json and ~/.memoraone/bindings/.`
|
|
5173
6800
|
};
|
|
5174
6801
|
}
|
|
5175
|
-
if (
|
|
5176
|
-
const detail =
|
|
5177
|
-
|
|
6802
|
+
if (verified.repositoryBindingId !== repositoryBindingId) {
|
|
6803
|
+
const detail = `expected repository_binding_id ${repositoryBindingId}, lookup returned ${verified.repositoryBindingId}`;
|
|
6804
|
+
await rollbackFailedConnect({
|
|
6805
|
+
snapshot,
|
|
6806
|
+
discardBindingId: repositoryBindingId,
|
|
6807
|
+
homeDir,
|
|
6808
|
+
credentialOptions: options.credentialOptions
|
|
6809
|
+
});
|
|
5178
6810
|
return {
|
|
5179
6811
|
exitCode: 1,
|
|
5180
|
-
repositoryBindingId
|
|
6812
|
+
repositoryBindingId,
|
|
6813
|
+
createdBinding,
|
|
6814
|
+
legacyM1WarningPath,
|
|
6815
|
+
executionMode: resolvedMode,
|
|
6816
|
+
verificationError: detail,
|
|
6817
|
+
message: `Connect verification failed for ${cwd2}: ${detail}. Previous root-specific local state was restored. IDE contracts were not updated.`
|
|
6818
|
+
};
|
|
6819
|
+
}
|
|
6820
|
+
const baseSuccess = `Connected repository binding ${repositoryBindingId}` + (redeemed.recovered ? " (recovered)" : "") + ` to project ${redeemed.project_id}.`;
|
|
6821
|
+
if (options.configureIdes !== false) {
|
|
6822
|
+
const targets = {
|
|
6823
|
+
cursor: true,
|
|
6824
|
+
vscode: true,
|
|
6825
|
+
jetbrains: true,
|
|
6826
|
+
claudeCode: true,
|
|
6827
|
+
windsurf: true,
|
|
6828
|
+
opencode: true,
|
|
6829
|
+
codex: true
|
|
6830
|
+
};
|
|
6831
|
+
const setup = options.setupIdeFiles ?? runSetupIdeFiles;
|
|
6832
|
+
const modeSetup = setupOptionsFromPackageExecutionMode(resolvedMode);
|
|
6833
|
+
let setupResult;
|
|
6834
|
+
try {
|
|
6835
|
+
setupResult = await setup({
|
|
6836
|
+
cwd: cwd2,
|
|
6837
|
+
// Use the exact bound workspace root — do not rediscover via .git / .m1.
|
|
6838
|
+
workspaceRoot: cwd2,
|
|
6839
|
+
targets,
|
|
6840
|
+
force: true,
|
|
6841
|
+
dryRun: false,
|
|
6842
|
+
noGitignore: true,
|
|
6843
|
+
skipDaemonCleanup: true,
|
|
6844
|
+
homeDir,
|
|
6845
|
+
// Propagate the redeemed binding API URL (backend only — not execution mode).
|
|
6846
|
+
apiUrl,
|
|
6847
|
+
...modeSetup,
|
|
6848
|
+
...options.setupIdeOptions
|
|
6849
|
+
});
|
|
6850
|
+
} catch (err) {
|
|
6851
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
6852
|
+
setupResult = {
|
|
6853
|
+
exitCode: 1,
|
|
6854
|
+
repoRoot: cwd2,
|
|
6855
|
+
outcomes: {},
|
|
6856
|
+
error: message
|
|
6857
|
+
};
|
|
6858
|
+
}
|
|
6859
|
+
if (setupResult.exitCode !== 0) {
|
|
6860
|
+
const detail = setupResult.error ?? "unknown IDE setup error";
|
|
6861
|
+
const repair = formatIdeSetupRepairHint(cwd2, resolvedMode);
|
|
6862
|
+
return {
|
|
6863
|
+
exitCode: 1,
|
|
6864
|
+
repositoryBindingId,
|
|
6865
|
+
projectId: redeemed.project_id,
|
|
6866
|
+
installationPublicId: redeemed.installation_public_id,
|
|
6867
|
+
recovered: redeemed.recovered,
|
|
6868
|
+
createdBinding,
|
|
6869
|
+
legacyM1WarningPath,
|
|
6870
|
+
ideSetupError: detail,
|
|
6871
|
+
executionMode: resolvedMode,
|
|
6872
|
+
message: `Repository connection exists for binding ${repositoryBindingId} (project ${redeemed.project_id}), but IDE configuration failed: ${detail}. Credentials and binding were kept. ${repair}`
|
|
6873
|
+
};
|
|
6874
|
+
}
|
|
6875
|
+
return {
|
|
6876
|
+
exitCode: 0,
|
|
6877
|
+
repositoryBindingId,
|
|
5181
6878
|
projectId: redeemed.project_id,
|
|
5182
6879
|
installationPublicId: redeemed.installation_public_id,
|
|
5183
6880
|
recovered: redeemed.recovered,
|
|
5184
|
-
createdBinding
|
|
5185
|
-
legacyM1WarningPath
|
|
5186
|
-
ideSetupError: detail,
|
|
6881
|
+
createdBinding,
|
|
6882
|
+
legacyM1WarningPath,
|
|
5187
6883
|
executionMode: resolvedMode,
|
|
5188
|
-
|
|
6884
|
+
configuredTargets: targets,
|
|
6885
|
+
setupResult,
|
|
6886
|
+
message: baseSuccess
|
|
5189
6887
|
};
|
|
5190
6888
|
}
|
|
5191
6889
|
return {
|
|
5192
6890
|
exitCode: 0,
|
|
5193
|
-
repositoryBindingId
|
|
6891
|
+
repositoryBindingId,
|
|
5194
6892
|
projectId: redeemed.project_id,
|
|
5195
6893
|
installationPublicId: redeemed.installation_public_id,
|
|
5196
6894
|
recovered: redeemed.recovered,
|
|
5197
|
-
createdBinding
|
|
5198
|
-
legacyM1WarningPath
|
|
6895
|
+
createdBinding,
|
|
6896
|
+
legacyM1WarningPath,
|
|
5199
6897
|
executionMode: resolvedMode,
|
|
5200
|
-
configuredTargets: targets,
|
|
5201
|
-
setupResult,
|
|
5202
6898
|
message: baseSuccess
|
|
5203
6899
|
};
|
|
6900
|
+
} catch (err) {
|
|
6901
|
+
await rollbackFailedConnect({
|
|
6902
|
+
snapshot,
|
|
6903
|
+
discardBindingId: repositoryBindingId,
|
|
6904
|
+
homeDir,
|
|
6905
|
+
credentialOptions: options.credentialOptions
|
|
6906
|
+
});
|
|
6907
|
+
throw err;
|
|
5204
6908
|
}
|
|
5205
|
-
return {
|
|
5206
|
-
exitCode: 0,
|
|
5207
|
-
repositoryBindingId: ensured.repositoryBindingId,
|
|
5208
|
-
projectId: redeemed.project_id,
|
|
5209
|
-
installationPublicId: redeemed.installation_public_id,
|
|
5210
|
-
recovered: redeemed.recovered,
|
|
5211
|
-
createdBinding: ensured.created,
|
|
5212
|
-
legacyM1WarningPath: ensured.legacyM1WarningPath,
|
|
5213
|
-
executionMode: resolvedMode,
|
|
5214
|
-
message: baseSuccess
|
|
5215
|
-
};
|
|
5216
6909
|
}
|
|
5217
6910
|
function parseConnectArgv(argv) {
|
|
5218
6911
|
let code;
|
|
@@ -5336,7 +7029,7 @@ if (args.includes("--version") || args.includes("-v")) {
|
|
|
5336
7029
|
}
|
|
5337
7030
|
if (args.includes("--help") || args.includes("-h")) {
|
|
5338
7031
|
console.log(
|
|
5339
|
-
"Usage: memoraone-mcp [--version] [--help]\n memoraone-mcp connect <code> [--api-url <url>] [--verbose]\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>] [--verbose]\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 --verbose: show developer diagnostics (paths, backups, daemon cleanup, handshake)\n memoraone-mcp cleanup [--project-id <uuid>] [--ide cursor|copilot-vscode|jetbrains] [--dry-run] [--all-projects] [--yes]"
|
|
7032
|
+
"Usage: memoraone-mcp [--version] [--help]\n memoraone-mcp connect <code> [--api-url <url>] [--verbose]\n memoraone-mcp [--daemon --binding-id <mrb_\u2026> [--ide cursor|copilot-vscode|jetbrains|claude-code|windsurf|opencode|codex]]\n memoraone-mcp setup-ide-files [--all|--cursor|--vscode|--jetbrains|--claude-code|--windsurf|--opencode|--codex] [--force] [--dry-run] [--no-gitignore] [--cleanup] [--dev] [--repair] [--workspace-root <path>] [--api-url <url>] [--verbose]\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 --verbose: show developer diagnostics (paths, backups, daemon cleanup, handshake)\n memoraone-mcp cleanup [--project-id <uuid>] [--ide cursor|copilot-vscode|jetbrains|claude-code|windsurf|opencode|codex] [--dry-run] [--all-projects] [--yes]"
|
|
5340
7033
|
);
|
|
5341
7034
|
process.exit(0);
|
|
5342
7035
|
}
|