patchcord 0.6.14 → 0.6.16
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/bin/patchcord.mjs
CHANGED
|
@@ -43,6 +43,59 @@ function isSafeId(s) {
|
|
|
43
43
|
return /^[A-Za-z0-9_\-]+$/.test(s) && s.length < 100;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
/** Undo 0.6.14 mistake: Claude Code uses /mcp (not /mcp/bearer). */
|
|
47
|
+
function revertClaudeMcpBearerUrl(mcpPath) {
|
|
48
|
+
if (!existsSync(mcpPath)) return false;
|
|
49
|
+
try {
|
|
50
|
+
const obj = JSON.parse(readFileSync(mcpPath, "utf-8"));
|
|
51
|
+
const pt = obj?.mcpServers?.patchcord;
|
|
52
|
+
const url = pt?.url;
|
|
53
|
+
if (!url || !url.endsWith("/mcp/bearer")) return false;
|
|
54
|
+
pt.url = url.replace(/\/mcp\/bearer$/, "/mcp");
|
|
55
|
+
writeFileSync(mcpPath, JSON.stringify(obj, null, 2) + "\n");
|
|
56
|
+
return true;
|
|
57
|
+
} catch {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Patchcord uses project .mcp.json only — drop stale local-scope duplicates. */
|
|
63
|
+
function dedupeClaudePatchcordMcp(cwd) {
|
|
64
|
+
if (run("which claude")) {
|
|
65
|
+
run("claude mcp remove patchcord -s local");
|
|
66
|
+
}
|
|
67
|
+
const claudeJson = join(HOME, ".claude.json");
|
|
68
|
+
if (!existsSync(claudeJson)) return false;
|
|
69
|
+
try {
|
|
70
|
+
const obj = JSON.parse(readFileSync(claudeJson, "utf-8"));
|
|
71
|
+
let changed = false;
|
|
72
|
+
if (obj.mcpServers?.patchcord) {
|
|
73
|
+
delete obj.mcpServers.patchcord;
|
|
74
|
+
if (Object.keys(obj.mcpServers).length === 0) delete obj.mcpServers;
|
|
75
|
+
changed = true;
|
|
76
|
+
}
|
|
77
|
+
const proj = obj.projects?.[cwd];
|
|
78
|
+
if (proj?.mcpServers?.patchcord) {
|
|
79
|
+
delete proj.mcpServers.patchcord;
|
|
80
|
+
if (Object.keys(proj.mcpServers).length === 0) delete proj.mcpServers;
|
|
81
|
+
changed = true;
|
|
82
|
+
}
|
|
83
|
+
if (changed) {
|
|
84
|
+
writeFileSync(claudeJson, JSON.stringify(obj, null, 2) + "\n");
|
|
85
|
+
}
|
|
86
|
+
return changed;
|
|
87
|
+
} catch {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function fixClaudePatchcordMcp(cwd) {
|
|
93
|
+
const mcpPath = join(cwd, ".mcp.json");
|
|
94
|
+
const reverted = revertClaudeMcpBearerUrl(mcpPath);
|
|
95
|
+
const deduped = dedupeClaudePatchcordMcp(cwd);
|
|
96
|
+
return { reverted, deduped };
|
|
97
|
+
}
|
|
98
|
+
|
|
46
99
|
// A trailing --help/-h on ANY subcommand must show help and exit — never run the
|
|
47
100
|
// command. (patchcord orchestrator --help used to PROVISION an orchestrator;
|
|
48
101
|
// patchcord login --help hung on the browser login. Side-effecting --help is a
|
|
@@ -708,6 +761,8 @@ if (cmd === "subscribe") {
|
|
|
708
761
|
const forceKimi = args.includes("--kimi");
|
|
709
762
|
const intervalArg = args.find((a) => a !== "--kimi") || "30";
|
|
710
763
|
|
|
764
|
+
fixClaudePatchcordMcp(process.cwd());
|
|
765
|
+
|
|
711
766
|
// Kimi Code uses polling instead of WebSocket realtime.
|
|
712
767
|
// Force Kimi mode with --kimi flag, or auto-detect from bearer config.
|
|
713
768
|
let isKimi = forceKimi;
|
|
@@ -1172,13 +1227,9 @@ if (cmd === "login" || cmd === "orchestrator" || cmd === "teamlead" || cmd === "
|
|
|
1172
1227
|
// default: claude_code. type:"http" is REQUIRED — without it Claude Code
|
|
1173
1228
|
// defaults to stdio transport and rejects the entry ("command: expected
|
|
1174
1229
|
// string, received undefined").
|
|
1175
|
-
// Use /mcp/bearer (not /mcp): Claude Code v2.1.113+ follows RFC 9728 OAuth
|
|
1176
|
-
// discovery on /mcp, drops the static bearer header, and stalls on "Needs
|
|
1177
|
-
// authentication". /mcp/bearer serves bearer-only resource metadata — same
|
|
1178
|
-
// path Cursor and Kimi Code already use.
|
|
1179
1230
|
return writeJson(join(dir, ".mcp.json"), (o) => {
|
|
1180
1231
|
o.mcpServers = o.mcpServers || {};
|
|
1181
|
-
o.mcpServers.patchcord = { type: "http", url: `${baseUrl}/mcp
|
|
1232
|
+
o.mcpServers.patchcord = { type: "http", url: `${baseUrl}/mcp`, headers: hdr };
|
|
1182
1233
|
});
|
|
1183
1234
|
};
|
|
1184
1235
|
|
|
@@ -1665,22 +1716,7 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
|
|
|
1665
1716
|
return out.replace(/,(\s*[}\]])/g, "$1");
|
|
1666
1717
|
}
|
|
1667
1718
|
|
|
1668
|
-
//
|
|
1669
|
-
// Rewrite legacy project configs to /mcp/bearer (Cursor/Kimi already use it).
|
|
1670
|
-
function _migrateClaudeMcpBearerUrl(mcpPath) {
|
|
1671
|
-
if (!existsSync(mcpPath)) return false;
|
|
1672
|
-
try {
|
|
1673
|
-
const obj = JSON.parse(readFileSync(mcpPath, "utf-8"));
|
|
1674
|
-
const pt = obj?.mcpServers?.patchcord;
|
|
1675
|
-
const url = pt?.url;
|
|
1676
|
-
if (!url || !/\/mcp$/.test(url) || url.endsWith("/mcp/bearer")) return false;
|
|
1677
|
-
pt.url = url.replace(/\/mcp$/, "/mcp/bearer");
|
|
1678
|
-
writeFileSync(mcpPath, JSON.stringify(obj, null, 2) + "\n");
|
|
1679
|
-
return true;
|
|
1680
|
-
} catch {
|
|
1681
|
-
return false;
|
|
1682
|
-
}
|
|
1683
|
-
}
|
|
1719
|
+
// dedupeClaudePatchcordMcp + revertClaudeMcpBearerUrl live at module scope.
|
|
1684
1720
|
|
|
1685
1721
|
// Read+merge+write a JSON config file. If the file exists but its contents
|
|
1686
1722
|
// can't be parsed, REFUSE to write — silently overwriting would erase
|
|
@@ -1740,8 +1776,13 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
|
|
|
1740
1776
|
const bold = "\x1b[1m";
|
|
1741
1777
|
const r = "\x1b[0m";
|
|
1742
1778
|
|
|
1743
|
-
|
|
1744
|
-
|
|
1779
|
+
const _installCwd = process.cwd();
|
|
1780
|
+
const _mcpFix = fixClaudePatchcordMcp(_installCwd);
|
|
1781
|
+
if (_mcpFix.reverted) {
|
|
1782
|
+
console.log(` ${green}✓${r} Claude Code MCP URL reverted to /mcp (0.6.14 /mcp/bearer was wrong for CC)`);
|
|
1783
|
+
}
|
|
1784
|
+
if (_mcpFix.deduped) {
|
|
1785
|
+
console.log(` ${green}✓${r} Removed stale local-scope patchcord MCP (use project .mcp.json only)`);
|
|
1745
1786
|
}
|
|
1746
1787
|
|
|
1747
1788
|
// ── Purge stale npx cache entries ────────────────────────────
|
|
@@ -1925,6 +1966,30 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
|
|
|
1925
1966
|
} else if (allowlistChanged) {
|
|
1926
1967
|
globalChanges.push("Cursor MCP allowlist configured");
|
|
1927
1968
|
}
|
|
1969
|
+
|
|
1970
|
+
// cursor-agent v2026.07+ also reads ~/.cursor/cli-config.json permissions.allow
|
|
1971
|
+
// for MCP tool approval — permissions.json alone leaves patchcord at
|
|
1972
|
+
// "not loaded (needs approval)" even with --yolo.
|
|
1973
|
+
const cliConfigPath = join(HOME, ".cursor", "cli-config.json");
|
|
1974
|
+
let cliAllowChanged = false;
|
|
1975
|
+
const cliOk = updateJsonConfig(cliConfigPath, (obj) => {
|
|
1976
|
+
obj.permissions = obj.permissions || {};
|
|
1977
|
+
if (!obj.permissions.allow) obj.permissions.allow = [];
|
|
1978
|
+
if (!Array.isArray(obj.permissions.allow)) {
|
|
1979
|
+
console.log(`\n ${yellow}⚠${r} Skipped Cursor CLI MCP allow — ${cliConfigPath} permissions.allow is not an array.`);
|
|
1980
|
+
return;
|
|
1981
|
+
}
|
|
1982
|
+
const pat = "Mcp(patchcord:*)";
|
|
1983
|
+
if (!obj.permissions.allow.includes(pat)) {
|
|
1984
|
+
obj.permissions.allow.push(pat);
|
|
1985
|
+
cliAllowChanged = true;
|
|
1986
|
+
}
|
|
1987
|
+
});
|
|
1988
|
+
if (!cliOk) {
|
|
1989
|
+
globalChanges.push("✗ Cursor CLI permissions error");
|
|
1990
|
+
} else if (cliAllowChanged) {
|
|
1991
|
+
globalChanges.push("Cursor CLI MCP allowlist configured");
|
|
1992
|
+
}
|
|
1928
1993
|
}
|
|
1929
1994
|
}
|
|
1930
1995
|
|
|
@@ -2362,6 +2427,13 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
|
|
|
2362
2427
|
}
|
|
2363
2428
|
toolSlug = toolFlag; // preserved as-is for the URL param
|
|
2364
2429
|
}
|
|
2430
|
+
// cursor-agent --yolo runs `npx patchcord` without --tool=cursor. Without
|
|
2431
|
+
// this, browser connect defaults to Claude and writes .mcp.json instead of
|
|
2432
|
+
// .cursor/mcp.json — MCP never loads in cursor-agent.
|
|
2433
|
+
if (!choice && process.env.CURSOR_AGENT) {
|
|
2434
|
+
choice = CLIENT_TYPE_MAP.cursor;
|
|
2435
|
+
toolSlug = toolSlug || "cursor";
|
|
2436
|
+
}
|
|
2365
2437
|
|
|
2366
2438
|
// --token bypass for power users / CI / self-hosters
|
|
2367
2439
|
const tokenFlag = flags.find(f => f.startsWith("--token="))?.split("=")[1]
|
|
@@ -2412,12 +2484,14 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
|
|
|
2412
2484
|
let existingToken = "";
|
|
2413
2485
|
let existingConfigFile = "";
|
|
2414
2486
|
const mcpJsonPath = join(cwd, ".mcp.json");
|
|
2487
|
+
const cursorMcpPath = join(cwd, ".cursor", "mcp.json");
|
|
2415
2488
|
const codexTomlPath = join(cwd, ".codex", "config.toml");
|
|
2416
2489
|
const grokTomlPath = join(cwd, ".grok", "config.toml");
|
|
2417
2490
|
const kimiJsonPath = join(cwd, ".kimi", "mcp.json");
|
|
2418
2491
|
|
|
2419
|
-
const slugForCheck = toolSlug ? toolSlug.replace(/-/g, "_") : "";
|
|
2492
|
+
const slugForCheck = toolSlug ? toolSlug.replace(/-/g, "_") : (process.env.CURSOR_AGENT ? "cursor" : "");
|
|
2420
2493
|
const checkMcpJson = !slugForCheck || slugForCheck === "claude_code";
|
|
2494
|
+
const checkCursorJson = !slugForCheck || slugForCheck === "cursor";
|
|
2421
2495
|
const checkCodexToml = !slugForCheck || slugForCheck === "codex";
|
|
2422
2496
|
const checkGrokToml = !slugForCheck || ["grok", "grok_cli", "grok_build"].includes(slugForCheck);
|
|
2423
2497
|
const checkKimiJson = !slugForCheck || slugForCheck === "kimi";
|
|
@@ -2432,6 +2506,16 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
|
|
|
2432
2506
|
}
|
|
2433
2507
|
} catch {}
|
|
2434
2508
|
}
|
|
2509
|
+
if (!existingToken && checkCursorJson && existsSync(cursorMcpPath)) {
|
|
2510
|
+
try {
|
|
2511
|
+
const existing = JSON.parse(readFileSync(cursorMcpPath, "utf-8"));
|
|
2512
|
+
const pt = existing?.mcpServers?.patchcord;
|
|
2513
|
+
if (pt?.headers?.Authorization) {
|
|
2514
|
+
existingToken = pt.headers.Authorization.replace(/^Bearer\s+/i, "");
|
|
2515
|
+
existingConfigFile = cursorMcpPath;
|
|
2516
|
+
}
|
|
2517
|
+
} catch {}
|
|
2518
|
+
}
|
|
2435
2519
|
if (!existingToken && checkCodexToml && existsSync(codexTomlPath)) {
|
|
2436
2520
|
try {
|
|
2437
2521
|
const content = readFileSync(codexTomlPath, "utf-8");
|
|
@@ -3276,7 +3360,7 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
|
|
|
3276
3360
|
mcpServers: {
|
|
3277
3361
|
patchcord: {
|
|
3278
3362
|
type: "http",
|
|
3279
|
-
url: `${serverUrl}/mcp
|
|
3363
|
+
url: `${serverUrl}/mcp`,
|
|
3280
3364
|
headers: {
|
|
3281
3365
|
Authorization: `Bearer ${token}`,
|
|
3282
3366
|
"X-Patchcord-Machine": hostname,
|
|
@@ -3295,6 +3379,7 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
|
|
|
3295
3379
|
// their JSON and re-runs.
|
|
3296
3380
|
process.exit(1);
|
|
3297
3381
|
}
|
|
3382
|
+
dedupeClaudePatchcordMcp(cwd);
|
|
3298
3383
|
console.log(`\n ${green}✓${r} Claude Code configured: ${dim}${mcpPath}${r}`);
|
|
3299
3384
|
|
|
3300
3385
|
// Statusline: ask whether to enable full mode (unless --full flag passed or already configured).
|
|
@@ -3410,6 +3495,9 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
|
|
|
3410
3495
|
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}`);
|
|
3411
3496
|
} else if (isKimi) {
|
|
3412
3497
|
console.log(`\n ${green}→${r} ${bold}Restart your ${toolName} session with ${cyan}kimi-pc${r}${bold}, then say: ${cyan}${bold}check inbox${r}`);
|
|
3498
|
+
} else if (isCursor && hasCursorAgent) {
|
|
3499
|
+
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}`);
|
|
3500
|
+
console.log(` ${dim}Then:${r} ${cyan}${bold}check inbox${r} ${dim}before${r} ${cyan}${bold}/patchcord-subscribe${r}`);
|
|
3413
3501
|
} else {
|
|
3414
3502
|
console.log(`\n ${green}→${r} ${bold}Restart your ${toolName} session${r}, then say: ${cyan}${bold}check inbox${r}`);
|
|
3415
3503
|
}
|
package/package.json
CHANGED
|
@@ -15,12 +15,21 @@ Start the persistent listener in a background Shell.
|
|
|
15
15
|
expose it as `mcp_patchcord_inbox`). Process every pending message according
|
|
16
16
|
to the patchcord inbox skill before starting the listener.
|
|
17
17
|
|
|
18
|
+
**Just provisioned in this same session?** Cursor/`cursor-agent` loads MCP
|
|
19
|
+
servers at session start only — `inbox()` will not exist until the user
|
|
20
|
+
starts a **new** session after connect. Do not spin on missing MCP tools.
|
|
21
|
+
Skip inbox drain, start the listener (step 3), and say one line: *MCP inbox
|
|
22
|
+
needs a fresh cursor-agent session after connect; listener is running — restart
|
|
23
|
+
once, then say check inbox.*
|
|
24
|
+
|
|
18
25
|
2. **Find the absolute project root.** Use the current session's git root or
|
|
19
26
|
agent worktree. Do not rely on the ambient Shell cwd: the listener must read
|
|
20
27
|
the `.cursor/mcp.json` for this project and therefore needs an explicit
|
|
21
28
|
`cd`.
|
|
22
29
|
|
|
23
|
-
3. **Spawn the background listener** with Cursor's Shell tool
|
|
30
|
+
3. **Spawn the background listener** with Cursor's Shell tool. **Always**
|
|
31
|
+
`block_until_ms: 0` — never run subscribe in the foreground (that yields
|
|
32
|
+
"Wait skipped" and no wake notifications):
|
|
24
33
|
|
|
25
34
|
```text
|
|
26
35
|
Shell(
|