patchcord 0.5.104 → 0.5.106

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 +84 -45
  2. package/package.json +1 -1
package/bin/patchcord.mjs CHANGED
@@ -797,7 +797,7 @@ if (cmd === "subscribe") {
797
797
  // (docs/main-agent.md). The "main" is the managing agent of a team; its main
798
798
  // token is a user-scoped provisioning credential stored at ~/.patchcord/main.json
799
799
  // (legacy: master.json) or $PATCHCORD_MAIN_TOKEN (legacy: $PATCHCORD_MASTER_TOKEN).
800
- if (cmd === "main" || cmd === "master" || cmd === "provision" || cmd === "team") {
800
+ if (cmd === "main" || cmd === "provision" || cmd === "team" || cmd === "schedule") {
801
801
  const M = { cyan: "\x1b[36m", green: "\x1b[32m", dim: "\x1b[2m", rst: "\x1b[0m" };
802
802
  const MAIN_CONFIG = join(HOME, ".patchcord", "main.json");
803
803
  const LEGACY_CONFIG = join(HOME, ".patchcord", "master.json"); // pre-rename
@@ -865,7 +865,7 @@ if (cmd === "main" || cmd === "master" || cmd === "provision" || cmd === "team")
865
865
  });
866
866
  };
867
867
 
868
- if (cmd === "main" || cmd === "master") {
868
+ if (cmd === "main") {
869
869
  const sub = process.argv[3];
870
870
  if (sub === "connect") {
871
871
  const harness = flagVal("harness", flagVal("tool", ""));
@@ -902,14 +902,7 @@ if (cmd === "main" || cmd === "master" || cmd === "provision" || cmd === "team")
902
902
  console.log(`\n ${M.green}✓${M.rst} Main token saved: ${M.dim}${MAIN_CONFIG}${M.rst}`);
903
903
  process.exit(0);
904
904
  }
905
- if (sub === "whoami") {
906
- const m = requireMain();
907
- const { status, json } = await _httpJSON("GET", `${m.baseUrl}/api/main/whoami`, m.token);
908
- if (status !== "200" || !json) { console.error(`main whoami failed (HTTP ${status})`); process.exit(1); }
909
- console.log(`main · user ${json.user_id} · ${json.agents}/${json.quota} agents`);
910
- process.exit(0);
911
- }
912
- console.error("Usage: patchcord main <connect|whoami>");
905
+ console.error("Usage: patchcord main connect");
913
906
  process.exit(1);
914
907
  }
915
908
 
@@ -993,9 +986,20 @@ if (cmd === "main" || cmd === "master" || cmd === "provision" || cmd === "team")
993
986
  const m = requireMain();
994
987
  const { status, json } = await _httpJSON("GET", `${m.baseUrl}/api/provision/list`, m.token);
995
988
  if (status !== "200") { console.error(`list failed (HTTP ${status})`); process.exit(1); }
996
- const agents = json?.agents || [];
997
- for (const a of agents) console.log(` ${a.namespace_id}:${a.agent_id} ${M.dim}${a.label || ""}${M.rst}`);
998
- if (!agents.length) console.log(" (no agents)");
989
+ // Dedup by namespace:agent the server returns one row per token, so a
990
+ // re-provisioned agent shows up many times. Optional --namespace filter.
991
+ const filterNs = flagVal("namespace");
992
+ const seen = new Set();
993
+ let n = 0;
994
+ for (const a of (json?.agents || [])) {
995
+ if (filterNs && a.namespace_id !== filterNs) continue;
996
+ const key = `${a.namespace_id}:${a.agent_id}`;
997
+ if (seen.has(key)) continue;
998
+ seen.add(key);
999
+ console.log(` ${key} ${M.dim}${a.label || ""}${M.rst}`);
1000
+ n++;
1001
+ }
1002
+ if (!n) console.log(" (no agents)");
999
1003
  process.exit(0);
1000
1004
  }
1001
1005
  if (sub === "status") {
@@ -1134,6 +1138,73 @@ if (cmd === "main" || cmd === "master" || cmd === "provision" || cmd === "team")
1134
1138
  console.error("Usage: patchcord team <init|list|launch|status>");
1135
1139
  process.exit(1);
1136
1140
  }
