patchcord 0.6.20 → 0.6.21

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.20",
4
+ "version": "0.6.21",
5
5
  "author": {
6
6
  "name": "ppravdin"
7
7
  },
package/bin/patchcord.mjs CHANGED
@@ -43,77 +43,6 @@ function isSafeId(s) {
43
43
  return /^[A-Za-z0-9_\-]+$/.test(s) && s.length < 100;
44
44
  }
45
45
 
46
- /** Claude Code marketplace plugin — call ONLY from the Claude provision branch. */
47
- function _installClaudeMarketplacePlugin(stablePluginDir) {
48
- if (!run("which claude")) return;
49
- const npmCachePatchcord = join(HOME, ".claude", "plugins", "npm-cache", "node_modules", "patchcord");
50
- if (existsSync(npmCachePatchcord)) {
51
- try { rmSync(npmCachePatchcord, { recursive: true, force: true }); } catch {}
52
- }
53
- run(`claude plugin marketplace remove patchcord-marketplace`);
54
- run(`claude plugin marketplace add "${stablePluginDir}"`);
55
- const hadPlugin = run(`claude plugin list`)?.includes("patchcord");
56
- if (hadPlugin) {
57
- run(`claude plugin update patchcord@patchcord-marketplace`);
58
- } else {
59
- run(`claude plugin install patchcord@patchcord-marketplace`);
60
- }
61
- return hadPlugin;
62
- }
63
-
64
- /** Undo 0.6.14 mistake: Claude Code uses /mcp (not /mcp/bearer). */
65
- function revertClaudeMcpBearerUrl(mcpPath) {
66
- if (!existsSync(mcpPath)) return false;
67
- try {
68
- const obj = JSON.parse(readFileSync(mcpPath, "utf-8"));
69
- const pt = obj?.mcpServers?.patchcord;
70
- const url = pt?.url;
71
- if (!url || !url.endsWith("/mcp/bearer")) return false;
72
- pt.url = url.replace(/\/mcp\/bearer$/, "/mcp");
73
- writeFileSync(mcpPath, JSON.stringify(obj, null, 2) + "\n");
74
- return true;
75
- } catch {
76
- return false;
77
- }
78
- }
79
-
80
- /** Patchcord uses project .mcp.json only — drop stale local-scope duplicates. */
81
- function dedupeClaudePatchcordMcp(cwd) {
82
- if (run("which claude")) {
83
- run("claude mcp remove patchcord -s local");
84
- }
85
- const claudeJson = join(HOME, ".claude.json");
86
- if (!existsSync(claudeJson)) return false;
87
- try {
88
- const obj = JSON.parse(readFileSync(claudeJson, "utf-8"));
89
- let changed = false;
90
- if (obj.mcpServers?.patchcord) {
91
- delete obj.mcpServers.patchcord;
92
- if (Object.keys(obj.mcpServers).length === 0) delete obj.mcpServers;
93
- changed = true;
94
- }
95
- const proj = obj.projects?.[cwd];
96
- if (proj?.mcpServers?.patchcord) {
97
- delete proj.mcpServers.patchcord;
98
- if (Object.keys(proj.mcpServers).length === 0) delete proj.mcpServers;
99
- changed = true;
100
- }
101
- if (changed) {
102
- writeFileSync(claudeJson, JSON.stringify(obj, null, 2) + "\n");
103
- }
104
- return changed;
105
- } catch {
106
- return false;
107
- }
108
- }
109
-
110
- function fixClaudePatchcordMcp(cwd) {
111
- const mcpPath = join(cwd, ".mcp.json");
112
- const reverted = revertClaudeMcpBearerUrl(mcpPath);
113
- const deduped = dedupeClaudePatchcordMcp(cwd);
114
- return { reverted, deduped };
115
- }
116
-
117
46
  // A trailing --help/-h on ANY subcommand must show help and exit — never run the
118
47
  // command. (patchcord orchestrator --help used to PROVISION an orchestrator;
119
48
  // patchcord login --help hung on the browser login. Side-effecting --help is a
