patchcord 0.5.68 → 0.5.70

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 +80 -30
  2. package/package.json +1 -1
package/bin/patchcord.mjs CHANGED
@@ -709,13 +709,56 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
709
709
  let wasPluginInstalled = false;
710
710
  const { readFileSync, writeFileSync, unlinkSync, rmSync, chmodSync, copyFileSync } = await import("fs");
711
711
 
712
+ // JSONC-safe comment stripper that preserves content inside string literals.
713
+ // Prevents corrupting URLs like "https://..." which contain "//".
714
+ function _stripJsoncOutsideStrings(str) {
715
+ let out = "";
716
+ let i = 0;
717
+ let inStr = false;
718
+ let strCh = "";
719
+ let prev = "";
720
+ while (i < str.length) {
721
+ const c = str[i];
722
+ if (inStr) {
723
+ out += c;
724
+ if (c === strCh && prev !== "\\") inStr = false;
725
+ prev = c;
726
+ i++;
727
+ continue;
728
+ }
729
+ if (c === '"' || c === "'") {
730
+ inStr = true;
731
+ strCh = c;
732
+ out += c;
733
+ prev = c;
734
+ i++;
735
+ continue;
736
+ }
737
+ if (c === "/" && str[i + 1] === "/") {
738
+ while (i < str.length && str[i] !== "\n") i++;
739
+ continue;
740
+ }
741
+ if (c === "/" && str[i + 1] === "*") {
742
+ i += 2;
743
+ while (i < str.length && !(str[i] === "*" && str[i + 1] === "/")) i++;
744
+ i += 2;
745
+ continue;
746
+ }
747
+ out += c;
748
+ prev = c;
749
+ i++;
750
+ }
751
+ return out.replace(/,(\s*[}\]])/g, "$1");
752
+ }
753
+
712
754
  function safeReadJson(filePath) {
713
755
  try {
714
- let content = readFileSync(filePath, "utf-8");
715
- // Strip JSONC comments (Zed, Gemini use JSONC)
716
- content = content.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
717
- content = content.replace(/,\s*([}\]])/g, "$1");
718
- return JSON.parse(content);
756
+ const raw = readFileSync(filePath, "utf-8");
757
+ // Try strict JSON first (avoids corrupting URLs with //)
758
+ try { return JSON.parse(raw); } catch {}
759
+ // Fall back to JSONC stripping for Zed/Gemini-style configs
760
+ const cleaned = _stripJsoncOutsideStrings(raw);
761
+ return cleaned.trim() ? JSON.parse(cleaned) : {};
719
762
  } catch { return null; }
720
763
  }
721
764
 
@@ -729,24 +772,28 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
729
772
  if (existsSync(filePath)) {
730
773
  const raw = readFileSync(filePath, "utf-8");
731
774
  try {
732
- // JSONC-tolerant: Zed/Gemini settings allow //, /* */, trailing commas.
733
- const cleaned = raw
734
- .replace(/\/\/.*$/gm, "")
735
- .replace(/\/\*[\s\S]*?\*\//g, "")
736
- .replace(/,\s*([}\]])/g, "$1");
737
- const result = cleaned.trim() ? JSON.parse(cleaned) : {};
738
- // null / arrays / primitives aren't config objects we can merge into;
739
- // refuse rather than blow them away.
740
- if (result === null || typeof result !== "object" || Array.isArray(result)) {
741
- console.log(`\n ${yellow}⚠${r} Skipped ${filePath} existing file is not a JSON object.`);
742
- console.log(` Replace it with a valid object ({...}) or remove it, then re-run.`);
775
+ // Try strict JSON first (most configs are plain JSON). This avoids
776
+ // corrupting URLs like "https://..." which contain "//".
777
+ parsed = JSON.parse(raw);
778
+ } catch {
779
+ // Fall back to JSONC stripping for Zed/Gemini-style configs.
780
+ // _stripJsoncOutsideStrings preserves content inside string literals
781
+ // so URLs like "https://..." survive.
782
+ try {
783
+ const cleaned = _stripJsoncOutsideStrings(raw);
784
+ parsed = cleaned.trim() ? JSON.parse(cleaned) : {};
785
+ } catch {
786
+ console.log(`\n ${yellow}⚠${r} Skipped ${filePath} — could not parse existing JSON.`);
787
+ console.log(` Fix the file by hand and re-run the installer. We will not`);
788
+ console.log(` overwrite it: that would erase your other servers/settings.`);
743
789
  return false;
744
790
  }
745
- parsed = result;
746
- } catch {
747
- console.log(`\n ${yellow}⚠${r} Skipped ${filePath} could not parse existing JSON.`);
748
- console.log(` Fix the file by hand and re-run the installer. We will not`);
749
- console.log(` overwrite it: that would erase your other servers/settings.`);
791
+ }
792
+ // null / arrays / primitives aren't config objects we can merge into;
793
+ // refuse rather than blow them away.
794
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
795
+ console.log(`\n ${yellow}⚠${r} Skipped ${filePath} existing file is not a JSON object.`);
796
+ console.log(` Replace it with a valid object ({...}) or remove it, then re-run.`);
750
797
  return false;
751
798
  }
