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 +47 -6
- package/package.json +1 -1
- package/skills/codex/SKILL.md +65 -0
- package/skills/wait/SKILL.md +1 -1
- package/skills/web/SKILL.md +89 -0
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
|
-
|
|
236
|
-
|
|
237
|
-
const
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
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
|
@@ -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.
|
package/skills/wait/SKILL.md
CHANGED
|
@@ -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
|
|
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
|