patchcord 0.3.5 → 0.3.6

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.5",
4
+ "version": "0.3.6",
5
5
  "author": {
6
6
  "name": "ppravdin"
7
7
  },
package/README.md CHANGED
@@ -2,69 +2,22 @@
2
2
 
3
3
  Cross-machine messaging between Claude Code agents.
4
4
 
5
- <<<<<<< Updated upstream
6
- ## Install
7
- ||||||| Stash base
8
- ## Setup (3 steps)
9
-
10
- **1.** Copy `.env.example` to `.env` and paste your token:
11
- =======
12
5
  This plugin is not the connection itself.
13
6
 
14
7
  The plugin provides:
15
- >>>>>>> Stashed changes
16
8
 
17
- <<<<<<< Updated upstream
18
- ```bash
19
- npx patchcord@latest install
20
- ```
21
- ||||||| Stash base
22
- ```bash
23
- cp .env.example .env
24
- # Open .env, replace paste-your-token-here with your actual token
25
- ```
26
- =======
27
9
  - Patchcord skills
28
10
  - statusline integration
29
11
  - turn-end inbox checks
30
- >>>>>>> Stashed changes
31
12
 
32
- <<<<<<< Updated upstream
33
- Or with full statusline (model, context%, git branch):
34
- ||||||| Stash base
35
- **2.** Load the env vars (pick one):
36
- =======
37
13
  The actual Patchcord connection must still come from the current project configuration.
38
- >>>>>>> Stashed changes
39
-
40
- <<<<<<< Updated upstream
41
- ```bash
42
- npx patchcord@latest install --full
43
- ```
44
- ||||||| Stash base
45
- ```bash
46
- # Option A: add to your shell profile (~/.bashrc or ~/.zshrc)
47
- echo 'source /path/to/your/.env' >> ~/.bashrc
48
14
 
49
- # Option B: use direnv (if you have it)
50
- cp .env .envrc && direnv allow
51
-
52
- # Option C: just export manually
53
- export PATCHCORD_TOKEN="your-token"
54
- ```
55
- =======
56
15
  ## Safe model
57
16
 
58
17
  Use this plugin with project-local Patchcord config.
59
18
 
60
19
  Good:
61
- >>>>>>> Stashed changes
62
20
 
63
- <<<<<<< Updated upstream
64
- The plugin provides skills, statusline integration, and turn-end inbox hooks. The actual Patchcord connection comes from the project's `.mcp.json`.
65
- ||||||| Stash base
66
- **3.** Install the plugin and start Claude Code:
67
- =======
68
21
  - install the plugin once
69
22
  - keep `.mcp.json` inside each Patchcord-enabled project
70
23
  - let the plugin no-op in projects that do not have Patchcord configured
@@ -78,63 +31,12 @@ Bad:
78
31
  ## Setup
79
32
 
80
33
  ### 1. Install the plugin
81
- >>>>>>> Stashed changes
82
-
83
- <<<<<<< Updated upstream
84
- ## How it works
85
-
86
- - Install the plugin once (globally)
87
- - Keep `.mcp.json` inside each Patchcord-enabled project
88
- - The plugin no-ops in projects without Patchcord configured
89
-
90
- Don't export `PATCHCORD_TOKEN` / `PATCHCORD_URL` globally or put config in `~/.mcp.json`.
91
34
 
92
- ## Configure the project
93
-
94
- Create a project-local `.mcp.json` in the project that should act as a Patchcord agent.
95
-
96
- ```json
97
- {
98
- "mcpServers": {
99
- "patchcord": {
100
- "type": "http",
101
- "url": "https://patchcord.yourdomain.com/mcp",
102
- "headers": {
103
- "Authorization": "Bearer <project-token>",
104
- "X-Patchcord-Client-Type": "claude_code"
105
- }
106
- }
107
- }
108
- }
109
- ||||||| Stash base
110
- ```bash
111
- claude plugin marketplace add /path/to/patchcord-internal
112
- claude plugin install patchcord@patchcord-marketplace
113
- claude
114
- =======
115
35
  ```bash
116
36
  claude plugin marketplace add /path/to/patchcord-internal
117
37
  claude plugin install patchcord@patchcord-marketplace
118
- >>>>>>> Stashed changes
119
38
  ```
120
39
 