@@ -779,8 +708,6 @@ if (cmd === "subscribe") {
779
708
  const forceKimi = args.includes("--kimi");
780
709
  const intervalArg = args.find((a) => a !== "--kimi") || "30";
781
710
 
782
- fixClaudePatchcordMcp(process.cwd());
783
-
784
711
  // Kimi Code uses polling instead of WebSocket realtime.
785
712
  // Force Kimi mode with --kimi flag, or auto-detect from bearer config.
786
713
  let isKimi = forceKimi;
@@ -1734,8 +1661,6 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
1734
1661
  return out.replace(/,(\s*[}\]])/g, "$1");
1735
1662
  }
1736
1663
 
1737
- // dedupeClaudePatchcordMcp + revertClaudeMcpBearerUrl live at module scope.
1738
-
1739
1664
  // Read+merge+write a JSON config file. If the file exists but its contents
1740
1665
  // can't be parsed, REFUSE to write — silently overwriting would erase
1741
1666
  // unrelated MCP servers, settings, or hand-edits the user has in there.
@@ -1794,15 +1719,6 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
1794
1719
  const bold = "\x1b[1m";
1795
1720
  const r = "\x1b[0m";
1796
1721
 
1797
- const _installCwd = process.cwd();
1798
- const _mcpFix = fixClaudePatchcordMcp(_installCwd);
1799
- if (_mcpFix.reverted) {
1800
- console.log(` ${green}✓${r} Claude Code MCP URL reverted to /mcp (0.6.14 /mcp/bearer was wrong for CC)`);
1801
- }
1802
- if (_mcpFix.deduped) {
1803
- console.log(` ${green}✓${r} Removed stale local-scope patchcord MCP (use project .mcp.json only)`);
1804
- }
1805
-
1806
1722
  // ── Purge stale npx cache entries ────────────────────────────
1807
1723
  // npx never auto-updates a cached package. Users with an old `npx patchcord`
1808
1724
  // entry get the stale binary indefinitely. On every install we sweep
@@ -1868,11 +1784,33 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
1868
1784
  let globalChanges = [];
1869
1785
  if (globalCliInstalled) globalChanges.push("Patchcord CLI installed globally");
1870
1786
 
1871
- // Claude Code — skip when install runs inside cursor-agent: Claude's plugin
1872
- // MCP registers as "plugin-patchcord-marketplace-patchcord", which cursor-agent
1873
- // tries instead of project .cursor/mcp.json → "MCP server not found".
1787
+ // Claude Code
1874
1788
  const hasClaude = run("which claude");
