patchcord 0.5.109 → 0.5.111
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 +40 -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("=");
|
|
@@ -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 };
|
|
@@ -922,10 +944,12 @@ if (cmd === "login" || cmd === "teamlead" || cmd === "provision" || cmd === "tea
|
|
|
922
944
|
o.mcp.patchcord = { type: "remote", url: `${baseUrl}/mcp`, headers: hdr };
|
|
923
945
|
});
|
|
924
946
|
}
|
|
925
|
-
// default: claude_code
|
|
947
|
+
// default: claude_code. type:"http" is REQUIRED — without it Claude Code
|
|
948
|
+
// defaults to stdio transport and rejects the entry ("command: expected
|
|
949
|
+
// string, received undefined").
|
|
926
950
|
return writeJson(join(dir, ".mcp.json"), (o) => {
|
|
927
951
|
o.mcpServers = o.mcpServers || {};
|
|
928
|
-
o.mcpServers.patchcord = { url: `${baseUrl}/mcp`, headers: hdr };
|
|
952
|
+
o.mcpServers.patchcord = { type: "http", url: `${baseUrl}/mcp`, headers: hdr };
|
|
929
953
|
});
|
|
930
954
|
};
|
|
931
955
|
|
|
@@ -937,14 +961,13 @@ if (cmd === "login" || cmd === "teamlead" || cmd === "provision" || cmd === "tea
|
|
|
937
961
|
}
|
|
938
962
|
|
|
939
963
|
if (cmd === "provision") {
|
|
940
|
-
const m = await requireAuth();
|
|
941
964
|
const arg = process.argv[3];
|
|
942
965
|
if (!arg || arg.startsWith("-")) { console.error("Usage: patchcord provision <agent> --tool X --role Y --namespace ns [--dir sub/]"); process.exit(1); }
|
|
943
966
|
if (arg === "revoke") {
|
|
944
967
|
const ra = process.argv[4];
|
|
945
968
|
const ns = flagVal("namespace");
|
|
946
969
|
if (!ra || !ns) { console.error("Usage: patchcord provision revoke <agent> --namespace ns"); process.exit(1); }
|
|
947
|
-
const { status, json } = await
|
|
970
|
+
const { status, json } = await accountCall("POST", "/api/provision/revoke", { namespace_id: ns, agent_id: ra });
|
|
948
971
|
if (status !== "200") { console.error(`revoke failed (HTTP ${status}): ${json?.error || ""}`); process.exit(1); }
|
|
949
972
|
console.log(`✓ revoked ${ns}:${ra} (${json.count} token(s))`);
|
|
950
973
|
process.exit(0);
|
|
@@ -954,7 +977,7 @@ if (cmd === "login" || cmd === "teamlead" || cmd === "provision" || cmd === "tea
|
|
|
954
977
|
const ns = flagVal("namespace");
|
|
955
978
|
const subdir = flagVal("dir", arg);
|
|
956
979
|
if (!ns) { console.error("--namespace <project-namespace> required"); process.exit(1); }
|
|
957
|
-
const { status, json } = await
|
|
980
|
+
const { status, json, m } = await accountCall("POST", "/api/provision", { namespace_id: ns, agent_id: arg, tool, role, label: `main:${tool}` });
|
|
958
981
|
if (status !== "200" || !json?.token) { console.error(`provision failed (HTTP ${status}): ${json?.error || ""}`); process.exit(1); }
|
|
959
982
|
const base = String(json.url || `${m.baseUrl}/mcp`).replace(/\/mcp$/, "");
|
|
960
983
|
const dir = join(process.cwd(), subdir);
|
|
@@ -983,12 +1006,11 @@ if (cmd === "login" || cmd === "teamlead" || cmd === "provision" || cmd === "tea
|
|
|
983
1006
|
// other agents. It is NOT a normal agent: it gets its own identity, its own
|
|
984
1007
|
// onboarding instruction, and on launch either ADOPTS this existing project
|
|
985
1008
|
// or CREATES a new team by interviewing the user.
|
|
986
|
-
const m = await requireAuth();
|
|
987
1009
|
const root = process.cwd();
|
|
988
1010
|
const ns = (flagVal("namespace", basename(root)) || "team").replace(/[^a-z0-9-]/gi, "-").toLowerCase();
|
|
989
1011
|
const tool = flagVal("tool", "claude_code");
|
|
990
1012
|
const hostname = run("hostname -s") || run("hostname") || "unknown";
|
|
991
|
-
const { status, json } = await
|
|
1013
|
+
const { status, json, m } = await accountCall("POST", "/api/provision", { namespace_id: ns, agent_id: "teamlead", tool, role: "teamlead", label: "teamlead:self" });
|
|
992
1014
|
if (status !== "200" || !json?.token) { console.error(`could not set up teamlead (HTTP ${status}): ${json?.error || ""}`); process.exit(1); }
|
|
993
1015
|
const base = String(json.url || `${m.baseUrl}/mcp`).replace(/\/mcp$/, "");
|
|
994
1016
|
writeWorkerConfig(tool, root, base, json.token, hostname);
|
|
@@ -1025,8 +1047,7 @@ you design the team, provision its agents, launch them, and manage them.
|
|
|
1025
1047
|
if (cmd === "team") {
|
|
1026
1048
|
const sub = process.argv[3];
|
|
1027
1049
|
if (sub === "list") {
|
|
1028
|
-
const
|
|
1029
|
-
const { status, json } = await _httpJSON("GET", `${m.baseUrl}/api/provision/list`, m.token);
|
|
1050
|
+
const { status, json } = await accountCall("GET", "/api/provision/list");
|
|
1030
1051
|
if (status !== "200") { console.error(`list failed (HTTP ${status})`); process.exit(1); }
|
|
1031
1052
|
// Dedup by namespace:agent — the server returns one row per token, so a
|
|
1032
1053
|
// re-provisioned agent shows up many times. Optional --namespace filter.
|
|
@@ -1184,16 +1205,15 @@ you design the team, provision its agents, launch them, and manage them.
|
|
|
1184
1205
|
if (cmd === "schedule") {
|
|
1185
1206
|
// User-level scheduled / recurring messages (server Plan 041). Authed by
|
|
1186
1207
|
// the main token — the user can manage schedules in any namespace they own.
|
|
1187
|
-
const m = await requireAuth();
|
|
1188
1208
|
const sub = process.argv[3];
|
|
1189
|
-
const BASE =
|
|
1209
|
+
const BASE = `/api/dashboard/scheduled`;
|
|
1190
1210
|
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
1211
|
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
1212
|
|
|
1193
1213
|
if (sub === "list") {
|
|
1194
1214
|
const ns = flagVal("namespace");
|
|
1195
1215
|
const url = ns ? `${BASE}?namespace=${encodeURIComponent(ns)}` : BASE;
|
|
1196
|
-
const { status, json } = await
|
|
1216
|
+
const { status, json } = await accountCall("GET", url);
|
|
1197
1217
|
if (status !== "200") { console.error(`list failed (HTTP ${status}): ${json?.error || ""}`); process.exit(1); }
|
|
1198
1218
|
const items = json?.schedules || [];
|
|
1199
1219
|
for (const s of items) console.log(" " + fmt(s));
|
|
@@ -1215,7 +1235,7 @@ you design the team, provision its agents, launch them, and manage them.
|
|
|
1215
1235
|
const thread = flagVal("thread"); if (thread) body.thread_slug = thread;
|
|
1216
1236
|
const maxRuns = flagVal("max-runs"); if (maxRuns) body.max_runs = parseInt(maxRuns, 10);
|
|
1217
1237
|
const expires = flagVal("expires"); if (expires) body.expires_at = expires;
|
|
1218
|
-
const { status, json } = await
|
|
1238
|
+
const { status, json } = await accountCall("POST", BASE, body);
|
|
1219
1239
|
if (status !== "201" && status !== "200") { console.error(`create failed (HTTP ${status}): ${json?.error || ""}`); process.exit(1); }
|
|
1220
1240
|
console.log(`✓ scheduled ${M.green}${ns}:${to}${M.rst} [${schedule_kind}] ${M.dim}${json?.schedule?.id || ""}${M.rst}`);
|
|
1221
1241
|
process.exit(0);
|
|
@@ -1223,7 +1243,7 @@ you design the team, provision its agents, launch them, and manage them.
|
|
|
1223
1243
|
if (sub === "cancel" || sub === "delete" || sub === "rm") {
|
|
1224
1244
|
const id = process.argv[4];
|
|
1225
1245
|
if (!id) { console.error("Usage: patchcord schedule cancel <id>"); process.exit(1); }
|
|
1226
|
-
const { status, json } = await
|
|
1246
|
+
const { status, json } = await accountCall("DELETE", `${BASE}/${id}`);
|
|
1227
1247
|
if (status !== "200") { console.error(`cancel failed (HTTP ${status}): ${json?.error || ""}`); process.exit(1); }
|
|
1228
1248
|
console.log(`✓ cancelled ${id}`);
|
|
1229
1249
|
process.exit(0);
|
|
@@ -1231,7 +1251,7 @@ you design the team, provision its agents, launch them, and manage them.
|
|
|
1231
1251
|
if (sub === "test" || sub === "fire") {
|
|
1232
1252
|
const id = process.argv[4];
|
|
1233
1253
|
if (!id) { console.error("Usage: patchcord schedule test <id>"); process.exit(1); }
|
|
1234
|
-
const { status, json } = await
|
|
1254
|
+
const { status, json } = await accountCall("POST", `${BASE}/${id}/test`, {});
|
|
1235
1255
|
if (status !== "200") { console.error(`test failed (HTTP ${status}): ${json?.error || ""}`); process.exit(1); }
|
|
1236
1256
|
console.log(`✓ fired ${id} once (does not consume max-runs)`);
|
|
1237
1257
|
process.exit(0);
|
|
@@ -1239,7 +1259,7 @@ you design the team, provision its agents, launch them, and manage them.
|
|
|
1239
1259
|
if (sub === "pause" || sub === "resume") {
|
|
1240
1260
|
const id = process.argv[4];
|
|
1241
1261
|
if (!id) { console.error(`Usage: patchcord schedule ${sub} <id>`); process.exit(1); }
|
|
1242
|
-
const { status, json } = await
|
|
1262
|
+
const { status, json } = await accountCall("PATCH", `${BASE}/${id}`, { active: sub === "resume" });
|
|
1243
1263
|
if (status !== "200") { console.error(`${sub} failed (HTTP ${status}): ${json?.error || ""}`); process.exit(1); }
|
|
1244
1264
|
console.log(`✓ ${sub === "resume" ? "resumed" : "paused"} ${id}`);
|
|
1245
1265
|
process.exit(0);
|
|
@@ -2897,5 +2917,5 @@ if (cmd === "skill") {
|
|
|
2897
2917
|
process.exit(0);
|
|
2898
2918
|
}
|
|
2899
2919
|
|
|
2900
|
-
console.error(`Unknown command: ${cmd}. Available: whoami, agents, upload, subscribe, update, --rename, --token, --agent-type, --version, --help`);
|
|
2920
|
+
console.error(`Unknown command: ${cmd}. Available: install, whoami, agents, upload, subscribe, update, login, provision, teamlead, team, schedule, --rename, --token, --agent-type, --version, --help`);
|
|
2901
2921
|
process.exit(1);
|