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.
- package/.claude-plugin/plugin.json +1 -1
- package/bin/patchcord.mjs +26 -152
- package/package.json +1 -1
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
|
|
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, "_") :
|
|
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