1875
1789
  if (hasClaude) {
1790
+ // Remove legacy npm-cache install (pre-marketplace era) — causes duplicate /patchcord commands.
1791
+ const npmCachePatchcord = join(HOME, ".claude", "plugins", "npm-cache", "node_modules", "patchcord");
1792
+ if (existsSync(npmCachePatchcord)) {
1793
+ try { rmSync(npmCachePatchcord, { recursive: true, force: true }); } catch {}
1794
+ }
1795
+
1796
+ // Always re-add marketplace (point at the STABLE mirror, not pluginRoot
1797
+ // which is the npx ephemeral cache). Claude Code stores the literal
1798
+ // path in its plugin manifest — pointing at npx cache breaks every
1799
+ // future session as soon as that cache dir is swept.
1800
+ // Remove any prior marketplace registration first so the path actually
1801
+ // updates for users who got the buggy npx-cache path on an earlier install.
1802
+ run(`claude plugin marketplace remove patchcord-marketplace`);
1803
+ run(`claude plugin marketplace add "${stablePluginDir}"`);
1804
+ const installed = run(`claude plugin list`)?.includes("patchcord");
1805
+ wasPluginInstalled = !!installed;
1806
+ if (installed) {
1807
+ run(`claude plugin update patchcord@patchcord-marketplace`);
1808
+ globalChanges.push("Claude Code plugin updated");
1809
+ } else {
1810
+ run(`claude plugin install patchcord@patchcord-marketplace`);
1811
+ globalChanges.push("Claude Code plugin installed");
1812
+ }
1813
+
1876
1814
  const claudeSettings = join(HOME, ".claude", "settings.json");
1877
1815
  if (existsSync(claudeSettings)) {
1878
1816
  try {
@@ -1962,30 +1900,6 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
1962
1900
  } else if (allowlistChanged) {
1963
1901
  globalChanges.push("Cursor MCP allowlist configured");
1964
1902
  }
1965
-
1966
- // cursor-agent v2026.07+ also reads ~/.cursor/cli-config.json permissions.allow
1967
- // for MCP tool approval — permissions.json alone leaves patchcord at
1968
- // "not loaded (needs approval)" even with --yolo.
1969
- const cliConfigPath = join(HOME, ".cursor", "cli-config.json");
1970
- let cliAllowChanged = false;
1971
- const cliOk = updateJsonConfig(cliConfigPath, (obj) => {
1972
- obj.permissions = obj.permissions || {};
1973
- if (!obj.permissions.allow) obj.permissions.allow = [];
1974
- if (!Array.isArray(obj.permissions.allow)) {
1975
- console.log(`\n ${yellow}⚠${r} Skipped Cursor CLI MCP allow — ${cliConfigPath} permissions.allow is not an array.`);
1976
- return;
1977
- }
1978
- const pat = "Mcp(patchcord:*)";
1979
- if (!obj.permissions.allow.includes(pat)) {
1980
- obj.permissions.allow.push(pat);
1981
- cliAllowChanged = true;
1982
- }
1983
- });
1984
- if (!cliOk) {
1985
- globalChanges.push("✗ Cursor CLI permissions error");
1986
- } else if (cliAllowChanged) {
1987
- globalChanges.push("Cursor CLI MCP allowlist configured");
1988
- }
1989
1903
  }
1990
1904
  }
1991
1905
 
@@ -2423,13 +2337,6 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
2423
2337
  }
2424
2338
  toolSlug = toolFlag; // preserved as-is for the URL param
2425
2339
  }
2426
- // cursor-agent --yolo runs `npx patchcord` without --tool=cursor. Without
2427
- // this, browser connect defaults to Claude and writes .mcp.json instead of
2428
- // .cursor/mcp.json — MCP never loads in cursor-agent.
2429
- if (!choice && process.env.CURSOR_AGENT) {
2430
- choice = CLIENT_TYPE_MAP.cursor;
2431
- toolSlug = toolSlug || "cursor";
2432
- }
2433
2340
 
2434
2341
  // --token bypass for power users / CI / self-hosters
2435
2342
  const tokenFlag = flags.find(f => f.startsWith("--token="))?.split("=")[1]
@@ -2480,14 +2387,12 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
2480
2387
  let existingToken = "";
2481
2388
  let existingConfigFile = "";
2482
2389
  const mcpJsonPath = join(cwd, ".mcp.json");
2483
- const cursorMcpPath = join(cwd, ".cursor", "mcp.json");
2484
2390
  const codexTomlPath = join(cwd, ".codex", "config.toml");
2485
2391
  const grokTomlPath = join(cwd, ".grok", "config.toml");
2486
2392
  const kimiJsonPath = join(cwd, ".kimi", "mcp.json");
2487
2393
 
2488
- const slugForCheck = toolSlug ? toolSlug.replace(/-/g, "_") : (process.env.CURSOR_AGENT ? "cursor" : "");
2394
+ const slugForCheck = toolSlug ? toolSlug.replace(/-/g, "_") : "";
2489
2395
  const checkMcpJson = !slugForCheck || slugForCheck === "claude_code";
2490
- const checkCursorJson = !slugForCheck || slugForCheck === "cursor";
2491
2396
  const checkCodexToml = !slugForCheck || slugForCheck === "codex";
2492
2397
  const checkGrokToml = !slugForCheck || ["grok", "grok_cli", "grok_build"].includes(slugForCheck);
2493
2398
  const checkKimiJson = !slugForCheck || slugForCheck === "kimi";
@@ -2502,16 +2407,6 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
2502
2407
  }
