@agent-play/cli 3.0.1 → 3.2.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.
Files changed (23) hide show
  1. package/README.md +76 -2
  2. package/dist/.root +1 -0
  3. package/dist/README.md +91 -0
  4. package/dist/cli.js +968 -184
  5. package/package.json +11 -5
  6. package/templates/agent-starter/langchain/.env.example +6 -0
  7. package/templates/agent-starter/langchain/README.md +24 -0
  8. package/templates/agent-starter/langchain/package.json +25 -0
  9. package/templates/agent-starter/langchain/src/bare-server.ts +12 -0
  10. package/templates/agent-starter/langchain/src/builtins/definitions.ts +66 -0
  11. package/templates/agent-starter/langchain/src/builtins/toolkits/starter-tools.ts +84 -0
  12. package/templates/agent-starter/langchain/src/express-server.ts +27 -0
  13. package/templates/agent-starter/langchain/src/index.ts +6 -0
  14. package/templates/agent-starter/langchain/src/load-env.ts +5 -0
  15. package/templates/agent-starter/langchain/src/register/register-builtins.ts +62 -0
  16. package/templates/agent-starter/langchain/src/register-agents.ts +1 -0
  17. package/templates/agent-starter/langchain/src/tool-handlers/assist-brainstorm.ts +8 -0
  18. package/templates/agent-starter/langchain/src/tool-handlers/assist-calculate-coefficient.ts +35 -0
  19. package/templates/agent-starter/langchain/src/tool-handlers/assist-collect-scene-details.ts +32 -0
  20. package/templates/agent-starter/langchain/src/tool-handlers/chat-tool.ts +6 -0
  21. package/templates/agent-starter/langchain/src/tool-handlers/execute-tool-capability.ts +12 -0
  22. package/templates/agent-starter/langchain/src/tool-handlers/tool-capability-registry.ts +49 -0
  23. package/templates/agent-starter/langchain/tsconfig.json +13 -0
package/dist/cli.js CHANGED
@@ -1,134 +1,564 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.ts
4
- import { mkdir, readFile, unlink, writeFile } from "fs/promises";
4
+ import { existsSync as existsSync2 } from "fs";
5
+ import { mkdir as mkdir2, unlink, writeFile as writeFile2 } from "fs/promises";
5
6
  import { homedir } from "os";
6
- import { join } from "path";
7
+ import { join as join2, resolve as resolve2 } from "path";
7
8
  import { createInterface } from "readline/promises";
8
9
  import { stdin as input, stdout as output } from "process";
