patchcord 0.5.107 → 0.5.108

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 +88 -68
  2. package/package.json +1 -1
package/bin/patchcord.mjs CHANGED
@@ -86,14 +86,18 @@ THIS AGENT — identity + messaging
86
86
  patchcord subscribe [interval] Start the realtime listener (Kimi: bg poll)
87
87
  patchcord upload <file> [--mime <t>] [--as <name>] Share a file as an attachment
88
88
 
89
- ACCOUNT — user-level (auto-auths the CLI in your browser on first use)
90
- patchcord main Log in / re-authenticate the CLI
89
+ ACCOUNT — log in to the CLI (commands below log in on demand)
90
+ patchcord login Authenticate the CLI (your account)
91
+
92
+ TEAMLEAD — set up the team-lead agent (the shepherd) in this folder
93
+ patchcord teamlead [--namespace <ns>] [--tool <harness>]
94
+ Provision the teamlead here; launch it
95
+ to adopt this project or build a new team
96
+
97
+ TEAM — the teamlead's tools (run from the project root)
91
98
  patchcord provision <agent> --tool <X> --role <Y> --namespace <ns> [--dir <sub/>]
92
99
  Create a worker agent (identity + config)
93
100
  patchcord provision revoke <agent> --namespace <ns>
94
-
95
- TEAM — the main agent's tools (run from the project root)
96
- patchcord team init <project> [--namespace <ns>] [--tool <harness>]
97
101
  patchcord team list [--namespace <ns>] Provisioned agents (server view, deduped)
98
102
  patchcord team status Reconcile folder ↔ identity ↔ tmux ↔ token
99
103
  patchcord team launch Launch each worker in mux
