@proagentstore/cli 0.1.8 → 0.1.9
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/browser-runner/runner.js +253 -38
- package/dist/browser-runner/test-job-server-auth-cli.js +44 -0
- package/dist/browser-runner/test-job-server-auth.js +450 -0
- package/dist/chunk-LNY7MNUC.js +423 -0
- package/dist/index.js +204 -397
- package/dist/runner-CM72EPKQ.js +34 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,25 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
__require,
|
|
4
|
+
runnerCommand,
|
|
5
|
+
writeError,
|
|
6
|
+
writeLine
|
|
7
|
+
} from "./chunk-LNY7MNUC.js";
|
|
2
8
|
|
|
3
9
|
// src/index.ts
|
|
4
10
|
import { createRequire } from "module";
|
|
5
|
-
import { Command as
|
|
11
|
+
import { Command as Command7 } from "commander";
|
|
6
12
|
|
|
7
13
|
// src/commands/check.ts
|
|
8
14
|
import { existsSync, readFileSync } from "fs";
|
|
9
15
|
import { join, resolve } from "path";
|
|
10
16
|
import { Command } from "commander";
|
|
11
|
-
|
|
12
|
-
// src/output.ts
|
|
13
|
-
function writeLine(message = "") {
|
|
14
|
-
process.stdout.write(`${message}
|
|
15
|
-
`);
|
|
16
|
-
}
|
|
17
|
-
function writeError(message = "") {
|
|
18
|
-
process.stderr.write(`${message}
|
|
19
|
-
`);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// src/commands/check.ts
|
|
23
17
|
var checkCommand = new Command("check").description("Run compliance checks on an agent").option("-d, --dir <path>", "Agent directory", ".").action((opts) => {
|
|
24
18
|
const dir = resolve(opts.dir);
|
|
25
19
|
const results = [];
|
|
@@ -363,9 +357,143 @@ jobs:
|
|
|
363
357
|
writeLine();
|
|
364
358
|
});
|
|
365
359
|
|
|
360
|
+
// src/commands/login.ts
|
|
361
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
362
|
+
import { createServer } from "http";
|
|
363
|
+
import { homedir } from "os";
|
|
364
|
+
import { join as join3 } from "path";
|
|
365
|
+
import { Command as Command3 } from "commander";
|
|
366
|
+
var CONFIG_DIR = join3(homedir(), ".config", "proagentstore");
|
|
367
|
+
var TOKEN_FILE = join3(CONFIG_DIR, "session.json");
|
|
368
|
+
var API_BASE = "https://api.proagentstore.online";
|
|
369
|
+
var FAS_API = "https://api.freeappstore.online";
|
|
370
|
+
function loadSession() {
|
|
371
|
+
try {
|
|
372
|
+
if (!existsSync3(TOKEN_FILE)) return null;
|
|
373
|
+
const data = JSON.parse(readFileSync2(TOKEN_FILE, "utf-8"));
|
|
374
|
+
if (data.expiresAt < Date.now()) {
|
|
375
|
+
writeLine("Session expired. Run: pags login");
|
|
376
|
+
return null;
|
|
377
|
+
}
|
|
378
|
+
return data;
|
|
379
|
+
} catch {
|
|
380
|
+
return null;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
function saveSession(session) {
|
|
384
|
+
mkdirSync2(CONFIG_DIR, { recursive: true });
|
|
385
|
+
writeFileSync2(TOKEN_FILE, JSON.stringify(session, null, 2));
|
|
386
|
+
}
|
|
387
|
+
function requireSession() {
|
|
388
|
+
const session = loadSession();
|
|
389
|
+
if (!session) {
|
|
390
|
+
writeError("Not logged in. Run: pags login");
|
|
391
|
+
process.exit(1);
|
|
392
|
+
}
|
|
393
|
+
return session;
|
|
394
|
+
}
|
|
395
|
+
var loginCommand = new Command3("login").description("Sign in with Google or GitHub (opens browser)").option("--github", "Use GitHub instead of Google").action(async (opts) => {
|
|
396
|
+
const provider = opts.github ? "github" : "google";
|
|
397
|
+
writeLine(`Opening browser for ${provider} sign-in...`);
|
|
398
|
+
const port = await findFreePort();
|
|
399
|
+
const callbackUrl = `http://127.0.0.1:${port}/callback`;
|
|
400
|
+
const tokenPromise = new Promise((resolve4, reject) => {
|
|
401
|
+
const timeout = setTimeout(() => {
|
|
402
|
+
server.close();
|
|
403
|
+
reject(new Error("Login timed out (60s)"));
|
|
404
|
+
}, 6e4);
|
|
405
|
+
const server = createServer(async (req, res) => {
|
|
406
|
+
const url = new URL(req.url || "/", `http://127.0.0.1:${port}`);
|
|
407
|
+
if (url.pathname !== "/callback") {
|
|
408
|
+
res.writeHead(404);
|
|
409
|
+
res.end();
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
const fasSession = url.searchParams.get("fas_session");
|
|
413
|
+
if (!fasSession) {
|
|
414
|
+
res.writeHead(400, { "Content-Type": "text/html" });
|
|
415
|
+
res.end("<h1>Sign-in failed</h1><p>No session received.</p>");
|
|
416
|
+
clearTimeout(timeout);
|
|
417
|
+
server.close();
|
|
418
|
+
reject(new Error("No fas_session in callback"));
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
try {
|
|
422
|
+
const exchangeRes = await fetch(`${API_BASE}/v1/auth/exchange`, {
|
|
423
|
+
method: "POST",
|
|
424
|
+
headers: { "Content-Type": "application/json" },
|
|
425
|
+
body: JSON.stringify({ fas_session: fasSession })
|
|
426
|
+
});
|
|
427
|
+
const data = await exchangeRes.json();
|
|
428
|
+
if (!data.token) throw new Error(data.error || "Exchange failed");
|
|
429
|
+
saveSession({
|
|
430
|
+
token: data.token,
|
|
431
|
+
user: data.user,
|
|
432
|
+
expiresAt: Date.now() + 29 * 24 * 60 * 60 * 1e3
|
|
433
|
+
// ~29 days
|
|
434
|
+
});
|
|
435
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
436
|
+
res.end(`<h1>Signed in as ${data.user?.login}</h1><p>You can close this tab and return to the terminal.</p><script>window.close()</script>`);
|
|
437
|
+
clearTimeout(timeout);
|
|
438
|
+
server.close();
|
|
439
|
+
resolve4(data.token);
|
|
440
|
+
} catch (err) {
|
|
441
|
+
res.writeHead(500, { "Content-Type": "text/html" });
|
|
442
|
+
res.end(`<h1>Sign-in failed</h1><p>${err instanceof Error ? err.message : String(err)}</p>`);
|
|
443
|
+
clearTimeout(timeout);
|
|
444
|
+
server.close();
|
|
445
|
+
reject(err);
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
server.listen(port, "127.0.0.1");
|
|
449
|
+
});
|
|
450
|
+
const oauthBase = provider === "google" ? `${FAS_API}/v1/auth/google/start` : `${FAS_API}/v1/auth/github/start`;
|
|
451
|
+
const oauthUrl = `${oauthBase}?app_id=pags-cli&response_mode=query&return_to=${encodeURIComponent(callbackUrl)}`;
|
|
452
|
+
const open = (await import("child_process")).exec;
|
|
453
|
+
const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
454
|
+
open(`${cmd} "${oauthUrl}"`);
|
|
455
|
+
try {
|
|
456
|
+
await tokenPromise;
|
|
457
|
+
const session = loadSession();
|
|
458
|
+
writeLine(`Signed in as ${session?.user.login}. Token stored at ${TOKEN_FILE}`);
|
|
459
|
+
} catch (err) {
|
|
460
|
+
writeError(err instanceof Error ? err.message : String(err));
|
|
461
|
+
process.exit(1);
|
|
462
|
+
}
|
|
463
|
+
});
|
|
464
|
+
var logoutCommand = new Command3("logout").description("Sign out and remove stored session").action(() => {
|
|
465
|
+
try {
|
|
466
|
+
if (existsSync3(TOKEN_FILE)) {
|
|
467
|
+
const { unlinkSync } = __require("fs");
|
|
468
|
+
unlinkSync(TOKEN_FILE);
|
|
469
|
+
}
|
|
470
|
+
writeLine("Signed out.");
|
|
471
|
+
} catch {
|
|
472
|
+
writeLine("Already signed out.");
|
|
473
|
+
}
|
|
474
|
+
});
|
|
475
|
+
var whoamiCommand = new Command3("whoami").description("Show current signed-in user").action(() => {
|
|
476
|
+
const session = loadSession();
|
|
477
|
+
if (session) {
|
|
478
|
+
writeLine(`${session.user.login} (${session.user.id})`);
|
|
479
|
+
} else {
|
|
480
|
+
writeLine("Not logged in. Run: pags login");
|
|
481
|
+
}
|
|
482
|
+
});
|
|
483
|
+
async function findFreePort() {
|
|
484
|
+
return new Promise((resolve4) => {
|
|
485
|
+
const server = createServer();
|
|
486
|
+
server.listen(0, "127.0.0.1", () => {
|
|
487
|
+
const addr = server.address();
|
|
488
|
+
const port = typeof addr === "object" && addr ? addr.port : 0;
|
|
489
|
+
server.close(() => resolve4(port));
|
|
490
|
+
});
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
|
|
366
494
|
// src/commands/mcp.ts
|
|
367
495
|
import { spawn } from "child_process";
|
|
368
|
-
import { Command as
|
|
496
|
+
import { Command as Command4 } from "commander";
|
|
369
497
|
var DEFAULT_MCP_URL = "https://mcp.proagentstore.online/mcp";
|
|
370
498
|
function buildMcpRemoteArgs(opts, extraArgs = []) {
|
|
371
499
|
return ["-y", "mcp-remote", opts.url || DEFAULT_MCP_URL, ...extraArgs];
|
|
@@ -375,31 +503,31 @@ async function runMcpProxy(opts, extraArgs = []) {
|
|
|
375
503
|
stdio: "inherit",
|
|
376
504
|
env: process.env
|
|
377
505
|
});
|
|
378
|
-
await new Promise((
|
|
506
|
+
await new Promise((resolve4, reject) => {
|
|
379
507
|
child.on("error", reject);
|
|
380
508
|
child.on("close", (code) => {
|
|
381
509
|
if (code && code !== 0) reject(new Error(`mcp proxy exited with code ${code}`));
|
|
382
|
-
else
|
|
510
|
+
else resolve4();
|
|
383
511
|
});
|
|
384
512
|
});
|
|
385
513
|
}
|
|
386
|
-
var mcpCommand = new
|
|
514
|
+
var mcpCommand = new Command4("mcp").description("Run a local stdio proxy for the official ProAgentStore MCP server").option("--url <url>", "Remote MCP endpoint", DEFAULT_MCP_URL).argument("[args...]", "Extra arguments passed to mcp-remote").action(async (args, opts) => {
|
|
387
515
|
await runMcpProxy(opts, args);
|
|
388
516
|
});
|
|
389
517
|
|
|
390
518
|
// src/commands/publish.ts
|
|
391
519
|
import { execFileSync } from "child_process";
|
|
392
|
-
import { existsSync as
|
|
393
|
-
import { join as
|
|
394
|
-
import { Command as
|
|
395
|
-
var publishCommand = new
|
|
520
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
|
|
521
|
+
import { join as join4, resolve as resolve3 } from "path";
|
|
522
|
+
import { Command as Command5 } from "commander";
|
|
523
|
+
var publishCommand = new Command5("publish").description("Publish an agent to ProAgentStore").option("-d, --dir <path>", "Agent directory", ".").action(async (opts) => {
|
|
396
524
|
const dir = resolve3(opts.dir);
|
|
397
|
-
const manifestPath =
|
|
398
|
-
if (!
|
|
525
|
+
const manifestPath = join4(dir, "agent.json");
|
|
526
|
+
if (!existsSync4(manifestPath)) {
|
|
399
527
|
writeError("No agent.json found. Run `pags init` first.");
|
|
400
528
|
process.exit(1);
|
|
401
529
|
}
|
|
402
|
-
const manifest = JSON.parse(
|
|
530
|
+
const manifest = JSON.parse(readFileSync3(manifestPath, "utf-8"));
|
|
403
531
|
const slug = manifest.id;
|
|
404
532
|
if (!slug) {
|
|
405
533
|
writeError("agent.json missing id");
|
|
@@ -470,393 +598,72 @@ var publishCommand = new Command4("publish").description("Publish an agent to Pr
|
|
|
470
598
|
writeLine();
|
|
471
599
|
});
|
|
472
600
|
|
|
473
|
-
// src/commands/
|
|
474
|
-
import {
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
return (clean(url) || clean(process.env.PAGS_RUNNER_URL) || "http://127.0.0.1:49171").replace(/\/$/, "");
|
|
483
|
-
}
|
|
484
|
-
function pagsApiBase(url) {
|
|
485
|
-
return (clean(url) || clean(process.env.PAGS_API_BASE) || "https://api.proagentstore.online").replace(/\/$/, "");
|
|
486
|
-
}
|
|
487
|
-
function pagsHeaders(token) {
|
|
488
|
-
const resolved = clean(token) || clean(process.env.PAGS_TOKEN);
|
|
489
|
-
return resolved ? { Authorization: `Bearer ${resolved}` } : {};
|
|
490
|
-
}
|
|
491
|
-
function runnerRequestHeaders(opts) {
|
|
492
|
-
const resolved = clean(opts.token) || clean(process.env.PAGS_RUNNER_TOKEN);
|
|
493
|
-
const headers = resolved ? { Authorization: `Bearer ${resolved}` } : {};
|
|
494
|
-
const instanceId = clean(opts.instanceId) || clean(process.env.PAGS_INSTANCE_ID);
|
|
495
|
-
if (instanceId) headers["X-PAGS-Instance-Id"] = instanceId;
|
|
496
|
-
return headers;
|
|
497
|
-
}
|
|
498
|
-
function apiPathSegment(value) {
|
|
499
|
-
return encodeURIComponent(value);
|
|
500
|
-
}
|
|
501
|
-
function buildRunnerArgs(opts) {
|
|
502
|
-
const args = [];
|
|
503
|
-
if (clean(opts.host)) args.push("--host", clean(opts.host));
|
|
504
|
-
if (clean(opts.port)) args.push("--port", clean(opts.port));
|
|
505
|
-
if (clean(opts.dataDir)) args.push("--data-dir", clean(opts.dataDir));
|
|
506
|
-
if (clean(opts.token)) args.push("--token", clean(opts.token));
|
|
507
|
-
if (clean(opts.instanceId)) args.push("--instance-id", clean(opts.instanceId));
|
|
508
|
-
if (opts.headless) args.push("--headless");
|
|
509
|
-
return args;
|
|
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
|
-
}
|
|
517
|
-
function buildRuntimeRegistrationBody(opts, capabilities = []) {
|
|
518
|
-
return {
|
|
519
|
-
endpointUrl: clean(opts.endpointUrl) || opts.endpointUrl,
|
|
520
|
-
token: clean(opts.runnerToken) || clean(opts.token) || clean(process.env.PAGS_RUNNER_TOKEN),
|
|
521
|
-
placement: opts.placement === "managed" ? "managed" : "local",
|
|
522
|
-
capabilities,
|
|
523
|
-
runnerVersion: clean(opts.runnerVersion) || ""
|
|
524
|
-
};
|
|
525
|
-
}
|
|
526
|
-
function clean(value) {
|
|
527
|
-
const trimmed = value?.trim();
|
|
528
|
-
return trimmed || void 0;
|
|
529
|
-
}
|
|
530
|
-
function findWorkspaceRoot() {
|
|
531
|
-
let dir = process.cwd();
|
|
532
|
-
for (let i = 0; i < 8; i++) {
|
|
533
|
-
if (existsSync4(resolve4(dir, "pnpm-workspace.yaml"))) return dir;
|
|
534
|
-
const parent = resolve4(dir, "..");
|
|
535
|
-
if (parent === dir) break;
|
|
536
|
-
dir = parent;
|
|
537
|
-
}
|
|
538
|
-
return process.cwd();
|
|
539
|
-
}
|
|
540
|
-
function bundledRunnerPath() {
|
|
541
|
-
return fileURLToPath2(new URL("./browser-runner/index.js", import.meta.url));
|
|
542
|
-
}
|
|
543
|
-
function runnerSpawnSpec(opts) {
|
|
544
|
-
const root = findWorkspaceRoot();
|
|
545
|
-
const localPackage = resolve4(root, "packages", "browser-runner", "src", "index.ts");
|
|
546
|
-
const bundledPackage = bundledRunnerPath();
|
|
547
|
-
const runnerArgs = buildRunnerArgs(opts);
|
|
548
|
-
let cwd = root;
|
|
549
|
-
let command = "pags-browser-runner";
|
|
550
|
-
let args = runnerArgs;
|
|
551
|
-
if (existsSync4(localPackage)) {
|
|
552
|
-
command = "pnpm";
|
|
553
|
-
args = ["--filter", "@proagentstore/browser-runner", "dev", "--", ...runnerArgs];
|
|
554
|
-
} else if (existsSync4(bundledPackage)) {
|
|
555
|
-
cwd = process.cwd();
|
|
556
|
-
command = process.execPath;
|
|
557
|
-
args = [bundledPackage, ...runnerArgs];
|
|
558
|
-
}
|
|
559
|
-
return { command, args, cwd };
|
|
560
|
-
}
|
|
561
|
-
function startRunnerForeground(opts) {
|
|
562
|
-
const spec = runnerSpawnSpec(opts);
|
|
563
|
-
return new Promise((resolvePromise, reject) => {
|
|
564
|
-
const child = spawn2(spec.command, spec.args, {
|
|
565
|
-
cwd: spec.cwd,
|
|
566
|
-
stdio: "inherit",
|
|
567
|
-
shell: process.platform === "win32"
|
|
568
|
-
});
|
|
569
|
-
child.on("error", reject);
|
|
570
|
-
child.on("exit", (code) => {
|
|
571
|
-
if (code && code !== 0) reject(new Error(`runner exited with code ${code}`));
|
|
572
|
-
else resolvePromise();
|
|
573
|
-
});
|
|
574
|
-
});
|
|
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
|
-
}
|
|
590
|
-
async function requestRunner(method, path, opts, body) {
|
|
591
|
-
const headers = {
|
|
592
|
-
...runnerRequestHeaders(opts)
|
|
593
|
-
};
|
|
594
|
-
if (body !== void 0) headers["Content-Type"] = "application/json";
|
|
595
|
-
const res = await fetch(`${runnerBaseUrl(opts.url)}${path}`, {
|
|
596
|
-
method,
|
|
597
|
-
headers,
|
|
598
|
-
body: body === void 0 ? void 0 : JSON.stringify(body)
|
|
601
|
+
// src/commands/up.ts
|
|
602
|
+
import { Command as Command6 } from "commander";
|
|
603
|
+
var API_BASE2 = "https://api.proagentstore.online";
|
|
604
|
+
var upCommand = new Command6("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").action(async (opts) => {
|
|
605
|
+
const session = requireSession();
|
|
606
|
+
writeLine(`Signed in as ${session.user.login}`);
|
|
607
|
+
writeLine("Fetching your instances...");
|
|
608
|
+
const res = await fetch(`${API_BASE2}/v1/instances/my/instances`, {
|
|
609
|
+
headers: { Authorization: `Bearer ${session.token}` }
|
|
599
610
|
});
|
|
600
|
-
const { text, data } = await readResponse(res);
|
|
601
611
|
if (!res.ok) {
|
|
602
|
-
|
|
603
|
-
|
|
612
|
+
writeError(`Failed to fetch instances: ${res.status}`);
|
|
613
|
+
process.exit(1);
|
|
604
614
|
}
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
...pagsHeaders(opts.pagsToken)
|
|
610
|
-
};
|
|
611
|
-
if (!headers.Authorization) {
|
|
612
|
-
throw new Error("PAGS token required. Set PAGS_TOKEN or pass --pags-token.");
|
|
615
|
+
const data = await res.json();
|
|
616
|
+
let instances = (data.instances || []).filter((i) => i.status === "active");
|
|
617
|
+
if (opts.instance) {
|
|
618
|
+
instances = instances.filter((i) => i.id === opts.instance || i.slug === opts.instance);
|
|
613
619
|
}
|
|
614
|
-
if (
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
headers,
|
|
618
|
-
body: body === void 0 ? void 0 : JSON.stringify(body)
|
|
619
|
-
});
|
|
620
|
-
const { text, data } = await readResponse(res);
|
|
621
|
-
if (!res.ok) {
|
|
622
|
-
const message = responseErrorMessage(data, text, res.statusText);
|
|
623
|
-
throw new Error(`${res.status} ${message}`);
|
|
620
|
+
if (instances.length === 0) {
|
|
621
|
+
writeError("No active instances found. Subscribe to an agent first at https://proagentstore.online");
|
|
622
|
+
process.exit(1);
|
|
624
623
|
}
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
624
|
+
writeLine(`Found ${instances.length} instance${instances.length === 1 ? "" : "s"}:`);
|
|
625
|
+
for (const inst of instances) {
|
|
626
|
+
writeLine(` ${inst.name || inst.slug || inst.id.slice(0, 8)} (${inst.id.slice(0, 8)}...)`);
|
|
627
|
+
}
|
|
628
|
+
const target = instances[0];
|
|
629
|
+
writeLine(`
|
|
630
|
+
Connecting runner to: ${target.name || target.id.slice(0, 8)}...`);
|
|
631
|
+
const { runnerCommand: runnerCommand2 } = await import("./runner-CM72EPKQ.js");
|
|
632
|
+
const connectCmd = runnerCommand2.commands.find((c) => c.name() === "connect");
|
|
633
|
+
if (!connectCmd) {
|
|
634
|
+
writeError("Runner connect command not found");
|
|
635
|
+
process.exit(1);
|
|
636
|
+
}
|
|
637
|
+
const args = [
|
|
638
|
+
"node",
|
|
639
|
+
"pags",
|
|
640
|
+
"runner",
|
|
641
|
+
"connect",
|
|
642
|
+
target.id,
|
|
643
|
+
"--pags-token",
|
|
644
|
+
session.token
|
|
645
|
+
];
|
|
646
|
+
if (opts.headless) args.push("--headless");
|
|
647
|
+
process.argv = args;
|
|
630
648
|
try {
|
|
631
|
-
|
|
632
|
-
} catch {
|
|
633
|
-
|
|
649
|
+
await connectCmd.parseAsync(args.slice(2), { from: "user" });
|
|
650
|
+
} catch (err) {
|
|
651
|
+
writeError(err instanceof Error ? err.message : String(err));
|
|
652
|
+
process.exit(1);
|
|
634
653
|
}
|
|
635
|
-
}
|
|
636
|
-
function responseErrorMessage(data, text, statusText) {
|
|
637
|
-
return typeof data.error === "string" ? data.error : text || statusText;
|
|
638
|
-
}
|
|
639
|
-
function collectCapability(value, previous = []) {
|
|
640
|
-
return [...previous, value];
|
|
641
|
-
}
|
|
642
|
-
function createRunnerCommand() {
|
|
643
|
-
const command = new Command5("runner").description(
|
|
644
|
-
"Manage the local FAGS browser runtime for ProAgentStore agents"
|
|
645
|
-
);
|
|
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) => {
|
|
647
|
-
await startRunnerForeground(opts);
|
|
648
|
-
});
|
|
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) => {
|
|
745
|
-
const health = await requestRunner("GET", "/health", opts);
|
|
746
|
-
const capabilities = await requestRunner("GET", "/capabilities", opts);
|
|
747
|
-
writeLine(JSON.stringify({ health, capabilities }, null, 2));
|
|
748
|
-
});
|
|
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(
|
|
750
|
-
async (opts) => {
|
|
751
|
-
let input = {};
|
|
752
|
-
if (opts.input) {
|
|
753
|
-
try {
|
|
754
|
-
input = JSON.parse(opts.input);
|
|
755
|
-
} catch {
|
|
756
|
-
writeError("--input must be valid JSON");
|
|
757
|
-
process.exit(1);
|
|
758
|
-
}
|
|
759
|
-
}
|
|
760
|
-
if (opts.jobUrl) input.url = opts.jobUrl;
|
|
761
|
-
const task = await requestRunner("POST", "/tasks", opts, {
|
|
762
|
-
type: opts.type,
|
|
763
|
-
input,
|
|
764
|
-
requiresApproval: Boolean(opts.approve),
|
|
765
|
-
approvalPrompt: opts.approvalPrompt
|
|
766
|
-
});
|
|
767
|
-
writeLine(JSON.stringify(task, null, 2));
|
|
768
|
-
}
|
|
769
|
-
);
|
|
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) => {
|
|
771
|
-
let capabilities = opts.capability || [];
|
|
772
|
-
if (opts.probe) {
|
|
773
|
-
const data = await requestRunner("GET", "/capabilities", {
|
|
774
|
-
url: opts.url || opts.endpointUrl,
|
|
775
|
-
token: opts.token || opts.runnerToken,
|
|
776
|
-
instanceId: opts.instanceId || instanceId
|
|
777
|
-
});
|
|
778
|
-
capabilities = Array.isArray(data.capabilities) ? data.capabilities.filter((item) => typeof item === "string") : capabilities;
|
|
779
|
-
}
|
|
780
|
-
const body = buildRuntimeRegistrationBody(opts, capabilities);
|
|
781
|
-
const result = await requestPags("POST", `/v1/instances/${apiPathSegment(instanceId)}/runtime`, opts, body);
|
|
782
|
-
writeLine(JSON.stringify(result, null, 2));
|
|
783
|
-
});
|
|
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) => {
|
|
785
|
-
const path = opts.probe ? `/v1/instances/${apiPathSegment(instanceId)}/runtime/status` : `/v1/instances/${apiPathSegment(instanceId)}/runtime`;
|
|
786
|
-
const result = await requestPags("GET", path, opts);
|
|
787
|
-
writeLine(JSON.stringify(result, null, 2));
|
|
788
|
-
});
|
|
789
|
-
command.command("unregister <instanceId>").description("Remove 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").action(async (instanceId, opts) => {
|
|
790
|
-
const result = await requestPags("DELETE", `/v1/instances/${apiPathSegment(instanceId)}/runtime`, opts);
|
|
791
|
-
writeLine(JSON.stringify(result, null, 2));
|
|
792
|
-
});
|
|
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(
|
|
794
|
-
async (instanceId, opts) => {
|
|
795
|
-
let input = {};
|
|
796
|
-
if (opts.input) {
|
|
797
|
-
try {
|
|
798
|
-
input = JSON.parse(opts.input);
|
|
799
|
-
} catch {
|
|
800
|
-
writeError("--input must be valid JSON");
|
|
801
|
-
process.exit(1);
|
|
802
|
-
}
|
|
803
|
-
}
|
|
804
|
-
if (opts.jobUrl) input.url = opts.jobUrl;
|
|
805
|
-
const result = await requestPags("POST", `/v1/instances/${apiPathSegment(instanceId)}/tasks`, opts, {
|
|
806
|
-
type: opts.type,
|
|
807
|
-
input,
|
|
808
|
-
requiresApproval: Boolean(opts.approve),
|
|
809
|
-
approvalPrompt: opts.approvalPrompt
|
|
810
|
-
});
|
|
811
|
-
writeLine(JSON.stringify(result, null, 2));
|
|
812
|
-
}
|
|
813
|
-
);
|
|
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) => {
|
|
815
|
-
const result = await requestPags(
|
|
816
|
-
"POST",
|
|
817
|
-
`/v1/instances/${apiPathSegment(instanceId)}/tasks/${apiPathSegment(taskId)}/approve`,
|
|
818
|
-
opts
|
|
819
|
-
);
|
|
820
|
-
writeLine(JSON.stringify(result, null, 2));
|
|
821
|
-
});
|
|
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) => {
|
|
823
|
-
const result = await requestPags(
|
|
824
|
-
"POST",
|
|
825
|
-
`/v1/instances/${apiPathSegment(instanceId)}/tasks/${apiPathSegment(taskId)}/cancel`,
|
|
826
|
-
opts
|
|
827
|
-
);
|
|
828
|
-
writeLine(JSON.stringify(result, null, 2));
|
|
829
|
-
});
|
|
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) => {
|
|
831
|
-
const result = await requestPags(
|
|
832
|
-
"GET",
|
|
833
|
-
`/v1/instances/${apiPathSegment(instanceId)}/task-events?limit=${Number(opts.limit) || 50}`,
|
|
834
|
-
opts
|
|
835
|
-
);
|
|
836
|
-
writeLine(JSON.stringify(result, null, 2));
|
|
837
|
-
});
|
|
838
|
-
command.command("approve <taskId>").description("Approve a task waiting on human approval").option("--url <url>", "Runner URL").option("--token <token>", "Runner bearer token").option("--instance-id <id>", "PAGS instance id header").action(async (taskId, opts) => {
|
|
839
|
-
const task = await requestRunner("POST", `/tasks/${apiPathSegment(taskId)}/approve`, opts);
|
|
840
|
-
writeLine(JSON.stringify(task, null, 2));
|
|
841
|
-
});
|
|
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) => {
|
|
843
|
-
const task = await requestRunner("POST", `/tasks/${apiPathSegment(taskId)}/cancel`, opts);
|
|
844
|
-
writeLine(JSON.stringify(task, null, 2));
|
|
845
|
-
});
|
|
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) => {
|
|
847
|
-
const events = await requestRunner("GET", `/events?limit=${Number(opts.limit) || 50}`, opts);
|
|
848
|
-
writeLine(JSON.stringify(events, null, 2));
|
|
849
|
-
});
|
|
850
|
-
return command;
|
|
851
|
-
}
|
|
654
|
+
});
|
|
852
655
|
|
|
853
656
|
// src/index.ts
|
|
854
657
|
var require2 = createRequire(import.meta.url);
|
|
855
658
|
var { version } = require2("../package.json");
|
|
856
|
-
var program = new
|
|
659
|
+
var program = new Command7();
|
|
857
660
|
program.name("pags").description(
|
|
858
661
|
"ProAgentStore CLI \u2014 create and publish server-powered AI agents"
|
|
859
662
|
).version(version);
|
|
663
|
+
program.addCommand(loginCommand);
|
|
664
|
+
program.addCommand(logoutCommand);
|
|
665
|
+
program.addCommand(whoamiCommand);
|
|
666
|
+
program.addCommand(upCommand);
|
|
860
667
|
program.addCommand(initCommand);
|
|
861
668
|
program.addCommand(checkCommand);
|
|
862
669
|
program.addCommand(publishCommand);
|