@proagentstore/cli 0.1.16 → 0.1.17

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 +130 -20
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -614,6 +614,9 @@ 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, platform } from "os";
617
620
  import { resolve as resolve4 } from "path";
618
621
  import { fileURLToPath as fileURLToPath2 } from "url";
619
622
  import { Command as Command6 } from "commander";
@@ -651,6 +654,112 @@ function buildRunnerArgs(opts) {
651
654
  function buildCloudflaredArgs(localUrl) {
652
655
  return ["tunnel", "--url", localUrl];
653
656
  }
657
+ function cloudflaredAssetForPlatform(os = platform(), cpu = arch()) {
658
+ if (os === "darwin") {
659
+ if (cpu === "arm64") {
660
+ return {
661
+ asset: "cloudflared-darwin-arm64.tgz",
662
+ executableName: "cloudflared",
663
+ archive: true
664
+ };
665
+ }
666
+ if (cpu === "x64") {
667
+ return {
668
+ asset: "cloudflared-darwin-amd64.tgz",
669
+ executableName: "cloudflared",
670
+ archive: true
671
+ };
672
+ }
673
+ }
674
+ if (os === "linux") {
675
+ if (cpu === "x64") {
676
+ return {
677
+ asset: "cloudflared-linux-amd64",
678
+ executableName: "cloudflared",
679
+ archive: false
680
+ };
681
+ }
682
+ if (cpu === "arm64") {
683
+ return {
684
+ asset: "cloudflared-linux-arm64",
685
+ executableName: "cloudflared",
686
+ archive: false
687
+ };
688
+ }
689
+ }
690
+ if (os === "win32" && cpu === "x64") {
691
+ return {
692
+ asset: "cloudflared-windows-amd64.exe",
693
+ executableName: "cloudflared.exe",
694
+ archive: false
695
+ };
696
+ }
697
+ throw new Error(
698
+ `Automatic cloudflared download is not supported on ${os}/${cpu}. Install cloudflared and pass --cloudflared <path>.`
699
+ );
700
+ }
701
+ function cloudflaredDownloadUrl(asset) {
702
+ return `https://github.com/cloudflare/cloudflared/releases/latest/download/${asset}`;
703
+ }
704
+ function extractCloudflaredBinary(asset, bytes) {
705
+ const buffer = Buffer.from(bytes);
706
+ if (!asset.archive) return buffer;
707
+ const tar = gunzipSync(buffer);
708
+ for (let offset = 0; offset + 512 <= tar.length; ) {
709
+ const header = tar.subarray(offset, offset + 512);
710
+ if (header.every((byte) => byte === 0)) break;
711
+ const rawName = header.subarray(0, 100).toString("utf8");
712
+ const name = rawName.slice(0, rawName.indexOf("\0") === -1 ? void 0 : rawName.indexOf("\0"));
713
+ const rawSize = header.subarray(124, 136).toString("utf8").replace(/\0.*$/, "").trim();
714
+ const size = Number.parseInt(rawSize || "0", 8);
715
+ const contentStart = offset + 512;
716
+ const contentEnd = contentStart + size;
717
+ if (name.split("/").pop() === asset.executableName) {
718
+ return tar.subarray(contentStart, contentEnd);
719
+ }
720
+ offset = contentStart + Math.ceil(size / 512) * 512;
721
+ }
722
+ throw new Error(`Downloaded ${asset.asset} did not contain ${asset.executableName}`);
723
+ }
724
+ function cloudflaredCacheDir() {
725
+ return resolve4(homedir2(), ".config", "proagentstore", "bin");
726
+ }
727
+ function cachedCloudflaredPath(asset) {
728
+ const suffix = asset.asset.replace(/[^a-zA-Z0-9._-]/g, "-");
729
+ return resolve4(cloudflaredCacheDir(), `${asset.executableName}-${suffix}`);
730
+ }
731
+ async function commandAvailable(command) {
732
+ return new Promise((resolvePromise) => {
733
+ const child = spawn2(command, ["--version"], {
734
+ stdio: "ignore",
735
+ shell: process.platform === "win32"
736
+ });
737
+ child.on("error", () => resolvePromise(false));
738
+ child.on("exit", (code) => resolvePromise(code === 0));
739
+ });
740
+ }
741
+ async function downloadCloudflared(asset) {
742
+ const target = cachedCloudflaredPath(asset);
743
+ if (existsSync5(target)) return target;
744
+ await mkdir(cloudflaredCacheDir(), { recursive: true });
745
+ const url = cloudflaredDownloadUrl(asset.asset);
746
+ writeLine(`cloudflared not found; downloading ${asset.asset}...`);
747
+ const res = await fetch(url);
748
+ if (!res.ok) {
749
+ throw new Error(`cloudflared download failed: ${res.status} ${res.statusText}`);
750
+ }
751
+ const binary = extractCloudflaredBinary(asset, new Uint8Array(await res.arrayBuffer()));
752
+ await writeFile(target, binary);
753
+ if (process.platform !== "win32") await chmod(target, 493);
754
+ writeLine(`cloudflared cached at ${target}`);
755
+ return target;
756
+ }
757
+ async function resolveCloudflaredCommand(requested) {
758
+ const explicit = clean(requested);
759
+ if (explicit && explicit !== "cloudflared") return explicit;
760
+ if (await commandAvailable("cloudflared")) return "cloudflared";
761
+ return downloadCloudflared(cloudflaredAssetForPlatform());
762
+ }
654
763
  function parseCloudflaredTunnelUrl(text) {
655
764
  return text.match(/https:\/\/[a-z0-9-]+\.trycloudflare\.com/i)?.[0] || null;
656
765
  }
@@ -786,7 +895,7 @@ function createRunnerCommand() {
786
895
  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) => {
787
896
  await startRunnerForeground(opts);
788
897
  });