121
- <<<<<<< Updated upstream
122
- ### 3. Start Claude Code in that project
123
-
124
- The plugin and statusline scripts read the current project configuration from the session's working tree.
125
-
126
- ## What happens in non-Patchcord projects
127
-
128
- Nothing Patchcord-specific should appear.
129
-
130
- - no Patchcord identity in the statusline
131
- - no inbox checks
132
- - no hook-driven Patchcord prompts
133
-
134
- The plugin can stay installed globally, but it must no-op unless the current project is configured.
135
- ||||||| Stash base
136
- That's it. Inbox, send messages, reply — all works automatically.
137
- =======
138
40
  ### 2. Configure the project
139
41
 
140
42
  Create a project-local `.mcp.json` in the project that should act as a Patchcord agent.
@@ -168,39 +70,10 @@ Nothing Patchcord-specific should appear.
168
70
  - no hook-driven Patchcord prompts
169
71
 
170
72
  The plugin is allowed to stay installed globally, but it must no-op unless the current project is configured.
171
- >>>>>>> Stashed changes
172
73
 
173
74
  ## Self-hosted server
174
75
 
175
- <<<<<<< Updated upstream
176
- Point the project `.mcp.json` at your own server URL.
177
-
178
- Bearer-token clients can also use `/mcp/bearer` if you want the dedicated bearer-only endpoint.
179
-
180
- ## What the plugin provides
181
-
182
- - Stop hook / turn-end inbox check
183
- - Patchcord skill for Claude
184
- - statusline identity display
185
-
186
- The MCP tools themselves come from the project's `.mcp.json` server connection, not from the plugin bundle.
187
-
188
- ## Statusline
189
-
190
- By default the statusline shows only Patchcord identity and inbox count. In non-Patchcord projects it outputs nothing.
191
-
192
- To also show model, context usage, repo, and git branch:
193
-
194
- ```bash
195
- npx patchcord@latest install --full
196
- ```
197
-
198
- Without `--full`:
199
- ||||||| Stash base
200
- By default the plugin connects to `https://patchcord.dev`. If you run your own server, add to your `.env`:
201
- =======
202
76
  The project `.mcp.json` should point to your own server URL:
203
- >>>>>>> Stashed changes
204
77
 
205
78
  ```json
206
79
  {
@@ -215,32 +88,6 @@ The project `.mcp.json` should point to your own server URL:
215
88
  }
216
89
  }
217
90
  ```
218
- <<<<<<< Updated upstream
219
- ds@default (thick) 2 msg
220
- ```
221
-
222
- With `--full`:
223
-
224
- ```
225
- Opus 4.6 │ 73% │ myproject (main) │ ds@default (thick) 2 msg
226
- ```
227
-
228
- ## Verify
229
-
230
- In a Patchcord-enabled project:
231
-
232
- - statusline should show the Patchcord identity and pending message count
233
- - `inbox()` should return the expected `namespace_id` and `agent_id`
234
-
235
- In an unrelated project:
236
-
237
- - statusline should be empty (default) or show only model/context/git (`--full`)
238
- - no Patchcord hooks should fire
239
- - no Patchcord tools should be present unless that project is configured
240
- ||||||| Stash base
241
- PATCHCORD_URL=https://your-server.example.com
242
- ```
243
- =======
244
91
 
245
92
  ## Verify
246
93
 
@@ -254,4 +101,3 @@ In an unrelated project:
254
101
  - statusline should not show Patchcord identity
255
102
  - no Patchcord hooks should fire
256
103
  - no Patchcord tools should be present unless that project is configured
