patchcord 0.3.23 → 0.3.25
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 +80 -5
- package/package.json +1 -2
- package/skills/inbox/SKILL.md +8 -8
- package/codex/SKILL.md +0 -69
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "patchcord",
|
|
3
3
|
"description": "Cross-machine agent messaging with auto-inbox checking. Agents automatically respond to messages from other agents without human intervention.",
|
|
4
|
-
"version": "0.3.
|
|
4
|
+
"version": "0.3.25",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "ppravdin"
|
|
7
7
|
},
|
package/bin/patchcord.mjs
CHANGED
|
@@ -117,6 +117,15 @@ if (!cmd || cmd === "install" || cmd === "agent") {
|
|
|
117
117
|
globalChanges.push("Cursor skill installed");
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
+
// Windsurf
|
|
121
|
+
const windsurfSkillDir = join(process.env.HOME || "", ".codeium", "windsurf", "skills", "patchcord");
|
|
122
|
+
const windsurfSkillsRoot = join(process.env.HOME || "", ".codeium", "windsurf", "skills");
|
|
123
|
+
if (existsSync(join(process.env.HOME || "", ".codeium", "windsurf")) && !existsSync(windsurfSkillDir)) {
|
|
124
|
+
mkdirSync(windsurfSkillDir, { recursive: true });
|
|
125
|
+
cpSync(join(pluginRoot, "skills", "inbox", "SKILL.md"), join(windsurfSkillDir, "SKILL.md"));
|
|
126
|
+
globalChanges.push("Windsurf skill installed");
|
|
127
|
+
}
|
|
128
|
+
|
|
120
129
|
// Codex CLI
|
|
121
130
|
const codexConfig = join(process.env.HOME || "", ".codex", "config.toml");
|
|
122
131
|
if (existsSync(codexConfig)) {
|
|
@@ -159,13 +168,15 @@ if (!cmd || cmd === "install" || cmd === "agent") {
|
|
|
159
168
|
console.log(`\n${bold}Which tool are you setting up?${r}\n`);
|
|
160
169
|
console.log(` ${cyan}1.${r} Claude Code`);
|
|
161
170
|
console.log(` ${cyan}2.${r} Codex CLI`);
|
|
162
|
-
console.log(` ${cyan}3.${r} Cursor
|
|
171
|
+
console.log(` ${cyan}3.${r} Cursor`);
|
|
172
|
+
console.log(` ${cyan}4.${r} Windsurf\n`);
|
|
163
173
|
|
|
164
|
-
const choice = (await ask(`${dim}Choose (1/2/3):${r} `)).trim();
|
|
174
|
+
const choice = (await ask(`${dim}Choose (1/2/3/4):${r} `)).trim();
|
|
165
175
|
const isCodex = choice === "2";
|
|
166
176
|
const isCursor = choice === "3";
|
|
177
|
+
const isWindsurf = choice === "4";
|
|
167
178
|
|
|
168
|
-
if (!["1", "2", "3"].includes(choice)) {
|
|
179
|
+
if (!["1", "2", "3", "4"].includes(choice)) {
|
|
169
180
|
console.error("Invalid choice.");
|
|
170
181
|
rl.close();
|
|
171
182
|
process.exit(1);
|
|
@@ -221,6 +232,37 @@ if (!cmd || cmd === "install" || cmd === "agent") {
|
|
|
221
232
|
}
|
|
222
233
|
} catch {}
|
|
223
234
|
}
|
|
235
|
+
} else if (isWindsurf) {
|
|
236
|
+
// Check per-project config
|
|
237
|
+
const wsPath = join(cwd, ".windsurf", "mcp.json");
|
|
238
|
+
if (existsSync(wsPath)) {
|
|
239
|
+
try {
|
|
240
|
+
const existing = JSON.parse(readFileSync(wsPath, "utf-8"));
|
|
241
|
+
if (existing.mcpServers?.patchcord) {
|
|
242
|
+
console.log(`\n ${yellow}⚠ Windsurf already configured in this project${r}`);
|
|
243
|
+
console.log(` ${dim}${wsPath}${r}`);
|
|
244
|
+
const replace = (await ask(` ${dim}Replace? (y/N):${r} `)).trim().toLowerCase();
|
|
245
|
+
if (replace !== "y" && replace !== "yes") {
|
|
246
|
+
console.log("Keeping existing config.");
|
|
247
|
+
rl.close();
|
|
248
|
+
process.exit(0);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
} catch {}
|
|
252
|
+
}
|
|
253
|
+
// Warn about global config conflict
|
|
254
|
+
const globalWs = join(process.env.HOME || "", ".codeium", "windsurf", "mcp_config.json");
|
|
255
|
+
if (existsSync(globalWs)) {
|
|
256
|
+
try {
|
|
257
|
+
const global = JSON.parse(readFileSync(globalWs, "utf-8"));
|
|
258
|
+
if (global.mcpServers?.patchcord) {
|
|
259
|
+
console.log(`\n ${yellow}⚠ Patchcord is also configured globally in Windsurf${r}`);
|
|
260
|
+
console.log(` ${dim}${globalWs}${r}`);
|
|
261
|
+
console.log(` ${yellow}Having both global AND per-project will cause duplicate tool calls.${r}`);
|
|
262
|
+
console.log(` ${dim}Remove patchcord from global: edit ~/.codeium/windsurf/mcp_config.json${r}`);
|
|
263
|
+
}
|
|
264
|
+
} catch {}
|
|
265
|
+
}
|
|
224
266
|
} else {
|
|
225
267
|
const configPath = join(cwd, ".codex", "config.toml");
|
|
226
268
|
if (existsSync(configPath)) {
|
|
@@ -327,11 +369,44 @@ if (!cmd || cmd === "install" || cmd === "agent") {
|
|
|
327
369
|
}
|
|
328
370
|
console.log(`\n ${green}✓${r} Cursor configured: ${dim}${cursorPath}${r}`);
|
|
329
371
|
console.log(` ${dim}Per-project only — other projects won't see this agent.${r}`);
|
|
372
|
+
} else if (isWindsurf) {
|
|
373
|
+
// Windsurf: write .windsurf/mcp.json (per-project)
|
|
374
|
+
const wsDir = join(cwd, ".windsurf");
|
|
375
|
+
mkdirSync(wsDir, { recursive: true });
|
|
376
|
+
const wsPath = join(wsDir, "mcp.json");
|
|
377
|
+
const wsConfig = {
|
|
378
|
+
mcpServers: {
|
|
379
|
+
patchcord: {
|
|
380
|
+
command: "npx",
|
|
381
|
+
args: [
|
|
382
|
+
"-y", "mcp-remote",
|
|
383
|
+
serverUrl,
|
|
384
|
+
"--header",
|
|
385
|
+
`Authorization: Bearer ${token}`,
|
|
386
|
+
],
|
|
387
|
+
},
|
|
388
|
+
},
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
if (existsSync(wsPath)) {
|
|
392
|
+
try {
|
|
393
|
+
const existing = JSON.parse(readFileSync(wsPath, "utf-8"));
|
|
394
|
+
existing.mcpServers = existing.mcpServers || {};
|
|
395
|
+
existing.mcpServers.patchcord = wsConfig.mcpServers.patchcord;
|
|
396
|
+
writeFileSync(wsPath, JSON.stringify(existing, null, 2) + "\n");
|
|
397
|
+
} catch {
|
|
398
|
+
writeFileSync(wsPath, JSON.stringify(wsConfig, null, 2) + "\n");
|
|
399
|
+
}
|
|
400
|
+
} else {
|
|
401
|
+
writeFileSync(wsPath, JSON.stringify(wsConfig, null, 2) + "\n");
|
|
402
|
+
}
|
|
403
|
+
console.log(`\n ${green}✓${r} Windsurf configured: ${dim}${wsPath}${r}`);
|
|
404
|
+
console.log(` ${dim}Per-project only — other projects won't see this agent.${r}`);
|
|
330
405
|
} else if (isCodex) {
|
|
331
406
|
// Codex: copy skill + write config
|
|
332
407
|
const dest = join(cwd, ".agents", "skills", "patchcord");
|
|
333
408
|
mkdirSync(dest, { recursive: true });
|
|
334
|
-
cpSync(join(pluginRoot, "
|
|
409
|
+
cpSync(join(pluginRoot, "skills", "inbox", "SKILL.md"), join(dest, "SKILL.md"));
|
|
335
410
|
|
|
336
411
|
const codexDir = join(cwd, ".codex");
|
|
337
412
|
mkdirSync(codexDir, { recursive: true });
|
|
@@ -373,7 +448,7 @@ if (!cmd || cmd === "install" || cmd === "agent") {
|
|
|
373
448
|
console.log(`\n ${green}✓${r} Claude Code configured: ${dim}${mcpPath}${r}`);
|
|
374
449
|
}
|
|
375
450
|
|
|
376
|
-
const toolName = isCursor ? "Cursor" : isCodex ? "Codex" : "Claude Code";
|
|
451
|
+
const toolName = isWindsurf ? "Windsurf" : isCursor ? "Cursor" : isCodex ? "Codex" : "Claude Code";
|
|
377
452
|
console.log(`\n${dim}Restart your ${toolName} session, then run:${r} ${bold}inbox()${r}`);
|
|
378
453
|
process.exit(0);
|
|
379
454
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "patchcord",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.25",
|
|
4
4
|
"description": "Cross-machine agent messaging for Claude Code and Codex",
|
|
5
5
|
"author": "ppravdin",
|
|
6
6
|
"license": "MIT",
|
|
@@ -26,7 +26,6 @@
|
|
|
26
26
|
"hooks/",
|
|
27
27
|
"scripts/",
|
|
28
28
|
"skills/",
|
|
29
|
-
"codex/",
|
|
30
29
|
"README.md"
|
|
31
30
|
]
|
|
32
31
|
}
|
package/skills/inbox/SKILL.md
CHANGED
|
@@ -8,7 +8,7 @@ description: >
|
|
|
8
8
|
---
|
|
9
9
|
# patchcord
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
7 MCP tools: inbox, send_message, reply, wait_for_message, attachment, recall, unsend.
|
|
12
12
|
|
|
13
13
|
## CRITICAL: DO THE WORK, NEVER JUST ACKNOWLEDGE
|
|
14
14
|
|
|
@@ -44,7 +44,7 @@ If there are pending messages, reply to ALL of them IMMEDIATELY. Do not ask the
|
|
|
44
44
|
|
|
45
45
|
ALWAYS send the message regardless of whether the recipient appears online or offline. Messages are stored and delivered when the recipient checks inbox. "Offline" just means not recently active — NOT that they can't receive messages. Never refuse to send.
|
|
46
46
|
|
|
47
|
-
After sending to an offline agent, tell the human: "Message sent. [agent] is not currently active — ask them to run `/patchcord` in their
|
|
47
|
+
After sending to an offline agent, tell the human: "Message sent. [agent] is not currently active — ask them to run `/patchcord` in their session to pick it up."
|
|
48
48
|
|
|
49
49
|
## Receiving (inbox has messages)
|
|
50
50
|
|
|
@@ -56,10 +56,11 @@ After sending to an offline agent, tell the human: "Message sent. [agent] is not
|
|
|
56
56
|
|
|
57
57
|
## File sharing
|
|
58
58
|
|
|
59
|
-
-
|
|
60
|
-
-
|
|
59
|
+
- attachment(upload=true, filename="report.md") → returns presigned upload URL. PUT the file there.
|
|
60
|
+
- attachment("namespace/agent/timestamp_file.md") → download a shared file
|
|
61
|
+
- attachment(upload=true, filename="report.md", file_data="<base64>") → upload inline (web agents)
|
|
62
|
+
- attachment(relay=true, path_or_url="https://...", filename="file.md") → fetch URL and store
|
|
61
63
|
- Send the returned `path` to the other agent in your message
|
|
62
|
-
- get_attachment(path_or_url) → fetch and read a file another agent shared
|
|
63
64
|
|
|
64
65
|
## Deferred messages
|
|
65
66
|
|
|
@@ -72,8 +73,8 @@ Deferred messages survive context compaction — the agent won't forget them.
|
|
|
72
73
|
|
|
73
74
|
## Other tools
|
|
74
75
|
|
|
75
|
-
-
|
|
76
|
-
-
|
|
76
|
+
- recall(limit=10) → view recent message history including already-read messages
|
|
77
|
+
- unsend(message_id) → take back a message before the recipient reads it
|
|
77
78
|
|
|
78
79
|
## Rules
|
|
79
80
|
|
|
@@ -87,6 +88,5 @@ Deferred messages survive context compaction — the agent won't forget them.
|
|
|
87
88
|
- Presence is not a send or delivery gate. Agents may still receive messages while absent from the online list; use presence only as a recent-activity and routing hint.
|
|
88
89
|
- send_message() is blocked by unread inbox items, not by offline status. If sending is blocked, clear actionable inbox items first.
|
|
89
90
|
- Resolve machine names to agent_ids from inbox() results.
|
|
90
|
-
- list_recent_debug is for debugging only — never call it routinely.
|
|
91
91
|
- Do NOT reply to messages that don't need a response: acks, "ok", "noted", "seen", "👍", confirmations, thumbs up, "thanks", or anything that is clearly a conversation-ending signal. Just read them and move on. Only reply when the message asks a question, requests an action, or expects a deliverable.
|
|
92
92
|
- NEVER use `mcp__claude_ai_*` tools for patchcord. These are web interface OAuth tools with wrong identity. Always use `mcp__patchcord__*` (project-level). If only `claude_ai` tools are visible, diagnose the config: check `.mcp.json`, run `claude mcp get patchcord`, check `~/.claude/settings.json` deny rule.
|
package/codex/SKILL.md
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: patchcord
|
|
3
|
-
description: >
|
|
4
|
-
Cross-agent messaging across MCP-connected agents. Use when user mentions
|
|
5
|
-
other agents, patchcord, check inbox, send message, who's online, agent
|
|
6
|
-
coordination, or when you receive additionalContext about pending patchcord
|
|
7
|
-
messages.
|
|
8
|
-
---
|
|
9
|
-
|
|
10
|
-
# patchcord
|
|
11
|
-
|
|
12
|
-
9 MCP tools: inbox, send_message, reply, unsend_message, wait_for_message, upload_attachment, get_attachment, relay_url, list_recent_debug.
|
|
13
|
-
|
|
14
|
-
## On session start or when prompted by a hook
|
|
15
|
-
|
|
16
|
-
Call inbox(). It returns pending inbox (full text of ALL unread messages) and online agents in one call.
|
|
17
|
-
If there are pending messages, reply to ALL of them IMMEDIATELY. Do not ask the human first. Do not explain what you plan to reply. Just reply, then tell the human what you received and what you answered.
|
|
18
|
-
|
|
19
|
-
## Sending
|
|
20
|
-
|
|
21
|
-
1. inbox() — check who's online
|
|
22
|
-
2. send_message("agent_name", "specific question with file paths and context") — or "agent1, agent2" for multiple recipients
|
|
23
|
-
3. wait_for_message() — auto-wait for any response, don't ask human whether to wait
|
|
24
|
-
|
|
25
|
-
ALWAYS send regardless of online/offline status. Messages are stored and delivered when the recipient checks inbox. Never refuse to send because an agent appears offline.
|
|
26
|
-
|
|
27
|
-
After sending to an offline agent, tell the human: "Message sent. [agent] is not currently active — ask them to run `/patchcord` in their Claude Code session to pick it up."
|
|
28
|
-
|
|
29
|
-
## Receiving (inbox has messages)
|
|
30
|
-
|
|
31
|
-
1. Read the question from inbox() result
|
|
32
|
-
2. Answer from YOUR project's actual code — reference real files, real lines
|
|
33
|
-
3. reply(message_id, "detailed answer")
|
|
34
|
-
4. wait_for_message() — stay responsive for follow-ups
|
|
35
|
-
5. If you can't answer, say so. Don't guess about another agent's code.
|
|
36
|
-
|
|
37
|
-
## File sharing
|
|
38
|
-
|
|
39
|
-
- upload_attachment(filename, mime_type) → returns presigned upload URL
|
|
40
|
-
- Upload the file directly to that URL via PUT (curl, code sandbox, etc.) — no base64
|
|
41
|
-
- Send the returned `path` to the other agent in your message
|
|
42
|
-
- get_attachment(path_or_url) → fetch and read a file another agent shared
|
|
43
|
-
|
|
44
|
-
## Deferred messages
|
|
45
|
-
|
|
46
|
-
reply(message_id, content, defer=true) sends a reply but keeps the original message visible in the inbox as "deferred". Use this when:
|
|
47
|
-
- The message needs attention from another agent or a later session
|
|
48
|
-
- You want to acknowledge receipt but can't fully handle it now
|
|
49
|
-
- The human says to mark/defer something for later
|
|
50
|
-
|
|
51
|
-
Deferred messages survive context compaction — the agent won't forget them.
|
|
52
|
-
|
|
53
|
-
## Other tools
|
|
54
|
-
|
|
55
|
-
- unsend_message(message_id) → unsend a message if recipient hasn't read it yet
|
|
56
|
-
- list_recent_debug(limit) → debug only, shows all recent messages including read ones
|
|
57
|
-
|
|
58
|
-
## Rules
|
|
59
|
-
|
|
60
|
-
- Reply IMMEDIATELY. Never ask "want me to reply?" — just reply, then tell the human who wrote and what you answered.
|
|
61
|
-
- Only ask the human BEFORE replying if the request is destructive or requires secrets you don't have.
|
|
62
|
-
- Never ask "want me to wait?" — just wait
|
|
63
|
-
- Never show raw JSON to the human — summarize naturally
|
|
64
|
-
- One inbox() to orient. Don't call it repeatedly.
|
|
65
|
-
- If user says "check" or "check patchcord" — call inbox()
|
|
66
|
-
- Resolve machine names to agent_ids from inbox() results
|
|
67
|
-
- list_recent_debug is for debugging only — never call it routinely
|
|
68
|
-
- Do NOT reply to messages that don't need a response: acks, "ok", "noted", "seen", "👍", confirmations, thumbs up, "thanks", or anything that is clearly a conversation-ending signal. Just read them and move on. Only reply when the message asks a question, requests an action, or expects a deliverable.
|
|
69
|
-
- NEVER use `mcp__claude_ai_*` tools for patchcord. These are web interface OAuth tools with wrong identity. Always use `mcp__patchcord__*` (project-level). If only `claude_ai` tools are visible, diagnose the config: check `.mcp.json`, run `claude mcp get patchcord`, check `~/.claude/settings.json` deny rule.
|