patchcord 0.3.6 → 0.3.8
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 +124 -120
- package/package.json +1 -1
|
@@ -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.8",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "ppravdin"
|
|
7
7
|
},
|
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,125 +35,158 @@ 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
|
-
|
|
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
|
+
}
|
|
78
|
+
|
|
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
|
+
}
|
|
85
|
+
|
|
86
|
+
console.log(`✓ Claude Code plugin installed.${fullStatusline ? " Full statusline enabled." : ""}`);
|
|
87
|
+
installedSomething = true;
|
|
78
88
|
}
|
|
79
89
|
|
|
80
|
-
//
|
|
90
|
+
// ── Codex CLI ──
|
|
81
91
|
const codexConfig = join(process.env.HOME || "", ".codex", "config.toml");
|
|
82
92
|
if (existsSync(codexConfig)) {
|
|
83
|
-
|
|
93
|
+
console.log("Found Codex CLI config.");
|
|
94
|
+
|
|
95
|
+
// Disable patchcord as ChatGPT app (prevents OAuth identity conflict)
|
|
84
96
|
const content = readFileSync(codexConfig, "utf-8");
|
|
85
97
|
if (!content.includes("[apps.patchcord]")) {
|
|
86
98
|
writeFileSync(codexConfig, content.trimEnd() + "\n\n[apps.patchcord]\nenabled = false\n");
|
|
87
99
|
console.log("✓ Disabled patchcord ChatGPT app in Codex (prevents identity conflict).");
|
|
88
100
|
}
|
|
89
|
-
}
|
|
90
101
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
if (existsSync(enableScript)) {
|
|
94
|
-
const slArg = fullStatusline ? " --full" : "";
|
|
95
|
-
const slResult = run(`bash "${enableScript}"${slArg}`);
|
|
96
|
-
if (slResult !== null) {
|
|
97
|
-
console.log(`✓ Plugin installed. Statusline${fullStatusline ? " (full)" : ""} enabled.
|
|
102
|
+
installedSomething = true;
|
|
103
|
+
}
|
|
98
104
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
: "Statusline shows: agent@namespace │ inbox\n Tip: run \"patchcord install --full\" for model, context%, git info too."}
|
|
105
|
+
if (!installedSomething) {
|
|
106
|
+
console.log(`No Claude Code or Codex CLI detected.
|
|
102
107
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
108
|
+
Install one first:
|
|
109
|
+
Claude Code: https://claude.ai/code
|
|
110
|
+
Codex CLI: npm install -g @openai/codex
|
|
106
111
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
} else {
|
|
110
|
-
console.log(`✓ Plugin installed.
|
|
111
|
-
|
|
112
|
-
Run "patchcord agent" in each project to set up MCP.`);
|
|
112
|
+
Then run: npx patchcord@latest install`);
|
|
113
|
+
process.exit(1);
|
|
113
114
|
}
|
|
115
|
+
|
|
116
|
+
console.log(`\nRun "npx patchcord@latest agent" in each project to set up MCP.`);
|
|
114
117
|
process.exit(0);
|
|
115
118
|
}
|
|
116
119
|
|
|
117
|
-
// ── agent: per-project MCP setup
|
|
120
|
+
// ── agent: per-project MCP setup (interactive, auto-detects tool) ──
|
|
118
121
|
if (cmd === "agent") {
|
|
119
|
-
const flag = process.argv[3];
|
|
120
122
|
const cwd = process.cwd();
|
|
123
|
+
const { readFileSync, writeFileSync } = await import("fs");
|
|
124
|
+
const { createInterface } = await import("readline");
|
|
121
125
|
|
|
122
|
-
|
|
123
|
-
|
|
126
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
127
|
+
const ask = (q) => new Promise((resolve) => rl.question(q, resolve));
|
|
128
|
+
|
|
129
|
+
// Ask for server URL with default
|
|
130
|
+
const serverUrl = (await ask("Server URL [https://mcp.patchcord.dev]: ")).trim() || "https://mcp.patchcord.dev";
|
|
131
|
+
|
|
132
|
+
// Ask for token
|
|
133
|
+
const token = (await ask("Paste your agent token: ")).trim();
|
|
134
|
+
rl.close();
|
|
135
|
+
|
|
136
|
+
if (!token) {
|
|
137
|
+
console.error("Token is required. Get one from your patchcord dashboard.");
|
|
138
|
+
process.exit(1);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Auto-detect: Codex project has .agents folder or .codex folder
|
|
142
|
+
const isCodex = existsSync(join(cwd, ".agents")) || existsSync(join(cwd, ".codex"));
|
|
143
|
+
|
|
144
|
+
if (isCodex) {
|
|
145
|
+
// Codex: copy skill + write config
|
|
124
146
|
const dest = join(cwd, ".agents", "skills", "patchcord");
|
|
125
147
|
mkdirSync(dest, { recursive: true });
|
|
126
148
|
cpSync(join(pluginRoot, "codex", "SKILL.md"), join(dest, "SKILL.md"));
|
|
127
|
-
console.log(`✓ Codex skill installed: ${dest}/SKILL.md
|
|
128
149
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
150
|
+
const codexDir = join(cwd, ".codex");
|
|
151
|
+
mkdirSync(codexDir, { recursive: true });
|
|
152
|
+
const configPath = join(codexDir, "config.toml");
|
|
153
|
+
let existing = existsSync(configPath) ? readFileSync(configPath, "utf-8") : "";
|
|
154
|
+
if (!existing.includes("[mcp_servers.patchcord]")) {
|
|
155
|
+
existing = existing.trimEnd() + `\n\n[mcp_servers.patchcord]\nurl = "${serverUrl}/mcp/bearer"\nhttp_headers = { "Authorization" = "Bearer ${token}", "X-Patchcord-Client-Type" = "codex" }\n`;
|
|
156
|
+
writeFileSync(configPath, existing);
|
|
157
|
+
}
|
|
158
|
+
console.log(`✓ Codex configured: ${configPath}\n✓ Skill installed: ${dest}/SKILL.md`);
|
|
135
159
|
} else {
|
|
136
|
-
// Claude Code:
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
160
|
+
// Claude Code: write .mcp.json
|
|
161
|
+
const mcpPath = join(cwd, ".mcp.json");
|
|
162
|
+
const mcpConfig = {
|
|
163
|
+
mcpServers: {
|
|
164
|
+
patchcord: {
|
|
165
|
+
type: "http",
|
|
166
|
+
url: `${serverUrl}/mcp`,
|
|
167
|
+
headers: {
|
|
168
|
+
Authorization: `Bearer ${token}`,
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
if (existsSync(mcpPath)) {
|
|
175
|
+
try {
|
|
176
|
+
const existing = JSON.parse(readFileSync(mcpPath, "utf-8"));
|
|
177
|
+
existing.mcpServers = existing.mcpServers || {};
|
|
178
|
+
existing.mcpServers.patchcord = mcpConfig.mcpServers.patchcord;
|
|
179
|
+
writeFileSync(mcpPath, JSON.stringify(existing, null, 2) + "\n");
|
|
180
|
+
} catch {
|
|
181
|
+
writeFileSync(mcpPath, JSON.stringify(mcpConfig, null, 2) + "\n");
|
|
148
182
|
}
|
|
183
|
+
} else {
|
|
184
|
+
writeFileSync(mcpPath, JSON.stringify(mcpConfig, null, 2) + "\n");
|
|
149
185
|
}
|
|
186
|
+
console.log(`✓ Claude Code configured: ${mcpPath}`);
|
|
150
187
|
}
|
|
151
188
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
claude mcp add patchcord "https://YOUR_SERVER/mcp" \\
|
|
155
|
-
--transport http -s project \\
|
|
156
|
-
-H "Authorization: Bearer YOUR_TOKEN" \\
|
|
157
|
-
-H "X-Patchcord-Client-Type: claude_code"`);
|
|
158
|
-
}
|
|
189
|
+
console.log("\nRestart your session, then run: inbox()");
|
|
159
190
|
process.exit(0);
|
|
160
191
|
}
|
|
161
192
|
|
|
@@ -163,12 +194,12 @@ Or use the CLI:
|
|
|
163
194
|
if (cmd === "init") {
|
|
164
195
|
console.log(`"patchcord init" is now two commands:
|
|
165
196
|
|
|
166
|
-
patchcord install
|
|
197
|
+
patchcord install One-time global setup (once)
|
|
167
198
|
patchcord agent Set up MCP for this project (per project)`);
|
|
168
199
|
process.exit(0);
|
|
169
200
|
}
|
|
170
201
|
|
|
171
|
-
// ── skill: custom skill
|
|
202
|
+
// ── skill: custom skill from web console ─────────────────────
|
|
172
203
|
if (cmd === "skill") {
|
|
173
204
|
const sub = process.argv[3];
|
|
174
205
|
const cwd = process.cwd();
|
|
@@ -215,9 +246,6 @@ if (cmd === "skill") {
|
|
|
215
246
|
process.exit(1);
|
|
216
247
|
}
|
|
217
248
|
|
|
218
|
-
// Custom skill goes to .claude/skills/patchcord-custom/SKILL.md
|
|
219
|
-
// Claude Code auto-discovers project-level skills from this directory.
|
|
220
|
-
// Only the custom part — default patchcord skill is already loaded globally by the plugin.
|
|
221
249
|
const skillDir = join(cwd, ".claude", "skills", "patchcord-custom");
|
|
222
250
|
const skillFile = join(skillDir, "SKILL.md");
|
|
223
251
|
|
|
@@ -241,34 +269,10 @@ if (cmd === "skill") {
|
|
|
241
269
|
console.error("Failed to parse skill response.");
|
|
242
270
|
process.exit(1);
|
|
243
271
|
}
|
|
244
|
-
} else if (sub === "reinstall") {
|
|
245
|
-
console.log(`Fetching custom skill for ${namespace}:${agentId}...`);
|
|
246
|
-
const resp = run(`curl -s -H "Authorization: Bearer ${token}" "${baseUrl}/api/skills/${namespace}/${agentId}"`);
|
|
247
|
-
try {
|
|
248
|
-
const data = JSON.parse(resp || "{}");
|
|
249
|
-
if (data.skill_text) {
|
|
250
|
-
mkdirSync(skillDir, { recursive: true });
|
|
251
|
-
writeFileSync(skillFile, data.skill_text.trim() + "\n");
|
|
252
|
-
console.log(`✓ Custom skill applied to ${skillFile}`);
|
|
253
|
-
} else {
|
|
254
|
-
// Remove custom skill if none set
|
|
255
|
-
if (existsSync(skillFile)) {
|
|
256
|
-
const { unlinkSync } = await import("fs");
|
|
257
|
-
unlinkSync(skillFile);
|
|
258
|
-
console.log("Custom skill removed (none set on server).");
|
|
259
|
-
} else {
|
|
260
|
-
console.log("No custom skill set for this agent.");
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
} catch {
|
|
264
|
-
console.log("No custom skill set or server unreachable.");
|
|
265
|
-
}
|
|
266
272
|
} else {
|
|
267
|
-
console.log(`
|
|
268
|
-
Usage:
|
|
269
|
-
patchcord skill apply Fetch and apply custom skill from server
|
|
270
|
-
patchcord skill reinstall Re-fetch custom skill from server`);
|
|
273
|
+
console.log(`Usage: patchcord skill apply`);
|
|
271
274
|
}
|
|
275
|
+
|
|
272
276
|
// Clean up old PATCHCORD.md if it exists
|
|
273
277
|
const oldFile = join(cwd, "PATCHCORD.md");
|
|
274
278
|
if (existsSync(oldFile)) {
|