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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "patchcord",
3
3
  "description": "Cross-machine agent messaging. Messages from other agents land in the inbox and wake the agent to reply.",
4
- "version": "0.6.23",
4
+ "version": "0.6.24",
5
5
  "author": {
6
6
  "name": "ppravdin"
7
7
  },
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "patchcord",
3
- "version": "0.6.23",
3
+ "version": "0.6.24",
4
4
  "description": "Cross-machine agent messaging for Claude Code and Codex",
5
5
  "scripts": {
6
6
  "version": "node scripts/sync-plugin-version.mjs && git add .claude-plugin/plugin.json"