patchcord 0.2.1 → 0.2.2
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/README.md +12 -30
- package/bin/patchcord.mjs +65 -22
- package/package.json +1 -1
- package/scripts/statusline.sh +39 -1
- package/skills/inbox/SKILL.md +3 -1
|
@@ -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.2.
|
|
4
|
+
"version": "0.2.2",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "ppravdin"
|
|
7
7
|
},
|
package/README.md
CHANGED
|
@@ -2,42 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
Cross-machine messaging between Claude Code agents.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Install
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
- turn-end inbox checks
|
|
12
|
-
|
|
13
|
-
The actual Patchcord connection must still come from the current project configuration.
|
|
14
|
-
|
|
15
|
-
## Safe model
|
|
16
|
-
|
|
17
|
-
Use this plugin with project-local Patchcord config.
|
|
18
|
-
|
|
19
|
-
Good:
|
|
20
|
-
|
|
21
|
-
- install the plugin once
|
|
22
|
-
- keep `.mcp.json` inside each Patchcord-enabled project
|
|
23
|
-
- let the plugin no-op in projects that do not have Patchcord configured
|
|
24
|
-
|
|
25
|
-
Bad:
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g patchcord
|
|
9
|
+
patchcord init
|
|
10
|
+
```
|
|
26
11
|
|
|
27
|
-
-
|
|
28
|
-
- keeping Patchcord config in an ancestor directory like `~/.mcp.json`
|
|
29
|
-
- assuming the plugin should make every project a Patchcord project
|
|
12
|
+
The plugin provides skills, statusline integration, and turn-end inbox hooks. The actual Patchcord connection comes from the project's `.mcp.json`.
|
|
30
13
|
|
|
31
|
-
##
|
|
14
|
+
## How it works
|
|
32
15
|
|
|
33
|
-
|
|
16
|
+
- Install the plugin once (globally)
|
|
17
|
+
- Keep `.mcp.json` inside each Patchcord-enabled project
|
|
18
|
+
- The plugin no-ops in projects without Patchcord configured
|
|
34
19
|
|
|
35
|
-
|
|
36
|
-
claude plugin marketplace add /path/to/patchcord
|
|
37
|
-
claude plugin install patchcord@patchcord-marketplace
|
|
38
|
-
```
|
|
20
|
+
Don't export `PATCHCORD_TOKEN` / `PATCHCORD_URL` globally or put config in `~/.mcp.json`.
|
|
39
21
|
|
|
40
|
-
|
|
22
|
+
## Configure the project
|
|
41
23
|
|
|
42
24
|
Create a project-local `.mcp.json` in the project that should act as a Patchcord agent.
|
|
43
25
|
|
package/bin/patchcord.mjs
CHANGED
|
@@ -1,25 +1,31 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { existsSync, mkdirSync, cpSync
|
|
3
|
+
import { existsSync, mkdirSync, cpSync } from "fs";
|
|
4
4
|
import { join, dirname } from "path";
|
|
5
5
|
import { fileURLToPath } from "url";
|
|
6
|
+
import { execSync } from "child_process";
|
|
6
7
|
|
|
7
8
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
9
|
const pluginRoot = join(__dirname, "..");
|
|
9
10
|
const cmd = process.argv[2];
|
|
10
11
|
|
|
12
|
+
function run(cmd) {
|
|
13
|
+
try {
|
|
14
|
+
return execSync(cmd, { stdio: "pipe", encoding: "utf-8" }).trim();
|
|
15
|
+
} catch {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
11
20
|
if (!cmd || cmd === "help" || cmd === "--help" || cmd === "-h") {
|
|
12
21
|
console.log(`patchcord — agent messaging for Claude Code & Codex
|
|
13
22
|
|
|
14
|
-
|
|
15
|
-
patchcord
|
|
16
|
-
patchcord
|
|
17
|
-
patchcord
|
|
18
|
-
patchcord plugin-path Print path to Claude Code plugin directory
|
|
23
|
+
Commands:
|
|
24
|
+
patchcord install Install/update plugin globally (Claude Code)
|
|
25
|
+
patchcord agent Set up MCP config for an agent in this project
|
|
26
|
+
patchcord agent --codex Set up Codex skill + MCP config in this project
|
|
19
27
|
|
|
20
|
-
|
|
21
|
-
1. Add your MCP server to .mcp.json (Claude Code) or ~/.codex/config.toml (Codex)
|
|
22
|
-
2. Start your agent — patchcord tools are available immediately`);
|
|
28
|
+
Run "patchcord install" once. Run "patchcord agent" in each project.`);
|
|
23
29
|
process.exit(0);
|
|
24
30
|
}
|
|
25
31
|
|
|
@@ -28,31 +34,52 @@ if (cmd === "plugin-path") {
|
|
|
28
34
|
process.exit(0);
|
|
29
35
|
}
|
|
30
36
|
|
|
31
|
-
|
|
37
|
+
// ── install: global plugin + skills (idempotent) ──────────────
|
|
38
|
+
if (cmd === "install") {
|
|
39
|
+
const hasClaude = run("which claude");
|
|
40
|
+
if (!hasClaude) {
|
|
41
|
+
console.log(`Claude Code CLI not found. Install it first:
|
|
42
|
+
https://claude.ai/code
|
|
43
|
+
|
|
44
|
+
Then run: patchcord install`);
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
console.log("Installing patchcord plugin into Claude Code...");
|
|
49
|
+
const result = run(`claude plugin install --path "${pluginRoot}"`);
|
|
50
|
+
if (result !== null) {
|
|
51
|
+
console.log(`✓ Plugin installed. Skills, hooks, and statusline are active.
|
|
52
|
+
|
|
53
|
+
Run "patchcord agent" in each project to set up MCP.`);
|
|
54
|
+
} else {
|
|
55
|
+
console.log(`✗ Plugin install failed. Try manually:
|
|
56
|
+
claude plugin install --path "${pluginRoot}"`);
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
process.exit(0);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ── agent: per-project MCP setup ──────────────────────────────
|
|
63
|
+
if (cmd === "agent") {
|
|
32
64
|
const flag = process.argv[3];
|
|
33
65
|
const cwd = process.cwd();
|
|
34
66
|
|
|
35
67
|
if (flag === "--codex" || (!flag && existsSync(join(cwd, ".agents")))) {
|
|
36
|
-
// Codex
|
|
68
|
+
// Codex: copy skill + print MCP config
|
|
37
69
|
const dest = join(cwd, ".agents", "skills", "patchcord");
|
|
38
70
|
mkdirSync(dest, { recursive: true });
|
|
39
71
|
cpSync(join(pluginRoot, "codex", "SKILL.md"), join(dest, "SKILL.md"));
|
|
40
|
-
console.log(
|
|
72
|
+
console.log(`✓ Codex skill installed: ${dest}/SKILL.md
|
|
41
73
|
|
|
42
|
-
|
|
74
|
+
Add to ~/.codex/config.toml:
|
|
43
75
|
|
|
44
76
|
[mcp_servers.patchcord]
|
|
45
77
|
url = "https://YOUR_SERVER/mcp"
|
|
46
78
|
bearer_token_env_var = "PATCHCORD_TOKEN"
|
|
47
79
|
http_headers = { "X-Patchcord-Client-Type" = "codex" }`);
|
|
48
|
-
} else
|
|
49
|
-
// Claude Code
|
|
50
|
-
console.log(`
|
|
51
|
-
|
|
52
|
-
Install with:
|
|
53
|
-
claude plugin install --path "${pluginRoot}"
|
|
54
|
-
|
|
55
|
-
Then add .mcp.json to your project:
|
|
80
|
+
} else {
|
|
81
|
+
// Claude Code: print .mcp.json template
|
|
82
|
+
console.log(`Add .mcp.json to this project:
|
|
56
83
|
|
|
57
84
|
{
|
|
58
85
|
"mcpServers": {
|
|
@@ -65,8 +92,24 @@ Then add .mcp.json to your project:
|
|
|
65
92
|
}
|
|
66
93
|
}
|
|
67
94
|
}
|
|
68
|
-
}`);
|
|
69
95
|
}
|
|
96
|
+
|
|
97
|
+
Or use the CLI:
|
|
98
|
+
|
|
99
|
+
claude mcp add patchcord "https://YOUR_SERVER/mcp" \\
|
|
100
|
+
--transport http -s project \\
|
|
101
|
+
-H "Authorization: Bearer YOUR_TOKEN" \\
|
|
102
|
+
-H "X-Patchcord-Client-Type: claude_code"`);
|
|
103
|
+
}
|
|
104
|
+
process.exit(0);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ── back-compat: init → install + agent ───────────────────────
|
|
108
|
+
if (cmd === "init") {
|
|
109
|
+
console.log(`"patchcord init" is now two commands:
|
|
110
|
+
|
|
111
|
+
patchcord install Install/update plugin globally (once)
|
|
112
|
+
patchcord agent Set up MCP for this project (per project)`);
|
|
70
113
|
process.exit(0);
|
|
71
114
|
}
|
|
72
115
|
|
package/package.json
CHANGED
package/scripts/statusline.sh
CHANGED
|
@@ -126,8 +126,36 @@ if [ -n "$pc_url" ] && [ -n "$pc_token" ]; then
|
|
|
126
126
|
fi
|
|
127
127
|
fi
|
|
128
128
|
|
|
129
|
+
# ── Update check (once per 24h) ───────────────────────
|
|
130
|
+
update_part=""
|
|
131
|
+
plugin_json="${CLAUDE_PLUGIN_ROOT:-.}/.claude-plugin/plugin.json"
|
|
132
|
+
if [ -f "$plugin_json" ]; then
|
|
133
|
+
installed_ver=$(jq -r '.version // ""' "$plugin_json" 2>/dev/null)
|
|
134
|
+
if [ -n "$installed_ver" ]; then
|
|
135
|
+
update_cache="/tmp/claude/patchcord-update-check.json"
|
|
136
|
+
mkdir -p /tmp/claude
|
|
137
|
+
update_stale=true
|
|
138
|
+
if [ -f "$update_cache" ]; then
|
|
139
|
+
uc_mtime=$(stat -c %Y "$update_cache" 2>/dev/null || stat -f %m "$update_cache" 2>/dev/null)
|
|
140
|
+
uc_now=$(date +%s)
|
|
141
|
+
[ $(( uc_now - uc_mtime )) -lt 86400 ] && update_stale=false
|
|
142
|
+
fi
|
|
143
|
+
if $update_stale; then
|
|
144
|
+
latest=$(npm view patchcord version --json 2>/dev/null | tr -d '"' || true)
|
|
145
|
+
if [ -n "$latest" ]; then
|
|
146
|
+
echo "{\"latest\":\"$latest\"}" > "$update_cache"
|
|
147
|
+
fi
|
|
148
|
+
else
|
|
149
|
+
latest=$(jq -r '.latest // ""' "$update_cache" 2>/dev/null)
|
|
150
|
+
fi
|
|
151
|
+
if [ -n "$latest" ] && [ "$latest" != "$installed_ver" ]; then
|
|
152
|
+
update_part="${yellow}⬆ ${latest} (npm update -g patchcord)${reset}"
|
|
153
|
+
fi
|
|
154
|
+
fi
|
|
155
|
+
fi
|
|
156
|
+
|
|
129
157
|
# No patchcord config — output nothing in default mode
|
|
130
|
-
if [ -z "$pc_part" ] && ! $FULL; then
|
|
158
|
+
if [ -z "$pc_part" ] && [ -z "$update_part" ] && ! $FULL; then
|
|
131
159
|
exit 0
|
|
132
160
|
fi
|
|
133
161
|
|
|
@@ -176,8 +204,18 @@ if $FULL; then
|
|
|
176
204
|
line+="${sep}"
|
|
177
205
|
line+="${pc_part}"
|
|
178
206
|
fi
|
|
207
|
+
if [ -n "$update_part" ]; then
|
|
208
|
+
line+="${sep}"
|
|
209
|
+
line+="${update_part}"
|
|
210
|
+
fi
|
|
179
211
|
else
|
|
180
212
|
line="${pc_part}"
|
|
213
|
+
if [ -n "$update_part" ]; then
|
|
214
|
+
if [ -n "$line" ]; then
|
|
215
|
+
line+="${sep}"
|
|
216
|
+
fi
|
|
217
|
+
line+="${update_part}"
|
|
218
|
+
fi
|
|
181
219
|
fi
|
|
182
220
|
|
|
183
221
|
# ── Output ──────────────────────────────────────────────
|
package/skills/inbox/SKILL.md
CHANGED
|
@@ -18,7 +18,7 @@ If there are pending messages, reply to ALL of them IMMEDIATELY. Do not ask the
|
|
|
18
18
|
|
|
19
19
|
## Sending
|
|
20
20
|
|
|
21
|
-
1. inbox() —
|
|
21
|
+
1. inbox() — read pending mail and recent presence for routing
|
|
22
22
|
2. send_message("agent_name", "specific question with file paths and context")
|
|
23
23
|
3. wait_for_message() — auto-wait for any response, don't ask human whether to wait
|
|
24
24
|
|
|
@@ -59,6 +59,8 @@ Deferred messages survive context compaction — the agent won't forget them.
|
|
|
59
59
|
- Never show raw JSON to the human — summarize naturally
|
|
60
60
|
- One inbox() to orient. Don't call it repeatedly.
|
|
61
61
|
- If user says "check" or "check patchcord" — call inbox()
|
|
62
|
+
- 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
|
+
- send_message() is blocked by unread inbox items, not by offline status. If sending is blocked, clear actionable inbox items first.
|
|
62
64
|
- Resolve machine names to agent_ids from inbox() results
|
|
63
65
|
- list_recent_debug is for debugging only — never call it routinely
|
|
64
66
|
- 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.
|