@@ -819,20 +823,21 @@ if (cmd === "subscribe") {
819
823
  // works the same as the space-form (--token foo). The internal flag parsing below
820
824
  // supports both. Non-flag commands (channel, init, skill, help, plugin-path) have
821
825
  // their own branches above and below.
822
- // ── Main agent: provisioning + team orchestration ──────────────────────────
823
- // (docs/main-agent.md). The "main" is the managing agent of a team; its main
824
- // token is a user-scoped provisioning credential stored at ~/.patchcord/main.json
825
- // (legacy: master.json) or $PATCHCORD_MAIN_TOKEN (legacy: $PATCHCORD_MASTER_TOKEN).
826
- if (cmd === "main" || cmd === "provision" || cmd === "team" || cmd === "schedule") {
826
+ // ── CLI account auth + teamlead/team/provisioning/schedule ─────────────────
827
+ // These commands act on the user's ACCOUNT, so they require CLI login. The
828
+ // account token (user-level, tied to NO agent) lives at ~/.patchcord/auth.json
829
+ // (legacy: main.json / master.json) or $PATCHCORD_TOKEN (legacy: *_MAIN/MASTER).
830
+ // `patchcord login` authenticates; everything else logs in on demand.
831
+ if (cmd === "login" || cmd === "teamlead" || cmd === "provision" || cmd === "team" || cmd === "schedule") {
827
832
  const M = { cyan: "\x1b[36m", green: "\x1b[32m", dim: "\x1b[2m", rst: "\x1b[0m" };
828
- const MAIN_CONFIG = join(HOME, ".patchcord", "main.json");
829
- const LEGACY_CONFIG = join(HOME, ".patchcord", "master.json"); // pre-rename
833
+ const AUTH_CONFIG = join(HOME, ".patchcord", "auth.json");
834
+ const LEGACY_CONFIGS = [join(HOME, ".patchcord", "main.json"), join(HOME, ".patchcord", "master.json")];
830
835
  const DEFAULT_API = process.env.PATCHCORD_BASE_URL || "https://api.patchcord.dev";
831
836
 
832
- const readMain = () => {
833
- const envTok = process.env.PATCHCORD_MAIN_TOKEN || process.env.PATCHCORD_MASTER_TOKEN;
837
+ const readAuth = () => {
838
+ const envTok = process.env.PATCHCORD_TOKEN || process.env.PATCHCORD_MAIN_TOKEN || process.env.PATCHCORD_MASTER_TOKEN;
834
839
  if (envTok) return { token: envTok, baseUrl: DEFAULT_API };
835
- for (const p of [MAIN_CONFIG, LEGACY_CONFIG]) {
840
+ for (const p of [AUTH_CONFIG, ...LEGACY_CONFIGS]) {
836
841
  try {
837
842
  const m = JSON.parse(readFileSync(p, "utf-8"));
838
843
  if (m && m.token) return { token: m.token, baseUrl: m.baseUrl || DEFAULT_API };
@@ -847,15 +852,15 @@ if (cmd === "main" || cmd === "provision" || cmd === "team" || cmd === "schedule
847
852
  if (i >= 0 && process.argv[i + 1] && !process.argv[i + 1].startsWith("-")) return process.argv[i + 1];
848
853
  return def;
849
854
  };
850
- // Browser auth flow → saves + returns the account token.
851
- const connectMain = async () => {
852
- const harness = flagVal("harness", flagVal("tool", ""));
853
- const create = run(`curl -sf --max-time 10 -X POST "${DEFAULT_API}/api/main/session" -H "Content-Type: application/json" -d '{"harness":"${harness}"}'`);
855
+ // Browser login → saves + returns the account token. Authenticates the CLI;
856
+ // not related to any agent.
857
+ const doLogin = async () => {
858
+ const create = run(`curl -sf --max-time 10 -X POST "${DEFAULT_API}/api/main/session" -H "Content-Type: application/json" -d '{}'`);
854
859
  let sessionId = "";
855
860
  try { sessionId = (JSON.parse(create).session_id) || ""; } catch {}
856
- if (!sessionId) { console.error("Could not start auth session."); process.exit(1); }
861
+ if (!sessionId) { console.error("Could not start login session."); process.exit(1); }
857
862
  const url = `https://patchcord.dev/master?session=${sessionId}`;
858
- console.log(`\n Authenticate the patchcord CLI in your browser:\n ${M.cyan}${url}${M.rst}\n`);
863
+ console.log(`\n Log in to patchcord in your browser:\n ${M.cyan}${url}${M.rst}\n`);
859
864
  console.log(` ${M.dim}Waiting...${M.rst}`);
860
865
  const http = await import("http");
861
866
  const https = await import("https");
@@ -877,15 +882,14 @@ if (cmd === "main" || cmd === "provision" || cmd === "team" || cmd === "schedule
877
882
  req.on("error", () => resolve(null));
878
883
  setTimeout(() => { try { req.destroy(); } catch {}; resolve(null); }, 900000);
879
884
  });
880
- if (!token) { console.error("\n Authentication timed out or failed."); process.exit(1); }
881
- mkdirSync(dirname(MAIN_CONFIG), { recursive: true });
882
- writeFileSync(MAIN_CONFIG, JSON.stringify({ token, baseUrl: DEFAULT_API }, null, 2) + "\n");
883
- console.log(` ${M.green}✓${M.rst} CLI authenticated ${M.dim}(${MAIN_CONFIG})${M.rst}\n`);
885
+ if (!token) { console.error("\n Login timed out or failed."); process.exit(1); }
886
+ mkdirSync(dirname(AUTH_CONFIG), { recursive: true });
887
+ writeFileSync(AUTH_CONFIG, JSON.stringify({ token, baseUrl: DEFAULT_API }, null, 2) + "\n");
888
+ console.log(` ${M.green}✓${M.rst} Logged in ${M.dim}(${AUTH_CONFIG})${M.rst}\n`);
884
889
  return { token, baseUrl: DEFAULT_API };
885
890
  };
886
- // Auth on demand: any command needing the account token authenticates the
887
- // CLI in the browser if it isn't logged in yet, then proceeds.
888
- const requireMain = async () => readMain() || await connectMain();
891
+ // Auth on demand: any command needing the account logs in if not already.
892
+ const requireAuth = async () => readAuth() || await doLogin();
889
893
  const writeWorkerConfig = (tool, dir, baseUrl, token, hostname) => {
890
894
  mkdirSync(dir, { recursive: true });
891
895
  const hdr = { Authorization: `Bearer ${token}`, "X-Patchcord-Machine": hostname };
@@ -925,15 +929,15 @@ if (cmd === "main" || cmd === "provision" || cmd === "team" || cmd === "schedule
925
929
  });
926
930
  };
927
931
 
928
- if (cmd === "main") {
929
- // Explicit (re)authentication. Other commands auth on demand via
930
- // requireMain(), so this is only needed to log in ahead of time or switch.
931
- await connectMain();
932
+ if (cmd === "login") {
933
+ // Explicit CLI login. Other commands log in on demand via requireAuth(),
934
+ // so this is only needed to authenticate ahead of time or switch accounts.
935
+ await doLogin();
932
936
  process.exit(0);
933
937
  }
934
938
 
935
939
  if (cmd === "provision") {
936
- const m = await requireMain();
940
+ const m = await requireAuth();
937
941
  const arg = process.argv[3];
938
942
  if (!arg || arg.startsWith("-")) { console.error("Usage: patchcord provision <agent> --tool X --role Y --namespace ns [--dir sub/]"); process.exit(1); }
939
943
  if (arg === "revoke") {
@@ -973,39 +977,55 @@ if (cmd === "main" || cmd === "provision" || cmd === "team" || cmd === "schedule
973
977
  process.exit(0);
974
978
  }
975
979
 
980
+ if (cmd === "teamlead") {
981
+ // Set up the TEAMLEAD agent in THIS folder. The teamlead is a distinct kind
982
+ // of agent — it shepherds a team: provisions, connects, and manages the
983
+ // other agents. It is NOT a normal agent: it gets its own identity, its own
984
+ // onboarding instruction, and on launch either ADOPTS this existing project
985
+ // or CREATES a new team by interviewing the user.
986
+ const m = await requireAuth();
987
+ const root = process.cwd();
988
+ const ns = (flagVal("namespace", basename(root)) || "team").replace(/[^a-z0-9-]/gi, "-").toLowerCase();
989
+ const tool = flagVal("tool", "claude_code");
990
+ const hostname = run("hostname -s") || run("hostname") || "unknown";
991
+ const { status, json } = await _httpJSON("POST", `${m.baseUrl}/api/provision`, m.token, { namespace_id: ns, agent_id: "teamlead", tool, role: "teamlead", label: "teamlead:self" });
992
+ if (status !== "200" || !json?.token) { console.error(`could not set up teamlead (HTTP ${status}): ${json?.error || ""}`); process.exit(1); }
993
+ const base = String(json.url || `${m.baseUrl}/mcp`).replace(/\/mcp$/, "");
994
+ writeWorkerConfig(tool, root, base, json.token, hostname);
995
+ mkdirSync(join(root, ".patchcord"), { recursive: true });
996
+ const tj = join(root, ".patchcord", "team.json");
997
+ let manifest = { project: basename(root), namespace: ns, teamlead: { agent: "teamlead", tool }, agents: [] };
998
+ try { if (existsSync(tj)) manifest = { ...JSON.parse(readFileSync(tj, "utf-8")), namespace: ns, teamlead: { agent: "teamlead", tool } }; } catch {}
999
+ writeFileSync(tj, JSON.stringify(manifest, null, 2) + "\n");
1000
+ // The teamlead's onboarding instruction (read on launch).
1001
+ writeFileSync(join(root, "TEAMLEAD.md"), `# You are the TEAMLEAD (${ns}:teamlead)
1002
+
1003
+ You shepherd a team of patchcord agents in this folder. You are NOT a worker —
1004
+ you design the team, provision its agents, launch them, and manage them.
1005
+
1006
+ ## On start, pick ONE
1007
+ 1. **Adopt this project.** If a team already exists here (\`.patchcord/team.json\`
1008
+ lists workers, or agents are already set up), take it into management:
1009
+ \`patchcord team status\` to reconcile folder ↔ identity ↔ tmux ↔ token, then run it.
1010
+ 2. **Create a new team.** Otherwise interview the user — what are we building,
1011
+ which roles/harnesses, how many — confirm the plan, then build it.
1012
+
1013
+ ## Build / run
1014
+ - Provision each worker: \`patchcord provision <agent> --tool <claude_code|codex|opencode|kimi> --role <role> --namespace ${ns} --dir <agent>/\`
1015
+ - Launch: \`patchcord team launch\` (mux), verify with \`patchcord team status\`.
1016
+ - Coordinate over patchcord (inbox / send_message); manage at the meta level.
1017
+ - Your own identity here is ${ns}:teamlead — \`patchcord whoami\` confirms it.
1018
+ `);
1019
+ console.log(`\n ${M.green}✓${M.rst} Teamlead ready: ${M.green}${ns}:teamlead${M.rst} [${tool}] in ${root}`);
1020
+ console.log(` ${M.dim}Launch it here — it adopts this project or creates a new team, asking you:${M.rst}`);
1021
+ console.log(` mux new ${tool === "claude_code" ? "claude" : (tool === "kimi-code" ? "kimi" : tool)} --dir .`);
1022
+ process.exit(0);
1023
+ }
1024
+
976
1025
  if (cmd === "team") {
977
1026
  const sub = process.argv[3];
978
- if (sub === "init") {
979
- const project = process.argv[4];
980
- if (!project) { console.error("Usage: patchcord team init <project> [--namespace ns] [--tool <main-harness>]"); process.exit(1); }
981
- const ns = flagVal("namespace", project.replace(/[^a-z0-9-]/gi, "-").toLowerCase());
982
- const mainTool = flagVal("tool", "claude_code");
983
- const root = join(process.cwd(), project);
984
- mkdirSync(join(root, ".patchcord"), { recursive: true });
985
- const manifest = { project, namespace: ns, pattern: "architect-workers-reviewer", main: null, agents: [] };
986
- // Provision the main's OWN messaging identity IN the team namespace and
987
- // write its config at the project root, so the main — running here —
988
- // resolves to ns:main and SEES every worker it provisions into ns.
989
- // Without this the main has no identity and whoami grabs a stale global.
990
- const m = await requireMain();
991
- const hostname = run("hostname -s") || run("hostname") || "unknown";
992
- 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" });
993
- if (status === "200" && json?.token) {
994
- const base = String(json.url || `${m.baseUrl}/mcp`).replace(/\/mcp$/, "");
995
- writeWorkerConfig(mainTool, root, base, json.token, hostname);
996
- manifest.main = { agent: "main", tool: mainTool };
997
- console.log(`✓ main identity: ${M.green}${ns}:main${M.rst} [${mainTool}] — config written at ${root}`);
998
- } else {
999
- console.log(`⚠ could not provision main identity (HTTP ${status}: ${json?.error || ""}) — scaffolding only`);
1000
- }
1001
- writeFileSync(join(root, ".patchcord", "team.json"), JSON.stringify(manifest, null, 2) + "\n");
1002
- 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`);
1003
- console.log(`✓ team scaffolded: ${root} (namespace ${ns})`);
1004
- console.log(` ${M.dim}cd ${project} — your identity there is ${ns}:main; you'll see every worker you provision into ${ns}.${M.rst}`);
1005
- process.exit(0);
1006
- }
1007
1027
  if (sub === "list") {
1008
- const m = await requireMain();
1028
+ const m = await requireAuth();
1009
1029
  const { status, json } = await _httpJSON("GET", `${m.baseUrl}/api/provision/list`, m.token);
1010
1030
  if (status !== "200") { console.error(`list failed (HTTP ${status})`); process.exit(1); }
1011
1031
  // Dedup by namespace:agent — the server returns one row per token, so a
@@ -1073,10 +1093,10 @@ if (cmd === "main" || cmd === "provision" || cmd === "team" || cmd === "schedule
1073
1093
  panes.push({ session, window, path: real(path), cmd, claimed: false });
1074
1094
  }
1075
1095
 
1076
- // Build the roster: main first, then workers.
1096
+ // Build the roster: teamlead first, then workers.
1077
1097
  const roster = [];
1078
- const mainEntry = manifest.main || manifest.master; // master = pre-rename
1079
- if (mainEntry) roster.push({ ...mainEntry, role: "main", dir: ".", _main: true });
1098
+ const leadEntry = manifest.teamlead || manifest.main || manifest.master; // pre-rename names
1099
+ if (leadEntry) roster.push({ ...leadEntry, role: "teamlead", dir: ".", _lead: true });
1080
1100
  for (const a of (manifest.agents || [])) roster.push(a);
1081
1101
 
1082
1102
  const rows = [];
@@ -1157,14 +1177,14 @@ if (cmd === "main" || cmd === "provision" || cmd === "team" || cmd === "schedule
1157
1177
  }
1158
1178
  process.exit(0);
1159
1179
  }
1160
- console.error("Usage: patchcord team <init|list|launch|status>");
1180
+ console.error("Usage: patchcord team <list|launch|status>");
1161
1181
  process.exit(1);
1162
1182
  }
1163
1183
 
1164
1184
  if (cmd === "schedule") {
1165
1185
  // User-level scheduled / recurring messages (server Plan 041). Authed by
1166
1186
  // the main token — the user can manage schedules in any namespace they own.
1167
- const m = await requireMain();
1187
+ const m = await requireAuth();
1168
1188
  const sub = process.argv[3];
1169
1189
  const BASE = `${m.baseUrl}/api/dashboard/scheduled`;
1170
1190
  const when = (s) => s.cron_expr ? `cron ${s.cron_expr}` : s.interval_sec ? `every ${s.interval_sec}s` : s.fire_at ? `once @ ${s.fire_at}` : s.schedule_kind;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "patchcord",
3
- "version": "0.5.107",
3
+ "version": "0.5.108",
4
4
  "description": "Cross-machine agent messaging for Claude Code and Codex",
5
5
  "author": "ppravdin",
6
6
  "license": "MIT",