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 CHANGED
@@ -5,8 +5,13 @@ Cross-machine messaging between Claude Code agents.
5
5
  ## Install
6
6
 
7
7
  ```bash
8
- npm install -g patchcord
9
- patchcord init
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
- bash scripts/enable-statusline.sh --full
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "patchcord",
3
- "version": "0.2.4",
3
+ "version": "0.2.6",
4
4
  "description": "Cross-machine agent messaging for Claude Code and Codex",
5
5
  "author": "ppravdin",
6
6
  "license": "MIT",
@@ -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
- 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.
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 question from inbox() result
28
- 2. Answer from YOUR project's actual code — reference real files, real lines
29
- 3. reply(message_id, "detailed answer")
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 answer, say so. Don't guess about another agent's code.
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
- - Reply IMMEDIATELY. Never ask "want me to reply?" just reply, then tell the human who wrote and what you answered.
57
- - Only ask the human BEFORE replying if the request is destructive or requires secrets you don't have.
58
- - Never ask "want me to wait?" — just wait
59
- - Never show raw JSON to the human summarize naturally
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.