@proagentstore/cli 0.2.11 → 0.3.0

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 +17 -336
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -614,9 +614,7 @@ var publishCommand = new Command5("publish").description("Publish an agent to Pr
614
614
  import { spawn as spawn2 } from "child_process";
615
615
  import { randomUUID } from "crypto";
616
616
  import { existsSync as existsSync5 } from "fs";
617
- import { chmod, mkdir, writeFile } from "fs/promises";
618
- import { gunzipSync } from "zlib";
619
- import { arch, homedir as homedir2, hostname, platform } from "os";
617
+ import { hostname } from "os";
620
618
  import { resolve as resolve4 } from "path";
621
619
  import { createServer as createServer2 } from "net";
622
620
  import { fileURLToPath as fileURLToPath2 } from "url";
@@ -672,122 +670,6 @@ function buildRunnerArgs(opts) {
672
670
  if (opts.headless) args.push("--headless");
673
671
  return args;
674
672
  }
675
- function buildCloudflaredArgs(localUrl) {
676
- return ["tunnel", "--url", localUrl];
677
- }
678
- function cloudflaredAssetForPlatform(os = platform(), cpu = arch()) {
679
- if (os === "darwin") {
680
- if (cpu === "arm64") {
681
- return {
682
- asset: "cloudflared-darwin-arm64.tgz",
683
- executableName: "cloudflared",
684
- archive: true
685
- };
686
- }
687
- if (cpu === "x64") {
688
- return {
689
- asset: "cloudflared-darwin-amd64.tgz",
690
- executableName: "cloudflared",
691
- archive: true
692
- };
693
- }
694
- }
695
- if (os === "linux") {
696
- if (cpu === "x64") {
697
- return {
698
- asset: "cloudflared-linux-amd64",
699
- executableName: "cloudflared",
700
- archive: false
701
- };
702
- }
703
- if (cpu === "arm64") {
704
- return {
705
- asset: "cloudflared-linux-arm64",
706
- executableName: "cloudflared",
707
- archive: false
708
- };
709
- }
710
- }
711
- if (os === "win32" && cpu === "x64") {
712
- return {
713
- asset: "cloudflared-windows-amd64.exe",
714
- executableName: "cloudflared.exe",
715
- archive: false
716
- };
717
- }
718
- throw new Error(
719
- `Automatic cloudflared download is not supported on ${os}/${cpu}. Install cloudflared and pass --cloudflared <path>.`
720
- );
721
- }
722
- function cloudflaredDownloadUrl(asset) {
723
- return `https://github.com/cloudflare/cloudflared/releases/latest/download/${asset}`;
724
- }
725
- function extractCloudflaredBinary(asset, bytes) {
726
- const buffer = Buffer.from(bytes);
727
- if (!asset.archive) return buffer;
728
- const tar = gunzipSync(buffer);
729
- for (let offset = 0; offset + 512 <= tar.length; ) {
730
- const header = tar.subarray(offset, offset + 512);
731
- if (header.every((byte) => byte === 0)) break;
732
- const rawName = header.subarray(0, 100).toString("utf8");
733
- const name = rawName.slice(0, rawName.indexOf("\0") === -1 ? void 0 : rawName.indexOf("\0"));
734
- const rawSize = header.subarray(124, 136).toString("utf8").replace(/\0.*$/, "").trim();
735
- const size = Number.parseInt(rawSize || "0", 8);
736
- const contentStart = offset + 512;
737
- const contentEnd = contentStart + size;
738
- if (name.split("/").pop() === asset.executableName) {
739
- return tar.subarray(contentStart, contentEnd);
740
- }
741
- offset = contentStart + Math.ceil(size / 512) * 512;
742
- }
743
- throw new Error(`Downloaded ${asset.asset} did not contain ${asset.executableName}`);
744
- }
745
- function cloudflaredCacheDir() {
746
- return resolve4(homedir2(), ".config", "proagentstore", "bin");
747
- }
748
- function cachedCloudflaredPath(asset) {
749
- const suffix = asset.asset.replace(/[^a-zA-Z0-9._-]/g, "-");
750
- return resolve4(cloudflaredCacheDir(), `${asset.executableName}-${suffix}`);
751
- }
752
- async function commandAvailable(command) {
753
- return new Promise((resolvePromise) => {
754
- const child = spawn2(command, ["--version"], {
755
- stdio: "ignore",
756
- shell: process.platform === "win32"
757
- });
758
- child.on("error", () => resolvePromise(false));
759
- child.on("exit", (code) => resolvePromise(code === 0));
760
- });
761
- }
762
- async function downloadCloudflared(asset) {
763
- const target = cachedCloudflaredPath(asset);
764
- if (existsSync5(target)) return target;
765
- await mkdir(cloudflaredCacheDir(), { recursive: true });
766
- const url = cloudflaredDownloadUrl(asset.asset);
767
- writeLine(`cloudflared not found; downloading ${asset.asset}...`);
768
- const res = await fetch(url);
769
- if (!res.ok) {
770
- throw new Error(`cloudflared download failed: ${res.status} ${res.statusText}`);
771
- }
772
- const binary = extractCloudflaredBinary(asset, new Uint8Array(await res.arrayBuffer()));
773
- await writeFile(target, binary);
774
- if (process.platform !== "win32") await chmod(target, 493);
775
- writeLine(`cloudflared cached at ${target}`);
776
- return target;
777
- }
778
- async function resolveCloudflaredCommand(requested) {
779
- const explicit = clean(requested);
780
- if (explicit && explicit !== "cloudflared") return explicit;
781
- if (await commandAvailable("cloudflared")) return "cloudflared";
782
- return downloadCloudflared(cloudflaredAssetForPlatform());
783
- }
784
- function parseCloudflaredTunnelUrl(text) {
785
- const matches = text.match(/https:\/\/[a-z0-9-]+\.trycloudflare\.com/gi) || [];
786
- for (const m of matches) {
787
- if (!/^https:\/\/api\.trycloudflare\.com/i.test(m)) return m;
788
- }
789
- return null;
790
- }
791
673
  function buildRuntimeRegistrationBody(opts, capabilities = []) {
792
674
  return {
793
675
  endpointUrl: clean(opts.endpointUrl) || opts.endpointUrl,
@@ -1043,18 +925,13 @@ function createRunnerCommand() {
1043
925
  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) => {
1044
926
  await startRunnerForeground(opts);
1045
927
  });
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) => {
928
+ command.command("connect <instanceIds...>").description("Start ONE local runtime, connect via WebSocket relay, 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("--runner-version <version>", "Runner version").option("--force", "Take over from another connected machine").action(async (instanceIds, opts) => {
1047
929
  const runnerToken = clean(opts.token) || clean(process.env.PAGS_RUNNER_TOKEN) || `pags_runner_${randomUUID()}`;
1048
930
  const host = clean(opts.host) || "127.0.0.1";
1049
931
  const port = clean(opts.port) || String(await findFreePort2(49171));
1050
932
  const localUrl = `http://${host}:${port}`;
1051
933
  const primary = instanceIds[0];
1052
- const runnerOpts = {
1053
- ...opts,
1054
- host,
1055
- port,
1056
- token: runnerToken
1057
- };
934
+ const runnerOpts = { ...opts, host, port, token: runnerToken };
1058
935
  const spec = runnerSpawnSpec(runnerOpts);
1059
936
  const runner = spawn2(spec.command, spec.args, {
1060
937
  cwd: spec.cwd,
@@ -1066,9 +943,7 @@ function createRunnerCommand() {
1066
943
  runner.on("exit", (code) => {
1067
944
  if (code && code !== 0) writeError(`runner exited with code ${code}`);
1068
945
  });
1069
- let tunnel = null;
1070
946
  const shutdown = () => {
1071
- if (tunnel && !tunnel.killed) tunnel.kill("SIGTERM");
1072
947
  if (!runner.killed) runner.kill("SIGTERM");
1073
948
  };
1074
949
  process.once("SIGINT", () => {
@@ -1082,193 +957,10 @@ function createRunnerCommand() {
1082
957
  try {
1083
958
  await waitForLocalRunner({ url: localUrl, token: runnerToken, instanceId: primary });
1084
959
  writeLine(`Local FAGS runtime healthy at ${localUrl}`);
1085
- if (opts.tunnel === "ws") {
1086
- await connectViaRelay(instanceIds, localUrl, runnerToken, opts, Boolean(opts.force));
1087
- await new Promise((resolvePromise) => {
1088
- runner.on("exit", () => resolvePromise());
1089
- });
1090
- return;
1091
- }
1092
- if (opts.tunnel === "named") {
1093
- writeLine("Provisioning named tunnel\u2026");
1094
- const provRes = await requestPags("POST", "/v1/tunnel/provision", opts, {});
1095
- const prov = provRes;
1096
- if (!prov.connectorToken) {
1097
- writeError(`Named tunnel provisioning failed: ${prov.error || "no token returned"}. Falling back to quick tunnel.`);
1098
- } else {
1099
- const tunnelUrl2 = prov.endpointUrl || `https://${prov.hostname}`;
1100
- writeLine(`Named tunnel: ${tunnelUrl2}`);
1101
- const cloudflaredPath2 = await resolveCloudflaredCommand(opts.cloudflared);
1102
- const tunnelProc = spawn2(cloudflaredPath2, ["tunnel", "run", "--token", prov.connectorToken], {
1103
- stdio: ["ignore", "pipe", "pipe"],
1104
- shell: process.platform === "win32"
1105
- });
1106
- tunnel = tunnelProc;
1107
- tunnelProc.stdout?.on("data", (d2) => {
1108
- const t = d2.toString("utf-8");
1109
- if (/registered/.test(t) || /CONNECTED/.test(t)) writeLine("Named tunnel connected \u2713");
1110
- });
1111
- tunnelProc.stderr?.on("data", (d2) => {
1112
- const t = d2.toString("utf-8");
1113
- if (/registered/.test(t) || /CONNECTED/.test(t)) writeLine("Named tunnel connected \u2713");
1114
- });
1115
- await new Promise((r) => setTimeout(r, 4e3));
1116
- const capabilities = await requestRunner("GET", "/capabilities", { url: localUrl, token: runnerToken, instanceId: primary });
1117
- const caps = Array.isArray(capabilities.capabilities) ? capabilities.capabilities.filter((item) => typeof item === "string") : [];
1118
- for (const id of instanceIds) {
1119
- try {
1120
- await requestPags("POST", `/v1/instances/${apiPathSegment(id)}/runtime`, opts, {
1121
- endpointUrl: tunnelUrl2,
1122
- token: runnerToken,
1123
- placement: "local",
1124
- capabilities: caps,
1125
- runnerVersion: clean(opts.runnerVersion) || "",
1126
- runnerNode: hostname()
1127
- });
1128
- } catch (error) {
1129
- writeError(`register ${id.slice(0, 8)}\u2026 failed (${error instanceof Error ? error.message : String(error)})`);
1130
- }
1131
- }
1132
- writeLine("Runtime registered with PAGS \u2713");
1133
- writeLine("");
1134
- 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");
1135
- writeLine(" \u2705 CONNECTED \u2014 named tunnel (production)");
1136
- writeLine(` Agents: ${instanceIds.length} instance${instanceIds.length === 1 ? "" : "s"}`);
1137
- writeLine(` Endpoint: ${tunnelUrl2}`);
1138
- writeLine(" Stable URL \u2014 no reconnect churn. Ctrl+C to disconnect.");
1139
- 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");
1140
- let stopped2 = false;
1141
- void (async () => {
1142
- while (!stopped2) {
1143
- await new Promise((r) => setTimeout(r, 3e4));
1144
- if (stopped2) break;
1145
- for (const id of instanceIds) {
1146
- await requestPags("POST", `/v1/instances/${apiPathSegment(id)}/runtime/heartbeat`, opts, {}).catch(() => void 0);
1147
- }
1148
- }
1149
- })();
1150
- await new Promise((resolvePromise) => {
1151
- runner.on("exit", () => resolvePromise());
1152
- });
1153
- stopped2 = true;
1154
- return;
1155
- }
1156
- }
1157
- const cloudflaredPath = await resolveCloudflaredCommand(opts.cloudflared);
1158
- const openTunnel = () => new Promise((resolveTunnel, reject) => {
1159
- let output = "";
1160
- const proc = spawn2(cloudflaredPath, buildCloudflaredArgs(localUrl), {
1161
- stdio: ["ignore", "pipe", "pipe"],
1162
- shell: process.platform === "win32"
1163
- });
1164
- const onData = (data) => {
1165
- output += data.toString("utf-8");
1166
- const parsed = parseCloudflaredTunnelUrl(output);
1167
- if (parsed) resolveTunnel({ url: parsed, proc });
1168
- };
1169
- proc.stdout?.on("data", onData);
1170
- proc.stderr?.on("data", onData);
1171
- proc.on("error", reject);
1172
- setTimeout(() => reject(new Error("timed out waiting for cloudflared tunnel URL")), 45e3).unref();
1173
- });
1174
- const registerRuntime = async (url) => {
1175
- const capabilities = await requestRunner("GET", "/capabilities", { url: localUrl, token: runnerToken, instanceId: primary });
1176
- const caps = Array.isArray(capabilities.capabilities) ? capabilities.capabilities.filter((item) => typeof item === "string") : [];
1177
- for (const id of instanceIds) {
1178
- try {
1179
- await requestPags("POST", `/v1/instances/${apiPathSegment(id)}/runtime`, opts, {
1180
- endpointUrl: url,
1181
- token: runnerToken,
1182
- placement: "local",
1183
- capabilities: caps,
1184
- runnerVersion: clean(opts.runnerVersion) || "",
1185
- runnerNode: hostname()
1186
- });
1187
- } catch (error) {
1188
- writeError(`register ${id.slice(0, 8)}\u2026 failed (${error instanceof Error ? error.message : String(error)}); will retry`);
1189
- }
1190
- }
1191
- };
1192
- const first = await openTunnel();
1193
- tunnel = first.proc;
1194
- let tunnelUrl = first.url;
1195
- await registerRuntime(tunnelUrl).catch((error) => {
1196
- writeError(`registration will retry (${error instanceof Error ? error.message : String(error)})`);
1197
- });
1198
- writeLine("Runtime registered with PAGS \u2713");
1199
- writeLine("");
1200
- 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");
1201
- writeLine(" \u2705 CONNECTED \u2014 runner is live");
1202
- writeLine(` Agents: ${instanceIds.length} instance${instanceIds.length === 1 ? "" : "s"} served by this runner`);
1203
- writeLine(` Tunnel: ${tunnelUrl}`);
1204
- writeLine(" Auto-reconnect is on \u2014 keep this terminal open. Ctrl+C to disconnect.");
1205
- 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");
1206
- let stopped = false;
1207
- let consecutiveFailures = 0;
1208
- let respawnBackoffMs = 3e4;
1209
- const PROBE_FAIL_THRESHOLD = 3;
1210
- const MAX_BACKOFF_MS = 5 * 6e4;
1211
- const probeTunnel = async (url) => {
1212
- try {
1213
- const res = await fetch(`${url.replace(/\/$/, "")}/health`, {
1214
- headers: { Authorization: `Bearer ${runnerToken}`, "X-PAGS-Instance-Id": primary },
1215
- signal: AbortSignal.timeout(1e4)
1216
- });
1217
- return res.ok;
1218
- } catch {
1219
- return false;
1220
- }
1221
- };
1222
- const heartbeatAll = async () => {
1223
- let allOk = true;
1224
- for (const id of instanceIds) {
1225
- const ok = await requestPags("POST", `/v1/instances/${apiPathSegment(id)}/runtime/heartbeat`, opts, {}).then(() => true).catch(() => false);
1226
- if (!ok) allOk = false;
1227
- }
1228
- return allOk;
1229
- };
1230
- void (async () => {
1231
- while (!stopped) {
1232
- await new Promise((r) => setTimeout(r, 3e4));
1233
- if (stopped) break;
1234
- if (await probeTunnel(tunnelUrl)) {
1235
- consecutiveFailures = 0;
1236
- respawnBackoffMs = 3e4;
1237
- const beat = await heartbeatAll();
1238
- if (!beat) {
1239
- await registerRuntime(tunnelUrl).then(() => writeLine("\u2705 Re-registered with PAGS")).catch(() => void 0);
1240
- }
1241
- continue;
1242
- }
1243
- consecutiveFailures++;
1244
- if (consecutiveFailures < PROBE_FAIL_THRESHOLD) {
1245
- writeLine(`\u26A0 Tunnel probe failed (${consecutiveFailures}/${PROBE_FAIL_THRESHOLD}) \u2014 will retry`);
1246
- continue;
1247
- }
1248
- writeLine("\u26A0 Tunnel unreachable \u2014 reconnecting\u2026");
1249
- consecutiveFailures = 0;
1250
- try {
1251
- if (tunnel && !tunnel.killed) tunnel.kill("SIGTERM");
1252
- } catch {
1253
- }
1254
- try {
1255
- const next = await openTunnel();
1256
- tunnel = next.proc;
1257
- tunnelUrl = next.url;
1258
- await registerRuntime(tunnelUrl);
1259
- respawnBackoffMs = 3e4;
1260
- writeLine(`\u2705 Reconnected: ${tunnelUrl}`);
1261
- } catch (error) {
1262
- writeError(`reconnect failed (${error instanceof Error ? error.message : String(error)}); retrying in ${Math.round(respawnBackoffMs / 1e3)}s`);
1263
- await new Promise((r) => setTimeout(r, respawnBackoffMs));
1264
- respawnBackoffMs = Math.min(respawnBackoffMs * 2, MAX_BACKOFF_MS);
1265
- }
1266
- }
1267
- })();
960
+ await connectViaRelay(instanceIds, localUrl, runnerToken, opts, Boolean(opts.force));
1268
961
  await new Promise((resolvePromise) => {
1269
962
  runner.on("exit", () => resolvePromise());
1270
963
  });
1271
- stopped = true;
1272
964
  } catch (error) {
1273
965
  shutdown();
1274
966
  throw error;
@@ -1498,8 +1190,7 @@ async function stopRunnerProcesses() {
1498
1190
  const patterns = [
1499
1191
  "dist/browser-runner/index.js",
1500
1192
  "browser-runner/src/index",
1501
- "runner connect",
1502
- "tunnel --url http://127.0.0.1"
1193
+ "runner connect"
1503
1194
  ];
1504
1195
  let stopped = false;
1505
1196
  for (const p of patterns) {
@@ -1511,7 +1202,7 @@ async function stopRunnerProcesses() {
1511
1202
  }
1512
1203
  return stopped;
1513
1204
  }
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) => {
1205
+ 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("--force", "Take over from another connected machine").action(async (opts) => {
1515
1206
  const session = requireSession();
1516
1207
  const state = {
1517
1208
  user: session.user.login,
@@ -1558,8 +1249,6 @@ var upCommand = new Command7("up").description("Start the browser runner for all
1558
1249
  const cliPath = process.argv[1];
1559
1250
  const args = [cliPath, "runner", "connect", ...instances.map((i) => i.id)];
1560
1251
  if (opts.headless) args.push("--headless");
1561
- if (opts.tunnel === "named") args.push("--tunnel", "named");
1562
- if (opts.tunnel === "ws") args.push("--tunnel", "ws");
1563
1252
  if (opts.force) args.push("--force");
1564
1253
  const child = spawn3(process.execPath, args, {
1565
1254
  stdio: ["ignore", "pipe", "pipe"],
@@ -1573,14 +1262,6 @@ var upCommand = new Command7("up").description("Start the browser runner for all
1573
1262
  if (!trimmed) continue;
1574
1263
  logs.push(trimmed);
1575
1264
  if (logs.length > 200) logs.shift();
1576
- const tunnelMatch = trimmed.match(/https:\/\/[a-z0-9-]+\.trycloudflare\.com/i);
1577
- if (tunnelMatch && !/^https:\/\/api\.trycloudflare\.com/i.test(tunnelMatch[0])) {
1578
- state.tunnel = "online";
1579
- state.tunnelUrl = tunnelMatch[0];
1580
- state.lastEvent = "Tunnel created";
1581
- printStatus(state);
1582
- continue;
1583
- }
1584
1265
  if (trimmed.includes("Relay connected:")) {
1585
1266
  state.tunnel = "online";
1586
1267
  state.tunnelUrl = "WebSocket relay";
@@ -1588,7 +1269,7 @@ var upCommand = new Command7("up").description("Start the browser runner for all
1588
1269
  printStatus(state);
1589
1270
  continue;
1590
1271
  }
1591
- if (trimmed.includes("WebSocket relay (no tunnel)")) {
1272
+ if (trimmed.includes("WebSocket relay")) {
1592
1273
  state.tunnel = "online";
1593
1274
  state.tunnelUrl = "WebSocket relay";
1594
1275
  state.registration = "registered";
@@ -1608,13 +1289,18 @@ var upCommand = new Command7("up").description("Start the browser runner for all
1608
1289
  printStatus(state);
1609
1290
  continue;
1610
1291
  }
1292
+ if (trimmed.includes("Another machine")) {
1293
+ state.registration = "failed";
1294
+ state.lastEvent = trimmed.slice(0, 80);
1295
+ printStatus(state);
1296
+ continue;
1297
+ }
1611
1298
  if (trimmed.includes("fetch failed")) {
1612
1299
  state.registration = "failed";
1613
- state.lastEvent = "PAGS registration failed (will retry on next task)";
1300
+ state.lastEvent = "PAGS registration failed";
1614
1301
  printStatus(state);
1615
1302
  continue;
1616
1303
  }
1617
- if (trimmed.includes("INF ")) continue;
1618
1304
  if (/error|Error|EADDRINUSE|ECONNREFUSED|failed/i.test(trimmed)) {
1619
1305
  state.lastEvent = trimmed.slice(0, 80);
1620
1306
  printStatus(state);
@@ -1629,7 +1315,7 @@ var upCommand = new Command7("up").description("Start the browser runner for all
1629
1315
  if (code && code !== 0) {
1630
1316
  state.runner = "error";
1631
1317
  state.lastEvent = `Runner exited (code ${code})`;
1632
- const recent = logs.slice(-5).filter((l) => !l.includes("INF "));
1318
+ const recent = logs.slice(-5);
1633
1319
  if (recent.length) state.lastEvent += ": " + recent[recent.length - 1].slice(0, 60);
1634
1320
  printStatus(state);
1635
1321
  }
@@ -1663,14 +1349,9 @@ var upCommand = new Command7("up").description("Start the browser runner for all
1663
1349
  if (key === "r") {
1664
1350
  if (childDead) {
1665
1351
  writeLine(" Restarting runner...");
1666
- state.runner = "starting";
1667
- state.tunnel = "offline";
1668
- state.tunnelUrl = "";
1669
- state.registration = "pending";
1670
- state.lastEvent = "Restarting...";
1671
1352
  const { execSync } = await import("child_process");
1672
1353
  try {
1673
- execSync(`${process.execPath} ${process.argv[1]} up${opts.headless ? " --headless" : ""}`, {
1354
+ execSync(`${process.execPath} ${process.argv[1]} up${opts.headless ? " --headless" : ""}${opts.force ? " --force" : ""}`, {
1674
1355
  stdio: "inherit",
1675
1356
  env: process.env
1676
1357
  });
@@ -1682,7 +1363,7 @@ var upCommand = new Command7("up").description("Start the browser runner for all
1682
1363
  }
1683
1364
  }
1684
1365
  });
1685
- var downCommand = new Command7("down").description("Stop the browser runner and disconnect (cleans up any stuck processes)").action(async () => {
1366
+ var downCommand = new Command7("down").description("Stop the browser runner and disconnect").action(async () => {
1686
1367
  clearScreen();
1687
1368
  printLogo(CLI_VERSION2);
1688
1369
  if (process.platform === "win32") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@proagentstore/cli",
3
- "version": "0.2.11",
3
+ "version": "0.3.0",
4
4
  "description": "CLI for creating, publishing, and running ProAgentStore agents",
5
5
  "license": "MIT",
6
6
  "type": "module",