patchcord 0.3.5 → 0.3.7

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.7",
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
@@ -21,12 +21,10 @@ if (!cmd || cmd === "help" || cmd === "--help" || cmd === "-h") {
21
21
  console.log(`patchcord — agent messaging for Claude Code & Codex
22
22
 
23
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
28
- patchcord skill apply Fetch and apply custom skill from the web console
29
- patchcord skill reinstall Full rewrite: default skill + custom skill from server
24
+ patchcord install One-time global setup (auto-detects Claude Code + Codex)
25
+ patchcord install --full Same + enable full statusline (model, context%, git)
26
+ patchcord agent Set up MCP config for this project (auto-detects tool)
27
+ patchcord skill apply Fetch custom skill from web console
30
28
 
31
29
  Run "patchcord install" once. Run "patchcord agent" in each project.`);
32
30
  process.exit(0);
@@ -37,90 +35,107 @@ if (cmd === "plugin-path") {
37
35
  process.exit(0);
38
36
  }
39
37
 
40
- // ── install: global plugin + skills (idempotent) ──────────────
38
+ // ── install: global setup for all detected tools (idempotent) ──
41
39
  if (cmd === "install") {
42
- const hasClaude = run("which claude");
43
- if (!hasClaude) {
44
- console.log(`Claude Code CLI not found. Install it first:
45
- https://claude.ai/code
46
-
47
- Then run: patchcord install`);
48
- process.exit(1);
49
- }
50
-
51
40
  const flags = process.argv.slice(3);
52
41
  const fullStatusline = flags.includes("--full");
42
+ const { readFileSync, writeFileSync } = await import("fs");
53
43
 
54
- console.log("Installing patchcord plugin into Claude Code...");
44
+ let installedSomething = false;
55
45
 
56
- // Register npm package as a local marketplace (idempotent)
57
- const marketplaceExists = run(`claude plugin marketplace list`)?.includes("patchcord");
58
- if (!marketplaceExists) {
59
- const addResult = run(`claude plugin marketplace add "${pluginRoot}"`);
60
- if (addResult === null) {
61
- console.log(`✗ Could not add marketplace. Try manually:
62
- claude plugin marketplace add "${pluginRoot}"
63
- claude plugin install patchcord`);
64
- process.exit(1);
46
+ // ── Claude Code ──
47
+ const hasClaude = run("which claude");
48
+ if (hasClaude) {
49
+ console.log("Found Claude Code. Installing patchcord plugin...");
50
+
51
+ // Register npm package as a local marketplace (idempotent)
52
+ const marketplaceExists = run(`claude plugin marketplace list`)?.includes("patchcord");
53
+ if (!marketplaceExists) {
54
+ run(`claude plugin marketplace add "${pluginRoot}"`);
65
55
  }
66
- }
67
56
 
68
- // Install or update the plugin from the marketplace
69
- const installed = run(`claude plugin list`)?.includes("patchcord");
70
- const result = installed
71
- ? run(`claude plugin update patchcord`)
72
- : run(`claude plugin install patchcord`);
73
- if (result === null && !installed) {
74
- console.log(`✗ Plugin install failed. Try manually:
75
- claude plugin marketplace add "${pluginRoot}"
76
- claude plugin install patchcord`);
77
- process.exit(1);
78
- }
57
+ // Install or update the plugin from the marketplace
58
+ const installed = run(`claude plugin list`)?.includes("patchcord");
59
+ installed ? run(`claude plugin update patchcord`) : run(`claude plugin install patchcord`);
60
+
61
+ // Block OAuth tool leakage from claude.ai web connector
62
+ const claudeSettings = join(process.env.HOME || "", ".claude", "settings.json");
63
+ if (existsSync(claudeSettings)) {
64
+ try {
65
+ const settings = JSON.parse(readFileSync(claudeSettings, "utf-8"));
66
+ const deny = settings.permissions?.deny || [];
67
+ if (!deny.includes("mcp__claude_ai_Patchcord__*")) {
68
+ if (!settings.permissions) settings.permissions = {};
69
+ if (!settings.permissions.deny) settings.permissions.deny = [];
70
+ settings.permissions.deny.push("mcp__claude_ai_Patchcord__*");
71
+ writeFileSync(claudeSettings, JSON.stringify(settings, null, 2) + "\n");
72
+ console.log("✓ Blocked OAuth tool leakage from claude.ai web connector.");
73
+ }
74
+ } catch {
75
+ // Non-fatal — settings.json might be malformed
76
+ }
77
+ }
79
78
 
80
- // Enable statusline
81
- const enableScript = join(pluginRoot, "scripts", "enable-statusline.sh");
82
- if (existsSync(enableScript)) {
83
- const slArg = fullStatusline ? " --full" : "";
84
- const slResult = run(`bash "${enableScript}"${slArg}`);
85
- if (slResult !== null) {
86
- console.log(`✓ Plugin installed. Statusline${fullStatusline ? " (full)" : ""} enabled.
79
+ // Enable statusline
80
+ const enableScript = join(pluginRoot, "scripts", "enable-statusline.sh");
81
+ if (existsSync(enableScript)) {
82
+ const slArg = fullStatusline ? " --full" : "";
83
+ run(`bash "${enableScript}"${slArg}`);
84
+ }
87
85
 
88
- ${fullStatusline
89
- ? "Statusline shows: model │ context% │ repo (branch) │ agent@namespace │ inbox"
90
- : "Statusline shows: agent@namespace │ inbox\n Tip: run \"patchcord install --full\" for model, context%, git info too."}
86
+ console.log(`✓ Claude Code plugin installed.${fullStatusline ? " Full statusline enabled." : ""}`);
87
+ installedSomething = true;
88
+ }
91
89
 
92
- Run "patchcord agent" in each project to set up MCP.`);
93
- } else {
94
- console.log(`✓ Plugin installed. Statusline setup skipped (non-fatal).
90
+ // ── Codex CLI ──
91
+ const codexConfig = join(process.env.HOME || "", ".codex", "config.toml");
92
+ if (existsSync(codexConfig)) {
93
+ console.log("Found Codex CLI config.");
95
94
 
96
- Run "patchcord agent" in each project to set up MCP.`);
95
+ // Disable patchcord as ChatGPT app (prevents OAuth identity conflict)
96
+ const content = readFileSync(codexConfig, "utf-8");
97
+ if (!content.includes("[apps.patchcord]")) {
98
+ writeFileSync(codexConfig, content.trimEnd() + "\n\n[apps.patchcord]\nenabled = false\n");
99
+ console.log("✓ Disabled patchcord ChatGPT app in Codex (prevents identity conflict).");
97
100
  }
98
- } else {
99
- console.log(`✓ Plugin installed.
100
101
 
101
- Run "patchcord agent" in each project to set up MCP.`);
102
+ installedSomething = true;
103
+ }
104
+
105
+ if (!installedSomething) {
106
+ console.log(`No Claude Code or Codex CLI detected.
107
+
108
+ Install one first:
109
+ Claude Code: https://claude.ai/code
110
+ Codex CLI: npm install -g @openai/codex
111
+
112
+ Then run: npx patchcord@latest install`);
113
+ process.exit(1);
102
114
  }
115
+
116
+ console.log(`\nRun "npx patchcord@latest agent" in each project to set up MCP.`);
103
117
  process.exit(0);
104
118
  }
105
119
 
106
- // ── agent: per-project MCP setup ──────────────────────────────
120
+ // ── agent: per-project MCP setup (auto-detects tool) ──────────
107
121
  if (cmd === "agent") {
108
- const flag = process.argv[3];
109
122
  const cwd = process.cwd();
110
123
 
111
- if (flag === "--codex" || (!flag && existsSync(join(cwd, ".agents")))) {
112
- // Codex: copy skill + print MCP config
124
+ // Auto-detect: Codex project has .agents folder or .codex folder
125
+ const isCodex = existsSync(join(cwd, ".agents")) || existsSync(join(cwd, ".codex"));
126
+
127
+ if (isCodex) {
128
+ // Codex: copy skill
113
129
  const dest = join(cwd, ".agents", "skills", "patchcord");
114
130
  mkdirSync(dest, { recursive: true });
115
131
  cpSync(join(pluginRoot, "codex", "SKILL.md"), join(dest, "SKILL.md"));
116
132
  console.log(`✓ Codex skill installed: ${dest}/SKILL.md
117
133
 
118
- Add to ~/.codex/config.toml:
134
+ Add to .codex/config.toml in this project:
119
135
 
120
136
  [mcp_servers.patchcord]
121
- url = "https://YOUR_SERVER/mcp"
122
- bearer_token_env_var = "PATCHCORD_TOKEN"
123
- http_headers = { "X-Patchcord-Client-Type" = "codex" }`);
137
+ url = "https://YOUR_SERVER/mcp/bearer"
138
+ http_headers = { "Authorization" = "Bearer YOUR_TOKEN", "X-Patchcord-Client-Type" = "codex" }`);
124
139
  } else {
125
140
  // Claude Code: print .mcp.json template
126
141
  console.log(`Add .mcp.json to this project:
@@ -131,8 +146,7 @@ Add to ~/.codex/config.toml:
131
146
  "type": "http",
132
147
  "url": "https://YOUR_SERVER/mcp",
133
148
  "headers": {
134
- "Authorization": "Bearer YOUR_TOKEN",
135
- "X-Patchcord-Client-Type": "claude_code"
149
+ "Authorization": "Bearer YOUR_TOKEN"
136
150
  }
137
151
  }
138
152
  }
@@ -142,8 +156,7 @@ Or use the CLI:
142
156
 
143
157
  claude mcp add patchcord "https://YOUR_SERVER/mcp" \\
144
158
  --transport http -s project \\
145
- -H "Authorization: Bearer YOUR_TOKEN" \\
146
- -H "X-Patchcord-Client-Type: claude_code"`);
159
+ -H "Authorization: Bearer YOUR_TOKEN"`);
147
160
  }
148
161
  process.exit(0);
149
162
  }
@@ -152,12 +165,12 @@ Or use the CLI:
152
165
  if (cmd === "init") {
153
166
  console.log(`"patchcord init" is now two commands:
154
167
 
155
- patchcord install Install/update plugin globally (once)
168
+ patchcord install One-time global setup (once)
156
169
  patchcord agent Set up MCP for this project (per project)`);
157
170
  process.exit(0);
158
171
  }
159
172
 
160
- // ── skill: custom skill management ───────────────────────────
173
+ // ── skill: custom skill from web console ─────────────────────
161
174
  if (cmd === "skill") {
162
175
  const sub = process.argv[3];
163
176
  const cwd = process.cwd();
@@ -204,9 +217,6 @@ if (cmd === "skill") {
204
217
  process.exit(1);
205
218
  }
206
219
 
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
220
  const skillDir = join(cwd, ".claude", "skills", "patchcord-custom");
211
221
  const skillFile = join(skillDir, "SKILL.md");
212
222
 
@@ -230,34 +240,10 @@ if (cmd === "skill") {
230
240
  console.error("Failed to parse skill response.");
231
241
  process.exit(1);
232
242
  }
233
- } else if (sub === "reinstall") {
234
- console.log(`Fetching custom skill for ${namespace}:${agentId}...`);
235
- const resp = run(`curl -s -H "Authorization: Bearer ${token}" "${baseUrl}/api/skills/${namespace}/${agentId}"`);
236
- try {
237
- const data = JSON.parse(resp || "{}");
238
- if (data.skill_text) {
239
- mkdirSync(skillDir, { recursive: true });
240
- writeFileSync(skillFile, data.skill_text.trim() + "\n");
241
- console.log(`✓ Custom skill applied to ${skillFile}`);
242
- } else {
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
- }
251
- }
252
- } catch {
253
- console.log("No custom skill set or server unreachable.");
254
- }
255
243
  } else {
256
- console.log(`Unknown skill subcommand: ${sub}
257
- Usage:
258
- patchcord skill apply Fetch and apply custom skill from server
259
- patchcord skill reinstall Re-fetch custom skill from server`);
244
+ console.log(`Usage: patchcord skill apply`);
260
245
  }
246
+
261
247
  // Clean up old PATCHCORD.md if it exists
262
248
  const oldFile = join(cwd, "PATCHCORD.md");
263
249
  if (existsSync(oldFile)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "patchcord",
3
- "version": "0.3.5",
3
+ "version": "0.3.7",
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 ──────────────────────────────────────────────