@memoraone/mcp 0.1.27 → 0.1.29
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 +329 -57
- package/package.json +1 -1
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.29",
|
|
34
34
|
type: "module",
|
|
35
35
|
main: "dist/index.cjs",
|
|
36
36
|
bin: {
|
|
@@ -67,9 +67,9 @@ var require_package = __commonJS({
|
|
|
67
67
|
});
|
|
68
68
|
|
|
69
69
|
// src/cli.ts
|
|
70
|
-
var
|
|
70
|
+
var path6 = __toESM(require("path"), 1);
|
|
71
71
|
var net = __toESM(require("net"), 1);
|
|
72
|
-
var
|
|
72
|
+
var import_node_child_process3 = require("child_process");
|
|
73
73
|
|
|
74
74
|
// src/socketPaths.ts
|
|
75
75
|
var os = __toESM(require("os"), 1);
|
|
@@ -292,8 +292,8 @@ function encodeResolvedBinding(binding) {
|
|
|
292
292
|
}
|
|
293
293
|
|
|
294
294
|
// src/setupIdeFiles.ts
|
|
295
|
-
var
|
|
296
|
-
var
|
|
295
|
+
var fs5 = __toESM(require("fs/promises"), 1);
|
|
296
|
+
var path5 = __toESM(require("path"), 1);
|
|
297
297
|
|
|
298
298
|
// src/cleanup.ts
|
|
299
299
|
var fs3 = __toESM(require("fs/promises"), 1);
|
|
@@ -685,27 +685,236 @@ async function cliCleanup(argv) {
|
|
|
685
685
|
return result.exitCode;
|
|
686
686
|
}
|
|
687
687
|
|
|
688
|
+
// src/cursorGlobalMcpConfig.ts
|
|
689
|
+
var fs4 = __toESM(require("fs/promises"), 1);
|
|
690
|
+
var os2 = __toESM(require("os"), 1);
|
|
691
|
+
var path4 = __toESM(require("path"), 1);
|
|
692
|
+
var import_node_child_process2 = require("child_process");
|
|
693
|
+
var import_node_util2 = require("util");
|
|
694
|
+
var execFileAsync2 = (0, import_node_util2.promisify)(import_node_child_process2.execFile);
|
|
695
|
+
function buildMemoraoneCursorMcpServer(npxPath) {
|
|
696
|
+
return {
|
|
697
|
+
command: npxPath,
|
|
698
|
+
args: ["-y", "@memoraone/mcp@latest"],
|
|
699
|
+
env: {
|
|
700
|
+
MEMORAONE_API_URL: "https://api.memoraone.com",
|
|
701
|
+
MEMORAONE_IDE_TYPE: "cursor"
|
|
702
|
+
}
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
async function pathExists(filePath) {
|
|
706
|
+
try {
|
|
707
|
+
await fs4.access(filePath);
|
|
708
|
+
return true;
|
|
709
|
+
} catch {
|
|
710
|
+
return false;
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
function stripLeadingLineComments(text) {
|
|
714
|
+
return text.split("\n").filter((line) => !/^\s*\/\//.test(line)).join("\n");
|
|
715
|
+
}
|
|
716
|
+
function getKnownCursorGlobalMcpConfigCandidates(homeDir) {
|
|
717
|
+
return [path4.join(homeDir, ".cursor", "mcp.json")];
|
|
718
|
+
}
|
|
719
|
+
async function detectCursorGlobalMcpConfig(options) {
|
|
720
|
+
if (options?.explicitPath) {
|
|
721
|
+
return { ok: true, path: options.explicitPath, detectedExisting: await pathExists(options.explicitPath) };
|
|
722
|
+
}
|
|
723
|
+
const homeDir = options?.homeDir ?? os2.homedir();
|
|
724
|
+
const candidates = getKnownCursorGlobalMcpConfigCandidates(homeDir);
|
|
725
|
+
const existing = [];
|
|
726
|
+
for (const candidate of candidates) {
|
|
727
|
+
if (await pathExists(candidate)) existing.push(candidate);
|
|
728
|
+
}
|
|
729
|
+
if (existing.length > 1) {
|
|
730
|
+
return {
|
|
731
|
+
ok: false,
|
|
732
|
+
error: "[setup-ide-files] Multiple Cursor global MCP config paths found. Specify one explicitly.",
|
|
733
|
+
candidates: existing
|
|
734
|
+
};
|
|
735
|
+
}
|
|
736
|
+
if (existing.length === 1) {
|
|
737
|
+
return { ok: true, path: existing[0], detectedExisting: true };
|
|
738
|
+
}
|
|
739
|
+
const defaultPath = candidates[0];
|
|
740
|
+
if (!defaultPath) {
|
|
741
|
+
return {
|
|
742
|
+
ok: false,
|
|
743
|
+
error: "[setup-ide-files] No known Cursor global MCP config path.",
|
|
744
|
+
candidates: []
|
|
745
|
+
};
|
|
746
|
+
}
|
|
747
|
+
return { ok: true, path: defaultPath, detectedExisting: false };
|
|
748
|
+
}
|
|
749
|
+
function formatBackupTimestamp(d = /* @__PURE__ */ new Date()) {
|
|
750
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
751
|
+
return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
|
|
752
|
+
}
|
|
753
|
+
async function isWorkingNpx(npxPath) {
|
|
754
|
+
try {
|
|
755
|
+
if (!await pathExists(npxPath)) return false;
|
|
756
|
+
if (process.platform !== "win32") {
|
|
757
|
+
try {
|
|
758
|
+
await fs4.access(npxPath, fs4.constants.X_OK);
|
|
759
|
+
} catch {
|
|
760
|
+
return false;
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
await execFileAsync2(npxPath, ["--version"], { timeout: 1e4 });
|
|
764
|
+
return true;
|
|
765
|
+
} catch {
|
|
766
|
+
return false;
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
async function resolveNpxPath() {
|
|
770
|
+
const npxName = process.platform === "win32" ? "npx.cmd" : "npx";
|
|
771
|
+
const candidates = [];
|
|
772
|
+
if (process.platform === "darwin") {
|
|
773
|
+
candidates.push("/opt/homebrew/bin/npx", "/usr/local/bin/npx");
|
|
774
|
+
} else if (process.platform === "linux") {
|
|
775
|
+
candidates.push("/usr/local/bin/npx");
|
|
776
|
+
}
|
|
777
|
+
const pathSep = process.platform === "win32" ? ";" : ":";
|
|
778
|
+
for (const dir of (process.env.PATH ?? "").split(pathSep)) {
|
|
779
|
+
if (!dir) continue;
|
|
780
|
+
candidates.push(path4.join(dir, npxName));
|
|
781
|
+
}
|
|
782
|
+
try {
|
|
783
|
+
const lookupCmd = process.platform === "win32" ? "where" : "which";
|
|
784
|
+
const { stdout } = await execFileAsync2(lookupCmd, [npxName], { timeout: 5e3 });
|
|
785
|
+
const first = stdout.trim().split(/\r?\n/).map((line) => line.trim()).find(Boolean);
|
|
786
|
+
if (first) candidates.unshift(first);
|
|
787
|
+
} catch {
|
|
788
|
+
}
|
|
789
|
+
const seen = /* @__PURE__ */ new Set();
|
|
790
|
+
for (const candidate of candidates) {
|
|
791
|
+
const abs = path4.isAbsolute(candidate) ? candidate : path4.resolve(candidate);
|
|
792
|
+
const key = process.platform === "win32" ? abs.toLowerCase() : abs;
|
|
793
|
+
if (seen.has(key)) continue;
|
|
794
|
+
seen.add(key);
|
|
795
|
+
if (await isWorkingNpx(abs)) return abs;
|
|
796
|
+
}
|
|
797
|
+
return null;
|
|
798
|
+
}
|
|
799
|
+
function mergeCursorGlobalMcpConfigObject(existing, npxPath) {
|
|
800
|
+
const base = existing && typeof existing === "object" ? { ...existing } : { mcpServers: {} };
|
|
801
|
+
const mcpServers = typeof base.mcpServers === "object" && base.mcpServers !== null && !Array.isArray(base.mcpServers) ? { ...base.mcpServers } : {};
|
|
802
|
+
mcpServers.memoraone = buildMemoraoneCursorMcpServer(npxPath);
|
|
803
|
+
return { ...base, mcpServers };
|
|
804
|
+
}
|
|
805
|
+
function memoraoneServerMatches(server, npxPath) {
|
|
806
|
+
if (!server || typeof server !== "object") return false;
|
|
807
|
+
const s = server;
|
|
808
|
+
if (s.command !== npxPath) return false;
|
|
809
|
+
if (!Array.isArray(s.args) || s.args.length !== 2) return false;
|
|
810
|
+
if (s.args[0] !== "-y" || s.args[1] !== "@memoraone/mcp@latest") return false;
|
|
811
|
+
const env = s.env;
|
|
812
|
+
if (!env || typeof env !== "object") return false;
|
|
813
|
+
const e = env;
|
|
814
|
+
return e.MEMORAONE_API_URL === "https://api.memoraone.com" && e.MEMORAONE_IDE_TYPE === "cursor";
|
|
815
|
+
}
|
|
816
|
+
function validateCursorGlobalMcpConfig(parsed, npxPath) {
|
|
817
|
+
if (!parsed || typeof parsed !== "object") {
|
|
818
|
+
throw new Error("[setup-ide-files] Cursor global MCP config must be a JSON object.");
|
|
819
|
+
}
|
|
820
|
+
const mcpServers = parsed.mcpServers;
|
|
821
|
+
if (!mcpServers || typeof mcpServers !== "object" || Array.isArray(mcpServers)) {
|
|
822
|
+
throw new Error("[setup-ide-files] Cursor global MCP config missing mcpServers object.");
|
|
823
|
+
}
|
|
824
|
+
const memoraone = mcpServers.memoraone;
|
|
825
|
+
if (!memoraoneServerMatches(memoraone, npxPath)) {
|
|
826
|
+
throw new Error(
|
|
827
|
+
"[setup-ide-files] Cursor global MCP config mcpServers.memoraone is missing or invalid."
|
|
828
|
+
);
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
async function setupCursorGlobalMcpConfig(options) {
|
|
832
|
+
const { configPath, npxPath, dryRun } = options;
|
|
833
|
+
const existed = await pathExists(configPath);
|
|
834
|
+
let existing = null;
|
|
835
|
+
if (existed) {
|
|
836
|
+
const raw = await fs4.readFile(configPath, "utf8");
|
|
837
|
+
try {
|
|
838
|
+
existing = JSON.parse(stripLeadingLineComments(raw));
|
|
839
|
+
} catch {
|
|
840
|
+
throw new Error(
|
|
841
|
+
`[setup-ide-files] Invalid JSON in Cursor global MCP config: ${configPath}`
|
|
842
|
+
);
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
const merged = mergeCursorGlobalMcpConfigObject(existing, npxPath);
|
|
846
|
+
const body = JSON.stringify(merged, null, 2) + "\n";
|
|
847
|
+
if (existed) {
|
|
848
|
+
const currentMemoraone = existing && typeof existing.mcpServers === "object" && existing.mcpServers !== null && !Array.isArray(existing.mcpServers) ? existing.mcpServers.memoraone : void 0;
|
|
849
|
+
if (memoraoneServerMatches(currentMemoraone, npxPath)) {
|
|
850
|
+
return { outcome: "skipped" };
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
if (dryRun) {
|
|
854
|
+
return { outcome: existed ? "updated" : "created" };
|
|
855
|
+
}
|
|
856
|
+
let backupPath;
|
|
857
|
+
if (existed) {
|
|
858
|
+
backupPath = `${configPath}.backup-${formatBackupTimestamp()}`;
|
|
859
|
+
await fs4.copyFile(configPath, backupPath);
|
|
860
|
+
}
|
|
861
|
+
await fs4.mkdir(path4.dirname(configPath), { recursive: true });
|
|
862
|
+
await fs4.writeFile(configPath, body, "utf8");
|
|
863
|
+
const verifyRaw = await fs4.readFile(configPath, "utf8");
|
|
864
|
+
const verifyParsed = JSON.parse(stripLeadingLineComments(verifyRaw));
|
|
865
|
+
validateCursorGlobalMcpConfig(verifyParsed, npxPath);
|
|
866
|
+
return { outcome: existed ? "updated" : "created", backupPath };
|
|
867
|
+
}
|
|
868
|
+
function logCursorGlobalMcpCliSummary(info, dryRun) {
|
|
869
|
+
const { configPath, npxPath, backupPath, outcome } = info;
|
|
870
|
+
console.log(`[setup-ide-files] Cursor global MCP config: ${configPath}`);
|
|
871
|
+
console.log(`[setup-ide-files] Resolved npx: ${npxPath}`);
|
|
872
|
+
if (backupPath) {
|
|
873
|
+
console.log(`[setup-ide-files] Cursor global MCP config backup: ${backupPath}`);
|
|
874
|
+
}
|
|
875
|
+
if (outcome === "created") {
|
|
876
|
+
console.log(
|
|
877
|
+
dryRun ? `[setup-ide-files] Cursor global MCP config would be created: ${configPath}` : `[setup-ide-files] Cursor global MCP config created: ${configPath}`
|
|
878
|
+
);
|
|
879
|
+
} else if (outcome === "updated") {
|
|
880
|
+
console.log(
|
|
881
|
+
dryRun ? `[setup-ide-files] Cursor global MCP config would be updated: ${configPath}` : `[setup-ide-files] Cursor global MCP config updated: ${configPath}`
|
|
882
|
+
);
|
|
883
|
+
} else if (outcome === "skipped") {
|
|
884
|
+
console.log(`[setup-ide-files] Cursor global MCP config unchanged: ${configPath}`);
|
|
885
|
+
}
|
|
886
|
+
console.log(
|
|
887
|
+
"[setup-ide-files] Repo-level .cursor/mcp.json was not created or modified."
|
|
888
|
+
);
|
|
889
|
+
console.log(
|
|
890
|
+
"[setup-ide-files] Fully quit Cursor and reopen this repo for MCP changes to take effect."
|
|
891
|
+
);
|
|
892
|
+
}
|
|
893
|
+
|
|
688
894
|
// src/setupIdeFiles.ts
|
|
689
895
|
var MANAGED_MARKER = "<!-- MemoraOne managed IDE helper -->";
|
|
690
896
|
var GITIGNORE_MEMORAONE_COMMENT = "# MemoraOne local project binding / API key";
|
|
691
897
|
var GITIGNORE_MEMORAONE_ENTRY = "memoraone.m1";
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
898
|
+
function buildMemoraoneMcpServer(ideType, command = "npx") {
|
|
899
|
+
return {
|
|
900
|
+
command,
|
|
901
|
+
args: ["-y", "@memoraone/mcp@latest"],
|
|
902
|
+
env: {
|
|
903
|
+
MEMORAONE_API_URL: "https://api.memoraone.com",
|
|
904
|
+
MEMORAONE_IDE_TYPE: ideType
|
|
905
|
+
}
|
|
906
|
+
};
|
|
907
|
+
}
|
|
699
908
|
function assertUnderRepoRoot(repoRoot, absPath) {
|
|
700
|
-
const normRoot =
|
|
701
|
-
const normPath =
|
|
702
|
-
if (normPath !==
|
|
909
|
+
const normRoot = path5.resolve(repoRoot) + path5.sep;
|
|
910
|
+
const normPath = path5.resolve(absPath);
|
|
911
|
+
if (normPath !== path5.resolve(repoRoot) && !normPath.startsWith(normRoot)) {
|
|
703
912
|
throw new Error(`[setup-ide-files] Refusing to write outside repo root: ${absPath}`);
|
|
704
913
|
}
|
|
705
914
|
}
|
|
706
|
-
async function
|
|
915
|
+
async function pathExists2(filePath) {
|
|
707
916
|
try {
|
|
708
|
-
await
|
|
917
|
+
await fs5.access(filePath);
|
|
709
918
|
return true;
|
|
710
919
|
} catch {
|
|
711
920
|
return false;
|
|
@@ -726,12 +935,12 @@ ${GITIGNORE_MEMORAONE_ENTRY}
|
|
|
726
935
|
}
|
|
727
936
|
async function ensureGitignoreMemoraone(repoRoot, opts) {
|
|
728
937
|
if (opts.noGitignore) return "skipped";
|
|
729
|
-
const abs =
|
|
938
|
+
const abs = path5.join(repoRoot, ".gitignore");
|
|
730
939
|
assertUnderRepoRoot(repoRoot, abs);
|
|
731
940
|
let prior = "";
|
|
732
941
|
let existed = false;
|
|
733
942
|
try {
|
|
734
|
-
prior = await
|
|
943
|
+
prior = await fs5.readFile(abs, "utf8");
|
|
735
944
|
existed = true;
|
|
736
945
|
} catch (err) {
|
|
737
946
|
const code = err && typeof err === "object" && "code" in err ? err.code : void 0;
|
|
@@ -742,25 +951,25 @@ async function ensureGitignoreMemoraone(repoRoot, opts) {
|
|
|
742
951
|
const separator = existed && prior.length > 0 ? prior.endsWith("\n") ? "\n" : "\n\n" : "";
|
|
743
952
|
const next = (existed ? prior : "") + separator + block;
|
|
744
953
|
if (opts.dryRun) return existed ? "updated" : "created";
|
|
745
|
-
await
|
|
954
|
+
await fs5.writeFile(abs, next, "utf8");
|
|
746
955
|
return existed ? "updated" : "created";
|
|
747
956
|
}
|
|
748
957
|
async function findRepoRoot(startDir) {
|
|
749
|
-
let current =
|
|
750
|
-
const root =
|
|
958
|
+
let current = path5.resolve(startDir);
|
|
959
|
+
const root = path5.parse(current).root;
|
|
751
960
|
while (true) {
|
|
752
|
-
const gitPath =
|
|
753
|
-
const m1Path =
|
|
754
|
-
if (await
|
|
961
|
+
const gitPath = path5.join(current, ".git");
|
|
962
|
+
const m1Path = path5.join(current, "memoraone.m1");
|
|
963
|
+
if (await pathExists2(gitPath) || await pathExists2(m1Path)) {
|
|
755
964
|
return current;
|
|
756
965
|
}
|
|
757
966
|
if (current === root) {
|
|
758
967
|
return null;
|
|
759
968
|
}
|
|
760
|
-
current =
|
|
969
|
+
current = path5.dirname(current);
|
|
761
970
|
}
|
|
762
971
|
}
|
|
763
|
-
function
|
|
972
|
+
function stripLeadingLineComments2(text) {
|
|
764
973
|
return text.split("\n").filter((line) => !/^\s*\/\//.test(line)).join("\n");
|
|
765
974
|
}
|
|
766
975
|
function cursorRuleBody() {
|
|
@@ -803,75 +1012,75 @@ function mcpJsonHeader() {
|
|
|
803
1012
|
return `// ${MANAGED_MARKER}
|
|
804
1013
|
`;
|
|
805
1014
|
}
|
|
806
|
-
function
|
|
1015
|
+
function buildVscodeMcpJsonBody(existing) {
|
|
807
1016
|
const base = existing && typeof existing === "object" ? { ...existing } : { servers: {} };
|
|
808
1017
|
const servers = typeof base.servers === "object" && base.servers !== null && !Array.isArray(base.servers) ? { ...base.servers } : {};
|
|
809
|
-
servers.memoraone =
|
|
1018
|
+
servers.memoraone = buildMemoraoneMcpServer("copilot-vscode");
|
|
810
1019
|
const merged = { ...base, servers };
|
|
811
1020
|
return mcpJsonHeader() + JSON.stringify(merged, null, 2) + "\n";
|
|
812
1021
|
}
|
|
813
1022
|
async function writeManagedMarkdown(repoRoot, relPath, fullContent, opts) {
|
|
814
|
-
const abs =
|
|
1023
|
+
const abs = path5.join(repoRoot, relPath);
|
|
815
1024
|
assertUnderRepoRoot(repoRoot, abs);
|
|
816
1025
|
let prior = "";
|
|
817
1026
|
let existed = false;
|
|
818
1027
|
try {
|
|
819
|
-
prior = await
|
|
1028
|
+
prior = await fs5.readFile(abs, "utf8");
|
|
820
1029
|
existed = true;
|
|
821
1030
|
} catch (err) {
|
|
822
1031
|
if (err?.code !== "ENOENT") throw err;
|
|
823
1032
|
}
|
|
824
1033
|
if (!existed) {
|
|
825
1034
|
if (opts.dryRun) return "created";
|
|
826
|
-
await
|
|
827
|
-
await
|
|
1035
|
+
await fs5.mkdir(path5.dirname(abs), { recursive: true });
|
|
1036
|
+
await fs5.writeFile(abs, fullContent, "utf8");
|
|
828
1037
|
return "created";
|
|
829
1038
|
}
|
|
830
1039
|
if (prior.includes(MANAGED_MARKER)) {
|
|
831
1040
|
if (prior === fullContent) return "skipped";
|
|
832
1041
|
if (opts.dryRun) return "updated";
|
|
833
|
-
await
|
|
834
|
-
await
|
|
1042
|
+
await fs5.mkdir(path5.dirname(abs), { recursive: true });
|
|
1043
|
+
await fs5.writeFile(abs, fullContent, "utf8");
|
|
835
1044
|
return "updated";
|
|
836
1045
|
}
|
|
837
1046
|
if (!opts.force) return "skipped-untracked";
|
|
838
1047
|
if (opts.dryRun) return "updated";
|
|
839
|
-
await
|
|
840
|
-
await
|
|
1048
|
+
await fs5.mkdir(path5.dirname(abs), { recursive: true });
|
|
1049
|
+
await fs5.writeFile(abs, fullContent, "utf8");
|
|
841
1050
|
return "updated";
|
|
842
1051
|
}
|
|
843
|
-
async function
|
|
844
|
-
const abs =
|
|
1052
|
+
async function writeIdeMcpJson(repoRoot, relPath, buildBody, opts) {
|
|
1053
|
+
const abs = path5.join(repoRoot, relPath);
|
|
845
1054
|
assertUnderRepoRoot(repoRoot, abs);
|
|
846
1055
|
let raw = "";
|
|
847
1056
|
let existed = false;
|
|
848
1057
|
try {
|
|
849
|
-
raw = await
|
|
1058
|
+
raw = await fs5.readFile(abs, "utf8");
|
|
850
1059
|
existed = true;
|
|
851
1060
|
} catch (err) {
|
|
852
1061
|
if (err?.code !== "ENOENT") throw err;
|
|
853
1062
|
}
|
|
854
1063
|
if (!existed) {
|
|
855
|
-
const body =
|
|
1064
|
+
const body = buildBody(null);
|
|
856
1065
|
if (opts.dryRun) return "created";
|
|
857
|
-
await
|
|
858
|
-
await
|
|
1066
|
+
await fs5.mkdir(path5.dirname(abs), { recursive: true });
|
|
1067
|
+
await fs5.writeFile(abs, body, "utf8");
|
|
859
1068
|
return "created";
|
|
860
1069
|
}
|
|
861
1070
|
const managed = raw.includes(MANAGED_MARKER);
|
|
862
1071
|
if (!managed && !opts.force) return "skipped-untracked";
|
|
863
1072
|
let parsed = null;
|
|
864
1073
|
try {
|
|
865
|
-
parsed = JSON.parse(
|
|
1074
|
+
parsed = JSON.parse(stripLeadingLineComments2(raw));
|
|
866
1075
|
} catch {
|
|
867
1076
|
parsed = null;
|
|
868
1077
|
}
|
|
869
1078
|
if (!parsed && !opts.force) return "skipped-untracked";
|
|
870
|
-
const next =
|
|
1079
|
+
const next = buildBody(parsed);
|
|
871
1080
|
if (managed && next === raw) return "skipped";
|
|
872
1081
|
if (opts.dryRun) return "updated";
|
|
873
|
-
await
|
|
874
|
-
await
|
|
1082
|
+
await fs5.mkdir(path5.dirname(abs), { recursive: true });
|
|
1083
|
+
await fs5.writeFile(abs, next, "utf8");
|
|
875
1084
|
return "updated";
|
|
876
1085
|
}
|
|
877
1086
|
function parseSetupIdeFlags(argv) {
|
|
@@ -926,6 +1135,7 @@ function summarizeOutcomes(outcomes) {
|
|
|
926
1135
|
}
|
|
927
1136
|
async function runSetupIdeFiles(o) {
|
|
928
1137
|
const outcomes = {};
|
|
1138
|
+
let cursorGlobalMcp;
|
|
929
1139
|
const repoRoot = await findRepoRoot(o.cwd);
|
|
930
1140
|
if (!repoRoot) {
|
|
931
1141
|
return {
|
|
@@ -945,18 +1155,73 @@ description: MemoraOne MCP \u2014 IDE agent instructions
|
|
|
945
1155
|
|
|
946
1156
|
` + cursorRuleBody();
|
|
947
1157
|
if (o.targets.cursor) {
|
|
1158
|
+
const detection = await detectCursorGlobalMcpConfig({
|
|
1159
|
+
homeDir: o.homeDir,
|
|
1160
|
+
explicitPath: o.cursorGlobalMcpConfigPath
|
|
1161
|
+
});
|
|
1162
|
+
if (!detection.ok) {
|
|
1163
|
+
return {
|
|
1164
|
+
exitCode: 1,
|
|
1165
|
+
repoRoot,
|
|
1166
|
+
outcomes,
|
|
1167
|
+
error: `${detection.error}
|
|
1168
|
+
${detection.candidates.join("\n ")}`
|
|
1169
|
+
};
|
|
1170
|
+
}
|
|
1171
|
+
let npxPath;
|
|
1172
|
+
if (o.npxPathOverride !== void 0) {
|
|
1173
|
+
npxPath = o.npxPathOverride;
|
|
1174
|
+
} else {
|
|
1175
|
+
npxPath = await resolveNpxPath();
|
|
1176
|
+
}
|
|
1177
|
+
if (!npxPath) {
|
|
1178
|
+
return {
|
|
1179
|
+
exitCode: 1,
|
|
1180
|
+
repoRoot,
|
|
1181
|
+
outcomes,
|
|
1182
|
+
error: "[setup-ide-files] Could not resolve a working npx executable. Install Node.js/npm or ensure npx is on PATH before configuring Cursor global MCP."
|
|
1183
|
+
};
|
|
1184
|
+
}
|
|
948
1185
|
outcomes[".cursor/rules/memoraone-mcp.mdc"] = await writeManagedMarkdown(
|
|
949
1186
|
repoRoot,
|
|
950
1187
|
".cursor/rules/memoraone-mcp.mdc",
|
|
951
1188
|
cursorContent,
|
|
952
1189
|
{ force: o.force, dryRun: o.dryRun }
|
|
953
1190
|
);
|
|
1191
|
+
try {
|
|
1192
|
+
const globalSetup = await setupCursorGlobalMcpConfig({
|
|
1193
|
+
configPath: detection.path,
|
|
1194
|
+
npxPath,
|
|
1195
|
+
dryRun: o.dryRun
|
|
1196
|
+
});
|
|
1197
|
+
cursorGlobalMcp = {
|
|
1198
|
+
configPath: detection.path,
|
|
1199
|
+
outcome: globalSetup.outcome,
|
|
1200
|
+
npxPath,
|
|
1201
|
+
backupPath: globalSetup.backupPath
|
|
1202
|
+
};
|
|
1203
|
+
outcomes[`cursor-global:${detection.path}`] = globalSetup.outcome;
|
|
1204
|
+
} catch (err) {
|
|
1205
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1206
|
+
return {
|
|
1207
|
+
exitCode: 1,
|
|
1208
|
+
repoRoot,
|
|
1209
|
+
outcomes,
|
|
1210
|
+
cursorGlobalMcp: { configPath: detection.path, outcome: "skipped", npxPath },
|
|
1211
|
+
error: message
|
|
1212
|
+
};
|
|
1213
|
+
}
|
|
954
1214
|
}
|
|
955
1215
|
if (o.targets.vscode) {
|
|
956
|
-
outcomes[".vscode/mcp.json"] = await
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
1216
|
+
outcomes[".vscode/mcp.json"] = await writeIdeMcpJson(
|
|
1217
|
+
repoRoot,
|
|
1218
|
+
".vscode/mcp.json",
|
|
1219
|
+
buildVscodeMcpJsonBody,
|
|
1220
|
+
{
|
|
1221
|
+
force: o.force,
|
|
1222
|
+
dryRun: o.dryRun
|
|
1223
|
+
}
|
|
1224
|
+
);
|
|
960
1225
|
outcomes[".github/copilot-instructions.md"] = await writeManagedMarkdown(
|
|
961
1226
|
repoRoot,
|
|
962
1227
|
".github/copilot-instructions.md",
|
|
@@ -972,7 +1237,7 @@ description: MemoraOne MCP \u2014 IDE agent instructions
|
|
|
972
1237
|
{ force: o.force, dryRun: o.dryRun }
|
|
973
1238
|
);
|
|
974
1239
|
}
|
|
975
|
-
return { exitCode: 0, repoRoot, outcomes };
|
|
1240
|
+
return { exitCode: 0, repoRoot, outcomes, cursorGlobalMcp };
|
|
976
1241
|
}
|
|
977
1242
|
async function cliSetupIdeFiles(argv) {
|
|
978
1243
|
const { targets, force, dryRun, noGitignore, cleanup, unknown } = parseSetupIdeFlags(argv);
|
|
@@ -989,11 +1254,18 @@ async function cliSetupIdeFiles(argv) {
|
|
|
989
1254
|
});
|
|
990
1255
|
if (result.error) {
|
|
991
1256
|
console.error(result.error);
|
|
1257
|
+
if (result.repoRoot) {
|
|
1258
|
+
console.log(`[setup-ide-files] Repo root: ${result.repoRoot}`);
|
|
1259
|
+
}
|
|
1260
|
+
summarizeOutcomes(result.outcomes);
|
|
992
1261
|
return result.exitCode;
|
|
993
1262
|
}
|
|
994
1263
|
if (result.repoRoot) {
|
|
995
1264
|
console.log(`[setup-ide-files] Repo root: ${result.repoRoot}`);
|
|
996
1265
|
}
|
|
1266
|
+
if (targets.cursor && result.cursorGlobalMcp) {
|
|
1267
|
+
logCursorGlobalMcpCliSummary(result.cursorGlobalMcp, dryRun);
|
|
1268
|
+
}
|
|
997
1269
|
summarizeOutcomes(result.outcomes);
|
|
998
1270
|
if (dryRun) {
|
|
999
1271
|
console.log("[setup-ide-files] Dry run: no files written.");
|
|
@@ -1057,8 +1329,8 @@ if (args[0] === "cleanup") {
|
|
|
1057
1329
|
const raw = process.env.WORKSPACE_FOLDER_PATHS;
|
|
1058
1330
|
const parts = [];
|
|
1059
1331
|
if (raw !== void 0 && raw.trim() !== "") {
|
|
1060
|
-
for (const p of raw.split(
|
|
1061
|
-
parts.push(
|
|
1332
|
+
for (const p of raw.split(path6.delimiter).map((s) => s.trim()).filter(Boolean)) {
|
|
1333
|
+
parts.push(path6.resolve(p));
|
|
1062
1334
|
}
|
|
1063
1335
|
}
|
|
1064
1336
|
parts.push(process.cwd());
|
|
@@ -1072,10 +1344,10 @@ if (args[0] === "cleanup") {
|
|
|
1072
1344
|
}
|
|
1073
1345
|
return deduped;
|
|
1074
1346
|
}, connectWithRetry = function(socketPath) {
|
|
1075
|
-
return new Promise((
|
|
1347
|
+
return new Promise((resolve6, reject) => {
|
|
1076
1348
|
const tryConnect = (attempt) => {
|
|
1077
1349
|
const socket = net.connect(socketPath, () => {
|
|
1078
|
-
|
|
1350
|
+
resolve6(socket);
|
|
1079
1351
|
});
|
|
1080
1352
|
socket.on("error", (err) => {
|
|
1081
1353
|
if (attempt >= MAX_RETRIES) {
|
|
@@ -1111,7 +1383,7 @@ if (args[0] === "cleanup") {
|
|
|
1111
1383
|
socket = await connectWithRetry(socketPath);
|
|
1112
1384
|
} catch {
|
|
1113
1385
|
log("daemon not running, spawning...");
|
|
1114
|
-
const child = (0,
|
|
1386
|
+
const child = (0, import_node_child_process3.spawn)(
|
|
1115
1387
|
process.execPath,
|
|
1116
1388
|
buildDaemonSpawnArgs(process.argv[1], binding.projectId),
|
|
1117
1389
|
{
|