1141
+
1142
+ if (cmd === "schedule") {
1143
+ // User-level scheduled / recurring messages (server Plan 041). Authed by
1144
+ // the main token — the user can manage schedules in any namespace they own.
1145
+ const m = requireMain();
1146
+ const sub = process.argv[3];
1147
+ const BASE = `${m.baseUrl}/api/dashboard/scheduled`;
1148
+ 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;
1149
+ 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}`;
1150
+
1151
+ if (sub === "list") {
1152
+ const ns = flagVal("namespace");
1153
+ const url = ns ? `${BASE}?namespace=${encodeURIComponent(ns)}` : BASE;
1154
+ const { status, json } = await _httpJSON("GET", url, m.token);
1155
+ if (status !== "200") { console.error(`list failed (HTTP ${status}): ${json?.error || ""}`); process.exit(1); }
1156
+ const items = json?.schedules || [];
1157
+ for (const s of items) console.log(" " + fmt(s));
1158
+ if (!items.length) console.log(" (no schedules)");
1159
+ process.exit(0);
1160
+ }
1161
+ if (sub === "create") {
1162
+ const name = process.argv[4];
1163
+ if (!name || name.startsWith("-")) { console.error('Usage: patchcord schedule create <name> --namespace <ns> --to <agent> --content "..." (--at <ISO> | --cron "<expr>" | --every <sec>) [--timezone <tz>] [--thread <slug>] [--max-runs N] [--expires <ISO>]'); process.exit(1); }
1164
+ const ns = flagVal("namespace"), to = flagVal("to"), content = flagVal("content");
1165
+ if (!ns || !to || !content) { console.error("--namespace, --to, and --content are required"); process.exit(1); }
1166
+ const at = flagVal("at"), cron = flagVal("cron"), every = flagVal("every");
1167
+ let schedule_kind, extra = {};
1168
+ if (cron) { schedule_kind = "cron"; extra.cron_expr = cron; }
1169
+ else if (every) { schedule_kind = "interval"; extra.interval_sec = parseInt(every, 10); }
1170
+ else if (at) { schedule_kind = "once"; extra.fire_at = at; }
1171
+ else { console.error('one of --at <ISO>, --cron "<expr>", or --every <seconds> is required'); process.exit(1); }
1172
+ const body = { namespace: ns, name, to_agent: to, content, schedule_kind, timezone: flagVal("timezone", "UTC"), ...extra };
1173
+ const thread = flagVal("thread"); if (thread) body.thread_slug = thread;
1174
+ const maxRuns = flagVal("max-runs"); if (maxRuns) body.max_runs = parseInt(maxRuns, 10);
1175
+ const expires = flagVal("expires"); if (expires) body.expires_at = expires;
1176
+ const { status, json } = await _httpJSON("POST", BASE, m.token, body);
1177
+ if (status !== "201" && status !== "200") { console.error(`create failed (HTTP ${status}): ${json?.error || ""}`); process.exit(1); }
1178
+ console.log(`✓ scheduled ${M.green}${ns}:${to}${M.rst} [${schedule_kind}] ${M.dim}${json?.schedule?.id || ""}${M.rst}`);
1179
+ process.exit(0);
1180
+ }
1181
+ if (sub === "cancel" || sub === "delete" || sub === "rm") {
1182
+ const id = process.argv[4];
1183
+ if (!id) { console.error("Usage: patchcord schedule cancel <id>"); process.exit(1); }
1184
+ const { status, json } = await _httpJSON("DELETE", `${BASE}/${id}`, m.token);
1185
+ if (status !== "200") { console.error(`cancel failed (HTTP ${status}): ${json?.error || ""}`); process.exit(1); }
1186
+ console.log(`✓ cancelled ${id}`);
1187
+ process.exit(0);
1188
+ }
1189
+ if (sub === "test" || sub === "fire") {
1190
+ const id = process.argv[4];
1191
+ if (!id) { console.error("Usage: patchcord schedule test <id>"); process.exit(1); }
1192
+ const { status, json } = await _httpJSON("POST", `${BASE}/${id}/test`, m.token, {});
1193
+ if (status !== "200") { console.error(`test failed (HTTP ${status}): ${json?.error || ""}`); process.exit(1); }
1194
+ console.log(`✓ fired ${id} once (does not consume max-runs)`);
1195
+ process.exit(0);
1196
+ }
1197
+ if (sub === "pause" || sub === "resume") {
1198
+ const id = process.argv[4];
1199
+ if (!id) { console.error(`Usage: patchcord schedule ${sub} <id>`); process.exit(1); }
1200
+ const { status, json } = await _httpJSON("PATCH", `${BASE}/${id}`, m.token, { active: sub === "resume" });
1201
+ if (status !== "200") { console.error(`${sub} failed (HTTP ${status}): ${json?.error || ""}`); process.exit(1); }
1202
+ console.log(`✓ ${sub === "resume" ? "resumed" : "paused"} ${id}`);
1203
+ process.exit(0);
1204
+ }
1205
+ console.error("Usage: patchcord schedule <create|list|cancel|test|pause|resume>");
1206
+ process.exit(1);
1207
+ }
1137
1208
  }
1138
1209
 
1139
1210
  if (cmd === "update" || cmd === "--update") {
@@ -2701,38 +2772,6 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
2701
2772
  }
2702
2773
 
2703
2774
  // ── channel: spawn the channel MCP server (used by .mcp.json) ──
2704
- if (cmd === "channel") {
2705
- const channelScript = join(pluginRoot, "channel", "server.ts");
2706
- if (!existsSync(channelScript)) {
2707
- console.error("Channel server not found. Reinstall patchcord.");
2708
- process.exit(1);
2709
- }
2710
- // Prefer bun, fall back to node (tsx)
2711
- const hasBun = run("which bun");
2712
- if (hasBun) {
2713
- const { spawnSync } = await import("child_process");
2714
- // Install deps if needed
2715
- const channelDir = join(pluginRoot, "channel");
2716
- if (!existsSync(join(channelDir, "node_modules"))) {
2717
- spawnSync("bun", ["install", "--no-summary"], { cwd: channelDir, stdio: "inherit" });
2718
- }
2719
- const result = spawnSync("bun", ["run", channelScript], { stdio: "inherit", env: process.env });
2720
- process.exit(result.status ?? 1);
2721
- } else {
2722
- console.error("Channel plugin requires bun. Install from https://bun.sh");
2723
- process.exit(1);
2724
- }
2725
- }
2726
-
2727
- // ── back-compat: init → install + agent ───────────────────────
2728
- if (cmd === "init") {
2729
- console.log(`"patchcord init" is now two commands:
2730
-
2731
- patchcord install One-time global setup (once)
2732
- patchcord agent Set up MCP for this project (per project)`);
2733
- process.exit(0);
2734
- }
2735
-
2736
2775
  // ── skill: custom skill from web console ─────────────────────
2737
2776
  if (cmd === "skill") {
2738
2777
  const sub = process.argv[3];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "patchcord",
3
- "version": "0.5.104",
3
+ "version": "0.5.106",
4
4
  "description": "Cross-machine agent messaging for Claude Code and Codex",
5
5
  "author": "ppravdin",
6
6
  "license": "MIT",