patchcord 0.3.40 → 0.3.42

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 +69 -27
  2. package/package.json +1 -1
package/bin/patchcord.mjs CHANGED
@@ -4,6 +4,9 @@ import { existsSync, mkdirSync, cpSync } from "fs";
4
4
  import { join, dirname } from "path";
5
5
  import { fileURLToPath } from "url";
6
6
  import { execSync } from "child_process";
7
+ import { homedir } from "os";
8
+
9
+ const HOME = homedir();
7
10
 
8
11
  const __dirname = dirname(fileURLToPath(import.meta.url));
9
12
  const pluginRoot = join(__dirname, "..");
@@ -71,7 +74,7 @@ if (!cmd || cmd === "install" || cmd === "agent") {
71
74
  globalChanges.push("Claude Code plugin installed");
72
75
  }
73
76
 
74
- const claudeSettings = join(process.env.HOME || "", ".claude", "settings.json");
77
+ const claudeSettings = join(HOME, ".claude", "settings.json");
75
78
  if (existsSync(claudeSettings)) {
76
79
  try {
77
80
  const settings = JSON.parse(readFileSync(claudeSettings, "utf-8"));
@@ -109,7 +112,7 @@ if (!cmd || cmd === "install" || cmd === "agent") {
109
112
  }
110
113
 
111
114
  // Cursor
112
- const cursorSkillsRoot = join(process.env.HOME || "", ".cursor", "skills-cursor");
115
+ const cursorSkillsRoot = join(HOME, ".cursor", "skills-cursor");
113
116
  if (existsSync(cursorSkillsRoot)) {
114
117
  const cursorSkillDir = join(cursorSkillsRoot, "patchcord");
115
118
  const cursorWaitDir = join(cursorSkillsRoot, "patchcord-wait");
@@ -128,9 +131,9 @@ if (!cmd || cmd === "install" || cmd === "agent") {
128
131
  }
129
132
 
130
133
  // Windsurf
131
- if (existsSync(join(process.env.HOME || "", ".codeium", "windsurf"))) {
132
- const windsurfSkillDir = join(process.env.HOME || "", ".codeium", "windsurf", "skills", "patchcord");
133
- const windsurfWaitDir = join(process.env.HOME || "", ".codeium", "windsurf", "skills", "patchcord-wait");
134
+ if (existsSync(join(HOME, ".codeium", "windsurf"))) {
135
+ const windsurfSkillDir = join(HOME, ".codeium", "windsurf", "skills", "patchcord");
136
+ const windsurfWaitDir = join(HOME, ".codeium", "windsurf", "skills", "patchcord-wait");
134
137
  let windsurfChanged = false;
135
138
  if (!existsSync(windsurfSkillDir)) {
136
139
  mkdirSync(windsurfSkillDir, { recursive: true });
@@ -146,10 +149,10 @@ if (!cmd || cmd === "install" || cmd === "agent") {
146
149
  }
147
150
 
148
151
  // Gemini CLI
149
- if (existsSync(join(process.env.HOME || "", ".gemini"))) {
150
- const geminiSkillDir = join(process.env.HOME || "", ".gemini", "skills", "patchcord");
151
- const geminiWaitDir = join(process.env.HOME || "", ".gemini", "skills", "patchcord-wait");
152
- const geminiCmdDir = join(process.env.HOME || "", ".gemini", "commands");
152
+ if (existsSync(join(HOME, ".gemini"))) {
153
+ const geminiSkillDir = join(HOME, ".gemini", "skills", "patchcord");
154
+ const geminiWaitDir = join(HOME, ".gemini", "skills", "patchcord-wait");
155
+ const geminiCmdDir = join(HOME, ".gemini", "commands");
153
156
  let geminiChanged = false;
154
157
  if (!existsSync(geminiSkillDir)) {
155
158
  mkdirSync(geminiSkillDir, { recursive: true });
@@ -171,7 +174,7 @@ if (!cmd || cmd === "install" || cmd === "agent") {
171
174
  }
172
175
 
173
176
  // Codex CLI
174
- const codexConfig = join(process.env.HOME || "", ".codex", "config.toml");
177
+ const codexConfig = join(HOME, ".codex", "config.toml");
175
178
  if (existsSync(codexConfig)) {
176
179
  const content = readFileSync(codexConfig, "utf-8");
177
180
  if (!content.includes("[apps.patchcord]")) {
@@ -207,17 +210,19 @@ if (!cmd || cmd === "install" || cmd === "agent") {
207
210
  console.log(` ${cyan}4.${r} Windsurf`);
208
211
  console.log(` ${cyan}5.${r} Gemini CLI`);
209
212
  console.log(` ${cyan}6.${r} VS Code (Copilot)`);
210
- console.log(` ${cyan}7.${r} Zed\n`);
213
+ console.log(` ${cyan}7.${r} Zed`);
214
+ console.log(` ${cyan}8.${r} OpenCode\n`);
211
215
 
212
- const choice = (await ask(`${dim}Choose (1-7):${r} `)).trim();
216
+ const choice = (await ask(`${dim}Choose (1-8):${r} `)).trim();
213
217
  const isCodex = choice === "2";
214
218
  const isCursor = choice === "3";
215
219
  const isWindsurf = choice === "4";
216
220
  const isGemini = choice === "5";
217
221
  const isVSCode = choice === "6";
218
222
  const isZed = choice === "7";
223
+ const isOpenCode = choice === "8";
219
224
 
220
- if (!["1", "2", "3", "4", "5", "6", "7"].includes(choice)) {
225
+ if (!["1", "2", "3", "4", "5", "6", "7", "8"].includes(choice)) {
221
226
  console.error("Invalid choice.");
222
227
  rl.close();
223
228
  process.exit(1);
@@ -273,7 +278,7 @@ if (!cmd || cmd === "install" || cmd === "agent") {
273
278
  } catch {}
274
279
  }
275
280
  // Warn about global config conflict
276
- const globalCursor = join(process.env.HOME || "", ".cursor", "mcp.json");
281
+ const globalCursor = join(HOME, ".cursor", "mcp.json");
277
282
  if (existsSync(globalCursor)) {
278
283
  try {
279
284
  const global = JSON.parse(readFileSync(globalCursor, "utf-8"));
@@ -286,7 +291,7 @@ if (!cmd || cmd === "install" || cmd === "agent") {
286
291
  } catch {}
287
292
  }
288
293
  } else if (isWindsurf) {
289
- const wsPath = join(process.env.HOME || "", ".codeium", "windsurf", "mcp_config.json");
294
+ const wsPath = join(HOME, ".codeium", "windsurf", "mcp_config.json");
290
295
  if (existsSync(wsPath)) {
291
296
  try {
292
297
  const content = readFileSync(wsPath, "utf-8").trim();
@@ -304,7 +309,7 @@ if (!cmd || cmd === "install" || cmd === "agent") {
304
309
  } catch {}
305
310
  }
306
311
  } else if (isGemini) {
307
- const geminiPath = join(process.env.HOME || "", ".gemini", "settings.json");
312
+ const geminiPath = join(HOME, ".gemini", "settings.json");
308
313
  if (existsSync(geminiPath)) {
309
314
  try {
310
315
  const existing = JSON.parse(readFileSync(geminiPath, "utf-8"));
@@ -339,8 +344,8 @@ if (!cmd || cmd === "install" || cmd === "agent") {
339
344
  }
340
345
  } else if (isZed) {
341
346
  const zedPath = process.platform === "darwin"
342
- ? join(process.env.HOME || "", "Library", "Application Support", "Zed", "settings.json")
343
- : join(process.env.HOME || "", ".config", "zed", "settings.json");
347
+ ? join(HOME, "Library", "Application Support", "Zed", "settings.json")
348
+ : join(HOME, ".config", "zed", "settings.json");
344
349
  if (existsSync(zedPath)) {
345
350
  try {
346
351
  const existing = JSON.parse(readFileSync(zedPath, "utf-8"));
@@ -356,6 +361,23 @@ if (!cmd || cmd === "install" || cmd === "agent") {
356
361
  }
357
362
  } catch {}
358
363
  }
364
+ } else if (isOpenCode) {
365
+ const ocPath = join(cwd, "opencode.json");
366
+ if (existsSync(ocPath)) {
367
+ try {
368
+ const existing = JSON.parse(readFileSync(ocPath, "utf-8"));
369
+ if (existing.mcp?.patchcord) {
370
+ console.log(`\n ${yellow}⚠ OpenCode already configured in this project${r}`);
371
+ console.log(` ${dim}${ocPath}${r}`);
372
+ const replace = (await ask(` ${dim}Replace? (y/N):${r} `)).trim().toLowerCase();
373
+ if (replace !== "y" && replace !== "yes") {
374
+ console.log("Keeping existing config.");
375
+ rl.close();
376
+ process.exit(0);
377
+ }
378
+ }
379
+ } catch {}
380
+ }
359
381
  } else if (isCodex) {
360
382
  const configPath = join(cwd, ".codex", "config.toml");
361
383
  if (existsSync(configPath)) {
@@ -468,7 +490,7 @@ if (!cmd || cmd === "install" || cmd === "agent") {
468
490
  console.log(` ${dim}Per-project only — other projects won't see this agent.${r}`);
469
491
  } else if (isWindsurf) {
470
492
  // Windsurf: global only (~/.codeium/windsurf/mcp_config.json)
471
- const wsPath = join(process.env.HOME || "", ".codeium", "windsurf", "mcp_config.json");
493
+ const wsPath = join(HOME, ".codeium", "windsurf", "mcp_config.json");
472
494
  const wsConfig = {
473
495
  mcpServers: {
474
496
  patchcord: {
@@ -496,7 +518,7 @@ if (!cmd || cmd === "install" || cmd === "agent") {
496
518
  writeFileSync(wsPath, JSON.stringify(wsConfig, null, 2) + "\n");
497
519
  }
498
520
  } else {
499
- mkdirSync(join(process.env.HOME || "", ".codeium", "windsurf"), { recursive: true });
521
+ mkdirSync(join(HOME, ".codeium", "windsurf"), { recursive: true });
500
522
  writeFileSync(wsPath, JSON.stringify(wsConfig, null, 2) + "\n");
501
523
  }
502
524
  // Install workflows as slash commands (.windsurf/workflows/) — per-project
@@ -509,7 +531,7 @@ if (!cmd || cmd === "install" || cmd === "agent") {
509
531
  console.log(` ${yellow}MCP config is global — all Windsurf projects share this agent.${r}`);
510
532
  } else if (isGemini) {
511
533
  // Gemini CLI: global only (~/.gemini/settings.json)
512
- const geminiPath = join(process.env.HOME || "", ".gemini", "settings.json");
534
+ const geminiPath = join(HOME, ".gemini", "settings.json");
513
535
  let geminiSettings = {};
514
536
  if (existsSync(geminiPath)) {
515
537
  try {
@@ -529,15 +551,15 @@ if (!cmd || cmd === "install" || cmd === "agent") {
529
551
  geminiSettings.tools.allowed = geminiSettings.tools.allowed.filter(t => !t.startsWith("mcp_patchcord_"));
530
552
  if (geminiSettings.tools.allowed.length === 0) delete geminiSettings.tools;
531
553
  }
532
- mkdirSync(join(process.env.HOME || "", ".gemini"), { recursive: true });
554
+ mkdirSync(join(HOME, ".gemini"), { recursive: true });
533
555
  writeFileSync(geminiPath, JSON.stringify(geminiSettings, null, 2) + "\n");
534
556
  console.log(`\n ${green}✓${r} Gemini CLI configured: ${dim}${geminiPath}${r}`);
535
557
  console.log(` ${yellow}Global config — all Gemini CLI projects share this agent.${r}`);
536
558
  } else if (isZed) {
537
559
  // Zed: global settings.json → context_servers
538
560
  const zedPath = process.platform === "darwin"
539
- ? join(process.env.HOME || "", "Library", "Application Support", "Zed", "settings.json")
540
- : join(process.env.HOME || "", ".config", "zed", "settings.json");
561
+ ? join(HOME, "Library", "Application Support", "Zed", "settings.json")
562
+ : join(HOME, ".config", "zed", "settings.json");
541
563
  let zedSettings = {};
542
564
  if (existsSync(zedPath)) {
543
565
  try {
@@ -553,12 +575,32 @@ if (!cmd || cmd === "install" || cmd === "agent") {
553
575
  },
554
576
  };
555
577
  const zedDir = process.platform === "darwin"
556
- ? join(process.env.HOME || "", "Library", "Application Support", "Zed")
557
- : join(process.env.HOME || "", ".config", "zed");
578
+ ? join(HOME, "Library", "Application Support", "Zed")
579
+ : join(HOME, ".config", "zed");
558
580
  mkdirSync(zedDir, { recursive: true });
559
581
  writeFileSync(zedPath, JSON.stringify(zedSettings, null, 2) + "\n");
560
582
  console.log(`\n ${green}✓${r} Zed configured: ${dim}${zedPath}${r}`);
561
583
  console.log(` ${yellow}Global config — all Zed projects share this agent.${r}`);
584
+ } else if (isOpenCode) {
585
+ // OpenCode: per-project opencode.json → mcp
586
+ const ocPath = join(cwd, "opencode.json");
587
+ let ocConfig = {};
588
+ if (existsSync(ocPath)) {
589
+ try {
590
+ ocConfig = JSON.parse(readFileSync(ocPath, "utf-8"));
591
+ } catch {}
592
+ }
593
+ if (!ocConfig.mcp) ocConfig.mcp = {};
594
+ ocConfig.mcp.patchcord = {
595
+ type: "remote",
596
+ url: `${serverUrl}/mcp`,
597
+ headers: {
598
+ Authorization: `Bearer ${token}`,
599
+ "X-Patchcord-Machine": hostname,
600
+ },
601
+ };
602
+ writeFileSync(ocPath, JSON.stringify(ocConfig, null, 2) + "\n");
603
+ console.log(`\n ${green}✓${r} OpenCode configured: ${dim}${ocPath}${r}`);
562
604
  } else if (isVSCode) {
563
605
  // VS Code: write .vscode/mcp.json (per-project)
564
606
  const vscodeDir = join(cwd, ".vscode");
@@ -654,7 +696,7 @@ if (!cmd || cmd === "install" || cmd === "agent") {
654
696
  console.log(`\n ${green}✓${r} Claude Code configured: ${dim}${mcpPath}${r}`);
655
697
  }
656
698
 
657
- const toolName = isZed ? "Zed" : isVSCode ? "VS Code" : isGemini ? "Gemini CLI" : isWindsurf ? "Windsurf" : isCursor ? "Cursor" : isCodex ? "Codex" : "Claude Code";
699
+ const toolName = isOpenCode ? "OpenCode" : isZed ? "Zed" : isVSCode ? "VS Code" : isGemini ? "Gemini CLI" : isWindsurf ? "Windsurf" : isCursor ? "Cursor" : isCodex ? "Codex" : "Claude Code";
658
700
  console.log(`\n${dim}Restart your ${toolName} session, then run:${r} ${bold}inbox()${r}`);
659
701
  process.exit(0);
660
702
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "patchcord",
3
- "version": "0.3.40",
3
+ "version": "0.3.42",
4
4
  "description": "Cross-machine agent messaging for Claude Code and Codex",
5
5
  "author": "ppravdin",
6
6
  "license": "MIT",