patchcord 0.5.116 → 0.5.118
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/bin/patchcord.mjs +95 -37
- package/package.json +1 -1
- package/scripts/kimi-stop-hook.sh +10 -5
- package/scripts/kimi-subscribe.sh +12 -11
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)
|
|
@@ -89,12 +97,12 @@ THIS AGENT — identity + messaging
|
|
|
89
97
|
ACCOUNT — log in to the CLI (commands below log in on demand)
|
|
90
98
|
patchcord login Authenticate the CLI (your account)
|
|
91
99
|
|
|
92
|
-
|
|
93
|
-
patchcord
|
|
94
|
-
Provision the
|
|
100
|
+
ORCHESTRATOR — set up the orchestrator agent (the shepherd) in this folder
|
|
101
|
+
patchcord orchestrator [--namespace <ns>] [--tool <harness>]
|
|
102
|
+
Provision the orchestrator here; launch it
|
|
95
103
|
to adopt this project or build a new team
|
|
96
104
|
|
|
97
|
-
TEAM — the
|
|
105
|
+
TEAM — the orchestrator's tools (run from the project root)
|
|
98
106
|
patchcord provision <agent> --tool <X> --role <Y> --namespace <ns> [--dir <sub/>]
|
|
99
107
|
Create a worker agent (identity + config)
|
|
100
108
|
patchcord pull <agent> --tool <X> --namespace <ns> [--dir <sub/>]
|
|
@@ -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; }
|
|
@@ -827,12 +837,12 @@ if (cmd === "subscribe") {
|
|
|
827
837
|
// works the same as the space-form (--token foo). The internal flag parsing below
|
|
828
838
|
// supports both. Non-flag commands (channel, init, skill, help, plugin-path) have
|
|
829
839
|
// their own branches above and below.
|
|
830
|
-
// ── CLI account auth +
|
|
840
|
+
// ── CLI account auth + orchestrator/team/provisioning/schedule ─────────────
|
|
831
841
|
// These commands act on the user's ACCOUNT, so they require CLI login. The
|
|
832
842
|
// account token (user-level, tied to NO agent) lives at ~/.patchcord/auth.json
|
|
833
843
|
// (legacy: main.json / master.json) or $PATCHCORD_TOKEN (legacy: *_MAIN/MASTER).
|
|
834
844
|
// `patchcord login` authenticates; everything else logs in on demand.
|
|
835
|
-
if (cmd === "login" || cmd === "teamlead" || cmd === "provision" || cmd === "pull" || cmd === "team" || cmd === "schedule") {
|
|
845
|
+
if (cmd === "login" || cmd === "orchestrator" || cmd === "teamlead" || cmd === "provision" || cmd === "pull" || cmd === "team" || cmd === "schedule") {
|
|
836
846
|
const M = { cyan: "\x1b[36m", green: "\x1b[32m", dim: "\x1b[2m", rst: "\x1b[0m" };
|
|
837
847
|
const AUTH_CONFIG = join(HOME, ".patchcord", "auth.json");
|
|
838
848
|
const LEGACY_CONFIGS = [join(HOME, ".patchcord", "main.json"), join(HOME, ".patchcord", "master.json")];
|
|
@@ -902,7 +912,19 @@ if (cmd === "login" || cmd === "teamlead" || cmd === "provision" || cmd === "pul
|
|
|
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 === "teamlead" || cmd === "provision" || cmd === "pul
|
|
|
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 === "teamlead" || cmd === "provision" || cmd === "pul
|
|
|
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").
|
|
@@ -1046,27 +1095,34 @@ if (cmd === "login" || cmd === "teamlead" || cmd === "provision" || cmd === "pul
|
|
|
1046
1095
|
process.exit(0);
|
|
1047
1096
|
}
|
|
1048
1097
|
|
|
1049
|
-
if (cmd === "teamlead") {
|
|
1050
|
-
// Set up the
|
|
1051
|
-
// of agent — it shepherds a team: provisions, connects, and
|
|
1052
|
-
// other agents. It is NOT a normal agent: it gets its own
|
|
1053
|
-
// onboarding instruction, and on launch either ADOPTS
|
|
1054
|
-
// or CREATES a new team by interviewing the user.
|
|
1098
|
+
if (cmd === "orchestrator" || cmd === "teamlead") {
|
|
1099
|
+
// Set up the ORCHESTRATOR agent in THIS folder. The orchestrator is a
|
|
1100
|
+
// distinct kind of agent — it shepherds a team: provisions, connects, and
|
|
1101
|
+
// manages the other agents. It is NOT a normal agent: it gets its own
|
|
1102
|
+
// identity, its own onboarding instruction, and on launch either ADOPTS
|
|
1103
|
+
// this existing project or CREATES a new team by interviewing the user.
|
|
1104
|
+
// (`teamlead` is kept as a back-compat alias for the command name.)
|
|
1055
1105
|
const root = process.cwd();
|
|
1056
1106
|
const ns = (flagVal("namespace", basename(root)) || "team").replace(/[^a-z0-9-]/gi, "-").toLowerCase();
|
|
1057
1107
|
const tool = flagVal("tool", "claude_code");
|
|
1058
1108
|
const hostname = run("hostname -s") || run("hostname") || "unknown";
|
|
1059
|
-
const { status, json, m } = await accountCall("POST", "/api/provision", { namespace_id: ns, agent_id: "
|
|
1060
|
-
if (status !== "200" || !json?.token) { console.error(`could not set up
|
|
1109
|
+
const { status, json, m } = await accountCall("POST", "/api/provision", { namespace_id: ns, agent_id: "orchestrator", tool, role: "orchestrator", label: "orchestrator:self" });
|
|
1110
|
+
if (status !== "200" || !json?.token) { console.error(`could not set up orchestrator (HTTP ${status}): ${json?.error || ""}`); process.exit(1); }
|
|
1061
1111
|
const base = String(json.url || `${m.baseUrl}/mcp`).replace(/\/mcp$/, "");
|
|
1062
1112
|
writeWorkerConfig(tool, root, base, json.token, hostname);
|
|
1063
1113
|
mkdirSync(join(root, ".patchcord"), { recursive: true });
|
|
1064
1114
|
const tj = join(root, ".patchcord", "team.json");
|
|
1065
|
-
let manifest = { project: basename(root), namespace: ns,
|
|
1066
|
-
try {
|
|
1115
|
+
let manifest = { project: basename(root), namespace: ns, orchestrator: { agent: "orchestrator", tool }, agents: [] };
|
|
1116
|
+
try {
|
|
1117
|
+
if (existsSync(tj)) {
|
|
1118
|
+
const prev = JSON.parse(readFileSync(tj, "utf-8"));
|
|
1119
|
+
delete prev.teamlead; delete prev.main; delete prev.master; // drop pre-rename keys
|
|
1120
|
+
manifest = { ...prev, namespace: ns, orchestrator: { agent: "orchestrator", tool } };
|
|
1121
|
+
}
|
|
1122
|
+
} catch {}
|
|
1067
1123
|
writeFileSync(tj, JSON.stringify(manifest, null, 2) + "\n");
|
|
1068
|
-
// The
|
|
1069
|
-
writeFileSync(join(root, "
|
|
1124
|
+
// The orchestrator's onboarding instruction (read on launch).
|
|
1125
|
+
writeFileSync(join(root, "ORCHESTRATOR.md"), `# You are the ORCHESTRATOR (${ns}:orchestrator)
|
|
1070
1126
|
|
|
1071
1127
|
You shepherd a team of patchcord agents in this folder. You are NOT a worker —
|
|
1072
1128
|
you design the team, provision its agents, launch them, and manage them.
|
|
@@ -1079,14 +1135,16 @@ you design the team, provision its agents, launch them, and manage them.
|
|
|
1079
1135
|
which roles/harnesses, how many — confirm the plan, then build it.
|
|
1080
1136
|
|
|
1081
1137
|
## Build / run
|
|
1082
|
-
- Provision each worker: \`patchcord provision <agent> --tool <claude_code|codex|opencode|kimi> --role <role> --namespace ${ns} --dir <agent>/\`
|
|
1138
|
+
- Provision each worker: \`patchcord provision <agent> --tool <claude_code|codex|opencode|kimi|agy> --role <role> --namespace ${ns} --dir <agent>/\`
|
|
1139
|
+
- Pull an existing agent into a folder: \`patchcord pull <agent> --tool <X> --namespace ${ns} --dir <agent>/\`
|
|
1083
1140
|
- Launch: \`patchcord team launch\` (mux), verify with \`patchcord team status\`.
|
|
1084
1141
|
- Coordinate over patchcord (inbox / send_message); manage at the meta level.
|
|
1085
|
-
- Your own identity here is ${ns}:
|
|
1142
|
+
- Your own identity here is ${ns}:orchestrator — \`patchcord whoami\` confirms it.
|
|
1086
1143
|
`);
|
|
1087
|
-
|
|
1144
|
+
try { if (existsSync(join(root, "TEAMLEAD.md"))) unlinkSync(join(root, "TEAMLEAD.md")); } catch {} // remove pre-rename onboarding file
|
|
1145
|
+
console.log(`\n ${M.green}✓${M.rst} Orchestrator ready: ${M.green}${ns}:orchestrator${M.rst} [${tool}] in ${root}`);
|
|
1088
1146
|
console.log(` ${M.dim}Launch it here — it adopts this project or creates a new team, asking you:${M.rst}`);
|
|
1089
|
-
console.log(` mux new ${tool === "claude_code" ? "claude" : (tool === "kimi-code" ? "kimi" : tool)} --dir .`);
|
|
1147
|
+
console.log(` mux new ${tool === "claude_code" ? "claude" : (tool === "kimi-code" ? "kimi" : (tool === "antigravity" || tool === "agy") ? "agy" : tool)} --dir .`);
|
|
1090
1148
|
process.exit(0);
|
|
1091
1149
|
}
|
|
1092
1150
|
|
|
@@ -1160,10 +1218,10 @@ you design the team, provision its agents, launch them, and manage them.
|
|
|
1160
1218
|
panes.push({ session, window, path: real(path), cmd, claimed: false });
|
|
1161
1219
|
}
|
|
1162
1220
|
|
|
1163
|
-
// Build the roster:
|
|
1221
|
+
// Build the roster: orchestrator first, then workers.
|
|
1164
1222
|
const roster = [];
|
|
1165
|
-
const leadEntry = manifest.teamlead || manifest.main || manifest.master; // pre-rename names
|
|
1166
|
-
if (leadEntry) roster.push({ ...leadEntry, role: "
|
|
1223
|
+
const leadEntry = manifest.orchestrator || manifest.teamlead || manifest.main || manifest.master; // pre-rename names
|
|
1224
|
+
if (leadEntry) roster.push({ ...leadEntry, role: "orchestrator", dir: ".", _lead: true });
|
|
1167
1225
|
for (const a of (manifest.agents || [])) roster.push(a);
|
|
1168
1226
|
|
|
1169
1227
|
const rows = [];
|
|
@@ -3013,5 +3071,5 @@ if (cmd === "skill") {
|
|
|
3013
3071
|
process.exit(0);
|
|
3014
3072
|
}
|
|
3015
3073
|
|
|
3016
|
-
console.error(`Unknown command: ${cmd}. Available: install, whoami, agents, upload, subscribe, update, login, provision, pull,
|
|
3074
|
+
console.error(`Unknown command: ${cmd}. Available: install, whoami, agents, upload, subscribe, update, login, provision, pull, orchestrator, team, schedule, --rename, --token, --agent-type, --version, --help`);
|
|
3017
3075
|
process.exit(1);
|
package/package.json
CHANGED
|
@@ -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)
|