@proagentstore/cli 0.1.6 → 0.1.8

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.
@@ -25,7 +25,7 @@ function configFromArgs() {
25
25
  };
26
26
  }
27
27
  if (flag("--help") || flag("-h")) {
28
- process.stdout.write(`ProAgentStore browser runner
28
+ process.stdout.write(`FreeAgentStore browser runtime for ProAgentStore
29
29
 
30
30
  Usage:
31
31
  pags-browser-runner [--host 127.0.0.1] [--port 49171] [--data-dir path] [--token token] [--instance-id id] [--headless]
@@ -44,9 +44,9 @@ Endpoints:
44
44
  }
45
45
  const config = configFromArgs();
46
46
  const started = await startRunnerServer(config);
47
- process.stdout.write(`PAGS browser runner listening at ${started.url}\n`);
47
+ process.stdout.write(`FAGS browser runtime listening at ${started.url}\n`);
48
48
  process.stdout.write(`Data dir: ${config.dataDir}\n`);
49
- process.stdout.write(`Brain placement: PAGS; runner role: tool-executor\n`);
49
+ process.stdout.write("Control plane: PAGS; runtime plane: FAGS; brain placement: PAGS; runner role: tool-executor\n");
50
50
  if (config.token)
51
51
  process.stdout.write("Auth: bearer token required\n");
52
52
  if (config.instanceId)
@@ -28,8 +28,10 @@ export class LocalRunner {
28
28
  }
29
29
  capabilities() {
30
30
  return {
31
- runtime: "local-browser-runner",
31
+ runtime: "fags-browser-runtime",
32
32
  brainPlacement: "pags",
33
+ controlPlane: "pags",
34
+ runtimePlane: "fags",
33
35
  runnerRole: "tool-executor",
34
36
  capabilities: CAPABILITIES,
35
37
  taskTypes: ["echo", "browser.open", "job.apply_basic"],
@@ -42,8 +42,10 @@ async function route(runner, req, res) {
42
42
  if (req.method === "GET" && path === "/health") {
43
43
  return json(res, 200, {
44
44
  ok: true,
45
- service: "proagentstore-browser-runner",
45
+ service: "freeagentstore-browser-runtime",
46
46
  brainPlacement: "pags",
47
+ controlPlane: "pags",
48
+ runtimePlane: "fags",
47
49
  instanceId: runner.config.instanceId,
48
50
  });
49
51
  }
package/dist/index.js CHANGED
@@ -472,6 +472,7 @@ var publishCommand = new Command4("publish").description("Publish an agent to Pr
472
472
 
473
473
  // src/commands/runner.ts
474
474
  import { spawn as spawn2 } from "child_process";
475
+ import { randomUUID } from "crypto";
475
476
  import { existsSync as existsSync4 } from "fs";
476
477
  import { resolve as resolve4 } from "path";
477
478
  import { fileURLToPath as fileURLToPath2 } from "url";
@@ -507,6 +508,12 @@ function buildRunnerArgs(opts) {
507
508
  if (opts.headless) args.push("--headless");
508
509
  return args;
509
510
  }
511
+ function buildCloudflaredArgs(localUrl) {
512
+ return ["tunnel", "--url", localUrl];
513
+ }
514
+ function parseCloudflaredTunnelUrl(text) {
515
+ return text.match(/https:\/\/[a-z0-9-]+\.trycloudflare\.com/i)?.[0] || null;
516
+ }
510
517
  function buildRuntimeRegistrationBody(opts, capabilities = []) {
511
518
  return {
512
519
  endpointUrl: clean(opts.endpointUrl) || opts.endpointUrl,
@@ -533,7 +540,7 @@ function findWorkspaceRoot() {
533
540
  function bundledRunnerPath() {
534
541
  return fileURLToPath2(new URL("./browser-runner/index.js", import.meta.url));
535
542
  }
536
- function startRunnerForeground(opts) {
543
+ function runnerSpawnSpec(opts) {
537
544
  const root = findWorkspaceRoot();
538
545
  const localPackage = resolve4(root, "packages", "browser-runner", "src", "index.ts");
539
546
  const bundledPackage = bundledRunnerPath();
@@ -549,9 +556,13 @@ function startRunnerForeground(opts) {
549
556
  command = process.execPath;
550
557
  args = [bundledPackage, ...runnerArgs];
551
558
  }
559
+ return { command, args, cwd };
560
+ }
561
+ function startRunnerForeground(opts) {
562
+ const spec = runnerSpawnSpec(opts);
552
563
  return new Promise((resolvePromise, reject) => {
553
- const child = spawn2(command, args, {
554
- cwd,
564
+ const child = spawn2(spec.command, spec.args, {
565
+ cwd: spec.cwd,
555
566
  stdio: "inherit",
556
567
  shell: process.platform === "win32"
557
568
  });
@@ -562,6 +573,20 @@ function startRunnerForeground(opts) {
562
573
  });
563
574
  });
564
575
  }
576
+ async function waitForLocalRunner(opts, timeoutMs = 15e3) {
577
+ const started = Date.now();
578
+ let lastError;
579
+ while (Date.now() - started < timeoutMs) {
580
+ try {
581
+ await requestRunner("GET", "/health", opts);
582
+ return;
583
+ } catch (error) {
584
+ lastError = error;
585
+ await new Promise((resolvePromise) => setTimeout(resolvePromise, 500));
586
+ }
587
+ }
588
+ throw new Error(`runner did not become healthy: ${lastError instanceof Error ? lastError.message : String(lastError)}`);
589
+ }
565
590
  async function requestRunner(method, path, opts, body) {
566
591
  const headers = {
567
592
  ...runnerRequestHeaders(opts)
@@ -616,17 +641,112 @@ function collectCapability(value, previous = []) {
616
641
  }
617
642
  function createRunnerCommand() {
618
643
  const command = new Command5("runner").description(
619
- "Manage a local ProAgentStore browser runner"
644
+ "Manage the local FAGS browser runtime for ProAgentStore agents"
620
645
  );
621
- command.command("start").description("Start the local browser runner 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) => {
646
+ 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) => {
622
647
  await startRunnerForeground(opts);
623
648
  });
624
- command.command("status").description("Check local runner health and capabilities").option("--url <url>", "Runner URL").option("--token <token>", "Runner bearer token").option("--instance-id <id>", "PAGS instance id header").action(async (opts) => {
649
+ 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) => {
650
+ const runnerToken = clean(opts.token) || clean(process.env.PAGS_RUNNER_TOKEN) || `pags_runner_${randomUUID()}`;
651
+ const host = clean(opts.host) || "127.0.0.1";
652
+ const port = clean(opts.port) || "49171";
653
+ const localUrl = `http://${host}:${port}`;
654
+ const runnerOpts = {
655
+ ...opts,
656
+ host,
657
+ port,
658
+ token: runnerToken,
659
+ instanceId
660
+ };
661
+ const spec = runnerSpawnSpec(runnerOpts);
662
+ const runner = spawn2(spec.command, spec.args, {
663
+ cwd: spec.cwd,
664
+ stdio: ["ignore", "pipe", "pipe"],
665
+ shell: process.platform === "win32"
666
+ });
667
+ runner.stdout?.on("data", (data) => process.stdout.write(data));
668
+ runner.stderr?.on("data", (data) => process.stderr.write(data));
669
+ runner.on("exit", (code) => {
670
+ if (code && code !== 0) writeError(`runner exited with code ${code}`);
671
+ });
672
+ let tunnel = null;
673
+ const shutdown = () => {
674
+ if (tunnel && !tunnel.killed) tunnel.kill("SIGTERM");
675
+ if (!runner.killed) runner.kill("SIGTERM");
676
+ };
677
+ process.once("SIGINT", () => {
678
+ shutdown();
679
+ process.exit(0);
680
+ });
681
+ process.once("SIGTERM", () => {
682
+ shutdown();
683
+ process.exit(0);
684
+ });
685
+ try {
686
+ await waitForLocalRunner({ url: localUrl, token: runnerToken, instanceId });
687
+ writeLine(`Local FAGS runtime healthy at ${localUrl}`);
688
+ const tunnelUrl = await new Promise((resolveTunnel, reject) => {
689
+ let output = "";
690
+ const cloudflared = clean(opts.cloudflared) || "cloudflared";
691
+ tunnel = spawn2(cloudflared, buildCloudflaredArgs(localUrl), {
692
+ stdio: ["ignore", "pipe", "pipe"],
693
+ shell: process.platform === "win32"
694
+ });
695
+ const onData = (data) => {
696
+ const text = data.toString("utf-8");
697
+ output += text;
698
+ process.stderr.write(text);
699
+ const parsed = parseCloudflaredTunnelUrl(output);
700
+ if (parsed) resolveTunnel(parsed);
701
+ };
702
+ tunnel.stdout?.on("data", onData);
703
+ tunnel.stderr?.on("data", onData);
704
+ tunnel.on("error", reject);
705
+ tunnel.on("exit", (code) => {
706
+ if (code && code !== 0) reject(new Error(`cloudflared exited with code ${code}`));
707
+ });
708
+ setTimeout(() => reject(new Error("timed out waiting for cloudflared tunnel URL")), 45e3).unref();
709
+ });
710
+ writeLine(`Tunnel ready: ${tunnelUrl}`);
711
+ await waitForLocalRunner({ url: tunnelUrl, token: runnerToken, instanceId }, 3e4);
712
+ writeLine("Tunnel health check passed");
713
+ const capabilities = await requestRunner("GET", "/capabilities", {
714
+ url: tunnelUrl,
715
+ token: runnerToken,
716
+ instanceId
717
+ });
718
+ const result = await requestPags("POST", `/v1/instances/${apiPathSegment(instanceId)}/runtime`, opts, {
719
+ endpointUrl: tunnelUrl,
720
+ token: runnerToken,
721
+ placement: "local",
722
+ capabilities: Array.isArray(capabilities.capabilities) ? capabilities.capabilities.filter((item) => typeof item === "string") : [],
723
+ runnerVersion: clean(opts.runnerVersion) || ""
724
+ });
725
+ writeLine(JSON.stringify(result, null, 2));
726
+ if (!opts.skipProbe) {
727
+ const status = await requestPags(
728
+ "GET",
729
+ `/v1/instances/${apiPathSegment(instanceId)}/runtime/status`,
730
+ opts
731
+ );
732
+ writeLine(JSON.stringify(status, null, 2));
733
+ }
734
+ writeLine("Connected. Keep this process running while the agent uses your FAGS browser runtime.");
735
+ await new Promise((resolvePromise) => {
736
+ runner.on("exit", () => resolvePromise());
737
+ tunnel?.on("exit", () => resolvePromise());
738
+ });
739
+ } catch (error) {
740
+ shutdown();
741
+ throw error;
742
+ }
743
+ });
744
+ command.command("status").description("Check local FAGS runtime health and capabilities").option("--url <url>", "Runner URL").option("--token <token>", "Runner bearer token").option("--instance-id <id>", "PAGS instance id header").action(async (opts) => {
625
745
  const health = await requestRunner("GET", "/health", opts);
626
746
  const capabilities = await requestRunner("GET", "/capabilities", opts);
627
747
  writeLine(JSON.stringify({ health, capabilities }, null, 2));
628
748
  });
629
- command.command("task").description("Create a runner task").requiredOption("--type <type>", "Task type, e.g. echo or browser.open").option("--url <url>", "Runner URL").option("--token <token>", "Runner bearer token").option("--instance-id <id>", "PAGS instance id header").option("--input <json>", "Task input JSON").option("--job-url <url>", "Shortcut input for browser.open").option("--approve", "Create task in needs_approval state").option("--approval-prompt <text>", "Approval prompt").action(
749
+ command.command("task").description("Create a local FAGS runtime task").requiredOption("--type <type>", "Task type, e.g. echo or browser.open").option("--url <url>", "Runner URL").option("--token <token>", "Runner bearer token").option("--instance-id <id>", "PAGS instance id header").option("--input <json>", "Task input JSON").option("--job-url <url>", "Shortcut input for browser.open").option("--approve", "Create task in needs_approval state").option("--approval-prompt <text>", "Approval prompt").action(
630
750
  async (opts) => {
631
751
  let input = {};
632
752
  if (opts.input) {
@@ -647,7 +767,7 @@ function createRunnerCommand() {
647
767
  writeLine(JSON.stringify(task, null, 2));
648
768
  }
649
769
  );
650
- command.command("register <instanceId>").description("Register a local or managed runner endpoint with a PAGS instance").requiredOption("--endpoint-url <url>", "Runner endpoint URL to store in PAGS").option("--api-base <url>", "PAGS API base URL").option("--pags-token <token>", "PAGS session token. Defaults to PAGS_TOKEN").option("--runner-token <token>", "Runner bearer token to store in PAGS").option("--url <url>", "Local runner URL to probe for capabilities").option("--token <token>", "Local runner bearer token for capability probe").option("--instance-id <id>", "PAGS instance id header for capability probe").option("--placement <placement>", "Runtime placement: local or managed", "local").option("--runner-version <version>", "Runner version").option("--capability <name>", "Runtime capability; repeatable", collectCapability, []).option("--probe", "Read capabilities from the runner before registering").action(async (instanceId, opts) => {
770
+ command.command("register <instanceId>").description("Register a local or managed FAGS runtime endpoint with a PAGS instance").requiredOption("--endpoint-url <url>", "FAGS runtime endpoint URL to store in PAGS").option("--api-base <url>", "PAGS API base URL").option("--pags-token <token>", "PAGS session token. Defaults to PAGS_TOKEN").option("--runner-token <token>", "FAGS runtime bearer token to store in PAGS").option("--url <url>", "Local FAGS runtime URL to probe for capabilities").option("--token <token>", "Local FAGS runtime bearer token for capability probe").option("--instance-id <id>", "PAGS instance id header for capability probe").option("--placement <placement>", "Runtime placement: local or managed", "local").option("--runner-version <version>", "Runner version").option("--capability <name>", "Runtime capability; repeatable", collectCapability, []).option("--probe", "Read capabilities from the FAGS runtime before registering").action(async (instanceId, opts) => {
651
771
  let capabilities = opts.capability || [];
652
772
  if (opts.probe) {
653
773
  const data = await requestRunner("GET", "/capabilities", {
@@ -661,7 +781,7 @@ function createRunnerCommand() {
661
781
  const result = await requestPags("POST", `/v1/instances/${apiPathSegment(instanceId)}/runtime`, opts, body);
662
782
  writeLine(JSON.stringify(result, null, 2));
663
783
  });
664
- command.command("runtime <instanceId>").description("Read the PAGS runtime registration for an instance").option("--api-base <url>", "PAGS API base URL").option("--pags-token <token>", "PAGS session token. Defaults to PAGS_TOKEN").option("--probe", "Ask PAGS to probe /health and /capabilities on the runner").action(async (instanceId, opts) => {
784
+ command.command("runtime <instanceId>").description("Read the PAGS runtime registration for an instance").option("--api-base <url>", "PAGS API base URL").option("--pags-token <token>", "PAGS session token. Defaults to PAGS_TOKEN").option("--probe", "Ask PAGS to probe /health and /capabilities on the FAGS runtime").action(async (instanceId, opts) => {
665
785
  const path = opts.probe ? `/v1/instances/${apiPathSegment(instanceId)}/runtime/status` : `/v1/instances/${apiPathSegment(instanceId)}/runtime`;
666
786
  const result = await requestPags("GET", path, opts);
667
787
  writeLine(JSON.stringify(result, null, 2));
@@ -670,7 +790,7 @@ function createRunnerCommand() {
670
790
  const result = await requestPags("DELETE", `/v1/instances/${apiPathSegment(instanceId)}/runtime`, opts);
671
791
  writeLine(JSON.stringify(result, null, 2));
672
792
  });
673
- command.command("run <instanceId>").description("Create a task through PAGS on an instance's registered runner").requiredOption("--type <type>", "Task type, e.g. echo or browser.open").option("--api-base <url>", "PAGS API base URL").option("--pags-token <token>", "PAGS session token. Defaults to PAGS_TOKEN").option("--input <json>", "Task input JSON").option("--job-url <url>", "Shortcut input for browser.open").option("--approve", "Create task in needs_approval state").option("--approval-prompt <text>", "Approval prompt").action(
793
+ command.command("run <instanceId>").description("Create a task through PAGS on an instance's registered FAGS runtime").requiredOption("--type <type>", "Task type, e.g. echo or browser.open").option("--api-base <url>", "PAGS API base URL").option("--pags-token <token>", "PAGS session token. Defaults to PAGS_TOKEN").option("--input <json>", "Task input JSON").option("--job-url <url>", "Shortcut input for browser.open").option("--approve", "Create task in needs_approval state").option("--approval-prompt <text>", "Approval prompt").action(
674
794
  async (instanceId, opts) => {
675
795
  let input = {};
676
796
  if (opts.input) {
@@ -691,7 +811,7 @@ function createRunnerCommand() {
691
811
  writeLine(JSON.stringify(result, null, 2));
692
812
  }
693
813
  );
694
- command.command("approve-task <instanceId> <taskId>").description("Approve a registered-runner task through PAGS").option("--api-base <url>", "PAGS API base URL").option("--pags-token <token>", "PAGS session token. Defaults to PAGS_TOKEN").action(async (instanceId, taskId, opts) => {
814
+ command.command("approve-task <instanceId> <taskId>").description("Approve a registered FAGS runtime task through PAGS").option("--api-base <url>", "PAGS API base URL").option("--pags-token <token>", "PAGS session token. Defaults to PAGS_TOKEN").action(async (instanceId, taskId, opts) => {
695
815
  const result = await requestPags(
696
816
  "POST",
697
817
  `/v1/instances/${apiPathSegment(instanceId)}/tasks/${apiPathSegment(taskId)}/approve`,
@@ -699,7 +819,7 @@ function createRunnerCommand() {
699
819
  );
700
820
  writeLine(JSON.stringify(result, null, 2));
701
821
  });
702
- command.command("cancel-task <instanceId> <taskId>").description("Cancel a registered-runner task through PAGS").option("--api-base <url>", "PAGS API base URL").option("--pags-token <token>", "PAGS session token. Defaults to PAGS_TOKEN").action(async (instanceId, taskId, opts) => {
822
+ command.command("cancel-task <instanceId> <taskId>").description("Cancel a registered FAGS runtime task through PAGS").option("--api-base <url>", "PAGS API base URL").option("--pags-token <token>", "PAGS session token. Defaults to PAGS_TOKEN").action(async (instanceId, taskId, opts) => {
703
823
  const result = await requestPags(
704
824
  "POST",
705
825
  `/v1/instances/${apiPathSegment(instanceId)}/tasks/${apiPathSegment(taskId)}/cancel`,
@@ -707,7 +827,7 @@ function createRunnerCommand() {
707
827
  );
708
828
  writeLine(JSON.stringify(result, null, 2));
709
829
  });
710
- command.command("task-events <instanceId>").description("Read registered-runner task events through PAGS").option("--api-base <url>", "PAGS API base URL").option("--pags-token <token>", "PAGS session token. Defaults to PAGS_TOKEN").option("--limit <n>", "Number of events", "50").action(async (instanceId, opts) => {
830
+ command.command("task-events <instanceId>").description("Read registered FAGS runtime task events through PAGS").option("--api-base <url>", "PAGS API base URL").option("--pags-token <token>", "PAGS session token. Defaults to PAGS_TOKEN").option("--limit <n>", "Number of events", "50").action(async (instanceId, opts) => {
711
831
  const result = await requestPags(
712
832
  "GET",
713
833
  `/v1/instances/${apiPathSegment(instanceId)}/task-events?limit=${Number(opts.limit) || 50}`,
@@ -719,11 +839,11 @@ function createRunnerCommand() {
719
839
  const task = await requestRunner("POST", `/tasks/${apiPathSegment(taskId)}/approve`, opts);
720
840
  writeLine(JSON.stringify(task, null, 2));
721
841
  });
722
- command.command("cancel <taskId>").description("Cancel a local runner task").option("--url <url>", "Runner URL").option("--token <token>", "Runner bearer token").option("--instance-id <id>", "PAGS instance id header").action(async (taskId, opts) => {
842
+ command.command("cancel <taskId>").description("Cancel a local FAGS runtime task").option("--url <url>", "Runner URL").option("--token <token>", "Runner bearer token").option("--instance-id <id>", "PAGS instance id header").action(async (taskId, opts) => {
723
843
  const task = await requestRunner("POST", `/tasks/${apiPathSegment(taskId)}/cancel`, opts);
724
844
  writeLine(JSON.stringify(task, null, 2));
725
845
  });
726
- command.command("events").description("List recent runner events").option("--url <url>", "Runner URL").option("--token <token>", "Runner bearer token").option("--instance-id <id>", "PAGS instance id header").option("--limit <n>", "Number of events", "50").action(async (opts) => {
846
+ command.command("events").description("List recent FAGS runtime events").option("--url <url>", "Runner URL").option("--token <token>", "Runner bearer token").option("--instance-id <id>", "PAGS instance id header").option("--limit <n>", "Number of events", "50").action(async (opts) => {
727
847
  const events = await requestRunner("GET", `/events?limit=${Number(opts.limit) || 50}`, opts);
728
848
  writeLine(JSON.stringify(events, null, 2));
729
849
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@proagentstore/cli",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "CLI for creating, publishing, and running ProAgentStore agents",
5
5
  "license": "MIT",
6
6
  "type": "module",