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.
- package/bin/patchcord.mjs +96 -74
- 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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
patchcord
|
|
78
|
-
patchcord
|
|
79
|
-
patchcord
|
|
80
|
-
patchcord
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
patchcord
|
|
84
|
-
patchcord --
|
|
85
|
-
patchcord
|
|
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
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
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
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
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 =
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
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(`⚠
|
|
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;
|