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.
@@ -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.6",
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 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,125 +35,158 @@ 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);
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
- // Disable patchcord as Codex ChatGPT app (prevents OAuth identity conflict)
90
+ // ── Codex CLI ──
81
91
  const codexConfig = join(process.env.HOME || "", ".codex", "config.toml");
82
92
  if (existsSync(codexConfig)) {
83
- const { readFileSync, writeFileSync } = await import("fs");
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
- // Enable statusline
92
- const enableScript = join(pluginRoot, "scripts", "enable-statusline.sh");
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
- ${fullStatusline
100
- ? "Statusline shows: model context% │ repo (branch) │ agent@namespace │ inbox"
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
- Run "patchcord agent" in each project to set up MCP.`);
104
- } else {
105
- console.log(`✓ Plugin installed. Statusline setup skipped (non-fatal).
108
+ Install one first:
109
+ Claude Code: https://claude.ai/code
110
+ Codex CLI: npm install -g @openai/codex
106
111
 
107
- Run "patchcord agent" in each project to set up MCP.`);
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
- if (flag === "--codex" || (!flag && existsSync(join(cwd, ".agents")))) {
123
- // Codex: copy skill + print MCP config
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
- Add to ~/.codex/config.toml:
130
-
131
- [mcp_servers.patchcord]
132
- url = "https://YOUR_SERVER/mcp"
133
- bearer_token_env_var = "PATCHCORD_TOKEN"
134
- http_headers = { "X-Patchcord-Client-Type" = "codex" }`);
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: print .mcp.json template
137
- console.log(`Add .mcp.json to this project:
138
-
139
- {
140
- "mcpServers": {
141
- "patchcord": {
142
- "type": "http",
143
- "url": "https://YOUR_SERVER/mcp",
144
- "headers": {
145
- "Authorization": "Bearer YOUR_TOKEN",
146
- "X-Patchcord-Client-Type": "claude_code"
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
- Or use the CLI:
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 Install/update plugin globally (once)
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 management ───────────────────────────
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(`Unknown skill subcommand: ${sub}
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)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "patchcord",
3
- "version": "0.3.6",
3
+ "version": "0.3.8",
4
4
  "description": "Cross-machine agent messaging for Claude Code and Codex",
5
5
  "author": "ppravdin",
6
6
  "license": "MIT",