752
799
  }
@@ -1358,7 +1405,8 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
1358
1405
  // page skips its type picker (the user already chose on the
1359
1406
  // dashboard tile).
1360
1407
  const toolParam = toolSlug ? `&tool=${encodeURIComponent(toolSlug)}` : "";
1361
- const connectUrl = `https://patchcord.dev/connect?session=${sessionId}${toolParam}`;
1408
+ const cwdParam = `&cwd=${encodeURIComponent(process.cwd())}`;
1409
+ const connectUrl = `https://patchcord.dev/connect?session=${sessionId}${toolParam}${cwdParam}`;
1362
1410
 
1363
1411
  if (canOpenBrowser()) {
1364
1412
  const opened = openBrowser(connectUrl);
@@ -1746,12 +1794,14 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
1746
1794
  // Install alias for per-project --mcp-config-file
1747
1795
  const aliasLine = `alias kimi-pc='kimi --mcp-config-file .kimi/mcp.json'`;
1748
1796
  const aliasComment = "# Patchcord Kimi alias — loads per-project .kimi/mcp.json";
1749
- const shellRcFiles = [
1750
- join(HOME, ".bashrc"),
1751
- join(HOME, ".zshrc"),
1752
- ];
1797
+ const isZsh = (process.env.SHELL || "").includes("zsh");
1798
+ const shellRcCandidates = isZsh
1799
+ ? [join(HOME, ".zshrc"), join(HOME, ".bashrc")]
1800
+ : [join(HOME, ".bashrc"), join(HOME, ".zshrc")];
1801
+ const primaryRc = shellRcCandidates[0];
1802
+ const primaryName = basename(primaryRc);
1753
1803
  let aliasInstalled = false;
1754
- for (const rcFile of shellRcFiles) {
1804
+ for (const rcFile of shellRcCandidates) {
1755
1805
  if (!existsSync(rcFile)) continue;
1756
1806
  const rcContent = readFileSync(rcFile, "utf-8");
1757
1807
  if (rcContent.includes("alias kimi-pc=")) continue; // already present
@@ -1762,12 +1812,12 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
1762
1812
  aliasInstalled = true;
1763
1813
  }
1764
1814
  if (aliasInstalled) {
1765
- console.log(`\n ${green}✓${r} Alias installed in shell config`);
1815
+ console.log(`\n ${green}✓${r} Alias installed in ${primaryName}`);
1766
1816
  console.log(` ${dim}Run ${bold}kimi-pc${r}${dim} instead of ${bold}kimi${r}${dim} in project directories.${r}`);
1767
- console.log(` ${dim}Reload your shell or run ${cyan}source ~/.bashrc${r}${dim} (or ~/.zshrc) to use it now.${r}`);
1817
+ console.log(` ${dim}Reload your shell or run ${cyan}source ~/${primaryName}${r}${dim} to use it now.${r}`);
1768
1818
  } else {
1769
1819
  console.log(`\n ${yellow}⚠${r} Could not auto-install alias.`);
1770
- console.log(` ${dim}Add this to your ~/.bashrc or ~/.zshrc manually:${r}`);
1820
+ console.log(` ${dim}Add this to your ~/${primaryName} manually:${r}`);
1771
1821
  console.log(` ${cyan}${aliasLine}${r}`);
1772
1822
  }
1773
1823
  } else if (isVSCode) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "patchcord",
3
- "version": "0.5.68",
3
+ "version": "0.5.70",
4
4
  "description": "Cross-machine agent messaging for Claude Code and Codex",
5
5
  "author": "ppravdin",
6
6
  "license": "MIT",