patchcord 0.3.10 → 0.3.12

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.10",
4
+ "version": "0.3.12",
5
5
  "author": {
6
6
  "name": "ppravdin"
7
7
  },
package/bin/patchcord.mjs CHANGED
@@ -84,8 +84,10 @@ if (cmd === "install") {
84
84
  if (changed) {
85
85
  writeFileSync(claudeSettings, JSON.stringify(settings, null, 2) + "\n");
86
86
  }
87
- } catch {
88
- // Non-fatal settings.json might be malformed
87
+ } catch (e) {
88
+ console.log(` ✗ Failed to update ${claudeSettings}`);
89
+ console.log(` Fix: your settings.json has invalid JSON (${e.message})`);
90
+ console.log(` Patchcord permissions were NOT configured. Fix the JSON and re-run install.`);
89
91
  }
90
92
  }
91
93
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "patchcord",
3
- "version": "0.3.10",
3
+ "version": "0.3.12",
4
4
  "description": "Cross-machine agent messaging for Claude Code and Codex",
5
5
  "author": "ppravdin",
6
6
  "license": "MIT",
@@ -1,7 +1,11 @@
1
1
  #!/bin/bash
2
- # Enable the patchcord statusline in Claude Code user settings.
2
+ # Enable the patchcord statusline in Claude Code settings.
3
3
  # Usage: bash enable-statusline.sh [--full]
4
4
  # --full: also show model, context%, repo (branch)
5
+ #
6
+ # Writes to user-level ~/.claude/settings.json unless another
7
+ # tool's statusline is already set there — in that case, writes
8
+ # to project-level .claude/settings.json to avoid overwriting.
5
9
  set -euo pipefail
6
10
 
7
11
  EXTRA_ARGS=""
@@ -9,32 +13,7 @@ for arg in "$@"; do
9
13
  [ "$arg" = "--full" ] && EXTRA_ARGS=" --full"
10
14
  done
11
15
 
12
- # Find the project root (walk up from cwd to find .mcp.json with patchcord)
13
- find_project_root() {
14
- local dir="${1:-$(pwd)}"
15
- while [ -n "$dir" ] && [ "$dir" != "/" ]; do
16
- if [ -f "$dir/.mcp.json" ] && jq -e '.mcpServers.patchcord' "$dir/.mcp.json" >/dev/null 2>&1; then
17
- printf '%s\n' "$dir"
18
- return 0
19
- fi
20
- dir=$(dirname "$dir")
21
- done
22
- return 1
23
- }
24
-
25
- PROJECT_ROOT=$(find_project_root || true)
26
-
27
- if [ -n "$PROJECT_ROOT" ]; then
28
- # Project-level settings (only affects this repo)
29
- SETTINGS="$PROJECT_ROOT/.claude/settings.json"
30
- mkdir -p "$PROJECT_ROOT/.claude"
31
- else
32
- # Fallback to user-level if no project found
33
- SETTINGS="$HOME/.claude/settings.json"
34
- mkdir -p "$HOME/.claude"
35
- fi
36
-
37
- # Find the plugin's statusline script
16
+ # Find the statusline script path
38
17
  SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
39
18
  STATUSLINE="$SCRIPT_DIR/statusline.sh"
40
19
 
@@ -43,6 +22,47 @@ if [ ! -f "$STATUSLINE" ]; then
43
22
  exit 1
44
23
  fi
45
24
 
25
+ NEW_CMD="bash \"$STATUSLINE\"${EXTRA_ARGS}"
26
+
27
+ # Decide where to write
28
+ USER_SETTINGS="$HOME/.claude/settings.json"
29
+ mkdir -p "$HOME/.claude"
30
+
31
+ SETTINGS="$USER_SETTINGS"
32
+
33
+ if [ -f "$USER_SETTINGS" ]; then
34
+ EXISTING_CMD=$(jq -r '.statusLine.command // ""' "$USER_SETTINGS" 2>/dev/null || true)
35
+ if [ -n "$EXISTING_CMD" ]; then
36
+ # Already has a statusline — is it ours?
37
+ if echo "$EXISTING_CMD" | grep -q "patchcord"; then
38
+ # Ours — update in place (e.g. add/remove --full)
39
+ SETTINGS="$USER_SETTINGS"
40
+ else
41
+ # Another tool's statusline — don't overwrite, use project
42
+ find_project_root() {
43
+ local dir="${1:-$(pwd)}"
44
+ while [ -n "$dir" ] && [ "$dir" != "/" ]; do
45
+ if [ -f "$dir/.mcp.json" ] && jq -e '.mcpServers.patchcord' "$dir/.mcp.json" >/dev/null 2>&1; then
46
+ printf '%s\n' "$dir"
47
+ return 0
48
+ fi
49
+ dir=$(dirname "$dir")
50
+ done
51
+ return 1
52
+ }
53
+ PROJECT_ROOT=$(find_project_root || true)
54
+ if [ -n "$PROJECT_ROOT" ]; then
55
+ SETTINGS="$PROJECT_ROOT/.claude/settings.json"
56
+ mkdir -p "$PROJECT_ROOT/.claude"
57
+ else
58
+ # No project found — skip, don't overwrite another tool
59
+ echo "Statusline: another tool's statusline is set globally. Skipping."
60
+ exit 0
61
+ fi
62
+ fi
63
+ fi
64
+ fi
65
+
46
66
  # Read existing settings or start fresh
