patchcord 0.6.22 → 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.22",
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
  }
@@ -1901,27 +1981,9 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
1901
1981
  globalChanges.push("Cursor MCP allowlist configured");
1902
1982
  }
1903
1983
 
1904
- // cursor-agent v2026.07+ reads ~/.cursor/cli-config.json permissions.allow
1905
- // for MCP — permissions.json mcpAllowlist alone leaves patchcord unloaded.
1906
- const cliConfigPath = join(HOME, ".cursor", "cli-config.json");
1907
- let cliAllowChanged = false;
1908
- const cliOk = updateJsonConfig(cliConfigPath, (obj) => {
1909
- obj.permissions = obj.permissions || {};
1910
- if (!obj.permissions.allow) obj.permissions.allow = [];
1911
- if (!Array.isArray(obj.permissions.allow)) {
1912
- console.log(`\n ${yellow}⚠${r} Skipped Cursor CLI MCP allow — ${cliConfigPath} permissions.allow is not an array.`);
1913
- return;
1914
- }
1915
- const pat = "Mcp(patchcord:*)";
1916
- if (!obj.permissions.allow.includes(pat)) {
1917
- obj.permissions.allow.push(pat);
1918
- cliAllowChanged = true;
1919
- }
1920
- });
1921
- if (!cliOk) {
1922
- globalChanges.push("✗ Cursor CLI permissions error");
1923
- } else if (cliAllowChanged) {
1924
- globalChanges.push("Cursor CLI MCP allowlist configured");
1984
+ // Existing projects: clear "not loaded (needs approval)" on update.
1985
+ if (_approveCursorProjectMcp(process.cwd())) {
1986
+ globalChanges.push("Cursor MCP approved for this project");
1925
1987
  }
1926
1988
  }
1927
1989
  }
@@ -2360,12 +2422,6 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
2360
2422
  }
2361
2423
  toolSlug = toolFlag; // preserved as-is for the URL param
2362
2424
  }
2363
- // cursor-agent --yolo runs npx patchcord without --tool=cursor; auto-select
2364
- // Cursor so provision writes .cursor/mcp.json for this harness.
2365
- if (!choice && process.env.CURSOR_AGENT) {
2366
- choice = CLIENT_TYPE_MAP.cursor;
2367
- toolSlug = toolSlug || "cursor";
2368
- }
2369
2425
 
2370
2426
  // --token bypass for power users / CI / self-hosters
2371
2427
  const tokenFlag = flags.find(f => f.startsWith("--token="))?.split("=")[1]
@@ -2416,14 +2472,12 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
2416
2472
  let existingToken = "";
2417
2473
  let existingConfigFile = "";
2418
2474
  const mcpJsonPath = join(cwd, ".mcp.json");
2419
- const cursorMcpPath = join(cwd, ".cursor", "mcp.json");
2420
2475
  const codexTomlPath = join(cwd, ".codex", "config.toml");
2421
2476
  const grokTomlPath = join(cwd, ".grok", "config.toml");
2422
2477
  const kimiJsonPath = join(cwd, ".kimi", "mcp.json");
2423
2478
 
2424
- const slugForCheck = toolSlug ? toolSlug.replace(/-/g, "_") : (process.env.CURSOR_AGENT ? "cursor" : "");
2479
+ const slugForCheck = toolSlug ? toolSlug.replace(/-/g, "_") : "";
2425
2480
  const checkMcpJson = !slugForCheck || slugForCheck === "claude_code";
2426
- const checkCursorJson = !slugForCheck || slugForCheck === "cursor";
2427
2481
  const checkCodexToml = !slugForCheck || slugForCheck === "codex";
2428
2482
  const checkGrokToml = !slugForCheck || ["grok", "grok_cli", "grok_build"].includes(slugForCheck);
2429
2483
  const checkKimiJson = !slugForCheck || slugForCheck === "kimi";
@@ -2438,16 +2492,6 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
2438
2492
  }
2439
2493
  } catch {}
2440
2494
  }
2441
- if (!existingToken && checkCursorJson && existsSync(cursorMcpPath)) {
2442
- try {
2443
- const existing = JSON.parse(readFileSync(cursorMcpPath, "utf-8"));
2444
- const pt = existing?.mcpServers?.patchcord;
2445
- if (pt?.headers?.Authorization) {
2446
- existingToken = pt.headers.Authorization.replace(/^Bearer\s+/i, "");
2447
- existingConfigFile = cursorMcpPath;
2448
- }
2449
- } catch {}
2450
- }
2451
2495
  if (!existingToken && checkCodexToml && existsSync(codexTomlPath)) {
2452
2496
  try {
2453
2497
  const content = readFileSync(codexTomlPath, "utf-8");
@@ -2770,16 +2814,8 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
2770
2814
  console.log(`\n ${green}✓${r} Cursor configured: ${dim}${cursorPath}${r}`);
2771
2815
  console.log(` ${dim}Per-project only — other projects won't see this agent.${r}`);
2772
2816
  }
2773
- const cursorCliPath = join(cursorDir, "cli.json");
2774
- const cursorCliOk = updateJsonConfig(cursorCliPath, (obj) => {
2775
- obj.permissions = obj.permissions || {};
2776
- if (!obj.permissions.allow) obj.permissions.allow = [];
2777
- if (!Array.isArray(obj.permissions.allow)) return;
2778
- const pat = "Mcp(patchcord:*)";
2779
- if (!obj.permissions.allow.includes(pat)) obj.permissions.allow.push(pat);
2780
- });
2781
- if (cursorCliOk) {
2782
- console.log(` ${green}✓${r} Cursor CLI permissions: ${dim}${cursorCliPath}${r}`);
2817
+ if (_approveCursorProjectMcp(cwd)) {
2818
+ console.log(` ${green}✓${r} Cursor MCP approved (cursor-agent mcp enable)`);
2783
2819
  }
2784
2820
  } else if (isGrok) {
2785
2821
  // Grok CLI: project-scoped .grok/config.toml. Grok loads this file from
@@ -3433,8 +3469,6 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
3433
3469
 
3434
3470
  if (isOpenClaw) {
3435
3471
  console.log(`\n${dim}Run${r} ${bold}openclaw gateway restart${r}${dim}, then tools will be available in your channels.${r}`);
3436
- } else if (isCursor && hasCursorAgent) {
3437
- console.log(`\n ${green}→${r} ${bold}Exit and start a new ${cyan}cursor-agent${r}${bold} session${r} ${dim}(MCP loads at session start)${r}`);
3438
3472
  } else if (isKimiCode) {
3439
3473
  console.log(`\n ${green}→${r} ${bold}Restart ${cyan}kimi${r}${bold} in this project (or run ${cyan}/reload${r}${bold}), then say: ${cyan}${bold}check inbox${r}`);
3440
3474
  } else if (isKimi) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "patchcord",
3
- "version": "0.6.22",
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"