789
- command.command("connect <instanceId>").description("Start the local FAGS runtime, open a Cloudflare quick tunnel, and register it with PAGS").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", "cloudflared").option("--runner-version <version>", "Runner version").option("--skip-probe", "Skip PAGS FAGS-runtime probe after registration").action(async (instanceId, opts) => {
898
+ command.command("connect <instanceId>").description("Start the local FAGS runtime, open a Cloudflare quick tunnel, and register it with PAGS").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").action(async (instanceId, opts) => {
790
899
  const runnerToken = clean(opts.token) || clean(process.env.PAGS_RUNNER_TOKEN) || `pags_runner_${randomUUID()}`;
791
900
  const host = clean(opts.host) || "127.0.0.1";
792
901
  const port = clean(opts.port) || "49171";
@@ -827,24 +936,25 @@ function createRunnerCommand() {
827
936
  writeLine(`Local FAGS runtime healthy at ${localUrl}`);
828
937
  const tunnelUrl = await new Promise((resolveTunnel, reject) => {
829
938
  let output = "";
830
- const cloudflared = clean(opts.cloudflared) || "cloudflared";
831
- tunnel = spawn2(cloudflared, buildCloudflaredArgs(localUrl), {
832
- stdio: ["ignore", "pipe", "pipe"],
833
- shell: process.platform === "win32"
834
- });
835
- const onData = (data) => {
836
- const text = data.toString("utf-8");
837
- output += text;
838
- process.stderr.write(text);
839
- const parsed = parseCloudflaredTunnelUrl(output);
840
- if (parsed) resolveTunnel(parsed);
841
- };
842
- tunnel.stdout?.on("data", onData);
843
- tunnel.stderr?.on("data", onData);
844
- tunnel.on("error", reject);
845
- tunnel.on("exit", (code) => {
846
- if (code && code !== 0) reject(new Error(`cloudflared exited with code ${code}`));
847
- });
939
+ resolveCloudflaredCommand(opts.cloudflared).then((cloudflared) => {
940
+ tunnel = spawn2(cloudflared, buildCloudflaredArgs(localUrl), {
941
+ stdio: ["ignore", "pipe", "pipe"],
942
+ shell: process.platform === "win32"
943
+ });
944
+ const onData = (data) => {
945
+ const text = data.toString("utf-8");
946
+ output += text;
947
+ process.stderr.write(text);
948
+ const parsed = parseCloudflaredTunnelUrl(output);
949
+ if (parsed) resolveTunnel(parsed);
950
+ };
951
+ tunnel.stdout?.on("data", onData);
952
+ tunnel.stderr?.on("data", onData);
953
+ tunnel.on("error", reject);
954
+ tunnel.on("exit", (code) => {
955
+ if (code && code !== 0) reject(new Error(`cloudflared exited with code ${code}`));
956
+ });
957
+ }).catch(reject);
848
958
  setTimeout(() => reject(new Error("timed out waiting for cloudflared tunnel URL")), 45e3).unref();
849
959
  });
850
960
  writeLine(`Tunnel ready: ${tunnelUrl}`);
@@ -853,7 +963,7 @@ function createRunnerCommand() {
853
963
  token: runnerToken,
854
964
  instanceId
855
965
  });
856
- const result = await requestPags("POST", `/v1/instances/${apiPathSegment(instanceId)}/runtime`, opts, {
966
+ await requestPags("POST", `/v1/instances/${apiPathSegment(instanceId)}/runtime`, opts, {
857
967
  endpointUrl: tunnelUrl,
858
968
  token: runnerToken,
859
969
  placement: "local",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@proagentstore/cli",
3
- "version": "0.1.16",
3
+ "version": "0.1.17",
4
4
  "description": "CLI for creating, publishing, and running ProAgentStore agents",
5
5
  "license": "MIT",
6
6
  "type": "module",