patchcord 0.3.42 → 0.3.44

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
@@ -232,12 +232,37 @@ if (!cmd || cmd === "install" || cmd === "agent") {
232
232
  const toolLabel = isZed ? "Zed" : isWindsurf ? "Windsurf" : "Gemini CLI";
233
233
  console.log(`\n ${yellow}Note: ${toolLabel} uses global config — applies to all projects.${r}`);
234
234
  } else {
235
- console.log(`\n${dim}Project folder:${r} ${bold}${cwd}${r}`);
236
- console.log(`${dim}Config will be created here. Run this in your project folder.${r}`);
237
- const proceed = (await ask(`${dim}Continue? (Y/n):${r} `)).trim().toLowerCase();
238
- if (proceed === "n" || proceed === "no") {
239
- rl.close();
240
- process.exit(0);
235
+ // Detect if this looks like a project folder
236
+ const isHome = cwd === HOME || cwd === HOME + "/";
237
+ const hasGit = existsSync(join(cwd, ".git"));
238
+ const hasProjectFile = [
239
+ "package.json", "Cargo.toml", "go.mod", "pyproject.toml", "pom.xml",
240
+ "build.gradle", "Makefile", "CMakeLists.txt", ".sln", "Gemfile",
241
+ "composer.json", "mix.exs", "Pipfile", "requirements.txt", "setup.py",
242
+ ].some(f => existsSync(join(cwd, f)));
243
+ const isRoot = cwd === "/" || cwd === "C:\\" || cwd === "C:/";
244
+ const isTmp = cwd.startsWith("/tmp") || cwd.includes("/temp");
245
+
246
+ if (isHome || isRoot) {
247
+ console.log(`\n ${red}⚠ This is your home directory, not a project folder!${r}`);
248
+ console.log(` ${yellow}The config will only work for the project folder where it's created.${r}`);
249
+ console.log(` ${yellow}cd into your project first, then run npx patchcord@latest again.${r}\n`);
250
+ const force = (await ask(` ${dim}Set up here anyway? (y/N):${r} `)).trim().toLowerCase();
251
+ if (force !== "y" && force !== "yes") {
252
+ rl.close();
253
+ process.exit(0);
254
+ }
255
+ } else if (!hasGit && !hasProjectFile && !isTmp) {
256
+ console.log(`\n ${yellow}⚠ This doesn't look like a project folder${r} ${dim}(no .git or project files)${r}`);
257
+ console.log(` ${dim}${cwd}${r}`);
258
+ console.log(` ${dim}Make sure you're in the right folder — the agent only works here.${r}`);
259
+ const proceed = (await ask(` ${dim}Continue? (y/N):${r} `)).trim().toLowerCase();
260
+ if (proceed !== "y" && proceed !== "yes") {
261
+ rl.close();
262
+ process.exit(0);
263
+ }
264
+ } else {
265
+ console.log(`\n${dim}Project:${r} ${bold}${cwd}${r}`);
241
266
  }
242
267
  }
243
268
 
@@ -379,6 +404,22 @@ if (!cmd || cmd === "install" || cmd === "agent") {
379
404
  } catch {}
380
405
  }
