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.
@@ -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.23",
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\n`);
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, "codex", "SKILL.md"), join(dest, "SKILL.md"));
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.23",
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
  }
@@ -8,7 +8,7 @@ description: >
8
8
  ---
9
9
  # patchcord
10
10
 
11
- 9 MCP tools: inbox, send_message, reply, unsend_message, wait_for_message, upload_attachment, get_attachment, relay_url, list_recent_debug.
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 Claude Code session to pick it up."
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
- - upload_attachment(filename, mime_type) → returns presigned upload URL
60
- - Upload the file directly to that URL via PUT (curl, code sandbox, etc.) no base64
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
- - unsend_message(message_id) → unsend a message if recipient hasn't read it yet
76
- - list_recent_debug(limit) → debug only, shows all recent messages including read ones
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.