patchcord 0.5.69 → 0.5.71

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 +87 -31
  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
  }
@@ -1747,15 +1794,21 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
1747
1794
  // Install alias for per-project --mcp-config-file
1748
1795
  const aliasLine = `alias kimi-pc='kimi --mcp-config-file .kimi/mcp.json'`;
1749
1796
  const aliasComment = "# Patchcord Kimi alias — loads per-project .kimi/mcp.json";
1750
- const shellRcFiles = [
1751
- join(HOME, ".bashrc"),
1752
- join(HOME, ".zshrc"),
1753
- ];
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);
1754
1803
  let aliasInstalled = false;
1755
- for (const rcFile of shellRcFiles) {
1804
+ let aliasAlreadyPresent = false;
1805
+ for (const rcFile of shellRcCandidates) {
1756
1806
  if (!existsSync(rcFile)) continue;
1757
1807
  const rcContent = readFileSync(rcFile, "utf-8");
1758
- if (rcContent.includes("alias kimi-pc=")) continue; // already present
1808
+ if (rcContent.includes("alias kimi-pc=")) {
1809
+ aliasAlreadyPresent = true;
1810
+ continue;
1811
+ }
1759
1812
  writeFileSync(
1760
1813
  rcFile,
1761
1814
  rcContent.trimEnd() + `\n\n${aliasComment}\n${aliasLine}\n`
@@ -1763,12 +1816,15 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
1763
1816
  aliasInstalled = true;
1764
1817
  }
1765
1818
  if (aliasInstalled) {
1766
- console.log(`\n ${green}✓${r} Alias installed in shell config`);
1819
+ console.log(`\n ${green}✓${r} Alias installed in ${primaryName}`);
1820
+ console.log(` ${dim}Run ${bold}kimi-pc${r}${dim} instead of ${bold}kimi${r}${dim} in project directories.${r}`);
1821
+ console.log(` ${dim}Reload your shell or run ${cyan}source ~/${primaryName}${r}${dim} to use it now.${r}`);
1822
+ } else if (aliasAlreadyPresent) {
1823
+ console.log(`\n ${green}✓${r} Alias already present in shell config`);
1767
1824
  console.log(` ${dim}Run ${bold}kimi-pc${r}${dim} instead of ${bold}kimi${r}${dim} in project directories.${r}`);
1768
- console.log(` ${dim}Reload your shell or run ${cyan}source ~/.bashrc${r}${dim} (or ~/.zshrc) to use it now.${r}`);
1769
1825
  } else {
1770
- console.log(`\n ${yellow}⚠${r} Could not auto-install alias.`);
1771
- console.log(` ${dim}Add this to your ~/.bashrc or ~/.zshrc manually:${r}`);
1826
+ console.log(`\n ${yellow}⚠${r} No shell config found (~/.bashrc or ~/.zshrc).`);
1827
+ console.log(` ${dim}Add this to your ~/${primaryName} manually:${r}`);
1772
1828
  console.log(` ${cyan}${aliasLine}${r}`);
1773
1829
  }
1774
1830
  } else if (isVSCode) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "patchcord",
3
- "version": "0.5.69",
3
+ "version": "0.5.71",
4
4
  "description": "Cross-machine agent messaging for Claude Code and Codex",
5
5
  "author": "ppravdin",
6
6
  "license": "MIT",