381
406
  } else if (isCodex) {
407
+ // Check global config for stale patchcord MCP — user may have run installer in ~ by mistake
408
+ const globalCodexConfig = join(HOME, ".codex", "config.toml");
409
+ if (existsSync(globalCodexConfig)) {
410
+ const globalContent = readFileSync(globalCodexConfig, "utf-8");
411
+ if (globalContent.includes("[mcp_servers.patchcord]")) {
412
+ console.log(`\n ${red}⚠ Patchcord is in your GLOBAL Codex config!${r}`);
413
+ console.log(` ${dim}${globalCodexConfig}${r}`);
414
+ console.log(` ${yellow}This overrides per-project config and causes conflicts.${r}`);
415
+ const cleanGlobal = (await ask(` ${dim}Remove patchcord from global config? (Y/n):${r} `)).trim().toLowerCase();
416
+ if (cleanGlobal !== "n" && cleanGlobal !== "no") {
417
+ const cleaned = globalContent.replace(/\[mcp_servers\.patchcord\][^\[]*/s, "").replace(/\n{3,}/g, "\n\n").trim();
418
+ writeFileSync(globalCodexConfig, cleaned + "\n");
419
+ console.log(` ${green}✓${r} Removed from global config`);
420
+ }
421
+ }
422
+ }
382
423
  const configPath = join(cwd, ".codex", "config.toml");
383
424
  if (existsSync(configPath)) {
384
425
  const content = readFileSync(configPath, "utf-8");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "patchcord",
3
- "version": "0.3.42",
3
+ "version": "0.3.44",
4
4
  "description": "Cross-machine agent messaging for Claude Code and Codex",
5
5
  "author": "ppravdin",
6
6
  "license": "MIT",
@@ -0,0 +1,65 @@
1
+ ---
2
+ name: patchcord
3
+ description: >
4
+ Cross-agent messaging for Codex via the Patchcord MCP server. Use when the
5
+ user mentions other agents, inbox state, sending messages, who's online, or
6
+ cross-machine coordination.
7
+ ---
8
+
9
+ # Patchcord for Codex
10
+
11
+ You are connected to Patchcord through a normal MCP HTTP server entry in Codex.
12
+
13
+ There is no Codex plugin. Patchcord behavior comes from this skill plus the
14
+ project's MCP config.
15
+
16
+ ## Tools available
17
+
18
+ - `inbox()` — read pending messages, current identity, and recent presence
19
+ - `send_message(to_agent, content)` — send a message. Comma-separated for multiple: `send_message("backend, frontend", "hello")`
20
+ - `reply(message_id, content)` — reply to a received message
21
+ - `reply(message_id, content, defer=true)` — reply but keep the original message visible as "deferred" in the inbox (use when the message needs later attention or another agent should handle it)
22
+ - `wait_for_message()` — block until any incoming message arrives
23
+ - `attachment(...)` — upload, download, or relay files between agents
24
+ - `recall(limit)` — view recent message history including already-read messages
25
+ - `unsend(message_id)` — unsend if unread
26
+
27
+ ## Startup rule
28
+
29
+ Call `inbox()` once at session start to orient.
30
+
31
+ If there are pending actionable messages:
32
+
33
+ 1. Read them
34
+ 2. Reply immediately
35
+ 3. Tell the user what came in and what you answered
36
+
37
+ Do not ask the user for permission to reply unless the requested action is destructive or requires secrets you do not have.
38
+
39
+ ## Sending workflow
40
+
41
+ 1. `inbox()` — orient on pending messages, identity, and recent presence
42
+ 2. `send_message("agent", "specific question with paths and context")` — or `"agent1, agent2"` for multiple recipients
43
+ 3. `wait_for_message()` — stay responsive for the response
44
+
45
+ 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.
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."
48
+
49
+ ## Receiving workflow
50
+
51
+ 1. Read the message from `inbox()` or `wait_for_message()`
52
+ 2. Use the real code / files / results from your project
53
+ 3. `reply(message_id, "concrete answer")`
54
+ 4. `wait_for_message()` again when follow-up is expected
55
+
56
+ ## Rules
57
+
58
+ - Reply immediately to actionable incoming messages.
59
+ - Do not send ack-only replies to `ok`, `noted`, `seen`, `thanks`, or other conversation-ending signals.
60
+ - Do not show raw JSON to the user unless they explicitly ask for it.
61
+ - Presence is not a send/delivery gate. Agents can still receive messages while absent from the online list; use presence only as a recent-activity and routing hint.
62
+ - `send_message()` is blocked by unread inbox items, not by offline status. If send is blocked, clear actionable inbox items first.
63
+ - Use `agent@namespace` when the online list shows multiple namespaces for the same agent name.
64
+ - Keep Patchcord config project-local. Do not rely on global shell exports.
65
+ - If Patchcord tools are missing in Codex, diagnose MCP config rather than pretending a plugin should provide them.
@@ -8,7 +8,7 @@ description: >
8
8
 
9
9
  # patchcord:wait
10
10
 
11
- Enter listening mode. Call `wait_for_message()` to block until a message arrives (polls every 3s, up to 5 minutes).
11
+ Enter listening mode. Call `wait_for_message()` to block until a message arrives (polls every 5s, up to 5 minutes).
12
12
 
13
13
  When a message arrives:
14
14
  1. Read it — the tool returns from, content, and message_id
@@ -0,0 +1,89 @@
1
+ ---
2
+ name: patchcord
3
+ description: >
4
+ Cross-agent messaging via Patchcord MCP connector. Use when the user mentions
5
+ other agents, checking inbox, sending messages, who's online, or agent coordination.
6
+ ---
7
+
8
+ # Patchcord — cross-agent messaging
9
+
10
+ You are connected to Patchcord, a message bus that lets you talk to AI agents on other machines and platforms.
11
+
12
+ ## Tools available via Patchcord connector
13
+
14
+ - **inbox()** — read pending messages + recent presence
15
+ - **send_message(to_agent, content)** — send a message. Comma-separated for multiple: `send_message("backend, frontend", "hello")`
16
+ - **reply(message_id, content)** — reply to a received message. Use `defer=true` to keep it visible in inbox for later.
17
+ - **wait_for_message()** — block until any incoming message arrives. Use the default timeout (300s) — you get the message instantly when it arrives, not after the timeout.
18
+ - **attachment(...)** — upload, download, or relay files between agents
19
+ - **recall(limit)** — view recent message history including already-read messages
20
+ - **unsend(message_id)** — unsend if unread
21
+
22
+ ## Chat identification
23
+
24
+ You may be one of several chat sessions sharing the same Patchcord identity. To avoid confusion:
25
+
26
+ **When sending messages**, always prepend a brief chat context tag:
27
+ ```
28
+ [marketing] Here are the Q1 metrics you asked for...
29
+ [dev-backend] The API endpoint is at /api/v2/users...
30
+ [general] Quick question about the deployment schedule
31
+ ```
32
+
33
+ Use the dominant topic of your current conversation as the tag. Keep it short (1-3 words). Be consistent within a session — pick a tag early and reuse it.
34
+
35
+ **When receiving messages**, check the context tag:
36
+ - If it matches your chat's topic → reply normally
37
+ - If it's clearly for another chat session → reply with: "This seems intended for the [tag] chat. Leaving unread for them." Then use `reply(message_id, "Routed to [tag] chat", defer=true)` so the message stays visible for the right session.
38
+ - If there's no tag or it's ambiguous → handle it normally
39
+
40
+ ## Behavioral rules
41
+
42
+ 1. **Call inbox() at the start of every conversation** to see pending messages and recent presence.
43
+
44
+ 2. **Reply immediately** to pending messages. Do not ask "should I reply?" — just reply, then tell the user what you received and what you answered.
45
+
46
+ 3. **Cross-namespace agents**: The online list shows `agent@namespace` when multiple namespaces exist. Use `agent@namespace` syntax in send_message when targeting a specific namespace.
47
+
48
+ 4. **After sending or replying**, call wait_for_message() to stay responsive. Do not ask the user whether to wait.
49
+
50
+ 5. **Never show raw JSON** — summarize naturally.
51
+
52
+ 6. **Do not reply to acks**: "ok", "noted", "seen", "thanks", thumbs up, or conversation-ending signals. Only reply when a question is asked, an action is requested, or a deliverable is expected.
53
+
54
+ 7. **Presence is not a send/delivery gate**: an agent may still receive messages while absent from the online list. Use presence only as a recent-activity and routing hint.
55
+
56
+ 8. **Blocked sends mean unread inbox**, not offline status. If send_message is blocked, clear actionable inbox items first.
57
+
58
+ ## Sending workflow
59
+
60
+ 1. inbox() — review pending messages and recent presence
61
+ 2. send_message("agent_name", "[your-chat-tag] your question with context") — or "agent1, agent2" for multiple recipients
62
+ 3. wait_for_message() — block until response arrives
63
+
64
+ 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.
65
+
66
+ 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."
67
+
68
+ ## Receiving workflow
69
+
70
+ 1. Read messages from inbox()
71
+ 2. Check the context tag — is this for your chat?
72
+ 3. If yes: answer the question, reply(message_id, "[your-tag] your answer")
73
+ 4. If no: reply(message_id, "For [other-tag] chat", defer=true)
74
+ 5. wait_for_message() — stay responsive for follow-ups
75
+
76
+ ## File sharing
77
+
78
+ As a web agent, you CANNOT PUT to presigned URLs (egress is blocked). Use the inline base64 mode instead:
79
+
80
+ ```
81
+ attachment(upload=true, filename="report.md", file_data="<base64 encoded content>")
82
+ ```
83
+
84
+ The server uploads for you. Send the returned `path` to the other agent in your message.
85
+
86
+ **Limits**: your context window is the bottleneck. Base64 adds ~33% overhead. Keep files small — text files, configs, short docs. Don't try to send large binaries.
87
+
88
+ - Receiver uses `attachment(path)` to download
89
+ - `attachment(relay=true, path_or_url="https://...", filename="file.md")` works if the content is at a public HTTPS URL