patchcord 0.5.98 → 0.5.100

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 +195 -34
  2. package/package.json +1 -1
package/bin/patchcord.mjs CHANGED
@@ -273,7 +273,7 @@ async function _resolveBearer(options = {}) {
273
273
  while (dir && dir !== "/") {
274
274
  for (const r of projectReaders) {
275
275
  const found = r(dir);
276
- if (found) return found;
276
+ if (found) { found.scope = "project"; return found; }
277
277
  }
278
278
  dir = dirname(dir);
279
279
  }
@@ -323,7 +323,7 @@ async function _resolveBearer(options = {}) {
323
323
  : [...defaultGlobalCandidates, kimiGlobalReader, kimiCodeGlobalReader];
324
324
  for (const r of globalCandidates) {
325
325
  const found = r();
326
- if (found) return found;
326
+ if (found) { found.scope = "global"; return found; }
327
327
  }
328
328
 
329
329
  return null;
@@ -353,6 +353,12 @@ if (cmd === "whoami") {
353
353
  console.error("No patchcord config found in current directory or any parent. Run `npx patchcord@latest` from a project directory first.");
354
354
  process.exit(1);
355
355
  }
356
+ if (found.scope === "global") {
357
+ console.error(`\x1b[33m⚠ No project patchcord config in ${process.cwd()} or any parent.`);
358
+ console.error(` Falling back to a GLOBAL config: ${found.configFile} (tool: ${found.tool}).`);
359
+ console.error(` If this isn't the agent you expected, you're in a directory without its own .mcp.json —`);
360
+ console.error(` the identity below belongs to that global config, NOT to whatever harness launched this.\x1b[0m`);
361
+ }
356
362
  const { token, baseUrl } = found;
357
363
  if (!isSafeToken(token) || !isSafeUrl(baseUrl)) {
358
364
  console.error(`Invalid patchcord URL or token in config.`);
@@ -787,22 +793,25 @@ if (cmd === "subscribe") {
787
793
  // works the same as the space-form (--token foo). The internal flag parsing below
788
794
  // supports both. Non-flag commands (channel, init, skill, help, plugin-path) have
789
795
  // their own branches above and below.
790
- // ── Master agent: provisioning + team orchestration ────────────────────────
791
- // (docs/master-agent.md). A master token is a user-scoped provisioning
792
- // credential stored at ~/.patchcord/master.json or $PATCHCORD_MASTER_TOKEN.
793
- if (cmd === "master" || cmd === "provision" || cmd === "team") {
796
+ // ── Main agent: provisioning + team orchestration ──────────────────────────
797
+ // (docs/main-agent.md). The "main" is the managing agent of a team; its main
798
+ // token is a user-scoped provisioning credential stored at ~/.patchcord/main.json
799
+ // (legacy: master.json) or $PATCHCORD_MAIN_TOKEN (legacy: $PATCHCORD_MASTER_TOKEN).
800
+ if (cmd === "main" || cmd === "master" || cmd === "provision" || cmd === "team") {
794
801
  const M = { cyan: "\x1b[36m", green: "\x1b[32m", dim: "\x1b[2m", rst: "\x1b[0m" };
795
- const MASTER_CONFIG = join(HOME, ".patchcord", "master.json");
802
+ const MAIN_CONFIG = join(HOME, ".patchcord", "main.json");
803
+ const LEGACY_CONFIG = join(HOME, ".patchcord", "master.json"); // pre-rename
796
804
  const DEFAULT_API = process.env.PATCHCORD_BASE_URL || "https://api.patchcord.dev";
797
805
 
798
- const readMaster = () => {
799
- if (process.env.PATCHCORD_MASTER_TOKEN) {
800
- return { token: process.env.PATCHCORD_MASTER_TOKEN, baseUrl: DEFAULT_API };
806
+ const readMain = () => {
807
+ const envTok = process.env.PATCHCORD_MAIN_TOKEN || process.env.PATCHCORD_MASTER_TOKEN;
808
+ if (envTok) return { token: envTok, baseUrl: DEFAULT_API };
809
+ for (const p of [MAIN_CONFIG, LEGACY_CONFIG]) {
810
+ try {
811
+ const m = JSON.parse(readFileSync(p, "utf-8"));
812
+ if (m && m.token) return { token: m.token, baseUrl: m.baseUrl || DEFAULT_API };
813
+ } catch {}
801
814
  }
802
- try {
803
- const m = JSON.parse(readFileSync(MASTER_CONFIG, "utf-8"));
804
- if (m && m.token) return { token: m.token, baseUrl: m.baseUrl || DEFAULT_API };
805
- } catch {}
806
815
  return null;
807
816
  };
808
817
  const flagVal = (name, def = "") => {
@@ -812,9 +821,9 @@ if (cmd === "master" || cmd === "provision" || cmd === "team") {
812
821
  if (i >= 0 && process.argv[i + 1] && !process.argv[i + 1].startsWith("-")) return process.argv[i + 1];
813
822
  return def;
814
823
  };
815
- const requireMaster = () => {
816
- const m = readMaster();
817
- if (!m) { console.error("No master token. Run: patchcord master connect"); process.exit(1); }
824
+ const requireMain = () => {
825
+ const m = readMain();
826
+ if (!m) { console.error("No main token. Run: patchcord main connect"); process.exit(1); }
818
827
  return m;
819
828
  };
820
829
  const writeWorkerConfig = (tool, dir, baseUrl, token, hostname) => {
@@ -856,16 +865,16 @@ if (cmd === "master" || cmd === "provision" || cmd === "team") {
856
865
  });
857
866
  };
858
867
 
859
- if (cmd === "master") {
868
+ if (cmd === "main" || cmd === "master") {
860
869
  const sub = process.argv[3];
861
870
  if (sub === "connect") {
862
871
  const harness = flagVal("harness", flagVal("tool", ""));
863
872
  const create = run(`curl -sf --max-time 10 -X POST "${DEFAULT_API}/api/master/session" -H "Content-Type: application/json" -d '{"harness":"${harness}"}'`);
864
873
  let sessionId = "";
865
874
  try { sessionId = (JSON.parse(create).session_id) || ""; } catch {}
866
- if (!sessionId) { console.error("Could not start master-connect session."); process.exit(1); }
875
+ if (!sessionId) { console.error("Could not start main-auth session."); process.exit(1); }
867
876
  const url = `https://patchcord.dev/master?session=${sessionId}`;
868
- console.log(`\n Authorize the master agent in your browser:\n ${M.cyan}${url}${M.rst}\n`);
877
+ console.log(`\n Authorize the main agent in your browser:\n ${M.cyan}${url}${M.rst}\n`);
869
878
  console.log(` ${M.dim}Waiting for authorization...${M.rst}`);
870
879
  const http = await import("http");
871
880
  const https = await import("https");
@@ -888,24 +897,24 @@ if (cmd === "master" || cmd === "provision" || cmd === "team") {
888
897
  setTimeout(() => { try { req.destroy(); } catch {}; resolve(null); }, 900000);
889
898
  });
890
899
  if (!token) { console.error("\n Authorization timed out or failed."); process.exit(1); }
891
- mkdirSync(dirname(MASTER_CONFIG), { recursive: true });
892
- writeFileSync(MASTER_CONFIG, JSON.stringify({ token, baseUrl: DEFAULT_API }, null, 2) + "\n");
893
- console.log(`\n ${M.green}✓${M.rst} Master token saved: ${M.dim}${MASTER_CONFIG}${M.rst}`);
900
+ mkdirSync(dirname(MAIN_CONFIG), { recursive: true });
901
+ writeFileSync(MAIN_CONFIG, JSON.stringify({ token, baseUrl: DEFAULT_API }, null, 2) + "\n");
902
+ console.log(`\n ${M.green}✓${M.rst} Main token saved: ${M.dim}${MAIN_CONFIG}${M.rst}`);
894
903
  process.exit(0);
895
904
  }
896
905
  if (sub === "whoami") {
897
- const m = requireMaster();
906
+ const m = requireMain();
898
907
  const { status, json } = await _httpJSON("GET", `${m.baseUrl}/api/master/whoami`, m.token);
899
- if (status !== "200" || !json) { console.error(`master whoami failed (HTTP ${status})`); process.exit(1); }
900
- console.log(`master · user ${json.user_id} · ${json.agents}/${json.quota} agents`);
908
+ if (status !== "200" || !json) { console.error(`main whoami failed (HTTP ${status})`); process.exit(1); }
909
+ console.log(`main · user ${json.user_id} · ${json.agents}/${json.quota} agents`);
901
910
  process.exit(0);
902
911
  }
903
- console.error("Usage: patchcord master <connect|whoami>");
912
+ console.error("Usage: patchcord main <connect|whoami>");
904
913
  process.exit(1);
905
914
  }
906
915
 
907
916
  if (cmd === "provision") {
908
- const m = requireMaster();
917
+ const m = requireMain();
909
918
  const arg = process.argv[3];
910
919
  if (!arg || arg.startsWith("-")) { console.error("Usage: patchcord provision <agent> --tool X --role Y --namespace ns [--dir sub/]"); process.exit(1); }
911
920
  if (arg === "revoke") {
@@ -922,7 +931,7 @@ if (cmd === "master" || cmd === "provision" || cmd === "team") {
922
931
  const ns = flagVal("namespace");
923
932
  const subdir = flagVal("dir", arg);
924
933
  if (!ns) { console.error("--namespace <project-namespace> required"); process.exit(1); }
925
- const { status, json } = await _httpJSON("POST", `${m.baseUrl}/api/provision`, m.token, { namespace_id: ns, agent_id: arg, tool, role, label: `master:${tool}` });
934
+ const { status, json } = await _httpJSON("POST", `${m.baseUrl}/api/provision`, m.token, { namespace_id: ns, agent_id: arg, tool, role, label: `main:${tool}` });
926
935
  if (status !== "200" || !json?.token) { console.error(`provision failed (HTTP ${status}): ${json?.error || ""}`); process.exit(1); }
927
936
  const base = String(json.url || `${m.baseUrl}/mcp`).replace(/\/mcp$/, "");
928
937
  const dir = join(process.cwd(), subdir);
@@ -931,6 +940,16 @@ if (cmd === "master" || cmd === "provision" || cmd === "team") {
931
940
  if (role) {
932
941
  try { writeFileSync(join(dir, "AGENTS.md"), `# ${arg} — role: ${role}\n\nYou are \`${arg}\` in the \`${ns}\` Patchcord team. Role: ${role}.\nCoordinate with teammates over Patchcord (inbox / send_message / reply).\n`); } catch {}
933
942
  }
943
+ // Record in the local team manifest so `team launch` / `team status` see it.
944
+ try {
945
+ const tj = join(process.cwd(), ".patchcord", "team.json");
946
+ if (existsSync(tj)) {
947
+ const man = JSON.parse(readFileSync(tj, "utf-8"));
948
+ man.agents = (man.agents || []).filter((a) => a.agent !== arg);
949
+ man.agents.push({ agent: arg, tool, role, dir: subdir, namespace: ns });
950
+ writeFileSync(tj, JSON.stringify(man, null, 2) + "\n");
951
+ }
952
+ } catch {}
934
953
  console.log(`✓ provisioned ${M.green}${ns}:${arg}${M.rst} [${tool}${role ? "/" + role : ""}] → ${dir}`);
935
954
  process.exit(0);
936
955
  }
@@ -939,17 +958,39 @@ if (cmd === "master" || cmd === "provision" || cmd === "team") {
939
958
  const sub = process.argv[3];
940
959
  if (sub === "init") {
941
960
  const project = process.argv[4];
942
- if (!project) { console.error("Usage: patchcord team init <project> [--namespace ns]"); process.exit(1); }
961
+ if (!project) { console.error("Usage: patchcord team init <project> [--namespace ns] [--tool <main-harness>]"); process.exit(1); }
943
962
  const ns = flagVal("namespace", project.replace(/[^a-z0-9-]/gi, "-").toLowerCase());
963
+ const mainTool = flagVal("tool", "claude_code");
944
964
  const root = join(process.cwd(), project);
945
965
  mkdirSync(join(root, ".patchcord"), { recursive: true });
946
- writeFileSync(join(root, ".patchcord", "team.json"), JSON.stringify({ project, namespace: ns, pattern: "architect-workers-reviewer", agents: [] }, null, 2) + "\n");
947
- writeFileSync(join(root, "AGENTS.md"), `# ${project} Patchcord team\n\nNamespace: ${ns}\nProvision: patchcord provision <agent> --tool X --role Y --namespace ${ns} --dir <agent>/\n`);
966
+ const manifest = { project, namespace: ns, pattern: "architect-workers-reviewer", main: null, agents: [] };
967
+ // Provision the main's OWN messaging identity IN the team namespace and
968
+ // write its config at the project root, so the main — running here —
969
+ // resolves to ns:main and SEES every worker it provisions into ns.
970
+ // Without this the main has no identity and whoami grabs a stale global.
971
+ const m = readMain();
972
+ if (m) {
973
+ const hostname = run("hostname -s") || run("hostname") || "unknown";
974
+ const { status, json } = await _httpJSON("POST", `${m.baseUrl}/api/provision`, m.token, { namespace_id: ns, agent_id: "main", tool: mainTool, role: "main", label: "main:self" });
975
+ if (status === "200" && json?.token) {
976
+ const base = String(json.url || `${m.baseUrl}/mcp`).replace(/\/mcp$/, "");
977
+ writeWorkerConfig(mainTool, root, base, json.token, hostname);
978
+ manifest.main = { agent: "main", tool: mainTool };
979
+ console.log(`✓ main identity: ${M.green}${ns}:main${M.rst} [${mainTool}] — config written at ${root}`);
980
+ } else {
981
+ console.log(`⚠ could not provision main identity (HTTP ${status}: ${json?.error || ""}) — scaffolding only`);
982
+ }
983
+ } else {
984
+ console.log(`⚠ no main token — run 'patchcord main connect' first, then re-run team init`);
985
+ }
986
+ writeFileSync(join(root, ".patchcord", "team.json"), JSON.stringify(manifest, null, 2) + "\n");
987
+ writeFileSync(join(root, "AGENTS.md"), `# ${project} — Patchcord team\n\nNamespace: ${ns}\nMain identity: ${ns}:main (you, when running patchcord from this folder)\nProvision workers: patchcord provision <agent> --tool X --role Y --namespace ${ns} --dir <agent>/\n`);
948
988
  console.log(`✓ team scaffolded: ${root} (namespace ${ns})`);
989
+ console.log(` ${M.dim}cd ${project} — your identity there is ${ns}:main; you'll see every worker you provision into ${ns}.${M.rst}`);
949
990
  process.exit(0);
950
991
  }
951
992
  if (sub === "list") {
952
- const m = requireMaster();
993
+ const m = requireMain();
953
994
  const { status, json } = await _httpJSON("GET", `${m.baseUrl}/api/provision/list`, m.token);
954
995
  if (status !== "200") { console.error(`list failed (HTTP ${status})`); process.exit(1); }
955
996
  const agents = json?.agents || [];
@@ -957,20 +998,140 @@ if (cmd === "master" || cmd === "provision" || cmd === "team") {
957
998
  if (!agents.length) console.log(" (no agents)");
958
999
  process.exit(0);
959
1000
  }
1001
+ if (sub === "status") {
1002
+ // Reconciler: folder ↔ patchcord identity ↔ tmux session ↔ token-active.
1003
+ // Truth = .patchcord/team.json. For each agent we resolve the token from
1004
+ // ITS folder's config (NOT cwd — identity is the folder), ask the server
1005
+ // who that token is, and cross-check against tmux panes by cwd.
1006
+ let manifest = null;
1007
+ try { manifest = JSON.parse(readFileSync(join(process.cwd(), ".patchcord", "team.json"), "utf-8")); } catch {}
1008
+ if (!manifest) { console.error("No .patchcord/team.json here — cd into the project root."); process.exit(1); }
1009
+ const ns = manifest.namespace;
1010
+ const real = (p) => run(`realpath -m ${JSON.stringify(p)}`) || p;
1011
+ const AGENT_CMDS = /(^|\/)(claude|codex|kimi|kimi-code|opencode|gemini|node)$/;
1012
+
1013
+ // Token reader for a specific folder + tool (folder IS the identity).
1014
+ const tokenInDir = (tool, dir) => {
1015
+ try {
1016
+ if (tool === "codex") {
1017
+ const p = join(dir, ".codex", "config.toml");
1018
+ if (!existsSync(p)) return null;
1019
+ const blk = readFileSync(p, "utf-8").match(/\[mcp_servers\.patchcord[-\w]*\]([\s\S]*?)(?=\n\[|$)/);
1020
+ if (!blk) return null;
1021
+ const u = blk[1].match(/url\s*=\s*"([^"]+)"/);
1022
+ const t = blk[1].match(/Bearer\s+([^\s"]+)/);
1023
+ if (!u || !t) return null;
1024
+ return { token: t[1], baseUrl: u[1].replace(/\/mcp(\/bearer)?$/, "") };
1025
+ }
1026
+ const map = {
1027
+ opencode: [join(dir, "opencode.json"), ["mcp", "patchcord"]],
1028
+ kimi: [join(dir, ".kimi-code", "mcp.json"), ["mcpServers", "patchcord"]],
1029
+ "kimi-code": [join(dir, ".kimi-code", "mcp.json"), ["mcpServers", "patchcord"]],
1030
+ claude_code: [join(dir, ".mcp.json"), ["mcpServers", "patchcord"]],
1031
+ };
1032
+ const [p, keyPath] = map[tool] || map.claude_code;
1033
+ if (!existsSync(p)) return null;
1034
+ const entry = keyPath.reduce((o, k) => o?.[k], JSON.parse(readFileSync(p, "utf-8")));
1035
+ const auth = entry?.headers?.Authorization;
1036
+ const url = entry?.url;
1037
+ if (!auth || !url) return null;
1038
+ return { token: auth.replace(/^Bearer\s+/i, ""), baseUrl: url.replace(/\/mcp(\/bearer)?$/, "") };
1039
+ } catch { return null; }
1040
+ };
1041
+
1042
+ // tmux pane inventory (best-effort).
1043
+ const panes = [];
1044
+ const tmuxOut = run(`tmux list-panes -a -F '#{session_name}\t#{window_index}\t#{pane_current_path}\t#{pane_current_command}'`);
1045
+ if (tmuxOut) for (const l of tmuxOut.split("\n").filter(Boolean)) {
1046
+ const [session, window, path, cmd] = l.split("\t");
1047
+ panes.push({ session, window, path: real(path), cmd, claimed: false });
1048
+ }
1049
+
1050
+ // Build the roster: main first, then workers.
1051
+ const roster = [];
1052
+ const mainEntry = manifest.main || manifest.master; // master = pre-rename
1053
+ if (mainEntry) roster.push({ ...mainEntry, role: "main", dir: ".", _main: true });
1054
+ for (const a of (manifest.agents || [])) roster.push(a);
1055
+
1056
+ const rows = [];
1057
+ for (const a of roster) {
1058
+ const dir = a.dir || a.agent;
1059
+ const abs = real(join(process.cwd(), dir));
1060
+ const exists = existsSync(abs);
1061
+ const cfg = exists ? tokenInDir(a.tool, abs) : null;
1062
+ let token = "no-config", identity = "—";
1063
+ if (cfg) {
1064
+ const { status, json } = await _httpJSON("GET", `${cfg.baseUrl}/api/agent/whoami`, cfg.token);
1065
+ if (status === "200" && json) {
1066
+ identity = `${json.namespace}:${json.agent}`;
1067
+ const expected = `${a.namespace || ns}:${a.agent}`;
1068
+ token = identity === expected ? "live" : `MISMATCH(want ${expected})`;
1069
+ } else if (status === "401" || status === "403") token = `dead(${status})`;
1070
+ else token = `err(${status})`;
1071
+ } else if (!exists) token = "no-folder";
1072
+ // tmux match by folder cwd.
1073
+ let tmux = "—";
1074
+ const hit = panes.find((p) => p.path === abs && AGENT_CMDS.test("/" + p.cmd));
1075
+ if (hit) { hit.claimed = true; tmux = `${hit.session}:${hit.window}(${hit.cmd})`; }
1076
+ else if (panes.some((p) => p.path === abs)) { const p = panes.find((p) => p.path === abs); p.claimed = true; tmux = `${p.session}:${p.window}(shell)`; }
1077
+ rows.push({ role: a.role || "", agent: a.agent, dir: dir === "." ? "./" : dir + "/", identity, token, tmux });
1078
+ }
1079
+
1080
+ // Render.
1081
+ const C = (s, c) => `${c}${s}${M.rst}`;
1082
+ console.log(`\n team ${C(manifest.project, M.cyan)} · namespace ${C(ns, M.cyan)}\n`);
1083
+ const cols = [["ROLE", "role"], ["AGENT", "agent"], ["FOLDER", "dir"], ["IDENTITY", "identity"], ["TOKEN", "token"], ["TMUX", "tmux"]];
1084
+ const w = cols.map(([h, k]) => Math.max(h.length, ...rows.map((r) => String(r[k]).length)));
1085
+ const line = (cells) => " " + cells.map((c, i) => String(c).padEnd(w[i])).join(" ");
1086
+ console.log(C(line(cols.map((c) => c[0])), M.dim));
1087
+ for (const r of rows) {
1088
+ const tokColor = r.token === "live" ? M.green : (r.token.startsWith("MISMATCH") || r.token.startsWith("dead") ? "\x1b[31m" : M.dim);
1089
+ const cells = cols.map(([, k]) => k === "token" ? C(String(r[k]).padEnd(w[cols.findIndex((c) => c[1] === "token")]), tokColor) : String(r[k]).padEnd(w[cols.findIndex((c) => c[1] === k)]));
1090
+ console.log(" " + cells.join(" "));
1091
+ }
1092
+
1093
+ // Orphans: agent CLIs running in folders that are NOT team agents.
1094
+ const orphans = panes.filter((p) => !p.claimed && AGENT_CMDS.test("/" + p.cmd));
1095
+ if (orphans.length) {
1096
+ console.log(`\n ${C("⚠ unanchored agent sessions", "\x1b[33m")} (running outside any team folder — they get GLOBAL identity, not a team seat):`);
1097
+ for (const p of orphans) {
1098
+ const anchored = existsSync(join(p.path, ".mcp.json")) || existsSync(join(p.path, "opencode.json")) || existsSync(join(p.path, ".codex", "config.toml")) || existsSync(join(p.path, ".kimi-code", "mcp.json"));
1099
+ console.log(` ${p.session}:${p.window} ${p.cmd} ${C(p.path, M.dim)}${anchored ? "" : C(" [no project config → falls back to a stale global identity]", "\x1b[33m")}`);
1100
+ }
1101
+ console.log(`\n ${C("fix:", M.dim)} provision a seat for the folder, then relaunch in place WITH the resume flag:`);
1102
+ console.log(` ${C(`patchcord provision <agent> --tool <tool> --role <role> --namespace ${ns} --dir <folder>/`, M.dim)}`);
1103
+ }
1104
+ console.log();
1105
+ process.exit(0);
1106
+ }
960
1107
  if (sub === "launch") {
961
1108
  let manifest = null;
962
1109
  try { manifest = JSON.parse(readFileSync(join(process.cwd(), ".patchcord", "team.json"), "utf-8")); } catch {}
963
1110
  if (!manifest) { console.error("No .patchcord/team.json here — cd into the project root."); process.exit(1); }
964
1111
  const { spawnSync } = await import("child_process");
1112
+ // Already-running folders: `mux new` would FRESH-launch and wipe their
1113
+ // context. Skip them — relaunch-for-identity must resume in place
1114
+ // (see the team-ops / mux skill). launch is for INITIAL boot only.
1115
+ const real = (p) => run(`realpath -m ${JSON.stringify(p)}`) || p;
1116
+ const running = new Set();
1117
+ const tmuxOut = run(`tmux list-panes -a -F '#{pane_current_path}\t#{pane_current_command}'`);
1118
+ if (tmuxOut) for (const l of tmuxOut.split("\n").filter(Boolean)) {
1119
+ const [path, cmd] = l.split("\t");
1120
+ if (/(^|\/)(claude|codex|kimi|kimi-code|opencode|gemini|node)$/.test("/" + cmd)) running.add(real(path));
1121
+ }
965
1122
  for (const a of (manifest.agents || [])) {
966
1123
  const dir = join(process.cwd(), a.dir || a.agent);
1124
+ if (running.has(real(dir))) {
1125
+ console.log(` ${M.dim}skip ${a.agent} — already running in ${dir}; relaunch-for-identity must resume in place, not fresh-launch (see team-ops skill).${M.rst}`);
1126
+ continue;
1127
+ }
967
1128
  const muxTool = a.tool === "claude_code" ? "claude" : (a.tool === "kimi-code" ? "kimi" : a.tool);
968
1129
  console.log(` launching ${a.agent} (${muxTool}) in ${dir}`);
969
1130
  spawnSync("mux", ["new", muxTool, "--dir", dir], { stdio: "inherit" });
970
1131
  }
971
1132
  process.exit(0);
972
1133
  }
973
- console.error("Usage: patchcord team <init|list|launch>");
1134
+ console.error("Usage: patchcord team <init|list|launch|status>");
974
1135
  process.exit(1);
975
1136
  }
976
1137
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "patchcord",
3
- "version": "0.5.98",
3
+ "version": "0.5.100",
4
4
  "description": "Cross-machine agent messaging for Claude Code and Codex",
5
5
  "author": "ppravdin",
6
6
  "license": "MIT",