patchcord 0.5.107 → 0.5.109
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 +89 -69
- 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 —
|
|
90
|
-
patchcord
|
|
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
|
-
// ──
|
|
823
|
-
//
|
|
824
|
-
// token
|
|
825
|
-
// (legacy: master.json) or $
|
|
826
|
-
|
|
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
|
|
829
|
-
const
|
|
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
|
|
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 [
|
|
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
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
const create = run(`curl -sf --max-time 10 -X POST "${DEFAULT_API}/api/
|
|
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/login/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
|
|
857
|
-
const url = `https://patchcord.dev/
|
|
858
|
-
console.log(`\n
|
|
861
|
+
if (!sessionId) { console.error("Could not start login session."); process.exit(1); }
|
|
862
|
+
const url = `https://patchcord.dev/connect/cli?session=${sessionId}`;
|
|
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
|
|
881
|
-
mkdirSync(dirname(
|
|
882
|
-
writeFileSync(
|
|
883
|
-
console.log(` ${M.green}✓${M.rst}
|
|
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
|
|
887
|
-
|
|
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 === "
|
|
929
|
-
// Explicit
|
|
930
|
-
//
|
|
931
|
-
await
|
|
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
|
|
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
|
|
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:
|
|
1096
|
+
// Build the roster: teamlead first, then workers.
|
|
1077
1097
|
const roster = [];
|
|
1078
|
-
const
|
|
1079
|
-
if (
|
|
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 <
|
|
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
|
|
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;
|