patchcord 0.5.123 → 0.5.125
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.
- package/.claude-plugin/plugin.json +1 -1
- package/bin/patchcord.mjs +64 -26
- package/package.json +1 -1
package/bin/patchcord.mjs
CHANGED
|
@@ -349,18 +349,19 @@ async function _resolveBearer(options = {}) {
|
|
|
349
349
|
);
|
|
350
350
|
})();
|
|
351
351
|
|
|
352
|
-
// NO global
|
|
353
|
-
// (.
|
|
354
|
-
//
|
|
352
|
+
// NO global reader for PER-PROJECT tools. Kimi (.kimi-code/mcp.json) and
|
|
353
|
+
// Antigravity (.agents/mcp_config.json) are per-project identities: a leftover
|
|
354
|
+
// token in a global config (~/.kimi(-code)/mcp.json, ~/.gemini/antigravity/
|
|
355
|
+
// mcp_config.json) would hijack every session everywhere and make the agent
|
|
356
|
+
// "check inbox" in projects it has nothing to do with. Such a tool outside a
|
|
355
357
|
// patchcord worktree is simply not an agent. (The tools below are global-ONLY
|
|
356
|
-
// by nature — Windsurf/Gemini/Zed/etc. store MCP config globally — so they
|
|
357
|
-
// their global readers.)
|
|
358
|
+
// by nature — Windsurf/Gemini CLI/Zed/etc. store MCP config globally — so they
|
|
359
|
+
// keep their global readers.)
|
|
358
360
|
const defaultGlobalCandidates = [
|
|
359
361
|
() => readJsonAt(join(HOME, ".codeium", "windsurf", "mcp_config.json"), ["mcpServers", "patchcord"], "windsurf"),
|
|
360
362
|
() => readJsonAt(join(HOME, ".gemini", "settings.json"), ["mcpServers", "patchcord"], "gemini"),
|
|
361
363
|
() => readJsonAt(zedPath, ["context_servers", "patchcord"], "zed"),
|
|
362
364
|
() => readJsonAt(join(HOME, ".openclaw", "openclaw.json"), ["mcp", "servers", "patchcord"], "openclaw"),
|
|
363
|
-
() => readJsonAt(join(HOME, ".gemini", "antigravity", "mcp_config.json"), ["mcpServers", "patchcord"], "antigravity"),
|
|
364
365
|
() => readHermesShape(join(HOME, ".hermes", "config.yaml")),
|
|
365
366
|
...clinePaths.map((p) => () => readJsonAt(p, ["mcpServers", "patchcord"], "cline")),
|
|
366
367
|
];
|
|
@@ -1669,13 +1670,33 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
|
|
|
1669
1670
|
if (windsurfChanged) globalChanges.push("Windsurf skills installed");
|
|
1670
1671
|
}
|
|
1671
1672
|
|
|
1672
|
-
// Gemini CLI + Antigravity
|
|
1673
|
-
//
|
|
1674
|
-
//
|
|
1675
|
-
//
|
|
1676
|
-
//
|
|
1677
|
-
//
|
|
1673
|
+
// Gemini CLI + Antigravity SHARE ~/.gemini/skills and ~/.gemini/commands, but
|
|
1674
|
+
// their MCP config is NOT shared: Gemini CLI stores patchcord globally
|
|
1675
|
+
// (~/.gemini/settings.json) while Antigravity is PER-PROJECT
|
|
1676
|
+
// (<project>/.agents/mcp_config.json). So the shared global skill must be
|
|
1677
|
+
// seeded ONLY off Gemini CLI's global config — never off an Antigravity config.
|
|
1678
|
+
// A patchcord entry in the global ~/.gemini/antigravity(-cli)/mcp_config.json is
|
|
1679
|
+
// a leaked per-project token: it makes agy load the patchcord MCP (and its
|
|
1680
|
+
// "check inbox first" instruction) in EVERY project, and it kept this skill
|
|
1681
|
+
// seeded everywhere. We strip that leak below and gate the skill on Gemini only.
|
|
1678
1682
|
if (existsSync(join(HOME, ".gemini"))) {
|
|
1683
|
+
// Remove any leaked patchcord server from Antigravity's GLOBAL configs so a
|
|
1684
|
+
// per-project token stops hijacking every agy session. Preserve other MCP
|
|
1685
|
+
// servers; only drop patchcord.
|
|
1686
|
+
for (const agGlobal of [
|
|
1687
|
+
join(HOME, ".gemini", "antigravity", "mcp_config.json"),
|
|
1688
|
+
join(HOME, ".gemini", "antigravity-cli", "mcp_config.json"),
|
|
1689
|
+
]) {
|
|
1690
|
+
if (!existsSync(agGlobal)) continue;
|
|
1691
|
+
try {
|
|
1692
|
+
const j = JSON.parse(readFileSync(agGlobal, "utf-8"));
|
|
1693
|
+
if (j?.mcpServers?.patchcord) {
|
|
1694
|
+
delete j.mcpServers.patchcord;
|
|
1695
|
+
writeFileSync(agGlobal, JSON.stringify(j, null, 2) + "\n");
|
|
1696
|
+
globalChanges.push("Removed leaked global Antigravity patchcord MCP (agy is per-project)");
|
|
1697
|
+
}
|
|
1698
|
+
} catch {}
|
|
1699
|
+
}
|
|
1679
1700
|
// true = configured, false = file absent (definitely not), null = unparseable (unknown)
|
|
1680
1701
|
const hasPatchcordMcp = (p, keyPath) => {
|
|
1681
1702
|
if (!existsSync(p)) return false;
|
|
@@ -1684,8 +1705,6 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
|
|
|
1684
1705
|
};
|
|
1685
1706
|
const checks = [
|
|
1686
1707
|
hasPatchcordMcp(join(HOME, ".gemini", "settings.json"), ["mcpServers", "patchcord"]),
|
|
1687
|
-
hasPatchcordMcp(join(HOME, ".gemini", "antigravity", "mcp_config.json"), ["mcpServers", "patchcord"]),
|
|
1688
|
-
hasPatchcordMcp(join(HOME, ".gemini", "antigravity-cli", "mcp_config.json"), ["mcpServers", "patchcord"]),
|
|
1689
1708
|
];
|
|
1690
1709
|
const mcpConfigured = checks.some((c) => c === true);
|
|
1691
1710
|
const mcpUnknown = checks.some((c) => c === null);
|
|
@@ -1767,6 +1786,33 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
|
|
|
1767
1786
|
// Kimi Code (Node) reads ~/.kimi-code/skills. The legacy Python kimi-cli
|
|
1768
1787
|
// (~/.kimi) is deprecated — install only into Kimi Code's home.
|
|
1769
1788
|
const kimiSkillRoots = [join(kimiCodeHome, "skills")];
|
|
1789
|
+
|
|
1790
|
+
// Remove [[hooks]] array-of-tables blocks whose body matches `predicate`,
|
|
1791
|
+
// preserving everything else. A block runs from a `[[hooks]]` header line
|
|
1792
|
+
// until the next TOML table / array-of-tables header (`[...]` / `[[...]]`)
|
|
1793
|
+
// or EOF — so trailing sections like [providers.*] and [models.*] are NOT
|
|
1794
|
+
// swallowed. (The old split("[[hooks]]")+filter approach dropped the whole
|
|
1795
|
+
// segment after the patchcord hook, deleting kimi's model/provider defs and
|
|
1796
|
+
// leaving "Model not configured" on every host on the next install.)
|
|
1797
|
+
const removeHookBlocks = (toml, predicate) => {
|
|
1798
|
+
const lines = toml.split("\n");
|
|
1799
|
+
const out = [];
|
|
1800
|
+
let i = 0;
|
|
1801
|
+
while (i < lines.length) {
|
|
1802
|
+
if (/^\s*\[\[hooks\]\]\s*$/.test(lines[i])) {
|
|
1803
|
+
const block = [lines[i]];
|
|
1804
|
+
let j = i + 1;
|
|
1805
|
+
while (j < lines.length && !/^\s*\[/.test(lines[j])) { block.push(lines[j]); j++; }
|
|
1806
|
+
if (!predicate(block.join("\n"))) out.push(...block);
|
|
1807
|
+
i = j;
|
|
1808
|
+
} else {
|
|
1809
|
+
out.push(lines[i]); i++;
|
|
1810
|
+
}
|
|
1811
|
+
}
|
|
1812
|
+
return out.join("\n");
|
|
1813
|
+
};
|
|
1814
|
+
const isPatchcordHook = (b) => /patchcord-(stop-hook|session-start|prompt-hook)/.test(b);
|
|
1815
|
+
|
|
1770
1816
|
for (const root of kimiSkillRoots) {
|
|
1771
1817
|
for (const stale of ["patchcord", "patchcord-wait", "patchcord:inbox", "patchcord:wait", "patchcord:subscribe"]) {
|
|
1772
1818
|
const d = join(root, stale);
|
|
@@ -1796,11 +1842,6 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
|
|
|
1796
1842
|
// Remove old inline hooks = [] (conflicts with [[hooks]] array-of-tables)
|
|
1797
1843
|
kimiConfig = kimiConfig.replace(/^hooks\s*=\s*\[\]\n?/gm, "");
|
|
1798
1844
|
|
|
1799
|
-
// Remove existing patchcord stop hook blocks
|
|
1800
|
-
const segments = kimiConfig.split("[[hooks]]");
|
|
1801
|
-
const kept = segments.filter((seg, idx) => idx === 0 || !seg.includes("patchcord-stop-hook"));
|
|
1802
|
-
kimiConfig = kept.join("[[hooks]]").replace(/\n{3,}/g, "\n\n").trim();
|
|
1803
|
-
|
|
1804
1845
|
// Install session-start hook
|
|
1805
1846
|
const kimiSessionSrc = join(pluginRoot, "scripts", "kimi-session-start.sh");
|
|
1806
1847
|
const kimiSessionDest = join(kimiCodeHome, "patchcord-session-start.sh");
|
|
@@ -1809,10 +1850,9 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
|
|
|
1809
1850
|
chmodSync(kimiSessionDest, 0o755);
|
|
1810
1851
|
}
|
|
1811
1852
|
|
|
1812
|
-
// Remove existing patchcord session-start
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
kimiConfig = kept2.join("[[hooks]]").replace(/\n{3,}/g, "\n\n").trim();
|
|
1853
|
+
// Remove any existing patchcord hook blocks (stop / session-start /
|
|
1854
|
+
// legacy prompt) in one pass, preserving all other tables.
|
|
1855
|
+
kimiConfig = removeHookBlocks(kimiConfig, isPatchcordHook).replace(/\n{3,}/g, "\n\n").trim();
|
|
1816
1856
|
|
|
1817
1857
|
// Append new hooks
|
|
1818
1858
|
kimiConfig = kimiConfig.trimEnd() + `\n\n[[hooks]]\nevent = "SessionStart"\ncommand = "${kimiSessionDest}"\ntimeout = 10\n\n[[hooks]]\nevent = "Stop"\ncommand = "${kimiHookDest}"\ntimeout = 10\n`;
|
|
@@ -1834,9 +1874,7 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
|
|
|
1834
1874
|
if (existsSync(legacyConfigPath)) {
|
|
1835
1875
|
try {
|
|
1836
1876
|
let lc = readFileSync(legacyConfigPath, "utf-8");
|
|
1837
|
-
|
|
1838
|
-
const keptLegacy = segs.filter((seg, idx) => idx === 0 || !seg.includes("patchcord-"));
|
|
1839
|
-
lc = keptLegacy.join("[[hooks]]").replace(/\n{3,}/g, "\n\n").trim();
|
|
1877
|
+
lc = removeHookBlocks(lc, (b) => /patchcord-/.test(b)).replace(/\n{3,}/g, "\n\n").trim();
|
|
1840
1878
|
writeFileSync(legacyConfigPath, lc ? lc + "\n" : "");
|
|
1841
1879
|
} catch {}
|
|
1842
1880
|
}
|
package/package.json
CHANGED