patchcord 0.3.46 → 0.3.47

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 +37 -18
  2. package/package.json +1 -1
package/bin/patchcord.mjs CHANGED
@@ -24,6 +24,18 @@ function isSafeToken(t) {
24
24
  return /^[A-Za-z0-9_\-=+/.]+$/.test(t) && t.length < 200;
25
25
  }
26
26
 
27
+ function isSafeUrl(u) {
28
+ try {
29
+ const parsed = new URL(u);
30
+ return parsed.protocol === "https:" || parsed.protocol === "http:";
31
+ } catch { return false; }
32
+ }
33
+
34
+ function isSafeId(s) {
35
+ return /^[A-Za-z0-9_\-]+$/.test(s) && s.length < 100;
36
+ }
37
+
38
+
27
39
  if (cmd === "help" || cmd === "--help" || cmd === "-h") {
28
40
  console.log(`patchcord — agent messaging for AI coding agents
29
41
 
@@ -47,6 +59,16 @@ if (!cmd || cmd === "install" || cmd === "agent") {
47
59
  const fullStatusline = flags.includes("--full");
48
60
  const { readFileSync, writeFileSync } = await import("fs");
49
61
 
62
+ function safeReadJson(filePath) {
63
+ try {
64
+ let content = readFileSync(filePath, "utf-8");
65
+ // Strip JSONC comments (Zed, Gemini use JSONC)
66
+ content = content.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
67
+ content = content.replace(/,\s*([}\]])/g, "$1");
68
+ return JSON.parse(content);
69
+ } catch { return null; }
70
+ }
71
+
50
72
  console.log(`
51
73
  ___ ____ ___ ____ _ _ ____ ____ ____ ___
52
74
  |__] |__| | | |__| | | | |__/ | \\
@@ -341,7 +363,7 @@ if (!cmd || cmd === "install" || cmd === "agent") {
341
363
  const geminiPath = join(HOME, ".gemini", "settings.json");
342
364
  if (existsSync(geminiPath)) {
343
365
  try {
344
- const existing = JSON.parse(readFileSync(geminiPath, "utf-8"));
366
+ const existing = safeReadJson(geminiPath) || {};
345
367
  if (existing.mcpServers?.patchcord) {
346
368
  console.log(`\n ${yellow}⚠ Gemini CLI already configured${r}`);
347
369
  console.log(` ${dim}${geminiPath}${r}`);
@@ -377,7 +399,7 @@ if (!cmd || cmd === "install" || cmd === "agent") {
377
399
  : join(HOME, ".config", "zed", "settings.json");
378
400
  if (existsSync(zedPath)) {
379
401
  try {
380
- const existing = JSON.parse(readFileSync(zedPath, "utf-8"));
402
+ const existing = safeReadJson(zedPath) || {};
381
403
  if (existing.context_servers?.patchcord) {
382
404
  console.log(`\n ${yellow}⚠ Zed already configured${r}`);
383
405
  console.log(` ${dim}${zedPath}${r}`);
@@ -418,7 +440,7 @@ if (!cmd || cmd === "install" || cmd === "agent") {
418
440
  console.log(` ${yellow}This overrides per-project config and causes conflicts.${r}`);
419
441
  const cleanGlobal = (await ask(` ${dim}Remove patchcord from global config? (Y/n):${r} `)).trim().toLowerCase();
420
442
  if (cleanGlobal !== "n" && cleanGlobal !== "no") {
421
- const cleaned = globalContent.replace(/\[mcp_servers\.patchcord\][^\[]*/s, "").replace(/\n{3,}/g, "\n\n").trim();
443
+ const cleaned = globalContent.replace(/\[mcp_servers\.patchcord\]\n(?:(?!\[)[^\n]*\n?)*/g, "").replace(/\n{3,}/g, "\n\n").trim();
422
444
  writeFileSync(globalCodexConfig, cleaned + "\n");
423
445
  console.log(` ${green}✓${r} Removed from global config`);
424
446
  }
@@ -484,7 +506,14 @@ if (!cmd || cmd === "install" || cmd === "agent") {
484
506
  const customUrl = (await ask(`\n${dim}Custom server URL? (y/N):${r} `)).trim().toLowerCase();
485
507
  if (customUrl === "y" || customUrl === "yes") {
486
508
  const url = (await ask("Server URL: ")).trim();
487
- if (url) serverUrl = url;
509
+ if (url) {
510
+ if (!isSafeUrl(url)) {
511
+ console.error("Invalid URL. Must start with https:// or http://");
512
+ rl.close();
513
+ process.exit(1);
514
+ }
515
+ serverUrl = url;
516
+ }
488
517
  }
489
518
 
490
519
  rl.close();
@@ -564,12 +593,7 @@ if (!cmd || cmd === "install" || cmd === "agent") {
564
593
  } else if (isGemini) {
565
594
  // Gemini CLI: global only (~/.gemini/settings.json)
566
595
  const geminiPath = join(HOME, ".gemini", "settings.json");
567
- let geminiSettings = {};
568
- if (existsSync(geminiPath)) {
569
- try {
570
- geminiSettings = JSON.parse(readFileSync(geminiPath, "utf-8"));
571
- } catch {}
572
- }
596
+ let geminiSettings = (existsSync(geminiPath) && safeReadJson(geminiPath)) || {};
573
597
  if (!geminiSettings.mcpServers) geminiSettings.mcpServers = {};
574
598
  geminiSettings.mcpServers.patchcord = {
575
599
  httpUrl: `${serverUrl}/mcp`,
@@ -592,12 +616,7 @@ if (!cmd || cmd === "install" || cmd === "agent") {
592
616
  const zedPath = process.platform === "darwin"
593
617
  ? join(HOME, "Library", "Application Support", "Zed", "settings.json")
594
618
  : join(HOME, ".config", "zed", "settings.json");
595
- let zedSettings = {};
596
- if (existsSync(zedPath)) {
597
- try {
598
- zedSettings = JSON.parse(readFileSync(zedPath, "utf-8"));
599
- } catch {}
600
- }
619
+ let zedSettings = (existsSync(zedPath) && safeReadJson(zedPath)) || {};
601
620
  if (!zedSettings.context_servers) zedSettings.context_servers = {};
602
621
  zedSettings.context_servers.patchcord = {
603
622
  url: `${serverUrl}/mcp`,
@@ -676,7 +695,7 @@ if (!cmd || cmd === "install" || cmd === "agent") {
676
695
  const configPath = join(codexDir, "config.toml");
677
696
  let existing = existsSync(configPath) ? readFileSync(configPath, "utf-8") : "";
678
697
  // Remove old patchcord config block if present
679
- existing = existing.replace(/\[mcp_servers\.patchcord\][^\[]*/s, "").replace(/\n{3,}/g, "\n\n").trim();
698
+ existing = existing.replace(/\[mcp_servers\.patchcord\]\n(?:(?!\[)[^\n]*\n?)*/g, "").replace(/\n{3,}/g, "\n\n").trim();
680
699
  existing = existing.trimEnd() + `\n\n[mcp_servers.patchcord]\nurl = "${serverUrl}/mcp/bearer"\nhttp_headers = { "Authorization" = "Bearer ${token}", "X-Patchcord-Machine" = "${hostname}" }\n`;
681
700
  writeFileSync(configPath, existing);
682
701
  // Clean up any PATCHCORD_TOKEN we previously wrote to .env
@@ -799,7 +818,7 @@ if (cmd === "skill") {
799
818
  }
800
819
  } catch {}
801
820
 
802
- if (!namespace || !agentId) {
821
+ if (!namespace || !agentId || !isSafeId(namespace) || !isSafeId(agentId)) {
803
822
  console.error("Cannot determine agent identity. Check your token.");
804
823
  process.exit(1);
805
824
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "patchcord",
3
- "version": "0.3.46",
3
+ "version": "0.3.47",
4
4
  "description": "Cross-machine agent messaging for Claude Code and Codex",
5
5
  "author": "ppravdin",
6
6
  "license": "MIT",