47
67
  if [ -f "$SETTINGS" ]; then
48
68
  CURRENT=$(cat "$SETTINGS")
@@ -51,10 +71,5 @@ else
51
71
  fi
52
72
 
53
73
  # Set statusLine field
54
- UPDATED=$(echo "$CURRENT" | jq --arg cmd "bash \"$STATUSLINE\"${EXTRA_ARGS}" '.statusLine = {"type": "command", "command": $cmd}')
74
+ UPDATED=$(echo "$CURRENT" | jq --arg cmd "$NEW_CMD" '.statusLine = {"type": "command", "command": $cmd}')
55
75
  echo "$UPDATED" > "$SETTINGS"
56
-
57
- echo "Patchcord statusline enabled."
58
- echo " Script: $STATUSLINE"
59
- echo " Settings: $SETTINGS"
60
- echo "Restart Claude Code to see the statusline."
@@ -1,9 +1,17 @@
1
1
  #!/bin/bash
2
2
  # Patchcord statusline for Claude Code.
3
- # Shows: model | context% | repo (branch) | agent@host [N msg]
3
+ #
4
+ # Default: shows only patchcord identity + inbox count.
5
+ # With --full: also shows model, context%, repo (branch).
6
+ #
4
7
  # Receives session JSON on stdin, outputs ANSI-formatted text.
5
8
  set -f
6
9
 
