patchcord 0.3.2 → 0.3.4

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.2",
4
+ "version": "0.3.4",
5
5
  "author": {
6
6
  "name": "ppravdin"
7
7
  },
package/bin/patchcord.mjs CHANGED
@@ -204,38 +204,13 @@ if (cmd === "skill") {
204
204
  process.exit(1);
205
205
  }
206
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
- }
207
+ // Custom skill goes to .claude/skills/patchcord-custom/SKILL.md
208
+ // Claude Code auto-discovers project-level skills from this directory.
209
+ // Only the custom part — default patchcord skill is already loaded globally by the plugin.
210
+ const skillDir = join(cwd, ".claude", "skills", "patchcord-custom");
211
+ const skillFile = join(skillDir, "SKILL.md");
236
212
 
237
213
  if (sub === "apply" || !sub) {
238
- // Fetch and apply custom skill
239
214
  console.log(`Fetching custom skill for ${namespace}:${agentId}...`);
240
215
  const resp = run(`curl -s -H "Authorization: Bearer ${token}" "${baseUrl}/api/skills/${namespace}/${agentId}"`);
241
216
  if (!resp) {
@@ -245,7 +220,8 @@ if (cmd === "skill") {
245
220
  try {
246
221
  const data = JSON.parse(resp);
247
222
  if (data.skill_text) {
248
- applyCustomSkill(data.skill_text);
223
+ mkdirSync(skillDir, { recursive: true });
224
+ writeFileSync(skillFile, data.skill_text.trim() + "\n");
249
225
  console.log(`✓ Custom skill applied to ${skillFile}`);
250
226
  } else {
251
227
  console.log("No custom skill set for this agent.");
@@ -255,30 +231,38 @@ if (cmd === "skill") {
255
231
  process.exit(1);
256
232
  }
257
233
  } 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
234
+ console.log(`Fetching custom skill for ${namespace}:${agentId}...`);
265
235
  const resp = run(`curl -s -H "Authorization: Bearer ${token}" "${baseUrl}/api/skills/${namespace}/${agentId}"`);
266
236
  try {
267
237
  const data = JSON.parse(resp || "{}");
268
238
  if (data.skill_text) {
269
- applyCustomSkill(data.skill_text);
270
- console.log(`✓ Skill reinstalled with custom block at ${skillFile}`);
239
+ mkdirSync(skillDir, { recursive: true });
240
+ writeFileSync(skillFile, data.skill_text.trim() + "\n");
241
+ console.log(`✓ Custom skill applied to ${skillFile}`);
271
242
  } else {
272
- console.log(`✓ Skill reinstalled (no custom block) at ${skillFile}`);
243
+ // Remove custom skill if none set
244
+ if (existsSync(skillFile)) {
245
+ const { unlinkSync } = await import("fs");
246
+ unlinkSync(skillFile);
247
+ console.log("Custom skill removed (none set on server).");
248
+ } else {
249
+ console.log("No custom skill set for this agent.");
250
+ }
273
251
  }
274
252
  } catch {
275
- console.log(`✓ Skill reinstalled (no custom block) at ${skillFile}`);
253
+ console.log("No custom skill set or server unreachable.");
276
254
  }
277
255
  } else {
278
256
  console.log(`Unknown skill subcommand: ${sub}
279
257
  Usage:
280
258
  patchcord skill apply Fetch and apply custom skill from server
281
- patchcord skill reinstall Full rewrite: default + custom skill`);
259
+ patchcord skill reinstall Re-fetch custom skill from server`);
260
+ }
261
+ // Clean up old PATCHCORD.md if it exists
262
+ const oldFile = join(cwd, "PATCHCORD.md");
263
+ if (existsSync(oldFile)) {
264
+ const { unlinkSync } = await import("fs");
265
+ unlinkSync(oldFile);
282
266
  }
283
267
  process.exit(0);
284
268
  }
package/codex/SKILL.md CHANGED
@@ -22,6 +22,10 @@ If there are pending messages, reply to ALL of them IMMEDIATELY. Do not ask the
22
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
+ 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
+
25
29
  ## Receiving (inbox has messages)
26
30
 
27
31
  1. Read the question from inbox() result
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "patchcord",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
4
4
  "description": "Cross-machine agent messaging for Claude Code and Codex",
5
5
  "author": "ppravdin",
6
6
  "license": "MIT",
@@ -32,11 +32,7 @@ if [ ! -f "$UPDATE_FLAG" ]; then
32
32
  if [ -n "$installed_ver" ]; then
33
33
  latest=$(npm view patchcord version --json 2>/dev/null | tr -d '"' || true)
34
34
  if [ -n "$latest" ] && [ "$latest" != "$installed_ver" ]; then
35
- jq -n --arg ver "$latest" '{
36
- "decision": "block",
37
- "reason": ("Patchcord plugin update available: v" + $ver + ". Run: npx patchcord@latest install")
38
- }'
39
- exit 0
35
+ echo "⬆ Patchcord plugin update: v${installed_ver} → v${latest}. Run: npx patchcord@latest install" >&2
40
36
  fi
41
37
  fi
42
38
  fi
@@ -66,7 +62,7 @@ MACHINE_NAME=$(hostname -s 2>/dev/null || echo "unknown")
66
62
  HTTP_CODE=$(curl -s -o /tmp/patchcord_inbox.json -w "%{http_code}" --max-time 5 \
67
63
  -H "Authorization: Bearer ${TOKEN}" \
68
64
  -H "x-patchcord-machine: ${MACHINE_NAME}" \
69
- "${URL}/api/inbox?status=pending&limit=1" 2>/dev/null || echo "000")
65
+ "${URL}/api/inbox?status=pending&limit=5&count_only=1" 2>/dev/null || echo "000")
70
66
 
71
67
  if [ "$HTTP_CODE" = "401" ] || [ "$HTTP_CODE" = "403" ]; then
72
68
  jq -n '{
@@ -87,8 +83,8 @@ RESPONSE=$(cat /tmp/patchcord_inbox.json 2>/dev/null || echo '{"count":0}')
87
83
  rm -f /tmp/patchcord_inbox.json
88
84
 
89
85
  # ── Auto-apply custom skill from web console ──────────────────
90
- # Fetch custom skill and update PATCHCORD.md if changed.
91
- # Runs silently alongside inbox check no user action needed.
86
+ # Writes to .claude/skills/patchcord-custom/SKILL.md Claude Code
87
+ # native project-level skill directory. Auto-discovered by Claude.
92
88
  NAMESPACE=$(echo "$RESPONSE" | jq -r '.namespace_id // empty' 2>/dev/null || true)
93
89
  AGENT_ID=$(echo "$RESPONSE" | jq -r '.agent_id // empty' 2>/dev/null || true)
94
90
 
@@ -104,21 +100,14 @@ if [ -n "$NAMESPACE" ] && [ -n "$AGENT_ID" ]; then
104
100
  OLD_HASH=$(cat "$CACHE_FILE" 2>/dev/null || echo "")
105
101
 
106
102
  if [ -n "$SKILL_TEXT" ] && [ "$SKILL_HASH" != "$OLD_HASH" ]; then
107
- # Find the skill file (project root PATCHCORD.md)
108
103
  PROJECT_ROOT=$(dirname "$MCP_JSON")
109
- SKILL_FILE="${PROJECT_ROOT}/PATCHCORD.md"
110
- START_DELIM="########## PATCHCORD CUSTOM SKILL ##########"
111
- END_DELIM="########## END CUSTOM SKILL ##########"
112
-
113
- if [ -f "$SKILL_FILE" ]; then
114
- # Remove existing custom block and append new one
115
- BEFORE=$(sed "/${START_DELIM}/,\$d" "$SKILL_FILE")
116
- printf '%s\n\n%s\n%s\n%s\n' "$BEFORE" "$START_DELIM" "$SKILL_TEXT" "$END_DELIM" > "$SKILL_FILE"
117
- else
118
- # Create with just the custom block
119
- printf '%s\n%s\n%s\n' "$START_DELIM" "$SKILL_TEXT" "$END_DELIM" > "$SKILL_FILE"
120
- fi
104
+ SKILL_DIR="${PROJECT_ROOT}/.claude/skills/patchcord-custom"
105
+ SKILL_FILE="${SKILL_DIR}/SKILL.md"
106
+ mkdir -p "$SKILL_DIR"
107
+ printf '%s\n' "$SKILL_TEXT" > "$SKILL_FILE"
121
108
  echo "$SKILL_HASH" > "$CACHE_FILE"
109
+ # Clean up old PATCHCORD.md if it exists
110
+ rm -f "${PROJECT_ROOT}/PATCHCORD.md"
122
111
  fi
123
112
  fi
124
113
  fi
@@ -28,6 +28,8 @@ The user can undo any change in 3 seconds with git. A wrong action costs nothing
28
28
 
29
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
30
 
31
+ **If you can't do it RIGHT NOW** (busy with something else, need to finish current task first): use `reply(message_id, "reason why deferred", defer=true)`. This keeps the message visible in your inbox so you WILL come back to it. NEVER silently skip a message — you WILL forget it. If you don't act and don't defer, the message is lost forever.
32
+
31
33
  ## On session start or when prompted by a hook
32
34
 
33
35
  Call inbox(). It returns pending inbox (full text of ALL unread messages) and online agents in one call.
@@ -40,6 +42,10 @@ If there are pending messages, reply to ALL of them IMMEDIATELY. Do not ask the
40
42
  2. send_message("agent_name", "specific question with file paths and context") — or "agent1, agent2" for multiple recipients
41
43
  3. wait_for_message() — auto-wait for any response, don't ask human whether to wait
42
44
 
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
+
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
+
43
49
  ## Receiving (inbox has messages)
44
50
 
45
51
  1. Read the message