@memoraone/mcp 0.1.37 → 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 +2311 -515
- 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;
|
|
@@ -2236,45 +2296,46 @@ function logCursorMcpConfigAudit(prefix, audit) {
|
|
|
2236
2296
|
function logCursorMcpCliSummary(info, dryRun, opts) {
|
|
2237
2297
|
const { repoConfigPath, repoOutcome, npxPath, cliPath, repoBackupPath, globalConfigPath, globalMemoraoneRemoved, globalBackupPath } = info;
|
|
2238
2298
|
const interactive = opts?.forInteractivePostSetup === true;
|
|
2239
|
-
|
|
2299
|
+
const println = opts?.println ?? console.log;
|
|
2300
|
+
println(`[setup-ide-files] Cursor repo MCP config: ${repoConfigPath}`);
|
|
2240
2301
|
if (cliPath) {
|
|
2241
|
-
|
|
2302
|
+
println(`[setup-ide-files] Resolved local CLI: ${cliPath}`);
|
|
2242
2303
|
} else if (npxPath) {
|
|
2243
|
-
|
|
2304
|
+
println(`[setup-ide-files] Resolved npx: ${npxPath}`);
|
|
2244
2305
|
}
|
|
2245
2306
|
if (repoBackupPath) {
|
|
2246
|
-
|
|
2307
|
+
println(`[setup-ide-files] Cursor repo MCP config backup: ${repoBackupPath}`);
|
|
2247
2308
|
}
|
|
2248
2309
|
if (!interactive) {
|
|
2249
2310
|
if (repoOutcome === "created") {
|
|
2250
|
-
|
|
2311
|
+
println(
|
|
2251
2312
|
dryRun ? `[setup-ide-files] Cursor repo MCP config would be created: ${repoConfigPath}` : `[setup-ide-files] Cursor repo MCP config created: ${repoConfigPath}`
|
|
2252
2313
|
);
|
|
2253
2314
|
} else if (repoOutcome === "updated") {
|
|
2254
|
-
|
|
2315
|
+
println(
|
|
2255
2316
|
dryRun ? `[setup-ide-files] Cursor repo MCP config would be updated: ${repoConfigPath}` : `[setup-ide-files] Cursor repo MCP config updated: ${repoConfigPath}`
|
|
2256
2317
|
);
|
|
2257
2318
|
} else if (repoOutcome === "skipped") {
|
|
2258
|
-
|
|
2319
|
+
println(`[setup-ide-files] Cursor repo MCP config unchanged: ${repoConfigPath}`);
|
|
2259
2320
|
}
|
|
2260
2321
|
}
|
|
2261
2322
|
if (globalMemoraoneRemoved && globalConfigPath) {
|
|
2262
|
-
|
|
2323
|
+
println(
|
|
2263
2324
|
dryRun ? `[setup-ide-files] Would remove memoraone from Cursor global MCP config: ${globalConfigPath}` : `[setup-ide-files] Removed memoraone from Cursor global MCP config: ${globalConfigPath}`
|
|
2264
2325
|
);
|
|
2265
2326
|
if (globalBackupPath) {
|
|
2266
|
-
|
|
2327
|
+
println(`[setup-ide-files] Cursor global MCP config backup: ${globalBackupPath}`);
|
|
2267
2328
|
}
|
|
2268
2329
|
} else if (globalConfigPath) {
|
|
2269
|
-
|
|
2330
|
+
println(
|
|
2270
2331
|
`[setup-ide-files] Cursor global MCP config unchanged (no managed memoraone to remove): ${globalConfigPath}`
|
|
2271
2332
|
);
|
|
2272
2333
|
}
|
|
2273
|
-
|
|
2334
|
+
println(
|
|
2274
2335
|
"[setup-ide-files] Each Cursor window uses this repo\u2019s .cursor/mcp.json (separate MCP process per repo)."
|
|
2275
2336
|
);
|
|
2276
2337
|
if (!interactive) {
|
|
2277
|
-
|
|
2338
|
+
println(
|
|
2278
2339
|
"[setup-ide-files] Fully quit Cursor and reopen this repo for MCP changes to take effect."
|
|
2279
2340
|
);
|
|
2280
2341
|
}
|
|
@@ -2420,14 +2481,14 @@ async function filterSocketPathsByIdeForCleanup(socketPaths, projectId, ide, wor
|
|
|
2420
2481
|
const resolvedRoot = workspaceRoot ? path15.resolve(workspaceRoot) : null;
|
|
2421
2482
|
const filtered = [];
|
|
2422
2483
|
for (const socketPath of socketPaths) {
|
|
2423
|
-
const
|
|
2424
|
-
if (isLegacySocketFilename(
|
|
2425
|
-
if (isSocketFilenameForProjectAndIde(
|
|
2484
|
+
const basename14 = path15.basename(socketPath);
|
|
2485
|
+
if (isLegacySocketFilename(basename14)) {
|
|
2486
|
+
if (isSocketFilenameForProjectAndIde(basename14, normalizedProjectId, ide)) {
|
|
2426
2487
|
filtered.push(socketPath);
|
|
2427
2488
|
}
|
|
2428
2489
|
continue;
|
|
2429
2490
|
}
|
|
2430
|
-
if (isHashSocketFilename(
|
|
2491
|
+
if (isHashSocketFilename(basename14)) {
|
|
2431
2492
|
const record = readBindingSidecarRecord(socketPath);
|
|
2432
2493
|
if (!record || record.ideType !== ide) continue;
|
|
2433
2494
|
const sameProject = record.projectId.trim().toLowerCase() === normalizedProjectId;
|
|
@@ -2807,6 +2868,179 @@ async function cliCleanup(argv) {
|
|
|
2807
2868
|
return result.exitCode;
|
|
2808
2869
|
}
|
|
2809
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
|
+
|
|
2810
3044
|
// src/bridgeProxy.ts
|
|
2811
3045
|
var defaultLog = (msg) => {
|
|
2812
3046
|
process.stderr.write(`[memoraone-mcp][bridge] ${msg}
|
|
@@ -2827,9 +3061,9 @@ function summarizeJsonRpcMethod(line) {
|
|
|
2827
3061
|
}
|
|
2828
3062
|
}
|
|
2829
3063
|
function connectWithRetry(socketPath, log, maxRetries, retryDelayMs, connect2) {
|
|
2830
|
-
return new Promise((
|
|
3064
|
+
return new Promise((resolve21, reject) => {
|
|
2831
3065
|
const tryConnect = (attempt) => {
|
|
2832
|
-
connect2(socketPath).then(
|
|
3066
|
+
connect2(socketPath).then(resolve21).catch((err) => {
|
|
2833
3067
|
if (attempt >= maxRetries) {
|
|
2834
3068
|
reject(err);
|
|
2835
3069
|
return;
|
|
@@ -2851,8 +3085,8 @@ async function resolveBridgeSessionBinding(params, env2 = process.env, options =
|
|
|
2851
3085
|
...bridgeOptions
|
|
2852
3086
|
});
|
|
2853
3087
|
}
|
|
2854
|
-
async function stopDaemonsForStaleBinding(stale, env2, log) {
|
|
2855
|
-
const ideType = resolveBindingIdeType(env2);
|
|
3088
|
+
async function stopDaemonsForStaleBinding(stale, env2, log, ideTypeOverride) {
|
|
3089
|
+
const ideType = resolveBindingIdeType(env2, ideTypeOverride);
|
|
2856
3090
|
let processes;
|
|
2857
3091
|
try {
|
|
2858
3092
|
processes = await defaultListDaemonProcesses();
|
|
@@ -2879,9 +3113,10 @@ async function connectOrSpawnDaemonForBinding(binding, opts) {
|
|
|
2879
3113
|
`refreshed stale binding ${sessionBinding.repositoryBindingId}: project=${sessionBinding.projectId}`
|
|
2880
3114
|
);
|
|
2881
3115
|
}
|
|
2882
|
-
const
|
|
3116
|
+
const ideType = opts.ideType;
|
|
3117
|
+
const socketPath = getBindingSocketPath(sessionBinding, opts.env, ideType);
|
|
2883
3118
|
opts.log(
|
|
2884
|
-
`target daemon socket=${socketPath} project=${sessionBinding.projectId} workspace=${sessionBinding.workspaceRoot}`
|
|
3119
|
+
`target daemon socket=${socketPath} project=${sessionBinding.projectId} workspace=${sessionBinding.workspaceRoot}` + (ideType ? ` ideType=${ideType}` : "")
|
|
2885
3120
|
);
|
|
2886
3121
|
let socket;
|
|
2887
3122
|
try {
|
|
@@ -2892,7 +3127,7 @@ async function connectOrSpawnDaemonForBinding(binding, opts) {
|
|
|
2892
3127
|
opts.retryDelayMs,
|
|
2893
3128
|
opts.connect
|
|
2894
3129
|
);
|
|
2895
|
-
verifyDaemonSidecarBinding(socketPath, sessionBinding, opts.env);
|
|
3130
|
+
verifyDaemonSidecarBinding(socketPath, sessionBinding, opts.env, ideType);
|
|
2896
3131
|
opts.log("reusing running daemon for session binding");
|
|
2897
3132
|
return { socket, binding: sessionBinding, cacheRefreshed: reconciled.cacheRefreshed };
|
|
2898
3133
|
} catch (err) {
|
|
@@ -2907,11 +3142,11 @@ async function connectOrSpawnDaemonForBinding(binding, opts) {
|
|
|
2907
3142
|
opts.log("stale daemon binding detected; replacing daemon for current local binding");
|
|
2908
3143
|
const staleSidecar = readBindingSidecar(socketPath);
|
|
2909
3144
|
if (staleSidecar) {
|
|
2910
|
-
await stopDaemonsForStaleBinding(staleSidecar, opts.env, opts.log);
|
|
3145
|
+
await stopDaemonsForStaleBinding(staleSidecar, opts.env, opts.log, ideType);
|
|
2911
3146
|
}
|
|
2912
|
-
await stopDaemonsForStaleBinding(sessionBinding, opts.env, opts.log);
|
|
3147
|
+
await stopDaemonsForStaleBinding(sessionBinding, opts.env, opts.log, ideType);
|
|
2913
3148
|
if (!bindingsMatch(binding, sessionBinding)) {
|
|
2914
|
-
await stopDaemonsForStaleBinding(binding, opts.env, opts.log);
|
|
3149
|
+
await stopDaemonsForStaleBinding(binding, opts.env, opts.log, ideType);
|
|
2915
3150
|
}
|
|
2916
3151
|
removeDaemonSocketArtifacts(socketPath);
|
|
2917
3152
|
}
|
|
@@ -2926,13 +3161,14 @@ async function connectOrSpawnDaemonForBinding(binding, opts) {
|
|
|
2926
3161
|
opts.retryDelayMs,
|
|
2927
3162
|
opts.connect
|
|
2928
3163
|
);
|
|
2929
|
-
verifyDaemonSidecarBinding(socketPath, sessionBinding, opts.env);
|
|
3164
|
+
verifyDaemonSidecarBinding(socketPath, sessionBinding, opts.env, ideType);
|
|
2930
3165
|
return { socket, binding: sessionBinding, cacheRefreshed: reconciled.cacheRefreshed };
|
|
2931
3166
|
}
|
|
2932
3167
|
var BridgeDaemonRouter = class {
|
|
2933
3168
|
constructor(options) {
|
|
2934
3169
|
this.activeSocket = null;
|
|
2935
3170
|
this.activeBinding = null;
|
|
3171
|
+
this.activeIdeType = void 0;
|
|
2936
3172
|
this.socketLineReader = null;
|
|
2937
3173
|
this.lastInitializeLine = null;
|
|
2938
3174
|
this.pendingDeferredClientLines = [];
|
|
@@ -2945,19 +3181,23 @@ var BridgeDaemonRouter = class {
|
|
|
2945
3181
|
this.maxRetries = options.maxRetries ?? 5;
|
|
2946
3182
|
this.retryDelayMs = options.retryDelayMs ?? 200;
|
|
2947
3183
|
this.lineReader = options.lineReader ?? null;
|
|
2948
|
-
this.connectImpl = options.connect ?? ((socketPath) => new Promise((
|
|
2949
|
-
const socket = net.connect(socketPath, () =>
|
|
3184
|
+
this.connectImpl = options.connect ?? ((socketPath) => new Promise((resolve21, reject) => {
|
|
3185
|
+
const socket = net.connect(socketPath, () => resolve21(socket));
|
|
2950
3186
|
socket.on("error", reject);
|
|
2951
3187
|
}));
|
|
2952
3188
|
this.spawnDaemonImpl = options.spawnDaemon ?? (async (binding) => {
|
|
3189
|
+
const ideType = this.activeIdeType;
|
|
2953
3190
|
const child = (0, import_node_child_process4.spawn)(
|
|
2954
3191
|
process.execPath,
|
|
2955
|
-
buildDaemonSpawnArgs(this.cliPath, binding.repositoryBindingId, this.env),
|
|
3192
|
+
buildDaemonSpawnArgs(this.cliPath, binding.repositoryBindingId, this.env, ideType),
|
|
2956
3193
|
{
|
|
2957
3194
|
detached: true,
|
|
2958
3195
|
stdio: "ignore",
|
|
2959
3196
|
env: {
|
|
2960
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 } : {},
|
|
2961
3201
|
// Secret-free handoff only; tokens stay in the OS keyring.
|
|
2962
3202
|
MEMORAONE_DAEMON_BINDING_B64: encodeResolvedBinding(binding)
|
|
2963
3203
|
}
|
|
@@ -2976,6 +3216,9 @@ var BridgeDaemonRouter = class {
|
|
|
2976
3216
|
getActiveBinding() {
|
|
2977
3217
|
return this.activeBinding;
|
|
2978
3218
|
}
|
|
3219
|
+
getActiveIdeType() {
|
|
3220
|
+
return this.activeIdeType;
|
|
3221
|
+
}
|
|
2979
3222
|
hasClientInitialize() {
|
|
2980
3223
|
return this.clientInitializeSeen;
|
|
2981
3224
|
}
|
|
@@ -2985,6 +3228,14 @@ var BridgeDaemonRouter = class {
|
|
|
2985
3228
|
this.env,
|
|
2986
3229
|
`initialize payload: ${summarizeInitializeParamsForDebug(params)}`
|
|
2987
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
|
+
}
|
|
2988
3239
|
const bridgeOptions = getBridgeBindingResolveOptions(this.env);
|
|
2989
3240
|
const initializeRoots = extractWorkspaceRootsFromInitialize(params);
|
|
2990
3241
|
let rootsListUris;
|
|
@@ -3034,6 +3285,7 @@ var BridgeDaemonRouter = class {
|
|
|
3034
3285
|
this.detachSocketReader();
|
|
3035
3286
|
this.activeSocket?.destroy();
|
|
3036
3287
|
this.activeSocket = null;
|
|
3288
|
+
this.activeIdeType = effectiveIdeType;
|
|
3037
3289
|
}
|
|
3038
3290
|
this.activeBinding = binding;
|
|
3039
3291
|
await this.connectActiveDaemon();
|
|
@@ -3077,6 +3329,7 @@ var BridgeDaemonRouter = class {
|
|
|
3077
3329
|
this.handshakeDeferredClientLines = [];
|
|
3078
3330
|
this.clientInitializeSeen = false;
|
|
3079
3331
|
this.activeBinding = null;
|
|
3332
|
+
this.activeIdeType = void 0;
|
|
3080
3333
|
}
|
|
3081
3334
|
async connectActiveDaemon() {
|
|
3082
3335
|
if (!this.activeBinding) {
|
|
@@ -3089,7 +3342,8 @@ var BridgeDaemonRouter = class {
|
|
|
3089
3342
|
maxRetries: this.maxRetries,
|
|
3090
3343
|
retryDelayMs: this.retryDelayMs,
|
|
3091
3344
|
connect: this.connectImpl,
|
|
3092
|
-
spawnDaemon: this.spawnDaemonImpl
|
|
3345
|
+
spawnDaemon: this.spawnDaemonImpl,
|
|
3346
|
+
ideType: this.activeIdeType
|
|
3093
3347
|
});
|
|
3094
3348
|
this.activeBinding = connected.binding;
|
|
3095
3349
|
this.activeSocket = connected.socket;
|
|
@@ -3208,37 +3462,17 @@ async function runBridgeProxy(options) {
|
|
|
3208
3462
|
}
|
|
3209
3463
|
|
|
3210
3464
|
// src/setupIdeFiles.ts
|
|
3211
|
-
var
|
|
3212
|
-
var
|
|
3213
|
-
var
|
|
3214
|
-
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");
|
|
3215
3469
|
|
|
3216
3470
|
// src/jetbrainsMcpConfig.ts
|
|
3217
|
-
var
|
|
3471
|
+
var fs13 = __toESM(require("fs/promises"), 1);
|
|
3218
3472
|
var os5 = __toESM(require("os"), 1);
|
|
3219
|
-
var
|
|
3473
|
+
var path17 = __toESM(require("path"), 1);
|
|
3220
3474
|
var import_node_crypto4 = require("crypto");
|
|
3221
3475
|
var import_node_child_process5 = require("child_process");
|
|
3222
|
-
|
|
3223
|
-
// src/configUtils.ts
|
|
3224
|
-
var DEFAULT_API_URL = "http://localhost:3001";
|
|
3225
|
-
var DEV_API_URL = "http://localhost:3001";
|
|
3226
|
-
function resolveApiUrl(env2) {
|
|
3227
|
-
const explicitUrl = env2.MEMORAONE_API_URL?.trim();
|
|
3228
|
-
if (explicitUrl) {
|
|
3229
|
-
return explicitUrl;
|
|
3230
|
-
}
|
|
3231
|
-
const aliasUrl = env2.MEMORA_API_URL?.trim();
|
|
3232
|
-
if (aliasUrl) {
|
|
3233
|
-
return aliasUrl;
|
|
3234
|
-
}
|
|
3235
|
-
if (env2.MEMORAONE_DEV_MODE === "1") {
|
|
3236
|
-
return DEV_API_URL;
|
|
3237
|
-
}
|
|
3238
|
-
return DEFAULT_API_URL;
|
|
3239
|
-
}
|
|
3240
|
-
|
|
3241
|
-
// src/jetbrainsMcpConfig.ts
|
|
3242
3476
|
var JETBRAINS_DEBUG_ENV_VARS = [
|
|
3243
3477
|
"MEMORAONE_DEBUG_INIT",
|
|
3244
3478
|
"MEMORAONE_DEBUG_MINIMAL_TOOLS",
|
|
@@ -3250,7 +3484,7 @@ function stripLeadingLineComments2(text) {
|
|
|
3250
3484
|
}
|
|
3251
3485
|
async function pathExists3(filePath) {
|
|
3252
3486
|
try {
|
|
3253
|
-
await
|
|
3487
|
+
await fs13.access(filePath);
|
|
3254
3488
|
return true;
|
|
3255
3489
|
} catch {
|
|
3256
3490
|
return false;
|
|
@@ -3261,12 +3495,12 @@ function formatJetBrainsBackupTimestamp(d = /* @__PURE__ */ new Date()) {
|
|
|
3261
3495
|
return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
|
|
3262
3496
|
}
|
|
3263
3497
|
function getJetBrainsGlobalMcpConfigPath(homeDir) {
|
|
3264
|
-
return
|
|
3498
|
+
return path17.join(homeDir, ".ai", "mcp", "mcp.json");
|
|
3265
3499
|
}
|
|
3266
3500
|
function getJetBrainsProjectMcpConfigPaths(repoRoot) {
|
|
3267
3501
|
return [
|
|
3268
|
-
{ kind: "project-ai", path:
|
|
3269
|
-
{ 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") }
|
|
3270
3504
|
];
|
|
3271
3505
|
}
|
|
3272
3506
|
function getKnownJetBrainsMcpConfigLocations(homeDir, repoRoot) {
|
|
@@ -3277,7 +3511,7 @@ function getKnownJetBrainsMcpConfigLocations(homeDir, repoRoot) {
|
|
|
3277
3511
|
}
|
|
3278
3512
|
async function isZeroByteConfigFile(filePath) {
|
|
3279
3513
|
if (!await pathExists3(filePath)) return false;
|
|
3280
|
-
const stat4 = await
|
|
3514
|
+
const stat4 = await fs13.stat(filePath);
|
|
3281
3515
|
return stat4.size === 0;
|
|
3282
3516
|
}
|
|
3283
3517
|
function buildMemoraoneJetBrainsMcpServer(options) {
|
|
@@ -3288,7 +3522,7 @@ function buildMemoraoneJetBrainsMcpServer(options) {
|
|
|
3288
3522
|
apiUrl: options.apiUrl ?? (environment === "local" ? DEV_API_URL : void 0)
|
|
3289
3523
|
}),
|
|
3290
3524
|
MEMORAONE_IDE_TYPE: "jetbrains",
|
|
3291
|
-
[MEMORAONE_WORKSPACE_ROOT_ENV]:
|
|
3525
|
+
[MEMORAONE_WORKSPACE_ROOT_ENV]: path17.resolve(options.workspaceRoot)
|
|
3292
3526
|
};
|
|
3293
3527
|
if (environment === "local" || options.devMode) {
|
|
3294
3528
|
env2.MEMORAONE_DEV_MODE = "1";
|
|
@@ -3310,18 +3544,18 @@ function mergeJetBrainsMcpConfigObject(existing, memoraone) {
|
|
|
3310
3544
|
return { ...base, mcpServers };
|
|
3311
3545
|
}
|
|
3312
3546
|
async function writeJetBrainsMcpJsonAtomic(filePath, content) {
|
|
3313
|
-
const dir =
|
|
3314
|
-
await
|
|
3315
|
-
const tmpPath =
|
|
3547
|
+
const dir = path17.dirname(filePath);
|
|
3548
|
+
await fs13.mkdir(dir, { recursive: true });
|
|
3549
|
+
const tmpPath = path17.join(
|
|
3316
3550
|
dir,
|
|
3317
|
-
`.${
|
|
3551
|
+
`.${path17.basename(filePath)}.${process.pid}.${(0, import_node_crypto4.randomBytes)(8).toString("hex")}.tmp`
|
|
3318
3552
|
);
|
|
3319
3553
|
try {
|
|
3320
|
-
await
|
|
3321
|
-
await
|
|
3554
|
+
await fs13.writeFile(tmpPath, content, "utf8");
|
|
3555
|
+
await fs13.rename(tmpPath, filePath);
|
|
3322
3556
|
} catch (err) {
|
|
3323
3557
|
try {
|
|
3324
|
-
await
|
|
3558
|
+
await fs13.unlink(tmpPath);
|
|
3325
3559
|
} catch {
|
|
3326
3560
|
}
|
|
3327
3561
|
throw err;
|
|
@@ -3362,13 +3596,13 @@ function validateJetBrainsMcpConfig(parsed2, expected) {
|
|
|
3362
3596
|
}
|
|
3363
3597
|
}
|
|
3364
3598
|
async function readJsonConfig(filePath) {
|
|
3365
|
-
const raw = await
|
|
3599
|
+
const raw = await fs13.readFile(filePath, "utf8");
|
|
3366
3600
|
if (raw.trim() === "") return null;
|
|
3367
3601
|
return JSON.parse(stripLeadingLineComments2(raw));
|
|
3368
3602
|
}
|
|
3369
3603
|
async function backupConfigFile(filePath) {
|
|
3370
3604
|
const backupPath = `${filePath}.bak-${formatJetBrainsBackupTimestamp()}`;
|
|
3371
|
-
await
|
|
3605
|
+
await fs13.copyFile(filePath, backupPath);
|
|
3372
3606
|
return backupPath;
|
|
3373
3607
|
}
|
|
3374
3608
|
async function repairZeroByteConfigFile(filePath, dryRun) {
|
|
@@ -3379,7 +3613,7 @@ async function repairZeroByteConfigFile(filePath, dryRun) {
|
|
|
3379
3613
|
return { repaired: true, backupPath: `${filePath}.bak-<timestamp>` };
|
|
3380
3614
|
}
|
|
3381
3615
|
const backupPath = await backupConfigFile(filePath);
|
|
3382
|
-
await
|
|
3616
|
+
await fs13.unlink(filePath);
|
|
3383
3617
|
return { repaired: true, backupPath };
|
|
3384
3618
|
}
|
|
3385
3619
|
function configHasMemoraone(parsed2) {
|
|
@@ -3410,24 +3644,24 @@ async function removeMemoraoneFromProjectConfig(options) {
|
|
|
3410
3644
|
delete mcpServers.memoraone;
|
|
3411
3645
|
const hasOtherServers = Object.keys(mcpServers).length > 0;
|
|
3412
3646
|
if (!hasOtherServers) {
|
|
3413
|
-
await
|
|
3647
|
+
await fs13.unlink(configPath);
|
|
3414
3648
|
return { changed: true, backupPath };
|
|
3415
3649
|
}
|
|
3416
3650
|
const next = { ...parsed2, mcpServers };
|
|
3417
|
-
await
|
|
3418
|
-
await
|
|
3651
|
+
await fs13.mkdir(path17.dirname(configPath), { recursive: true });
|
|
3652
|
+
await fs13.writeFile(configPath, JSON.stringify(next, null, 2) + "\n", "utf8");
|
|
3419
3653
|
return { changed: true, backupPath };
|
|
3420
3654
|
}
|
|
3421
3655
|
async function resolveLocalCliPathAsync() {
|
|
3422
|
-
const here = process.argv[1] ?
|
|
3656
|
+
const here = process.argv[1] ? path17.dirname(path17.resolve(process.argv[1])) : process.cwd();
|
|
3423
3657
|
const candidates = [
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3658
|
+
path17.join(here, "cli.cjs"),
|
|
3659
|
+
path17.join(here, "..", "dist", "cli.cjs"),
|
|
3660
|
+
path17.join(here, "..", "..", "dist", "cli.cjs")
|
|
3427
3661
|
];
|
|
3428
3662
|
for (const candidate of candidates) {
|
|
3429
3663
|
if (await pathExists3(candidate)) {
|
|
3430
|
-
return
|
|
3664
|
+
return path17.resolve(candidate);
|
|
3431
3665
|
}
|
|
3432
3666
|
}
|
|
3433
3667
|
return null;
|
|
@@ -3472,12 +3706,21 @@ async function buildJetBrainsMemoraoneServer(options) {
|
|
|
3472
3706
|
apiUrl: options.apiUrl
|
|
3473
3707
|
});
|
|
3474
3708
|
}
|
|
3709
|
+
function isOptionalJetBrainsHandshakeUnavailable(detail) {
|
|
3710
|
+
if (!detail) return false;
|
|
3711
|
+
if (detail.includes("-32601")) return true;
|
|
3712
|
+
if (/Method not found/i.test(detail)) return true;
|
|
3713
|
+
return false;
|
|
3714
|
+
}
|
|
3715
|
+
function formatOptionalJetBrainsHandshakeUnavailableDetail(detail) {
|
|
3716
|
+
return "MCP handshake optional verification unavailable while configuration succeeded" + (detail ? ` (${detail})` : "");
|
|
3717
|
+
}
|
|
3475
3718
|
async function verifyJetBrainsMcpHandshake(options) {
|
|
3476
3719
|
const timeoutMs = options.timeoutMs ?? 15e3;
|
|
3477
3720
|
const { server } = options;
|
|
3478
|
-
return new Promise((
|
|
3721
|
+
return new Promise((resolve21) => {
|
|
3479
3722
|
let settled = false;
|
|
3480
|
-
const finish = (ok, detail) => {
|
|
3723
|
+
const finish = (ok, detail, optionalUnavailable) => {
|
|
3481
3724
|
if (settled) return;
|
|
3482
3725
|
settled = true;
|
|
3483
3726
|
clearTimeout(timer);
|
|
@@ -3485,7 +3728,7 @@ async function verifyJetBrainsMcpHandshake(options) {
|
|
|
3485
3728
|
child.kill();
|
|
3486
3729
|
} catch {
|
|
3487
3730
|
}
|
|
3488
|
-
|
|
3731
|
+
resolve21({ ok, detail, optionalUnavailable });
|
|
3489
3732
|
};
|
|
3490
3733
|
const child = (0, import_node_child_process5.spawn)(server.command, [...server.args], {
|
|
3491
3734
|
env: { ...process.env, ...server.env },
|
|
@@ -3525,7 +3768,12 @@ async function verifyJetBrainsMcpHandshake(options) {
|
|
|
3525
3768
|
finish(true, "initialize OK; tools/list OK");
|
|
3526
3769
|
}
|
|
3527
3770
|
if (msg.error) {
|
|
3528
|
-
|
|
3771
|
+
const errDetail = `JSON-RPC error: ${JSON.stringify(msg.error)}`;
|
|
3772
|
+
if (isOptionalJetBrainsHandshakeUnavailable(errDetail)) {
|
|
3773
|
+
finish(true, errDetail, true);
|
|
3774
|
+
} else {
|
|
3775
|
+
finish(false, errDetail);
|
|
3776
|
+
}
|
|
3529
3777
|
}
|
|
3530
3778
|
}
|
|
3531
3779
|
});
|
|
@@ -3554,7 +3802,7 @@ async function verifyJetBrainsMcpHandshake(options) {
|
|
|
3554
3802
|
async function setupJetBrainsMcpConfig(options) {
|
|
3555
3803
|
const homeDir = options.homeDir ?? os5.homedir();
|
|
3556
3804
|
const globalPath = options.globalConfigPath ?? getJetBrainsGlobalMcpConfigPath(homeDir);
|
|
3557
|
-
const workspaceRoot =
|
|
3805
|
+
const workspaceRoot = path17.resolve(options.repoRoot);
|
|
3558
3806
|
const repairActions = [];
|
|
3559
3807
|
const allLocations = getKnownJetBrainsMcpConfigLocations(homeDir, options.repoRoot);
|
|
3560
3808
|
for (const location of allLocations) {
|
|
@@ -3627,7 +3875,7 @@ async function setupJetBrainsMcpConfig(options) {
|
|
|
3627
3875
|
backupPath = await backupConfigFile(globalPath);
|
|
3628
3876
|
}
|
|
3629
3877
|
await writeJetBrainsMcpJsonAtomic(globalPath, body);
|
|
3630
|
-
const verifyRaw = await
|
|
3878
|
+
const verifyRaw = await fs13.readFile(globalPath, "utf8");
|
|
3631
3879
|
const verifyParsed = JSON.parse(stripLeadingLineComments2(verifyRaw));
|
|
3632
3880
|
validateJetBrainsMcpConfig(verifyParsed, memoraone);
|
|
3633
3881
|
const outcome = existed ? "updated" : "created";
|
|
@@ -3637,98 +3885,1070 @@ async function setupJetBrainsMcpConfig(options) {
|
|
|
3637
3885
|
if (options.verify !== false) {
|
|
3638
3886
|
const verify = await verifyJetBrainsMcpHandshake({ server: memoraone });
|
|
3639
3887
|
verifyOk = verify.ok;
|
|
3640
|
-
verifyDetail = verify.detail;
|
|
3641
|
-
repairActions.push({
|
|
3888
|
+
verifyDetail = verify.optionalUnavailable ? formatOptionalJetBrainsHandshakeUnavailableDetail(verify.detail) : verify.detail;
|
|
3889
|
+
repairActions.push({
|
|
3890
|
+
type: "verify-handshake",
|
|
3891
|
+
ok: verify.ok,
|
|
3892
|
+
detail: verifyDetail ?? verify.detail
|
|
3893
|
+
});
|
|
3642
3894
|
}
|
|
3643
3895
|
return { outcome, backupPath, repairActions, verifyOk, verifyDetail, memoraone };
|
|
3644
3896
|
}
|
|
3645
|
-
function
|
|
3897
|
+
function formatJetBrainsHandshakeLogLine(action) {
|
|
3898
|
+
const optional = /optional verification unavailable/i.test(action.detail) || isOptionalJetBrainsHandshakeUnavailable(action.detail);
|
|
3899
|
+
if (optional) {
|
|
3900
|
+
if (/optional verification unavailable/i.test(action.detail)) {
|
|
3901
|
+
return `[setup-ide-files] ${action.detail}`;
|
|
3902
|
+
}
|
|
3903
|
+
return `[setup-ide-files] ${formatOptionalJetBrainsHandshakeUnavailableDetail(action.detail)}`;
|
|
3904
|
+
}
|
|
3905
|
+
if (action.ok) {
|
|
3906
|
+
return `[setup-ide-files] MCP handshake verification: ${action.detail}`;
|
|
3907
|
+
}
|
|
3908
|
+
return `[setup-ide-files] MCP handshake verification skipped/failed: ${action.detail}`;
|
|
3909
|
+
}
|
|
3910
|
+
function logJetBrainsMcpCliSummary(info, dryRun, println = console.log) {
|
|
3646
3911
|
for (const action of info.repairActions) {
|
|
3647
3912
|
if (action.type === "found-config") {
|
|
3648
|
-
|
|
3913
|
+
println(`[setup-ide-files] Found JetBrains MCP config (${action.location.kind}): ${action.location.path}`);
|
|
3649
3914
|
} else if (action.type === "repaired-zero-byte") {
|
|
3650
|
-
|
|
3651
|
-
|
|
3915
|
+
println(`[setup-ide-files] Repaired zero-byte MCP config: ${action.path}`);
|
|
3916
|
+
println(`[setup-ide-files] Backup: ${action.backupPath}`);
|
|
3652
3917
|
} else if (action.type === "backed-up-conflicting-project-config") {
|
|
3653
|
-
|
|
3654
|
-
|
|
3918
|
+
println(`[setup-ide-files] Backed up conflicting project MCP config: ${action.path}`);
|
|
3919
|
+
println(`[setup-ide-files] Backup: ${action.backupPath}`);
|
|
3655
3920
|
} else if (action.type === "removed-project-memoraone") {
|
|
3656
|
-
|
|
3921
|
+
println(`[setup-ide-files] Removed project-scoped memoraone definition: ${action.path}`);
|
|
3657
3922
|
} else if (action.type === "verify-handshake") {
|
|
3658
|
-
|
|
3659
|
-
console.log(`[setup-ide-files] MCP handshake verification: ${action.detail}`);
|
|
3660
|
-
} else {
|
|
3661
|
-
console.log(`[setup-ide-files] MCP handshake verification skipped/failed: ${action.detail}`);
|
|
3662
|
-
}
|
|
3923
|
+
println(formatJetBrainsHandshakeLogLine(action));
|
|
3663
3924
|
}
|
|
3664
3925
|
}
|
|
3665
3926
|
const prefix = dryRun ? "would be " : "";
|
|
3666
3927
|
if (info.outcome === "created") {
|
|
3667
|
-
|
|
3928
|
+
println(`[setup-ide-files] JetBrains global MCP config ${prefix}created: ${info.activeConfigPath}`);
|
|
3668
3929
|
} else if (info.outcome === "updated") {
|
|
3669
|
-
|
|
3930
|
+
println(`[setup-ide-files] JetBrains global MCP config ${prefix}updated: ${info.activeConfigPath}`);
|
|
3670
3931
|
} else {
|
|
3671
|
-
|
|
3932
|
+
println(`[setup-ide-files] JetBrains global MCP config unchanged: ${info.activeConfigPath}`);
|
|
3672
3933
|
}
|
|
3673
3934
|
if (info.backupPath) {
|
|
3674
|
-
|
|
3935
|
+
println(`[setup-ide-files] JetBrains global MCP config backup: ${info.backupPath}`);
|
|
3675
3936
|
}
|
|
3676
3937
|
if (info.npxPath) {
|
|
3677
|
-
|
|
3938
|
+
println(`[setup-ide-files] Resolved npx: ${info.npxPath}`);
|
|
3678
3939
|
}
|
|
3679
|
-
|
|
3680
|
-
|
|
3940
|
+
println(`[setup-ide-files] Final active JetBrains MCP config: ${info.activeConfigPath}`);
|
|
3941
|
+
println(
|
|
3681
3942
|
"[setup-ide-files] Fully quit JetBrains IDE and reopen this repo for MCP changes to take effect."
|
|
3682
3943
|
);
|
|
3683
3944
|
}
|
|
3684
3945
|
|
|
3685
|
-
// src/
|
|
3686
|
-
var
|
|
3687
|
-
var
|
|
3688
|
-
var
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
yellow: "\x1B[33m",
|
|
3697
|
-
cyan: "\x1B[36m"
|
|
3698
|
-
};
|
|
3699
|
-
function isCiLikeEnv(env2 = process.env) {
|
|
3700
|
-
if (env2.CI === "true" || env2.CI === "1") return true;
|
|
3701
|
-
if (env2.GITHUB_ACTIONS === "true" || env2.GITHUB_ACTIONS === "1") return true;
|
|
3702
|
-
if (env2.GITLAB_CI === "true" || env2.GITLAB_CI === "1") return true;
|
|
3703
|
-
if (env2.CIRCLECI === "true" || env2.CIRCLECI === "1") return true;
|
|
3704
|
-
if (env2.BUILDKITE === "true" || env2.BUILDKITE === "1") return true;
|
|
3705
|
-
if (typeof env2.CI === "string" && env2.CI.trim() !== "" && env2.CI !== "0" && env2.CI !== "false") {
|
|
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);
|
|
3706
3957
|
return true;
|
|
3958
|
+
} catch {
|
|
3959
|
+
return false;
|
|
3707
3960
|
}
|
|
3708
|
-
return false;
|
|
3709
3961
|
}
|
|
3710
|
-
function
|
|
3711
|
-
|
|
3712
|
-
const env2 = opts.env ?? process.env;
|
|
3713
|
-
if (env2.NO_COLOR !== void 0) return false;
|
|
3714
|
-
if (isCiLikeEnv(env2)) return false;
|
|
3715
|
-
const tty = opts.stdoutIsTty ?? process.stdout.isTTY === true;
|
|
3716
|
-
return tty;
|
|
3962
|
+
function getWindsurfGlobalMcpConfigPath(homeDir) {
|
|
3963
|
+
return path18.join(homeDir, ".config", "devin", "mcp_config.json");
|
|
3717
3964
|
}
|
|
3718
|
-
function
|
|
3719
|
-
|
|
3720
|
-
const env2 = opts.env ?? process.env;
|
|
3721
|
-
const tty = opts.stdoutIsTty ?? process.stdout.isTTY === true;
|
|
3722
|
-
if (!tty) return false;
|
|
3723
|
-
if (env2.TERM === "dumb") return false;
|
|
3724
|
-
if (process.platform === "win32") {
|
|
3725
|
-
return Boolean(
|
|
3726
|
-
env2.WT_SESSION || env2.WT_PROFILE_ID || env2.ConEmuANSI === "ON" || env2.TERM_PROGRAM === "vscode" || env2.TERM_PROGRAM === "cursor" || typeof env2.TERM === "string" && env2.TERM !== "" && env2.TERM !== "dumb"
|
|
3727
|
-
);
|
|
3728
|
-
}
|
|
3729
|
-
return true;
|
|
3965
|
+
function getWindsurfLegacyMcpConfigPath(homeDir) {
|
|
3966
|
+
return path18.join(homeDir, ".codeium", "windsurf", "mcp_config.json");
|
|
3730
3967
|
}
|
|
3731
|
-
function
|
|
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"
|
|
3988
|
+
);
|
|
3989
|
+
}
|
|
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;
|
|
4950
|
+
}
|
|
4951
|
+
function paint(enabled, code, text) {
|
|
3732
4952
|
if (!enabled || text === "") return text;
|
|
3733
4953
|
return `${code}${text}${ANSI.reset}`;
|
|
3734
4954
|
}
|
|
@@ -3750,6 +4970,7 @@ function createTerminalPresentation(opts = {}) {
|
|
|
3750
4970
|
warningSymbol,
|
|
3751
4971
|
nextActionPrefix,
|
|
3752
4972
|
successLine: (message) => paint(color, ANSI.green, `${successSymbol} ${message}`),
|
|
4973
|
+
checkLine: (message) => `${paint(color, ANSI.green, successSymbol)} ${message}`,
|
|
3753
4974
|
warningLine: (message) => paint(color, ANSI.yellow, `${warningSymbol} ${message}`),
|
|
3754
4975
|
nextActionLine: (message) => `${nextActionPrefix} ${message}`,
|
|
3755
4976
|
heading: (text) => paint(color, ANSI.bold, text),
|
|
@@ -3802,44 +5023,6 @@ async function confirmYesDefault(question, deps = {}) {
|
|
|
3802
5023
|
rl.close();
|
|
3803
5024
|
}
|
|
3804
5025
|
}
|
|
3805
|
-
function collectOutcomePaths(outcomes) {
|
|
3806
|
-
const created = [];
|
|
3807
|
-
const updated = [];
|
|
3808
|
-
for (const [file, outcome] of Object.entries(outcomes)) {
|
|
3809
|
-
if (outcome === "created") created.push(file);
|
|
3810
|
-
else if (outcome === "updated") updated.push(file);
|
|
3811
|
-
}
|
|
3812
|
-
return { created, updated };
|
|
3813
|
-
}
|
|
3814
|
-
function formatCursorSetupCompletedSummary(opts, presentation = createTerminalPresentation({ color: false, unicode: false })) {
|
|
3815
|
-
const { created, updated } = collectOutcomePaths(opts.outcomes);
|
|
3816
|
-
const tp = presentation;
|
|
3817
|
-
const lines = [
|
|
3818
|
-
tp.successLine("MemoraOne setup completed for Cursor"),
|
|
3819
|
-
"",
|
|
3820
|
-
tp.heading("Repository"),
|
|
3821
|
-
tp.indent(tp.cyan(opts.repoRoot)),
|
|
3822
|
-
"",
|
|
3823
|
-
tp.heading("Changes")
|
|
3824
|
-
];
|
|
3825
|
-
if (created.length === 0 && updated.length === 0) {
|
|
3826
|
-
lines.push(tp.indent(tp.dim("No file changes needed")));
|
|
3827
|
-
} else {
|
|
3828
|
-
if (created.length) {
|
|
3829
|
-
lines.push(tp.indent(`Created: ${created.join(", ")}`));
|
|
3830
|
-
}
|
|
3831
|
-
if (updated.length) {
|
|
3832
|
-
lines.push(tp.indent(`Updated: ${updated.join(", ")}`));
|
|
3833
|
-
}
|
|
3834
|
-
}
|
|
3835
|
-
return lines;
|
|
3836
|
-
}
|
|
3837
|
-
function printCursorSetupCompletedSummary(opts, println = console.log, presentation) {
|
|
3838
|
-
const tp = presentation ?? createTerminalPresentation();
|
|
3839
|
-
for (const line of formatCursorSetupCompletedSummary(opts, tp)) {
|
|
3840
|
-
println(line);
|
|
3841
|
-
}
|
|
3842
|
-
}
|
|
3843
5026
|
function macosOpenCursorMcpSettingsAppleScript() {
|
|
3844
5027
|
return [
|
|
3845
5028
|
'tell application "Cursor" to activate',
|
|
@@ -3910,6 +5093,69 @@ async function runOpenCursorMcpSettingsFlow(deps = {}) {
|
|
|
3910
5093
|
printManualCursorMcpSettingsSteps(platform2, println);
|
|
3911
5094
|
}
|
|
3912
5095
|
|
|
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.";
|
|
5101
|
+
var RESTART_LINE = "Restart your IDEs to finish setup.";
|
|
5102
|
+
function formatSetupSuccessLines(opts = {}) {
|
|
5103
|
+
const tp = opts.presentation ?? createTerminalPresentation(opts.presentationOptions ?? { color: false, unicode: true });
|
|
5104
|
+
const targets = opts.targets ?? {};
|
|
5105
|
+
const lines = [];
|
|
5106
|
+
if (opts.repositoryConnected) {
|
|
5107
|
+
lines.push(tp.checkLine("Repository connected"));
|
|
5108
|
+
}
|
|
5109
|
+
if (targets.cursor) {
|
|
5110
|
+
lines.push(tp.checkLine("Cursor configured"));
|
|
5111
|
+
}
|
|
5112
|
+
if (targets.vscode) {
|
|
5113
|
+
lines.push(tp.checkLine("VS Code configured"));
|
|
5114
|
+
}
|
|
5115
|
+
if (targets.jetbrains) {
|
|
5116
|
+
lines.push(tp.checkLine("JetBrains configured"));
|
|
5117
|
+
}
|
|
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
|
+
);
|
|
5133
|
+
lines.push("");
|
|
5134
|
+
lines.push(tp.checkLine("MemoraOne is ready"));
|
|
5135
|
+
if (hasIde) {
|
|
5136
|
+
lines.push("");
|
|
5137
|
+
lines.push(RESTART_LINE);
|
|
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
|
+
}
|
|
5151
|
+
return lines;
|
|
5152
|
+
}
|
|
5153
|
+
function printSetupSuccess(opts, println = console.log) {
|
|
5154
|
+
for (const line of formatSetupSuccessLines(opts)) {
|
|
5155
|
+
println(line);
|
|
5156
|
+
}
|
|
5157
|
+
}
|
|
5158
|
+
|
|
3913
5159
|
// src/setupIdeFiles.ts
|
|
3914
5160
|
var MANAGED_MARKER = "<!-- MemoraOne managed IDE helper -->";
|
|
3915
5161
|
function buildMemoraoneMcpServer(ideType, options = {}) {
|
|
@@ -3918,13 +5164,14 @@ function buildMemoraoneMcpServer(ideType, options = {}) {
|
|
|
3918
5164
|
MEMORAONE_API_URL: resolveIdeApiUrl({ environment, apiUrl: options.apiUrl }),
|
|
3919
5165
|
MEMORAONE_IDE_TYPE: ideType
|
|
3920
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
|
+
}
|
|
3921
5171
|
if (environment === "local") {
|
|
3922
5172
|
if (!options.cliPath) {
|
|
3923
5173
|
throw new Error("[setup-ide-files] Local VS Code MCP config requires a built CLI path.");
|
|
3924
5174
|
}
|
|
3925
|
-
if (options.workspaceRoot !== void 0) {
|
|
3926
|
-
env2[MEMORAONE_WORKSPACE_ROOT_ENV] = path17.resolve(options.workspaceRoot);
|
|
3927
|
-
}
|
|
3928
5175
|
return {
|
|
3929
5176
|
command: "node",
|
|
3930
5177
|
args: [options.cliPath],
|
|
@@ -3939,15 +5186,15 @@ function buildMemoraoneMcpServer(ideType, options = {}) {
|
|
|
3939
5186
|
};
|
|
3940
5187
|
}
|
|
3941
5188
|
function assertUnderRepoRoot(repoRoot, absPath) {
|
|
3942
|
-
const normRoot =
|
|
3943
|
-
const normPath =
|
|
3944
|
-
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)) {
|
|
3945
5192
|
throw new Error(`[setup-ide-files] Refusing to write outside repo root: ${absPath}`);
|
|
3946
5193
|
}
|
|
3947
5194
|
}
|
|
3948
|
-
async function
|
|
5195
|
+
async function pathExists7(filePath) {
|
|
3949
5196
|
try {
|
|
3950
|
-
await
|
|
5197
|
+
await fs17.access(filePath);
|
|
3951
5198
|
return true;
|
|
3952
5199
|
} catch {
|
|
3953
5200
|
return false;
|
|
@@ -3957,21 +5204,21 @@ async function ensureGitignoreMemoraone(_repoRoot, _opts) {
|
|
|
3957
5204
|
return "skipped";
|
|
3958
5205
|
}
|
|
3959
5206
|
async function findRepoRoot(startDir) {
|
|
3960
|
-
let current =
|
|
3961
|
-
const root =
|
|
5207
|
+
let current = path21.resolve(startDir);
|
|
5208
|
+
const root = path21.parse(current).root;
|
|
3962
5209
|
while (true) {
|
|
3963
|
-
const gitPath =
|
|
3964
|
-
const m1Path =
|
|
3965
|
-
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)) {
|
|
3966
5213
|
return current;
|
|
3967
5214
|
}
|
|
3968
5215
|
if (current === root) {
|
|
3969
5216
|
return null;
|
|
3970
5217
|
}
|
|
3971
|
-
current =
|
|
5218
|
+
current = path21.dirname(current);
|
|
3972
5219
|
}
|
|
3973
5220
|
}
|
|
3974
|
-
function
|
|
5221
|
+
function stripLeadingLineComments4(text) {
|
|
3975
5222
|
return text.split("\n").filter((line) => !/^\s*\/\//.test(line)).join("\n");
|
|
3976
5223
|
}
|
|
3977
5224
|
function cursorRuleBody() {
|
|
@@ -4023,18 +5270,18 @@ function buildVscodeMcpJsonBody(existing, options = {}) {
|
|
|
4023
5270
|
return mcpJsonHeader() + JSON.stringify(merged, null, 2) + "\n";
|
|
4024
5271
|
}
|
|
4025
5272
|
async function writeSharedMcpJsonAtomic(filePath, content) {
|
|
4026
|
-
const dir =
|
|
4027
|
-
await
|
|
4028
|
-
const tmpPath =
|
|
5273
|
+
const dir = path21.dirname(filePath);
|
|
5274
|
+
await fs17.mkdir(dir, { recursive: true });
|
|
5275
|
+
const tmpPath = path21.join(
|
|
4029
5276
|
dir,
|
|
4030
|
-
`.${
|
|
5277
|
+
`.${path21.basename(filePath)}.${process.pid}.${(0, import_node_crypto8.randomBytes)(8).toString("hex")}.tmp`
|
|
4031
5278
|
);
|
|
4032
5279
|
try {
|
|
4033
|
-
await
|
|
4034
|
-
await
|
|
5280
|
+
await fs17.writeFile(tmpPath, content, "utf8");
|
|
5281
|
+
await fs17.rename(tmpPath, filePath);
|
|
4035
5282
|
} catch (err) {
|
|
4036
5283
|
try {
|
|
4037
|
-
await
|
|
5284
|
+
await fs17.unlink(tmpPath);
|
|
4038
5285
|
} catch {
|
|
4039
5286
|
}
|
|
4040
5287
|
throw err;
|
|
@@ -4053,43 +5300,79 @@ function buildCursorMcpJsonBody(existing, writeOptions) {
|
|
|
4053
5300
|
const merged = mergeCursorRepoMcpConfigObject(existing, writeOptions);
|
|
4054
5301
|
return mcpJsonHeader() + JSON.stringify(merged, null, 2) + "\n";
|
|
4055
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
|
+
}
|
|
4056
5339
|
async function writeManagedMarkdown(repoRoot, relPath, fullContent, opts) {
|
|
4057
|
-
const abs =
|
|
5340
|
+
const abs = path21.join(repoRoot, relPath);
|
|
4058
5341
|
assertUnderRepoRoot(repoRoot, abs);
|
|
4059
5342
|
let prior = "";
|
|
4060
5343
|
let existed = false;
|
|
4061
5344
|
try {
|
|
4062
|
-
prior = await
|
|
5345
|
+
prior = await fs17.readFile(abs, "utf8");
|
|
4063
5346
|
existed = true;
|
|
4064
5347
|
} catch (err) {
|
|
4065
5348
|
if (err?.code !== "ENOENT") throw err;
|
|
4066
5349
|
}
|
|
4067
5350
|
if (!existed) {
|
|
4068
5351
|
if (opts.dryRun) return "created";
|
|
4069
|
-
await
|
|
4070
|
-
await
|
|
5352
|
+
await fs17.mkdir(path21.dirname(abs), { recursive: true });
|
|
5353
|
+
await fs17.writeFile(abs, fullContent, "utf8");
|
|
4071
5354
|
return "created";
|
|
4072
5355
|
}
|
|
4073
5356
|
if (prior.includes(MANAGED_MARKER)) {
|
|
4074
5357
|
if (prior === fullContent) return "skipped";
|
|
4075
5358
|
if (opts.dryRun) return "updated";
|
|
4076
|
-
await
|
|
4077
|
-
await
|
|
5359
|
+
await fs17.mkdir(path21.dirname(abs), { recursive: true });
|
|
5360
|
+
await fs17.writeFile(abs, fullContent, "utf8");
|
|
4078
5361
|
return "updated";
|
|
4079
5362
|
}
|
|
4080
5363
|
if (!opts.force) return "skipped-untracked";
|
|
4081
5364
|
if (opts.dryRun) return "updated";
|
|
4082
|
-
await
|
|
4083
|
-
await
|
|
5365
|
+
await fs17.mkdir(path21.dirname(abs), { recursive: true });
|
|
5366
|
+
await fs17.writeFile(abs, fullContent, "utf8");
|
|
4084
5367
|
return "updated";
|
|
4085
5368
|
}
|
|
4086
5369
|
async function writeIdeMcpJson(repoRoot, relPath, buildBody, opts) {
|
|
4087
|
-
const abs =
|
|
5370
|
+
const abs = path21.join(repoRoot, relPath);
|
|
4088
5371
|
assertUnderRepoRoot(repoRoot, abs);
|
|
4089
5372
|
let raw = "";
|
|
4090
5373
|
let existed = false;
|
|
4091
5374
|
try {
|
|
4092
|
-
raw = await
|
|
5375
|
+
raw = await fs17.readFile(abs, "utf8");
|
|
4093
5376
|
existed = true;
|
|
4094
5377
|
} catch (err) {
|
|
4095
5378
|
if (err?.code !== "ENOENT") throw err;
|
|
@@ -4104,7 +5387,7 @@ async function writeIdeMcpJson(repoRoot, relPath, buildBody, opts) {
|
|
|
4104
5387
|
if (!managed && !opts.force) return "skipped-untracked";
|
|
4105
5388
|
let parsed2;
|
|
4106
5389
|
try {
|
|
4107
|
-
parsed2 = JSON.parse(
|
|
5390
|
+
parsed2 = JSON.parse(stripLeadingLineComments4(raw));
|
|
4108
5391
|
} catch {
|
|
4109
5392
|
throw new SharedMcpJsonParseError(abs);
|
|
4110
5393
|
}
|
|
@@ -4121,6 +5404,10 @@ function parseSetupIdeFlags(argv) {
|
|
|
4121
5404
|
let cursor = false;
|
|
4122
5405
|
let vscode = false;
|
|
4123
5406
|
let jetbrains = false;
|
|
5407
|
+
let claudeCode = false;
|
|
5408
|
+
let windsurf = false;
|
|
5409
|
+
let opencode = false;
|
|
5410
|
+
let codex = false;
|
|
4124
5411
|
let all = false;
|
|
4125
5412
|
let force = false;
|
|
4126
5413
|
let dryRun = false;
|
|
@@ -4130,6 +5417,7 @@ function parseSetupIdeFlags(argv) {
|
|
|
4130
5417
|
let repair = false;
|
|
4131
5418
|
let local = false;
|
|
4132
5419
|
let staging = false;
|
|
5420
|
+
let verbose = false;
|
|
4133
5421
|
let workspaceRoot;
|
|
4134
5422
|
let apiUrl;
|
|
4135
5423
|
const unknown = [];
|
|
@@ -4139,6 +5427,10 @@ function parseSetupIdeFlags(argv) {
|
|
|
4139
5427
|
if (a === "--cursor") cursor = true;
|
|
4140
5428
|
else if (a === "--vscode") vscode = true;
|
|
4141
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;
|
|
4142
5434
|
else if (a === "--all") all = true;
|
|
4143
5435
|
else if (a === "--force") force = true;
|
|
4144
5436
|
else if (a === "--dry-run") dryRun = true;
|
|
@@ -4148,6 +5440,7 @@ function parseSetupIdeFlags(argv) {
|
|
|
4148
5440
|
else if (a === "--repair") repair = true;
|
|
4149
5441
|
else if (a === "--local") local = true;
|
|
4150
5442
|
else if (a === "--staging") staging = true;
|
|
5443
|
+
else if (a === "--verbose") verbose = true;
|
|
4151
5444
|
else if (a === "--workspace-root") {
|
|
4152
5445
|
const value = argv[++i];
|
|
4153
5446
|
if (!value || value.startsWith("-")) {
|
|
@@ -4178,12 +5471,20 @@ function parseSetupIdeFlags(argv) {
|
|
|
4178
5471
|
}
|
|
4179
5472
|
} else if (a.startsWith("-")) unknown.push(a);
|
|
4180
5473
|
}
|
|
4181
|
-
const specific = cursor || vscode || jetbrains;
|
|
5474
|
+
const specific = cursor || vscode || jetbrains || claudeCode || windsurf || opencode || codex;
|
|
4182
5475
|
let targets;
|
|
4183
5476
|
if (all || !specific) {
|
|
4184
|
-
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
|
+
};
|
|
4185
5486
|
} else {
|
|
4186
|
-
targets = { cursor, vscode, jetbrains };
|
|
5487
|
+
targets = { cursor, vscode, jetbrains, claudeCode, windsurf, opencode, codex };
|
|
4187
5488
|
}
|
|
4188
5489
|
if (!flagError && local && staging) {
|
|
4189
5490
|
flagError = "[setup-ide-files] --local and --staging are mutually exclusive.";
|
|
@@ -4205,10 +5506,46 @@ function parseSetupIdeFlags(argv) {
|
|
|
4205
5506
|
workspaceRoot,
|
|
4206
5507
|
apiUrl,
|
|
4207
5508
|
explicitCursor: cursor,
|
|
5509
|
+
verbose,
|
|
4208
5510
|
unknown,
|
|
4209
5511
|
flagError
|
|
4210
5512
|
};
|
|
4211
5513
|
}
|
|
5514
|
+
function logSetupIdeFilesVerboseSuccess(opts, println = console.log) {
|
|
5515
|
+
const { targets, dryRun, result } = opts;
|
|
5516
|
+
if (result.repoRoot) {
|
|
5517
|
+
println(`[setup-ide-files] Repo root: ${result.repoRoot}`);
|
|
5518
|
+
}
|
|
5519
|
+
if (result.daemonCleanup && !result.daemonCleanup.skipped) {
|
|
5520
|
+
logSetupIdeCleanupSummary(result.daemonCleanup, println);
|
|
5521
|
+
}
|
|
5522
|
+
if (targets.cursor && result.cursorMcp) {
|
|
5523
|
+
logCursorMcpCliSummary(result.cursorMcp, dryRun, {
|
|
5524
|
+
forInteractivePostSetup: opts.forInteractivePostSetup,
|
|
5525
|
+
println
|
|
5526
|
+
});
|
|
5527
|
+
}
|
|
5528
|
+
if (targets.jetbrains && result.jetbrainsMcp) {
|
|
5529
|
+
logJetBrainsMcpCliSummary(result.jetbrainsMcp, dryRun, println);
|
|
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
|
+
}
|
|
5540
|
+
summarizeOutcomes(result.outcomes, println);
|
|
5541
|
+
if (dryRun) {
|
|
5542
|
+
println("[setup-ide-files] Dry run: no files written.");
|
|
5543
|
+
if (result.daemonCleanup && !result.daemonCleanup.skipped) {
|
|
5544
|
+
println("[setup-ide-files] Dry run: no daemons stopped, no sockets removed.");
|
|
5545
|
+
}
|
|
5546
|
+
}
|
|
5547
|
+
println(`[setup-ide-files] ${restartIdeInstruction(targets)}`);
|
|
5548
|
+
}
|
|
4212
5549
|
async function resolveSetupApiUrl(o, repoRoot) {
|
|
4213
5550
|
if (o.apiUrl) return normalizeApiUrl2(o.apiUrl);
|
|
4214
5551
|
const binding = await findBindingRecordByWorkspaceRoot(repoRoot, o.homeDir);
|
|
@@ -4235,7 +5572,7 @@ function cursorEnvironmentFromFlags(local, staging) {
|
|
|
4235
5572
|
if (staging) return "staging";
|
|
4236
5573
|
return "production";
|
|
4237
5574
|
}
|
|
4238
|
-
function summarizeOutcomes(outcomes) {
|
|
5575
|
+
function summarizeOutcomes(outcomes, println = console.log) {
|
|
4239
5576
|
const created = [];
|
|
4240
5577
|
const updated = [];
|
|
4241
5578
|
const skipped = [];
|
|
@@ -4253,17 +5590,21 @@ function summarizeOutcomes(outcomes) {
|
|
|
4253
5590
|
if (skippedUntracked.length) {
|
|
4254
5591
|
lines.push(` skipped (unmanaged existing file, use --force): ${skippedUntracked.join(", ")}`);
|
|
4255
5592
|
}
|
|
4256
|
-
|
|
5593
|
+
println(lines.join("\n"));
|
|
4257
5594
|
}
|
|
4258
5595
|
function ideTypesFromSetupTargets(targets) {
|
|
4259
5596
|
const ides = [];
|
|
4260
5597
|
if (targets.cursor) ides.push("cursor");
|
|
4261
5598
|
if (targets.vscode) ides.push("copilot-vscode");
|
|
4262
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");
|
|
4263
5604
|
return ides;
|
|
4264
5605
|
}
|
|
4265
5606
|
function setupTargetsAllIdes(targets) {
|
|
4266
|
-
return targets.cursor && targets.vscode && targets.jetbrains;
|
|
5607
|
+
return targets.cursor && targets.vscode && targets.jetbrains && targets.claudeCode && targets.windsurf && targets.opencode && targets.codex;
|
|
4267
5608
|
}
|
|
4268
5609
|
function aggregateCleanupResults(results) {
|
|
4269
5610
|
const killedPids = /* @__PURE__ */ new Set();
|
|
@@ -4294,28 +5635,28 @@ function aggregateCleanupResults(results) {
|
|
|
4294
5635
|
error
|
|
4295
5636
|
};
|
|
4296
5637
|
}
|
|
4297
|
-
function logSetupIdeCleanupSummary(cleanup) {
|
|
5638
|
+
function logSetupIdeCleanupSummary(cleanup, println = console.log) {
|
|
4298
5639
|
if (cleanup.skipped) return;
|
|
4299
|
-
|
|
5640
|
+
println(`[setup-ide-files] Project id: ${cleanup.projectId}`);
|
|
4300
5641
|
if (cleanup.foundDaemonCount > 0) {
|
|
4301
|
-
|
|
5642
|
+
println(`[setup-ide-files] Found ${cleanup.foundDaemonCount} stale daemon(s)`);
|
|
4302
5643
|
if (cleanup.dryRun) {
|
|
4303
|
-
|
|
5644
|
+
println(`[setup-ide-files] Would stop ${cleanup.foundDaemonCount} stale daemon(s)`);
|
|
4304
5645
|
} else if (cleanup.stoppedDaemonCount > 0) {
|
|
4305
|
-
|
|
5646
|
+
println(`[setup-ide-files] Stopped ${cleanup.stoppedDaemonCount} stale daemon(s)`);
|
|
4306
5647
|
}
|
|
4307
5648
|
} else {
|
|
4308
|
-
|
|
5649
|
+
println("[setup-ide-files] No stale daemons found for this project and IDE target(s).");
|
|
4309
5650
|
}
|
|
4310
5651
|
if (cleanup.removedSocketCount > 0) {
|
|
4311
5652
|
if (cleanup.dryRun) {
|
|
4312
|
-
|
|
5653
|
+
println(`[setup-ide-files] Would remove ${cleanup.removedSocketCount} stale socket(s)`);
|
|
4313
5654
|
} else {
|
|
4314
|
-
|
|
5655
|
+
println(`[setup-ide-files] Removed ${cleanup.removedSocketCount} stale socket(s)`);
|
|
4315
5656
|
}
|
|
4316
5657
|
}
|
|
4317
5658
|
if (cleanup.skippedUnrelatedDaemonCount > 0) {
|
|
4318
|
-
|
|
5659
|
+
println(
|
|
4319
5660
|
`[setup-ide-files] Skipped ${cleanup.skippedUnrelatedDaemonCount} unrelated project daemon(s)`
|
|
4320
5661
|
);
|
|
4321
5662
|
}
|
|
@@ -4383,8 +5724,18 @@ function restartIdeInstruction(targets) {
|
|
|
4383
5724
|
if (targets.cursor) names.push("Cursor");
|
|
4384
5725
|
if (targets.vscode) names.push("VS Code");
|
|
4385
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");
|
|
4386
5731
|
if (names.length === 0) return "Fully quit your IDE and reopen this repo for MCP changes to take effect.";
|
|
4387
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
|
+
}
|
|
4388
5739
|
return `Fully quit ${names[0]} and reopen this repo for MCP changes to take effect.`;
|
|
4389
5740
|
}
|
|
4390
5741
|
const last = names.pop();
|
|
@@ -4394,7 +5745,10 @@ async function runSetupIdeFiles(o) {
|
|
|
4394
5745
|
const outcomes = {};
|
|
4395
5746
|
let cursorMcp;
|
|
4396
5747
|
let jetbrainsMcp;
|
|
4397
|
-
|
|
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);
|
|
4398
5752
|
if (!repoRoot) {
|
|
4399
5753
|
return {
|
|
4400
5754
|
exitCode: 1,
|
|
@@ -4616,7 +5970,7 @@ description: MemoraOne MCP \u2014 IDE agent instructions
|
|
|
4616
5970
|
{ force: o.force, dryRun: o.dryRun }
|
|
4617
5971
|
);
|
|
4618
5972
|
try {
|
|
4619
|
-
const homeDir = o.jetbrainsHomeDir ??
|
|
5973
|
+
const homeDir = o.jetbrainsHomeDir ?? os7.homedir();
|
|
4620
5974
|
const activePath = o.jetbrainsGlobalMcpConfigPath ?? getJetBrainsGlobalMcpConfigPath(homeDir);
|
|
4621
5975
|
const jetbrainsSetup = await setupJetBrainsMcpConfig({
|
|
4622
5976
|
homeDir,
|
|
@@ -4655,7 +6009,207 @@ description: MemoraOne MCP \u2014 IDE agent instructions
|
|
|
4655
6009
|
};
|
|
4656
6010
|
}
|
|
4657
6011
|
}
|
|
4658
|
-
|
|
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,
|
|
6170
|
+
dryRun: o.dryRun,
|
|
6171
|
+
// Local dogfood: --dev / local environment → node + built CLI.
|
|
6172
|
+
// Published staging/production: npx + channel-specific package spec.
|
|
6173
|
+
devMode: localOrDev,
|
|
6174
|
+
environment: localOrDev ? "local" : cursorEnvironment,
|
|
6175
|
+
npmPackageChannel,
|
|
6176
|
+
apiUrl: resolvedApiUrl2,
|
|
6177
|
+
npxPathOverride: o.npxPathOverride,
|
|
6178
|
+
cliPathOverride: o.cliPathOverride ?? o.cursorLocalCliPathOverride
|
|
6179
|
+
});
|
|
6180
|
+
codexMcp = {
|
|
6181
|
+
activeConfigPath: activePath,
|
|
6182
|
+
outcome: codexSetup.outcome,
|
|
6183
|
+
npxPath: codexSetup.memoraone?.command
|
|
6184
|
+
};
|
|
6185
|
+
outcomes[".codex/config.toml"] = codexSetup.outcome;
|
|
6186
|
+
} catch (err) {
|
|
6187
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
6188
|
+
return {
|
|
6189
|
+
exitCode: 1,
|
|
6190
|
+
repoRoot,
|
|
6191
|
+
outcomes,
|
|
6192
|
+
cursorMcp,
|
|
6193
|
+
jetbrainsMcp,
|
|
6194
|
+
windsurfMcp,
|
|
6195
|
+
opencodeMcp,
|
|
6196
|
+
codexMcp,
|
|
6197
|
+
daemonCleanup,
|
|
6198
|
+
error: message
|
|
6199
|
+
};
|
|
6200
|
+
}
|
|
6201
|
+
}
|
|
6202
|
+
return {
|
|
6203
|
+
exitCode: 0,
|
|
6204
|
+
repoRoot,
|
|
6205
|
+
outcomes,
|
|
6206
|
+
cursorMcp,
|
|
6207
|
+
jetbrainsMcp,
|
|
6208
|
+
windsurfMcp,
|
|
6209
|
+
opencodeMcp,
|
|
6210
|
+
codexMcp,
|
|
6211
|
+
daemonCleanup
|
|
6212
|
+
};
|
|
4659
6213
|
}
|
|
4660
6214
|
async function cliSetupIdeFiles(argv, options = {}) {
|
|
4661
6215
|
const {
|
|
@@ -4672,6 +6226,7 @@ async function cliSetupIdeFiles(argv, options = {}) {
|
|
|
4672
6226
|
workspaceRoot,
|
|
4673
6227
|
apiUrl,
|
|
4674
6228
|
explicitCursor,
|
|
6229
|
+
verbose,
|
|
4675
6230
|
unknown,
|
|
4676
6231
|
flagError
|
|
4677
6232
|
} = parseSetupIdeFlags(argv);
|
|
@@ -4687,6 +6242,7 @@ async function cliSetupIdeFiles(argv, options = {}) {
|
|
|
4687
6242
|
const openDeps = options.openCursorMcpSettings ?? {};
|
|
4688
6243
|
const stdinIsTty = openDeps.stdinIsTty ?? process.stdin.isTTY === true;
|
|
4689
6244
|
const env2 = openDeps.env ?? process.env;
|
|
6245
|
+
const println = options.println ?? openDeps.println ?? console.log;
|
|
4690
6246
|
const promptOpenCursorSettings = shouldPromptOpenCursorMcpSettings({
|
|
4691
6247
|
explicitCursor,
|
|
4692
6248
|
all,
|
|
@@ -4712,55 +6268,42 @@ async function cliSetupIdeFiles(argv, options = {}) {
|
|
|
4712
6268
|
if (result.error) {
|
|
4713
6269
|
console.error(result.error);
|
|
4714
6270
|
if (result.repoRoot) {
|
|
4715
|
-
|
|
6271
|
+
println(`[setup-ide-files] Repo root: ${result.repoRoot}`);
|
|
4716
6272
|
}
|
|
4717
6273
|
summarizeOutcomes(result.outcomes);
|
|
4718
6274
|
return result.exitCode;
|
|
4719
6275
|
}
|
|
4720
|
-
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
}
|
|
4726
|
-
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
|
|
6276
|
+
const presentation = openDeps.presentation ?? createTerminalPresentation({
|
|
6277
|
+
env: env2,
|
|
6278
|
+
stdoutIsTty: openDeps.stdoutIsTty ?? process.stdout.isTTY === true,
|
|
6279
|
+
color: openDeps.color,
|
|
6280
|
+
unicode: openDeps.unicode
|
|
6281
|
+
});
|
|
6282
|
+
const showVerbose = verbose || dryRun;
|
|
6283
|
+
if (showVerbose) {
|
|
6284
|
+
logSetupIdeFilesVerboseSuccess(
|
|
6285
|
+
{
|
|
6286
|
+
targets,
|
|
6287
|
+
dryRun,
|
|
6288
|
+
result,
|
|
6289
|
+
forInteractivePostSetup: promptOpenCursorSettings
|
|
6290
|
+
},
|
|
6291
|
+
println
|
|
6292
|
+
);
|
|
6293
|
+
} else {
|
|
6294
|
+
printSetupSuccess({ targets, presentation }, println);
|
|
4733
6295
|
}
|
|
4734
6296
|
if (promptOpenCursorSettings && result.repoRoot) {
|
|
4735
|
-
const presentation = openDeps.presentation ?? createTerminalPresentation({
|
|
4736
|
-
env: env2,
|
|
4737
|
-
stdoutIsTty: openDeps.stdoutIsTty ?? process.stdout.isTTY === true,
|
|
4738
|
-
color: openDeps.color,
|
|
4739
|
-
unicode: openDeps.unicode
|
|
4740
|
-
});
|
|
4741
|
-
printCursorSetupCompletedSummary(
|
|
4742
|
-
{ repoRoot: result.repoRoot, outcomes: result.outcomes },
|
|
4743
|
-
openDeps.println,
|
|
4744
|
-
presentation
|
|
4745
|
-
);
|
|
4746
6297
|
await runOpenCursorMcpSettingsFlow({
|
|
4747
6298
|
...openDeps,
|
|
4748
6299
|
stdinIsTty,
|
|
4749
6300
|
env: env2,
|
|
4750
|
-
presentation
|
|
4751
|
-
|
|
4752
|
-
|
|
4753
|
-
summarizeOutcomes(result.outcomes);
|
|
4754
|
-
if (dryRun) {
|
|
4755
|
-
console.log("[setup-ide-files] Dry run: no files written.");
|
|
4756
|
-
if (result.daemonCleanup && !result.daemonCleanup.skipped) {
|
|
4757
|
-
console.log("[setup-ide-files] Dry run: no daemons stopped, no sockets removed.");
|
|
4758
|
-
}
|
|
4759
|
-
}
|
|
4760
|
-
console.log(`[setup-ide-files] ${restartIdeInstruction(targets)}`);
|
|
6301
|
+
presentation,
|
|
6302
|
+
println
|
|
6303
|
+
});
|
|
4761
6304
|
}
|
|
4762
6305
|
if (cleanup) {
|
|
4763
|
-
|
|
6306
|
+
println("[setup-ide-files] Running additional full-project cleanup (--cleanup)...");
|
|
4764
6307
|
const cleanupResult = await runCleanup({
|
|
4765
6308
|
cwd: cwd2,
|
|
4766
6309
|
dryRun,
|
|
@@ -4776,85 +6319,13 @@ async function cliSetupIdeFiles(argv, options = {}) {
|
|
|
4776
6319
|
}
|
|
4777
6320
|
|
|
4778
6321
|
// src/localState/connectCommand.ts
|
|
4779
|
-
var
|
|
4780
|
-
var
|
|
4781
|
-
|
|
4782
|
-
// src/config.ts
|
|
4783
|
-
var process2 = __toESM(require("process"), 1);
|
|
4784
|
-
var fs14 = __toESM(require("fs"), 1);
|
|
4785
|
-
var path18 = __toESM(require("path"), 1);
|
|
4786
|
-
var dotenv = __toESM(require("dotenv"), 1);
|
|
4787
|
-
var import_v4 = require("zod/v4");
|
|
4788
|
-
var dotenvPath = path18.resolve(process2.cwd(), ".env");
|
|
4789
|
-
if (fs14.existsSync(dotenvPath)) {
|
|
4790
|
-
try {
|
|
4791
|
-
dotenv.config({ path: dotenvPath });
|
|
4792
|
-
} catch (err) {
|
|
4793
|
-
process2.stderr.write("[memoraone-mcp] Failed to load .env: " + String(err) + "\n");
|
|
4794
|
-
}
|
|
4795
|
-
}
|
|
4796
|
-
var EnvSchema = import_v4.z.object({
|
|
4797
|
-
MEMORAONE_API_URL: import_v4.z.string().url().optional(),
|
|
4798
|
-
MEMORAONE_API_KEY: import_v4.z.string().min(1).optional(),
|
|
4799
|
-
MEMORAONE_DEV_MODE: import_v4.z.string().min(1).optional(),
|
|
4800
|
-
MEMORAONE_AGENT_NAME: import_v4.z.string().min(1).optional(),
|
|
4801
|
-
MEMORAONE_AGENT_TYPE: import_v4.z.string().min(1).optional(),
|
|
4802
|
-
MEMORAONE_SOURCE: import_v4.z.string().min(1).optional(),
|
|
4803
|
-
MEMORAONE_IDE_TYPE: import_v4.z.enum(["cursor", "copilot-vscode", "jetbrains"]).optional(),
|
|
4804
|
-
MEMORAONE_WORKLOG: import_v4.z.string().min(1).optional(),
|
|
4805
|
-
MEMORAONE_HEARTBEAT: import_v4.z.string().min(1).optional(),
|
|
4806
|
-
MEMORAONE_HEARTBEAT_INTERVAL_MS: import_v4.z.string().min(1).optional()
|
|
4807
|
-
});
|
|
4808
|
-
var requiredEnvVars = [];
|
|
4809
|
-
var missingEnvVars = requiredEnvVars.filter((key) => {
|
|
4810
|
-
const value = process2.env[key];
|
|
4811
|
-
return value === void 0 || value.trim() === "";
|
|
4812
|
-
});
|
|
4813
|
-
if (missingEnvVars.length > 0) {
|
|
4814
|
-
for (const key of missingEnvVars) {
|
|
4815
|
-
process2.stderr.write(`Missing ${key}
|
|
4816
|
-
`);
|
|
4817
|
-
}
|
|
4818
|
-
process2.exit(1);
|
|
4819
|
-
}
|
|
4820
|
-
var parsed = EnvSchema.safeParse(process2.env);
|
|
4821
|
-
var resolvedApiUrl = resolveApiUrl(process2.env);
|
|
4822
|
-
if (!parsed.success) {
|
|
4823
|
-
const formatted = parsed.error.format();
|
|
4824
|
-
process2.stderr.write(
|
|
4825
|
-
"[memoraone-mcp] Invalid environment variables " + JSON.stringify(formatted) + "\n"
|
|
4826
|
-
);
|
|
4827
|
-
throw new Error("Config validation failed");
|
|
4828
|
-
}
|
|
4829
|
-
var parseBooleanFlag2 = (value, defaultValue) => {
|
|
4830
|
-
if (value === void 0) {
|
|
4831
|
-
return defaultValue;
|
|
4832
|
-
}
|
|
4833
|
-
const normalized = value.trim().toLowerCase();
|
|
4834
|
-
if (["1", "true", "yes", "on"].includes(normalized)) {
|
|
4835
|
-
return true;
|
|
4836
|
-
}
|
|
4837
|
-
if (["0", "false", "no", "off"].includes(normalized)) {
|
|
4838
|
-
return false;
|
|
4839
|
-
}
|
|
4840
|
-
return defaultValue;
|
|
4841
|
-
};
|
|
4842
|
-
var config2 = {
|
|
4843
|
-
apiUrl: resolvedApiUrl.replace(/\/+$/, ""),
|
|
4844
|
-
apiKey: parsed.data.MEMORAONE_API_KEY,
|
|
4845
|
-
agentName: parsed.data.MEMORAONE_AGENT_NAME ?? "cursor",
|
|
4846
|
-
agentType: parsed.data.MEMORAONE_AGENT_TYPE ?? "agent",
|
|
4847
|
-
source: parsed.data.MEMORAONE_SOURCE ?? "cursor",
|
|
4848
|
-
ideType: parsed.data.MEMORAONE_IDE_TYPE,
|
|
4849
|
-
devMode: parseBooleanFlag2(parsed.data.MEMORAONE_DEV_MODE, false),
|
|
4850
|
-
worklogEnabled: parseBooleanFlag2(parsed.data.MEMORAONE_WORKLOG, true),
|
|
4851
|
-
heartbeatEnabled: parseBooleanFlag2(parsed.data.MEMORAONE_HEARTBEAT, true),
|
|
4852
|
-
heartbeatIntervalMs: Number.parseInt(parsed.data.MEMORAONE_HEARTBEAT_INTERVAL_MS ?? "30000", 10)
|
|
4853
|
-
};
|
|
6322
|
+
var fs20 = __toESM(require("fs/promises"), 1);
|
|
6323
|
+
var path24 = __toESM(require("path"), 1);
|
|
6324
|
+
var os8 = __toESM(require("os"), 1);
|
|
4854
6325
|
|
|
4855
6326
|
// src/repoFingerprint.ts
|
|
4856
|
-
var
|
|
4857
|
-
var
|
|
6327
|
+
var fs18 = __toESM(require("fs"), 1);
|
|
6328
|
+
var path22 = __toESM(require("path"), 1);
|
|
4858
6329
|
var crypto2 = __toESM(require("crypto"), 1);
|
|
4859
6330
|
var parseBooleanFlag3 = (value) => {
|
|
4860
6331
|
if (!value) {
|
|
@@ -4884,16 +6355,16 @@ var sha256 = (value) => {
|
|
|
4884
6355
|
};
|
|
4885
6356
|
var resolveGitDir = (gitPath) => {
|
|
4886
6357
|
try {
|
|
4887
|
-
const stat4 =
|
|
6358
|
+
const stat4 = fs18.statSync(gitPath);
|
|
4888
6359
|
if (stat4.isDirectory()) {
|
|
4889
6360
|
return gitPath;
|
|
4890
6361
|
}
|
|
4891
6362
|
if (stat4.isFile()) {
|
|
4892
|
-
const content =
|
|
6363
|
+
const content = fs18.readFileSync(gitPath, "utf8");
|
|
4893
6364
|
const match = content.match(/^gitdir:\s*(.+)$/m);
|
|
4894
6365
|
if (match) {
|
|
4895
6366
|
const gitDir = match[1].trim();
|
|
4896
|
-
return
|
|
6367
|
+
return path22.resolve(path22.dirname(gitPath), gitDir);
|
|
4897
6368
|
}
|
|
4898
6369
|
}
|
|
4899
6370
|
} catch {
|
|
@@ -4902,16 +6373,16 @@ var resolveGitDir = (gitPath) => {
|
|
|
4902
6373
|
return null;
|
|
4903
6374
|
};
|
|
4904
6375
|
var findGitRoot = (start) => {
|
|
4905
|
-
let current =
|
|
6376
|
+
let current = path22.resolve(start);
|
|
4906
6377
|
while (true) {
|
|
4907
|
-
const gitPath =
|
|
4908
|
-
if (
|
|
6378
|
+
const gitPath = path22.join(current, ".git");
|
|
6379
|
+
if (fs18.existsSync(gitPath)) {
|
|
4909
6380
|
const gitDir = resolveGitDir(gitPath);
|
|
4910
6381
|
if (gitDir) {
|
|
4911
6382
|
return { gitRoot: current, gitDir };
|
|
4912
6383
|
}
|
|
4913
6384
|
}
|
|
4914
|
-
const parent =
|
|
6385
|
+
const parent = path22.dirname(current);
|
|
4915
6386
|
if (parent === current) {
|
|
4916
6387
|
break;
|
|
4917
6388
|
}
|
|
@@ -4920,9 +6391,9 @@ var findGitRoot = (start) => {
|
|
|
4920
6391
|
return null;
|
|
4921
6392
|
};
|
|
4922
6393
|
var readOriginRemote = (gitDir) => {
|
|
4923
|
-
const configPath =
|
|
6394
|
+
const configPath = path22.join(gitDir, "config");
|
|
4924
6395
|
try {
|
|
4925
|
-
const content =
|
|
6396
|
+
const content = fs18.readFileSync(configPath, "utf8");
|
|
4926
6397
|
const lines = content.split(/\r?\n/);
|
|
4927
6398
|
let inOrigin = false;
|
|
4928
6399
|
for (const line of lines) {
|
|
@@ -4946,7 +6417,7 @@ var readOriginRemote = (gitDir) => {
|
|
|
4946
6417
|
function resolveRepoFingerprint(cwd2) {
|
|
4947
6418
|
const found = findGitRoot(cwd2);
|
|
4948
6419
|
if (!found) {
|
|
4949
|
-
const fallbackPath =
|
|
6420
|
+
const fallbackPath = path22.resolve(cwd2);
|
|
4950
6421
|
const fingerprint2 = sha256(fallbackPath);
|
|
4951
6422
|
debugLog(`repo fingerprint=${fingerprint2} source=path-fallback`);
|
|
4952
6423
|
return {
|
|
@@ -4968,7 +6439,7 @@ function resolveRepoFingerprint(cwd2) {
|
|
|
4968
6439
|
source: "git-remote"
|
|
4969
6440
|
};
|
|
4970
6441
|
}
|
|
4971
|
-
const fingerprint = sha256(
|
|
6442
|
+
const fingerprint = sha256(path22.resolve(gitRoot));
|
|
4972
6443
|
debugLog(`repo fingerprint=${fingerprint} source=path-fallback`);
|
|
4973
6444
|
return {
|
|
4974
6445
|
fingerprint,
|
|
@@ -4977,6 +6448,188 @@ function resolveRepoFingerprint(cwd2) {
|
|
|
4977
6448
|
};
|
|
4978
6449
|
}
|
|
4979
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
|
+
|
|
4980
6633
|
// src/localState/connectCommand.ts
|
|
4981
6634
|
function normalizeConnectCode(code) {
|
|
4982
6635
|
const trimmed = code.trim();
|
|
@@ -4994,12 +6647,29 @@ function normalizeGitRemote(remoteUrl) {
|
|
|
4994
6647
|
normalized = normalized.replace(/\/+$/, "");
|
|
4995
6648
|
return normalized.toLowerCase() || null;
|
|
4996
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
|
+
}
|
|
4997
6666
|
async function runConnectCommand(options) {
|
|
4998
6667
|
const code = normalizeConnectCode(options.code);
|
|
4999
|
-
const cwd2 =
|
|
6668
|
+
const cwd2 = path24.resolve(options.cwd ?? process.cwd());
|
|
5000
6669
|
const apiUrl = (options.apiUrl ?? config2.apiUrl).replace(/\/+$/, "");
|
|
5001
|
-
const homeDir = options.homeDir ??
|
|
6670
|
+
const homeDir = options.homeDir ?? os8.homedir();
|
|
5002
6671
|
const environment = "local";
|
|
6672
|
+
const verifyLocalBinding = options.verifyLocalBinding ?? resolveLocalBinding;
|
|
5003
6673
|
const executionMode = await resolvePackageExecutionMode({
|
|
5004
6674
|
executionMode: options.executionMode,
|
|
5005
6675
|
// Only honor an explicit caller cliPath; never auto-resolve before mode detection
|
|
@@ -5020,154 +6690,240 @@ async function runConnectCommand(options) {
|
|
|
5020
6690
|
}
|
|
5021
6691
|
resolvedMode = { kind: "local", cliPath };
|
|
5022
6692
|
}
|
|
5023
|
-
const
|
|
5024
|
-
|
|
5025
|
-
|
|
5026
|
-
createIfMissing: true
|
|
5027
|
-
});
|
|
5028
|
-
if (ensured.legacyM1WarningPath) {
|
|
6693
|
+
const fingerprint = resolveRepoFingerprint(cwd2);
|
|
6694
|
+
const legacyM1WarningPath = await detectLegacyM1Warning2(cwd2);
|
|
6695
|
+
if (legacyM1WarningPath) {
|
|
5029
6696
|
process.stderr.write(
|
|
5030
|
-
`[memoraone-mcp] warning: ignoring legacy ${
|
|
6697
|
+
`[memoraone-mcp] warning: ignoring legacy ${path24.basename(legacyM1WarningPath)} at ${legacyM1WarningPath} (credentials and binding are package-managed)
|
|
5031
6698
|
`
|
|
5032
6699
|
);
|
|
5033
6700
|
}
|
|
5034
|
-
const
|
|
5035
|
-
const displayName = cwd2;
|
|
5036
|
-
const normalizedGitRemote = normalizeGitRemote(fingerprint.remoteUrl);
|
|
5037
|
-
const { clientRedeemKey } = await ensureClientRedeemKey(
|
|
5038
|
-
ensured.repositoryBindingId,
|
|
5039
|
-
options.credentialOptions
|
|
5040
|
-
);
|
|
5041
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5042
|
-
const pendingRecord = {
|
|
5043
|
-
v: 1,
|
|
5044
|
-
repositoryBindingId: ensured.repositoryBindingId,
|
|
5045
|
-
workspaceRoot: cwd2,
|
|
5046
|
-
filesystemIdentity: ensured.identity,
|
|
5047
|
-
rootFingerprint: fingerprint.fingerprint,
|
|
5048
|
-
displayName,
|
|
5049
|
-
environment,
|
|
5050
|
-
apiUrl,
|
|
5051
|
-
normalizedGitRemote,
|
|
5052
|
-
status: "pending",
|
|
5053
|
-
createdAt: now,
|
|
5054
|
-
updatedAt: now,
|
|
5055
|
-
packageVersion: options.packageVersion ?? null,
|
|
5056
|
-
ideType: options.ideType ?? null
|
|
5057
|
-
};
|
|
5058
|
-
await writeBindingRecord(pendingRecord, homeDir);
|
|
5059
|
-
await upsertPathIndexEntry({
|
|
5060
|
-
repositoryBindingId: ensured.repositoryBindingId,
|
|
5061
|
-
workspaceRoot: cwd2,
|
|
5062
|
-
identity: ensured.identity,
|
|
6701
|
+
const snapshot = await snapshotAndRemoveRootLocalState(cwd2, {
|
|
5063
6702
|
homeDir,
|
|
5064
|
-
|
|
6703
|
+
identityDeps: options.identityDeps,
|
|
6704
|
+
credentialOptions: options.credentialOptions,
|
|
6705
|
+
extraFingerprints: [fingerprint.fingerprint]
|
|
5065
6706
|
});
|
|
5066
|
-
const
|
|
5067
|
-
|
|
5068
|
-
|
|
5069
|
-
|
|
5070
|
-
|
|
5071
|
-
|
|
5072
|
-
|
|
5073
|
-
|
|
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,
|
|
5074
6725
|
environment,
|
|
5075
|
-
|
|
5076
|
-
|
|
5077
|
-
|
|
5078
|
-
|
|
5079
|
-
|
|
5080
|
-
|
|
5081
|
-
|
|
5082
|
-
|
|
5083
|
-
|
|
5084
|
-
{
|
|
5085
|
-
|
|
5086
|
-
|
|
5087
|
-
|
|
5088
|
-
|
|
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,
|
|
5089
6771
|
installationPublicId: redeemed.installation_public_id,
|
|
5090
|
-
projectId: redeemed.project_id
|
|
5091
|
-
|
|
5092
|
-
|
|
5093
|
-
|
|
5094
|
-
|
|
5095
|
-
|
|
5096
|
-
installationPublicId: redeemed.installation_public_id,
|
|
5097
|
-
projectId: redeemed.project_id,
|
|
5098
|
-
status: "connected",
|
|
5099
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
5100
|
-
};
|
|
5101
|
-
await writeBindingRecord(connectedRecord, homeDir);
|
|
5102
|
-
const baseSuccess = `Connected repository binding ${ensured.repositoryBindingId}` + (redeemed.recovered ? " (recovered)" : "") + ` to project ${redeemed.project_id}.`;
|
|
5103
|
-
if (options.configureIdes !== false) {
|
|
5104
|
-
const targets = { cursor: true, vscode: true, jetbrains: true };
|
|
5105
|
-
const setup = options.setupIdeFiles ?? runSetupIdeFiles;
|
|
5106
|
-
const modeSetup = setupOptionsFromPackageExecutionMode(resolvedMode);
|
|
5107
|
-
let setupResult;
|
|
6772
|
+
projectId: redeemed.project_id,
|
|
6773
|
+
status: "connected",
|
|
6774
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
6775
|
+
};
|
|
6776
|
+
await writeBindingRecord(connectedRecord, homeDir);
|
|
6777
|
+
let verified;
|
|
5108
6778
|
try {
|
|
5109
|
-
|
|
5110
|
-
cwd: cwd2,
|
|
5111
|
-
// Use the exact bound workspace root — do not rediscover via .git / .m1.
|
|
5112
|
-
workspaceRoot: cwd2,
|
|
5113
|
-
targets,
|
|
5114
|
-
force: true,
|
|
5115
|
-
dryRun: false,
|
|
5116
|
-
noGitignore: true,
|
|
5117
|
-
skipDaemonCleanup: true,
|
|
6779
|
+
verified = await verifyLocalBinding(cwd2, {
|
|
5118
6780
|
homeDir,
|
|
5119
|
-
|
|
5120
|
-
|
|
5121
|
-
...modeSetup,
|
|
5122
|
-
...options.setupIdeOptions
|
|
6781
|
+
identityDeps: options.identityDeps,
|
|
6782
|
+
credentialOptions: options.credentialOptions
|
|
5123
6783
|
});
|
|
5124
6784
|
} catch (err) {
|
|
5125
|
-
const
|
|
5126
|
-
|
|
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 {
|
|
5127
6793
|
exitCode: 1,
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
|
|
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/.`
|
|
5131
6800
|
};
|
|
5132
6801
|
}
|
|
5133
|
-
if (
|
|
5134
|
-
const detail =
|
|
5135
|
-
|
|
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
|
+
});
|
|
5136
6810
|
return {
|
|
5137
6811
|
exitCode: 1,
|
|
5138
|
-
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,
|
|
5139
6878
|
projectId: redeemed.project_id,
|
|
5140
6879
|
installationPublicId: redeemed.installation_public_id,
|
|
5141
6880
|
recovered: redeemed.recovered,
|
|
5142
|
-
createdBinding
|
|
5143
|
-
legacyM1WarningPath
|
|
5144
|
-
ideSetupError: detail,
|
|
6881
|
+
createdBinding,
|
|
6882
|
+
legacyM1WarningPath,
|
|
5145
6883
|
executionMode: resolvedMode,
|
|
5146
|
-
|
|
6884
|
+
configuredTargets: targets,
|
|
6885
|
+
setupResult,
|
|
6886
|
+
message: baseSuccess
|
|
5147
6887
|
};
|
|
5148
6888
|
}
|
|
6889
|
+
return {
|
|
6890
|
+
exitCode: 0,
|
|
6891
|
+
repositoryBindingId,
|
|
6892
|
+
projectId: redeemed.project_id,
|
|
6893
|
+
installationPublicId: redeemed.installation_public_id,
|
|
6894
|
+
recovered: redeemed.recovered,
|
|
6895
|
+
createdBinding,
|
|
6896
|
+
legacyM1WarningPath,
|
|
6897
|
+
executionMode: resolvedMode,
|
|
6898
|
+
message: baseSuccess
|
|
6899
|
+
};
|
|
6900
|
+
} catch (err) {
|
|
6901
|
+
await rollbackFailedConnect({
|
|
6902
|
+
snapshot,
|
|
6903
|
+
discardBindingId: repositoryBindingId,
|
|
6904
|
+
homeDir,
|
|
6905
|
+
credentialOptions: options.credentialOptions
|
|
6906
|
+
});
|
|
6907
|
+
throw err;
|
|
5149
6908
|
}
|
|
5150
|
-
return {
|
|
5151
|
-
exitCode: 0,
|
|
5152
|
-
repositoryBindingId: ensured.repositoryBindingId,
|
|
5153
|
-
projectId: redeemed.project_id,
|
|
5154
|
-
installationPublicId: redeemed.installation_public_id,
|
|
5155
|
-
recovered: redeemed.recovered,
|
|
5156
|
-
createdBinding: ensured.created,
|
|
5157
|
-
legacyM1WarningPath: ensured.legacyM1WarningPath,
|
|
5158
|
-
executionMode: resolvedMode,
|
|
5159
|
-
message: baseSuccess
|
|
5160
|
-
};
|
|
5161
6909
|
}
|
|
5162
6910
|
function parseConnectArgv(argv) {
|
|
5163
6911
|
let code;
|
|
5164
6912
|
let apiUrl;
|
|
6913
|
+
let verbose = false;
|
|
5165
6914
|
for (let i = 0; i < argv.length; i++) {
|
|
5166
6915
|
const a = argv[i];
|
|
6916
|
+
if (a === "--verbose") {
|
|
6917
|
+
verbose = true;
|
|
6918
|
+
continue;
|
|
6919
|
+
}
|
|
5167
6920
|
if (a === "--api-url") {
|
|
5168
6921
|
const value = argv[++i];
|
|
5169
6922
|
if (!value || value.startsWith("-")) {
|
|
5170
|
-
return {
|
|
6923
|
+
return {
|
|
6924
|
+
verbose,
|
|
6925
|
+
error: "Usage: memoraone-mcp connect <code> [--api-url <url>] [--verbose]"
|
|
6926
|
+
};
|
|
5171
6927
|
}
|
|
5172
6928
|
apiUrl = value;
|
|
5173
6929
|
continue;
|
|
@@ -5175,23 +6931,29 @@ function parseConnectArgv(argv) {
|
|
|
5175
6931
|
if (a.startsWith("--api-url=")) {
|
|
5176
6932
|
const value = a.slice("--api-url=".length);
|
|
5177
6933
|
if (!value) {
|
|
5178
|
-
return {
|
|
6934
|
+
return {
|
|
6935
|
+
verbose,
|
|
6936
|
+
error: "Usage: memoraone-mcp connect <code> [--api-url <url>] [--verbose]"
|
|
6937
|
+
};
|
|
5179
6938
|
}
|
|
5180
6939
|
apiUrl = value;
|
|
5181
6940
|
continue;
|
|
5182
6941
|
}
|
|
5183
6942
|
if (a.startsWith("-")) {
|
|
5184
|
-
return { error: `Unknown connect option: ${a}` };
|
|
6943
|
+
return { verbose, error: `Unknown connect option: ${a}` };
|
|
5185
6944
|
}
|
|
5186
6945
|
if (!code) {
|
|
5187
6946
|
code = a;
|
|
5188
6947
|
continue;
|
|
5189
6948
|
}
|
|
5190
|
-
return {
|
|
6949
|
+
return {
|
|
6950
|
+
verbose,
|
|
6951
|
+
error: "Usage: memoraone-mcp connect <code> [--api-url <url>] [--verbose]"
|
|
6952
|
+
};
|
|
5191
6953
|
}
|
|
5192
|
-
return { code, apiUrl };
|
|
6954
|
+
return { code, apiUrl, verbose };
|
|
5193
6955
|
}
|
|
5194
|
-
async function cliConnect(argv) {
|
|
6956
|
+
async function cliConnect(argv, options = {}) {
|
|
5195
6957
|
const parsed2 = parseConnectArgv(argv);
|
|
5196
6958
|
if (parsed2.error) {
|
|
5197
6959
|
process.stderr.write(`${parsed2.error}
|
|
@@ -5199,19 +6961,53 @@ async function cliConnect(argv) {
|
|
|
5199
6961
|
return 1;
|
|
5200
6962
|
}
|
|
5201
6963
|
if (!parsed2.code) {
|
|
5202
|
-
process.stderr.write("Usage: memoraone-mcp connect <code> [--api-url <url>]\n");
|
|
6964
|
+
process.stderr.write("Usage: memoraone-mcp connect <code> [--api-url <url>] [--verbose]\n");
|
|
5203
6965
|
return 1;
|
|
5204
6966
|
}
|
|
6967
|
+
const println = options.println ?? ((line) => process.stdout.write(`${line}
|
|
6968
|
+
`));
|
|
5205
6969
|
try {
|
|
5206
6970
|
const result = await runConnectCommand({
|
|
5207
6971
|
code: parsed2.code,
|
|
5208
|
-
cwd: process.cwd(),
|
|
6972
|
+
cwd: options.cwd ?? process.cwd(),
|
|
5209
6973
|
apiUrl: parsed2.apiUrl,
|
|
5210
|
-
packageVersion: process.env.npm_package_version ?? null
|
|
6974
|
+
packageVersion: process.env.npm_package_version ?? null,
|
|
6975
|
+
env: options.env,
|
|
6976
|
+
...options.connectOptions
|
|
5211
6977
|
});
|
|
5212
6978
|
if (result.exitCode === 0) {
|
|
5213
|
-
|
|
5214
|
-
|
|
6979
|
+
if (parsed2.verbose) {
|
|
6980
|
+
println(result.message);
|
|
6981
|
+
if (result.projectId) {
|
|
6982
|
+
println(`[memoraone-mcp] Project id: ${result.projectId}`);
|
|
6983
|
+
}
|
|
6984
|
+
println(`[memoraone-mcp] Repository root: ${options.cwd ?? process.cwd()}`);
|
|
6985
|
+
if (result.setupResult && result.configuredTargets) {
|
|
6986
|
+
logSetupIdeFilesVerboseSuccess(
|
|
6987
|
+
{
|
|
6988
|
+
targets: result.configuredTargets,
|
|
6989
|
+
dryRun: false,
|
|
6990
|
+
result: result.setupResult
|
|
6991
|
+
},
|
|
6992
|
+
println
|
|
6993
|
+
);
|
|
6994
|
+
}
|
|
6995
|
+
} else {
|
|
6996
|
+
const presentation = createTerminalPresentation({
|
|
6997
|
+
env: options.env ?? process.env,
|
|
6998
|
+
stdoutIsTty: options.stdoutIsTty ?? process.stdout.isTTY === true,
|
|
6999
|
+
color: options.color,
|
|
7000
|
+
unicode: options.unicode
|
|
7001
|
+
});
|
|
7002
|
+
printSetupSuccess(
|
|
7003
|
+
{
|
|
7004
|
+
repositoryConnected: true,
|
|
7005
|
+
targets: result.configuredTargets,
|
|
7006
|
+
presentation
|
|
7007
|
+
},
|
|
7008
|
+
println
|
|
7009
|
+
);
|
|
7010
|
+
}
|
|
5215
7011
|
} else {
|
|
5216
7012
|
process.stderr.write(`[memoraone-mcp] ${result.message}
|
|
5217
7013
|
`);
|
|
@@ -5233,7 +7029,7 @@ if (args.includes("--version") || args.includes("-v")) {
|
|
|
5233
7029
|
}
|
|
5234
7030
|
if (args.includes("--help") || args.includes("-h")) {
|
|
5235
7031
|
console.log(
|
|
5236
|
-
"Usage: memoraone-mcp [--version] [--help]\n memoraone-mcp connect <code> [--api-url <url>]\n memoraone-mcp [--daemon --binding-id <mrb_\u2026> [--ide cursor|copilot-vscode|jetbrains]]\n memoraone-mcp setup-ide-files [--all|--cursor|--vscode|--jetbrains] [--force] [--dry-run] [--no-gitignore] [--cleanup] [--dev] [--repair] [--workspace-root <path>] [--api-url <url>]\n Cursor API environment (with --cursor or --all): --local (node + built cli.cjs + local API) | --staging (npx + staging API)\n --workspace-root: configure an explicit bound workspace (skips .git discovery; for fileless Local MCP repair)\n --api-url: developer-only local/dev API endpoint (defaults from binding or http://localhost:3001; never Studio :3000)\n memoraone-mcp cleanup [--project-id <uuid>] [--ide cursor|copilot-vscode|jetbrains] [--dry-run] [--all-projects] [--yes]"
|
|
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]"
|
|
5237
7033
|
);
|
|
5238
7034
|
process.exit(0);
|
|
5239
7035
|
}
|