patchcord 0.5.132 → 0.6.0
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/README.md +5 -1
- package/bin/patchcord.mjs +100 -9
- package/package.json +1 -1
- package/per-project-skills/cursor/subscribe/SKILL.md +75 -0
- package/per-project-skills/grok/inbox/SKILL.md +45 -0
- package/per-project-skills/grok/subscribe/SKILL.md +58 -0
- package/per-project-skills/grok/wait/SKILL.md +19 -0
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ Cross-machine messaging between AI coding agents.
|
|
|
8
8
|
npx patchcord@latest
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
One command. Opens browser, configures everything. Works with Claude Code, Codex CLI, Cursor, Windsurf, Gemini CLI, VS Code, Zed, OpenCode, Kimi CLI.
|
|
11
|
+
One command. Opens browser, configures everything. Works with Claude Code, Codex CLI, Cursor, Grok CLI, Windsurf, Gemini CLI, VS Code, Zed, OpenCode, Kimi CLI.
|
|
12
12
|
|
|
13
13
|
Self-hosted:
|
|
14
14
|
|
|
@@ -26,6 +26,10 @@ npx patchcord@latest --token <token> --server https://patchcord.yourdomain.com
|
|
|
26
26
|
- **Cursor CLI** — bearer-only MCP endpoint, `patchcord:*` pre-allowed in
|
|
27
27
|
`~/.cursor/permissions.json`, and inbox/wait skills installed when
|
|
28
28
|
`cursor-agent` is detected
|
|
29
|
+
- **Cursor subscribe** — `/patchcord:subscribe` starts a background Shell
|
|
30
|
+
listener; `^PATCHCORD:` output wakes the interactive agent
|
|
31
|
+
- **Grok CLI** — project-scoped `.grok/config.toml` plus inbox/subscribe/wait
|
|
32
|
+
skills; `/patchcord-subscribe` uses persistent `monitor` + WebSocket wakeup
|
|
29
33
|
- **Kimi CLI** — global MCP config plus skills in `~/.kimi/skills/`
|
|
30
34
|
|
|
31
35
|
## How it works
|
package/bin/patchcord.mjs
CHANGED
|
@@ -46,7 +46,7 @@ const PROJECT_MARKERS = [
|
|
|
46
46
|
".git", "package.json", "package-lock.json", "Cargo.toml", "go.mod", "go.sum",
|
|
47
47
|
"pyproject.toml", "pom.xml", "build.gradle", "Makefile", "CMakeLists.txt",
|
|
48
48
|
"Gemfile", "composer.json", "mix.exs", "requirements.txt", "setup.py",
|
|
49
|
-
".claude", ".codex", ".cursor", ".vscode", ".kimi", ".openclaw",
|
|
49
|
+
".claude", ".codex", ".cursor", ".grok", ".vscode", ".kimi", ".openclaw",
|
|
50
50
|
];
|
|
51
51
|
|
|
52
52
|
function detectFolder(dir) {
|
|
@@ -153,7 +153,7 @@ if (cmd === "plugin-path") {
|
|
|
153
153
|
}
|
|
154
154
|
|
|
155
155
|
// Shared bearer/base-url resolver. Project-local configs win over globals.
|
|
156
|
-
// Supports all
|
|
156
|
+
// Supports all 13 installer targets: claude_code, codex, cursor, grok, vscode,
|
|
157
157
|
// opencode (per-project) + windsurf, gemini, zed, openclaw, antigravity,
|
|
158
158
|
// cline, kimi (global). Each tool stores the bearer in its own shape.
|
|
159
159
|
function _kimiContextRequested(options = {}) {
|
|
@@ -224,6 +224,32 @@ function readHermesShape(path) {
|
|
|
224
224
|
return { token, baseUrl: url.replace(/\/mcp(\/bearer)?$/, ""), configFile: path, tool: "hermes" };
|
|
225
225
|
}
|
|
226
226
|
|
|
227
|
+
// Read url + bearer token from Grok CLI's project-scoped .grok/config.toml.
|
|
228
|
+
// Grok supports both nested and inline TOML headers, e.g.:
|
|
229
|
+
// [mcp_servers.patchcord.headers] / Authorization = "Bearer pct-..."
|
|
230
|
+
function readGrokTomlShape(path) {
|
|
231
|
+
if (!existsSync(path)) return null;
|
|
232
|
+
try {
|
|
233
|
+
const content = readFileSync(path, "utf-8");
|
|
234
|
+
const header = content.match(/^\[mcp_servers\.patchcord(?:[-\w]*)?\]\s*$/m);
|
|
235
|
+
if (!header) return null;
|
|
236
|
+
const rest = content.slice(header.index + header[0].length);
|
|
237
|
+
// Keep nested [mcp_servers.patchcord.headers] fields in the same server
|
|
238
|
+
// block, but stop at the next server or unrelated top-level TOML table.
|
|
239
|
+
const nextSection = rest.search(/^\[(?!mcp_servers\.patchcord(?:\.|[-\w]*\]))/m);
|
|
240
|
+
const block = nextSection === -1 ? rest : rest.slice(0, nextSection);
|
|
241
|
+
const urlMatch = block.match(/^\s*url\s*=\s*["']([^"']+)["']/m);
|
|
242
|
+
const tokenMatch = block.match(/["']?Authorization["']?\s*=\s*["']Bearer\s+([^"']+)["']/i);
|
|
243
|
+
if (!urlMatch || !tokenMatch) return null;
|
|
244
|
+
return {
|
|
245
|
+
token: tokenMatch[1],
|
|
246
|
+
baseUrl: urlMatch[1].replace(/\/mcp(\/bearer)?$/, ""),
|
|
247
|
+
configFile: path,
|
|
248
|
+
tool: "grok",
|
|
249
|
+
};
|
|
250
|
+
} catch { return null; }
|
|
251
|
+
}
|
|
252
|
+
|
|
227
253
|
async function _resolveBearer(options = {}) {
|
|
228
254
|
const { readFileSync } = await import("fs");
|
|
229
255
|
const preferKimi = _kimiContextRequested(options);
|
|
@@ -324,6 +350,7 @@ async function _resolveBearer(options = {}) {
|
|
|
324
350
|
(cwd) => readJsonAt(join(cwd, ".vscode", "mcp.json"), ["servers", "patchcord"], "vscode"),
|
|
325
351
|
(cwd) => readJsonAt(join(cwd, "opencode.json"), ["mcp", "patchcord"], "opencode"),
|
|
326
352
|
(cwd) => readJsonAt(join(cwd, ".agents", "mcp_config.json"), ["mcpServers", "patchcord"], "antigravity"),
|
|
353
|
+
(cwd) => readGrokTomlShape(join(cwd, ".grok", "config.toml")),
|
|
327
354
|
(cwd) => readCodexTomlShape(join(cwd, ".codex", "config.toml")),
|
|
328
355
|
];
|
|
329
356
|
const projectReaders = preferKimi
|
|
@@ -738,6 +765,7 @@ if (cmd === "subscribe") {
|
|
|
738
765
|
vscode: (cwd) => readMcpJsonShape(join(cwd, ".vscode", "mcp.json"), "servers"),
|
|
739
766
|
opencode: (cwd) => readOpenCodeShape(join(cwd, "opencode.json")),
|
|
740
767
|
codex: (cwd) => readCodexTomlShape(join(cwd, ".codex", "config.toml")),
|
|
768
|
+
grok: (cwd) => readGrokTomlShape(join(cwd, ".grok", "config.toml")),
|
|
741
769
|
};
|
|
742
770
|
|
|
743
771
|
if (tool && !READERS[tool]) {
|
|
@@ -764,7 +792,7 @@ if (cmd === "subscribe") {
|
|
|
764
792
|
if (!found) {
|
|
765
793
|
const whichFile = tool
|
|
766
794
|
? `the ${tool} config file in or above ${cwd}`
|
|
767
|
-
: `any supported tool config (.mcp.json, .cursor/mcp.json, .vscode/mcp.json, opencode.json, .codex/config.toml) above ${cwd}`;
|
|
795
|
+
: `any supported tool config (.mcp.json, .cursor/mcp.json, .grok/config.toml, .vscode/mcp.json, opencode.json, .codex/config.toml) above ${cwd}`;
|
|
768
796
|
console.error(`No patchcord bearer found in ${whichFile}. Run from the agent's project folder.`);
|
|
769
797
|
process.exit(1);
|
|
770
798
|
}
|
|
@@ -995,6 +1023,15 @@ if (cmd === "login" || cmd === "orchestrator" || cmd === "teamlead" || cmd === "
|
|
|
995
1023
|
writeFileSync(p, ex);
|
|
996
1024
|
return p;
|
|
997
1025
|
}
|
|
1026
|
+
if (tool === "grok") {
|
|
1027
|
+
const gdir = join(dir, ".grok"); mkdirSync(gdir, { recursive: true });
|
|
1028
|
+
const p = join(gdir, "config.toml");
|
|
1029
|
+
let ex = existsSync(p) ? readFileSync(p, "utf-8") : "";
|
|
1030
|
+
ex = ex.replace(/\[mcp_servers\.patchcord(?:[-\w]*)?\][\s\S]*?(?=\n\[(?!mcp_servers\.patchcord(?:\.|[-\w]*\]))|$)/g, "").replace(/\n{3,}/g, "\n\n").trim();
|
|
1031
|
+
ex = (ex ? ex + "\n\n" : "") + `[mcp_servers.patchcord]\nurl = "${baseUrl}/mcp"\nenabled = true\n\n[mcp_servers.patchcord.headers]\nAuthorization = "Bearer ${token}"\nX-Patchcord-Machine = "${hostname}"\n`;
|
|
1032
|
+
writeFileSync(p, ex);
|
|
1033
|
+
return p;
|
|
1034
|
+
}
|
|
998
1035
|
if (tool === "kimi" || tool === "kimi-code") {
|
|
999
1036
|
const kdir = join(dir, ".kimi-code"); mkdirSync(kdir, { recursive: true });
|
|
1000
1037
|
return writeJson(join(kdir, "mcp.json"), (o) => {
|
|
@@ -1014,8 +1051,13 @@ if (cmd === "login" || cmd === "orchestrator" || cmd === "teamlead" || cmd === "
|
|
|
1014
1051
|
const cdir = join(dir, ".cursor"); mkdirSync(cdir, { recursive: true });
|
|
1015
1052
|
return writeJson(join(cdir, "mcp.json"), (o) => {
|
|
1016
1053
|
o.mcpServers = o.mcpServers || {};
|
|
1054
|
+
// NO "type" field — cursor-agent silently drops the entire server entry
|
|
1055
|
+
// when "type" is set to any value here (verified: tools simply never
|
|
1056
|
+
// load, ListMcpResources returns empty, no error surfaced anywhere).
|
|
1057
|
+
// Confirmed via 3 headless `cursor-agent --print` variants: with "type"
|
|
1058
|
+
// set, 0 tools; without it, all patchcord tools load correctly — both
|
|
1059
|
+
// against /mcp/bearer and plain /mcp. Do not re-add this field.
|
|
1017
1060
|
o.mcpServers.patchcord = {
|
|
1018
|
-
type: "streamable-http",
|
|
1019
1061
|
url: `${baseUrl}/mcp/bearer`,
|
|
1020
1062
|
headers: hdr,
|
|
1021
1063
|
};
|
|
@@ -1292,7 +1334,7 @@ you design the team, provision its agents, launch them, and manage them.
|
|
|
1292
1334
|
if (!manifest) { console.error("No .patchcord/team.json here — cd into the project root."); process.exit(1); }
|
|
1293
1335
|
const ns = manifest.namespace;
|
|
1294
1336
|
const real = (p) => run(`realpath -m ${JSON.stringify(p)}`) || p;
|
|
1295
|
-
const AGENT_CMDS = /(^|\/)(claude|codex|kimi|kimi-code|opencode|gemini|agy|node)$/;
|
|
1337
|
+
const AGENT_CMDS = /(^|\/)(claude|codex|grok|kimi|kimi-code|opencode|gemini|agy|node)$/;
|
|
1296
1338
|
|
|
1297
1339
|
// Token reader for a specific folder + tool (folder IS the identity).
|
|
1298
1340
|
const tokenInDir = (tool, dir) => {
|
|
@@ -1307,6 +1349,9 @@ you design the team, provision its agents, launch them, and manage them.
|
|
|
1307
1349
|
if (!u || !t) return null;
|
|
1308
1350
|
return { token: t[1], baseUrl: u[1].replace(/\/mcp(\/bearer)?$/, "") };
|
|
1309
1351
|
}
|
|
1352
|
+
if (tool === "grok") {
|
|
1353
|
+
return readGrokTomlShape(join(dir, ".grok", "config.toml"));
|
|
1354
|
+
}
|
|
1310
1355
|
const map = {
|
|
1311
1356
|
opencode: [join(dir, "opencode.json"), ["mcp", "patchcord"]],
|
|
1312
1357
|
kimi: [join(dir, ".kimi-code", "mcp.json"), ["mcpServers", "patchcord"]],
|
|
@@ -1753,6 +1798,7 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
|
|
|
1753
1798
|
mkdirSync(cursorSkillsRoot, { recursive: true });
|
|
1754
1799
|
const cursorSkillDir = join(cursorSkillsRoot, "patchcord");
|
|
1755
1800
|
const cursorWaitDir = join(cursorSkillsRoot, "patchcord-wait");
|
|
1801
|
+
const cursorSubscribeDir = join(cursorSkillsRoot, "patchcord-subscribe");
|
|
1756
1802
|
let cursorChanged = false;
|
|
1757
1803
|
if (!existsSync(cursorSkillDir)) {
|
|
1758
1804
|
mkdirSync(cursorSkillDir, { recursive: true });
|
|
@@ -1764,6 +1810,14 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
|
|
|
1764
1810
|
cpSync(join(pluginRoot, "skills", "wait", "SKILL.md"), join(cursorWaitDir, "SKILL.md"));
|
|
1765
1811
|
cursorChanged = true;
|
|
1766
1812
|
}
|
|
1813
|
+
if (!existsSync(cursorSubscribeDir)) {
|
|
1814
|
+
mkdirSync(cursorSubscribeDir, { recursive: true });
|
|
1815
|
+
cpSync(
|
|
1816
|
+
join(pluginRoot, "per-project-skills", "cursor", "subscribe", "SKILL.md"),
|
|
1817
|
+
join(cursorSubscribeDir, "SKILL.md")
|
|
1818
|
+
);
|
|
1819
|
+
cursorChanged = true;
|
|
1820
|
+
}
|
|
1767
1821
|
if (cursorChanged) globalChanges.push("Cursor skills installed");
|
|
1768
1822
|
|
|
1769
1823
|
if (hasCursorAgent) {
|
|
@@ -2139,7 +2193,7 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
|
|
|
2139
2193
|
let choice = "";
|
|
2140
2194
|
|
|
2141
2195
|
const CLIENT_TYPE_MAP = {
|
|
2142
|
-
"claude_code": "1", "codex": "2", "cursor": "3", "windsurf": "4",
|
|
2196
|
+
"claude_code": "1", "codex": "2", "cursor": "3", "grok": "14", "grok_cli": "14", "grok_build": "14", "windsurf": "4",
|
|
2143
2197
|
"gemini": "5", "vscode": "6", "zed": "7", "opencode": "8", "openclaw": "9", "antigravity": "10",
|
|
2144
2198
|
"cline": "11", "kimi": "12", "hermes": "13",
|
|
2145
2199
|
};
|
|
@@ -2234,11 +2288,13 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
|
|
|
2234
2288
|
let existingConfigFile = "";
|
|
2235
2289
|
const mcpJsonPath = join(cwd, ".mcp.json");
|
|
2236
2290
|
const codexTomlPath = join(cwd, ".codex", "config.toml");
|
|
2291
|
+
const grokTomlPath = join(cwd, ".grok", "config.toml");
|
|
2237
2292
|
const kimiJsonPath = join(cwd, ".kimi", "mcp.json");
|
|
2238
2293
|
|
|
2239
2294
|
const slugForCheck = toolSlug ? toolSlug.replace(/-/g, "_") : "";
|
|
2240
2295
|
const checkMcpJson = !slugForCheck || slugForCheck === "claude_code";
|
|
2241
2296
|
const checkCodexToml = !slugForCheck || slugForCheck === "codex";
|
|
2297
|
+
const checkGrokToml = !slugForCheck || ["grok", "grok_cli", "grok_build"].includes(slugForCheck);
|
|
2242
2298
|
const checkKimiJson = !slugForCheck || slugForCheck === "kimi";
|
|
2243
2299
|
|
|
2244
2300
|
if (checkMcpJson && existsSync(mcpJsonPath)) {
|
|
@@ -2261,6 +2317,13 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
|
|
|
2261
2317
|
}
|
|
2262
2318
|
} catch {}
|
|
2263
2319
|
}
|
|
2320
|
+
if (!existingToken && checkGrokToml && existsSync(grokTomlPath)) {
|
|
2321
|
+
const existing = readGrokTomlShape(grokTomlPath);
|
|
2322
|
+
if (existing) {
|
|
2323
|
+
existingToken = existing.token;
|
|
2324
|
+
existingConfigFile = grokTomlPath;
|
|
2325
|
+
}
|
|
2326
|
+
}
|
|
2264
2327
|
if (!existingToken && checkKimiJson && existsSync(kimiJsonPath)) {
|
|
2265
2328
|
try {
|
|
2266
2329
|
const existing = JSON.parse(readFileSync(kimiJsonPath, "utf-8"));
|
|
@@ -2278,6 +2341,7 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
|
|
|
2278
2341
|
// Figure out which tool is already configured
|
|
2279
2342
|
const existingToolName = existingConfigFile.includes(".kimi") ? "Kimi Code"
|
|
2280
2343
|
: existingConfigFile.includes(".codex") ? "Codex"
|
|
2344
|
+
: existingConfigFile.includes(".grok") ? "Grok CLI"
|
|
2281
2345
|
: existingConfigFile.includes(".agents") ? "Antigravity CLI"
|
|
2282
2346
|
: existingConfigFile.includes("antigravity") ? "Antigravity"
|
|
2283
2347
|
: existingConfigFile.includes("openclaw") ? "OpenClaw"
|
|
@@ -2512,6 +2576,7 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
|
|
|
2512
2576
|
|
|
2513
2577
|
const isCodex = choice === "2";
|
|
2514
2578
|
const isCursor = choice === "3";
|
|
2579
|
+
const isGrok = choice === "14";
|
|
2515
2580
|
const isWindsurf = choice === "4";
|
|
2516
2581
|
const isGemini = choice === "5";
|
|
2517
2582
|
const isVSCode = choice === "6";
|
|
@@ -2548,8 +2613,11 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
|
|
|
2548
2613
|
const cursorPath = join(cursorDir, "mcp.json");
|
|
2549
2614
|
const cursorConfig = {
|
|
2550
2615
|
mcpServers: {
|
|
2616
|
+
// NO "type" field — cursor-agent silently drops the entire server
|
|
2617
|
+
// entry when "type" is set (verified: tools never load, no error
|
|
2618
|
+
// surfaced anywhere). Confirmed via headless cursor-agent --print
|
|
2619
|
+
// testing. Do not re-add this field.
|
|
2551
2620
|
patchcord: {
|
|
2552
|
-
type: "streamable-http",
|
|
2553
2621
|
url: `${serverUrl}/mcp/bearer`,
|
|
2554
2622
|
headers: {
|
|
2555
2623
|
Authorization: `Bearer ${token}`,
|
|
@@ -2567,6 +2635,29 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
|
|
|
2567
2635
|
console.log(`\n ${green}✓${r} Cursor configured: ${dim}${cursorPath}${r}`);
|
|
2568
2636
|
console.log(` ${dim}Per-project only — other projects won't see this agent.${r}`);
|
|
2569
2637
|
}
|
|
2638
|
+
} else if (isGrok) {
|
|
2639
|
+
// Grok CLI: project-scoped .grok/config.toml. Grok loads this file from
|
|
2640
|
+
// the current directory up through the git root, so the bearer remains
|
|
2641
|
+
// isolated to this project. Use Grok's native HTTP MCP form.
|
|
2642
|
+
const grokDir = join(cwd, ".grok");
|
|
2643
|
+
mkdirSync(grokDir, { recursive: true });
|
|
2644
|
+
const grokPath = join(grokDir, "config.toml");
|
|
2645
|
+
let grokConfig = existsSync(grokPath) ? readFileSync(grokPath, "utf-8") : "";
|
|
2646
|
+
grokConfig = grokConfig.replace(/\[mcp_servers\.patchcord(?:[-\w]*)?\][\s\S]*?(?=\n\[(?!mcp_servers\.patchcord(?:\.|[-\w]*\]))|$)/g, "").replace(/\n{3,}/g, "\n\n").trim();
|
|
2647
|
+
grokConfig = (grokConfig ? grokConfig + "\n\n" : "") + `[mcp_servers.patchcord]\nurl = "${serverUrl}/mcp"\nenabled = true\n\n[mcp_servers.patchcord.headers]\nAuthorization = "Bearer ${token}"\nX-Patchcord-Machine = "${hostname}"\n`;
|
|
2648
|
+
try {
|
|
2649
|
+
writeFileSync(grokPath, grokConfig);
|
|
2650
|
+
console.log(`\n ${green}✓${r} Grok CLI configured: ${dim}${grokPath}${r}`);
|
|
2651
|
+
console.log(` ${dim}Per-project only — Grok loads configs from this folder up to the git root.${r}`);
|
|
2652
|
+
const grokSkillsSrc = join(pluginRoot, "per-project-skills", "grok");
|
|
2653
|
+
const grokSkillsDest = join(grokDir, "skills");
|
|
2654
|
+
if (existsSync(grokSkillsSrc)) {
|
|
2655
|
+
cpSync(grokSkillsSrc, grokSkillsDest, { recursive: true, force: true });
|
|
2656
|
+
console.log(` ${green}✓${r} Grok Patchcord skills installed: ${dim}${grokSkillsDest}${r}`);
|
|
2657
|
+
}
|
|
2658
|
+
} catch (e) {
|
|
2659
|
+
console.log(`\n ${yellow}⚠ Failed to write ${grokPath}: ${e.message}${r}`);
|
|
2660
|
+
}
|
|
2570
2661
|
} else if (isHermes) {
|
|
2571
2662
|
// Hermes: global only (~/.hermes/config.yaml, YAML, mcp_servers key)
|
|
2572
2663
|
const hermesPath = join(HOME, ".hermes", "config.yaml");
|
|
@@ -3152,7 +3243,7 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
|
|
|
3152
3243
|
// Hermes is global config (~/.hermes/config.yaml) — no per-project file to ignore.
|
|
3153
3244
|
if (!isWindsurf && !isGemini && !isZed && !isOpenClaw && !isCline && !isHermes) {
|
|
3154
3245
|
const gitignorePath = join(cwd, ".gitignore");
|
|
3155
|
-
const configFile = isKimiCode ? ".kimi-code/mcp.json" : isKimi ? ".kimi/mcp.json" : isCodex ? ".codex/config.toml" : isCursor ? ".cursor/mcp.json" : isVSCode ? ".vscode/mcp.json" : isOpenCode ? "opencode.json" : isAntigravity ? ".agents/mcp_config.json" : ".mcp.json";
|
|
3246
|
+
const configFile = isKimiCode ? ".kimi-code/mcp.json" : isKimi ? ".kimi/mcp.json" : isCodex ? ".codex/config.toml" : isCursor ? ".cursor/mcp.json" : isGrok ? ".grok/config.toml" : isVSCode ? ".vscode/mcp.json" : isOpenCode ? "opencode.json" : isAntigravity ? ".agents/mcp_config.json" : ".mcp.json";
|
|
3156
3247
|
// Forms that already cover this config (its file or its dir)
|
|
3157
3248
|
const patterns = [configFile];
|
|
3158
3249
|
if (isKimiCode) patterns.push(".kimi-code/");
|
|
@@ -3180,7 +3271,7 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
|
|
|
3180
3271
|
}
|
|
3181
3272
|
}
|
|
3182
3273
|
|
|
3183
|
-
const toolName = isHermes ? "Hermes" : isKimiCode ? "Kimi Code" : isKimi ? "Kimi Code" : isAntigravity ? "Antigravity CLI" : isCline ? "Cline" : isOpenClaw ? "OpenClaw" : isOpenCode ? "OpenCode" : isZed ? "Zed" : isVSCode ? "VS Code" : isGemini ? "Gemini CLI" : isWindsurf ? "Windsurf" : isCursor ? "Cursor" : isCodex ? "Codex" : "Claude Code";
|
|
3274
|
+
const toolName = isHermes ? "Hermes" : isKimiCode ? "Kimi Code" : isKimi ? "Kimi Code" : isAntigravity ? "Antigravity CLI" : isCline ? "Cline" : isOpenClaw ? "OpenClaw" : isOpenCode ? "OpenCode" : isZed ? "Zed" : isVSCode ? "VS Code" : isGemini ? "Gemini CLI" : isWindsurf ? "Windsurf" : isGrok ? "Grok CLI" : isCursor ? "Cursor" : isCodex ? "Codex" : "Claude Code";
|
|
3184
3275
|
|
|
3185
3276
|
if (!isWindsurf && !isGemini && !isZed && !isOpenClaw && !isCline && !isKimi && !isHermes) {
|
|
3186
3277
|
console.log(`\n ${dim}To connect a second agent:${r}`);
|
package/package.json
CHANGED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: patchcord:subscribe
|
|
3
|
+
description: >
|
|
4
|
+
Start a persistent background WebSocket listener that wakes the Cursor agent
|
|
5
|
+
when new Patchcord messages arrive. Use ONLY when the user runs
|
|
6
|
+
/patchcord:subscribe or when a project explicitly asks to auto-arm it.
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
User invoked `/patchcord:subscribe` — do NOT substitute `wait_for_message()`.
|
|
10
|
+
Start the persistent listener in a background Shell.
|
|
11
|
+
|
|
12
|
+
## Start
|
|
13
|
+
|
|
14
|
+
1. **Drain the inbox first.** Call the Patchcord `inbox` MCP tool (Cursor may
|
|
15
|
+
expose it as `mcp_patchcord_inbox`). Process every pending message according
|
|
16
|
+
to the patchcord inbox skill before starting the listener.
|
|
17
|
+
|
|
18
|
+
2. **Find the absolute project root.** Use the current session's git root or
|
|
19
|
+
agent worktree. Do not rely on the ambient Shell cwd: the listener must read
|
|
20
|
+
the `.cursor/mcp.json` for this project and therefore needs an explicit
|
|
21
|
+
`cd`.
|
|
22
|
+
|
|
23
|
+
3. **Spawn the background listener** with Cursor's Shell tool:
|
|
24
|
+
|
|
25
|
+
```text
|
|
26
|
+
Shell(
|
|
27
|
+
description: "Patchcord realtime listener",
|
|
28
|
+
block_until_ms: 0,
|
|
29
|
+
command: "cd <ABSOLUTE_PROJECT_DIR> && patchcord subscribe | grep --line-buffered '^PATCHCORD:'; exit ${PIPESTATUS[0]}",
|
|
30
|
+
notify_on_output: { pattern: "^PATCHCORD:", reason: "Patchcord message" }
|
|
31
|
+
)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Replace `<ABSOLUTE_PROJECT_DIR>` with the root found in step 2. The grep
|
|
35
|
+
filter drops listener diagnostics and heartbeat lines; only `PATCHCORD:`
|
|
36
|
+
lines wake the agent. `${PIPESTATUS[0]}` preserves `patchcord subscribe`'s
|
|
37
|
+
exit code through the pipe. `subscribe.mjs` owns the pidfile and
|
|
38
|
+
reconnect/backoff loop; do not re-arm it after every notification.
|
|
39
|
+
|
|
40
|
+
4. **Confirm one line:**
|
|
41
|
+
`Patchcord listener active — I'll pick up new messages as they arrive.`
|
|
42
|
+
|
|
43
|
+
## When a notification fires
|
|
44
|
+
|
|
45
|
+
Cursor surfaces a line such as `PATCHCORD: 1 new from <sender>`:
|
|
46
|
+
|
|
47
|
+
1. Say: `Got a Patchcord ping — checking inbox.`
|
|
48
|
+
2. Call the Patchcord `inbox` MCP tool.
|
|
49
|
+
3. Process and reply to each message according to the patchcord inbox skill.
|
|
50
|
+
4. Do not spawn another listener unless the background Shell exited.
|
|
51
|
+
|
|
52
|
+
## Stopping
|
|
53
|
+
|
|
54
|
+
- End the Cursor agent session, or
|
|
55
|
+
- Kill the listener with:
|
|
56
|
+
`kill $(cat /tmp/patchcord_subscribe_<namespace>_<agent>.pid)`
|
|
57
|
+
|
|
58
|
+
## Errors
|
|
59
|
+
|
|
60
|
+
Read the background Shell terminal output and surface the exact cause:
|
|
61
|
+
|
|
62
|
+
- `already running (pid N)` (exit 2) — another listener is active; report it
|
|
63
|
+
and do not respawn.
|
|
64
|
+
- `ticket: token rejected (HTTP 401|403)` — the bearer token is invalid or
|
|
65
|
+
expired.
|
|
66
|
+
- `no patchcord config found` — the listener was not started from a project
|
|
67
|
+
containing `.cursor/mcp.json` (or a parent project directory).
|
|
68
|
+
- `subscribe: fatal: ...` — report the fatal line verbatim.
|
|
69
|
+
|
|
70
|
+
If the stream ends without one of these errors, it likely ended with the Cursor
|
|
71
|
+
session or because its stdout consumer closed. Start it again only when the
|
|
72
|
+
user invokes `/patchcord:subscribe` again.
|
|
73
|
+
|
|
74
|
+
**Forbidden on failure:** no hand-rolled `nohup ... &`, no pidfile editing, no
|
|
75
|
+
`curl`-to-MCP workarounds, and no re-arm loop after each inbox notification.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: patchcord-inbox
|
|
3
|
+
description: >
|
|
4
|
+
Check and process Patchcord messages for this Grok project. Use when the
|
|
5
|
+
user asks to check inbox, mentions Patchcord or another agent, or a monitor
|
|
6
|
+
notification reports a new message.
|
|
7
|
+
user-invocable: true
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Patchcord inbox for Grok
|
|
11
|
+
|
|
12
|
+
Use this skill only when the Patchcord MCP tools are loaded in the current
|
|
13
|
+
Grok session. If they are not loaded, do not read `.grok/config.toml`, scrape a
|
|
14
|
+
token, or call Patchcord over HTTP; report that the MCP is unavailable.
|
|
15
|
+
|
|
16
|
+
The Grok MCP server namespaces its tools as `patchcord__*`:
|
|
17
|
+
|
|
18
|
+
- `patchcord__inbox` — read pending messages and presence
|
|
19
|
+
- `patchcord__send_message` — send to one or more agents
|
|
20
|
+
- `patchcord__reply` — reply to a received message
|
|
21
|
+
- `patchcord__wait_for_message` — wait for a response
|
|
22
|
+
- `patchcord__attachment`, `patchcord__recall`, `patchcord__unsend`
|
|
23
|
+
|
|
24
|
+
## Intake
|
|
25
|
+
|
|
26
|
+
1. Call `patchcord__inbox` first.
|
|
27
|
+
2. For every actionable message, do the requested work before replying.
|
|
28
|
+
3. Reply with concrete files, changes, and verification; never send an ack-only
|
|
29
|
+
response. Close an ack-only thread silently when needed.
|
|
30
|
+
4. If a request is destructive or needs credentials, stop and ask the user.
|
|
31
|
+
5. Do not reply to conversation-ending acks such as “ok”, “thanks”, or
|
|
32
|
+
“noted”.
|
|
33
|
+
|
|
34
|
+
## Sending
|
|
35
|
+
|
|
36
|
+
Call `patchcord__inbox` before sending. Use `patchcord__send_message` with the
|
|
37
|
+
agent name and a specific, complete message. Use named threads for multi-turn
|
|
38
|
+
work. If the recipient is not actively listening, the message is still queued;
|
|
39
|
+
do not retry in a loop.
|
|
40
|
+
|
|
41
|
+
## Message classification
|
|
42
|
+
|
|
43
|
+
For each new message, classify it as ACK, BLOCKED, ACTIONABLE, or FYI. For
|
|
44
|
+
ACTIONABLE work, implement and verify it, then report `DONE` or `BLOCKED` with
|
|
45
|
+
the evidence. Never claim a test or fix without running it in this checkout.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: patchcord-subscribe
|
|
3
|
+
description: >
|
|
4
|
+
Start a persistent Patchcord realtime listener that wakes Grok when new
|
|
5
|
+
messages arrive. Use ONLY when the user explicitly runs
|
|
6
|
+
/patchcord-subscribe or asks to subscribe to Patchcord.
|
|
7
|
+
disable-model-invocation: true
|
|
8
|
+
user-invocable: true
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Patchcord subscribe for Grok
|
|
12
|
+
|
|
13
|
+
The user explicitly requested `/patchcord-subscribe`. Do not replace this with
|
|
14
|
+
`patchcord__wait_for_message` or a polling loop.
|
|
15
|
+
|
|
16
|
+
## Start
|
|
17
|
+
|
|
18
|
+
1. Drain the backlog with `patchcord__inbox`. Process pending actionable
|
|
19
|
+
messages before starting the listener.
|
|
20
|
+
2. Start a persistent Grok `monitor` with this command:
|
|
21
|
+
|
|
22
|
+
```text
|
|
23
|
+
monitor(
|
|
24
|
+
description: "patchcord realtime listener",
|
|
25
|
+
persistent: true,
|
|
26
|
+
timeout_ms: 3600000,
|
|
27
|
+
command: "patchcord subscribe | grep --line-buffered '^PATCHCORD:'; exit ${PIPESTATUS[0]}"
|
|
28
|
+
)
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The explicit `cd` is not needed when Grok runs from the project directory;
|
|
32
|
+
`patchcord subscribe` resolves `.grok/config.toml` by walking to the git
|
|
33
|
+
root. The listener owns its pidfile and reconnects itself.
|
|
34
|
+
3. Tell the user: **“Patchcord listener active — I’ll pick up new messages as
|
|
35
|
+
they arrive.”**
|
|
36
|
+
|
|
37
|
+
## On each notification
|
|
38
|
+
|
|
39
|
+
When monitor surfaces `PATCHCORD: 1 new from <sender>`:
|
|
40
|
+
|
|
41
|
+
1. Say that a Patchcord ping arrived and call `patchcord__inbox`.
|
|
42
|
+
2. Do the requested work.
|
|
43
|
+
3. Reply with concrete results using `patchcord__reply`; classify the outcome
|
|
44
|
+
as DONE, BLOCKED, or ACTIONABLE.
|
|
45
|
+
4. Do not start another monitor. The existing listener persists across turns.
|
|
46
|
+
|
|
47
|
+
## Errors and stopping
|
|
48
|
+
|
|
49
|
+
If the monitor stream ends, inspect its final output and report one sentence:
|
|
50
|
+
|
|
51
|
+
- `already running (pid N)` — another listener is active; do not respawn.
|
|
52
|
+
- `ticket: token rejected (HTTP 401|403)` — the project bearer is invalid.
|
|
53
|
+
- `no patchcord config found` — Grok is outside a configured project.
|
|
54
|
+
- `subscribe: fatal: ...` — report the fatal line verbatim.
|
|
55
|
+
|
|
56
|
+
Do not use `pgrep`, `ps`, `pkill`, `killall`, hand-written pidfiles, or
|
|
57
|
+
respawn loops. Stop by ending the Grok session or killing the pid from:
|
|
58
|
+
`/tmp/patchcord_subscribe_<namespace>_<agent>.pid`.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: patchcord-wait
|
|
3
|
+
description: >
|
|
4
|
+
Wait for one Patchcord response in this Grok project. Use only when the user
|
|
5
|
+
explicitly asks Grok to wait for a Patchcord message.
|
|
6
|
+
user-invocable: true
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Patchcord wait for Grok
|
|
10
|
+
|
|
11
|
+
Use only when the user explicitly asks to wait. If Patchcord MCP tools are not
|
|
12
|
+
loaded, do not substitute the CLI, HTTP, or a config-file token.
|
|
13
|
+
|
|
14
|
+
Call `patchcord__wait_for_message` once. When it returns, process the message
|
|
15
|
+
before replying with concrete results. Use `patchcord__reply` with `resolve`
|
|
16
|
+
when the thread is complete. Do not send text replies to ack-only messages.
|
|
17
|
+
|
|
18
|
+
This is a one-shot wait. For a persistent realtime listener, use
|
|
19
|
+
`patchcord-subscribe` instead.
|