patchcord 0.6.23 → 0.6.24
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/.claude-plugin/plugin.json +1 -1
- package/bin/patchcord.mjs +88 -0
- package/package.json +1 -1
package/bin/patchcord.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import { existsSync, mkdirSync, cpSync, readdirSync, readFileSync, writeFileSync
|
|
|
4
4
|
import { join, dirname, basename } from "path";
|
|
5
5
|
import { fileURLToPath } from "url";
|
|
6
6
|
import { execSync } from "child_process";
|
|
7
|
+
import { createHash } from "crypto";
|
|
7
8
|
import { homedir } from "os";
|
|
8
9
|
|
|
9
10
|
const HOME = homedir();
|
|
@@ -28,6 +29,85 @@ function run(cmd) {
|
|
|
28
29
|
}
|
|
29
30
|
}
|
|
30
31
|
|
|
32
|
+
/**
|
|
33
|
+
* Cursor CLI gates project MCP behind per-project approval
|
|
34
|
+
* (`mcp list` → "not loaded (needs approval)"). permissions.json mcpAllowlist
|
|
35
|
+
* only covers IDE auto-run AFTER the server is loaded — it does not clear that
|
|
36
|
+
* gate. Approve via `cursor-agent mcp enable` (+ CLI allowlist + approvals file).
|
|
37
|
+
*/
|
|
38
|
+
function _approveCursorProjectMcp(projectDir) {
|
|
39
|
+
if (!projectDir || !existsSync(join(projectDir, ".cursor", "mcp.json"))) return false;
|
|
40
|
+
let changed = false;
|
|
41
|
+
|
|
42
|
+
// CLI tool allow — official format in cli-config.json (separate from permissions.json).
|
|
43
|
+
const cliConfigPath = join(HOME, ".cursor", "cli-config.json");
|
|
44
|
+
try {
|
|
45
|
+
mkdirSync(dirname(cliConfigPath), { recursive: true });
|
|
46
|
+
let obj = {};
|
|
47
|
+
if (existsSync(cliConfigPath)) {
|
|
48
|
+
try { obj = JSON.parse(readFileSync(cliConfigPath, "utf-8")); } catch { obj = {}; }
|
|
49
|
+
}
|
|
50
|
+
obj.permissions = obj.permissions || {};
|
|
51
|
+
if (!Array.isArray(obj.permissions.allow)) obj.permissions.allow = [];
|
|
52
|
+
const pat = "Mcp(patchcord:*)";
|
|
53
|
+
if (!obj.permissions.allow.includes(pat)) {
|
|
54
|
+
obj.permissions.allow.push(pat);
|
|
55
|
+
writeFileSync(cliConfigPath, JSON.stringify(obj, null, 2) + "\n");
|
|
56
|
+
changed = true;
|
|
57
|
+
}
|
|
58
|
+
} catch {}
|
|
59
|
+
|
|
60
|
+
// Official CLI API: records approval under ~/.cursor/projects/<slug>/mcp-approvals.json
|
|
61
|
+
if (run("which cursor-agent")) {
|
|
62
|
+
try {
|
|
63
|
+
execSync("cursor-agent mcp enable patchcord", {
|
|
64
|
+
cwd: projectDir,
|
|
65
|
+
stdio: "pipe",
|
|
66
|
+
encoding: "utf-8",
|
|
67
|
+
timeout: 20000,
|
|
68
|
+
});
|
|
69
|
+
changed = true;
|
|
70
|
+
} catch {
|
|
71
|
+
// Fall through to writing mcp-approvals.json directly.
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Fallback: write the same approval key cursor-agent expects (URL MCP servers).
|
|
76
|
+
try {
|
|
77
|
+
const mcpPath = join(projectDir, ".cursor", "mcp.json");
|
|
78
|
+
const cfg = JSON.parse(readFileSync(mcpPath, "utf-8"));
|
|
79
|
+
const server = cfg?.mcpServers?.patchcord;
|
|
80
|
+
const url = server?.url;
|
|
81
|
+
if (url) {
|
|
82
|
+
const slug = projectDir.replace(/[^A-Za-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
83
|
+
const approvalsDir = join(HOME, ".cursor", "projects", slug);
|
|
84
|
+
mkdirSync(approvalsDir, { recursive: true });
|
|
85
|
+
const approvalsPath = join(approvalsDir, "mcp-approvals.json");
|
|
86
|
+
let approvals = [];
|
|
87
|
+
if (existsSync(approvalsPath)) {
|
|
88
|
+
try {
|
|
89
|
+
const raw = JSON.parse(readFileSync(approvalsPath, "utf-8"));
|
|
90
|
+
if (Array.isArray(raw)) approvals = raw;
|
|
91
|
+
} catch {}
|
|
92
|
+
}
|
|
93
|
+
// Cursor hashes { path, server: { url } } and sometimes the full server object.
|
|
94
|
+
const keys = [
|
|
95
|
+
`patchcord-${createHash("sha256").update(JSON.stringify({ path: projectDir, server: { url } })).digest("hex").slice(0, 16)}`,
|
|
96
|
+
`patchcord-${createHash("sha256").update(JSON.stringify({ path: projectDir, server })).digest("hex").slice(0, 16)}`,
|
|
97
|
+
];
|
|
98
|
+
for (const key of keys) {
|
|
99
|
+
if (!approvals.includes(key)) {
|
|
100
|
+
approvals.push(key);
|
|
101
|
+
changed = true;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
writeFileSync(approvalsPath, JSON.stringify(approvals, null, 2) + "\n");
|
|
105
|
+
}
|
|
106
|
+
} catch {}
|
|
107
|
+
|
|
108
|
+
return changed;
|
|
109
|
+
}
|
|
110
|
+
|
|
31
111
|
function isSafeToken(t) {
|
|
32
112
|
return /^[A-Za-z0-9_\-=+/.]+$/.test(t) && t.length < 200;
|
|
33
113
|
}
|
|
@@ -1900,6 +1980,11 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
|
|
|
1900
1980
|
} else if (allowlistChanged) {
|
|
1901
1981
|
globalChanges.push("Cursor MCP allowlist configured");
|
|
1902
1982
|
}
|
|
1983
|
+
|
|
1984
|
+
// Existing projects: clear "not loaded (needs approval)" on update.
|
|
1985
|
+
if (_approveCursorProjectMcp(process.cwd())) {
|
|
1986
|
+
globalChanges.push("Cursor MCP approved for this project");
|
|
1987
|
+
}
|
|
1903
1988
|
}
|
|
1904
1989
|
}
|
|
1905
1990
|
|
|
@@ -2729,6 +2814,9 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
|
|
|
2729
2814
|
console.log(`\n ${green}✓${r} Cursor configured: ${dim}${cursorPath}${r}`);
|
|
2730
2815
|
console.log(` ${dim}Per-project only — other projects won't see this agent.${r}`);
|
|
2731
2816
|
}
|
|
2817
|
+
if (_approveCursorProjectMcp(cwd)) {
|
|
2818
|
+
console.log(` ${green}✓${r} Cursor MCP approved (cursor-agent mcp enable)`);
|
|
2819
|
+
}
|
|
2732
2820
|
} else if (isGrok) {
|
|
2733
2821
|
// Grok CLI: project-scoped .grok/config.toml. Grok loads this file from
|
|
2734
2822
|
// the current directory up through the git root, so the bearer remains
|
package/package.json
CHANGED