@proagentstore/cli 0.4.39 → 0.4.40
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.
- package/dist/index.js +76 -19
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -617,11 +617,57 @@ var publishCommand = new Command5("publish").description("Publish an agent to Pr
|
|
|
617
617
|
|
|
618
618
|
// src/commands/runner/command.ts
|
|
619
619
|
import { spawn as spawn3 } from "child_process";
|
|
620
|
-
import { randomUUID } from "crypto";
|
|
620
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
621
621
|
import { Command as Command6 } from "commander";
|
|
622
622
|
|
|
623
623
|
// src/commands/runner/http.ts
|
|
624
|
-
import { hostname } from "os";
|
|
624
|
+
import { hostname as hostname2 } from "os";
|
|
625
|
+
|
|
626
|
+
// src/machine.ts
|
|
627
|
+
import { randomUUID } from "crypto";
|
|
628
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
629
|
+
import { homedir as homedir2, hostname } from "os";
|
|
630
|
+
import { join as join5 } from "path";
|
|
631
|
+
var CONFIG_DIR2 = join5(homedir2(), ".config", "proagentstore");
|
|
632
|
+
var MACHINE_FILE = join5(CONFIG_DIR2, "machine.json");
|
|
633
|
+
var MAX_NAMES = 10;
|
|
634
|
+
function isValidMachineId(value) {
|
|
635
|
+
return typeof value === "string" && /^[A-Za-z0-9_-]{8,64}$/.test(value);
|
|
636
|
+
}
|
|
637
|
+
function parseMachineFile(text) {
|
|
638
|
+
try {
|
|
639
|
+
const data = JSON.parse(text);
|
|
640
|
+
if (!isValidMachineId(data.id)) return null;
|
|
641
|
+
const names = Array.isArray(data.names) ? data.names.filter((n) => typeof n === "string" && n.trim().length > 0).map((n) => n.trim()) : [];
|
|
642
|
+
return { id: data.id, names: names.slice(0, MAX_NAMES) };
|
|
643
|
+
} catch {
|
|
644
|
+
return null;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
function withName(identity, name) {
|
|
648
|
+
const current = name.trim();
|
|
649
|
+
const prev = identity?.names ?? [];
|
|
650
|
+
const names = current ? [current, ...prev.filter((n) => n !== current)] : [...prev];
|
|
651
|
+
return { id: identity?.id ?? "", names: names.slice(0, MAX_NAMES) };
|
|
652
|
+
}
|
|
653
|
+
function loadMachineIdentity(now = hostname()) {
|
|
654
|
+
let stored = null;
|
|
655
|
+
try {
|
|
656
|
+
if (existsSync5(MACHINE_FILE)) stored = parseMachineFile(readFileSync4(MACHINE_FILE, "utf-8"));
|
|
657
|
+
} catch {
|
|
658
|
+
}
|
|
659
|
+
const next = withName(stored ?? { id: randomUUID(), names: [] }, now);
|
|
660
|
+
if (stored && stored.id === next.id && stored.names.join("\0") === next.names.join("\0")) return next;
|
|
661
|
+
try {
|
|
662
|
+
mkdirSync3(CONFIG_DIR2, { recursive: true });
|
|
663
|
+
writeFileSync3(MACHINE_FILE, JSON.stringify(next, null, 2));
|
|
664
|
+
return next;
|
|
665
|
+
} catch {
|
|
666
|
+
return stored ?? { id: "", names: [] };
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// src/commands/runner/http.ts
|
|
625
671
|
function clean(value) {
|
|
626
672
|
const trimmed = value?.trim();
|
|
627
673
|
return trimmed || void 0;
|
|
@@ -647,13 +693,17 @@ function apiPathSegment(value) {
|
|
|
647
693
|
return encodeURIComponent(value);
|
|
648
694
|
}
|
|
649
695
|
function buildRuntimeRegistrationBody(opts, capabilities = []) {
|
|
696
|
+
const node = hostname2();
|
|
697
|
+
const machine = loadMachineIdentity(node);
|
|
650
698
|
return {
|
|
651
699
|
endpointUrl: clean(opts.endpointUrl) || opts.endpointUrl,
|
|
652
700
|
token: clean(opts.runnerToken) || clean(opts.token) || clean(process.env.PAGS_RUNNER_TOKEN),
|
|
653
701
|
placement: opts.placement === "managed" ? "managed" : "local",
|
|
654
702
|
capabilities,
|
|
655
703
|
runnerVersion: clean(opts.runnerVersion) || "",
|
|
656
|
-
runnerNode:
|
|
704
|
+
runnerNode: node,
|
|
705
|
+
machineId: machine.id,
|
|
706
|
+
machineNames: machine.names
|
|
657
707
|
};
|
|
658
708
|
}
|
|
659
709
|
async function requestRunner(method, path, opts, body) {
|
|
@@ -708,7 +758,7 @@ function responseErrorMessage(data, text, statusText) {
|
|
|
708
758
|
|
|
709
759
|
// src/commands/runner/process.ts
|
|
710
760
|
import { spawn as spawn2 } from "child_process";
|
|
711
|
-
import { existsSync as
|
|
761
|
+
import { existsSync as existsSync6 } from "fs";
|
|
712
762
|
import { resolve as resolve4 } from "path";
|
|
713
763
|
import { createServer as createServer2 } from "net";
|
|
714
764
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
@@ -745,7 +795,7 @@ function buildRunnerArgs(opts) {
|
|
|
745
795
|
function findWorkspaceRoot() {
|
|
746
796
|
let dir = process.cwd();
|
|
747
797
|
for (let i = 0; i < 8; i++) {
|
|
748
|
-
if (
|
|
798
|
+
if (existsSync6(resolve4(dir, "pnpm-workspace.yaml"))) return dir;
|
|
749
799
|
const parent = resolve4(dir, "..");
|
|
750
800
|
if (parent === dir) break;
|
|
751
801
|
dir = parent;
|
|
@@ -763,10 +813,10 @@ function runnerSpawnSpec(opts) {
|
|
|
763
813
|
let cwd = root;
|
|
764
814
|
let command = "pags-browser-runner";
|
|
765
815
|
let args = runnerArgs;
|
|
766
|
-
if (
|
|
816
|
+
if (existsSync6(localPackage)) {
|
|
767
817
|
command = "pnpm";
|
|
768
818
|
args = ["--filter", "@proagentstore/browser-runner", "dev", "--", ...runnerArgs];
|
|
769
|
-
} else if (
|
|
819
|
+
} else if (existsSync6(bundledPackage)) {
|
|
770
820
|
cwd = process.cwd();
|
|
771
821
|
command = process.execPath;
|
|
772
822
|
args = [bundledPackage, ...runnerArgs];
|
|
@@ -804,19 +854,19 @@ async function waitForLocalRunner(opts, timeoutMs = 15e3) {
|
|
|
804
854
|
}
|
|
805
855
|
|
|
806
856
|
// src/commands/runner/relay.ts
|
|
807
|
-
import { hostname as
|
|
857
|
+
import { hostname as hostname3 } from "os";
|
|
808
858
|
|
|
809
859
|
// src/commands/runner/membership.ts
|
|
810
|
-
function isEligible(inst, thisNode) {
|
|
860
|
+
function isEligible(inst, thisNode, alsoKnownAs = []) {
|
|
811
861
|
if (inst.status !== "active") return false;
|
|
812
862
|
if (inst.capabilities?.runtime == null) return false;
|
|
813
863
|
const pin = inst.config?.runnerNode;
|
|
814
|
-
if (pin && pin !== thisNode) return false;
|
|
864
|
+
if (pin && pin !== thisNode && !alsoKnownAs.includes(pin)) return false;
|
|
815
865
|
return true;
|
|
816
866
|
}
|
|
817
|
-
function diffMembership(attached, eligible, thisNode, blocked = /* @__PURE__ */ new Set()) {
|
|
867
|
+
function diffMembership(attached, eligible, thisNode, blocked = /* @__PURE__ */ new Set(), alsoKnownAs = []) {
|
|
818
868
|
const have = new Set(attached);
|
|
819
|
-
const want = eligible.filter((i) => isEligible(i, thisNode));
|
|
869
|
+
const want = eligible.filter((i) => isEligible(i, thisNode, alsoKnownAs));
|
|
820
870
|
const wantIds = new Set(want.map((i) => i.id));
|
|
821
871
|
return {
|
|
822
872
|
attach: want.filter((i) => !have.has(i.id) && !blocked.has(i.id)),
|
|
@@ -836,7 +886,8 @@ async function connectViaRelay(instanceIds, localUrl, runnerToken, opts, force =
|
|
|
836
886
|
const apiBase = pagsApiBase(opts.apiBase).replace(/^http/, "ws");
|
|
837
887
|
const pagsToken = clean(opts.pagsToken) || clean(process.env.PAGS_TOKEN) || clean(loadSession()?.token);
|
|
838
888
|
if (!pagsToken) throw new Error("PAGS token required for WebSocket relay");
|
|
839
|
-
const runnerNode =
|
|
889
|
+
const runnerNode = hostname3();
|
|
890
|
+
const machine = loadMachineIdentity(runnerNode);
|
|
840
891
|
const capabilities = await requestRunner("GET", "/capabilities", { url: localUrl, token: runnerToken, instanceId: instanceIds[0] });
|
|
841
892
|
const caps = Array.isArray(capabilities.capabilities) ? capabilities.capabilities.filter((item) => typeof item === "string") : [];
|
|
842
893
|
const registerRuntime = async (id) => {
|
|
@@ -848,6 +899,8 @@ async function connectViaRelay(instanceIds, localUrl, runnerToken, opts, force =
|
|
|
848
899
|
capabilities: caps,
|
|
849
900
|
runnerVersion: CLI_VERSION,
|
|
850
901
|
runnerNode,
|
|
902
|
+
machineId: machine.id,
|
|
903
|
+
machineNames: machine.names,
|
|
851
904
|
force
|
|
852
905
|
});
|
|
853
906
|
} catch (e) {
|
|
@@ -881,7 +934,7 @@ async function connectViaRelay(instanceIds, localUrl, runnerToken, opts, force =
|
|
|
881
934
|
writeLine("Runtime registered with PAGS \u2713");
|
|
882
935
|
writeLine("");
|
|
883
936
|
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");
|
|
884
|
-
writeLine(` \u2705 CONNECTED \u2014 WebSocket relay \xB7 ${
|
|
937
|
+
writeLine(` \u2705 CONNECTED \u2014 WebSocket relay \xB7 ${hostname3()}`);
|
|
885
938
|
writeLine(` Agents: ${instanceIds.length} instance${instanceIds.length === 1 ? "" : "s"}`);
|
|
886
939
|
writeLine(" No cloudflared needed. Ctrl+C to disconnect.");
|
|
887
940
|
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");
|
|
@@ -922,7 +975,11 @@ async function connectViaRelay(instanceIds, localUrl, runnerToken, opts, force =
|
|
|
922
975
|
attached.keys(),
|
|
923
976
|
res.instances ?? [],
|
|
924
977
|
runnerNode,
|
|
925
|
-
blocked
|
|
978
|
+
blocked,
|
|
979
|
+
// The names this machine has also worn. Without them a pin made under a
|
|
980
|
+
// previous hostname reads as "pinned to another machine", and this poll
|
|
981
|
+
// detaches the agent twenty seconds after startup attached it (#379).
|
|
982
|
+
machine.names
|
|
926
983
|
);
|
|
927
984
|
for (const inst of toAttach) {
|
|
928
985
|
await registerRuntime(inst.id);
|
|
@@ -963,7 +1020,7 @@ function openRelaySocket(instanceId, wsBase, mintToken, localUrl, runnerToken, f
|
|
|
963
1020
|
backoffMs = Math.min(backoffMs * 2, 3e4);
|
|
964
1021
|
return;
|
|
965
1022
|
}
|
|
966
|
-
const params = new URLSearchParams({ token: relayToken, node:
|
|
1023
|
+
const params = new URLSearchParams({ token: relayToken, node: hostname3() });
|
|
967
1024
|
if (force) params.set("force", "1");
|
|
968
1025
|
const url = `${wsBase}/v1/relay/${encodeURIComponent(instanceId)}/connect?${params.toString()}`;
|
|
969
1026
|
const ws = new WebSocket(url);
|
|
@@ -1069,7 +1126,7 @@ function createRunnerCommand() {
|
|
|
1069
1126
|
await startRunnerForeground(opts);
|
|
1070
1127
|
});
|
|
1071
1128
|
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").option("--watch-instances", "Attach newly eligible agents while running, without a restart").action(async (instanceIds, opts) => {
|
|
1072
|
-
const runnerToken = clean(opts.token) || clean(process.env.PAGS_RUNNER_TOKEN) || `pags_runner_${
|
|
1129
|
+
const runnerToken = clean(opts.token) || clean(process.env.PAGS_RUNNER_TOKEN) || `pags_runner_${randomUUID2()}`;
|
|
1073
1130
|
const host = clean(opts.host) || "127.0.0.1";
|
|
1074
1131
|
const port = clean(opts.port) || String(await findFreePort2(49171));
|
|
1075
1132
|
const localUrl = `http://${host}:${port}`;
|
|
@@ -1231,7 +1288,7 @@ import { Command as Command7 } from "commander";
|
|
|
1231
1288
|
|
|
1232
1289
|
// src/tui.ts
|
|
1233
1290
|
import chalk from "chalk";
|
|
1234
|
-
import { hostname as
|
|
1291
|
+
import { hostname as hostname4 } from "os";
|
|
1235
1292
|
import readline from "readline";
|
|
1236
1293
|
var ACCENT = "#7c3aed";
|
|
1237
1294
|
var c = chalk.hex(ACCENT);
|
|
@@ -1273,7 +1330,7 @@ function printStatus(state) {
|
|
|
1273
1330
|
clearScreen();
|
|
1274
1331
|
printLogo(state.version);
|
|
1275
1332
|
const connected = state.runner === "online" && state.tunnel === "online" && state.registration === "registered";
|
|
1276
|
-
console.log(pad + d("Signed in as ") + w(state.user) + d(" \xB7 agent: ") + w(state.activeInstance) + d(" \xB7 node: ") + w(
|
|
1333
|
+
console.log(pad + d("Signed in as ") + w(state.user) + d(" \xB7 agent: ") + w(state.activeInstance) + d(" \xB7 node: ") + w(hostname4()));
|
|
1277
1334
|
console.log("");
|
|
1278
1335
|
const row = (kind, s) => {
|
|
1279
1336
|
const { icon, label, note } = describe(kind, s);
|