patchcord 0.5.117 → 0.5.119
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 +3 -6
- package/bin/patchcord.mjs +60 -11
- package/package.json +4 -1
- package/scripts/kimi-stop-hook.sh +10 -5
- package/scripts/kimi-subscribe.sh +12 -11
- package/scripts/sync-plugin-version.mjs +24 -0
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "patchcord",
|
|
3
|
-
"description": "Cross-machine agent messaging
|
|
4
|
-
"version": "0.5.
|
|
3
|
+
"description": "Cross-machine agent messaging. Messages from other agents arrive via inbox with Stop-hook wake-up.",
|
|
4
|
+
"version": "0.5.119",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "ppravdin"
|
|
7
7
|
},
|
|
8
8
|
"repository": "https://github.com/ppravdin/patchcord",
|
|
9
|
-
"skills": "./skills/"
|
|
10
|
-
"channel": {
|
|
11
|
-
"server": "./channel/server.ts"
|
|
12
|
-
}
|
|
9
|
+
"skills": "./skills/"
|
|
13
10
|
}
|
package/bin/patchcord.mjs
CHANGED
|
@@ -66,12 +66,20 @@ function detectFolder(dir) {
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
|
|
69
|
-
|
|
69
|
+
// A trailing --help/-h on ANY subcommand must show help and exit — never run the
|
|
70
|
+
// command. (patchcord orchestrator --help used to PROVISION an orchestrator;
|
|
71
|
+
// patchcord login --help hung on the browser login. Side-effecting --help is a
|
|
72
|
+
// footgun.) We short-circuit here, before any account/provision/login dispatch.
|
|
73
|
+
if (
|
|
74
|
+
cmd === "help" || cmd === "--help" || cmd === "-h" ||
|
|
75
|
+
process.argv.slice(3).some((a) => a === "--help" || a === "-h")
|
|
76
|
+
) {
|
|
70
77
|
console.log(`patchcord — cross-agent messaging + team orchestration for AI coding agents
|
|
71
78
|
|
|
72
79
|
Two layers: THIS AGENT (a per-project identity, by bearer token) and your ACCOUNT
|
|
73
|
-
(user-level; provisions agents, runs teams + schedules).
|
|
74
|
-
|
|
80
|
+
(user-level; provisions agents, runs teams + schedules). Run \`patchcord login\`
|
|
81
|
+
once to authenticate the CLI; account/team/schedule commands then use that token
|
|
82
|
+
(they never open a browser on their own).
|
|
75
83
|
|
|
76
84
|
SETUP (per agent / project)
|
|
77
85
|
patchcord Set up this project's agent (browser)
|
|
@@ -341,8 +349,12 @@ async function _resolveBearer(options = {}) {
|
|
|
341
349
|
);
|
|
342
350
|
})();
|
|
343
351
|
|
|
344
|
-
|
|
345
|
-
|
|
352
|
+
// NO global kimi/kimi-code reader. Kimi is a PER-PROJECT identity
|
|
353
|
+
// (.kimi-code/mcp.json in the worktree); a global ~/.kimi(-code)/mcp.json would
|
|
354
|
+
// make a leftover token hijack every kimi session everywhere. A kimi outside a
|
|
355
|
+
// 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 keep
|
|
357
|
+
// their global readers.)
|
|
346
358
|
const defaultGlobalCandidates = [
|
|
347
359
|
() => readJsonAt(join(HOME, ".codeium", "windsurf", "mcp_config.json"), ["mcpServers", "patchcord"], "windsurf"),
|
|
348
360
|
() => readJsonAt(join(HOME, ".gemini", "settings.json"), ["mcpServers", "patchcord"], "gemini"),
|
|
@@ -352,9 +364,7 @@ async function _resolveBearer(options = {}) {
|
|
|
352
364
|
() => readHermesShape(join(HOME, ".hermes", "config.yaml")),
|
|
353
365
|
...clinePaths.map((p) => () => readJsonAt(p, ["mcpServers", "patchcord"], "cline")),
|
|
354
366
|
];
|
|
355
|
-
const globalCandidates =
|
|
356
|
-
? [kimiGlobalReader, kimiCodeGlobalReader, ...defaultGlobalCandidates]
|
|
357
|
-
: [...defaultGlobalCandidates, kimiGlobalReader, kimiCodeGlobalReader];
|
|
367
|
+
const globalCandidates = defaultGlobalCandidates;
|
|
358
368
|
for (const r of globalCandidates) {
|
|
359
369
|
const found = r();
|
|
360
370
|
if (found) { found.scope = "global"; return found; }
|
|
@@ -902,7 +912,19 @@ if (cmd === "login" || cmd === "orchestrator" || cmd === "teamlead" || cmd === "
|
|
|
902
912
|
return { token, baseUrl: DEFAULT_API };
|
|
903
913
|
};
|
|
904
914
|
// Auth on demand: any command needing the account logs in if not already.
|
|
905
|
-
const requireAuth = async () =>
|
|
915
|
+
const requireAuth = async () => {
|
|
916
|
+
const a = readAuth();
|
|
917
|
+
if (a) return a;
|
|
918
|
+
// No account token. NEVER auto-drop into the interactive browser login here:
|
|
919
|
+
// doLogin() prints a URL and BLOCKS up to 15 minutes for a browser that never
|
|
920
|
+
// comes in a headless/agent context (the orchestrator misread that hang as
|
|
921
|
+
// "provision produced no output"). TTY detection is unreliable (a pty/agent
|
|
922
|
+
// shell can report isTTY=true), so we fail fast unconditionally. Only the
|
|
923
|
+
// explicit `patchcord login` command opens a browser.
|
|
924
|
+
console.error("No patchcord account token — no pcm- token in ~/.patchcord/auth.json or $PATCHCORD_TOKEN.");
|
|
925
|
+
console.error("Run `patchcord login` once (interactive), or set $PATCHCORD_TOKEN. Cannot continue.");
|
|
926
|
+
process.exit(1);
|
|
927
|
+
};
|
|
906
928
|
// Account-authed request with stale-token recovery. If the server rejects the
|
|
907
929
|
// stored token (401/403), forget it, re-log-in once, and retry. Returns the
|
|
908
930
|
// _httpJSON result plus the auth used (for callers that need m.baseUrl).
|
|
@@ -910,9 +932,11 @@ if (cmd === "login" || cmd === "orchestrator" || cmd === "teamlead" || cmd === "
|
|
|
910
932
|
let m = await requireAuth();
|
|
911
933
|
let r = await _httpJSON(method, `${m.baseUrl}${path}`, m.token, body);
|
|
912
934
|
if (r.status === "401" || r.status === "403") {
|
|
935
|
+
// Stale/invalid account token. Forget it and fail fast — do NOT auto-relogin
|
|
936
|
+
// (would hang headless, see requireAuth). The user re-runs `patchcord login`.
|
|
913
937
|
forgetAuth();
|
|
914
|
-
|
|
915
|
-
|
|
938
|
+
console.error(`patchcord account token rejected (HTTP ${r.status}). Run \`patchcord login\` again. Cannot continue.`);
|
|
939
|
+
process.exit(1);
|
|
916
940
|
}
|
|
917
941
|
return { ...r, m };
|
|
918
942
|
};
|
|
@@ -957,6 +981,31 @@ if (cmd === "login" || cmd === "orchestrator" || cmd === "teamlead" || cmd === "
|
|
|
957
981
|
o.mcpServers.patchcord = { serverUrl: `${baseUrl}/mcp`, headers: hdr };
|
|
958
982
|
});
|
|
959
983
|
}
|
|
984
|
+
if (tool === "hermes") {
|
|
985
|
+
// Hermes reads MCP servers ONLY from its GLOBAL ~/.hermes/config.yaml
|
|
986
|
+
// (mcp_servers key) — it ignores a project-local .mcp.json. So unlike the
|
|
987
|
+
// other tools we cannot write into `dir`; we upsert the global config,
|
|
988
|
+
// mirroring the `npx patchcord` installer's Hermes path. NOTE: global =
|
|
989
|
+
// one patchcord identity per machine, so provisioning a second hermes
|
|
990
|
+
// worker in another namespace overwrites the first's token. Single-hermes
|
|
991
|
+
// teams only until Hermes gains project-scoped MCP config.
|
|
992
|
+
const hermesPath = join(HOME, ".hermes", "config.yaml");
|
|
993
|
+
mkdirSync(dirname(hermesPath), { recursive: true });
|
|
994
|
+
let existingYaml = "";
|
|
995
|
+
try { existingYaml = existsSync(hermesPath) ? readFileSync(hermesPath, "utf-8") : ""; } catch {}
|
|
996
|
+
writeFileSync(hermesPath, upsertHermesConfig(existingYaml, `${baseUrl}/mcp`, token));
|
|
997
|
+
// Install the Hermes patchcord skills (inbox/subscribe/wait) so the worker
|
|
998
|
+
// knows the messaging flow, same as the installer does.
|
|
999
|
+
try {
|
|
1000
|
+
const hermesSkillsSrc = join(pluginRoot, "per-project-skills", "hermes");
|
|
1001
|
+
if (existsSync(hermesSkillsSrc)) {
|
|
1002
|
+
const hermesSkillsDest = join(HOME, ".hermes", "skills", "integrations");
|
|
1003
|
+
mkdirSync(hermesSkillsDest, { recursive: true });
|
|
1004
|
+
cpSync(hermesSkillsSrc, hermesSkillsDest, { recursive: true });
|
|
1005
|
+
}
|
|
1006
|
+
} catch {}
|
|
1007
|
+
return hermesPath;
|
|
1008
|
+
}
|
|
960
1009
|
// default: claude_code. type:"http" is REQUIRED — without it Claude Code
|
|
961
1010
|
// defaults to stdio transport and rejects the entry ("command: expected
|
|
962
1011
|
// string, received undefined").
|
package/package.json
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "patchcord",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.119",
|
|
4
4
|
"description": "Cross-machine agent messaging for Claude Code and Codex",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"version": "node scripts/sync-plugin-version.mjs && git add .claude-plugin/plugin.json"
|
|
7
|
+
},
|
|
5
8
|
"author": "ppravdin",
|
|
6
9
|
"license": "MIT",
|
|
7
10
|
"repository": {
|
|
@@ -18,23 +18,28 @@ 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:
|
|
21
|
+
# Resolve MCP config: PROJECT-scoped ONLY (walk up from cwd for .kimi/mcp.json).
|
|
22
|
+
# NO global ~/.kimi/mcp.json fallback — a Kimi launched outside a patchcord
|
|
23
|
+
# worktree is not that agent and must not be nagged. No MCP authed in the cwd
|
|
24
|
+
# tree => no inbox check. (This Stop hook is registered globally in
|
|
25
|
+
# ~/.kimi/config.toml, so the project-scoped token is the ONLY thing that should
|
|
26
|
+
# make it act.)
|
|
22
27
|
KIMI_MCP=""
|
|
23
28
|
CWD=$(echo "$INPUT" | jq -r '.cwd // empty' 2>/dev/null || echo "")
|
|
24
29
|
|
|
25
30
|
if [ -n "$CWD" ]; then
|
|
26
31
|
dir="$CWD"
|
|
27
32
|
while [ "$dir" != "/" ]; do
|
|
28
|
-
if [ -f "$dir/.kimi/mcp.json" ]; then
|
|
29
|
-
KIMI_MCP="$dir/.kimi/mcp.json"
|
|
33
|
+
if [ -f "$dir/.kimi-code/mcp.json" ]; then
|
|
34
|
+
KIMI_MCP="$dir/.kimi-code/mcp.json"
|
|
30
35
|
break
|
|
31
36
|
fi
|
|
32
37
|
dir=$(dirname "$dir")
|
|
33
38
|
done
|
|
34
39
|
fi
|
|
35
40
|
|
|
36
|
-
#
|
|
37
|
-
[ -z "$KIMI_MCP" ] &&
|
|
41
|
+
# No project-scoped .kimi-code/mcp.json in the cwd tree => not a patchcord agent here.
|
|
42
|
+
[ -z "$KIMI_MCP" ] && exit 0
|
|
38
43
|
|
|
39
44
|
TOKEN=""
|
|
40
45
|
URL=""
|
|
@@ -14,25 +14,26 @@ set -euo pipefail
|
|
|
14
14
|
|
|
15
15
|
command -v jq >/dev/null 2>&1 || { echo "jq required" >&2; exit 1; }
|
|
16
16
|
|
|
17
|
-
# Resolve MCP config:
|
|
17
|
+
# Resolve MCP config: PROJECT-scoped ONLY (walk up from cwd for .kimi-code/mcp.json).
|
|
18
|
+
# Kimi Code (the current product) uses .kimi-code/ — the legacy .kimi/ path is
|
|
19
|
+
# deprecated and MUST NOT be used; it also mismatched where `patchcord provision`
|
|
20
|
+
# writes the config (.kimi-code/mcp.json), so the listener never found it.
|
|
21
|
+
# NO global fallback: auto-launched by the (global) SessionStart hook on EVERY
|
|
22
|
+
# Kimi session, so a global fallback would start a listener for some leftover
|
|
23
|
+
# identity in every folder. No project config in the cwd tree => no patchcord
|
|
24
|
+
# agent here => exit quietly (0, not 1: must not spam stderr in non-patchcord dirs).
|
|
18
25
|
KIMI_MCP=""
|
|
19
26
|
dir="$PWD"
|
|
20
27
|
while [ "$dir" != "/" ]; do
|
|
21
|
-
if [ -f "$dir/.kimi/mcp.json" ]; then
|
|
22
|
-
KIMI_MCP="$dir/.kimi/mcp.json"
|
|
28
|
+
if [ -f "$dir/.kimi-code/mcp.json" ]; then
|
|
29
|
+
KIMI_MCP="$dir/.kimi-code/mcp.json"
|
|
23
30
|
break
|
|
24
31
|
fi
|
|
25
32
|
dir=$(dirname "$dir")
|
|
26
33
|
done
|
|
27
34
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
KIMI_MCP="${HOME}/.kimi/mcp.json"
|
|
31
|
-
fi
|
|
32
|
-
|
|
33
|
-
if [ ! -f "$KIMI_MCP" ]; then
|
|
34
|
-
echo "Kimi MCP config not found at $KIMI_MCP or any parent — run npx patchcord@latest first" >&2
|
|
35
|
-
exit 1
|
|
35
|
+
if [ -z "$KIMI_MCP" ] || [ ! -f "$KIMI_MCP" ]; then
|
|
36
|
+
exit 0
|
|
36
37
|
fi
|
|
37
38
|
|
|
38
39
|
TOKEN=$(jq -r '.mcpServers.patchcord.headers.Authorization // empty' "$KIMI_MCP" 2>/dev/null | sed 's/^Bearer //i' || true)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Keep .claude-plugin/plugin.json's version in lockstep with package.json.
|
|
3
|
+
//
|
|
4
|
+
// Runs from the npm "version" lifecycle (see package.json scripts.version), so
|
|
5
|
+
// every `npm version <x>` rewrites the plugin manifest and stages it in the same
|
|
6
|
+
// commit. This prevents the drift that shipped a 0.5.14 manifest inside a 0.5.118
|
|
7
|
+
// package — which, combined with a manifest field pointing at an unshipped file,
|
|
8
|
+
// crashed every interactive Claude Code startup.
|
|
9
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
10
|
+
import { fileURLToPath } from "node:url";
|
|
11
|
+
import { dirname, join } from "node:path";
|
|
12
|
+
|
|
13
|
+
const root = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
14
|
+
const pkg = JSON.parse(readFileSync(join(root, "package.json"), "utf8"));
|
|
15
|
+
const manifestPath = join(root, ".claude-plugin", "plugin.json");
|
|
16
|
+
const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
17
|
+
|
|
18
|
+
if (manifest.version === pkg.version) {
|
|
19
|
+
process.exit(0);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
manifest.version = pkg.version;
|
|
23
|
+
writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + "\n");
|
|
24
|
+
process.stderr.write(`synced plugin.json version -> ${pkg.version}\n`);
|