patchcord 0.2.5 → 0.2.6

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.
Files changed (2) hide show
  1. package/bin/patchcord.mjs +128 -0
  2. package/package.json +1 -1
package/bin/patchcord.mjs CHANGED
@@ -25,6 +25,8 @@ Commands:
25
25
  patchcord install --full Install + enable full statusline (model, context%, git)
26
26
  patchcord agent Set up MCP config for an agent in this project
27
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
28
30
 
29
31
  Run "patchcord install" once. Run "patchcord agent" in each project.`);
30
32
  process.exit(0);
@@ -155,5 +157,131 @@ if (cmd === "init") {
155
157
  process.exit(0);
156
158
  }
157
159
 
160
+ // ── skill: custom skill management ───────────────────────────
161
+ if (cmd === "skill") {
162
+ const sub = process.argv[3];
163
+ const cwd = process.cwd();
164
+
165
+ // Find .mcp.json to get URL and token
166
+ let mcpJson = null;
167
+ let dir = cwd;
168
+ while (dir && dir !== "/") {
169
+ const p = join(dir, ".mcp.json");
170
+ if (existsSync(p)) { mcpJson = p; break; }
171
+ dir = dirname(dir);
172
+ }
173
+
174
+ if (!mcpJson) {
175
+ console.error("No .mcp.json found. Run 'patchcord agent' first.");
176
+ process.exit(1);
177
+ }
178
+
179
+ const { readFileSync, writeFileSync } = await import("fs");
180
+ const config = JSON.parse(readFileSync(mcpJson, "utf-8"));
181
+ const mcpUrl = config?.mcpServers?.patchcord?.url || "";
182
+ const auth = config?.mcpServers?.patchcord?.headers?.Authorization || "";
183
+ const baseUrl = mcpUrl.replace(/\/mcp(\/bearer)?$/, "");
184
+ const token = auth.replace(/^Bearer\s+/, "");
185
+
186
+ if (!baseUrl || !token) {
187
+ console.error("Cannot read patchcord URL/token from .mcp.json");
188
+ process.exit(1);
189
+ }
190
+
191
+ // Derive namespace and agent from the token by calling /api/inbox
192
+ let namespace = "", agentId = "";
193
+ try {
194
+ const resp = run(`curl -s -H "Authorization: Bearer ${token}" "${baseUrl}/api/inbox?limit=0"`);
195
+ if (resp) {
196
+ const data = JSON.parse(resp);
197
+ namespace = data.namespace_id || "";
198
+ agentId = data.agent_id || "";
199
+ }
200
+ } catch {}
201
+
202
+ if (!namespace || !agentId) {
203
+ console.error("Cannot determine agent identity. Check your token.");
204
+ process.exit(1);
205
+ }
206
+
207
+ const START_DELIM = "########## PATCHCORD CUSTOM SKILL ##########";
208
+ const END_DELIM = "########## END CUSTOM SKILL ##########";
209
+
210
+ // Find the skill file
211
+ const skillFile = join(cwd, "PATCHCORD.md");
212
+ const pluginSkill = join(pluginRoot, "skills", "inbox", "SKILL.md");
213
+
214
+ function applyCustomSkill(skillText) {
215
+ let content = "";
216
+ if (existsSync(skillFile)) {
217
+ content = readFileSync(skillFile, "utf-8");
218
+ } else if (existsSync(pluginSkill)) {
219
+ content = readFileSync(pluginSkill, "utf-8");
220
+ }
221
+
222
+ // Remove existing custom skill block
223
+ const startIdx = content.indexOf(START_DELIM);
224
+ const endIdx = content.indexOf(END_DELIM);
225
+ if (startIdx !== -1 && endIdx !== -1) {
226
+ content = content.substring(0, startIdx).trimEnd();
227
+ }
228
+
229
+ // Append new custom skill
230
+ if (skillText && skillText.trim()) {
231
+ content = content.trimEnd() + "\n\n" + START_DELIM + "\n" + skillText.trim() + "\n" + END_DELIM + "\n";
232
+ }
233
+
234
+ writeFileSync(skillFile, content);
235
+ }
236
+
237
+ if (sub === "apply" || !sub) {
238
+ // Fetch and apply custom skill
239
+ console.log(`Fetching custom skill for ${namespace}:${agentId}...`);
240
+ const resp = run(`curl -s -H "Authorization: Bearer ${token}" "${baseUrl}/api/skills/${namespace}/${agentId}"`);
241
+ if (!resp) {
242
+ console.log("No custom skill found or server unreachable.");
243
+ process.exit(0);
244
+ }
245
+ try {
246
+ const data = JSON.parse(resp);
247
+ if (data.skill_text) {
248
+ applyCustomSkill(data.skill_text);
249
+ console.log(`✓ Custom skill applied to ${skillFile}`);
250
+ } else {
251
+ console.log("No custom skill set for this agent.");
252
+ }
253
+ } catch {
254
+ console.error("Failed to parse skill response.");
255
+ process.exit(1);
256
+ }
257
+ } else if (sub === "reinstall") {
258
+ // Full rewrite: default skill + custom skill
259
+ console.log("Reinstalling patchcord skill...");
260
+ if (existsSync(pluginSkill)) {
261
+ let defaultContent = readFileSync(pluginSkill, "utf-8");
262
+ writeFileSync(skillFile, defaultContent);
263
+ }
264
+ // Then apply custom on top
265
+ const resp = run(`curl -s -H "Authorization: Bearer ${token}" "${baseUrl}/api/skills/${namespace}/${agentId}"`);
266
+ try {
267
+ const data = JSON.parse(resp || "{}");
268
+ if (data.skill_text) {
269
+ applyCustomSkill(data.skill_text);
270
+ console.log(`✓ Skill reinstalled with custom block at ${skillFile}`);
271
+ } else {
272
+ console.log(`✓ Skill reinstalled (no custom block) at ${skillFile}`);
273
+ }
274
+ } catch {
275
+ console.log(`✓ Skill reinstalled (no custom block) at ${skillFile}`);
276
+ }
277
+ } else {
278
+ console.log(`Unknown skill subcommand: ${sub}
279
+ Usage:
280
+ patchcord skill apply Fetch and apply custom skill from server
281
+ patchcord skill reinstall Full rewrite: default + custom skill`);
282
+ }
283
+ process.exit(0);
284
+ }
285
+
158
286
  console.error(`Unknown command: ${cmd}. Run 'patchcord help' for usage.`);
159
287
  process.exit(1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "patchcord",
3
- "version": "0.2.5",
3
+ "version": "0.2.6",
4
4
  "description": "Cross-machine agent messaging for Claude Code and Codex",
5
5
  "author": "ppravdin",
6
6
  "license": "MIT",