patchcord 0.5.108 → 0.5.110
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 +38 -20
- package/package.json +1 -1
package/bin/patchcord.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { existsSync, mkdirSync, cpSync, readdirSync, readFileSync, writeFileSync } from "fs";
|
|
3
|
+
import { existsSync, mkdirSync, cpSync, readdirSync, readFileSync, writeFileSync, unlinkSync } from "fs";
|
|
4
4
|
import { join, dirname, basename } from "path";
|
|
5
5
|
import { fileURLToPath } from "url";
|
|
6
6
|
import { execSync } from "child_process";
|
|
@@ -834,17 +834,26 @@ if (cmd === "login" || cmd === "teamlead" || cmd === "provision" || cmd === "tea
|
|
|
834
834
|
const LEGACY_CONFIGS = [join(HOME, ".patchcord", "main.json"), join(HOME, ".patchcord", "master.json")];
|
|
835
835
|
const DEFAULT_API = process.env.PATCHCORD_BASE_URL || "https://api.patchcord.dev";
|
|
836
836
|
|
|
837
|
+
// Account tokens are always `pcm-` prefixed. Anything else (e.g. a stale
|
|
838
|
+
// worker token `pct-...` or a legacy bearer in $PATCHCORD_TOKEN) is NOT an
|
|
839
|
+
// account credential — ignore it so auth-on-demand falls through to login
|
|
840
|
+
// instead of feeding junk to /api/* and hard-failing with 401.
|
|
841
|
+
const isAccountToken = (t) => typeof t === "string" && t.startsWith("pcm-");
|
|
837
842
|
const readAuth = () => {
|
|
838
843
|
const envTok = process.env.PATCHCORD_TOKEN || process.env.PATCHCORD_MAIN_TOKEN || process.env.PATCHCORD_MASTER_TOKEN;
|
|
839
|
-
if (envTok) return { token: envTok, baseUrl: DEFAULT_API };
|
|
844
|
+
if (envTok && isAccountToken(envTok)) return { token: envTok, baseUrl: DEFAULT_API };
|
|
840
845
|
for (const p of [AUTH_CONFIG, ...LEGACY_CONFIGS]) {
|
|
841
846
|
try {
|
|
842
847
|
const m = JSON.parse(readFileSync(p, "utf-8"));
|
|
843
|
-
if (m && m.token) return { token: m.token, baseUrl: m.baseUrl || DEFAULT_API };
|
|
848
|
+
if (m && isAccountToken(m.token)) return { token: m.token, baseUrl: m.baseUrl || DEFAULT_API };
|
|
844
849
|
} catch {}
|
|
845
850
|
}
|
|
846
851
|
return null;
|
|
847
852
|
};
|
|
853
|
+
// Drop a stale stored account token so the next requireAuth re-logs-in.
|
|
854
|
+
const forgetAuth = () => {
|
|
855
|
+
for (const p of [AUTH_CONFIG, ...LEGACY_CONFIGS]) { try { unlinkSync(p); } catch {} }
|
|
856
|
+
};
|
|
848
857
|
const flagVal = (name, def = "") => {
|
|
849
858
|
const eq = process.argv.find((a) => a.startsWith(`--${name}=`));
|
|
850
859
|
if (eq) return eq.split("=").slice(1).join("=");
|
|
@@ -855,11 +864,11 @@ if (cmd === "login" || cmd === "teamlead" || cmd === "provision" || cmd === "tea
|
|
|
855
864
|
// Browser login → saves + returns the account token. Authenticates the CLI;
|
|
856
865
|
// not related to any agent.
|
|
857
866
|
const doLogin = async () => {
|
|
858
|
-
const create = run(`curl -sf --max-time 10 -X POST "${DEFAULT_API}/api/
|
|
867
|
+
const create = run(`curl -sf --max-time 10 -X POST "${DEFAULT_API}/api/login/session" -H "Content-Type: application/json" -d '{}'`);
|
|
859
868
|
let sessionId = "";
|
|
860
869
|
try { sessionId = (JSON.parse(create).session_id) || ""; } catch {}
|
|
861
870
|
if (!sessionId) { console.error("Could not start login session."); process.exit(1); }
|
|
862
|
-
const url = `https://patchcord.dev/
|
|
871
|
+
const url = `https://patchcord.dev/connect/cli?session=${sessionId}`;
|
|
863
872
|
console.log(`\n Log in to patchcord in your browser:\n ${M.cyan}${url}${M.rst}\n`);
|
|
864
873
|
console.log(` ${M.dim}Waiting...${M.rst}`);
|
|
865
874
|
const http = await import("http");
|
|
@@ -890,6 +899,19 @@ if (cmd === "login" || cmd === "teamlead" || cmd === "provision" || cmd === "tea
|
|
|
890
899
|
};
|
|
891
900
|
// Auth on demand: any command needing the account logs in if not already.
|
|
892
901
|
const requireAuth = async () => readAuth() || await doLogin();
|
|
902
|
+
// Account-authed request with stale-token recovery. If the server rejects the
|
|
903
|
+
// stored token (401/403), forget it, re-log-in once, and retry. Returns the
|
|
904
|
+
// _httpJSON result plus the auth used (for callers that need m.baseUrl).
|
|
905
|
+
const accountCall = async (method, path, body) => {
|
|
906
|
+
let m = await requireAuth();
|
|
907
|
+
let r = await _httpJSON(method, `${m.baseUrl}${path}`, m.token, body);
|
|
908
|
+
if (r.status === "401" || r.status === "403") {
|
|
909
|
+
forgetAuth();
|
|
910
|
+
m = await doLogin();
|
|
911
|
+
r = await _httpJSON(method, `${m.baseUrl}${path}`, m.token, body);
|
|
912
|
+
}
|
|
913
|
+
return { ...r, m };
|
|
914
|
+
};
|
|
893
915
|
const writeWorkerConfig = (tool, dir, baseUrl, token, hostname) => {
|
|
894
916
|
mkdirSync(dir, { recursive: true });
|
|
895
917
|
const hdr = { Authorization: `Bearer ${token}`, "X-Patchcord-Machine": hostname };
|
|
@@ -937,14 +959,13 @@ if (cmd === "login" || cmd === "teamlead" || cmd === "provision" || cmd === "tea
|
|
|
937
959
|
}
|
|
938
960
|
|
|
939
961
|
if (cmd === "provision") {
|
|
940
|
-
const m = await requireAuth();
|
|
941
962
|
const arg = process.argv[3];
|
|
942
963
|
if (!arg || arg.startsWith("-")) { console.error("Usage: patchcord provision <agent> --tool X --role Y --namespace ns [--dir sub/]"); process.exit(1); }
|
|
943
964
|
if (arg === "revoke") {
|
|
944
965
|
const ra = process.argv[4];
|
|
945
966
|
const ns = flagVal("namespace");
|
|
946
967
|
if (!ra || !ns) { console.error("Usage: patchcord provision revoke <agent> --namespace ns"); process.exit(1); }
|
|
947
|
-
const { status, json } = await
|
|
968
|
+
const { status, json } = await accountCall("POST", "/api/provision/revoke", { namespace_id: ns, agent_id: ra });
|
|
948
969
|
if (status !== "200") { console.error(`revoke failed (HTTP ${status}): ${json?.error || ""}`); process.exit(1); }
|
|
949
970
|
console.log(`✓ revoked ${ns}:${ra} (${json.count} token(s))`);
|
|
950
971
|
process.exit(0);
|
|
@@ -954,7 +975,7 @@ if (cmd === "login" || cmd === "teamlead" || cmd === "provision" || cmd === "tea
|
|
|
954
975
|
const ns = flagVal("namespace");
|
|
955
976
|
const subdir = flagVal("dir", arg);
|
|
956
977
|
if (!ns) { console.error("--namespace <project-namespace> required"); process.exit(1); }
|
|
957
|
-
const { status, json } = await
|
|
978
|
+
const { status, json, m } = await accountCall("POST", "/api/provision", { namespace_id: ns, agent_id: arg, tool, role, label: `main:${tool}` });
|
|
958
979
|
if (status !== "200" || !json?.token) { console.error(`provision failed (HTTP ${status}): ${json?.error || ""}`); process.exit(1); }
|
|
959
980
|
const base = String(json.url || `${m.baseUrl}/mcp`).replace(/\/mcp$/, "");
|
|
960
981
|
const dir = join(process.cwd(), subdir);
|
|
@@ -983,12 +1004,11 @@ if (cmd === "login" || cmd === "teamlead" || cmd === "provision" || cmd === "tea
|
|
|
983
1004
|
// other agents. It is NOT a normal agent: it gets its own identity, its own
|
|
984
1005
|
// onboarding instruction, and on launch either ADOPTS this existing project
|
|
985
1006
|
// or CREATES a new team by interviewing the user.
|
|
986
|
-
const m = await requireAuth();
|
|
987
1007
|
const root = process.cwd();
|
|
988
1008
|
const ns = (flagVal("namespace", basename(root)) || "team").replace(/[^a-z0-9-]/gi, "-").toLowerCase();
|
|
989
1009
|
const tool = flagVal("tool", "claude_code");
|
|
990
1010
|
const hostname = run("hostname -s") || run("hostname") || "unknown";
|
|
991
|
-
const { status, json } = await
|
|
1011
|
+
const { status, json, m } = await accountCall("POST", "/api/provision", { namespace_id: ns, agent_id: "teamlead", tool, role: "teamlead", label: "teamlead:self" });
|
|
992
1012
|
if (status !== "200" || !json?.token) { console.error(`could not set up teamlead (HTTP ${status}): ${json?.error || ""}`); process.exit(1); }
|
|
993
1013
|
const base = String(json.url || `${m.baseUrl}/mcp`).replace(/\/mcp$/, "");
|
|
994
1014
|
writeWorkerConfig(tool, root, base, json.token, hostname);
|
|
@@ -1025,8 +1045,7 @@ you design the team, provision its agents, launch them, and manage them.
|
|
|
1025
1045
|
if (cmd === "team") {
|
|
1026
1046
|
const sub = process.argv[3];
|
|
1027
1047
|
if (sub === "list") {
|
|
1028
|
-
const
|
|
1029
|
-
const { status, json } = await _httpJSON("GET", `${m.baseUrl}/api/provision/list`, m.token);
|
|
1048
|
+
const { status, json } = await accountCall("GET", "/api/provision/list");
|
|
1030
1049
|
if (status !== "200") { console.error(`list failed (HTTP ${status})`); process.exit(1); }
|
|
1031
1050
|
// Dedup by namespace:agent — the server returns one row per token, so a
|
|
1032
1051
|
// re-provisioned agent shows up many times. Optional --namespace filter.
|
|
@@ -1184,16 +1203,15 @@ you design the team, provision its agents, launch them, and manage them.
|
|
|
1184
1203
|
if (cmd === "schedule") {
|
|
1185
1204
|
// User-level scheduled / recurring messages (server Plan 041). Authed by
|
|
1186
1205
|
// the main token — the user can manage schedules in any namespace they own.
|
|
1187
|
-
const m = await requireAuth();
|
|
1188
1206
|
const sub = process.argv[3];
|
|
1189
|
-
const BASE =
|
|
1207
|
+
const BASE = `/api/dashboard/scheduled`;
|
|
1190
1208
|
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;
|
|
1191
1209
|
const fmt = (s) => `${s.id} ${M.green}${s.namespace_id}:${s.to_agent}${M.rst} ${s.active ? "" : M.dim + "[paused] " + M.rst}${s.name} ${M.dim}${when(s)}${s.next_fire_at ? ` → ${s.next_fire_at}` : ""}${M.rst}`;
|
|
1192
1210
|
|
|
1193
1211
|
if (sub === "list") {
|
|
1194
1212
|
const ns = flagVal("namespace");
|
|
1195
1213
|
const url = ns ? `${BASE}?namespace=${encodeURIComponent(ns)}` : BASE;
|
|
1196
|
-
const { status, json } = await
|
|
1214
|
+
const { status, json } = await accountCall("GET", url);
|
|
1197
1215
|
if (status !== "200") { console.error(`list failed (HTTP ${status}): ${json?.error || ""}`); process.exit(1); }
|
|
1198
1216
|
const items = json?.schedules || [];
|
|
1199
1217
|
for (const s of items) console.log(" " + fmt(s));
|
|
@@ -1215,7 +1233,7 @@ you design the team, provision its agents, launch them, and manage them.
|
|
|
1215
1233
|
const thread = flagVal("thread"); if (thread) body.thread_slug = thread;
|
|
1216
1234
|
const maxRuns = flagVal("max-runs"); if (maxRuns) body.max_runs = parseInt(maxRuns, 10);
|
|
1217
1235
|
const expires = flagVal("expires"); if (expires) body.expires_at = expires;
|
|
1218
|
-
const { status, json } = await
|
|
1236
|
+
const { status, json } = await accountCall("POST", BASE, body);
|
|
1219
1237
|
if (status !== "201" && status !== "200") { console.error(`create failed (HTTP ${status}): ${json?.error || ""}`); process.exit(1); }
|
|
1220
1238
|
console.log(`✓ scheduled ${M.green}${ns}:${to}${M.rst} [${schedule_kind}] ${M.dim}${json?.schedule?.id || ""}${M.rst}`);
|
|
1221
1239
|
process.exit(0);
|
|
@@ -1223,7 +1241,7 @@ you design the team, provision its agents, launch them, and manage them.
|
|
|
1223
1241
|
if (sub === "cancel" || sub === "delete" || sub === "rm") {
|
|
1224
1242
|
const id = process.argv[4];
|
|
1225
1243
|
if (!id) { console.error("Usage: patchcord schedule cancel <id>"); process.exit(1); }
|
|
1226
|
-
const { status, json } = await
|
|
1244
|
+
const { status, json } = await accountCall("DELETE", `${BASE}/${id}`);
|
|
1227
1245
|
if (status !== "200") { console.error(`cancel failed (HTTP ${status}): ${json?.error || ""}`); process.exit(1); }
|
|
1228
1246
|
console.log(`✓ cancelled ${id}`);
|
|
1229
1247
|
process.exit(0);
|
|
@@ -1231,7 +1249,7 @@ you design the team, provision its agents, launch them, and manage them.
|
|
|
1231
1249
|
if (sub === "test" || sub === "fire") {
|
|
1232
1250
|
const id = process.argv[4];
|
|
1233
1251
|
if (!id) { console.error("Usage: patchcord schedule test <id>"); process.exit(1); }
|
|
1234
|
-
const { status, json } = await
|
|
1252
|
+
const { status, json } = await accountCall("POST", `${BASE}/${id}/test`, {});
|
|
1235
1253
|
if (status !== "200") { console.error(`test failed (HTTP ${status}): ${json?.error || ""}`); process.exit(1); }
|
|
1236
1254
|
console.log(`✓ fired ${id} once (does not consume max-runs)`);
|
|
1237
1255
|
process.exit(0);
|
|
@@ -1239,7 +1257,7 @@ you design the team, provision its agents, launch them, and manage them.
|
|
|
1239
1257
|
if (sub === "pause" || sub === "resume") {
|
|
1240
1258
|
const id = process.argv[4];
|
|
1241
1259
|
if (!id) { console.error(`Usage: patchcord schedule ${sub} <id>`); process.exit(1); }
|
|
1242
|
-
const { status, json } = await
|
|
1260
|
+
const { status, json } = await accountCall("PATCH", `${BASE}/${id}`, { active: sub === "resume" });
|
|
1243
1261
|
if (status !== "200") { console.error(`${sub} failed (HTTP ${status}): ${json?.error || ""}`); process.exit(1); }
|
|
1244
1262
|
console.log(`✓ ${sub === "resume" ? "resumed" : "paused"} ${id}`);
|
|
1245
1263
|
process.exit(0);
|
|
@@ -2897,5 +2915,5 @@ if (cmd === "skill") {
|
|
|
2897
2915
|
process.exit(0);
|
|
2898
2916
|
}
|
|
2899
2917
|
|
|
2900
|
-
console.error(`Unknown command: ${cmd}. Available: whoami, agents, upload, subscribe, update, --rename, --token, --agent-type, --version, --help`);
|
|
2918
|
+
console.error(`Unknown command: ${cmd}. Available: install, whoami, agents, upload, subscribe, update, login, provision, teamlead, team, schedule, --rename, --token, --agent-type, --version, --help`);
|
|
2901
2919
|
process.exit(1);
|