patchcord 0.6.19 → 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.
- package/.claude-plugin/plugin.json +1 -1
- package/bin/patchcord.mjs +3 -147
- package/package.json +1 -1
package/bin/patchcord.mjs
CHANGED
|
@@ -43,71 +43,6 @@ function isSafeId(s) {
|
|
|
43
43
|
return /^[A-Za-z0-9_\-]+$/.test(s) && s.length < 100;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
/** cursor-agent --yolo often spawns `npx patchcord` without CURSOR_AGENT in env. */
|
|
47
|
-
function _runningUnderCursorAgent() {
|
|
48
|
-
if (process.env.CURSOR_AGENT) return true;
|
|
49
|
-
if (process.platform === "linux") {
|
|
50
|
-
try {
|
|
51
|
-
const cmdline = readFileSync(`/proc/${process.ppid}/cmdline`, "utf-8").replace(/\0/g, " ");
|
|
52
|
-
if (/cursor-agent|\bcursor agent\b/i.test(cmdline)) return true;
|
|
53
|
-
} catch {}
|
|
54
|
-
}
|
|
55
|
-
return false;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/** Undo 0.6.14 mistake: Claude Code uses /mcp (not /mcp/bearer). */
|
|
59
|
-
function revertClaudeMcpBearerUrl(mcpPath) {
|
|
60
|
-
if (!existsSync(mcpPath)) return false;
|
|
61
|
-
try {
|
|
62
|
-
const obj = JSON.parse(readFileSync(mcpPath, "utf-8"));
|
|
63
|
-
const pt = obj?.mcpServers?.patchcord;
|
|
64
|
-
const url = pt?.url;
|
|
65
|
-
if (!url || !url.endsWith("/mcp/bearer")) return false;
|
|
66
|
-
pt.url = url.replace(/\/mcp\/bearer$/, "/mcp");
|
|
67
|
-
writeFileSync(mcpPath, JSON.stringify(obj, null, 2) + "\n");
|
|
68
|
-
return true;
|
|
69
|
-
} catch {
|
|
70
|
-
return false;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/** Patchcord uses project .mcp.json only — drop stale local-scope duplicates. */
|
|
75
|
-
function dedupeClaudePatchcordMcp(cwd) {
|
|
76
|
-
if (run("which claude")) {
|
|
77
|
-
run("claude mcp remove patchcord -s local");
|
|
78
|
-
}
|
|
79
|
-
const claudeJson = join(HOME, ".claude.json");
|
|
80
|
-
if (!existsSync(claudeJson)) return false;
|
|
81
|
-
try {
|
|
82
|
-
const obj = JSON.parse(readFileSync(claudeJson, "utf-8"));
|
|
83
|
-
let changed = false;
|
|
84
|
-
if (obj.mcpServers?.patchcord) {
|
|
85
|
-
delete obj.mcpServers.patchcord;
|
|
86
|
-
if (Object.keys(obj.mcpServers).length === 0) delete obj.mcpServers;
|
|
87
|
-
changed = true;
|
|
88
|
-
}
|
|
89
|
-
const proj = obj.projects?.[cwd];
|
|
90
|
-
if (proj?.mcpServers?.patchcord) {
|
|
91
|
-
delete proj.mcpServers.patchcord;
|
|
92
|
-
if (Object.keys(proj.mcpServers).length === 0) delete proj.mcpServers;
|
|
93
|
-
changed = true;
|
|
94
|
-
}
|
|
95
|
-
if (changed) {
|
|
96
|
-
writeFileSync(claudeJson, JSON.stringify(obj, null, 2) + "\n");
|
|
97
|
-
}
|
|
98
|
-
return changed;
|
|
99
|
-
} catch {
|
|
100
|
-
return false;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function fixClaudePatchcordMcp(cwd) {
|
|
105
|
-
const mcpPath = join(cwd, ".mcp.json");
|
|
106
|
-
const reverted = revertClaudeMcpBearerUrl(mcpPath);
|
|
107
|
-
const deduped = dedupeClaudePatchcordMcp(cwd);
|
|
108
|
-
return { reverted, deduped };
|
|
109
|
-
}
|
|
110
|
-
|
|
111
46
|
// A trailing --help/-h on ANY subcommand must show help and exit — never run the
|
|
112
47
|
// command. (patchcord orchestrator --help used to PROVISION an orchestrator;
|
|
113
48
|
// patchcord login --help hung on the browser login. Side-effecting --help is a
|
|
@@ -773,8 +708,6 @@ if (cmd === "subscribe") {
|
|
|
773
708
|
const forceKimi = args.includes("--kimi");
|
|
774
709
|
const intervalArg = args.find((a) => a !== "--kimi") || "30";
|
|
775
710
|
|
|
776
|
-
fixClaudePatchcordMcp(process.cwd());
|
|
777
|
-
|
|
778
711
|
// Kimi Code uses polling instead of WebSocket realtime.
|
|
779
712
|
// Force Kimi mode with --kimi flag, or auto-detect from bearer config.
|
|
780
713
|
let isKimi = forceKimi;
|
|
@@ -1683,11 +1616,6 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
|
|
|
1683
1616
|
const flags = cmd?.startsWith("--") ? process.argv.slice(2) : process.argv.slice(3);
|
|
1684
1617
|
const fullStatusline = flags.includes("--full");
|
|
1685
1618
|
const updateOnly = flags.includes("--update-only");
|
|
1686
|
-
const toolFlagEarly = flags.find((f) => f.startsWith("--tool="))?.split("=")[1]
|
|
1687
|
-
|| (flags.includes("--tool") ? flags[flags.indexOf("--tool") + 1] : "");
|
|
1688
|
-
const skipClaudePluginForCursor =
|
|
1689
|
-
_runningUnderCursorAgent()
|
|
1690
|
-
|| toolFlagEarly.replace(/-/g, "_") === "cursor";
|
|
1691
1619
|
let wasPluginInstalled = false;
|
|
1692
1620
|
const { readFileSync, writeFileSync, unlinkSync, rmSync, chmodSync, copyFileSync } = await import("fs");
|
|
1693
1621
|
|
|
@@ -1733,8 +1661,6 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
|
|
|
1733
1661
|
return out.replace(/,(\s*[}\]])/g, "$1");
|
|
1734
1662
|
}
|
|
1735
1663
|
|
|
1736
|
-
// dedupeClaudePatchcordMcp + revertClaudeMcpBearerUrl live at module scope.
|
|
1737
|
-
|
|
1738
1664
|
// Read+merge+write a JSON config file. If the file exists but its contents
|
|
1739
1665
|
// can't be parsed, REFUSE to write — silently overwriting would erase
|
|
1740
1666
|
// unrelated MCP servers, settings, or hand-edits the user has in there.
|
|
@@ -1793,15 +1719,6 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
|
|
|
1793
1719
|
const bold = "\x1b[1m";
|
|
1794
1720
|
const r = "\x1b[0m";
|
|
1795
1721
|
|
|
1796
|
-
const _installCwd = process.cwd();
|
|
1797
|
-
const _mcpFix = fixClaudePatchcordMcp(_installCwd);
|
|
1798
|
-
if (_mcpFix.reverted) {
|
|
1799
|
-
console.log(` ${green}✓${r} Claude Code MCP URL reverted to /mcp (0.6.14 /mcp/bearer was wrong for CC)`);
|
|
1800
|
-
}
|
|
1801
|
-
if (_mcpFix.deduped) {
|
|
1802
|
-
console.log(` ${green}✓${r} Removed stale local-scope patchcord MCP (use project .mcp.json only)`);
|
|
1803
|
-
}
|
|
1804
|
-
|
|
1805
1722
|
// ── Purge stale npx cache entries ────────────────────────────
|
|
1806
1723
|
// npx never auto-updates a cached package. Users with an old `npx patchcord`
|
|
1807
1724
|
// entry get the stale binary indefinitely. On every install we sweep
|
|
@@ -1867,11 +1784,9 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
|
|
|
1867
1784
|
let globalChanges = [];
|
|
1868
1785
|
if (globalCliInstalled) globalChanges.push("Patchcord CLI installed globally");
|
|
1869
1786
|
|
|
1870
|
-
// Claude Code
|
|
1871
|
-
// MCP registers as "plugin-patchcord-marketplace-patchcord", which cursor-agent
|
|
1872
|
-
// tries instead of project .cursor/mcp.json → "MCP server not found".
|
|
1787
|
+
// Claude Code
|
|
1873
1788
|
const hasClaude = run("which claude");
|
|
1874
|
-
if (hasClaude
|
|
1789
|
+
if (hasClaude) {
|
|
1875
1790
|
// Remove legacy npm-cache install (pre-marketplace era) — causes duplicate /patchcord commands.
|
|
1876
1791
|
const npmCachePatchcord = join(HOME, ".claude", "plugins", "npm-cache", "node_modules", "patchcord");
|
|
1877
1792
|
if (existsSync(npmCachePatchcord)) {
|
|
@@ -1985,30 +1900,6 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
|
|
|
1985
1900
|
} else if (allowlistChanged) {
|
|
1986
1901
|
globalChanges.push("Cursor MCP allowlist configured");
|
|
1987
1902
|
}
|
|
1988
|
-
|
|
1989
|
-
// cursor-agent v2026.07+ also reads ~/.cursor/cli-config.json permissions.allow
|
|
1990
|
-
// for MCP tool approval — permissions.json alone leaves patchcord at
|
|
1991
|
-
// "not loaded (needs approval)" even with --yolo.
|
|
1992
|
-
const cliConfigPath = join(HOME, ".cursor", "cli-config.json");
|
|
1993
|
-
let cliAllowChanged = false;
|
|
1994
|
-
const cliOk = updateJsonConfig(cliConfigPath, (obj) => {
|
|
1995
|
-
obj.permissions = obj.permissions || {};
|
|
1996
|
-
if (!obj.permissions.allow) obj.permissions.allow = [];
|
|
1997
|
-
if (!Array.isArray(obj.permissions.allow)) {
|
|
1998
|
-
console.log(`\n ${yellow}⚠${r} Skipped Cursor CLI MCP allow — ${cliConfigPath} permissions.allow is not an array.`);
|
|
1999
|
-
return;
|
|
2000
|
-
}
|
|
2001
|
-
const pat = "Mcp(patchcord:*)";
|
|
2002
|
-
if (!obj.permissions.allow.includes(pat)) {
|
|
2003
|
-
obj.permissions.allow.push(pat);
|
|
2004
|
-
cliAllowChanged = true;
|
|
2005
|
-
}
|
|
2006
|
-
});
|
|
2007
|
-
if (!cliOk) {
|
|
2008
|
-
globalChanges.push("✗ Cursor CLI permissions error");
|
|
2009
|
-
} else if (cliAllowChanged) {
|
|
2010
|
-
globalChanges.push("Cursor CLI MCP allowlist configured");
|
|
2011
|
-
}
|
|
2012
1903
|
}
|
|
2013
1904
|
}
|
|
2014
1905
|
|
|
@@ -2446,13 +2337,6 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
|
|
|
2446
2337
|
}
|
|
2447
2338
|
toolSlug = toolFlag; // preserved as-is for the URL param
|
|
2448
2339
|
}
|
|
2449
|
-
// cursor-agent --yolo runs `npx patchcord` without --tool=cursor. Without
|
|
2450
|
-
// this, browser connect defaults to Claude and writes .mcp.json instead of
|
|
2451
|
-
// .cursor/mcp.json — MCP never loads in cursor-agent.
|
|
2452
|
-
if (!choice && process.env.CURSOR_AGENT) {
|
|
2453
|
-
choice = CLIENT_TYPE_MAP.cursor;
|
|
2454
|
-
toolSlug = toolSlug || "cursor";
|
|
2455
|
-
}
|
|
2456
2340
|
|
|
2457
2341
|
// --token bypass for power users / CI / self-hosters
|
|
2458
2342
|
const tokenFlag = flags.find(f => f.startsWith("--token="))?.split("=")[1]
|
|
@@ -2503,14 +2387,12 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
|
|
|
2503
2387
|
let existingToken = "";
|
|
2504
2388
|
let existingConfigFile = "";
|
|
2505
2389
|
const mcpJsonPath = join(cwd, ".mcp.json");
|
|
2506
|
-
const cursorMcpPath = join(cwd, ".cursor", "mcp.json");
|
|
2507
2390
|
const codexTomlPath = join(cwd, ".codex", "config.toml");
|
|
2508
2391
|
const grokTomlPath = join(cwd, ".grok", "config.toml");
|
|
2509
2392
|
const kimiJsonPath = join(cwd, ".kimi", "mcp.json");
|
|
2510
2393
|
|
|
2511
|
-
const slugForCheck = toolSlug ? toolSlug.replace(/-/g, "_") :
|
|
2394
|
+
const slugForCheck = toolSlug ? toolSlug.replace(/-/g, "_") : "";
|
|
2512
2395
|
const checkMcpJson = !slugForCheck || slugForCheck === "claude_code";
|
|
2513
|
-
const checkCursorJson = !slugForCheck || slugForCheck === "cursor";
|
|
2514
2396
|
const checkCodexToml = !slugForCheck || slugForCheck === "codex";
|
|
2515
2397
|
const checkGrokToml = !slugForCheck || ["grok", "grok_cli", "grok_build"].includes(slugForCheck);
|
|
2516
2398
|
const checkKimiJson = !slugForCheck || slugForCheck === "kimi";
|
|
@@ -2525,16 +2407,6 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
|
|
|
2525
2407
|
}
|
|
2526
2408
|
} catch {}
|
|
2527
2409
|
}
|
|
2528
|
-
if (!existingToken && checkCursorJson && existsSync(cursorMcpPath)) {
|
|
2529
|
-
try {
|
|
2530
|
-
const existing = JSON.parse(readFileSync(cursorMcpPath, "utf-8"));
|
|
2531
|
-
const pt = existing?.mcpServers?.patchcord;
|
|
2532
|
-
if (pt?.headers?.Authorization) {
|
|
2533
|
-
existingToken = pt.headers.Authorization.replace(/^Bearer\s+/i, "");
|
|
2534
|
-
existingConfigFile = cursorMcpPath;
|
|
2535
|
-
}
|
|
2536
|
-
} catch {}
|
|
2537
|
-
}
|
|
2538
2410
|
if (!existingToken && checkCodexToml && existsSync(codexTomlPath)) {
|
|
2539
2411
|
try {
|
|
2540
2412
|
const content = readFileSync(codexTomlPath, "utf-8");
|
|
@@ -2857,18 +2729,6 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
|
|
|
2857
2729
|
console.log(`\n ${green}✓${r} Cursor configured: ${dim}${cursorPath}${r}`);
|
|
2858
2730
|
console.log(` ${dim}Per-project only — other projects won't see this agent.${r}`);
|
|
2859
2731
|
}
|
|
2860
|
-
// Project-layer CLI permissions (cursor-agent merges .cursor/cli.json from git root).
|
|
2861
|
-
const cursorCliPath = join(cursorDir, "cli.json");
|
|
2862
|
-
const cursorCliOk = updateJsonConfig(cursorCliPath, (obj) => {
|
|
2863
|
-
obj.permissions = obj.permissions || {};
|
|
2864
|
-
if (!obj.permissions.allow) obj.permissions.allow = [];
|
|
2865
|
-
if (!Array.isArray(obj.permissions.allow)) return;
|
|
2866
|
-
const pat = "Mcp(patchcord:*)";
|
|
2867
|
-
if (!obj.permissions.allow.includes(pat)) obj.permissions.allow.push(pat);
|
|
2868
|
-
});
|
|
2869
|
-
if (cursorCliOk) {
|
|
2870
|
-
console.log(` ${green}✓${r} Cursor CLI permissions: ${dim}${cursorCliPath}${r}`);
|
|
2871
|
-
}
|
|
2872
2732
|
} else if (isGrok) {
|
|
2873
2733
|
// Grok CLI: project-scoped .grok/config.toml. Grok loads this file from
|
|
2874
2734
|
// the current directory up through the git root, so the bearer remains
|
|
@@ -3410,7 +3270,6 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
|
|
|
3410
3270
|
// their JSON and re-runs.
|
|
3411
3271
|
process.exit(1);
|
|
3412
3272
|
}
|
|
3413
|
-
dedupeClaudePatchcordMcp(cwd);
|
|
3414
3273
|
console.log(`\n ${green}✓${r} Claude Code configured: ${dim}${mcpPath}${r}`);
|
|
3415
3274
|
|
|
3416
3275
|
// Statusline: ask whether to enable full mode (unless --full flag passed or already configured).
|
|
@@ -3526,9 +3385,6 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
|
|
|
3526
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}`);
|
|
3527
3386
|
} else if (isKimi) {
|
|
3528
3387
|
console.log(`\n ${green}→${r} ${bold}Restart your ${toolName} session with ${cyan}kimi-pc${r}${bold}, then say: ${cyan}${bold}check inbox${r}`);
|
|
3529
|
-
} else if (isCursor && hasCursorAgent) {
|
|
3530
|
-
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}`);
|
|
3531
|
-
console.log(` ${dim}Then:${r} ${cyan}${bold}check inbox${r} ${dim}before${r} ${cyan}${bold}/patchcord-subscribe${r}`);
|
|
3532
3388
|
} else {
|
|
3533
3389
|
console.log(`\n ${green}→${r} ${bold}Restart your ${toolName} session${r}, then say: ${cyan}${bold}check inbox${r}`);
|
|
3534
3390
|
}
|
package/package.json
CHANGED