patchcord 0.3.3 → 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.
- package/.claude-plugin/plugin.json +1 -1
- package/bin/patchcord.mjs +27 -43
- package/codex/SKILL.md +4 -0
- package/package.json +1 -1
- package/scripts/check-inbox.sh +9 -16
- package/skills/inbox/SKILL.md +6 -0
|
@@ -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.
|
|
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
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
const skillFile = join(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
270
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
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
package/scripts/check-inbox.sh
CHANGED
|
@@ -62,7 +62,7 @@ MACHINE_NAME=$(hostname -s 2>/dev/null || echo "unknown")
|
|
|
62
62
|
HTTP_CODE=$(curl -s -o /tmp/patchcord_inbox.json -w "%{http_code}" --max-time 5 \
|
|
63
63
|
-H "Authorization: Bearer ${TOKEN}" \
|
|
64
64
|
-H "x-patchcord-machine: ${MACHINE_NAME}" \
|
|
65
|
-
"${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")
|
|
66
66
|
|
|
67
67
|
if [ "$HTTP_CODE" = "401" ] || [ "$HTTP_CODE" = "403" ]; then
|
|
68
68
|
jq -n '{
|
|
@@ -83,8 +83,8 @@ RESPONSE=$(cat /tmp/patchcord_inbox.json 2>/dev/null || echo '{"count":0}')
|
|
|
83
83
|
rm -f /tmp/patchcord_inbox.json
|
|
84
84
|
|
|
85
85
|
# ── Auto-apply custom skill from web console ──────────────────
|
|
86
|
-
#
|
|
87
|
-
#
|
|
86
|
+
# Writes to .claude/skills/patchcord-custom/SKILL.md — Claude Code
|
|
87
|
+
# native project-level skill directory. Auto-discovered by Claude.
|
|
88
88
|
NAMESPACE=$(echo "$RESPONSE" | jq -r '.namespace_id // empty' 2>/dev/null || true)
|
|
89
89
|
AGENT_ID=$(echo "$RESPONSE" | jq -r '.agent_id // empty' 2>/dev/null || true)
|
|
90
90
|
|
|
@@ -100,21 +100,14 @@ if [ -n "$NAMESPACE" ] && [ -n "$AGENT_ID" ]; then
|
|
|
100
100
|
OLD_HASH=$(cat "$CACHE_FILE" 2>/dev/null || echo "")
|
|
101
101
|
|
|
102
102
|
if [ -n "$SKILL_TEXT" ] && [ "$SKILL_HASH" != "$OLD_HASH" ]; then
|
|
103
|
-
# Find the skill file (project root PATCHCORD.md)
|
|
104
103
|
PROJECT_ROOT=$(dirname "$MCP_JSON")
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
if [ -f "$SKILL_FILE" ]; then
|
|
110
|
-
# Remove existing custom block and append new one
|
|
111
|
-
BEFORE=$(sed "/${START_DELIM}/,\$d" "$SKILL_FILE")
|
|
112
|
-
printf '%s\n\n%s\n%s\n%s\n' "$BEFORE" "$START_DELIM" "$SKILL_TEXT" "$END_DELIM" > "$SKILL_FILE"
|
|
113
|
-
else
|
|
114
|
-
# Create with just the custom block
|
|
115
|
-
printf '%s\n%s\n%s\n' "$START_DELIM" "$SKILL_TEXT" "$END_DELIM" > "$SKILL_FILE"
|
|
116
|
-
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"
|
|
117
108
|
echo "$SKILL_HASH" > "$CACHE_FILE"
|
|
109
|
+
# Clean up old PATCHCORD.md if it exists
|
|
110
|
+
rm -f "${PROJECT_ROOT}/PATCHCORD.md"
|
|
118
111
|
fi
|
|
119
112
|
fi
|
|
120
113
|
fi
|
package/skills/inbox/SKILL.md
CHANGED
|
@@ -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
|