patchcord 0.5.106 → 0.5.107

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 +96 -74
  2. package/package.json +1 -1
package/bin/patchcord.mjs CHANGED
@@ -67,22 +67,48 @@ function detectFolder(dir) {
67
67
 
68
68
 
69
69
  if (cmd === "help" || cmd === "--help" || cmd === "-h") {
70
- console.log(`patchcord — agent messaging for AI coding agents
71
-
72
- Usage:
73
- patchcord Setup via browser (patchcord.dev)
74
- patchcord --token <token> [--server <url>] Headless / self-hosted setup
75
- patchcord --full Same + full statusline
76
- patchcord --rename <new-name> [--agent-type <type>] Rename this agent
77
- patchcord whoami Show your identity + project
78
- patchcord whoami --propose "<text>" Update your whoami (two-shot: first call asks you to show human, second call applies)
79
- patchcord agents List every agent (with whoami)
80
- patchcord agents <name> Show one agent's whoami
81
- patchcord upload <file> [--mime <type>] [--as <name>] Upload a file as a patchcord attachment (prints the storage path)
82
- patchcord subscribe [interval] Start the realtime listener (Kimi: background poll, default 30s)
83
- patchcord update Update to the latest version
84
- patchcord --version Show installed version
85
- patchcord --help Show this help
70
+ console.log(`patchcord — cross-agent messaging + team orchestration for AI coding agents
71
+
72
+ Two layers: THIS AGENT (a per-project identity, by bearer token) and your ACCOUNT
73
+ (user-level; provisions agents, runs teams + schedules). Account/team/schedule
74
+ commands authenticate the CLI in your browser automatically on first use.
75
+
76
+ SETUP (per agent / project)
77
+ patchcord Set up this project's agent (browser)
78
+ patchcord --token <t> [--server <url>] Headless / self-hosted setup
79
+ patchcord --full Same + full statusline
80
+ patchcord --rename <name> [--agent-type <t>] Rename this agent
81
+
82
+ THIS AGENT identity + messaging
83
+ patchcord whoami Who am I this agent's identity + project
84
+ patchcord whoami --propose "<text>" Set your self-description (two-shot confirm)
85
+ patchcord agents [name] List agents in your namespace (or show one)
86
+ patchcord subscribe [interval] Start the realtime listener (Kimi: bg poll)
87
+ patchcord upload <file> [--mime <t>] [--as <name>] Share a file as an attachment
88
+
89
+ ACCOUNT — user-level (auto-auths the CLI in your browser on first use)
90
+ patchcord main Log in / re-authenticate the CLI
91
+ patchcord provision <agent> --tool <X> --role <Y> --namespace <ns> [--dir <sub/>]
92
+ Create a worker agent (identity + config)
93
+ 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
+ patchcord team list [--namespace <ns>] Provisioned agents (server view, deduped)
98
+ patchcord team status Reconcile folder ↔ identity ↔ tmux ↔ token
99
+ patchcord team launch Launch each worker in mux
100
+
101
+ SCHEDULES — timed / recurring messages (user-level)
102
+ patchcord schedule create <name> --namespace <ns> --to <agent> --content "..." \\
103
+ (--at <ISO> | --cron "<expr>" | --every <sec>) \\
104
+ [--timezone <tz>] [--thread <slug>] [--max-runs N] [--expires <ISO>]
105
+ patchcord schedule list [--namespace <ns>]
106
+ patchcord schedule cancel|test|pause|resume <id>
107
+
108
+ MISC
109
+ patchcord update Update to the latest version
110
+ patchcord --version Show installed version
111
+ patchcord --help Show this help
86
112
 
87
113
  First install: npx patchcord@latest`);
88
114
  process.exit(0);
@@ -821,11 +847,45 @@ if (cmd === "main" || cmd === "provision" || cmd === "team" || cmd === "schedule
821
847
  if (i >= 0 && process.argv[i + 1] && !process.argv[i + 1].startsWith("-")) return process.argv[i + 1];
822
848
  return def;
823
849
  };
824
- const requireMain = () => {
825
- const m = readMain();
826
- if (!m) { console.error("No main token. Run: patchcord main connect"); process.exit(1); }
827
- return m;
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}"}'`);
854
+ let sessionId = "";
855
+ try { sessionId = (JSON.parse(create).session_id) || ""; } catch {}
856
+ if (!sessionId) { console.error("Could not start auth session."); process.exit(1); }
857
+ 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`);
859
+ console.log(` ${M.dim}Waiting...${M.rst}`);
860
+ const http = await import("http");
861
+ const https = await import("https");
862
+ const token = await new Promise((resolve) => {
863
+ const lib = DEFAULT_API.startsWith("https") ? https : http;
864
+ const req = lib.get(`${DEFAULT_API}/api/connect/session/${sessionId}/wait`, { headers: { Accept: "text/event-stream" } }, (res) => {
865
+ let buf = "";
866
+ res.on("data", (c) => {
867
+ buf += c.toString();
868
+ for (const line of buf.split("\n")) {
869
+ if (line.startsWith("data: ")) {
870
+ try { const d = JSON.parse(line.slice(6)); if (d.token) { resolve(d.token); try { req.destroy(); } catch {} return; } } catch {}
871
+ }
872
+ }
873
+ });
874
+ res.on("end", () => resolve(null));
875
+ res.on("error", () => resolve(null));
876
+ });
877
+ req.on("error", () => resolve(null));
878
+ setTimeout(() => { try { req.destroy(); } catch {}; resolve(null); }, 900000);
879
+ });
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`);
884
+ return { token, baseUrl: DEFAULT_API };
828
885
  };
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();
829
889
  const writeWorkerConfig = (tool, dir, baseUrl, token, hostname) => {
830
890
  mkdirSync(dir, { recursive: true });
831
891
  const hdr = { Authorization: `Bearer ${token}`, "X-Patchcord-Machine": hostname };
@@ -866,48 +926,14 @@ if (cmd === "main" || cmd === "provision" || cmd === "team" || cmd === "schedule
866
926
  };
867
927
 
868
928
  if (cmd === "main") {
869
- const sub = process.argv[3];
870
- if (sub === "connect") {
871
- const harness = flagVal("harness", flagVal("tool", ""));
872
- const create = run(`curl -sf --max-time 10 -X POST "${DEFAULT_API}/api/main/session" -H "Content-Type: application/json" -d '{"harness":"${harness}"}'`);
873
- let sessionId = "";
874
- try { sessionId = (JSON.parse(create).session_id) || ""; } catch {}
875
- if (!sessionId) { console.error("Could not start main-auth session."); process.exit(1); }
876
- const url = `https://patchcord.dev/master?session=${sessionId}`;
877
- console.log(`\n Authorize the main agent in your browser:\n ${M.cyan}${url}${M.rst}\n`);
878
- console.log(` ${M.dim}Waiting for authorization...${M.rst}`);
879
- const http = await import("http");
880
- const https = await import("https");
881
- const token = await new Promise((resolve) => {
882
- const lib = DEFAULT_API.startsWith("https") ? https : http;
883
- const req = lib.get(`${DEFAULT_API}/api/connect/session/${sessionId}/wait`, { headers: { Accept: "text/event-stream" } }, (res) => {
884
- let buf = "";
885
- res.on("data", (c) => {
886
- buf += c.toString();
887
- for (const line of buf.split("\n")) {
888
- if (line.startsWith("data: ")) {
889
- try { const d = JSON.parse(line.slice(6)); if (d.token) { resolve(d.token); try { req.destroy(); } catch {} return; } } catch {}
890
- }
891
- }
892
- });
893
- res.on("end", () => resolve(null));
894
- res.on("error", () => resolve(null));
895
- });
896
- req.on("error", () => resolve(null));
897
- setTimeout(() => { try { req.destroy(); } catch {}; resolve(null); }, 900000);
898
- });
899
- if (!token) { console.error("\n Authorization timed out or failed."); process.exit(1); }
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}`);
903
- process.exit(0);
904
- }
905
- console.error("Usage: patchcord main connect");
906
- process.exit(1);
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
+ process.exit(0);
907
933
  }
908
934
 
909
935
  if (cmd === "provision") {
910
- const m = requireMain();
936
+ const m = await requireMain();
911
937
  const arg = process.argv[3];
912
938
  if (!arg || arg.startsWith("-")) { console.error("Usage: patchcord provision <agent> --tool X --role Y --namespace ns [--dir sub/]"); process.exit(1); }
913
939
  if (arg === "revoke") {
@@ -961,20 +987,16 @@ if (cmd === "main" || cmd === "provision" || cmd === "team" || cmd === "schedule
961
987
  // write its config at the project root, so the main — running here —
962
988
  // resolves to ns:main and SEES every worker it provisions into ns.
963
989
  // Without this the main has no identity and whoami grabs a stale global.
964
- const m = readMain();
965
- if (m) {
966
- const hostname = run("hostname -s") || run("hostname") || "unknown";
967
- 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" });
968
- if (status === "200" && json?.token) {
969
- const base = String(json.url || `${m.baseUrl}/mcp`).replace(/\/mcp$/, "");
970
- writeWorkerConfig(mainTool, root, base, json.token, hostname);
971
- manifest.main = { agent: "main", tool: mainTool };
972
- console.log(`✓ main identity: ${M.green}${ns}:main${M.rst} [${mainTool}] — config written at ${root}`);
973
- } else {
974
- console.log(`⚠ could not provision main identity (HTTP ${status}: ${json?.error || ""}) — scaffolding only`);
975
- }
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}`);
976
998
  } else {
977
- console.log(`⚠ no main token run 'patchcord main connect' first, then re-run team init`);
999
+ console.log(`⚠ could not provision main identity (HTTP ${status}: ${json?.error || ""}) scaffolding only`);
978
1000
  }
