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.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +0 -154
- package/bin/patchcord.mjs +84 -98
- package/package.json +1 -1
- package/scripts/statusline.sh +50 -154
|
@@ -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.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
|
|
25
|
-
patchcord install --full
|
|
26
|
-
patchcord agent Set up MCP config for
|
|
27
|
-
patchcord
|
|
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
|
|
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
|
-
|
|
44
|
+
let installedSomething = false;
|
|
55
45
|
|
|
56
|
-
//
|
|
57
|
-
const
|
|
58
|
-
if (
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
-
|
|
89
|
-
|
|
90
|
-
|
|
86
|
+
console.log(`✓ Claude Code plugin installed.${fullStatusline ? " Full statusline enabled." : ""}`);
|
|
87
|
+
installedSomething = true;
|
|
88
|
+
}
|
|
91
89
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
112
|
-
|
|
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
|
|
134
|
+
Add to .codex/config.toml in this project:
|
|
119
135
|
|
|
120
136
|
[mcp_servers.patchcord]
|
|
121
|
-
url = "https://YOUR_SERVER/mcp"
|
|
122
|
-
|
|
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
|
|
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
|
|
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(`
|
|
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
package/scripts/statusline.sh
CHANGED
|
@@ -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
|
-
# ──
|
|
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
|
-
|
|
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
|
-
|
|
171
|
-
|
|
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
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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+="${
|
|
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 ──────────────────────────────────────────────
|