257
- >>>>>>> Stashed changes
package/bin/patchcord.mjs CHANGED
@@ -77,6 +77,17 @@ Then run: patchcord install`);
77
77
  process.exit(1);
78
78
  }
79
79
 
80
+ // Disable patchcord as Codex ChatGPT app (prevents OAuth identity conflict)
81
+ const codexConfig = join(process.env.HOME || "", ".codex", "config.toml");
82
+ if (existsSync(codexConfig)) {
83
+ const { readFileSync, writeFileSync } = await import("fs");
84
+ const content = readFileSync(codexConfig, "utf-8");
85
+ if (!content.includes("[apps.patchcord]")) {
86
+ writeFileSync(codexConfig, content.trimEnd() + "\n\n[apps.patchcord]\nenabled = false\n");
87
+ console.log("✓ Disabled patchcord ChatGPT app in Codex (prevents identity conflict).");
88
+ }
89
+ }
90
+
80
91
  // Enable statusline
81
92
  const enableScript = join(pluginRoot, "scripts", "enable-statusline.sh");
82
93
  if (existsSync(enableScript)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "patchcord",
3
- "version": "0.3.5",
3
+ "version": "0.3.6",
4
4
  "description": "Cross-machine agent messaging for Claude Code and Codex",
5
5
  "author": "ppravdin",
6
6
  "license": "MIT",
@@ -1,20 +1,9 @@
1
1
  #!/bin/bash
2
2
  # Patchcord statusline for Claude Code.
3
- #
4
- # Default: shows only patchcord identity + inbox count.
5
- # With --full: also shows model, context%, repo (branch).
6
- #
7
- # --full mode model/context/git display based on claude-statusline by Kamran Ahmed
8
- # https://github.com/kamranahmedse/claude-statusline (MIT)
9
- #
3
+ # Shows: model | context% | repo (branch) | agent@host [N msg]
10
4
  # Receives session JSON on stdin, outputs ANSI-formatted text.
11
5
  set -f
12
6
 
13
- FULL=false
14
- for arg in "$@"; do
15
- [ "$arg" = "--full" ] && FULL=true
16
- done
17
-
18
7
  find_patchcord_mcp_json() {
19
8
  local dir="$1"
20
9
  while [ -n "$dir" ] && [ "$dir" != "/" ]; do
@@ -30,6 +19,7 @@ find_patchcord_mcp_json() {
30
19
  input=$(cat)
31
20
 
32
21
  if [ -z "$input" ]; then
22
+ printf "Claude"
33
23
  exit 0
34
24
  fi
35
25
 
@@ -46,10 +36,44 @@ reset='\033[0m'
46
36
 
47
37
  sep=" ${dim}│${reset} "
48
38
 
49
- # ── Patchcord: agent identity + inbox ───────────────────
39
+ # ── Model + context ─────────────────────────────────────
40
+ model_name=$(echo "$input" | jq -r '.model.display_name // "Claude"')
41
+
42
+ size=$(echo "$input" | jq -r '.context_window.context_window_size // 200000')
43
+ [ "$size" -eq 0 ] 2>/dev/null && size=200000
44
+
45
+ input_tokens=$(echo "$input" | jq -r '.context_window.current_usage.input_tokens // 0')
46
+ cache_create=$(echo "$input" | jq -r '.context_window.current_usage.cache_creation_input_tokens // 0')
47
+ cache_read=$(echo "$input" | jq -r '.context_window.current_usage.cache_read_input_tokens // 0')
48
+ current=$(( input_tokens + cache_create + cache_read ))
49
+
50
+ if [ "$size" -gt 0 ]; then
51
+ pct_used=$(( current * 100 / size ))
52
+ else
53
+ pct_used=0
54
+ fi
55
+
56
+ if [ "$pct_used" -ge 90 ]; then pct_color="$red"
57
+ elif [ "$pct_used" -ge 70 ]; then pct_color="$yellow"
58
+ elif [ "$pct_used" -ge 50 ]; then pct_color="$orange"
59
+ else pct_color="$green"
60
+ fi
61
+
62
+ # ── Directory + git ─────────────────────────────────────
50
63
  cwd=$(echo "$input" | jq -r '.cwd // ""')
51
64
  [ -z "$cwd" ] || [ "$cwd" = "null" ] && cwd=$(pwd)
65
+ dirname=$(basename "$cwd")
66
+
67
+ git_branch=""
68
+ git_dirty=""
69
+ if git -C "$cwd" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
70
+ git_branch=$(git -C "$cwd" symbolic-ref --short HEAD 2>/dev/null)
71
+ if [ -n "$(git -C "$cwd" status --porcelain 2>/dev/null)" ]; then
72
+ git_dirty="*"
73
+ fi
74
+ fi
52
75
 
76
+ # ── Patchcord: agent identity + inbox ───────────────────
53
77
  pc_token=""
54
78
  pc_url=""
55
79
  mcp_json=$(find_patchcord_mcp_json "$cwd" || true)
@@ -83,54 +107,16 @@ if [ -n "$pc_url" ] && [ -n "$pc_token" ]; then
83
107
  fi
84
108
 
85
109
  if $needs_refresh; then
86
- http_code=$(curl -s -o /tmp/claude/patchcord-sl-resp.json -w "%{http_code}" --max-time 3 \
110
+ response=$(curl -sf --max-time 3 \
87
111
  -H "Authorization: Bearer $pc_token" \
88
- <<<<<<< Updated upstream
89
- "${pc_url}/api/inbox?status=pending&limit=50" 2>/dev/null || echo "000")
90
- if [ "$http_code" = "401" ] || [ "$http_code" = "403" ]; then
91
- pc_data='{"_auth_error":true}'
92
- echo "$pc_data" > "$cache_file"
93
- elif [ "$http_code" = "200" ]; then
94
- pc_data=$(cat /tmp/claude/patchcord-sl-resp.json 2>/dev/null)
95
- [ -n "$pc_data" ] && echo "$pc_data" > "$cache_file"
96
- ||||||| Stash base
97
112
  "${pc_url}/api/inbox?status=pending&limit=50" 2>/dev/null || true)
98
113
  if [ -n "$response" ]; then
99
114
  pc_data="$response"
100
115
  echo "$response" > "$cache_file"
101
- elif [ -f "$cache_file" ]; then
102
- pc_data=$(cat "$cache_file" 2>/dev/null)
103
- =======
104
- "${pc_url}/api/inbox?status=pending&limit=50" 2>/dev/null || true)
105
- if [ -n "$response" ]; then
106
- pc_data="$response"
107
- echo "$response" > "$cache_file"
108
- >>>>>>> Stashed changes
109
116
  fi
110
- rm -f /tmp/claude/patchcord-sl-resp.json
111
117
  fi
112
118
 
113
119
  if [ -n "$pc_data" ]; then
114
- <<<<<<< Updated upstream
115
- auth_error=$(echo "$pc_data" | jq -r '._auth_error // false' 2>/dev/null)
116
- if [ "$auth_error" = "true" ]; then
117
- pc_part="${red}BAD TOKEN${reset}"
118
- else
119
- agent_id=$(echo "$pc_data" | jq -r '.agent_id // empty' 2>/dev/null)
120
- namespace_id=$(echo "$pc_data" | jq -r '.namespace_id // empty' 2>/dev/null)
121
- machine=$(echo "$pc_data" | jq -r '.machine_name // empty' 2>/dev/null)
122
- if [ -z "$machine" ] || [ "$machine" = "null" ]; then
123
- machine=$(hostname -s 2>/dev/null || hostname 2>/dev/null || echo "")
124
- ||||||| Stash base
125
- agent_id=$(echo "$pc_data" | jq -r '.agent_id // empty' 2>/dev/null)
126
- machine=$(hostname -s 2>/dev/null || hostname 2>/dev/null || echo "")
127
- count=$(echo "$pc_data" | jq -r '.count // .pending_count // 0' 2>/dev/null)
128
-
129
- if [ -n "$agent_id" ]; then
130
- pc_part="${white}${agent_id}${reset}"
131
- if [ -n "$machine" ]; then
132
- pc_part+="${dim}@${machine}${reset}"
133
- =======
134
120
  agent_id=$(echo "$pc_data" | jq -r '.agent_id // empty' 2>/dev/null)
135
121
  namespace_id=$(echo "$pc_data" | jq -r '.namespace_id // empty' 2>/dev/null)
136
122
  machine=$(echo "$pc_data" | jq -r '.machine_name // empty' 2>/dev/null)
@@ -146,117 +132,27 @@ if [ -n "$pc_url" ] && [ -n "$pc_token" ]; then
146
132
  fi
147
133
  if [ -n "$machine" ]; then
148
134
  pc_part+=" ${dim}(${machine})${reset}"
149
- >>>>>>> Stashed changes
150
- fi
151
- count=$(echo "$pc_data" | jq -r '.count // .pending_count // 0' 2>/dev/null)
152
-
153
- if [ -n "$agent_id" ]; then
154
- pc_part="${white}${agent_id}${reset}"
155
- if [ -n "$namespace_id" ] && [ "$namespace_id" != "null" ]; then
156
- pc_part+="${dim}@${namespace_id}${reset}"
157
- fi
158
- if [ -n "$machine" ]; then
159
- pc_part+=" ${dim}(${machine})${reset}"
160
- fi
161
- fi
162
-
163
- if [ "$count" -gt 0 ] 2>/dev/null; then
164
- pc_part+=" ${red}${count} msg${reset}"
165
135
  fi
166
136
  fi
167
- fi
168
- fi
169
137
 
170
- # ── Update check (once per 24h) ───────────────────────
171
- update_part=""
172
- plugin_json="${CLAUDE_PLUGIN_ROOT:-.}/.claude-plugin/plugin.json"
173
- if [ -f "$plugin_json" ]; then
174
- installed_ver=$(jq -r '.version // ""' "$plugin_json" 2>/dev/null)
175
- if [ -n "$installed_ver" ]; then
176
- update_cache="/tmp/claude/patchcord-update-check.json"
177
- mkdir -p /tmp/claude
178
- update_stale=true
179
- if [ -f "$update_cache" ]; then
180
- uc_mtime=$(stat -c %Y "$update_cache" 2>/dev/null || stat -f %m "$update_cache" 2>/dev/null)
181
- uc_now=$(date +%s)
182
- [ $(( uc_now - uc_mtime )) -lt 86400 ] && update_stale=false
183
- fi
184
- if $update_stale; then
185
- latest=$(npm view patchcord version --json 2>/dev/null | tr -d '"' || true)
186
- if [ -n "$latest" ]; then
187
- echo "{\"latest\":\"$latest\"}" > "$update_cache"
188
- fi
189
- else
190
- latest=$(jq -r '.latest // ""' "$update_cache" 2>/dev/null)
191
- fi
192
- if [ -n "$latest" ] && [ "$latest" != "$installed_ver" ]; then
193
- update_part="${yellow}⬆ ${latest} (npm update -g patchcord)${reset}"
138
+ if [ "$count" -gt 0 ] 2>/dev/null; then
139
+ pc_part+=" ${red}${count} msg${reset}"
194
140
  fi
195
141
  fi
196
142
  fi
197
143
 
198
- # No patchcord config — output nothing in default mode
199
- if [ -z "$pc_part" ] && [ -z "$update_part" ] && ! $FULL; then
200
- exit 0
201
- fi
202
-
203
144
  # ── Build line ──────────────────────────────────────────
204
- line=""
205
-
206
- if $FULL; then
207
- model_name=$(echo "$input" | jq -r '.model.display_name // "Claude"')
208
-
209
- size=$(echo "$input" | jq -r '.context_window.context_window_size // 200000')
210
- [ "$size" -eq 0 ] 2>/dev/null && size=200000
211
- input_tokens=$(echo "$input" | jq -r '.context_window.current_usage.input_tokens // 0')
212
- cache_create=$(echo "$input" | jq -r '.context_window.current_usage.cache_creation_input_tokens // 0')
213
- cache_read=$(echo "$input" | jq -r '.context_window.current_usage.cache_read_input_tokens // 0')
214
- current=$(( input_tokens + cache_create + cache_read ))
215
- if [ "$size" -gt 0 ]; then
216
- pct_used=$(( current * 100 / size ))
217
- else
218
- pct_used=0
219
- fi
220
- if [ "$pct_used" -ge 90 ]; then pct_color="$red"
221
- elif [ "$pct_used" -ge 70 ]; then pct_color="$yellow"
222
- elif [ "$pct_used" -ge 50 ]; then pct_color="$orange"
223
- else pct_color="$green"
224
- fi
225
-
226
- dirname=$(basename "$cwd")
227
- git_branch=""
228
- git_dirty=""
229
- if git -C "$cwd" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
230
- git_branch=$(git -C "$cwd" symbolic-ref --short HEAD 2>/dev/null)
231
- if [ -n "$(git -C "$cwd" status --porcelain 2>/dev/null)" ]; then
232
- git_dirty="*"
233
- fi
234
- fi
235
-
236
- line="${blue}${model_name}${reset}"
237
- line+="${sep}"
238
- line+="${pct_color}${pct_used}%${reset}"
145
+ line="${blue}${model_name}${reset}"
146
+ line+="${sep}"
147
+ line+="${pct_color}${pct_used}%${reset}"
148
+ line+="${sep}"
149
+ line+="${cyan}${dirname}${reset}"
150
+ if [ -n "$git_branch" ]; then
151
+ line+=" ${green}(${git_branch}${red}${git_dirty}${green})${reset}"
152
+ fi
153
+ if [ -n "$pc_part" ]; then
239
154
  line+="${sep}"
240
- line+="${cyan}${dirname}${reset}"
241
- if [ -n "$git_branch" ]; then
242
- line+=" ${green}(${git_branch}${red}${git_dirty}${green})${reset}"
243
- fi
244
- if [ -n "$pc_part" ]; then
245
- line+="${sep}"
246
- line+="${pc_part}"
247
- fi
248
- if [ -n "$update_part" ]; then
249
- line+="${sep}"
250
- line+="${update_part}"
251
- fi
252
- else
253
- line="${pc_part}"
254
- if [ -n "$update_part" ]; then
255
- if [ -n "$line" ]; then
256
- line+="${sep}"
257
- fi
258
- line+="${update_part}"
259
- fi
155
+ line+="${pc_part}"
260
156
  fi
261
157
 
262
158
  # ── Output ──────────────────────────────────────────────