patchcord 0.5.122 → 0.5.123

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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "patchcord",
3
3
  "description": "Cross-machine agent messaging. Messages from other agents land in the inbox and wake the agent to reply.",
4
- "version": "0.5.122",
4
+ "version": "0.5.123",
5
5
  "author": {
6
6
  "name": "ppravdin"
7
7
  },
package/bin/patchcord.mjs CHANGED
@@ -596,7 +596,7 @@ if (cmd === "subscribe") {
596
596
  const forceKimi = args.includes("--kimi");
597
597
  const intervalArg = args.find((a) => a !== "--kimi") || "30";
598
598
 
599
- // Kimi CLI uses polling instead of WebSocket realtime.
599
+ // Kimi Code uses polling instead of WebSocket realtime.
600
600
  // Force Kimi mode with --kimi flag, or auto-detect from bearer config.
601
601
  let isKimi = forceKimi;
602
602
  let bearerInfo = null;
@@ -606,13 +606,15 @@ if (cmd === "subscribe") {
606
606
  }
607
607
 
608
608
  if (isKimi) {
609
+ const kimiCodeHome = process.env.KIMI_CODE_HOME || join(HOME, ".kimi-code");
609
610
  // Remove old combined skill so only patchcord:inbox/subscribe/wait appear
610
- const oldKimiSkillDir = join(HOME, ".kimi", "skills", "patchcord");
611
- if (existsSync(oldKimiSkillDir)) {
612
- try { rmSync(oldKimiSkillDir, { recursive: true, force: true }); } catch {}
611
+ for (const oldSkillRoot of [join(kimiCodeHome, "skills", "patchcord"), join(HOME, ".kimi", "skills", "patchcord")]) {
612
+ if (existsSync(oldSkillRoot)) {
613
+ try { rmSync(oldSkillRoot, { recursive: true, force: true }); } catch {}
614
+ }
613
615
  }
614
616
 
615
- const kimiSubScript = join(HOME, ".kimi", "patchcord-subscribe.sh");
617
+ const kimiSubScript = join(kimiCodeHome, "patchcord-subscribe.sh");
616
618
  if (!existsSync(kimiSubScript)) {
617
619
  console.error(`Kimi subscribe script not found at ${kimiSubScript}`);
618
620
  console.error(`Run npx patchcord@latest to install it.`);
@@ -835,7 +837,7 @@ if (cmd === "subscribe") {
835
837
  // ── main flow: global setup + project setup (or just install/agent for back-compat) ──
836
838
  // Any --flag enters this branch so equals-form (--token=foo, --tool=foo, --server=foo)
837
839
  // works the same as the space-form (--token foo). The internal flag parsing below
838
- // supports both. Non-flag commands (channel, init, skill, help, plugin-path) have
840
+ // supports both. Non-flag commands (init, skill, help, plugin-path) have
839
841
  // their own branches above and below.
840
842
  // ── CLI account auth + orchestrator/team/provisioning/schedule ─────────────
841
843
  // These commands act on the user's ACCOUNT, so they require CLI login. The
@@ -1743,7 +1745,7 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
1743
1745
  }
1744
1746
  }
1745
1747
 
1746
- // Kimi CLI
1748
+ // Kimi Code
1747
1749
  const hasKimi = run("which kimi");
1748
1750
  if (hasKimi) {
1749
1751
  let kimiChanged = false;
@@ -1755,15 +1757,16 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
1755
1757
  copyFileSync(src, dest);
1756
1758
  return changed;
1757
1759
  };
1758
- // Kimi CLI (Python) reads ~/.kimi/skills; Kimi Code (Node) reads
1760
+ // Kimi Code (Python) reads ~/.kimi/skills; Kimi Code (Node) reads
1759
1761
  // ~/.kimi-code/skills. Install into BOTH user dirs that exist — a global
1760
1762
  // `npx patchcord` must fix whichever Kimi the user runs. Clean up the old
1761
1763
  // combined skill AND colon-named flow skills (Kimi requires names to be
1762
1764
  // lowercase/digits/hyphens only — a colon kept them out of /flow:, showing
1763
1765
  // them only under /skill:). Renamed to patchcord-inbox etc.
1764
1766
  const kimiCodeHome = process.env.KIMI_CODE_HOME || join(HOME, ".kimi-code");
1765
- const kimiSkillRoots = [join(HOME, ".kimi", "skills")];
1766
- if (existsSync(kimiCodeHome)) kimiSkillRoots.push(join(kimiCodeHome, "skills"));
1767
+ // Kimi Code (Node) reads ~/.kimi-code/skills. The legacy Python kimi-cli
1768
+ // (~/.kimi) is deprecated — install only into Kimi Code's home.
1769
+ const kimiSkillRoots = [join(kimiCodeHome, "skills")];
1767
1770
  for (const root of kimiSkillRoots) {
1768
1771
  for (const stale of ["patchcord", "patchcord-wait", "patchcord:inbox", "patchcord:wait", "patchcord:subscribe"]) {
1769
1772
  const d = join(root, stale);
@@ -1774,16 +1777,20 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
1774
1777
  kimiChanged = installKimiSkill(join(root, "patchcord-subscribe"), "subscribe") || kimiChanged;
1775
1778
  }
1776
1779
 
1777
- // Install/update stop hook — fires after each Kimi turn to check inbox
1780
+ // Install/update stop hook — fires after each Kimi turn to check inbox.
1781
+ // Kimi Code (the current product) reads ~/.kimi-code/config.toml and the
1782
+ // hook scripts it points to. The legacy Python kimi-cli home (~/.kimi) is
1783
+ // deprecated, so install into Kimi Code's home and strip any patchcord
1784
+ // artifacts a prior install left in ~/.kimi (see legacy cleanup below).
1778
1785
  let hookChanged = false;
1779
1786
  const kimiHookSrc = join(pluginRoot, "scripts", "kimi-stop-hook.sh");
1780
- const kimiHookDest = join(HOME, ".kimi", "patchcord-stop-hook.sh");
1787
+ const kimiHookDest = join(kimiCodeHome, "patchcord-stop-hook.sh");
1781
1788
  if (existsSync(kimiHookSrc)) {
1782
- const hookAlreadyExisted = existsSync(kimiHookDest);
1789
+ mkdirSync(kimiCodeHome, { recursive: true });
1783
1790
  copyFileSync(kimiHookSrc, kimiHookDest);
1784
1791
  chmodSync(kimiHookDest, 0o755);
1785
1792
 
1786
- const kimiConfigPath = join(HOME, ".kimi", "config.toml");
1793
+ const kimiConfigPath = join(kimiCodeHome, "config.toml");
1787
1794
  let kimiConfig = existsSync(kimiConfigPath) ? readFileSync(kimiConfigPath, "utf-8") : "";
1788
1795
 
1789
1796
  // Remove old inline hooks = [] (conflicts with [[hooks]] array-of-tables)
@@ -1796,7 +1803,7 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
1796
1803
 
1797
1804
  // Install session-start hook
1798
1805
  const kimiSessionSrc = join(pluginRoot, "scripts", "kimi-session-start.sh");
1799
- const kimiSessionDest = join(HOME, ".kimi", "patchcord-session-start.sh");
1806
+ const kimiSessionDest = join(kimiCodeHome, "patchcord-session-start.sh");
1800
1807
  if (existsSync(kimiSessionSrc)) {
1801
1808
  copyFileSync(kimiSessionSrc, kimiSessionDest);
1802
1809
  chmodSync(kimiSessionDest, 0o755);
@@ -1807,28 +1814,40 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
1807
1814
  const kept2 = segments2.filter((seg, idx) => idx === 0 || !seg.includes("patchcord-session-start") && !seg.includes("patchcord-prompt-hook"));
1808
1815
  kimiConfig = kept2.join("[[hooks]]").replace(/\n{3,}/g, "\n\n").trim();
1809
1816
 
1810
- // Remove the legacy UserPromptSubmit shim script if a prior install left it.
1811
- // Kimi displays stderr to the user but does not feed it to the LLM, so the
1812
- // shim never auto-executed anything — flow skills handle slash invocations.
1813
- const kimiPromptDest = join(HOME, ".kimi", "patchcord-prompt-hook.sh");
1814
- if (existsSync(kimiPromptDest)) {
1815
- try { rmSync(kimiPromptDest, { force: true }); } catch {}
1816
- }
1817
-
1818
1817
  // Append new hooks
1819
1818
  kimiConfig = kimiConfig.trimEnd() + `\n\n[[hooks]]\nevent = "SessionStart"\ncommand = "${kimiSessionDest}"\ntimeout = 10\n\n[[hooks]]\nevent = "Stop"\ncommand = "${kimiHookDest}"\ntimeout = 10\n`;
1820
1819
 
1821
1820
  mkdirSync(dirname(kimiConfigPath), { recursive: true });
1822
1821
  writeFileSync(kimiConfigPath, kimiConfig);
1823
1822
  hookChanged = true;
1823
+
1824
+ // Clean up deprecated ~/.kimi patchcord artifacts: stale hook/subscribe
1825
+ // scripts and any [[hooks]] blocks pointing at patchcord in the legacy
1826
+ // config, so the dead Python CLI can't double-fire or reference a script
1827
+ // we no longer maintain there.
1828
+ const legacyKimiHome = join(HOME, ".kimi");
1829
+ for (const f of ["patchcord-stop-hook.sh", "patchcord-session-start.sh", "patchcord-prompt-hook.sh", "patchcord-subscribe.sh"]) {
1830
+ const p = join(legacyKimiHome, f);
1831
+ if (existsSync(p)) { try { rmSync(p, { force: true }); } catch {} }
1832
+ }
1833
+ const legacyConfigPath = join(legacyKimiHome, "config.toml");
1834
+ if (existsSync(legacyConfigPath)) {
1835
+ try {
1836
+ let lc = readFileSync(legacyConfigPath, "utf-8");
1837
+ const segs = lc.split("[[hooks]]");
1838
+ const keptLegacy = segs.filter((seg, idx) => idx === 0 || !seg.includes("patchcord-"));
1839
+ lc = keptLegacy.join("[[hooks]]").replace(/\n{3,}/g, "\n\n").trim();
1840
+ writeFileSync(legacyConfigPath, lc ? lc + "\n" : "");
1841
+ } catch {}
1842
+ }
1824
1843
  }
1825
1844
 
1826
1845
  // Install/update Kimi subscribe script (background polling)
1827
1846
  let subChanged = false;
1828
1847
  const kimiSubSrc = join(pluginRoot, "scripts", "kimi-subscribe.sh");
1829
- const kimiSubDest = join(HOME, ".kimi", "patchcord-subscribe.sh");
1848
+ const kimiSubDest = join(kimiCodeHome, "patchcord-subscribe.sh");
1830
1849
  if (existsSync(kimiSubSrc)) {
1831
- const subAlreadyExisted = existsSync(kimiSubDest);
1850
+ mkdirSync(kimiCodeHome, { recursive: true });
1832
1851
  copyFileSync(kimiSubSrc, kimiSubDest);
1833
1852
  chmodSync(kimiSubDest, 0o755);
1834
1853
  subChanged = true;
@@ -1839,7 +1858,7 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
1839
1858
  if (hookChanged) kimiParts.push("hooks");
1840
1859
  if (subChanged) kimiParts.push("subscribe script");
1841
1860
  if (kimiParts.length > 0) {
1842
- globalChanges.push(`Kimi CLI ${kimiParts.join(" + ")} installed`);
1861
+ globalChanges.push(`Kimi Code ${kimiParts.join(" + ")} installed`);
1843
1862
  }
1844
1863
  }
1845
1864
 
@@ -1907,8 +1926,8 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
1907
1926
  }
1908
1927
 
1909
1928
  if (!hasClaude && !existsSync(codexConfig) && !hasKimi) {
1910
- console.log(`${dim}No Claude Code, Codex CLI, or Kimi CLI detected — skipping global setup.${r}`);
1911
- console.log(`${dim} Kimi CLI: https://kimi.moonshot.cn/download${r}`);
1929
+ console.log(`${dim}No Claude Code, Codex CLI, or Kimi Code detected — skipping global setup.${r}`);
1930
+ console.log(`${dim} Kimi Code: https://kimi.moonshot.cn/download${r}`);
1912
1931
  }
1913
1932
 
1914
1933
  if (updateOnly) process.exit(0);
@@ -1981,7 +2000,7 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
1981
2000
  console.log(` ${cyan}1.${r} Claude Code ${cyan}5.${r} Gemini CLI ${cyan}9.${r} OpenClaw`);
1982
2001
  console.log(` ${cyan}2.${r} Codex CLI ${cyan}6.${r} VS Code ${cyan}10.${r} Antigravity CLI`);
1983
2002
  console.log(` ${cyan}3.${r} Cursor ${cyan}7.${r} Zed ${cyan}11.${r} Cline`);
1984
- console.log(` ${cyan}4.${r} Windsurf ${cyan}8.${r} OpenCode ${cyan}12.${r} Kimi CLI`);
2003
+ console.log(` ${cyan}4.${r} Windsurf ${cyan}8.${r} OpenCode ${cyan}12.${r} Kimi Code`);
1985
2004
  console.log(` ${cyan}13.${r} Hermes\n`);
1986
2005
  choice = (await ask(`${dim}Choose (1-13):${r} `)).trim();
1987
2006
  if (!["1","2","3","4","5","6","7","8","9","10","11","12","13"].includes(choice)) {
@@ -2065,7 +2084,7 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
2065
2084
  // project setup. Only per-project configs trigger "already configured."
2066
2085
  if (existingToken) {
2067
2086
  // Figure out which tool is already configured
2068
- const existingToolName = existingConfigFile.includes(".kimi") ? "Kimi CLI"
2087
+ const existingToolName = existingConfigFile.includes(".kimi") ? "Kimi Code"
2069
2088
  : existingConfigFile.includes(".codex") ? "Codex"
2070
2089
  : existingConfigFile.includes(".agents") ? "Antigravity CLI"
2071
2090
  : existingConfigFile.includes("antigravity") ? "Antigravity"
@@ -2635,7 +2654,7 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
2635
2654
  console.log(` ${green}✓${r} Skills installed: ${dim}${join(kimiCodeHome, "skills")}${r} ${dim}(/flow:patchcord-inbox)${r}`);
2636
2655
  } catch {}
2637
2656
  } else if (isKimi) {
2638
- // Kimi CLI (Python): per-project .kimi/mcp.json + shell wrapper for --mcp-config-file
2657
+ // Kimi Code (Python): per-project .kimi/mcp.json + shell wrapper for --mcp-config-file
2639
2658
  const kimiDir = join(cwd, ".kimi");
2640
2659
  const kimiPath = join(kimiDir, "mcp.json");
2641
2660
  const kimiOk = updateJsonConfig(kimiPath, (obj) => {
@@ -2649,7 +2668,7 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
2649
2668
  };
2650
2669
  });
2651
2670
  if (kimiOk) {
2652
- console.log(`\n ${green}✓${r} Kimi CLI configured: ${dim}${kimiPath}${r}`);
2671
+ console.log(`\n ${green}✓${r} Kimi Code configured: ${dim}${kimiPath}${r}`);
2653
2672
  console.log(` ${dim}Per-project — use the kimi-pc wrapper (see below) so Kimi loads this config.${r}`);
2654
2673
  }
2655
2674
  // Install/update global skills. Hyphen names → /flow:patchcord-inbox
@@ -2968,7 +2987,7 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
2968
2987
  }
2969
2988
  }
2970
2989
 
2971
- const toolName = isHermes ? "Hermes" : isKimiCode ? "Kimi Code" : isKimi ? "Kimi CLI" : isAntigravity ? "Antigravity CLI" : isCline ? "Cline" : isOpenClaw ? "OpenClaw" : isOpenCode ? "OpenCode" : isZed ? "Zed" : isVSCode ? "VS Code" : isGemini ? "Gemini CLI" : isWindsurf ? "Windsurf" : isCursor ? "Cursor" : isCodex ? "Codex" : "Claude Code";
2990
+ const toolName = isHermes ? "Hermes" : isKimiCode ? "Kimi Code" : isKimi ? "Kimi Code" : isAntigravity ? "Antigravity CLI" : isCline ? "Cline" : isOpenClaw ? "OpenClaw" : isOpenCode ? "OpenCode" : isZed ? "Zed" : isVSCode ? "VS Code" : isGemini ? "Gemini CLI" : isWindsurf ? "Windsurf" : isCursor ? "Cursor" : isCodex ? "Codex" : "Claude Code";
2972
2991
 
2973
2992
  if (!isWindsurf && !isGemini && !isZed && !isOpenClaw && !isCline && !isKimi && !isHermes) {
2974
2993
  console.log(`\n ${dim}To connect a second agent:${r}`);
@@ -2987,7 +3006,6 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
2987
3006
  process.exit(0);
2988
3007
  }
2989
3008
 
2990
- // ── channel: spawn the channel MCP server (used by .mcp.json) ──
2991
3009
  // ── skill: custom skill from web console ─────────────────────
2992
3010
  if (cmd === "skill") {
2993
3011
  const sub = process.argv[3];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "patchcord",
3
- "version": "0.5.122",
3
+ "version": "0.5.123",
4
4
  "description": "Cross-machine agent messaging for Claude Code and Codex",
5
5
  "scripts": {
6
6
  "version": "node scripts/sync-plugin-version.mjs && git add .claude-plugin/plugin.json"
@@ -5,13 +5,13 @@ set -euo pipefail
5
5
  # Runs when a Kimi session starts or resumes. Idempotent: the subscribe script
6
6
  # has a pidfile guard that prevents duplicate instances.
7
7
 
8
- KIMI_SUB="${HOME}/.kimi/patchcord-subscribe.sh"
8
+ KIMI_SUB="${KIMI_CODE_HOME:-${HOME}/.kimi-code}/patchcord-subscribe.sh"
9
9
  if [ ! -f "$KIMI_SUB" ]; then
10
10
  exit 0
11
11
  fi
12
12
 
13
13
  # Kimi sends hook metadata on stdin. Use its cwd when present so the
14
- # subscribe script resolves the right per-project .kimi/mcp.json.
14
+ # subscribe script resolves the right per-project .kimi-code/mcp.json.
15
15
  if [ -t 0 ]; then
16
16
  INPUT=""
17
17
  else
@@ -4,8 +4,8 @@ set -euo pipefail
4
4
  # Kimi Stop hook — checks patchcord inbox after each turn.
5
5
  # Installed automatically by `npx patchcord` when Kimi CLI is detected.
6
6
  #
7
- # Supports per-project .kimi/mcp.json (walks up from cwd) and
8
- # falls back to global ~/.kimi/mcp.json.
7
+ # Resolves per-project .kimi-code/mcp.json only (walks up from cwd).
8
+ # No global fallback a Kimi outside a patchcord worktree is not that agent.
9
9
  #
10
10
  # CRITICAL: Kimi v1.44.0 ONLY acts on Stop hooks when they BLOCK (exit code 2).
11
11
  # stdout is silently discarded. stderr becomes the "reason" that triggers a new turn.
@@ -18,7 +18,7 @@ INPUT=$(cat)
18
18
  STOP_ACTIVE=$(echo "$INPUT" | jq -r '.stop_hook_active // false' 2>/dev/null || echo "false")
19
19
  [ "$STOP_ACTIVE" = "true" ] && exit 0
20
20
 
21
- # Resolve MCP config: PROJECT-scoped ONLY (walk up from cwd for .kimi/mcp.json).
21
+ # Resolve MCP config: PROJECT-scoped ONLY (walk up from cwd for .kimi-code/mcp.json).
22
22
  # NO global ~/.kimi/mcp.json fallback — a Kimi launched outside a patchcord
23
23
  # worktree is not that agent and must not be nagged. No MCP authed in the cwd
24
24
  # tree => no inbox check. (This Stop hook is registered globally in
@@ -9,8 +9,8 @@ set -euo pipefail
9
9
  # Usage: patchcord subscribe (starts with default 30s interval)
10
10
  # patchcord subscribe 10 (starts with 10s interval)
11
11
  #
12
- # Supports per-project .kimi/mcp.json (walks up from cwd) and
13
- # falls back to global ~/.kimi/mcp.json.
12
+ # Resolves per-project .kimi-code/mcp.json only (walks up from cwd).
13
+ # No global fallback.
14
14
 
15
15
  command -v jq >/dev/null 2>&1 || { echo "jq required" >&2; exit 1; }
16
16