patchcord 0.2.1 → 0.2.3

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.2.1",
4
+ "version": "0.2.3",
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
- This plugin is not the connection itself.
5
+ ## Install
6
6
 
7
- The plugin provides:
8
-
9
- - Patchcord skills
10
- - statusline integration
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
- - exporting `PATCHCORD_TOKEN` / `PATCHCORD_URL` globally in shell startup files
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
- ## Setup
14
+ ## How it works
32
15
 
33
- ### 1. Install the plugin
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
- ```bash
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
- ### 2. Configure the project
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,32 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { existsSync, mkdirSync, cpSync, readFileSync } from "fs";
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
- Usage:
15
- patchcord init Auto-detect and set up for current project
16
- patchcord init --codex Set up Codex skill in current project
17
- patchcord init --claude Set up Claude Code plugin
18
- patchcord plugin-path Print path to Claude Code plugin directory
23
+ Commands:
24
+ patchcord install Install/update plugin globally (Claude Code)
25
+ patchcord install --full Install + enable full statusline (model, context%, git)
26
+ patchcord agent Set up MCP config for an agent in this project
27
+ patchcord agent --codex Set up Codex skill + MCP config in this project
19
28
 
20
- Setup after init:
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`);
29
+ Run "patchcord install" once. Run "patchcord agent" in each project.`);
23
30
  process.exit(0);
24
31
  }
25
32
 
@@ -28,31 +35,75 @@ if (cmd === "plugin-path") {
28
35
  process.exit(0);
29
36
  }
30
37
 
31
- if (cmd === "init") {
38
+ // ── install: global plugin + skills (idempotent) ──────────────
39
+ if (cmd === "install") {
40
+ const hasClaude = run("which claude");
41
+ if (!hasClaude) {
42
+ console.log(`Claude Code CLI not found. Install it first:
43
+ https://claude.ai/code
44
+
45
+ Then run: patchcord install`);
46
+ process.exit(1);
47
+ }
48
+
49
+ const flags = process.argv.slice(3);
50
+ const fullStatusline = flags.includes("--full");
51
+
52
+ console.log("Installing patchcord plugin into Claude Code...");
53
+ const result = run(`claude plugin install --path "${pluginRoot}"`);
54
+ if (result === null) {
55
+ console.log(`✗ Plugin install failed. Try manually:
56
+ claude plugin install --path "${pluginRoot}"`);
57
+ process.exit(1);
58
+ }
59
+
60
+ // Enable statusline
61
+ const enableScript = join(pluginRoot, "scripts", "enable-statusline.sh");
62
+ if (existsSync(enableScript)) {
63
+ const slArg = fullStatusline ? " --full" : "";
64
+ const slResult = run(`bash "${enableScript}"${slArg}`);
65
+ if (slResult !== null) {
66
+ console.log(`✓ Plugin installed. Statusline${fullStatusline ? " (full)" : ""} enabled.
67
+
68
+ ${fullStatusline
69
+ ? "Statusline shows: model │ context% │ repo (branch) │ agent@namespace │ inbox"
70
+ : "Statusline shows: agent@namespace │ inbox\n Tip: run \"patchcord install --full\" for model, context%, git info too."}
71
+
72
+ Run "patchcord agent" in each project to set up MCP.`);
73
+ } else {
74
+ console.log(`✓ Plugin installed. Statusline setup skipped (non-fatal).
75
+
76
+ Run "patchcord agent" in each project to set up MCP.`);
77
+ }
78
+ } else {
79
+ console.log(`✓ Plugin installed.
80
+
81
+ Run "patchcord agent" in each project to set up MCP.`);
82
+ }
83
+ process.exit(0);
84
+ }
85
+
86
+ // ── agent: per-project MCP setup ──────────────────────────────
87
+ if (cmd === "agent") {
32
88
  const flag = process.argv[3];
33
89
  const cwd = process.cwd();
34
90
 
35
91
  if (flag === "--codex" || (!flag && existsSync(join(cwd, ".agents")))) {
36
- // Codex setup: copy SKILL.md to .agents/skills/patchcord/
92
+ // Codex: copy skill + print MCP config
37
93
  const dest = join(cwd, ".agents", "skills", "patchcord");
38
94
  mkdirSync(dest, { recursive: true });
39
95
  cpSync(join(pluginRoot, "codex", "SKILL.md"), join(dest, "SKILL.md"));
40
- console.log(`Codex skill installed: ${dest}/SKILL.md
96
+ console.log(`✓ Codex skill installed: ${dest}/SKILL.md
41
97
 
42
- Next: add patchcord MCP server to ~/.codex/config.toml:
98
+ Add to ~/.codex/config.toml:
43
99
 
44
100
  [mcp_servers.patchcord]
45
101
  url = "https://YOUR_SERVER/mcp"
46
102
  bearer_token_env_var = "PATCHCORD_TOKEN"
47
103
  http_headers = { "X-Patchcord-Client-Type" = "codex" }`);
48
- } else if (flag === "--claude" || !flag) {
49
- // Claude Code setup: install plugin
50
- console.log(`Claude Code plugin path: ${pluginRoot}
51
-
52
- Install with:
53
- claude plugin install --path "${pluginRoot}"
54
-
55
- Then add .mcp.json to your project:
104
+ } else {
105
+ // Claude Code: print .mcp.json template
106
+ console.log(`Add .mcp.json to this project:
56
107
 
57
108
  {
58
109
  "mcpServers": {
@@ -65,8 +116,24 @@ Then add .mcp.json to your project:
65
116
  }
66
117
  }
67
118
  }
68
- }`);
69
119
  }
120
+
121
+ Or use the CLI:
122
+
123
+ claude mcp add patchcord "https://YOUR_SERVER/mcp" \\
124
+ --transport http -s project \\
125
+ -H "Authorization: Bearer YOUR_TOKEN" \\
126
+ -H "X-Patchcord-Client-Type: claude_code"`);
127
+ }
128
+ process.exit(0);
129
+ }
130
+
131
+ // ── back-compat: init → install + agent ───────────────────────
132
+ if (cmd === "init") {
133
+ console.log(`"patchcord init" is now two commands:
134
+
135
+ patchcord install Install/update plugin globally (once)
136
+ patchcord agent Set up MCP for this project (per project)`);
70
137
  process.exit(0);
71
138
  }
72
139
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "patchcord",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "Cross-machine agent messaging for Claude Code and Codex",
5
5
  "author": "ppravdin",
6
6
  "license": "MIT",
@@ -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 ──────────────────────────────────────────────
@@ -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() — check who's online
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.