2503
2408
  } catch {}
2504
2409
  }
2505
- if (!existingToken && checkCursorJson && existsSync(cursorMcpPath)) {
2506
- try {
2507
- const existing = JSON.parse(readFileSync(cursorMcpPath, "utf-8"));
2508
- const pt = existing?.mcpServers?.patchcord;
2509
- if (pt?.headers?.Authorization) {
2510
- existingToken = pt.headers.Authorization.replace(/^Bearer\s+/i, "");
2511
- existingConfigFile = cursorMcpPath;
2512
- }
2513
- } catch {}
2514
- }
2515
2410
  if (!existingToken && checkCodexToml && existsSync(codexTomlPath)) {
2516
2411
  try {
2517
2412
  const content = readFileSync(codexTomlPath, "utf-8");
@@ -2834,18 +2729,6 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
2834
2729
  console.log(`\n ${green}✓${r} Cursor configured: ${dim}${cursorPath}${r}`);
2835
2730
  console.log(` ${dim}Per-project only — other projects won't see this agent.${r}`);
2836
2731
  }
2837
- // Project-layer CLI permissions (cursor-agent merges .cursor/cli.json from git root).
2838
- const cursorCliPath = join(cursorDir, "cli.json");
2839
- const cursorCliOk = updateJsonConfig(cursorCliPath, (obj) => {
2840
- obj.permissions = obj.permissions || {};
2841
- if (!obj.permissions.allow) obj.permissions.allow = [];
2842
- if (!Array.isArray(obj.permissions.allow)) return;
2843
- const pat = "Mcp(patchcord:*)";
2844
- if (!obj.permissions.allow.includes(pat)) obj.permissions.allow.push(pat);
2845
- });
2846
- if (cursorCliOk) {
2847
- console.log(` ${green}✓${r} Cursor CLI permissions: ${dim}${cursorCliPath}${r}`);
2848
- }
2849
2732
  } else if (isGrok) {
2850
2733
  // Grok CLI: project-scoped .grok/config.toml. Grok loads this file from
2851
2734
  // the current directory up through the git root, so the bearer remains
@@ -3387,14 +3270,8 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
3387
3270
  // their JSON and re-runs.
3388
3271
  process.exit(1);
3389
3272
  }
3390
- dedupeClaudePatchcordMcp(cwd);
3391
3273
  console.log(`\n ${green}✓${r} Claude Code configured: ${dim}${mcpPath}${r}`);
3392
3274
 
3393
- if (run("which claude")) {
3394
- wasPluginInstalled = _installClaudeMarketplacePlugin(stablePluginDir);
3395
- console.log(` ${green}✓${r} Claude Code plugin ${wasPluginInstalled ? "updated" : "installed"}`);
3396
- }
3397
-
3398
3275
  // Statusline: ask whether to enable full mode (unless --full flag passed or already configured).
3399
3276
  const enableScript = join(pluginRoot, "scripts", "enable-statusline.sh");
3400
3277
  if (existsSync(enableScript)) {
@@ -3508,9 +3385,6 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
3508
3385
  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}`);
3509
3386
  } else if (isKimi) {
3510
3387
  console.log(`\n ${green}→${r} ${bold}Restart your ${toolName} session with ${cyan}kimi-pc${r}${bold}, then say: ${cyan}${bold}check inbox${r}`);
3511
- } else if (isCursor && hasCursorAgent) {
3512
- console.log(`\n ${green}→${r} ${bold}Exit and start a new ${cyan}cursor-agent${r}${bold} session${r} ${dim}(MCP loads at session start — not mid-session)${r}`);
3513
- console.log(` ${dim}Then:${r} ${cyan}${bold}check inbox${r} ${dim}before${r} ${cyan}${bold}/patchcord-subscribe${r}`);
3514
3388
  } else {
3515
3389
  console.log(`\n ${green}→${r} ${bold}Restart your ${toolName} session${r}, then say: ${cyan}${bold}check inbox${r}`);
3516
3390
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "patchcord",
3
- "version": "0.6.20",
3
+ "version": "0.6.21",
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"