979
1001
  writeFileSync(join(root, ".patchcord", "team.json"), JSON.stringify(manifest, null, 2) + "\n");
980
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`);
@@ -983,7 +1005,7 @@ if (cmd === "main" || cmd === "provision" || cmd === "team" || cmd === "schedule
983
1005
  process.exit(0);
984
1006
  }
985
1007
  if (sub === "list") {
986
- const m = requireMain();
1008
+ const m = await requireMain();
987
1009
  const { status, json } = await _httpJSON("GET", `${m.baseUrl}/api/provision/list`, m.token);
988
1010
  if (status !== "200") { console.error(`list failed (HTTP ${status})`); process.exit(1); }
989
1011
  // Dedup by namespace:agent — the server returns one row per token, so a
@@ -1142,7 +1164,7 @@ if (cmd === "main" || cmd === "provision" || cmd === "team" || cmd === "schedule
1142
1164
  if (cmd === "schedule") {
1143
1165
  // User-level scheduled / recurring messages (server Plan 041). Authed by
1144
1166
  // the main token — the user can manage schedules in any namespace they own.
1145
- const m = requireMain();
1167
+ const m = await requireMain();
1146
1168
  const sub = process.argv[3];
1147
1169
  const BASE = `${m.baseUrl}/api/dashboard/scheduled`;
1148
1170
  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.106",
3
+ "version": "0.5.107",
4
4
  "description": "Cross-machine agent messaging for Claude Code and Codex",
5
5
  "author": "ppravdin",
6
6
  "license": "MIT",