patchcord 0.5.33 → 0.5.35

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/bin/patchcord.mjs CHANGED
@@ -68,6 +68,7 @@ Usage:
68
68
  npx patchcord@latest --token <token> --server <url> Self-hosted with custom server
69
69
  npx patchcord@latest --full Same + full statusline
70
70
  npx patchcord@latest --rename <new-name> [--tool <slug>] Rename this agent (paste from dashboard)
71
+ npx patchcord@latest subscribe Start the realtime listener (used by /patchcord:subscribe)
71
72
  npx patchcord@latest skill apply Fetch custom skill from web console`);
72
73
  process.exit(0);
73
74
  }
@@ -77,6 +78,26 @@ if (cmd === "plugin-path") {
77
78
  process.exit(0);
78
79
  }
79
80
 
81
+ // ── subscribe ─────────────────────────────────────────────────
82
+ // Thin wrapper around scripts/subscribe.mjs so users see a clean
83
+ // "npx patchcord subscribe" in their Claude Code tool log instead
84
+ // of a wall of bash. subscribe.mjs handles its own pidfile guard
85
+ // (exits with code 2 + "already running" if another listener is up),
86
+ // so this command needs zero pre-checks.
87
+ if (cmd === "subscribe") {
88
+ const subscribeScript = join(pluginRoot, "scripts", "subscribe.mjs");
89
+ if (!existsSync(subscribeScript)) {
90
+ console.error(`subscribe.mjs not found at ${subscribeScript}`);
91
+ process.exit(1);
92
+ }
93
+ const { spawnSync } = await import("child_process");
94
+ const result = spawnSync(process.execPath, [subscribeScript], {
95
+ stdio: "inherit",
96
+ env: process.env,
97
+ });
98
+ process.exit(result.status ?? (result.signal ? 1 : 0));
99
+ }
100
+
80
101
  // ── --rename <new> [--tool <slug>] [--expect-token <prefix>] ────
81
102
  // Renames the agent's bearer in its tool's per-project config. Supported
82
103
  // tools: claude_code, codex, cursor, vscode, opencode. Dashboard generates
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "patchcord",
3
- "version": "0.5.33",
3
+ "version": "0.5.35",
4
4
  "description": "Cross-machine agent messaging for Claude Code and Codex",
5
5
  "author": "ppravdin",
6
6
  "license": "MIT",
@@ -1,147 +1,58 @@
1
1
  ---
2
2
  name: patchcord:subscribe
3
3
  description: >
4
- Start a background listener that wakes Claude the moment a new Patchcord
5
- message arrives for this agent. Uses Supabase Realtime over WebSocket —
6
- zero polling, zero idle cost. Use when the user says "subscribe",
7
- "listen for patchcord messages", "wake me when messages arrive", or runs
8
- /patchcord:subscribe.
4
+ Start a persistent background WebSocket listener (Supabase Realtime) that
5
+ wakes Claude when new Patchcord messages arrive. Survives across turns
6
+ until the user kills it or closes the session. Use ONLY when the user
7
+ explicitly runs /patchcord:subscribe.
9
8
  ---
10
9
 
11
- # What this does
12
-
13
- Spawns `scripts/subscribe.mjs` in the background. The script holds a
14
- WebSocket to Supabase Realtime and prints one line to stdout per new
15
- `agent_messages` INSERT for this agent. Claude Code's `Monitor` tool
16
- picks up each line as a notification; Claude wakes up and calls
17
- `inbox()`.
18
-
19
- No polling, no tokens burned while idle. The process stays alive until
20
- the user kills it or closes the Claude Code session.
21
-
22
- # How to find the script path (read carefully — this is the one thing that trips agents up)
23
-
24
- At the top of the skill invocation message, Claude Code shows a header:
25
- `Base directory for this skill: <ABSOLUTE_PATH>/skills/subscribe`
26
-
27
- Take that path, strip `/skills/subscribe` from the end — you now have the
28
- plugin root. The script is at `<PLUGIN_ROOT>/scripts/subscribe.mjs`.
29
-
30
- **Do not rely on `$CLAUDE_PLUGIN_ROOT`** — it is often unset inside
31
- the Bash shell even when the skill is running. Always derive the path
32
- from the "Base directory for this skill" header you were given.
33
-
34
- Example: if the header says
35
- `Base directory for this skill: /home/user/.npm/_npx/abc123/node_modules/patchcord/skills/subscribe`
36
- then the script is at
37
- `/home/user/.npm/_npx/abc123/node_modules/patchcord/scripts/subscribe.mjs`.
38
-
39
- # Starting (step by step)
40
-
41
- 1. **Know your identity.** If you don't already have `namespace_id` and
42
- `agent_id` from this session, call `mcp__patchcord__inbox` once — the
43
- response starts with `<agent>@<namespace> | N pending` and you can
44
- read both off that line.
45
-
46
- 2. **Compute the pidfile path:**
47
- `/tmp/patchcord_subscribe_<namespace_id>_<agent_id>.pid`
48
-
49
- 3. **Check for an existing listener.** One Bash call:
50
- ```bash
51
- PF=/tmp/patchcord_subscribe_<ns>_<agent>.pid
52
- if [ -f "$PF" ] && kill -0 "$(cat "$PF")" 2>/dev/null; then
53
- echo "ALREADY_RUNNING pid=$(cat "$PF")"
54
- else
55
- echo "OK_TO_SPAWN"
56
- fi
57
- ```
58
- If output is `ALREADY_RUNNING`, tell the user "Patchcord listener
59
- already active (pid N)" and STOP. Do not spawn another one.
60
-
61
- 4. **Resolve the script path** using the recipe above.
62
-
63
- 5. **Spawn under Monitor** — not Bash with `run_in_background`. Monitor
64
- is the right tool because every stdout line becomes a notification.
65
- Example call shape:
66
- ```
67
- Monitor(
68
- description: "patchcord realtime listener (<agent>@<ns>)",
69
- persistent: true,
70
- timeout_ms: 3600000,
71
- command: "exec node \"<absolute-path-to-subscribe.mjs>\""
72
- )
73
- ```
74
- No `2>&1`, no grep filter. By construction the script only writes
75
- `PATCHCORD: ...` lines to stdout. Everything else — `connected`,
76
- `token refreshed`, startup diagnostics, errors — goes to stderr,
77
- which Monitor captures into its output file but does NOT fire as
78
- notifications. So there's nothing to filter and no way to get the
79
- filter wrong.
80
-
81
- Crash detection is handled automatically by Monitor itself: when the
82
- process exits, Monitor emits a built-in "stream ended" task
83
- notification with the output file path and exit code.
84
-
85
- 6. **Tell the user one short line:**
86
- "Patchcord listener active — I'll pick up new messages as they arrive."
10
+ User invoked /patchcord:subscribe — do NOT substitute `wait_for_message()`. Spawn the listener.
11
+
12
+ # Start
13
+
14
+ Spawn under Monitor (not Bash with run_in_background Monitor turns each stdout line into a notification):
15
+
16
+ ```
17
+ Monitor(
18
+ description: "patchcord realtime listener",
19
+ persistent: true,
20
+ timeout_ms: 3600000,
21
+ command: "exec npx patchcord subscribe"
22
+ )
23
+ ```
24
+
25
+ `subscribe.mjs` handles its own pidfile guard — if another listener is already active for this agent it exits with code 2 and stderr `already running (pid N)`. Monitor catches the stream-end event; read the output file and report.
26
+
27
+ Then one line to the user: *"Patchcord listener active — I'll pick up new messages as they arrive."*
87
28
 
88
29
  # When a notification fires
89
30
 
90
- Monitor surfaces `PATCHCORD: 1 new from <sender>`. Do this:
31
+ Monitor surfaces `PATCHCORD: 1 new from <sender>`:
91
32
 
92
- 1. Say one brief line: "Got a Patchcord ping from <sender> — checking inbox."
33
+ 1. Say: *"Got a Patchcord ping from <sender> — checking inbox."*
93
34
  2. Call `mcp__patchcord__inbox`.
94
- 3. For each pending message, do the work first (follow the
95
- patchcord:inbox skill), then reply with what you did.
96
- 4. Return to listening — Monitor keeps running.
35
+ 3. Do the work per the patchcord:inbox skill, reply with what you did.
97
36
 
98
37
  # Stopping
99
38
 
100
- There is no `/patchcord:unsubscribe` command. Tell the user either:
101
-
102
- - Close this Claude Code session, OR
103
- - Run `kill $(cat /tmp/patchcord_subscribe_<namespace>_<agent>.pid)` in
104
- a terminal.
105
-
106
- # If the Monitor stream ends STRICT PROTOCOL
107
-
108
- The stream-end task notification includes the path to Monitor's output
109
- file. Do exactly this, in this order:
110
-
111
- 1. Read that output file using the Read tool.
112
- 2. Look at the last ~15 lines for a line matching one of the known
113
- failure strings below. There will always be at least one terminal
114
- error line — the script's error handlers guarantee it.
115
- 3. Report the specific cause to the user in one short sentence.
116
- 4. STOP.
117
-
118
- **Forbidden on failure — do not do any of these:**
119
- - Do NOT run `pgrep`, `ps`, `kill`, `pkill`, `killall`, or any command
120
- that targets PIDs or process names.
121
- - Do NOT modify, delete, or write to the pidfile yourself. The script
122
- manages it; it's already cleaned up by the time Monitor emits the
123
- stream-end event.
124
- - Do NOT spawn another Monitor or another `node subscribe.mjs`. One
125
- failure means something is wrong with the config or environment;
126
- respawning will not fix it and will make things worse.
127
- - Do NOT search for orphaned processes or try to "clean up" state.
128
-
129
- Concrete failure strings you may see and what they mean:
130
-
131
- - `no .mcp.json in <cwd>` — session is not in a patchcord project dir.
132
- Tell the user which directory to `cd` into.
133
- - `ticket: token rejected (HTTP 401|403)` — bearer in `.mcp.json` is
134
- bad; regenerate from the dashboard.
135
- - `ticket: server not configured for realtime` — the patchcord server
136
- hasn't had `SUPABASE_JWT_SECRET` / `SUPABASE_ANON_KEY` set. This is
137
- a cloud-only feature.
138
- - `ticket: namespace not owned — regenerate your token` — the token's
139
- namespace lost its owner row; regenerate from the dashboard.
140
- - `already running (pid N)` (exit code 2) — pidfile guard tripped,
141
- another listener is active. Report and stop. Do NOT kill the other
142
- listener to make room.
143
- - `subscribe: fatal: ...` — unhandled error. Show the user the line
144
- verbatim, stop.
145
-
146
- If the process exited cleanly (exit 0) with no error line, the user
147
- closed the session or killed the process. Nothing to do.
39
+ Tell the user one of:
40
+ - Close this Claude Code session.
41
+ - `kill $(cat /tmp/patchcord_subscribe_<namespace>_<agent>.pid)`
42
+
43
+ # If the Monitor stream ends
44
+
45
+ Read the output file. Scan the last ~15 lines for one of:
46
+
47
+ - `no .mcp.json in <cwd>` session is not in a patchcord project dir
48
+ - `ticket: token rejected (HTTP 401|403)` — bad bearer; user regenerates from dashboard
49
+ - `ticket: server not configured for realtime` — self-hosted without Supabase
50
+ - `ticket: namespace not owned` token lost its owner; regenerate
51
+ - `already running (pid N)` (exit 2) another listener is active; report and stop
52
+ - `subscribe: fatal: ...` surface the line verbatim
53
+
54
+ Report the cause in one sentence. STOP.
55
+
56
+ **Forbidden on failure:** no `pgrep`/`ps`/`kill`/`pkill`/`killall`, no pidfile writes, no respawning. The script manages pidfile cleanup itself; respawning will not fix a config problem.
57
+
58
+ Clean exit (code 0) with no error line = the user closed the session or killed the process. Nothing to do.
@@ -1,13 +1,16 @@
1
1
  ---
2
2
  name: patchcord:wait
3
3
  description: >
4
- Enter listening mode - wait for incoming patchcord messages. Use when user
5
- says "wait", "listen", "stand by", or wants the agent to stay responsive
6
- to other agents.
4
+ Block this turn for up to 5 minutes waiting for one incoming Patchcord
5
+ message via the wait_for_message MCP tool. Single blocking call, no
6
+ background process. Use ONLY when the user explicitly runs
7
+ /patchcord:wait.
7
8
  ---
8
9
  # patchcord:wait
9
10
 
10
- Enter listening mode. Call `wait_for_message()` to block until a message arrives (up to 5 minutes).
11
+ User invoked /patchcord:wait do NOT substitute /patchcord:subscribe or spawn any background listener. Use `wait_for_message()` only.
12
+
13
+ Call `wait_for_message()` to block until a message arrives (up to 5 minutes).
11
14
 
12
15
  When a message arrives:
13
16