10
+ FULL=false
11
+ for arg in "$@"; do
12
+ [ "$arg" = "--full" ] && FULL=true
13
+ done
14
+
7
15
  find_patchcord_mcp_json() {
8
16
  local dir="$1"
9
17
  while [ -n "$dir" ] && [ "$dir" != "/" ]; do
@@ -19,7 +27,6 @@ find_patchcord_mcp_json() {
19
27
  input=$(cat)
20
28
 
21
29
  if [ -z "$input" ]; then
22
- printf "Claude"
23
30
  exit 0
24
31
  fi
25
32
 
@@ -36,44 +43,10 @@ reset='\033[0m'
36
43
 
37
44
  sep=" ${dim}│${reset} "
38
45
 
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 ─────────────────────────────────────
46
+ # ── Patchcord: agent identity + inbox ───────────────────
63
47
  cwd=$(echo "$input" | jq -r '.cwd // ""')
64
48
  [ -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
75
49
 
76
- # ── Patchcord: agent identity + inbox ───────────────────
77
50
  pc_token=""
78
51
  pc_url=""
79
52
  mcp_json=$(find_patchcord_mcp_json "$cwd" || true)
@@ -107,52 +80,101 @@ if [ -n "$pc_url" ] && [ -n "$pc_token" ]; then
107
80
  fi
108
81
 
109
82
  if $needs_refresh; then
110
- response=$(curl -sf --max-time 3 \
83
+ http_code=$(curl -s -o /tmp/claude/patchcord-sl-resp.json -w "%{http_code}" --max-time 3 \
111
84
  -H "Authorization: Bearer $pc_token" \
112
- "${pc_url}/api/inbox?status=pending&limit=50" 2>/dev/null || true)
113
- if [ -n "$response" ]; then
114
- pc_data="$response"
115
- echo "$response" > "$cache_file"
85
+ "${pc_url}/api/inbox?status=pending&limit=50" 2>/dev/null || echo "000")
86
+ if [ "$http_code" = "401" ] || [ "$http_code" = "403" ]; then
87
+ pc_data='{"_auth_error":true}'
88
+ echo "$pc_data" > "$cache_file"
89
+ elif [ "$http_code" = "200" ]; then
90
+ pc_data=$(cat /tmp/claude/patchcord-sl-resp.json 2>/dev/null)
91
+ [ -n "$pc_data" ] && echo "$pc_data" > "$cache_file"
116
92
  fi
93
+ rm -f /tmp/claude/patchcord-sl-resp.json
117
94
  fi
118
95
 
119
96
  if [ -n "$pc_data" ]; then
120
- agent_id=$(echo "$pc_data" | jq -r '.agent_id // empty' 2>/dev/null)
121
- namespace_id=$(echo "$pc_data" | jq -r '.namespace_id // empty' 2>/dev/null)
122
- machine=$(echo "$pc_data" | jq -r '.machine_name // empty' 2>/dev/null)
123
- if [ -z "$machine" ] || [ "$machine" = "null" ]; then
124
- machine=$(hostname -s 2>/dev/null || hostname 2>/dev/null || echo "")
125
- fi
126
- count=$(echo "$pc_data" | jq -r '.count // .pending_count // 0' 2>/dev/null)
127
-
128
- if [ -n "$agent_id" ]; then
129
- pc_part="${white}${agent_id}${reset}"
130
- if [ -n "$namespace_id" ] && [ "$namespace_id" != "null" ]; then
131
- pc_part+="${dim}@${namespace_id}${reset}"
97
+ auth_error=$(echo "$pc_data" | jq -r '._auth_error // false' 2>/dev/null)
98
+ if [ "$auth_error" = "true" ]; then
99
+ pc_part="${red}BAD TOKEN${reset}"
100
+ else
101
+ agent_id=$(echo "$pc_data" | jq -r '.agent_id // empty' 2>/dev/null)
102
+ namespace_id=$(echo "$pc_data" | jq -r '.namespace_id // empty' 2>/dev/null)
103
+ machine=$(echo "$pc_data" | jq -r '.machine_name // empty' 2>/dev/null)
104
+ if [ -z "$machine" ] || [ "$machine" = "null" ]; then
105
+ machine=$(hostname -s 2>/dev/null || hostname 2>/dev/null || echo "")
132
106
  fi
133
- if [ -n "$machine" ]; then
134
- pc_part+=" ${dim}(${machine})${reset}"
107
+ count=$(echo "$pc_data" | jq -r '.count // .pending_count // 0' 2>/dev/null)
108
+
109
+ if [ -n "$agent_id" ]; then
110
+ pc_part="${white}${agent_id}${reset}"
111
+ if [ -n "$namespace_id" ] && [ "$namespace_id" != "null" ]; then
112
+ pc_part+="${dim}@${namespace_id}${reset}"
113
+ fi
114
+ if [ -n "$machine" ]; then
115
+ pc_part+=" ${dim}(${machine})${reset}"
116
+ fi
135
117
  fi
136
- fi
137
118
 
138
- if [ "$count" -gt 0 ] 2>/dev/null; then
139
- pc_part+=" ${red}${count} msg${reset}"
119
+ if [ "$count" -gt 0 ] 2>/dev/null; then
120
+ pc_part+=" ${red}${count} msg${reset}"
121
+ fi
140
122
  fi
141
123
  fi
142
124
  fi
143
125
 
144
- # ── Build line ──────────────────────────────────────────
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}"
126
+ # No patchcord config — output nothing in default mode
127
+ if [ -z "$pc_part" ] && ! $FULL; then
128
+ exit 0
152
129
  fi
153
- if [ -n "$pc_part" ]; then
130
+
131
+ # ── Build line ──────────────────────────────────────────
132
+ line=""
133
+
134
+ if $FULL; then
135
+ model_name=$(echo "$input" | jq -r '.model.display_name // "Claude"')
136
+
137
+ size=$(echo "$input" | jq -r '.context_window.context_window_size // 200000')
138
+ [ "$size" -eq 0 ] 2>/dev/null && size=200000
139
+ input_tokens=$(echo "$input" | jq -r '.context_window.current_usage.input_tokens // 0')
140
+ cache_create=$(echo "$input" | jq -r '.context_window.current_usage.cache_creation_input_tokens // 0')
141
+ cache_read=$(echo "$input" | jq -r '.context_window.current_usage.cache_read_input_tokens // 0')
142
+ current=$(( input_tokens + cache_create + cache_read ))
143
+ if [ "$size" -gt 0 ]; then
144
+ pct_used=$(( current * 100 / size ))
145
+ else
146
+ pct_used=0
147
+ fi
148
+ if [ "$pct_used" -ge 90 ]; then pct_color="$red"
149
+ elif [ "$pct_used" -ge 70 ]; then pct_color="$yellow"
150
+ elif [ "$pct_used" -ge 50 ]; then pct_color="$orange"
151
+ else pct_color="$green"
152
+ fi
153
+
154
+ dirname=$(basename "$cwd")
155
+ git_branch=""
156
+ git_dirty=""
157
+ if git -C "$cwd" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
158
+ git_branch=$(git -C "$cwd" symbolic-ref --short HEAD 2>/dev/null)
159
+ if [ -n "$(git -C "$cwd" status --porcelain 2>/dev/null)" ]; then
160
+ git_dirty="*"
161
+ fi
162
+ fi
163
+
164
+ line="${blue}${model_name}${reset}"
165
+ line+="${sep}"
166
+ line+="${pct_color}${pct_used}%${reset}"
154
167
  line+="${sep}"
155
- line+="${pc_part}"
168
+ line+="${cyan}${dirname}${reset}"
169
+ if [ -n "$git_branch" ]; then
170
+ line+=" ${green}(${git_branch}${red}${git_dirty}${green})${reset}"
171
+ fi
172
+ if [ -n "$pc_part" ]; then
173
+ line+="${sep}"
174
+ line+="${pc_part}"
175
+ fi
176
+ else
177
+ line="${pc_part}"
156
178
  fi
157
179
 
158
180
  # ── Output ──────────────────────────────────────────────