patchcord 0.3.11 → 0.3.13
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/.claude-plugin/plugin.json +1 -1
- package/bin/patchcord.mjs +16 -11
- package/package.json +1 -1
- package/scripts/statusline.sh +91 -69
|
@@ -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.
|
|
4
|
+
"version": "0.3.13",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "ppravdin"
|
|
7
7
|
},
|
package/bin/patchcord.mjs
CHANGED
|
@@ -60,8 +60,11 @@ if (cmd === "install") {
|
|
|
60
60
|
const installed = run(`claude plugin list`)?.includes("patchcord");
|
|
61
61
|
installed ? run(`claude plugin update patchcord`) : run(`claude plugin install patchcord`);
|
|
62
62
|
|
|
63
|
+
console.log(` ✓ Plugin installed`);
|
|
64
|
+
|
|
63
65
|
// Block OAuth tool leakage from claude.ai web connector
|
|
64
66
|
const claudeSettings = join(process.env.HOME || "", ".claude", "settings.json");
|
|
67
|
+
let settingsOk = false;
|
|
65
68
|
if (existsSync(claudeSettings)) {
|
|
66
69
|
try {
|
|
67
70
|
const settings = JSON.parse(readFileSync(claudeSettings, "utf-8"));
|
|
@@ -69,12 +72,10 @@ if (cmd === "install") {
|
|
|
69
72
|
if (!settings.permissions.allow) settings.permissions.allow = [];
|
|
70
73
|
if (!settings.permissions.deny) settings.permissions.deny = [];
|
|
71
74
|
let changed = false;
|
|
72
|
-
// Allow patchcord MCP tools without prompts
|
|
73
75
|
if (!settings.permissions.allow.includes("mcp__patchcord__*")) {
|
|
74
76
|
settings.permissions.allow.push("mcp__patchcord__*");
|
|
75
77
|
changed = true;
|
|
76
78
|
}
|
|
77
|
-
// Block OAuth tool leakage (both casings)
|
|
78
79
|
for (const pattern of ["mcp__claude_ai_Patchcord__*", "mcp__claude_ai_patchcord__*"]) {
|
|
79
80
|
if (!settings.permissions.deny.includes(pattern)) {
|
|
80
81
|
settings.permissions.deny.push(pattern);
|
|
@@ -84,20 +85,24 @@ if (cmd === "install") {
|
|
|
84
85
|
if (changed) {
|
|
85
86
|
writeFileSync(claudeSettings, JSON.stringify(settings, null, 2) + "\n");
|
|
86
87
|
}
|
|
87
|
-
|
|
88
|
-
|
|
88
|
+
settingsOk = true;
|
|
89
|
+
console.log(` ✓ Permissions configured`);
|
|
90
|
+
} catch (e) {
|
|
91
|
+
console.log(` ✗ Settings update failed — invalid JSON in ${claudeSettings}`);
|
|
92
|
+
console.log(` ${e.message}`);
|
|
93
|
+
console.log(` Fix the JSON and re-run: npx patchcord@latest install`);
|
|
89
94
|
}
|
|
90
95
|
}
|
|
91
96
|
|
|
92
97
|
// Enable statusline
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
98
|
+
if (settingsOk) {
|
|
99
|
+
const enableScript = join(pluginRoot, "scripts", "enable-statusline.sh");
|
|
100
|
+
if (existsSync(enableScript)) {
|
|
101
|
+
const slArg = fullStatusline ? " --full" : "";
|
|
102
|
+
run(`bash "${enableScript}"${slArg}`);
|
|
103
|
+
console.log(` ✓ Statusline${fullStatusline ? " (full)" : ""} enabled`);
|
|
104
|
+
}
|
|
97
105
|
}
|
|
98
|
-
|
|
99
|
-
console.log(` ✓ Plugin installed${fullStatusline ? " (full statusline)" : ""}`);
|
|
100
|
-
console.log(` ✓ OAuth tool leakage blocked`);
|
|
101
106
|
installedSomething = true;
|
|
102
107
|
}
|
|
103
108
|
|
package/package.json
CHANGED
package/scripts/statusline.sh
CHANGED
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
# Patchcord statusline for Claude Code.
|
|
3
|
-
#
|
|
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
|
-
# ──
|
|
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
|
-
|
|
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 ||
|
|
113
|
-
if [
|
|
114
|
-
pc_data="
|
|
115
|
-
echo "$
|
|
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
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
-
|
|
134
|
-
|
|
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
|
-
|
|
139
|
-
|
|
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
|
-
#
|
|
145
|
-
|
|
146
|
-
|
|
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
|
-
|
|
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+="${
|
|
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 ──────────────────────────────────────────────
|