patchcord 0.2.4 → 0.2.6
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/README.md +8 -3
- package/bin/patchcord.mjs +128 -0
- package/codex/SKILL.md +1 -1
- package/package.json +1 -1
- package/skills/inbox/SKILL.md +33 -14
package/README.md
CHANGED
|
@@ -5,8 +5,13 @@ Cross-machine messaging between Claude Code agents.
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
npx patchcord@latest install
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or with full statusline (model, context%, git branch):
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npx patchcord@latest install --full
|
|
10
15
|
```
|
|
11
16
|
|
|
12
17
|
The plugin provides skills, statusline integration, and turn-end inbox hooks. The actual Patchcord connection comes from the project's `.mcp.json`.
|
|
@@ -73,7 +78,7 @@ By default the statusline shows only Patchcord identity and inbox count. In non-
|
|
|
73
78
|
To also show model, context usage, repo, and git branch:
|
|
74
79
|
|
|
75
80
|
```bash
|
|
76
|
-
|
|
81
|
+
npx patchcord@latest install --full
|
|
77
82
|
```
|
|
78
83
|
|
|
79
84
|
Without `--full`:
|
package/bin/patchcord.mjs
CHANGED
|
@@ -25,6 +25,8 @@ Commands:
|
|
|
25
25
|
patchcord install --full Install + enable full statusline (model, context%, git)
|
|
26
26
|
patchcord agent Set up MCP config for an agent in this project
|
|
27
27
|
patchcord agent --codex Set up Codex skill + MCP config in this project
|
|
28
|
+
patchcord skill apply Fetch and apply custom skill from the web console
|
|
29
|
+
patchcord skill reinstall Full rewrite: default skill + custom skill from server
|
|
28
30
|
|
|
29
31
|
Run "patchcord install" once. Run "patchcord agent" in each project.`);
|
|
30
32
|
process.exit(0);
|
|
@@ -155,5 +157,131 @@ if (cmd === "init") {
|
|
|
155
157
|
process.exit(0);
|
|
156
158
|
}
|
|
157
159
|
|
|
160
|
+
// ── skill: custom skill management ───────────────────────────
|
|
161
|
+
if (cmd === "skill") {
|
|
162
|
+
const sub = process.argv[3];
|
|
163
|
+
const cwd = process.cwd();
|
|
164
|
+
|
|
165
|
+
// Find .mcp.json to get URL and token
|
|
166
|
+
let mcpJson = null;
|
|
167
|
+
let dir = cwd;
|
|
168
|
+
while (dir && dir !== "/") {
|
|
169
|
+
const p = join(dir, ".mcp.json");
|
|
170
|
+
if (existsSync(p)) { mcpJson = p; break; }
|
|
171
|
+
dir = dirname(dir);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (!mcpJson) {
|
|
175
|
+
console.error("No .mcp.json found. Run 'patchcord agent' first.");
|
|
176
|
+
process.exit(1);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const { readFileSync, writeFileSync } = await import("fs");
|
|
180
|
+
const config = JSON.parse(readFileSync(mcpJson, "utf-8"));
|
|
181
|
+
const mcpUrl = config?.mcpServers?.patchcord?.url || "";
|
|
182
|
+
const auth = config?.mcpServers?.patchcord?.headers?.Authorization || "";
|
|
183
|
+
const baseUrl = mcpUrl.replace(/\/mcp(\/bearer)?$/, "");
|
|
184
|
+
const token = auth.replace(/^Bearer\s+/, "");
|
|
185
|
+
|
|
186
|
+
if (!baseUrl || !token) {
|
|
187
|
+
console.error("Cannot read patchcord URL/token from .mcp.json");
|
|
188
|
+
process.exit(1);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Derive namespace and agent from the token by calling /api/inbox
|
|
192
|
+
let namespace = "", agentId = "";
|
|
193
|
+
try {
|
|
194
|
+
const resp = run(`curl -s -H "Authorization: Bearer ${token}" "${baseUrl}/api/inbox?limit=0"`);
|
|
195
|
+
if (resp) {
|
|
196
|
+
const data = JSON.parse(resp);
|
|
197
|
+
namespace = data.namespace_id || "";
|
|
198
|
+
agentId = data.agent_id || "";
|
|
199
|
+
}
|
|
200
|
+
} catch {}
|
|
201
|
+
|
|
202
|
+
if (!namespace || !agentId) {
|
|
203
|
+
console.error("Cannot determine agent identity. Check your token.");
|
|
204
|
+
process.exit(1);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const START_DELIM = "########## PATCHCORD CUSTOM SKILL ##########";
|
|
208
|
+
const END_DELIM = "########## END CUSTOM SKILL ##########";
|
|
209
|
+
|
|
210
|
+
// Find the skill file
|
|
211
|
+
const skillFile = join(cwd, "PATCHCORD.md");
|
|
212
|
+
const pluginSkill = join(pluginRoot, "skills", "inbox", "SKILL.md");
|
|
213
|
+
|
|
214
|
+
function applyCustomSkill(skillText) {
|
|
215
|
+
let content = "";
|
|
216
|
+
if (existsSync(skillFile)) {
|
|
217
|
+
content = readFileSync(skillFile, "utf-8");
|
|
218
|
+
} else if (existsSync(pluginSkill)) {
|
|
219
|
+
content = readFileSync(pluginSkill, "utf-8");
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Remove existing custom skill block
|
|
223
|
+
const startIdx = content.indexOf(START_DELIM);
|
|
224
|
+
const endIdx = content.indexOf(END_DELIM);
|
|
225
|
+
if (startIdx !== -1 && endIdx !== -1) {
|
|
226
|
+
content = content.substring(0, startIdx).trimEnd();
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Append new custom skill
|
|
230
|
+
if (skillText && skillText.trim()) {
|
|
231
|
+
content = content.trimEnd() + "\n\n" + START_DELIM + "\n" + skillText.trim() + "\n" + END_DELIM + "\n";
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
writeFileSync(skillFile, content);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (sub === "apply" || !sub) {
|
|
238
|
+
// Fetch and apply custom skill
|
|
239
|
+
console.log(`Fetching custom skill for ${namespace}:${agentId}...`);
|
|
240
|
+
const resp = run(`curl -s -H "Authorization: Bearer ${token}" "${baseUrl}/api/skills/${namespace}/${agentId}"`);
|
|
241
|
+
if (!resp) {
|
|
242
|
+
console.log("No custom skill found or server unreachable.");
|
|
243
|
+
process.exit(0);
|
|
244
|
+
}
|
|
245
|
+
try {
|
|
246
|
+
const data = JSON.parse(resp);
|
|
247
|
+
if (data.skill_text) {
|
|
248
|
+
applyCustomSkill(data.skill_text);
|
|
249
|
+
console.log(`✓ Custom skill applied to ${skillFile}`);
|
|
250
|
+
} else {
|
|
251
|
+
console.log("No custom skill set for this agent.");
|
|
252
|
+
}
|
|
253
|
+
} catch {
|
|
254
|
+
console.error("Failed to parse skill response.");
|
|
255
|
+
process.exit(1);
|
|
256
|
+
}
|
|
257
|
+
} else if (sub === "reinstall") {
|
|
258
|
+
// Full rewrite: default skill + custom skill
|
|
259
|
+
console.log("Reinstalling patchcord skill...");
|
|
260
|
+
if (existsSync(pluginSkill)) {
|
|
261
|
+
let defaultContent = readFileSync(pluginSkill, "utf-8");
|
|
262
|
+
writeFileSync(skillFile, defaultContent);
|
|
263
|
+
}
|
|
264
|
+
// Then apply custom on top
|
|
265
|
+
const resp = run(`curl -s -H "Authorization: Bearer ${token}" "${baseUrl}/api/skills/${namespace}/${agentId}"`);
|
|
266
|
+
try {
|
|
267
|
+
const data = JSON.parse(resp || "{}");
|
|
268
|
+
if (data.skill_text) {
|
|
269
|
+
applyCustomSkill(data.skill_text);
|
|
270
|
+
console.log(`✓ Skill reinstalled with custom block at ${skillFile}`);
|
|
271
|
+
} else {
|
|
272
|
+
console.log(`✓ Skill reinstalled (no custom block) at ${skillFile}`);
|
|
273
|
+
}
|
|
274
|
+
} catch {
|
|
275
|
+
console.log(`✓ Skill reinstalled (no custom block) at ${skillFile}`);
|
|
276
|
+
}
|
|
277
|
+
} else {
|
|
278
|
+
console.log(`Unknown skill subcommand: ${sub}
|
|
279
|
+
Usage:
|
|
280
|
+
patchcord skill apply Fetch and apply custom skill from server
|
|
281
|
+
patchcord skill reinstall Full rewrite: default + custom skill`);
|
|
282
|
+
}
|
|
283
|
+
process.exit(0);
|
|
284
|
+
}
|
|
285
|
+
|
|
158
286
|
console.error(`Unknown command: ${cmd}. Run 'patchcord help' for usage.`);
|
|
159
287
|
process.exit(1);
|
package/codex/SKILL.md
CHANGED
|
@@ -19,7 +19,7 @@ If there are pending messages, reply to ALL of them IMMEDIATELY. Do not ask the
|
|
|
19
19
|
## Sending
|
|
20
20
|
|
|
21
21
|
1. inbox() — check who's online
|
|
22
|
-
2. send_message("agent_name", "specific question with file paths and context")
|
|
22
|
+
2. send_message("agent_name", "specific question with file paths and context") — or "agent1, agent2" for multiple recipients
|
|
23
23
|
3. wait_for_message() — auto-wait for any response, don't ask human whether to wait
|
|
24
24
|
|
|
25
25
|
## Receiving (inbox has messages)
|
package/package.json
CHANGED
package/skills/inbox/SKILL.md
CHANGED
|
@@ -6,29 +6,47 @@ description: >
|
|
|
6
6
|
coordination, or when you receive additionalContext about pending patchcord
|
|
7
7
|
messages.
|
|
8
8
|
---
|
|
9
|
-
|
|
10
9
|
# patchcord
|
|
11
10
|
|
|
12
11
|
9 MCP tools: inbox, send_message, reply, unsend_message, wait_for_message, upload_attachment, get_attachment, relay_url, list_recent_debug.
|
|
13
12
|
|
|
13
|
+
## CRITICAL: DO THE WORK, NEVER JUST ACKNOWLEDGE
|
|
14
|
+
|
|
15
|
+
When you receive a message from another agent:
|
|
16
|
+
|
|
17
|
+
1. **DO THE TASK described in the message FIRST.** Update the file. Write the code. Fix the bug. Create the document. Whatever the message asks — DO IT.
|
|
18
|
+
2. **THEN reply with what you DID.** Not what you plan to do. Not that you received it. What you ACTUALLY DID. File paths, line numbers, concrete changes.
|
|
19
|
+
3. **NEVER reply with only an acknowledgment.** "Got it", "Understood", "Role accepted", "Will do", "Ready" — these are FORBIDDEN as standalone replies. If your reply doesn't describe completed work, you failed.
|
|
20
|
+
|
|
21
|
+
The user can undo any change in 3 seconds with git. A wrong action costs nothing. A useless ack wastes everyone's time and breaks the workflow.
|
|
22
|
+
|
|
23
|
+
**If a message contains a spec, update, or instruction → ACT ON IT IMMEDIATELY:**
|
|
24
|
+
- Spec received → update the relevant docs/code NOW, reply with what you changed
|
|
25
|
+
- Bug report received → investigate and fix NOW, reply with the fix
|
|
26
|
+
- Architecture decision received → update the relevant files NOW, reply with what you updated
|
|
27
|
+
- Role assignment received → start doing that role NOW, reply with first actions taken
|
|
28
|
+
|
|
29
|
+
**If you genuinely cannot act** (missing file access, need credentials, ambiguous target): say SPECIFICALLY what's blocking you. "I need the path to the docs folder" — not "Understood, I'll do it when ready."
|
|
30
|
+
|
|
14
31
|
## On session start or when prompted by a hook
|
|
15
32
|
|
|
16
33
|
Call inbox(). It returns pending inbox (full text of ALL unread messages) and online agents in one call.
|
|
17
|
-
|
|
34
|
+
|
|
35
|
+
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 DO THE WORK described in each message, then reply with what you did, then tell the human what you received and what you did about it.
|
|
18
36
|
|
|
19
37
|
## Sending
|
|
20
38
|
|
|
21
39
|
1. inbox() — read pending mail and recent presence for routing
|
|
22
|
-
2. send_message("agent_name", "specific question with file paths and context")
|
|
40
|
+
2. send_message("agent_name", "specific question with file paths and context") — or "agent1, agent2" for multiple recipients
|
|
23
41
|
3. wait_for_message() — auto-wait for any response, don't ask human whether to wait
|
|
24
42
|
|
|
25
43
|
## Receiving (inbox has messages)
|
|
26
44
|
|
|
27
|
-
1. Read the
|
|
28
|
-
2.
|
|
29
|
-
3. reply(message_id, "
|
|
45
|
+
1. Read the message
|
|
46
|
+
2. DO THE WORK described in the message — using YOUR project's actual code, real files, real lines
|
|
47
|
+
3. reply(message_id, "here's what I did: [concrete changes with file paths]")
|
|
30
48
|
4. wait_for_message() — stay responsive for follow-ups
|
|
31
|
-
5. If you can't
|
|
49
|
+
5. If you can't do the work, say specifically what's blocking you. Don't guess about another agent's code.
|
|
32
50
|
|
|
33
51
|
## File sharing
|
|
34
52
|
|
|
@@ -53,15 +71,16 @@ Deferred messages survive context compaction — the agent won't forget them.
|
|
|
53
71
|
|
|
54
72
|
## Rules
|
|
55
73
|
|
|
56
|
-
-
|
|
57
|
-
-
|
|
58
|
-
- Never ask "
|
|
59
|
-
- Never
|
|
74
|
+
- DO THE WORK FIRST, REPLY SECOND. Never reply before completing the task.
|
|
75
|
+
- Never ask "want me to reply?" — just do the work and reply with results.
|
|
76
|
+
- Never ask "should I do this?" — just do it. User can undo in 3 seconds.
|
|
77
|
+
- Never ask "want me to wait?" — just wait.
|
|
78
|
+
- Never show raw JSON to the human — summarize naturally.
|
|
60
79
|
- One inbox() to orient. Don't call it repeatedly.
|
|
61
|
-
- If user says "check" or "check patchcord" — call inbox()
|
|
80
|
+
- If user says "check" or "check patchcord" — call inbox().
|
|
62
81
|
- 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.
|
|
63
82
|
- send_message() is blocked by unread inbox items, not by offline status. If sending is blocked, clear actionable inbox items first.
|
|
64
|
-
- Resolve machine names to agent_ids from inbox() results
|
|
65
|
-
- list_recent_debug is for debugging only — never call it routinely
|
|
83
|
+
- Resolve machine names to agent_ids from inbox() results.
|
|
84
|
+
- list_recent_debug is for debugging only — never call it routinely.
|
|
66
85
|
- 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.
|
|
67
86
|
- 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.
|