@proagentstore/cli 0.2.9 → 0.2.11

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/dist/index.js +76 -38
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -7,7 +7,7 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
7
7
  });
8
8
 
9
9
  // src/index.ts
10
- import { createRequire as createRequire2 } from "module";
10
+ import { createRequire as createRequire3 } from "module";
11
11
  import { Command as Command8 } from "commander";
12
12
 
13
13
  // src/commands/check.ts
@@ -620,7 +620,15 @@ import { arch, homedir as homedir2, hostname, platform } from "os";
620
620
  import { resolve as resolve4 } from "path";
621
621
  import { createServer as createServer2 } from "net";
622
622
  import { fileURLToPath as fileURLToPath2 } from "url";
623
+ import { createRequire } from "module";
623
624
  import { Command as Command6 } from "commander";
625
+ var CLI_VERSION = (() => {
626
+ try {
627
+ return createRequire(import.meta.url)("../package.json").version;
628
+ } catch {
629
+ return "";
630
+ }
631
+ })();
624
632
  async function findFreePort2(start) {
625
633
  for (let p = start; p < start + 25; p++) {
626
634
  const free = await new Promise((res) => {
@@ -906,52 +914,61 @@ function responseErrorMessage(data, text, statusText) {
906
914
  function collectCapability(value, previous = []) {
907
915
  return [...previous, value];
908
916
  }
909
- async function connectViaRelay(instanceIds, localUrl, runnerToken, opts) {
917
+ async function connectViaRelay(instanceIds, localUrl, runnerToken, opts, force = false) {
910
918
  const apiBase = pagsApiBase(opts.apiBase).replace(/^http/, "ws");
911
919
  const pagsToken = clean(opts.pagsToken) || clean(process.env.PAGS_TOKEN) || clean(loadSession()?.token);
912
920
  if (!pagsToken) throw new Error("PAGS token required for WebSocket relay");
913
921
  const capabilities = await requestRunner("GET", "/capabilities", { url: localUrl, token: runnerToken, instanceId: instanceIds[0] });
914
922
  const caps = Array.isArray(capabilities.capabilities) ? capabilities.capabilities.filter((item) => typeof item === "string") : [];
915
923
  for (const id of instanceIds) {
916
- await requestPags("POST", `/v1/instances/${apiPathSegment(id)}/runtime`, opts, {
917
- endpointUrl: localUrl,
918
- token: runnerToken,
919
- placement: "local",
920
- capabilities: caps,
921
- runnerVersion: "",
922
- runnerNode: hostname()
923
- }).catch((e) => writeError(`register ${id.slice(0, 8)}\u2026 failed: ${e instanceof Error ? e.message : String(e)}`));
924
+ try {
925
+ await requestPags("POST", `/v1/instances/${apiPathSegment(id)}/runtime`, opts, {
926
+ endpointUrl: localUrl,
927
+ token: runnerToken,
928
+ placement: "local",
929
+ capabilities: caps,
930
+ runnerVersion: CLI_VERSION,
931
+ runnerNode: hostname(),
932
+ force
933
+ });
934
+ } catch (e) {
935
+ const msg = e instanceof Error ? e.message : String(e);
936
+ if (msg.includes("409") && msg.includes("Another machine")) {
937
+ writeError(msg);
938
+ writeLine("");
939
+ writeLine(" To take over, run: pags up --tunnel ws --force");
940
+ writeLine("");
941
+ throw new Error("Another machine is already connected");
942
+ }
943
+ writeError(`register ${id.slice(0, 8)}\u2026 failed: ${msg}`);
944
+ }
924
945
  }
925
946
  for (const id of instanceIds) {
926
- openRelaySocket(id, apiBase, pagsToken, localUrl, runnerToken);
947
+ openRelaySocket(id, apiBase, pagsToken, localUrl, runnerToken, force);
927
948
  }
928
949
  writeLine("Runtime registered with PAGS \u2713");
929
950
  writeLine("");
930
951
  writeLine("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550");
931
- writeLine(" \u2705 CONNECTED \u2014 WebSocket relay (no tunnel)");
952
+ writeLine(` \u2705 CONNECTED \u2014 WebSocket relay \xB7 ${hostname()}`);
932
953
  writeLine(` Agents: ${instanceIds.length} instance${instanceIds.length === 1 ? "" : "s"}`);
933
954
  writeLine(" No cloudflared needed. Ctrl+C to disconnect.");
934
955
  writeLine("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550");
935
- let stopped = false;
936
- const heartbeatLoop = async () => {
937
- while (!stopped) {
938
- await new Promise((r) => setTimeout(r, 3e4));
939
- if (stopped) break;
956
+ const heartbeat = () => {
957
+ const timer = setTimeout(async () => {
940
958
  for (const id of instanceIds) {
941
959
  await requestPags("POST", `/v1/instances/${apiPathSegment(id)}/runtime/heartbeat`, opts, {}).catch(() => void 0);
942
960
  }
943
- }
961
+ heartbeat();
962
+ }, 3e4);
963
+ timer.unref();
944
964
  };
945
- void heartbeatLoop();
946
- process.once("beforeExit", () => {
947
- stopped = true;
948
- });
965
+ heartbeat();
949
966
  }
950
- function openRelaySocket(instanceId, wsBase, pagsToken, localUrl, runnerToken) {
967
+ function openRelaySocket(instanceId, wsBase, pagsToken, localUrl, runnerToken, force = false) {
951
968
  let backoffMs = 1e3;
952
969
  let reconnecting = false;
953
970
  const connect = () => {
954
- const url = `${wsBase}/v1/relay/${encodeURIComponent(instanceId)}/connect?token=${encodeURIComponent(pagsToken)}`;
971
+ const url = `${wsBase}/v1/relay/${encodeURIComponent(instanceId)}/connect?token=${encodeURIComponent(pagsToken)}${force ? "&force=1" : ""}`;
955
972
  const ws = new WebSocket(url);
956
973
  ws.onopen = () => {
957
974
  backoffMs = 1e3;
@@ -959,6 +976,13 @@ function openRelaySocket(instanceId, wsBase, pagsToken, localUrl, runnerToken) {
959
976
  };
960
977
  ws.onmessage = async (event) => {
961
978
  const text = typeof event.data === "string" ? event.data : String(event.data);
979
+ if (text === "ping") {
980
+ try {
981
+ ws.send("pong");
982
+ } catch {
983
+ }
984
+ return;
985
+ }
962
986
  let cmd;
963
987
  try {
964
988
  cmd = JSON.parse(text);
@@ -978,16 +1002,29 @@ function openRelaySocket(instanceId, wsBase, pagsToken, localUrl, runnerToken) {
978
1002
  headers,
979
1003
  body: hasBody ? JSON.stringify(cmd.body) : void 0
980
1004
  });
981
- const result = await res.json().catch(() => ({}));
982
- ws.send(JSON.stringify({ id: cmd.id, status: res.status, result }));
1005
+ const text2 = await res.text().catch(() => "");
1006
+ let result;
1007
+ try {
1008
+ result = text2 ? JSON.parse(text2) : {};
1009
+ } catch {
1010
+ result = { raw: text2.slice(0, 500) };
1011
+ }
1012
+ try {
1013
+ ws.send(JSON.stringify({ id: cmd.id, status: res.status, result }));
1014
+ } catch {
1015
+ }
983
1016
  } catch (err) {
984
- ws.send(JSON.stringify({ id: cmd.id, status: 500, error: err instanceof Error ? err.message : String(err) }));
1017
+ try {
1018
+ ws.send(JSON.stringify({ id: cmd.id, status: 500, error: err instanceof Error ? err.message : String(err) }));
1019
+ } catch {
1020
+ }
985
1021
  }
986
1022
  };
987
- ws.onclose = () => {
1023
+ ws.onclose = (ev) => {
988
1024
  if (reconnecting) return;
989
1025
  reconnecting = true;
990
- writeLine(`Relay disconnected: ${instanceId.slice(0, 8)}\u2026 \u2014 reconnecting in ${Math.round(backoffMs / 1e3)}s`);
1026
+ const reason = ev.code === 4401 || ev.code === 1008 ? " (token expired \u2014 run `pags login` then `pags up`)" : "";
1027
+ writeLine(`Relay disconnected: ${instanceId.slice(0, 8)}\u2026${reason} \u2014 reconnecting in ${Math.round(backoffMs / 1e3)}s`);
991
1028
  setTimeout(() => {
992
1029
  reconnecting = false;
993
1030
  connect();
@@ -1006,7 +1043,7 @@ function createRunnerCommand() {
1006
1043
  command.command("start").description("Start the local FAGS browser runtime in the foreground").option("--host <host>", "Host to bind", "127.0.0.1").option("--port <port>", "Port to bind", "49171").option("--data-dir <path>", "Runner data directory").option("--token <token>", "Require this bearer token").option("--instance-id <id>", "Bind runner requests to a PAGS instance id").option("--headless", "Run Playwright headless").action(async (opts) => {
1007
1044
  await startRunnerForeground(opts);
1008
1045
  });
1009
- command.command("connect <instanceIds...>").description("Start ONE local runtime, open a Cloudflare quick tunnel, and register it for every given PAGS instance").option("--host <host>", "Host to bind", "127.0.0.1").option("--port <port>", "Port to bind", "49171").option("--data-dir <path>", "Runner data directory").option("--token <token>", "Runner bearer token. Defaults to PAGS_RUNNER_TOKEN or a generated token").option("--headless", "Run Playwright headless").option("--api-base <url>", "PAGS API base URL").option("--pags-token <token>", "PAGS session token. Defaults to PAGS_TOKEN").option("--cloudflared <path>", "cloudflared executable").option("--runner-version <version>", "Runner version").option("--skip-probe", "Skip PAGS FAGS-runtime probe after registration").option("--tunnel <mode>", "Tunnel mode: 'ws' (WebSocket relay), 'named' (production), or 'quick' (default)", "quick").action(async (instanceIds, opts) => {
1046
+ command.command("connect <instanceIds...>").description("Start ONE local runtime, open a Cloudflare quick tunnel, and register it for every given PAGS instance").option("--host <host>", "Host to bind", "127.0.0.1").option("--port <port>", "Port to bind", "49171").option("--data-dir <path>", "Runner data directory").option("--token <token>", "Runner bearer token. Defaults to PAGS_RUNNER_TOKEN or a generated token").option("--headless", "Run Playwright headless").option("--api-base <url>", "PAGS API base URL").option("--pags-token <token>", "PAGS session token. Defaults to PAGS_TOKEN").option("--cloudflared <path>", "cloudflared executable").option("--runner-version <version>", "Runner version").option("--skip-probe", "Skip PAGS FAGS-runtime probe after registration").option("--tunnel <mode>", "Tunnel mode: 'ws' (WebSocket relay, default), 'named' (stable hostname), or 'quick' (cloudflared)", "ws").option("--force", "Take over from another connected machine").action(async (instanceIds, opts) => {
1010
1047
  const runnerToken = clean(opts.token) || clean(process.env.PAGS_RUNNER_TOKEN) || `pags_runner_${randomUUID()}`;
1011
1048
  const host = clean(opts.host) || "127.0.0.1";
1012
1049
  const port = clean(opts.port) || String(await findFreePort2(49171));
@@ -1046,7 +1083,7 @@ function createRunnerCommand() {
1046
1083
  await waitForLocalRunner({ url: localUrl, token: runnerToken, instanceId: primary });
1047
1084
  writeLine(`Local FAGS runtime healthy at ${localUrl}`);
1048
1085
  if (opts.tunnel === "ws") {
1049
- await connectViaRelay(instanceIds, localUrl, runnerToken, opts);
1086
+ await connectViaRelay(instanceIds, localUrl, runnerToken, opts, Boolean(opts.force));
1050
1087
  await new Promise((resolvePromise) => {
1051
1088
  runner.on("exit", () => resolvePromise());
1052
1089
  });
@@ -1347,7 +1384,7 @@ function createRunnerCommand() {
1347
1384
  }
1348
1385
 
1349
1386
  // src/commands/up.ts
1350
- import { createRequire } from "module";
1387
+ import { createRequire as createRequire2 } from "module";
1351
1388
  import { Command as Command7 } from "commander";
1352
1389
 
1353
1390
  // src/tui.ts
@@ -1454,7 +1491,7 @@ async function waitForKey(keys) {
1454
1491
 
1455
1492
  // src/commands/up.ts
1456
1493
  var API_BASE2 = "https://api.proagentstore.online";
1457
- var CLI_VERSION = createRequire(import.meta.url)("../package.json").version;
1494
+ var CLI_VERSION2 = createRequire2(import.meta.url)("../package.json").version;
1458
1495
  async function stopRunnerProcesses() {
1459
1496
  if (process.platform === "win32") return false;
1460
1497
  const { execSync } = await import("child_process");
@@ -1474,7 +1511,7 @@ async function stopRunnerProcesses() {
1474
1511
  }
1475
1512
  return stopped;
1476
1513
  }
1477
- var upCommand = new Command7("up").description("Start the browser runner for all your agent instances").option("--headless", "Run browser in headless mode").option("--instance <id>", "Connect to a specific instance only").option("--tunnel <mode>", "Tunnel mode: 'ws' (WebSocket relay, recommended), 'named' (production, stable), or 'quick' (default, trycloudflare.com)", "quick").action(async (opts) => {
1514
+ var upCommand = new Command7("up").description("Start the browser runner for all your agent instances").option("--headless", "Run browser in headless mode").option("--instance <id>", "Connect to a specific instance only").option("--tunnel <mode>", "Tunnel mode: 'ws' (WebSocket relay, default), 'named' (stable hostname), or 'quick' (cloudflared)", "ws").option("--force", "Take over from another connected machine").action(async (opts) => {
1478
1515
  const session = requireSession();
1479
1516
  const state = {
1480
1517
  user: session.user.login,
@@ -1486,10 +1523,10 @@ var upCommand = new Command7("up").description("Start the browser runner for all
1486
1523
  registration: "pending",
1487
1524
  lastEvent: "Fetching instances...",
1488
1525
  taskCount: 0,
1489
- version: CLI_VERSION
1526
+ version: CLI_VERSION2
1490
1527
  };
1491
1528
  clearScreen();
1492
- printLogo(CLI_VERSION);
1529
+ printLogo(CLI_VERSION2);
1493
1530
  printStep("Signed in as " + session.user.login, "ok");
1494
1531
  printStep("Fetching instances...", "wait");
1495
1532
  const res = await fetch(`${API_BASE2}/v1/instances/my/instances`, {
@@ -1523,6 +1560,7 @@ var upCommand = new Command7("up").description("Start the browser runner for all
1523
1560
  if (opts.headless) args.push("--headless");
1524
1561
  if (opts.tunnel === "named") args.push("--tunnel", "named");
1525
1562
  if (opts.tunnel === "ws") args.push("--tunnel", "ws");
1563
+ if (opts.force) args.push("--force");
1526
1564
  const child = spawn3(process.execPath, args, {
1527
1565
  stdio: ["ignore", "pipe", "pipe"],
1528
1566
  env: { ...process.env, PAGS_TOKEN: session.token }
@@ -1646,7 +1684,7 @@ var upCommand = new Command7("up").description("Start the browser runner for all
1646
1684
  });
1647
1685
  var downCommand = new Command7("down").description("Stop the browser runner and disconnect (cleans up any stuck processes)").action(async () => {
1648
1686
  clearScreen();
1649
- printLogo(CLI_VERSION);
1687
+ printLogo(CLI_VERSION2);
1650
1688
  if (process.platform === "win32") {
1651
1689
  writeLine(" On Windows: switch to the 'pags up' window and press Ctrl+C to disconnect.");
1652
1690
  writeLine("");
@@ -1664,7 +1702,7 @@ var downCommand = new Command7("down").description("Stop the browser runner and
1664
1702
  });
1665
1703
 
1666
1704
  // src/index.ts
1667
- var require2 = createRequire2(import.meta.url);
1705
+ var require2 = createRequire3(import.meta.url);
1668
1706
  var { version } = require2("../package.json");
1669
1707
  var program = new Command8();
1670
1708
  program.name("pags").description(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@proagentstore/cli",
3
- "version": "0.2.9",
3
+ "version": "0.2.11",
4
4
  "description": "CLI for creating, publishing, and running ProAgentStore agents",
5
5
  "license": "MIT",
6
6
  "type": "module",