9
- function credentialsPath() {
10
- return join(homedir(), ".agent-play", "credentials.json");
10
+ import {
11
+ createNodeCredentialFromPassw,
12
+ deriveNodeIdFromPassword,
13
+ generateNodePassw,
14
+ hashNodePassword,
15
+ loadAgentPlayCredentialsFileFromPath,
16
+ loadRootKey
17
+ } from "@agent-play/node-tools";
18
+
19
+ // src/initialize.ts
20
+ import { existsSync } from "fs";
21
+ import { mkdir, readdir, readFile, writeFile } from "fs/promises";
22
+ import { dirname, join, resolve } from "path";
23
+ import { fileURLToPath } from "url";
24
+ function resolveServerUrlForEnvironment(environment) {
25
+ if (environment === "test") {
26
+ return "https://test-agent-play.com";
27
+ }
28
+ if (environment === "production") {
29
+ return "https://agent-play.com";
30
+ }
31
+ return "http://127.0.0.1:3000";
11
32
  }
12
- async function loadCredentials() {
13
- try {
14
- const raw = await readFile(credentialsPath(), "utf8");
15
- const json = JSON.parse(raw);
16
- if (typeof json !== "object" || json === null) return null;
17
- const o = json;
18
- if (typeof o.serverUrl !== "string" || typeof o.token !== "string") {
33
+ var TEMPLATE_ROOT = fileURLToPath(
34
+ new URL("../templates/agent-starter/langchain", import.meta.url)
35
+ );
36
+ function parseInitializeArgs(argv) {
37
+ const out = {
38
+ template: "langchain",
39
+ yes: false,
40
+ force: false
41
+ };
42
+ for (let i = 0; i < argv.length; i++) {
43
+ const token = argv[i];
44
+ if (token === "--dir" && typeof argv[i + 1] === "string") {
45
+ out.dir = argv[++i];
46
+ continue;
47
+ }
48
+ if (token === "--name" && typeof argv[i + 1] === "string") {
49
+ out.name = argv[++i];
50
+ continue;
51
+ }
52
+ if (token === "--template" && typeof argv[i + 1] === "string") {
53
+ const template = argv[++i];
54
+ if (template !== "langchain") {
55
+ return null;
56
+ }
57
+ out.template = template;
58
+ continue;
59
+ }
60
+ if (token === "--yes") {
61
+ out.yes = true;
62
+ continue;
63
+ }
64
+ if (token === "--force") {
65
+ out.force = true;
66
+ continue;
67
+ }
68
+ if (token === "--bootstrap-nodes") {
69
+ out.bootstrapNodes = true;
70
+ continue;
71
+ }
72
+ if (token === "--agent-count" && typeof argv[i + 1] === "string") {
73
+ const raw = Number(argv[++i]);
74
+ if (raw !== 1 && raw !== 2) {
75
+ return null;
76
+ }
77
+ out.agentCount = raw;
78
+ continue;
79
+ }
80
+ if (token === "--environment" && typeof argv[i + 1] === "string") {
81
+ const value = argv[++i].trim().toLowerCase();
82
+ if (value === "development" || value === "test" || value === "production") {
83
+ out.environment = value;
84
+ continue;
85
+ }
86
+ return null;
87
+ }
88
+ if (token === "--server-type" && typeof argv[i + 1] === "string") {
89
+ const value = argv[++i].trim().toLowerCase();
90
+ if (value === "bare" || value === "express") {
91
+ out.serverType = value;
92
+ continue;
93
+ }
19
94
  return null;
20
95
  }
21
- return { serverUrl: o.serverUrl.replace(/\/$/, ""), token: o.token };
22
- } catch {
23
96
  return null;
24
97
  }
98
+ return out;
99
+ }
100
+ function normalizeProjectName(raw) {
101
+ if (typeof raw !== "string") {
102
+ return "agent-play-agent-starter";
103
+ }
104
+ const normalized = raw.trim().toLowerCase().replace(/[^a-z0-9-_]+/g, "-");
105
+ if (normalized.length === 0) {
106
+ return "agent-play-agent-starter";
107
+ }
108
+ return normalized;
109
+ }
110
+ async function listTemplateFiles(dir) {
111
+ const entries = await readdir(dir, { withFileTypes: true });
112
+ const files = [];
113
+ for (const entry of entries) {
114
+ const full = join(dir, entry.name);
115
+ if (entry.isDirectory()) {
116
+ const nested = await listTemplateFiles(full);
117
+ files.push(...nested.map((path) => join(entry.name, path)));
118
+ continue;
119
+ }
120
+ files.push(entry.name);
121
+ }
122
+ return files;
123
+ }
124
+ async function ensureSafeTarget(options) {
125
+ const targetExists = existsSync(options.targetDir);
126
+ if (!targetExists) {
127
+ await mkdir(options.targetDir, { recursive: true });
128
+ return;
129
+ }
130
+ if (options.force) {
131
+ return;
132
+ }
133
+ const existing = await readdir(options.targetDir);
134
+ if (existing.length > 0) {
135
+ throw new Error(
136
+ `Target directory is not empty: ${options.targetDir}. Re-run with --force to overwrite scaffold-managed files.`
137
+ );
138
+ }
139
+ }
140
+ function patchEnvContent(options) {
141
+ const lines = options.envContent.split(/\r?\n/);
142
+ const updates = /* @__PURE__ */ new Map([
143
+ ["AGENT_PLAY_WEB_UI_URL", options.serverUrl],
144
+ ["AGENT_PLAY_MAIN_NODE_ID", options.mainNodeId],
145
+ ["AGENT_PLAY_AGENT_NODE_ID_1", options.agentNodeIds[0] ?? ""],
146
+ ["AGENT_PLAY_AGENT_NODE_ID_2", options.agentNodeIds[1] ?? ""]
147
+ ]);
148
+ const seen = /* @__PURE__ */ new Set();
149
+ const next = lines.map((line) => {
150
+ const eqIndex = line.indexOf("=");
151
+ if (eqIndex <= 0) {
152
+ return line;
153
+ }
154
+ const key = line.slice(0, eqIndex);
155
+ const update = updates.get(key);
156
+ if (update === void 0) {
157
+ return line;
158
+ }
159
+ seen.add(key);
160
+ return `${key}=${update}`;
161
+ });
162
+ for (const [key, value] of updates.entries()) {
163
+ if (!seen.has(key)) {
164
+ next.push(`${key}=${value}`);
165
+ }
166
+ }
167
+ return next.join("\n");
168
+ }
169
+ async function renderTemplate(options) {
170
+ const files = await listTemplateFiles(TEMPLATE_ROOT);
171
+ for (const relativePath of files) {
172
+ const templatePath = join(TEMPLATE_ROOT, relativePath);
173
+ const targetPath = join(options.targetDir, relativePath);
174
+ if (!options.force && existsSync(targetPath)) {
175
+ continue;
176
+ }
177
+ await mkdir(dirname(targetPath), { recursive: true });
178
+ const source = await readFile(templatePath, "utf8");
179
+ const serverModule = options.serverType === "express" ? "./express-server.js" : "./bare-server.js";
180
+ const content = source.replaceAll("__PROJECT_NAME__", options.projectName).replaceAll("__AGENT_NAME__", "Starter Agent").replaceAll("__SERVER_MODULE__", serverModule);
181
+ await writeFile(targetPath, content, "utf8");
182
+ }
183
+ }
184
+ async function cmdInitialize(options) {
185
+ const parsed = parseInitializeArgs(options.argv);
186
+ if (parsed === null) {
187
+ throw new Error(
188
+ "Usage: agent-play initialize [--dir <path>] [--name <project-name>] [--template langchain] [--environment <development|test|production>] [--server-type <bare|express>] [--yes] [--force] [--bootstrap-nodes] [--agent-count <1|2>]"
189
+ );
190
+ }
191
+ const targetDir = resolve(parsed.dir ?? process.cwd());
192
+ const projectName = normalizeProjectName(parsed.name ?? basenameFromPath(targetDir));
193
+ const environment = parsed.environment ?? (parsed.yes ? "development" : await options.promptApi.askEnvironment());
194
+ const serverType = parsed.serverType ?? (parsed.yes ? "bare" : await options.promptApi.askServerType());
195
+ const serverUrl = resolveServerUrlForEnvironment(environment);
196
+ const agentCount = parsed.agentCount ?? (parsed.yes ? 1 : await options.promptApi.askAgentCount());
197
+ const bootstrapNodes = parsed.bootstrapNodes ?? (parsed.yes ? false : await options.promptApi.askBootstrapNodes());
198
+ await ensureSafeTarget({ targetDir, force: parsed.force });
199
+ await renderTemplate({
200
+ targetDir,
201
+ projectName,
202
+ serverType,
203
+ force: parsed.force
204
+ });
205
+ const envExamplePath = join(targetDir, ".env.example");
206
+ const envPath = join(targetDir, ".env");
207
+ if (!existsSync(envPath) && existsSync(envExamplePath)) {
208
+ await writeFile(envPath, await readFile(envExamplePath, "utf8"), "utf8");
209
+ }
210
+ if (bootstrapNodes) {
211
+ const bootstrapped = await options.runtimeApi.bootstrapNodeIds({
212
+ agentCount,
213
+ serverUrl
214
+ });
215
+ const envContent = existsSync(envPath) ? await readFile(envPath, "utf8") : "";
216
+ const nextEnv = patchEnvContent({
217
+ envContent,
218
+ serverUrl,
219
+ mainNodeId: bootstrapped.mainNodeId,
220
+ agentNodeIds: bootstrapped.agentNodeIds
221
+ });
222
+ await writeFile(envPath, nextEnv, "utf8");
223
+ console.log(`Bootstrapped main node id: ${bootstrapped.mainNodeId}`);
224
+ for (const [index, nodeId] of bootstrapped.agentNodeIds.entries()) {
225
+ console.log(`Bootstrapped agent node ${String(index + 1)} id: ${nodeId}`);
226
+ }
227
+ } else if (existsSync(envPath)) {
228
+ const envContent = await readFile(envPath, "utf8");
229
+ const nextEnv = patchEnvContent({
230
+ envContent,
231
+ serverUrl,
232
+ mainNodeId: "",
233
+ agentNodeIds: []
234
+ });
235
+ await writeFile(envPath, nextEnv, "utf8");
236
+ }
237
+ console.log("");
238
+ console.log("Agent starter scaffold created.");
239
+ console.log(`Location: ${targetDir}`);
240
+ console.log("Next steps:");
241
+ console.log(` cd "${targetDir}"`);
242
+ console.log(" npm install");
243
+ if (!bootstrapNodes) {
244
+ console.log(" npx agent-play create-main-node");
245
+ console.log(" npx agent-play create-agent-node");
246
+ if (agentCount === 2) {
247
+ console.log(" npx agent-play create-agent-node");
248
+ }
249
+ console.log(" copy node ids into .env");
250
+ }
251
+ console.log(" npm run dev");
252
+ }
253
+ function basenameFromPath(pathValue) {
254
+ const split = pathValue.split(/[\\/]/).filter((part) => part.length > 0);
255
+ return split[split.length - 1] ?? "agent-play-agent-starter";
256
+ }
257
+
258
+ // src/cli.ts
259
+ function nodeAuthHeaders(cred) {
260
+ return {
261
+ "x-node-id": cred.nodeId,
262
+ "x-node-passw": hashNodePassword(cred.passw)
263
+ };
264
+ }
265
+ function parseAgentRows(agentsRaw) {
266
+ if (!Array.isArray(agentsRaw)) {
267
+ return [];
268
+ }
269
+ const agents = [];
270
+ for (const a of agentsRaw) {
271
+ if (typeof a !== "object" || a === null) continue;
272
+ const o = a;
273
+ if (typeof o.agentId === "string" && typeof o.name === "string") {
274
+ agents.push({ agentId: o.agentId, name: o.name });
275
+ }
276
+ }
277
+ return agents;
278
+ }
279
+ function credentialsPath() {
280
+ return join2(homedir(), ".agent-play", "credentials.json");
281
+ }
282
+ async function loadCredentials() {
283
+ return loadAgentPlayCredentialsFileFromPath(credentialsPath());
25
284
  }
26
285
  async function saveCredentials(c) {
27
- const dir = join(homedir(), ".agent-play");
28
- await mkdir(dir, { recursive: true });
29
- await writeFile(
286
+ const dir = join2(homedir(), ".agent-play");
287
+ await mkdir2(dir, { recursive: true });
288
+ await writeFile2(
30
289
  credentialsPath(),
31
- JSON.stringify({ serverUrl: c.serverUrl, token: c.token }, null, 2),
290
+ JSON.stringify(c, null, 2),
32
291
  "utf8"
33
292
  );
34
293
  }
35
- function defaultServerUrl() {
36
- return process.env.AGENT_PLAY_SERVER_URL ?? "http://127.0.0.1:3000";
294
+ var BOOTSTRAP_ENVIRONMENTS = [
295
+ { id: "local-server", url: "http://127.0.0.1:3000" },
296
+ { id: "test-server", url: "https://test-agent-play.com" },
297
+ { id: "main-server", url: "https://agent-play.com" }
298
+ ];
299
+ function parseBootstrapEnvironmentAnswer(raw) {
300
+ const t = raw.trim().toLowerCase();
301
+ if (t === "" || t === "1") {
302
+ return BOOTSTRAP_ENVIRONMENTS[0].url;
303
+ }
304
+ if (t === "2") {
305
+ return BOOTSTRAP_ENVIRONMENTS[1].url;
306
+ }
307
+ if (t === "3") {
308
+ return BOOTSTRAP_ENVIRONMENTS[2].url;
309
+ }
310
+ for (const e of BOOTSTRAP_ENVIRONMENTS) {
311
+ if (t === e.id) {
312
+ return e.url;
313
+ }
314
+ }
315
+ if (t === "local") {
316
+ return BOOTSTRAP_ENVIRONMENTS[0].url;
317
+ }
318
+ if (t === "test") {
319
+ return BOOTSTRAP_ENVIRONMENTS[1].url;
320
+ }
321
+ if (t === "main") {
322
+ return BOOTSTRAP_ENVIRONMENTS[2].url;
323
+ }
324
+ return null;
37
325
  }
38
- async function cmdLogin() {
39
- const rl = createInterface({ input, output });
40
- const serverUrl = ((await rl.question(
41
- `Server URL [${defaultServerUrl()}]: `
42
- )).trim() || defaultServerUrl()).replace(/\/$/, "");
43
- const email = (await rl.question("Email: ")).trim();
44
- if (email.length === 0) {
45
- rl.close();
46
- console.error("Email is required.");
47
- process.exitCode = 1;
48
- return;
326
+ async function promptBootstrapEnvironment(rl) {
327
+ const lines = [
328
+ "Choose environment (sets server URL):",
329
+ ` 1) ${BOOTSTRAP_ENVIRONMENTS[0].id} \u2192 ${BOOTSTRAP_ENVIRONMENTS[0].url}`,
330
+ ` 2) ${BOOTSTRAP_ENVIRONMENTS[1].id} \u2192 ${BOOTSTRAP_ENVIRONMENTS[1].url}`,
331
+ ` 3) ${BOOTSTRAP_ENVIRONMENTS[2].id} \u2192 ${BOOTSTRAP_ENVIRONMENTS[2].url}`,
332
+ "Enter 1\u20133, or local-server / test-server / main-server [1]: "
333
+ ].join("\n");
334
+ for (; ; ) {
335
+ const answer = await rl.question(lines);
336
+ const url = parseBootstrapEnvironmentAnswer(answer);
337
+ if (url !== null) {
338
+ return url.replace(/\/$/, "");
339
+ }
340
+ console.log(
341
+ "Invalid choice. Enter 1, 2, or 3, or one of: local-server, test-server, main-server."
342
+ );
49
343
  }
50
- const lookupRes = await fetch(`${serverUrl}/api/auth/lookup`, {
51
- method: "POST",
52
- headers: { "content-type": "application/json" },
53
- body: JSON.stringify({ email })
54
- });
55
- const lookupText = await lookupRes.text();
56
- if (!lookupRes.ok) {
57
- rl.close();
58
- console.error(`Lookup failed (${lookupRes.status}): ${lookupText}`);
59
- process.exitCode = 1;
60
- return;
344
+ }
345
+ function parseInitializeBootstrapAnswer(raw) {
346
+ const trimmed = raw.trim().toLowerCase();
347
+ if (trimmed === "" || trimmed === "y" || trimmed === "yes") {
348
+ return true;
61
349
  }
62
- let lookupJson;
63
- try {
64
- lookupJson = JSON.parse(lookupText);
65
- } catch {
66
- rl.close();
67
- console.error("Invalid JSON from server.");
68
- process.exitCode = 1;
69
- return;
350
+ if (trimmed === "n" || trimmed === "no") {
351
+ return false;
70
352
  }
71
- const exists = typeof lookupJson === "object" && lookupJson !== null && lookupJson.exists === true;
72
- let token;
73
- if (exists) {
74
- const password = (await rl.question("Password: ")).trim();
75
- rl.close();
76
- const loginRes = await fetch(`${serverUrl}/api/auth/login`, {
77
- method: "POST",
78
- headers: { "content-type": "application/json" },
79
- body: JSON.stringify({ email, password })
80
- });
81
- const loginText = await loginRes.text();
82
- if (!loginRes.ok) {
83
- console.error(`Login failed (${loginRes.status}): ${loginText}`);
84
- process.exitCode = 1;
85
- return;
353
+ return null;
354
+ }
355
+ async function promptInitializeBootstrapChoice(rl) {
356
+ for (; ; ) {
357
+ const answer = await rl.question("Create node identities now? [Y/n]: ");
358
+ const parsed = parseInitializeBootstrapAnswer(answer);
359
+ if (parsed !== null) {
360
+ return parsed;
86
361
  }
87
- const loginJson = JSON.parse(loginText);
88
- if (typeof loginJson.token !== "string") {
89
- console.error("Missing token in response.");
90
- process.exitCode = 1;
91
- return;
362
+ console.log("Please answer yes or no.");
363
+ }
364
+ }
365
+ function parseInitializeAgentCount(raw) {
366
+ const trimmed = raw.trim();
367
+ if (trimmed === "" || trimmed === "1") {
368
+ return 1;
369
+ }
370
+ if (trimmed === "2") {
371
+ return 2;
372
+ }
373
+ return null;
374
+ }
375
+ async function promptInitializeAgentCount(rl) {
376
+ for (; ; ) {
377
+ const answer = await rl.question("How many agents do you want to deploy? (1-2) [1]: ");
378
+ const parsed = parseInitializeAgentCount(answer);
379
+ if (parsed !== null) {
380
+ return parsed;
92
381
  }
93
- token = loginJson.token;
94
- } else {
95
- const name = (await rl.question("Your name: ")).trim() || "User";
96
- const password = (await rl.question("Choose a password (min 8 chars): ")).trim();
97
- if (password.length < 8) {
98
- rl.close();
99
- console.error("Password must be at least 8 characters.");
100
- process.exitCode = 1;
101
- return;
382
+ console.log("Invalid value. Enter 1 or 2.");
383
+ }
384
+ }
385
+ function parseInitializeEnvironmentAnswer(raw) {
386
+ const trimmed = raw.trim().toLowerCase();
387
+ if (trimmed === "" || trimmed === "1" || trimmed === "development" || trimmed === "dev") {
388
+ return "development";
389
+ }
390
+ if (trimmed === "2" || trimmed === "test") {
391
+ return "test";
392
+ }
393
+ if (trimmed === "3" || trimmed === "production" || trimmed === "prod") {
394
+ return "production";
395
+ }
396
+ return null;
397
+ }
398
+ async function promptInitializeEnvironment(rl) {
399
+ const lines = [
400
+ "Choose environment for initialization:",
401
+ " 1) development \u2192 http://127.0.0.1:3000",
402
+ " 2) test \u2192 https://test-agent-play.com",
403
+ " 3) production \u2192 https://agent-play.com",
404
+ "Enter 1-3, or development/test/production [1]: "
405
+ ].join("\n");
406
+ for (; ; ) {
407
+ const answer = await rl.question(lines);
408
+ const parsed = parseInitializeEnvironmentAnswer(answer);
409
+ if (parsed !== null) {
410
+ return parsed;
102
411
  }
103
- rl.close();
104
- const regRes = await fetch(`${serverUrl}/api/auth/register`, {
105
- method: "POST",
106
- headers: { "content-type": "application/json" },
107
- body: JSON.stringify({ email, name, password })
108
- });
109
- const regText = await regRes.text();
110
- if (!regRes.ok) {
111
- console.error(`Sign up failed (${regRes.status}): ${regText}`);
112
- process.exitCode = 1;
113
- return;
412
+ console.log("Invalid choice. Enter 1, 2, 3, development, test, or production.");
413
+ }
414
+ }
415
+ function parseInitializeServerTypeAnswer(raw) {
416
+ const trimmed = raw.trim().toLowerCase();
417
+ if (trimmed === "" || trimmed === "1" || trimmed === "bare") {
418
+ return "bare";
419
+ }
420
+ if (trimmed === "2" || trimmed === "express") {
421
+ return "express";
422
+ }
423
+ return null;
424
+ }
425
+ async function promptInitializeServerType(rl) {
426
+ const lines = [
427
+ "Choose server runtime:",
428
+ " 1) bare \u2192 simple process entrypoint (minimal)",
429
+ " 2) express \u2192 deployable HTTP server with /health endpoint",
430
+ "Enter 1-2, or bare/express [1]: "
431
+ ].join("\n");
432
+ for (; ; ) {
433
+ const answer = await rl.question(lines);
434
+ const parsed = parseInitializeServerTypeAnswer(answer);
435
+ if (parsed !== null) {
436
+ return parsed;
114
437
  }
115
- const regJson = JSON.parse(regText);
116
- if (typeof regJson.token !== "string") {
117
- console.error("Missing token in response.");
118
- process.exitCode = 1;
119
- return;
438
+ console.log("Invalid choice. Enter 1, 2, bare, or express.");
439
+ }
440
+ }
441
+ function parseBootstrapNodeArgs(argv) {
442
+ const out = {};
443
+ for (let i = 0; i < argv.length; i++) {
444
+ const a = argv[i];
445
+ if (a === "--root-file" && typeof argv[i + 1] === "string") {
446
+ out.rootFilePath = argv[++i];
447
+ }
448
+ }
449
+ return out;
450
+ }
451
+ function parseValidateAgentNodeArgs(argv) {
452
+ let wantsAll = false;
453
+ let ids = [];
454
+ for (let i = 0; i < argv.length; i++) {
455
+ const a = argv[i];
456
+ if (a === "--all") {
457
+ wantsAll = true;
458
+ continue;
459
+ }
460
+ if (a === "--agent-node-ids" && typeof argv[i + 1] === "string") {
461
+ const raw = argv[++i].trim();
462
+ ids = raw.split(",").map((x) => x.trim()).filter((x) => x.length > 0);
463
+ continue;
464
+ }
465
+ return null;
466
+ }
467
+ if (wantsAll) {
468
+ return { mode: "all" };
469
+ }
470
+ if (ids.length > 0) {
471
+ return { mode: "ids", agentNodeIds: ids };
472
+ }
473
+ return null;
474
+ }
475
+ function resolveAgentPlayRootPath(options) {
476
+ if (typeof options.rootFilePath === "string" && options.rootFilePath.trim().length > 0) {
477
+ return resolve2(options.rootFilePath.trim());
478
+ }
479
+ const fromEnv = process.env.AGENT_PLAY_ROOT_FILE_PATH;
480
+ if (typeof fromEnv === "string" && fromEnv.trim().length > 0) {
481
+ return resolve2(fromEnv.trim());
482
+ }
483
+ const homeRoot = join2(homedir(), ".agent-play", ".root");
484
+ if (existsSync2(homeRoot)) {
485
+ return homeRoot;
486
+ }
487
+ const cwdRoot = resolve2(process.cwd(), ".root");
488
+ if (existsSync2(cwdRoot)) {
489
+ return cwdRoot;
490
+ }
491
+ throw new Error(
492
+ "Agent Play root key not found. Pass --root-file <path>, set AGENT_PLAY_ROOT_FILE_PATH, or place .root in ~/.agent-play/ or the project directory."
493
+ );
494
+ }
495
+ async function registerNodeOnServer(serverUrl, passw, expectedNodeId) {
496
+ const res = await fetch(`${serverUrl}/api/nodes`, {
497
+ method: "POST",
498
+ headers: { "content-type": "application/json" },
499
+ body: JSON.stringify({ kind: "main", passw })
500
+ });
501
+ const text = await res.text();
502
+ if (res.status === 409) {
503
+ return;
504
+ }
505
+ if (!res.ok) {
506
+ let msg = text;
507
+ try {
508
+ const err = JSON.parse(text);
509
+ if (typeof err.error === "string") {
510
+ msg = err.error;
511
+ }
512
+ } catch {
120
513
  }
121
- token = regJson.token;
514
+ throw new Error(
515
+ `Node registration failed (${String(res.status)}): ${msg}`
516
+ );
517
+ }
518
+ const json = JSON.parse(text);
519
+ if (typeof json.nodeId !== "string") {
520
+ throw new Error("Invalid response from server.");
521
+ }
522
+ if (json.nodeId !== expectedNodeId) {
523
+ console.log("json", json);
524
+ console.log("expectedNodeId", expectedNodeId);
525
+ throw new Error(
526
+ "Server node id does not match local derivation; check root file and server configuration."
527
+ );
122
528
  }
123
- await saveCredentials({ serverUrl, token });
124
- console.log(`Signed in. Credentials saved to ${credentialsPath()}`);
125
529
  }
126
- async function cmdLogout() {
530
+ async function cmdBootstrapNode(argv) {
531
+ const opts = parseBootstrapNodeArgs(argv);
532
+ const rl = createInterface({ input, output });
533
+ const serverUrl = await promptBootstrapEnvironment(rl);
534
+ rl.close();
535
+ console.log(`Using server: ${serverUrl}`);
536
+ const rootPath = resolveAgentPlayRootPath(opts);
537
+ const rootKey = loadRootKey(rootPath);
538
+ const dir = join2(homedir(), ".agent-play");
539
+ await mkdir2(dir, { recursive: true });
540
+ const generatedPassw = generateNodePassw();
541
+ const hashedPassw = hashNodePassword(generatedPassw);
542
+ const credential = createNodeCredentialFromPassw({ passw: hashedPassw, rootKey });
543
+ await registerNodeOnServer(serverUrl, hashedPassw, credential.nodeId);
544
+ await saveCredentials({
545
+ serverUrl,
546
+ nodeId: credential.nodeId,
547
+ passw: generatedPassw
548
+ });
549
+ console.log(
550
+ `genesisNodeId (platform root key from .root; all main nodes derive under this): ${rootKey}`
551
+ );
552
+ console.log(`mainNodeId (your developer node): ${credential.nodeId}`);
553
+ console.log(`passw: ${generatedPassw}`);
554
+ console.log("Keep this material safe. Losing it means losing access.");
555
+ }
556
+ async function cmdClearNodeCredentials() {
127
557
  try {
128
558
  await unlink(credentialsPath());
129
- console.log("Logged out.");
559
+ console.log("Credentials removed.");
130
560
  } catch {
131
- console.log("No saved session.");
561
+ console.log("No saved credentials.");
132
562
  }
133
563
  }
134
564
  function printAgentPlayIntegrationGuide() {
@@ -136,7 +566,7 @@ function printAgentPlayIntegrationGuide() {
136
566
  console.log("How your agent appears on the play world");
137
567
  console.log("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
138
568
  console.log(
139
- " \u2022 One account API key: run `agent-play create-key` (after login) if you do not have one yet."
569
+ " \u2022 Use ~/.agent-play/credentials.json + .root with RemotePlayWorld({ nodeCredentials })."
140
570
  );
141
571
  console.log(
142
572
  " \u2022 LangChain: use langchainRegistration(agent) and pass agent.toolNames to addPlayer."
@@ -148,22 +578,38 @@ function printAgentPlayIntegrationGuide() {
148
578
  " \u2022 Structures on the map are derived from those tool names \u2014 keep them aligned with your real tools."
149
579
  );
150
580
  console.log(
151
- " \u2022 RemotePlayWorld({ apiKey: <account key> }) and addPlayer({ ..., agentId: <id below> })."
581
+ " \u2022 RemotePlayWorld({ nodeCredentials: { rootKey, passw } }) and addAgent({ nodeId, ... })."
152
582
  );
153
583
  console.log("");
154
584
  }
155
- async function cmdCreateKey() {
585
+ async function cmdCreateAgentNode() {
156
586
  const cred = await loadCredentials();
157
587
  if (cred === null) {
158
- console.error("Run `agent-play login` first.");
588
+ console.error(
589
+ "Run `agent-play create-main-node` (or `bootstrap-node`) first."
590
+ );
159
591
  process.exitCode = 1;
160
592
  return;
161
593
  }
162
- const res = await fetch(`${cred.serverUrl}/api/agents/api-key`, {
594
+ const rootKey = loadRootKey(resolveAgentPlayRootPath({}));
595
+ const agentPassw = generateNodePassw();
596
+ const hashedAgentPassw = hashNodePassword(agentPassw);
597
+ const agentNodeId = deriveNodeIdFromPassword({
598
+ password: hashedAgentPassw,
599
+ rootKey
600
+ });
601
+ const res = await fetch(`${cred.serverUrl}/api/nodes/agent-node`, {
163
602
  method: "POST",
164
603
  headers: {
165
- authorization: `Bearer ${cred.token}`
166
- }
604
+ "content-type": "application/json",
605
+ ...nodeAuthHeaders(cred)
606
+ },
607
+ body: JSON.stringify({
608
+ kind: "agent",
609
+ parentNodeId: cred.nodeId,
610
+ agentNodeId,
611
+ agentNodePassw: hashedAgentPassw
612
+ })
167
613
  });
168
614
  const text = await res.text();
169
615
  if (!res.ok) {
@@ -173,65 +619,116 @@ async function cmdCreateKey() {
173
619
  if (typeof err.error === "string") msg = err.error;
174
620
  } catch {
175
621
  }
176
- console.error(`create-key failed (${res.status}): ${msg}`);
622
+ console.error(`Create failed (${res.status}): ${msg}`);
177
623
  process.exitCode = 1;
178
624
  return;
179
625
  }
180
626
  const json = JSON.parse(text);
181
- if (typeof json.plainApiKey !== "string") {
627
+ if (typeof json.agentId !== "string") {
182
628
  console.error("Invalid response from server.");
183
629
  process.exitCode = 1;
184
630
  return;
185
631
  }
186
- console.log("API key (store securely; shown once):");
187
- console.log(json.plainApiKey);
188
- console.log("");
189
- }
190
- async function cmdViewKeys() {
191
- const cred = await loadCredentials();
192
- if (cred === null) {
193
- console.error("Run `agent-play login` first.");
632
+ if (json.agentId !== agentNodeId) {
633
+ console.error(
634
+ "Server returned a different agent node id than the locally derived one."
635
+ );
194
636
  process.exitCode = 1;
195
637
  return;
196
638
  }
197
- const res = await fetch(`${cred.serverUrl}/api/agents/api-key`, {
198
- headers: { authorization: `Bearer ${cred.token}` }
639
+ const nextAgentNodes = [
640
+ ...(cred.agentNodes ?? []).filter((n) => n.nodeId !== agentNodeId),
641
+ {
642
+ nodeId: agentNodeId,
643
+ passw: agentPassw,
644
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
645
+ }
646
+ ];
647
+ await saveCredentials({
648
+ ...cred,
649
+ agentNodes: nextAgentNodes
650
+ });
651
+ printAgentPlayIntegrationGuide();
652
+ console.log(`Created agent node id: ${json.agentId}`);
653
+ console.log(`Agent node passw: ${agentPassw}`);
654
+ console.log(
655
+ `Saved agent node credentials to ${credentialsPath()} (agentNodes).`
656
+ );
657
+ console.log("Keep this material safe. Losing it means losing access.");
658
+ console.log("");
659
+ }
660
+ async function ensureMainCredentialsForInitialize(serverUrl) {
661
+ const existing = await loadCredentials();
662
+ if (existing !== null && existing.serverUrl.replace(/\/$/, "") === serverUrl.replace(/\/$/, "")) {
663
+ return existing;
664
+ }
665
+ const rootKey = loadRootKey(resolveAgentPlayRootPath({}));
666
+ const generatedPassw = generateNodePassw();
667
+ const hashedPassw = hashNodePassword(generatedPassw);
668
+ const credential = createNodeCredentialFromPassw({ passw: hashedPassw, rootKey });
669
+ await registerNodeOnServer(serverUrl, hashedPassw, credential.nodeId);
670
+ const created = {
671
+ serverUrl,
672
+ nodeId: credential.nodeId,
673
+ passw: generatedPassw
674
+ };
675
+ await saveCredentials(created);
676
+ return created;
677
+ }
678
+ async function createAgentNodeForInitialize(cred) {
679
+ const rootKey = loadRootKey(resolveAgentPlayRootPath({}));
680
+ const agentPassw = generateNodePassw();
681
+ const hashedAgentPassw = hashNodePassword(agentPassw);
682
+ const agentNodeId = deriveNodeIdFromPassword({
683
+ password: hashedAgentPassw,
684
+ rootKey
685
+ });
686
+ const res = await fetch(`${cred.serverUrl}/api/nodes/agent-node`, {
687
+ method: "POST",
688
+ headers: {
689
+ "content-type": "application/json",
690
+ ...nodeAuthHeaders(cred)
691
+ },
692
+ body: JSON.stringify({
693
+ kind: "agent",
694
+ parentNodeId: cred.nodeId,
695
+ agentNodeId,
696
+ agentNodePassw: hashedAgentPassw
697
+ })
199
698
  });
200
699
  const text = await res.text();
201
700
  if (!res.ok) {
202
- console.error(`view-keys failed (${res.status}): ${text}`);
203
- process.exitCode = 1;
204
- return;
701
+ throw new Error(`Create failed (${String(res.status)}): ${text}`);
205
702
  }
206
703
  const json = JSON.parse(text);
207
- if (json.hasKey === true) {
208
- const when = typeof json.createdAt === "string" ? json.createdAt : "unknown time";
209
- console.log(`Account API key: active (created ${when}).`);
210
- console.log(
211
- "The secret value cannot be shown again. Use the key you saved when you ran `agent-play create-key`."
212
- );
213
- } else {
214
- console.log("No API key for this account.");
215
- console.log("Run `agent-play create-key` to generate one (shown once).");
704
+ if (typeof json.agentId !== "string" || json.agentId !== agentNodeId) {
705
+ throw new Error("Invalid agent creation response.");
216
706
  }
707
+ const nextAgentNodes = [
708
+ ...(cred.agentNodes ?? []).filter((n) => n.nodeId !== agentNodeId),
709
+ {
710
+ nodeId: agentNodeId,
711
+ passw: agentPassw,
712
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
713
+ }
714
+ ];
715
+ await saveCredentials({
716
+ ...cred,
717
+ agentNodes: nextAgentNodes
718
+ });
719
+ return agentNodeId;
217
720
  }
218
- async function cmdCreate() {
721
+ async function cmdInspectNode() {
219
722
  const cred = await loadCredentials();
220
723
  if (cred === null) {
221
- console.error("Run `agent-play login` first.");
724
+ console.error(
725
+ "Run `agent-play create-main-node` (or `bootstrap-node`) first."
726
+ );
222
727
  process.exitCode = 1;
223
728
  return;
224
729
  }
225
- const rl = createInterface({ input, output });
226
- const name = (await rl.question("Agent name: ")).trim() || "agent";
227
- rl.close();
228
- const res = await fetch(`${cred.serverUrl}/api/agents`, {
229
- method: "POST",
230
- headers: {
231
- "content-type": "application/json",
232
- authorization: `Bearer ${cred.token}`
233
- },
234
- body: JSON.stringify({ name })
730
+ const res = await fetch(`${cred.serverUrl}/api/nodes`, {
731
+ headers: nodeAuthHeaders(cred)
235
732
  });
236
733
  const text = await res.text();
237
734
  if (!res.ok) {
@@ -241,29 +738,71 @@ async function cmdCreate() {
241
738
  if (typeof err.error === "string") msg = err.error;
242
739
  } catch {
243
740
  }
244
- console.error(`Create failed (${res.status}): ${msg}`);
741
+ console.error(`Inspect failed (${res.status}): ${msg}`);
245
742
  process.exitCode = 1;
246
743
  return;
247
744
  }
248
745
  const json = JSON.parse(text);
249
- if (typeof json.agentId !== "string") {
250
- console.error("Invalid response from server.");
746
+ if (typeof json.genesisNodeId !== "string") {
747
+ console.error("Invalid inspect response.");
251
748
  process.exitCode = 1;
252
749
  return;
253
750
  }
254
- printAgentPlayIntegrationGuide();
255
- console.log(`Created agent id: ${json.agentId}`);
751
+ const main2 = json.mainNode;
752
+ if (typeof main2 !== "object" || main2 === null) {
753
+ console.error("Invalid inspect response.");
754
+ process.exitCode = 1;
755
+ return;
756
+ }
757
+ const mn = main2;
758
+ if (typeof mn.nodeId !== "string" || typeof mn.createdAt !== "string") {
759
+ console.error("Invalid inspect response.");
760
+ process.exitCode = 1;
761
+ return;
762
+ }
763
+ const agentNodeIdsFromMain = Array.isArray(mn.agentNodeIds) ? mn.agentNodeIds.filter((x) => typeof x === "string") : [];
764
+ const runtimeAgents = parseAgentRows(json.agentNodes);
765
+ console.log("Platform genesis node id (from server .root / root key):");
766
+ console.log(` ${json.genesisNodeId}`);
767
+ console.log("");
768
+ console.log("Your main developer node:");
769
+ console.log(` nodeId: ${mn.nodeId}`);
770
+ console.log(` createdAt: ${mn.createdAt}`);
771
+ console.log("");
772
+ console.log(
773
+ `Agent node identities (create-agent-node) (${String(agentNodeIdsFromMain.length)}):`
774
+ );
775
+ if (agentNodeIdsFromMain.length === 0) {
776
+ console.log(" (none)");
777
+ } else {
778
+ agentNodeIdsFromMain.forEach((id, i) => {
779
+ console.log(` ${String(i + 1)}. ${id}`);
780
+ });
781
+ }
782
+ console.log("");
783
+ console.log(
784
+ `Runtime agents \u2014 SDK metadata (${String(runtimeAgents.length)}):`
785
+ );
786
+ if (runtimeAgents.length === 0) {
787
+ console.log(" (none)");
788
+ } else {
789
+ runtimeAgents.forEach((a, i) => {
790
+ console.log(` ${String(i + 1)}. ${a.agentId} \u2014 ${a.name}`);
791
+ });
792
+ }
256
793
  console.log("");
257
794
  }
258
- async function cmdDelete() {
795
+ async function cmdListAgentNodes() {
259
796
  const cred = await loadCredentials();
260
797
  if (cred === null) {
261
- console.error("Run `agent-play login` first.");
798
+ console.error(
799
+ "Run `agent-play create-main-node` (or `bootstrap-node`) first."
800
+ );
262
801
  process.exitCode = 1;
263
802
  return;
264
803
  }
265
804
  const listRes = await fetch(`${cred.serverUrl}/api/agents`, {
266
- headers: { authorization: `Bearer ${cred.token}` }
805
+ headers: nodeAuthHeaders(cred)
267
806
  });
268
807
  const listText = await listRes.text();
269
808
  if (!listRes.ok) {
@@ -272,30 +811,48 @@ async function cmdDelete() {
272
811
  return;
273
812
  }
274
813
  const listJson = JSON.parse(listText);
275
- const agentsRaw = listJson.agents;
276
- if (!Array.isArray(agentsRaw)) {
277
- console.error("Invalid list response.");
278
- process.exitCode = 1;
279
- return;
280
- }
281
- const agents = [];
282
- for (const a of agentsRaw) {
283
- if (typeof a !== "object" || a === null) continue;
284
- const o = a;
285
- if (typeof o.agentId === "string" && typeof o.name === "string") {
286
- agents.push({ agentId: o.agentId, name: o.name });
287
- }
288
- }
814
+ const agents = parseAgentRows(listJson.agents);
289
815
  if (agents.length === 0) {
290
- console.log("No agents.");
816
+ console.log("No agent nodes.");
291
817
  return;
292
818
  }
293
819
  agents.forEach((a, i) => {
294
- console.log(`${i + 1}. ${a.agentId} (${a.name})`);
820
+ console.log(`${String(i + 1)}. ${a.agentId} (${a.name})`);
295
821
  });
296
- const rl = createInterface({ input, output });
297
- const pick = (await rl.question("Agent id to delete (empty = cancel): ")).trim();
298
- rl.close();
822
+ }
823
+ async function cmdDeleteAgentNode(argv) {
824
+ const cred = await loadCredentials();
825
+ if (cred === null) {
826
+ console.error(
827
+ "Run `agent-play create-main-node` (or `bootstrap-node`) first."
828
+ );
829
+ process.exitCode = 1;
830
+ return;
831
+ }
832
+ let pick = argv[0]?.trim() ?? "";
833
+ if (pick.length === 0) {
834
+ const listRes = await fetch(`${cred.serverUrl}/api/agents`, {
835
+ headers: nodeAuthHeaders(cred)
836
+ });
837
+ const listText = await listRes.text();
838
+ if (!listRes.ok) {
839
+ console.error(`List failed (${listRes.status}): ${listText}`);
840
+ process.exitCode = 1;
841
+ return;
842
+ }
843
+ const listJson = JSON.parse(listText);
844
+ const agents = parseAgentRows(listJson.agents);
845
+ if (agents.length === 0) {
846
+ console.log("No agents.");
847
+ return;
848
+ }
849
+ agents.forEach((a, i) => {
850
+ console.log(`${i + 1}. ${a.agentId} (${a.name})`);
851
+ });
852
+ const rl = createInterface({ input, output });
853
+ pick = (await rl.question("Agent id to delete (empty = cancel): ")).trim();
854
+ rl.close();
855
+ }
299
856
  if (pick.length === 0) {
300
857
  console.log("Cancelled.");
301
858
  return;
@@ -304,7 +861,7 @@ async function cmdDelete() {
304
861
  `${cred.serverUrl}/api/agents?id=${encodeURIComponent(pick)}`,
305
862
  {
306
863
  method: "DELETE",
307
- headers: { authorization: `Bearer ${cred.token}` }
864
+ headers: nodeAuthHeaders(cred)
308
865
  }
309
866
  );
310
867
  const delText = await delRes.text();
@@ -316,34 +873,261 @@ async function cmdDelete() {
316
873
  const delJson = JSON.parse(delText);
317
874
  console.log(delJson.ok === true ? "Deleted." : "Not found.");
318
875
  }
876
+ async function cmdDeleteMainNode() {
877
+ const cred = await loadCredentials();
878
+ if (cred === null) {
879
+ console.error(
880
+ "Run `agent-play create-main-node` (or `bootstrap-node`) first."
881
+ );
882
+ process.exitCode = 1;
883
+ return;
884
+ }
885
+ console.error("");
886
+ console.error("WARNING: You are about to delete your main developer node.");
887
+ console.error(
888
+ "The server will remove this node and cascade-delete every registered"
889
+ );
890
+ console.error(
891
+ "agent node (SDK agent registration) that belongs to it. This cannot be undone."
892
+ );
893
+ console.error(
894
+ "You will need a new passphrase and secret file to join the platform again."
895
+ );
896
+ console.error("");
897
+ const rl = createInterface({ input, output });
898
+ const typed = (await rl.question(
899
+ `Type your main node id exactly to confirm (${cred.nodeId}): `
900
+ )).trim();
901
+ rl.close();
902
+ if (typed !== cred.nodeId) {
903
+ console.log("Confirmation did not match. Cancelled.");
904
+ return;
905
+ }
906
+ const res = await fetch(`${cred.serverUrl}/api/nodes`, {
907
+ method: "DELETE",
908
+ headers: {
909
+ "content-type": "application/json",
910
+ ...nodeAuthHeaders(cred)
911
+ },
912
+ body: JSON.stringify({ confirmNodeId: cred.nodeId })
913
+ });
914
+ const text = await res.text();
915
+ if (!res.ok) {
916
+ let msg = text;
917
+ try {
918
+ const err = JSON.parse(text);
919
+ if (typeof err.error === "string") msg = err.error;
920
+ } catch {
921
+ }
922
+ console.error(`Delete main node failed (${res.status}): ${msg}`);
923
+ process.exitCode = 1;
924
+ return;
925
+ }
926
+ const json = JSON.parse(text);
927
+ if (json.ok !== true) {
928
+ console.error("Unexpected response from server.");
929
+ process.exitCode = 1;
930
+ return;
931
+ }
932
+ const n = typeof json.deletedAgentCount === "number" ? json.deletedAgentCount : 0;
933
+ console.log(
934
+ `Main node removed. Cascaded agent nodes deleted: ${String(n)}.`
935
+ );
936
+ console.log("Run `agent-play clear-node-credentials` to forget local creds.");
937
+ }
938
+ async function validateNodeIdentityOnServer(options) {
939
+ const res = await fetch(`${options.cred.serverUrl}/api/nodes/validate`, {
940
+ method: "POST",
941
+ headers: {
942
+ "content-type": "application/json",
943
+ ...nodeAuthHeaders(options.cred)
944
+ },
945
+ body: JSON.stringify({
946
+ nodeId: options.nodeId,
947
+ rootKey: options.rootKey,
948
+ mainNodeId: options.mainNodeId
949
+ })
950
+ });
951
+ const text = await res.text();
952
+ let json;
953
+ try {
954
+ json = JSON.parse(text);
955
+ } catch {
956
+ throw new Error(`Validate failed (${String(res.status)}): ${text}`);
957
+ }
958
+ if (typeof json !== "object" || json === null) {
959
+ throw new Error(`Validate failed (${String(res.status)}): invalid response`);
960
+ }
961
+ const obj = json;
962
+ if (typeof obj.ok !== "boolean") {
963
+ const err = typeof obj.error === "string" ? obj.error : text;
964
+ throw new Error(`Validate failed (${String(res.status)}): ${err}`);
965
+ }
966
+ return {
967
+ ok: obj.ok,
968
+ reason: typeof obj.reason === "string" ? obj.reason : void 0,
969
+ nodeKind: typeof obj.nodeKind === "string" ? obj.nodeKind : void 0
970
+ };
971
+ }
972
+ async function cmdValidateMainNode() {
973
+ const cred = await loadCredentials();
974
+ if (cred === null) {
975
+ console.error(
976
+ "Run `agent-play create-main-node` (or `bootstrap-node`) first."
977
+ );
978
+ process.exitCode = 1;
979
+ return;
980
+ }
981
+ const rootKey = loadRootKey(resolveAgentPlayRootPath({}));
982
+ const result = await validateNodeIdentityOnServer({
983
+ cred,
984
+ rootKey,
985
+ nodeId: cred.nodeId
986
+ });
987
+ if (!result.ok) {
988
+ console.error(
989
+ `Main node validation failed: ${result.reason ?? "unknown reason"}`
990
+ );
991
+ process.exitCode = 1;
992
+ return;
993
+ }
994
+ console.log(`Main node validation passed: ${cred.nodeId}`);
995
+ }
996
+ async function cmdValidateAgentNode(argv) {
997
+ const cred = await loadCredentials();
998
+ if (cred === null) {
999
+ console.error(
1000
+ "Run `agent-play create-main-node` (or `bootstrap-node`) first."
1001
+ );
1002
+ process.exitCode = 1;
1003
+ return;
1004
+ }
1005
+ const opts = parseValidateAgentNodeArgs(argv);
1006
+ if (opts === null) {
1007
+ console.error(
1008
+ "Usage: agent-play validate-agent-node --all | --agent-node-ids <id1,id2,...>"
1009
+ );
1010
+ process.exitCode = 1;
1011
+ return;
1012
+ }
1013
+ const rootKey = loadRootKey(resolveAgentPlayRootPath({}));
1014
+ const targetIds = opts.mode === "all" ? (cred.agentNodes ?? []).map((n) => n.nodeId) : opts.agentNodeIds;
1015
+ const dedupedIds = Array.from(new Set(targetIds.filter((id) => id.length > 0)));
1016
+ if (dedupedIds.length === 0) {
1017
+ console.log("No agent node ids to validate.");
1018
+ return;
1019
+ }
1020
+ let failures = 0;
1021
+ for (const nodeId of dedupedIds) {
1022
+ const result = await validateNodeIdentityOnServer({
1023
+ cred,
1024
+ rootKey,
1025
+ nodeId,
1026
+ mainNodeId: cred.nodeId
1027
+ });
1028
+ if (!result.ok) {
1029
+ failures += 1;
1030
+ console.error(
1031
+ `FAIL ${nodeId}: ${result.reason ?? "unknown reason"}`
1032
+ );
1033
+ continue;
1034
+ }
1035
+ console.log(`PASS ${nodeId}`);
1036
+ }
1037
+ if (failures > 0) {
1038
+ process.exitCode = 1;
1039
+ return;
1040
+ }
1041
+ console.log(`Validated ${String(dedupedIds.length)} agent node(s) successfully.`);
1042
+ }
1043
+ async function cmdInitialize2(argv) {
1044
+ const rl = createInterface({ input, output });
1045
+ try {
1046
+ await cmdInitialize({
1047
+ argv,
1048
+ promptApi: {
1049
+ askEnvironment: () => promptInitializeEnvironment(rl),
1050
+ askServerType: () => promptInitializeServerType(rl),
1051
+ askBootstrapNodes: () => promptInitializeBootstrapChoice(rl),
1052
+ askAgentCount: () => promptInitializeAgentCount(rl)
1053
+ },
1054
+ runtimeApi: {
1055
+ bootstrapNodeIds: async (options) => {
1056
+ const mainCred = await ensureMainCredentialsForInitialize(options.serverUrl);
1057
+ const agentNodeIds = [];
1058
+ for (let i = 0; i < options.agentCount; i++) {
1059
+ const refreshedCred = await loadCredentials() ?? mainCred;
1060
+ const nodeId = await createAgentNodeForInitialize(refreshedCred);
1061
+ agentNodeIds.push(nodeId);
1062
+ }
1063
+ return {
1064
+ mainNodeId: mainCred.nodeId,
1065
+ agentNodeIds
1066
+ };
1067
+ }
1068
+ }
1069
+ });
1070
+ } finally {
1071
+ rl.close();
1072
+ }
1073
+ }
319
1074
  async function main() {
320
1075
  const cmd = process.argv[2];
321
- if (cmd === "login") {
322
- await cmdLogin();
1076
+ if (cmd === "bootstrap-node" || cmd === "create-main-node") {
1077
+ await cmdBootstrapNode(process.argv.slice(3));
1078
+ return;
1079
+ }
1080
+ if (cmd === "clear-node-credentials") {
1081
+ await cmdClearNodeCredentials();
1082
+ return;
1083
+ }
1084
+ if (cmd === "inspect-node") {
1085
+ await cmdInspectNode();
1086
+ return;
1087
+ }
1088
+ if (cmd === "create-agent-node" || cmd === "create") {
1089
+ await cmdCreateAgentNode();
1090
+ return;
1091
+ }
1092
+ if (cmd === "list-agent-nodes" || cmd === "list") {
1093
+ await cmdListAgentNodes();
323
1094
  return;
324
1095
  }
325
- if (cmd === "logout") {
326
- await cmdLogout();
1096
+ if (cmd === "delete-agent-node" || cmd === "delete" || cmd === "remove") {
1097
+ await cmdDeleteAgentNode(process.argv.slice(3));
327
1098
  return;
328
1099
  }
329
- if (cmd === "create") {
330
- await cmdCreate();
1100
+ if (cmd === "delete-main-node") {
1101
+ await cmdDeleteMainNode();
331
1102
  return;
332
1103
  }
333
- if (cmd === "create-key" || cmd === "generate-key") {
334
- await cmdCreateKey();
1104
+ if (cmd === "validate-main-node") {
1105
+ await cmdValidateMainNode();
335
1106
  return;
336
1107
  }
337
- if (cmd === "view-keys") {
338
- await cmdViewKeys();
1108
+ if (cmd === "validate-agent-node") {
1109
+ await cmdValidateAgentNode(process.argv.slice(3));
339
1110
  return;
340
1111
  }
341
- if (cmd === "delete" || cmd === "remove") {
342
- await cmdDelete();
1112
+ if (cmd === "initialize" || cmd === "init") {
1113
+ await cmdInitialize2(process.argv.slice(3));
343
1114
  return;
344
1115
  }
345
1116
  console.error(
346
- "Usage: agent-play login | logout | create-key | view-keys | create | delete"
1117
+ [
1118
+ "Usage:",
1119
+ " agent-play create-main-node | bootstrap-node [--root-file <path>]",
1120
+ " agent-play inspect-node",
1121
+ " agent-play create-agent-node | create",
1122
+ " agent-play list-agent-nodes | list",
1123
+ " agent-play delete-agent-node | delete [agent-id]",
1124
+ " agent-play delete-main-node",
1125
+ " agent-play validate-main-node",
1126
+ " agent-play validate-agent-node --all",
1127
+ " agent-play validate-agent-node --agent-node-ids <id1,id2,...>",
1128
+ " agent-play initialize | init [--dir <path>] [--name <project-name>] [--template langchain] [--environment <development|test|production>] [--server-type <bare|express>] [--yes] [--force] [--bootstrap-nodes] [--agent-count <1|2>]",
1129
+ " agent-play clear-node-credentials"
1130
+ ].join("\n")
347
1131
  );
348
1132
  process.exitCode = 1;
349
1133
  }