@proagentstore/cli 0.1.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.
- package/dist/browser-runner/index.js +58 -0
- package/dist/browser-runner/runner.js +203 -0
- package/dist/browser-runner/server.js +124 -0
- package/dist/browser-runner/store.js +82 -0
- package/dist/browser-runner/test-job-server-cli.js +35 -0
- package/dist/browser-runner/test-job-server.js +226 -0
- package/dist/browser-runner/types.js +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +722 -0
- package/package.json +44 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,722 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { Command as Command5 } from "commander";
|
|
5
|
+
|
|
6
|
+
// src/commands/check.ts
|
|
7
|
+
import { existsSync, readFileSync } from "fs";
|
|
8
|
+
import { join, resolve } from "path";
|
|
9
|
+
import { Command } from "commander";
|
|
10
|
+
|
|
11
|
+
// src/output.ts
|
|
12
|
+
function writeLine(message = "") {
|
|
13
|
+
process.stdout.write(`${message}
|
|
14
|
+
`);
|
|
15
|
+
}
|
|
16
|
+
function writeError(message = "") {
|
|
17
|
+
process.stderr.write(`${message}
|
|
18
|
+
`);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// src/commands/check.ts
|
|
22
|
+
var checkCommand = new Command("check").description("Run compliance checks on an agent").option("-d, --dir <path>", "Agent directory", ".").action((opts) => {
|
|
23
|
+
const dir = resolve(opts.dir);
|
|
24
|
+
const results = [];
|
|
25
|
+
const manifestPath = join(dir, "agent.json");
|
|
26
|
+
const hasManifest = existsSync(manifestPath);
|
|
27
|
+
results.push({
|
|
28
|
+
name: "agent.json",
|
|
29
|
+
pass: hasManifest,
|
|
30
|
+
message: hasManifest ? "Found" : "Missing agent.json manifest"
|
|
31
|
+
});
|
|
32
|
+
let manifest = {};
|
|
33
|
+
if (hasManifest) {
|
|
34
|
+
try {
|
|
35
|
+
manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
|
|
36
|
+
} catch {
|
|
37
|
+
results.push({
|
|
38
|
+
name: "agent.json parse",
|
|
39
|
+
pass: false,
|
|
40
|
+
message: "Invalid JSON"
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
for (const field of ["id", "name", "storeType", "category"]) {
|
|
45
|
+
const has = !!manifest[field];
|
|
46
|
+
results.push({
|
|
47
|
+
name: `manifest.${field}`,
|
|
48
|
+
pass: has,
|
|
49
|
+
message: has ? String(manifest[field]) : `Missing ${field}`
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
const hasWrangler = existsSync(join(dir, "wrangler.toml"));
|
|
53
|
+
results.push({
|
|
54
|
+
name: "wrangler.toml",
|
|
55
|
+
pass: hasWrangler,
|
|
56
|
+
message: hasWrangler ? "Found" : "Missing"
|
|
57
|
+
});
|
|
58
|
+
const hasEntry = existsSync(join(dir, "src", "index.ts"));
|
|
59
|
+
results.push({
|
|
60
|
+
name: "src/index.ts",
|
|
61
|
+
pass: hasEntry,
|
|
62
|
+
message: hasEntry ? "Found" : "Missing entry point"
|
|
63
|
+
});
|
|
64
|
+
const hasPkg = existsSync(join(dir, "package.json"));
|
|
65
|
+
results.push({
|
|
66
|
+
name: "package.json",
|
|
67
|
+
pass: hasPkg,
|
|
68
|
+
message: hasPkg ? "Found" : "Missing"
|
|
69
|
+
});
|
|
70
|
+
const hasWorkflow = existsSync(
|
|
71
|
+
join(dir, ".github", "workflows", "deploy.yml")
|
|
72
|
+
);
|
|
73
|
+
results.push({
|
|
74
|
+
name: "deploy.yml",
|
|
75
|
+
pass: hasWorkflow,
|
|
76
|
+
message: hasWorkflow ? "Found" : "Missing deploy workflow"
|
|
77
|
+
});
|
|
78
|
+
const hasReadme = existsSync(join(dir, "README.md"));
|
|
79
|
+
results.push({
|
|
80
|
+
name: "README.md",
|
|
81
|
+
pass: hasReadme,
|
|
82
|
+
message: hasReadme ? "Found" : "Missing"
|
|
83
|
+
});
|
|
84
|
+
const wranglerContent = hasWrangler ? readFileSync(join(dir, "wrangler.toml"), "utf-8") : "";
|
|
85
|
+
const hasSecretInWrangler = /secret|password|token/i.test(wranglerContent) && /sk-|gsk_|sk-ant-/.test(wranglerContent);
|
|
86
|
+
results.push({
|
|
87
|
+
name: "no secrets",
|
|
88
|
+
pass: !hasSecretInWrangler,
|
|
89
|
+
message: hasSecretInWrangler ? "Possible secret in wrangler.toml!" : "Clean"
|
|
90
|
+
});
|
|
91
|
+
const passed = results.filter((r) => r.pass).length;
|
|
92
|
+
const total = results.length;
|
|
93
|
+
writeLine(
|
|
94
|
+
`
|
|
95
|
+
ProAgentStore compliance check: ${passed}/${total} passed
|
|
96
|
+
`
|
|
97
|
+
);
|
|
98
|
+
for (const r of results) {
|
|
99
|
+
writeLine(
|
|
100
|
+
` ${r.pass ? "\x1B[32m\u2713\x1B[0m" : "\x1B[31m\u2717\x1B[0m"} ${r.name} \u2014 ${r.message}`
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
writeLine();
|
|
104
|
+
if (passed < total) process.exit(1);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// src/commands/init.ts
|
|
108
|
+
import { cpSync, existsSync as existsSync2, mkdirSync, writeFileSync } from "fs";
|
|
109
|
+
import { dirname, join as join2, resolve as resolve2 } from "path";
|
|
110
|
+
import { fileURLToPath } from "url";
|
|
111
|
+
import { Command as Command2 } from "commander";
|
|
112
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
113
|
+
var __dirname = dirname(__filename);
|
|
114
|
+
var TEMPLATES = ["worker", "cron", "api"];
|
|
115
|
+
var TEMPLATE_DESC = {
|
|
116
|
+
worker: "Durable Object agent with memory, conversation, and tools",
|
|
117
|
+
cron: "Scheduled worker for daily digests, monitoring, batch processing",
|
|
118
|
+
api: "Stateless API endpoint for transform, generate, or analyze"
|
|
119
|
+
};
|
|
120
|
+
var initCommand = new Command2("init").description("Scaffold a new ProAgentStore agent").argument("<name>", "Agent name (lowercase, hyphens)").option(
|
|
121
|
+
"-t, --template <type>",
|
|
122
|
+
`Template: ${TEMPLATES.join(", ")}`,
|
|
123
|
+
"worker"
|
|
124
|
+
).action((name, opts) => {
|
|
125
|
+
const slug = name.toLowerCase().replace(/[^a-z0-9-]/g, "-");
|
|
126
|
+
const template = opts.template;
|
|
127
|
+
if (!TEMPLATES.includes(template)) {
|
|
128
|
+
writeError(
|
|
129
|
+
`Unknown template: ${template}. Choose: ${TEMPLATES.join(", ")}`
|
|
130
|
+
);
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
const dir = resolve2(slug);
|
|
134
|
+
if (existsSync2(dir)) {
|
|
135
|
+
writeError(`Directory ${slug} already exists`);
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|
|
138
|
+
const templateDir = resolve2(
|
|
139
|
+
__dirname,
|
|
140
|
+
"..",
|
|
141
|
+
"..",
|
|
142
|
+
"..",
|
|
143
|
+
"..",
|
|
144
|
+
"templates",
|
|
145
|
+
`template-agent-${template}`
|
|
146
|
+
);
|
|
147
|
+
const templateHasFiles = existsSync2(join2(templateDir, "agent.json"));
|
|
148
|
+
if (templateHasFiles) {
|
|
149
|
+
cpSync(templateDir, dir, { recursive: true });
|
|
150
|
+
writeLine(`Copied template: template-agent-${template}`);
|
|
151
|
+
} else {
|
|
152
|
+
mkdirSync(dir, { recursive: true });
|
|
153
|
+
mkdirSync(join2(dir, "src"));
|
|
154
|
+
mkdirSync(join2(dir, "migrations"));
|
|
155
|
+
mkdirSync(join2(dir, ".github", "workflows"), { recursive: true });
|
|
156
|
+
writeFileSync(
|
|
157
|
+
join2(dir, "agent.json"),
|
|
158
|
+
`${JSON.stringify(
|
|
159
|
+
{
|
|
160
|
+
id: slug,
|
|
161
|
+
name,
|
|
162
|
+
description: "",
|
|
163
|
+
storeType: template === "api" ? "tool" : template === "cron" ? "worker" : "agent",
|
|
164
|
+
category: "general",
|
|
165
|
+
model: "@cf/meta/llama-3.2-3b-instruct",
|
|
166
|
+
template,
|
|
167
|
+
serverConfig: {
|
|
168
|
+
durableObject: template === "worker",
|
|
169
|
+
cron: template === "cron" ? "0 8 * * *" : void 0,
|
|
170
|
+
routes: [`${slug}.proagentstore.online/*`],
|
|
171
|
+
aiBilling: "caller-provided"
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
null,
|
|
175
|
+
2
|
|
176
|
+
)}
|
|
177
|
+
`
|
|
178
|
+
);
|
|
179
|
+
writeFileSync(
|
|
180
|
+
join2(dir, "package.json"),
|
|
181
|
+
`${JSON.stringify(
|
|
182
|
+
{
|
|
183
|
+
name: `@proagentstore/${slug}`,
|
|
184
|
+
version: "0.0.1",
|
|
185
|
+
private: true,
|
|
186
|
+
type: "module",
|
|
187
|
+
scripts: {
|
|
188
|
+
dev: "wrangler dev",
|
|
189
|
+
deploy: "wrangler deploy",
|
|
190
|
+
typecheck: "tsc --noEmit"
|
|
191
|
+
},
|
|
192
|
+
dependencies: { hono: "^4.7.0" },
|
|
193
|
+
devDependencies: {
|
|
194
|
+
"@cloudflare/workers-types": "^4.20250530.0",
|
|
195
|
+
typescript: "^5.7.0",
|
|
196
|
+
wrangler: "^4.0.0"
|
|
197
|
+
}
|
|
198
|
+
},
|
|
199
|
+
null,
|
|
200
|
+
2
|
|
201
|
+
)}
|
|
202
|
+
`
|
|
203
|
+
);
|
|
204
|
+
const wranglerLines = [
|
|
205
|
+
`name = "proagentstore-${slug}"`,
|
|
206
|
+
'main = "src/index.ts"',
|
|
207
|
+
'compatibility_date = "2026-01-01"',
|
|
208
|
+
'compatibility_flags = ["nodejs_compat"]',
|
|
209
|
+
"",
|
|
210
|
+
"[[routes]]",
|
|
211
|
+
`pattern = "${slug}.proagentstore.online/*"`,
|
|
212
|
+
'zone_name = "proagentstore.online"'
|
|
213
|
+
];
|
|
214
|
+
if (template === "worker") {
|
|
215
|
+
wranglerLines.push(
|
|
216
|
+
"",
|
|
217
|
+
"[[durable_objects.bindings]]",
|
|
218
|
+
'name = "AGENT"',
|
|
219
|
+
'class_name = "AgentDO"'
|
|
220
|
+
);
|
|
221
|
+
wranglerLines.push(
|
|
222
|
+
"",
|
|
223
|
+
"[[migrations]]",
|
|
224
|
+
'tag = "v1"',
|
|
225
|
+
'new_classes = ["AgentDO"]'
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
if (template === "cron") {
|
|
229
|
+
wranglerLines.push("", "[triggers]", 'crons = ["0 8 * * *"]');
|
|
230
|
+
}
|
|
231
|
+
writeFileSync(
|
|
232
|
+
join2(dir, "wrangler.toml"),
|
|
233
|
+
`${wranglerLines.join("\n")}
|
|
234
|
+
`
|
|
235
|
+
);
|
|
236
|
+
writeFileSync(
|
|
237
|
+
join2(dir, "tsconfig.json"),
|
|
238
|
+
`${JSON.stringify(
|
|
239
|
+
{
|
|
240
|
+
compilerOptions: {
|
|
241
|
+
target: "ESNext",
|
|
242
|
+
module: "ESNext",
|
|
243
|
+
moduleResolution: "bundler",
|
|
244
|
+
lib: ["ESNext"],
|
|
245
|
+
types: ["@cloudflare/workers-types"],
|
|
246
|
+
strict: true,
|
|
247
|
+
skipLibCheck: true,
|
|
248
|
+
outDir: "dist",
|
|
249
|
+
rootDir: "src"
|
|
250
|
+
},
|
|
251
|
+
include: ["src"],
|
|
252
|
+
exclude: ["src/**/*.test.ts"]
|
|
253
|
+
},
|
|
254
|
+
null,
|
|
255
|
+
2
|
|
256
|
+
)}
|
|
257
|
+
`
|
|
258
|
+
);
|
|
259
|
+
if (template === "worker") {
|
|
260
|
+
writeFileSync(
|
|
261
|
+
join2(dir, "src", "index.ts"),
|
|
262
|
+
`import { Hono } from 'hono';
|
|
263
|
+
|
|
264
|
+
const app = new Hono();
|
|
265
|
+
|
|
266
|
+
app.get('/', (c) => c.json({ agent: '${slug}', status: 'ok' }));
|
|
267
|
+
|
|
268
|
+
export default app;
|
|
269
|
+
`
|
|
270
|
+
);
|
|
271
|
+
} else if (template === "cron") {
|
|
272
|
+
writeFileSync(
|
|
273
|
+
join2(dir, "src", "index.ts"),
|
|
274
|
+
`export default {
|
|
275
|
+
async scheduled(event: ScheduledEvent, env: unknown, ctx: ExecutionContext) {
|
|
276
|
+
// Your scheduled logic here. Access event.cron if you need the trigger expression.
|
|
277
|
+
},
|
|
278
|
+
async fetch() {
|
|
279
|
+
return new Response('${slug} cron worker', { status: 200 });
|
|
280
|
+
},
|
|
281
|
+
};
|
|
282
|
+
`
|
|
283
|
+
);
|
|
284
|
+
} else {
|
|
285
|
+
writeFileSync(
|
|
286
|
+
join2(dir, "src", "index.ts"),
|
|
287
|
+
`import { Hono } from 'hono';
|
|
288
|
+
|
|
289
|
+
const app = new Hono();
|
|
290
|
+
|
|
291
|
+
app.post('/run', async (c) => {
|
|
292
|
+
const { input } = await c.req.json();
|
|
293
|
+
// Your API logic here
|
|
294
|
+
return c.json({ result: input });
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
export default app;
|
|
298
|
+
`
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
writeFileSync(
|
|
302
|
+
join2(dir, "README.md"),
|
|
303
|
+
`# ${name}
|
|
304
|
+
|
|
305
|
+
A ProAgentStore ${template} agent.
|
|
306
|
+
|
|
307
|
+
## AI billing
|
|
308
|
+
|
|
309
|
+
This scaffold does not include a ProAgentStore Cloudflare Workers AI binding. AI calls must use caller-owned credentials or another explicit billing source.
|
|
310
|
+
|
|
311
|
+
## Development
|
|
312
|
+
|
|
313
|
+
\`\`\`bash
|
|
314
|
+
pnpm install
|
|
315
|
+
pnpm dev
|
|
316
|
+
\`\`\`
|
|
317
|
+
|
|
318
|
+
## Deploy
|
|
319
|
+
|
|
320
|
+
\`\`\`bash
|
|
321
|
+
pags publish
|
|
322
|
+
\`\`\`
|
|
323
|
+
`
|
|
324
|
+
);
|
|
325
|
+
writeFileSync(
|
|
326
|
+
join2(dir, ".github", "workflows", "deploy.yml"),
|
|
327
|
+
`name: Deploy
|
|
328
|
+
on:
|
|
329
|
+
push:
|
|
330
|
+
branches: [main]
|
|
331
|
+
jobs:
|
|
332
|
+
deploy:
|
|
333
|
+
runs-on: ubuntu-latest
|
|
334
|
+
steps:
|
|
335
|
+
- uses: actions/checkout@v4
|
|
336
|
+
- uses: pnpm/action-setup@v4
|
|
337
|
+
- uses: actions/setup-node@v4
|
|
338
|
+
with:
|
|
339
|
+
node-version: 22
|
|
340
|
+
cache: pnpm
|
|
341
|
+
- run: pnpm install --frozen-lockfile
|
|
342
|
+
- uses: cloudflare/wrangler-action@v3
|
|
343
|
+
with:
|
|
344
|
+
apiToken: \${{ secrets.CLOUDFLARE_API_TOKEN }}
|
|
345
|
+
accountId: \${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
|
346
|
+
command: deploy
|
|
347
|
+
`
|
|
348
|
+
);
|
|
349
|
+
writeFileSync(
|
|
350
|
+
join2(dir, ".gitignore"),
|
|
351
|
+
"node_modules/\ndist/\n.wrangler/\n"
|
|
352
|
+
);
|
|
353
|
+
writeFileSync(join2(dir, "LICENSE"), "MIT License\n");
|
|
354
|
+
}
|
|
355
|
+
writeLine(`
|
|
356
|
+
Created ${slug}/`);
|
|
357
|
+
writeLine(` Template: ${template} \u2014 ${TEMPLATE_DESC[template]}`);
|
|
358
|
+
writeLine(`
|
|
359
|
+
cd ${slug}`);
|
|
360
|
+
writeLine(" pnpm install");
|
|
361
|
+
writeLine(" pnpm dev");
|
|
362
|
+
writeLine();
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
// src/commands/publish.ts
|
|
366
|
+
import { execFileSync } from "child_process";
|
|
367
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
|
|
368
|
+
import { join as join3, resolve as resolve3 } from "path";
|
|
369
|
+
import { Command as Command3 } from "commander";
|
|
370
|
+
var publishCommand = new Command3("publish").description("Publish an agent to ProAgentStore").option("-d, --dir <path>", "Agent directory", ".").action(async (opts) => {
|
|
371
|
+
const dir = resolve3(opts.dir);
|
|
372
|
+
const manifestPath = join3(dir, "agent.json");
|
|
373
|
+
if (!existsSync3(manifestPath)) {
|
|
374
|
+
writeError("No agent.json found. Run `pags init` first.");
|
|
375
|
+
process.exit(1);
|
|
376
|
+
}
|
|
377
|
+
const manifest = JSON.parse(readFileSync2(manifestPath, "utf-8"));
|
|
378
|
+
const slug = manifest.id;
|
|
379
|
+
if (!slug) {
|
|
380
|
+
writeError("agent.json missing id");
|
|
381
|
+
process.exit(1);
|
|
382
|
+
}
|
|
383
|
+
writeLine(`
|
|
384
|
+
Publishing ${manifest.name} (${slug})...
|
|
385
|
+
`);
|
|
386
|
+
writeLine(" Running compliance checks...");
|
|
387
|
+
try {
|
|
388
|
+
execFileSync("pags", ["check"], { cwd: dir, stdio: "inherit" });
|
|
389
|
+
} catch {
|
|
390
|
+
writeError("\n Compliance checks failed. Fix issues and retry.\n");
|
|
391
|
+
process.exit(1);
|
|
392
|
+
}
|
|
393
|
+
const org = "ProAgentStore";
|
|
394
|
+
const repoName = slug;
|
|
395
|
+
writeLine(`
|
|
396
|
+
Checking GitHub repo: ${org}/${repoName}`);
|
|
397
|
+
let repoExists = false;
|
|
398
|
+
try {
|
|
399
|
+
execFileSync("gh", ["api", `repos/${org}/${repoName}`, "--jq", ".name"], {
|
|
400
|
+
stdio: "pipe"
|
|
401
|
+
});
|
|
402
|
+
repoExists = true;
|
|
403
|
+
writeLine(" Repo exists, pushing...");
|
|
404
|
+
} catch {
|
|
405
|
+
writeLine(" Creating repo...");
|
|
406
|
+
try {
|
|
407
|
+
execFileSync(
|
|
408
|
+
"gh",
|
|
409
|
+
["repo", "create", `${org}/${repoName}`, "--public", `--source=${dir}`, "--push"],
|
|
410
|
+
{ stdio: "inherit" }
|
|
411
|
+
);
|
|
412
|
+
repoExists = true;
|
|
413
|
+
} catch (e) {
|
|
414
|
+
writeError(` Failed to create repo: ${e}`);
|
|
415
|
+
process.exit(1);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
if (repoExists) {
|
|
419
|
+
try {
|
|
420
|
+
execFileSync("git", ["remote", "get-url", "origin"], {
|
|
421
|
+
cwd: dir,
|
|
422
|
+
stdio: "pipe"
|
|
423
|
+
});
|
|
424
|
+
} catch {
|
|
425
|
+
execFileSync(
|
|
426
|
+
"git",
|
|
427
|
+
["remote", "add", "origin", `https://github.com/${org}/${repoName}.git`],
|
|
428
|
+
{ cwd: dir }
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
try {
|
|
432
|
+
execFileSync("git", ["push", "-u", "origin", "main"], {
|
|
433
|
+
cwd: dir,
|
|
434
|
+
stdio: "inherit"
|
|
435
|
+
});
|
|
436
|
+
} catch {
|
|
437
|
+
writeLine(" Push skipped (up to date or no commits)");
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
writeLine("\n Registering agent in store...");
|
|
441
|
+
writeLine(`
|
|
442
|
+
Published! ${slug}.proagentstore.online`);
|
|
443
|
+
writeLine(` Store: https://proagentstore.online/agents/${slug}/`);
|
|
444
|
+
writeLine(` Repo: https://github.com/${org}/${repoName}`);
|
|
445
|
+
writeLine();
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
// src/commands/runner.ts
|
|
449
|
+
import { spawn } from "child_process";
|
|
450
|
+
import { existsSync as existsSync4 } from "fs";
|
|
451
|
+
import { resolve as resolve4 } from "path";
|
|
452
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
453
|
+
import { Command as Command4 } from "commander";
|
|
454
|
+
var runnerCommand = createRunnerCommand();
|
|
455
|
+
function runnerBaseUrl(url) {
|
|
456
|
+
return (clean(url) || clean(process.env.PAGS_RUNNER_URL) || "http://127.0.0.1:49171").replace(/\/$/, "");
|
|
457
|
+
}
|
|
458
|
+
function pagsApiBase(url) {
|
|
459
|
+
return (clean(url) || clean(process.env.PAGS_API_BASE) || "https://api.proagentstore.online").replace(/\/$/, "");
|
|
460
|
+
}
|
|
461
|
+
function pagsHeaders(token) {
|
|
462
|
+
const resolved = clean(token) || clean(process.env.PAGS_TOKEN);
|
|
463
|
+
return resolved ? { Authorization: `Bearer ${resolved}` } : {};
|
|
464
|
+
}
|
|
465
|
+
function runnerRequestHeaders(opts) {
|
|
466
|
+
const resolved = clean(opts.token) || clean(process.env.PAGS_RUNNER_TOKEN);
|
|
467
|
+
const headers = resolved ? { Authorization: `Bearer ${resolved}` } : {};
|
|
468
|
+
const instanceId = clean(opts.instanceId) || clean(process.env.PAGS_INSTANCE_ID);
|
|
469
|
+
if (instanceId) headers["X-PAGS-Instance-Id"] = instanceId;
|
|
470
|
+
return headers;
|
|
471
|
+
}
|
|
472
|
+
function apiPathSegment(value) {
|
|
473
|
+
return encodeURIComponent(value);
|
|
474
|
+
}
|
|
475
|
+
function buildRunnerArgs(opts) {
|
|
476
|
+
const args = [];
|
|
477
|
+
if (clean(opts.host)) args.push("--host", clean(opts.host));
|
|
478
|
+
if (clean(opts.port)) args.push("--port", clean(opts.port));
|
|
479
|
+
if (clean(opts.dataDir)) args.push("--data-dir", clean(opts.dataDir));
|
|
480
|
+
if (clean(opts.token)) args.push("--token", clean(opts.token));
|
|
481
|
+
if (clean(opts.instanceId)) args.push("--instance-id", clean(opts.instanceId));
|
|
482
|
+
if (opts.headless) args.push("--headless");
|
|
483
|
+
return args;
|
|
484
|
+
}
|
|
485
|
+
function buildRuntimeRegistrationBody(opts, capabilities = []) {
|
|
486
|
+
return {
|
|
487
|
+
endpointUrl: clean(opts.endpointUrl) || opts.endpointUrl,
|
|
488
|
+
token: clean(opts.runnerToken) || clean(opts.token) || clean(process.env.PAGS_RUNNER_TOKEN),
|
|
489
|
+
placement: opts.placement === "managed" ? "managed" : "local",
|
|
490
|
+
capabilities,
|
|
491
|
+
runnerVersion: clean(opts.runnerVersion) || ""
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
function clean(value) {
|
|
495
|
+
const trimmed = value?.trim();
|
|
496
|
+
return trimmed || void 0;
|
|
497
|
+
}
|
|
498
|
+
function findWorkspaceRoot() {
|
|
499
|
+
let dir = process.cwd();
|
|
500
|
+
for (let i = 0; i < 8; i++) {
|
|
501
|
+
if (existsSync4(resolve4(dir, "pnpm-workspace.yaml"))) return dir;
|
|
502
|
+
const parent = resolve4(dir, "..");
|
|
503
|
+
if (parent === dir) break;
|
|
504
|
+
dir = parent;
|
|
505
|
+
}
|
|
506
|
+
return process.cwd();
|
|
507
|
+
}
|
|
508
|
+
function bundledRunnerPath() {
|
|
509
|
+
return fileURLToPath2(new URL("./browser-runner/index.js", import.meta.url));
|
|
510
|
+
}
|
|
511
|
+
function startRunnerForeground(opts) {
|
|
512
|
+
const root = findWorkspaceRoot();
|
|
513
|
+
const localPackage = resolve4(root, "packages", "browser-runner", "src", "index.ts");
|
|
514
|
+
const bundledPackage = bundledRunnerPath();
|
|
515
|
+
const runnerArgs = buildRunnerArgs(opts);
|
|
516
|
+
let cwd = root;
|
|
517
|
+
let command = "pags-browser-runner";
|
|
518
|
+
let args = runnerArgs;
|
|
519
|
+
if (existsSync4(localPackage)) {
|
|
520
|
+
command = "pnpm";
|
|
521
|
+
args = ["--filter", "@proagentstore/browser-runner", "dev", "--", ...runnerArgs];
|
|
522
|
+
} else if (existsSync4(bundledPackage)) {
|
|
523
|
+
cwd = process.cwd();
|
|
524
|
+
command = process.execPath;
|
|
525
|
+
args = [bundledPackage, ...runnerArgs];
|
|
526
|
+
}
|
|
527
|
+
return new Promise((resolvePromise, reject) => {
|
|
528
|
+
const child = spawn(command, args, {
|
|
529
|
+
cwd,
|
|
530
|
+
stdio: "inherit",
|
|
531
|
+
shell: process.platform === "win32"
|
|
532
|
+
});
|
|
533
|
+
child.on("error", reject);
|
|
534
|
+
child.on("exit", (code) => {
|
|
535
|
+
if (code && code !== 0) reject(new Error(`runner exited with code ${code}`));
|
|
536
|
+
else resolvePromise();
|
|
537
|
+
});
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
async function requestRunner(method, path, opts, body) {
|
|
541
|
+
const headers = {
|
|
542
|
+
...runnerRequestHeaders(opts)
|
|
543
|
+
};
|
|
544
|
+
if (body !== void 0) headers["Content-Type"] = "application/json";
|
|
545
|
+
const res = await fetch(`${runnerBaseUrl(opts.url)}${path}`, {
|
|
546
|
+
method,
|
|
547
|
+
headers,
|
|
548
|
+
body: body === void 0 ? void 0 : JSON.stringify(body)
|
|
549
|
+
});
|
|
550
|
+
const { text, data } = await readResponse(res);
|
|
551
|
+
if (!res.ok) {
|
|
552
|
+
const message = responseErrorMessage(data, text, res.statusText);
|
|
553
|
+
throw new Error(`${res.status} ${message}`);
|
|
554
|
+
}
|
|
555
|
+
return data;
|
|
556
|
+
}
|
|
557
|
+
async function requestPags(method, path, opts, body) {
|
|
558
|
+
const headers = {
|
|
559
|
+
...pagsHeaders(opts.pagsToken)
|
|
560
|
+
};
|
|
561
|
+
if (!headers.Authorization) {
|
|
562
|
+
throw new Error("PAGS token required. Set PAGS_TOKEN or pass --pags-token.");
|
|
563
|
+
}
|
|
564
|
+
if (body !== void 0) headers["Content-Type"] = "application/json";
|
|
565
|
+
const res = await fetch(`${pagsApiBase(opts.apiBase)}${path}`, {
|
|
566
|
+
method,
|
|
567
|
+
headers,
|
|
568
|
+
body: body === void 0 ? void 0 : JSON.stringify(body)
|
|
569
|
+
});
|
|
570
|
+
const { text, data } = await readResponse(res);
|
|
571
|
+
if (!res.ok) {
|
|
572
|
+
const message = responseErrorMessage(data, text, res.statusText);
|
|
573
|
+
throw new Error(`${res.status} ${message}`);
|
|
574
|
+
}
|
|
575
|
+
return data;
|
|
576
|
+
}
|
|
577
|
+
async function readResponse(res) {
|
|
578
|
+
const text = await res.text();
|
|
579
|
+
if (!text) return { text, data: {} };
|
|
580
|
+
try {
|
|
581
|
+
return { text, data: JSON.parse(text) };
|
|
582
|
+
} catch {
|
|
583
|
+
return { text, data: {} };
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
function responseErrorMessage(data, text, statusText) {
|
|
587
|
+
return typeof data.error === "string" ? data.error : text || statusText;
|
|
588
|
+
}
|
|
589
|
+
function collectCapability(value, previous = []) {
|
|
590
|
+
return [...previous, value];
|
|
591
|
+
}
|
|
592
|
+
function createRunnerCommand() {
|
|
593
|
+
const command = new Command4("runner").description(
|
|
594
|
+
"Manage a local ProAgentStore browser runner"
|
|
595
|
+
);
|
|
596
|
+
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) => {
|
|
597
|
+
await startRunnerForeground(opts);
|
|
598
|
+
});
|
|
599
|
+
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) => {
|
|
600
|
+
const health = await requestRunner("GET", "/health", opts);
|
|
601
|
+
const capabilities = await requestRunner("GET", "/capabilities", opts);
|
|
602
|
+
writeLine(JSON.stringify({ health, capabilities }, null, 2));
|
|
603
|
+
});
|
|
604
|
+
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(
|
|
605
|
+
async (opts) => {
|
|
606
|
+
let input = {};
|
|
607
|
+
if (opts.input) {
|
|
608
|
+
try {
|
|
609
|
+
input = JSON.parse(opts.input);
|
|
610
|
+
} catch {
|
|
611
|
+
writeError("--input must be valid JSON");
|
|
612
|
+
process.exit(1);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
if (opts.jobUrl) input.url = opts.jobUrl;
|
|
616
|
+
const task = await requestRunner("POST", "/tasks", opts, {
|
|
617
|
+
type: opts.type,
|
|
618
|
+
input,
|
|
619
|
+
requiresApproval: Boolean(opts.approve),
|
|
620
|
+
approvalPrompt: opts.approvalPrompt
|
|
621
|
+
});
|
|
622
|
+
writeLine(JSON.stringify(task, null, 2));
|
|
623
|
+
}
|
|
624
|
+
);
|
|
625
|
+
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) => {
|
|
626
|
+
let capabilities = opts.capability || [];
|
|
627
|
+
if (opts.probe) {
|
|
628
|
+
const data = await requestRunner("GET", "/capabilities", {
|
|
629
|
+
url: opts.url || opts.endpointUrl,
|
|
630
|
+
token: opts.token || opts.runnerToken,
|
|
631
|
+
instanceId: opts.instanceId || instanceId
|
|
632
|
+
});
|
|
633
|
+
capabilities = Array.isArray(data.capabilities) ? data.capabilities.filter((item) => typeof item === "string") : capabilities;
|
|
634
|
+
}
|
|
635
|
+
const body = buildRuntimeRegistrationBody(opts, capabilities);
|
|
636
|
+
const result = await requestPags("POST", `/v1/instances/${apiPathSegment(instanceId)}/runtime`, opts, body);
|
|
637
|
+
writeLine(JSON.stringify(result, null, 2));
|
|
638
|
+
});
|
|
639
|
+
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) => {
|
|
640
|
+
const path = opts.probe ? `/v1/instances/${apiPathSegment(instanceId)}/runtime/status` : `/v1/instances/${apiPathSegment(instanceId)}/runtime`;
|
|
641
|
+
const result = await requestPags("GET", path, opts);
|
|
642
|
+
writeLine(JSON.stringify(result, null, 2));
|
|
643
|
+
});
|
|
644
|
+
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) => {
|
|
645
|
+
const result = await requestPags("DELETE", `/v1/instances/${apiPathSegment(instanceId)}/runtime`, opts);
|
|
646
|
+
writeLine(JSON.stringify(result, null, 2));
|
|
647
|
+
});
|
|
648
|
+
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(
|
|
649
|
+
async (instanceId, opts) => {
|
|
650
|
+
let input = {};
|
|
651
|
+
if (opts.input) {
|
|
652
|
+
try {
|
|
653
|
+
input = JSON.parse(opts.input);
|
|
654
|
+
} catch {
|
|
655
|
+
writeError("--input must be valid JSON");
|
|
656
|
+
process.exit(1);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
if (opts.jobUrl) input.url = opts.jobUrl;
|
|
660
|
+
const result = await requestPags("POST", `/v1/instances/${apiPathSegment(instanceId)}/tasks`, opts, {
|
|
661
|
+
type: opts.type,
|
|
662
|
+
input,
|
|
663
|
+
requiresApproval: Boolean(opts.approve),
|
|
664
|
+
approvalPrompt: opts.approvalPrompt
|
|
665
|
+
});
|
|
666
|
+
writeLine(JSON.stringify(result, null, 2));
|
|
667
|
+
}
|
|
668
|
+
);
|
|
669
|
+
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) => {
|
|
670
|
+
const result = await requestPags(
|
|
671
|
+
"POST",
|
|
672
|
+
`/v1/instances/${apiPathSegment(instanceId)}/tasks/${apiPathSegment(taskId)}/approve`,
|
|
673
|
+
opts
|
|
674
|
+
);
|
|
675
|
+
writeLine(JSON.stringify(result, null, 2));
|
|
676
|
+
});
|
|
677
|
+
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) => {
|
|
678
|
+
const result = await requestPags(
|
|
679
|
+
"POST",
|
|
680
|
+
`/v1/instances/${apiPathSegment(instanceId)}/tasks/${apiPathSegment(taskId)}/cancel`,
|
|
681
|
+
opts
|
|
682
|
+
);
|
|
683
|
+
writeLine(JSON.stringify(result, null, 2));
|
|
684
|
+
});
|
|
685
|
+
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) => {
|
|
686
|
+
const result = await requestPags(
|
|
687
|
+
"GET",
|
|
688
|
+
`/v1/instances/${apiPathSegment(instanceId)}/task-events?limit=${Number(opts.limit) || 50}`,
|
|
689
|
+
opts
|
|
690
|
+
);
|
|
691
|
+
writeLine(JSON.stringify(result, null, 2));
|
|
692
|
+
});
|
|
693
|
+
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) => {
|
|
694
|
+
const task = await requestRunner("POST", `/tasks/${apiPathSegment(taskId)}/approve`, opts);
|
|
695
|
+
writeLine(JSON.stringify(task, null, 2));
|
|
696
|
+
});
|
|
697
|
+
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) => {
|
|
698
|
+
const task = await requestRunner("POST", `/tasks/${apiPathSegment(taskId)}/cancel`, opts);
|
|
699
|
+
writeLine(JSON.stringify(task, null, 2));
|
|
700
|
+
});
|
|
701
|
+
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) => {
|
|
702
|
+
const events = await requestRunner("GET", `/events?limit=${Number(opts.limit) || 50}`, opts);
|
|
703
|
+
writeLine(JSON.stringify(events, null, 2));
|
|
704
|
+
});
|
|
705
|
+
return command;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
// src/index.ts
|
|
709
|
+
var program = new Command5();
|
|
710
|
+
program.name("pags").description(
|
|
711
|
+
"ProAgentStore CLI \u2014 create and publish server-powered AI agents"
|
|
712
|
+
).version("0.1.0");
|
|
713
|
+
program.addCommand(initCommand);
|
|
714
|
+
program.addCommand(checkCommand);
|
|
715
|
+
program.addCommand(publishCommand);
|
|
716
|
+
program.addCommand(runnerCommand);
|
|
717
|
+
try {
|
|
718
|
+
await program.parseAsync();
|
|
719
|
+
} catch (error) {
|
|
720
|
+
writeError(error instanceof Error ? error.message : String(error));
|
|
721
|
+
process.exit(1);
|
|
722
|
+
}
|