@agent-play/cli 3.1.0 → 3.2.1

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 (22) hide show
  1. package/README.md +43 -27
  2. package/dist/README.md +94 -0
  3. package/dist/cli.js +461 -45
  4. package/package.json +8 -4
  5. package/templates/agent-starter/langchain/.env.example +6 -0
  6. package/templates/agent-starter/langchain/README.md +24 -0
  7. package/templates/agent-starter/langchain/package.json +25 -0
  8. package/templates/agent-starter/langchain/src/bare-server.ts +12 -0
  9. package/templates/agent-starter/langchain/src/builtins/definitions.ts +66 -0
  10. package/templates/agent-starter/langchain/src/builtins/toolkits/starter-tools.ts +84 -0
  11. package/templates/agent-starter/langchain/src/express-server.ts +27 -0
  12. package/templates/agent-starter/langchain/src/index.ts +6 -0
  13. package/templates/agent-starter/langchain/src/load-env.ts +5 -0
  14. package/templates/agent-starter/langchain/src/register/register-builtins.ts +62 -0
  15. package/templates/agent-starter/langchain/src/register-agents.ts +1 -0
  16. package/templates/agent-starter/langchain/src/tool-handlers/assist-brainstorm.ts +8 -0
  17. package/templates/agent-starter/langchain/src/tool-handlers/assist-calculate-coefficient.ts +35 -0
  18. package/templates/agent-starter/langchain/src/tool-handlers/assist-collect-scene-details.ts +32 -0
  19. package/templates/agent-starter/langchain/src/tool-handlers/chat-tool.ts +6 -0
  20. package/templates/agent-starter/langchain/src/tool-handlers/execute-tool-capability.ts +12 -0
  21. package/templates/agent-starter/langchain/src/tool-handlers/tool-capability-registry.ts +49 -0
  22. package/templates/agent-starter/langchain/tsconfig.json +13 -0
package/dist/cli.js CHANGED
@@ -1,24 +1,263 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.ts
4
- import { existsSync } from "fs";
5
- import { mkdir, unlink, writeFile } from "fs/promises";
4
+ import { existsSync as existsSync2 } from "fs";
5
+ import { mkdir as mkdir2, unlink, writeFile as writeFile2 } from "fs/promises";
6
6
  import { homedir } from "os";
7
- import { join, resolve } from "path";
7
+ import { join as join2, resolve as resolve2 } from "path";
8
8
  import { createInterface } from "readline/promises";
9
9
  import { stdin as input, stdout as output } from "process";
10
10
  import {
11
- createNodeCredentialFromPassw,
12
- deriveNodeIdFromPassword,
13
- generateNodePassw,
14
- hashNodePassword,
11
+ createNodeCredentialMaterial,
15
12
  loadAgentPlayCredentialsFileFromPath,
16
- loadRootKey
13
+ loadRootKey,
14
+ nodeCredentialsMaterialFromHumanPassphrase
17
15
  } from "@agent-play/node-tools";
16
+
17
+ // src/initialize.ts
18
+ import { existsSync } from "fs";
19
+ import { mkdir, readdir, readFile, writeFile } from "fs/promises";
20
+ import { dirname, join, resolve } from "path";
21
+ import { fileURLToPath } from "url";
22
+ function resolveServerUrlForEnvironment(environment) {
23
+ if (environment === "test") {
24
+ return "https://test-agent-play.com";
25
+ }
26
+ if (environment === "production") {
27
+ return "https://agent-play.com";
28
+ }
29
+ return "http://127.0.0.1:3000";
30
+ }
31
+ var TEMPLATE_ROOT = fileURLToPath(
32
+ new URL("../templates/agent-starter/langchain", import.meta.url)
33
+ );
34
+ function parseInitializeArgs(argv) {
35
+ const out = {
36
+ template: "langchain",
37
+ yes: false,
38
+ force: false
39
+ };
40
+ for (let i = 0; i < argv.length; i++) {
41
+ const token = argv[i];
42
+ if (token === "--dir" && typeof argv[i + 1] === "string") {
43
+ out.dir = argv[++i];
44
+ continue;
45
+ }
46
+ if (token === "--name" && typeof argv[i + 1] === "string") {
47
+ out.name = argv[++i];
48
+ continue;
49
+ }
50
+ if (token === "--template" && typeof argv[i + 1] === "string") {
51
+ const template = argv[++i];
52
+ if (template !== "langchain") {
53
+ return null;
54
+ }
55
+ out.template = template;
56
+ continue;
57
+ }
58
+ if (token === "--yes") {
59
+ out.yes = true;
60
+ continue;
61
+ }
62
+ if (token === "--force") {
63
+ out.force = true;
64
+ continue;
65
+ }
66
+ if (token === "--bootstrap-nodes") {
67
+ out.bootstrapNodes = true;
68
+ continue;
69
+ }
70
+ if (token === "--agent-count" && typeof argv[i + 1] === "string") {
71
+ const raw = Number(argv[++i]);
72
+ if (raw !== 1 && raw !== 2) {
73
+ return null;
74
+ }
75
+ out.agentCount = raw;
76
+ continue;
77
+ }
78
+ if (token === "--environment" && typeof argv[i + 1] === "string") {
79
+ const value = argv[++i].trim().toLowerCase();
80
+ if (value === "development" || value === "test" || value === "production") {
81
+ out.environment = value;
82
+ continue;
83
+ }
84
+ return null;
85
+ }
86
+ if (token === "--server-type" && typeof argv[i + 1] === "string") {
87
+ const value = argv[++i].trim().toLowerCase();
88
+ if (value === "bare" || value === "express") {
89
+ out.serverType = value;
90
+ continue;
91
+ }
92
+ return null;
93
+ }
94
+ return null;
95
+ }
96
+ return out;
97
+ }
98
+ function normalizeProjectName(raw) {
99
+ if (typeof raw !== "string") {
100
+ return "agent-play-agent-starter";
101
+ }
102
+ const normalized = raw.trim().toLowerCase().replace(/[^a-z0-9-_]+/g, "-");
103
+ if (normalized.length === 0) {
104
+ return "agent-play-agent-starter";
105
+ }
106
+ return normalized;
107
+ }
108
+ async function listTemplateFiles(dir) {
109
+ const entries = await readdir(dir, { withFileTypes: true });
110
+ const files = [];
111
+ for (const entry of entries) {
112
+ const full = join(dir, entry.name);
113
+ if (entry.isDirectory()) {
114
+ const nested = await listTemplateFiles(full);
115
+ files.push(...nested.map((path) => join(entry.name, path)));
116
+ continue;
117
+ }
118
+ files.push(entry.name);
119
+ }
120
+ return files;
121
+ }
122
+ async function ensureSafeTarget(options) {
123
+ const targetExists = existsSync(options.targetDir);
124
+ if (!targetExists) {
125
+ await mkdir(options.targetDir, { recursive: true });
126
+ return;
127
+ }
128
+ if (options.force) {
129
+ return;
130
+ }
131
+ const existing = await readdir(options.targetDir);
132
+ if (existing.length > 0) {
133
+ throw new Error(
134
+ `Target directory is not empty: ${options.targetDir}. Re-run with --force to overwrite scaffold-managed files.`
135
+ );
136
+ }
137
+ }
138
+ function patchEnvContent(options) {
139
+ const lines = options.envContent.split(/\r?\n/);
140
+ const updates = /* @__PURE__ */ new Map([
141
+ ["AGENT_PLAY_WEB_UI_URL", options.serverUrl],
142
+ ["AGENT_PLAY_MAIN_NODE_ID", options.mainNodeId],
143
+ ["AGENT_PLAY_AGENT_NODE_ID_1", options.agentNodeIds[0] ?? ""],
144
+ ["AGENT_PLAY_AGENT_NODE_ID_2", options.agentNodeIds[1] ?? ""]
145
+ ]);
146
+ const seen = /* @__PURE__ */ new Set();
147
+ const next = lines.map((line) => {
148
+ const eqIndex = line.indexOf("=");
149
+ if (eqIndex <= 0) {
150
+ return line;
151
+ }
152
+ const key = line.slice(0, eqIndex);
153
+ const update = updates.get(key);
154
+ if (update === void 0) {
155
+ return line;
156
+ }
157
+ seen.add(key);
158
+ return `${key}=${update}`;
159
+ });
160
+ for (const [key, value] of updates.entries()) {
161
+ if (!seen.has(key)) {
162
+ next.push(`${key}=${value}`);
163
+ }
164
+ }
165
+ return next.join("\n");
166
+ }
167
+ async function renderTemplate(options) {
168
+ const files = await listTemplateFiles(TEMPLATE_ROOT);
169
+ for (const relativePath of files) {
170
+ const templatePath = join(TEMPLATE_ROOT, relativePath);
171
+ const targetPath = join(options.targetDir, relativePath);
172
+ if (!options.force && existsSync(targetPath)) {
173
+ continue;
174
+ }
175
+ await mkdir(dirname(targetPath), { recursive: true });
176
+ const source = await readFile(templatePath, "utf8");
177
+ const serverModule = options.serverType === "express" ? "./express-server.js" : "./bare-server.js";
178
+ const content = source.replaceAll("__PROJECT_NAME__", options.projectName).replaceAll("__AGENT_NAME__", "Starter Agent").replaceAll("__SERVER_MODULE__", serverModule);
179
+ await writeFile(targetPath, content, "utf8");
180
+ }
181
+ }
182
+ async function cmdInitialize(options) {
183
+ const parsed = parseInitializeArgs(options.argv);
184
+ if (parsed === null) {
185
+ throw new Error(
186
+ "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>]"
187
+ );
188
+ }
189
+ const targetDir = resolve(parsed.dir ?? process.cwd());
190
+ const projectName = normalizeProjectName(parsed.name ?? basenameFromPath(targetDir));
191
+ const environment = parsed.environment ?? (parsed.yes ? "development" : await options.promptApi.askEnvironment());
192
+ const serverType = parsed.serverType ?? (parsed.yes ? "bare" : await options.promptApi.askServerType());
193
+ const serverUrl = resolveServerUrlForEnvironment(environment);
194
+ const agentCount = parsed.agentCount ?? (parsed.yes ? 1 : await options.promptApi.askAgentCount());
195
+ const bootstrapNodes = parsed.bootstrapNodes ?? (parsed.yes ? false : await options.promptApi.askBootstrapNodes());
196
+ await ensureSafeTarget({ targetDir, force: parsed.force });
197
+ await renderTemplate({
198
+ targetDir,
199
+ projectName,
200
+ serverType,
201
+ force: parsed.force
202
+ });
203
+ const envExamplePath = join(targetDir, ".env.example");
204
+ const envPath = join(targetDir, ".env");
205
+ if (!existsSync(envPath) && existsSync(envExamplePath)) {
206
+ await writeFile(envPath, await readFile(envExamplePath, "utf8"), "utf8");
207
+ }
208
+ if (bootstrapNodes) {
209
+ const bootstrapped = await options.runtimeApi.bootstrapNodeIds({
210
+ agentCount,
211
+ serverUrl
212
+ });
213
+ const envContent = existsSync(envPath) ? await readFile(envPath, "utf8") : "";
214
+ const nextEnv = patchEnvContent({
215
+ envContent,
216
+ serverUrl,
217
+ mainNodeId: bootstrapped.mainNodeId,
218
+ agentNodeIds: bootstrapped.agentNodeIds
219
+ });
220
+ await writeFile(envPath, nextEnv, "utf8");
221
+ console.log(`Bootstrapped main node id: ${bootstrapped.mainNodeId}`);
222
+ for (const [index, nodeId] of bootstrapped.agentNodeIds.entries()) {
223
+ console.log(`Bootstrapped agent node ${String(index + 1)} id: ${nodeId}`);
224
+ }
225
+ } else if (existsSync(envPath)) {
226
+ const envContent = await readFile(envPath, "utf8");
227
+ const nextEnv = patchEnvContent({
228
+ envContent,
229
+ serverUrl,
230
+ mainNodeId: "",
231
+ agentNodeIds: []
232
+ });
233
+ await writeFile(envPath, nextEnv, "utf8");
234
+ }
235
+ console.log("");
236
+ console.log("Agent starter scaffold created.");
237
+ console.log(`Location: ${targetDir}`);
238
+ console.log("Next steps:");
239
+ console.log(` cd "${targetDir}"`);
240
+ console.log(" npm install");
241
+ if (!bootstrapNodes) {
242
+ console.log(" npx agent-play create-main-node");
243
+ console.log(" npx agent-play create-agent-node");
244
+ if (agentCount === 2) {
245
+ console.log(" npx agent-play create-agent-node");
246
+ }
247
+ console.log(" copy node ids into .env");
248
+ }
249
+ console.log(" npm run dev");
250
+ }
251
+ function basenameFromPath(pathValue) {
252
+ const split = pathValue.split(/[\\/]/).filter((part) => part.length > 0);
253
+ return split[split.length - 1] ?? "agent-play-agent-starter";
254
+ }
255
+
256
+ // src/cli.ts
18
257
  function nodeAuthHeaders(cred) {
19
258
  return {
20
259
  "x-node-id": cred.nodeId,
21
- "x-node-passw": hashNodePassword(cred.passw)
260
+ "x-node-passw": nodeCredentialsMaterialFromHumanPassphrase(cred.passw)
22
261
  };
23
262
  }
24
263
  function parseAgentRows(agentsRaw) {
@@ -36,15 +275,15 @@ function parseAgentRows(agentsRaw) {
36
275
  return agents;
37
276
  }
38
277
  function credentialsPath() {
39
- return join(homedir(), ".agent-play", "credentials.json");
278
+ return join2(homedir(), ".agent-play", "credentials.json");
40
279
  }
41
280
  async function loadCredentials() {
42
281
  return loadAgentPlayCredentialsFileFromPath(credentialsPath());
43
282
  }
44
283
  async function saveCredentials(c) {
45
- const dir = join(homedir(), ".agent-play");
46
- await mkdir(dir, { recursive: true });
47
- await writeFile(
284
+ const dir = join2(homedir(), ".agent-play");
285
+ await mkdir2(dir, { recursive: true });
286
+ await writeFile2(
48
287
  credentialsPath(),
49
288
  JSON.stringify(c, null, 2),
50
289
  "utf8"
@@ -101,6 +340,102 @@ async function promptBootstrapEnvironment(rl) {
101
340
  );
102
341
  }
103
342
  }
343
+ function parseInitializeBootstrapAnswer(raw) {
344
+ const trimmed = raw.trim().toLowerCase();
345
+ if (trimmed === "" || trimmed === "y" || trimmed === "yes") {
346
+ return true;
347
+ }
348
+ if (trimmed === "n" || trimmed === "no") {
349
+ return false;
350
+ }
351
+ return null;
352
+ }
353
+ async function promptInitializeBootstrapChoice(rl) {
354
+ for (; ; ) {
355
+ const answer = await rl.question("Create node identities now? [Y/n]: ");
356
+ const parsed = parseInitializeBootstrapAnswer(answer);
357
+ if (parsed !== null) {
358
+ return parsed;
359
+ }
360
+ console.log("Please answer yes or no.");
361
+ }
362
+ }
363
+ function parseInitializeAgentCount(raw) {
364
+ const trimmed = raw.trim();
365
+ if (trimmed === "" || trimmed === "1") {
366
+ return 1;
367
+ }
368
+ if (trimmed === "2") {
369
+ return 2;
370
+ }
371
+ return null;
372
+ }
373
+ async function promptInitializeAgentCount(rl) {
374
+ for (; ; ) {
375
+ const answer = await rl.question("How many agents do you want to deploy? (1-2) [1]: ");
376
+ const parsed = parseInitializeAgentCount(answer);
377
+ if (parsed !== null) {
378
+ return parsed;
379
+ }
380
+ console.log("Invalid value. Enter 1 or 2.");
381
+ }
382
+ }
383
+ function parseInitializeEnvironmentAnswer(raw) {
384
+ const trimmed = raw.trim().toLowerCase();
385
+ if (trimmed === "" || trimmed === "1" || trimmed === "development" || trimmed === "dev") {
386
+ return "development";
387
+ }
388
+ if (trimmed === "2" || trimmed === "test") {
389
+ return "test";
390
+ }
391
+ if (trimmed === "3" || trimmed === "production" || trimmed === "prod") {
392
+ return "production";
393
+ }
394
+ return null;
395
+ }
396
+ async function promptInitializeEnvironment(rl) {
397
+ const lines = [
398
+ "Choose environment for initialization:",
399
+ " 1) development \u2192 http://127.0.0.1:3000",
400
+ " 2) test \u2192 https://test-agent-play.com",
401
+ " 3) production \u2192 https://agent-play.com",
402
+ "Enter 1-3, or development/test/production [1]: "
403
+ ].join("\n");
404
+ for (; ; ) {
405
+ const answer = await rl.question(lines);
406
+ const parsed = parseInitializeEnvironmentAnswer(answer);
407
+ if (parsed !== null) {
408
+ return parsed;
409
+ }
410
+ console.log("Invalid choice. Enter 1, 2, 3, development, test, or production.");
411
+ }
412
+ }
413
+ function parseInitializeServerTypeAnswer(raw) {
414
+ const trimmed = raw.trim().toLowerCase();
415
+ if (trimmed === "" || trimmed === "1" || trimmed === "bare") {
416
+ return "bare";
417
+ }
418
+ if (trimmed === "2" || trimmed === "express") {
419
+ return "express";
420
+ }
421
+ return null;
422
+ }
423
+ async function promptInitializeServerType(rl) {
424
+ const lines = [
425
+ "Choose server runtime:",
426
+ " 1) bare \u2192 simple process entrypoint (minimal)",
427
+ " 2) express \u2192 deployable HTTP server with /health endpoint",
428
+ "Enter 1-2, or bare/express [1]: "
429
+ ].join("\n");
430
+ for (; ; ) {
431
+ const answer = await rl.question(lines);
432
+ const parsed = parseInitializeServerTypeAnswer(answer);
433
+ if (parsed !== null) {
434
+ return parsed;
435
+ }
436
+ console.log("Invalid choice. Enter 1, 2, bare, or express.");
437
+ }
438
+ }
104
439
  function parseBootstrapNodeArgs(argv) {
105
440
  const out = {};
106
441
  for (let i = 0; i < argv.length; i++) {
@@ -137,29 +472,29 @@ function parseValidateAgentNodeArgs(argv) {
137
472
  }
138
473
  function resolveAgentPlayRootPath(options) {
139
474
  if (typeof options.rootFilePath === "string" && options.rootFilePath.trim().length > 0) {
140
- return resolve(options.rootFilePath.trim());
475
+ return resolve2(options.rootFilePath.trim());
141
476
  }
142
477
  const fromEnv = process.env.AGENT_PLAY_ROOT_FILE_PATH;
143
478
  if (typeof fromEnv === "string" && fromEnv.trim().length > 0) {
144
- return resolve(fromEnv.trim());
479
+ return resolve2(fromEnv.trim());
145
480
  }
146
- const homeRoot = join(homedir(), ".agent-play", ".root");
147
- if (existsSync(homeRoot)) {
481
+ const homeRoot = join2(homedir(), ".agent-play", ".root");
482
+ if (existsSync2(homeRoot)) {
148
483
  return homeRoot;
149
484
  }
150
- const cwdRoot = resolve(process.cwd(), ".root");
151
- if (existsSync(cwdRoot)) {
485
+ const cwdRoot = resolve2(process.cwd(), ".root");
486
+ if (existsSync2(cwdRoot)) {
152
487
  return cwdRoot;
153
488
  }
154
489
  throw new Error(
155
490
  "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."
156
491
  );
157
492
  }
158
- async function registerNodeOnServer(serverUrl, passw, expectedNodeId) {
493
+ async function registerNodeOnServer(serverUrl, nodeId, passwHash) {
159
494
  const res = await fetch(`${serverUrl}/api/nodes`, {
160
495
  method: "POST",
161
496
  headers: { "content-type": "application/json" },
162
- body: JSON.stringify({ kind: "main", passw })
497
+ body: JSON.stringify({ kind: "main", nodeId, passwHash })
163
498
  });
164
499
  const text = await res.text();
165
500
  if (res.status === 409) {
@@ -182,9 +517,7 @@ async function registerNodeOnServer(serverUrl, passw, expectedNodeId) {
182
517
  if (typeof json.nodeId !== "string") {
183
518
  throw new Error("Invalid response from server.");
184
519
  }
185
- if (json.nodeId !== expectedNodeId) {
186
- console.log("json", json);
187
- console.log("expectedNodeId", expectedNodeId);
520
+ if (json.nodeId !== nodeId) {
188
521
  throw new Error(
189
522
  "Server node id does not match local derivation; check root file and server configuration."
190
523
  );
@@ -198,22 +531,20 @@ async function cmdBootstrapNode(argv) {
198
531
  console.log(`Using server: ${serverUrl}`);
199
532
  const rootPath = resolveAgentPlayRootPath(opts);
200
533
  const rootKey = loadRootKey(rootPath);
201
- const dir = join(homedir(), ".agent-play");
202
- await mkdir(dir, { recursive: true });
203
- const generatedPassw = generateNodePassw();
204
- const hashedPassw = hashNodePassword(generatedPassw);
205
- const credential = createNodeCredentialFromPassw({ passw: hashedPassw, rootKey });
206
- await registerNodeOnServer(serverUrl, hashedPassw, credential.nodeId);
534
+ const dir = join2(homedir(), ".agent-play");
535
+ await mkdir2(dir, { recursive: true });
536
+ const credential = createNodeCredentialMaterial({ rootKey });
537
+ await registerNodeOnServer(serverUrl, credential.nodeId, credential.passwHash);
207
538
  await saveCredentials({
208
539
  serverUrl,
209
540
  nodeId: credential.nodeId,
210
- passw: generatedPassw
541
+ passw: credential.phrase
211
542
  });
212
543
  console.log(
213
544
  `genesisNodeId (platform root key from .root; all main nodes derive under this): ${rootKey}`
214
545
  );
215
546
  console.log(`mainNodeId (your developer node): ${credential.nodeId}`);
216
- console.log(`passw: ${generatedPassw}`);
547
+ console.log(`passw: ${credential.phrase}`);
217
548
  console.log("Keep this material safe. Losing it means losing access.");
218
549
  }
219
550
  async function cmdClearNodeCredentials() {
@@ -255,12 +586,7 @@ async function cmdCreateAgentNode() {
255
586
  return;
256
587
  }
257
588
  const rootKey = loadRootKey(resolveAgentPlayRootPath({}));
258
- const agentPassw = generateNodePassw();
259
- const hashedAgentPassw = hashNodePassword(agentPassw);
260
- const agentNodeId = deriveNodeIdFromPassword({
261
- password: hashedAgentPassw,
262
- rootKey
263
- });
589
+ const agentCredential = createNodeCredentialMaterial({ rootKey });
264
590
  const res = await fetch(`${cred.serverUrl}/api/nodes/agent-node`, {
265
591
  method: "POST",
266
592
  headers: {
@@ -270,8 +596,8 @@ async function cmdCreateAgentNode() {
270
596
  body: JSON.stringify({
271
597
  kind: "agent",
272
598
  parentNodeId: cred.nodeId,
273
- agentNodeId,
274
- agentNodePassw: hashedAgentPassw
599
+ agentNodeId: agentCredential.nodeId,
600
+ agentNodePasswHash: agentCredential.passwHash
275
601
  })
276
602
  });
277
603
  const text = await res.text();
@@ -292,7 +618,7 @@ async function cmdCreateAgentNode() {
292
618
  process.exitCode = 1;
293
619
  return;
294
620
  }
295
- if (json.agentId !== agentNodeId) {
621
+ if (json.agentId !== agentCredential.nodeId) {
296
622
  console.error(
297
623
  "Server returned a different agent node id than the locally derived one."
298
624
  );
@@ -300,10 +626,10 @@ async function cmdCreateAgentNode() {
300
626
  return;
301
627
  }
302
628
  const nextAgentNodes = [
303
- ...(cred.agentNodes ?? []).filter((n) => n.nodeId !== agentNodeId),
629
+ ...(cred.agentNodes ?? []).filter((n) => n.nodeId !== agentCredential.nodeId),
304
630
  {
305
- nodeId: agentNodeId,
306
- passw: agentPassw,
631
+ nodeId: agentCredential.nodeId,
632
+ passw: agentCredential.phrase,
307
633
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
308
634
  }
309
635
  ];
@@ -313,13 +639,67 @@ async function cmdCreateAgentNode() {
313
639
  });
314
640
  printAgentPlayIntegrationGuide();
315
641
  console.log(`Created agent node id: ${json.agentId}`);
316
- console.log(`Agent node passw: ${agentPassw}`);
642
+ console.log(`Agent node passw: ${agentCredential.phrase}`);
317
643
  console.log(
318
644
  `Saved agent node credentials to ${credentialsPath()} (agentNodes).`
319
645
  );
320
646
  console.log("Keep this material safe. Losing it means losing access.");
321
647
  console.log("");
322
648
  }
649
+ async function ensureMainCredentialsForInitialize(serverUrl) {
650
+ const existing = await loadCredentials();
651
+ if (existing !== null && existing.serverUrl.replace(/\/$/, "") === serverUrl.replace(/\/$/, "")) {
652
+ return existing;
653
+ }
654
+ const rootKey = loadRootKey(resolveAgentPlayRootPath({}));
655
+ const credential = createNodeCredentialMaterial({ rootKey });
656
+ await registerNodeOnServer(serverUrl, credential.nodeId, credential.passwHash);
657
+ const created = {
658
+ serverUrl,
659
+ nodeId: credential.nodeId,
660
+ passw: credential.phrase
661
+ };
662
+ await saveCredentials(created);
663
+ return created;
664
+ }
665
+ async function createAgentNodeForInitialize(cred) {
666
+ const rootKey = loadRootKey(resolveAgentPlayRootPath({}));
667
+ const agentCredential = createNodeCredentialMaterial({ rootKey });
668
+ const res = await fetch(`${cred.serverUrl}/api/nodes/agent-node`, {
669
+ method: "POST",
670
+ headers: {
671
+ "content-type": "application/json",
672
+ ...nodeAuthHeaders(cred)
673
+ },
674
+ body: JSON.stringify({
675
+ kind: "agent",
676
+ parentNodeId: cred.nodeId,
677
+ agentNodeId: agentCredential.nodeId,
678
+ agentNodePasswHash: agentCredential.passwHash
679
+ })
680
+ });
681
+ const text = await res.text();
682
+ if (!res.ok) {
683
+ throw new Error(`Create failed (${String(res.status)}): ${text}`);
684
+ }
685
+ const json = JSON.parse(text);
686
+ if (typeof json.agentId !== "string" || json.agentId !== agentCredential.nodeId) {
687
+ throw new Error("Invalid agent creation response.");
688
+ }
689
+ const nextAgentNodes = [
690
+ ...(cred.agentNodes ?? []).filter((n) => n.nodeId !== agentCredential.nodeId),
691
+ {
692
+ nodeId: agentCredential.nodeId,
693
+ passw: agentCredential.phrase,
694
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
695
+ }
696
+ ];
697
+ await saveCredentials({
698
+ ...cred,
699
+ agentNodes: nextAgentNodes
700
+ });
701
+ return agentCredential.nodeId;
702
+ }
323
703
  async function cmdInspectNode() {
324
704
  const cred = await loadCredentials();
325
705
  if (cred === null) {
@@ -642,6 +1022,37 @@ async function cmdValidateAgentNode(argv) {
642
1022
  }
643
1023
  console.log(`Validated ${String(dedupedIds.length)} agent node(s) successfully.`);
644
1024
  }
1025
+ async function cmdInitialize2(argv) {
1026
+ const rl = createInterface({ input, output });
1027
+ try {
1028
+ await cmdInitialize({
1029
+ argv,
1030
+ promptApi: {
1031
+ askEnvironment: () => promptInitializeEnvironment(rl),
1032
+ askServerType: () => promptInitializeServerType(rl),
1033
+ askBootstrapNodes: () => promptInitializeBootstrapChoice(rl),
1034
+ askAgentCount: () => promptInitializeAgentCount(rl)
1035
+ },
1036
+ runtimeApi: {
1037
+ bootstrapNodeIds: async (options) => {
1038
+ const mainCred = await ensureMainCredentialsForInitialize(options.serverUrl);
1039
+ const agentNodeIds = [];
1040
+ for (let i = 0; i < options.agentCount; i++) {
1041
+ const refreshedCred = await loadCredentials() ?? mainCred;
1042
+ const nodeId = await createAgentNodeForInitialize(refreshedCred);
1043
+ agentNodeIds.push(nodeId);
1044
+ }
1045
+ return {
1046
+ mainNodeId: mainCred.nodeId,
1047
+ agentNodeIds
1048
+ };
1049
+ }
1050
+ }
1051
+ });
1052
+ } finally {
1053
+ rl.close();
1054
+ }
1055
+ }
645
1056
  async function main() {
646
1057
  const cmd = process.argv[2];
647
1058
  if (cmd === "bootstrap-node" || cmd === "create-main-node") {
@@ -680,6 +1091,10 @@ async function main() {
680
1091
  await cmdValidateAgentNode(process.argv.slice(3));
681
1092
  return;
682
1093
  }
1094
+ if (cmd === "initialize" || cmd === "init") {
1095
+ await cmdInitialize2(process.argv.slice(3));
1096
+ return;
1097
+ }
683
1098
  console.error(
684
1099
  [
685
1100
  "Usage:",
@@ -692,6 +1107,7 @@ async function main() {
692
1107
  " agent-play validate-main-node",
693
1108
  " agent-play validate-agent-node --all",
694
1109
  " agent-play validate-agent-node --agent-node-ids <id1,id2,...>",
1110
+ " 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>]",
695
1111
  " agent-play clear-node-credentials"
696
1112
  ].join("\n")
697
1113
  );
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@agent-play/cli",
3
- "version": "3.1.0",
4
- "description": "Command-line tool for Agent Play: login, API keys, and agent registration against the web UI.",
3
+ "version": "3.2.1",
4
+ "description": "Command-line tool for Agent Play: node configurations, agent starter kit, and agent registration against the web UI.",
5
5
  "private": false,
6
6
  "type": "module",
7
7
  "bin": {
@@ -9,6 +9,7 @@
9
9
  },
10
10
  "files": [
11
11
  "dist",
12
+ "templates",
12
13
  "README.md"
13
14
  ],
14
15
  "repository": {
@@ -20,7 +21,9 @@
20
21
  "access": "public"
21
22
  },
22
23
  "scripts": {
23
- "build": "tsup src/cli.ts --format esm --platform node --target node20 --out-dir dist --clean --external @agent-play/node-tools && node ../../scripts/copy-root-file.mjs cli"
24
+ "build": "tsup src/cli.ts --format esm --platform node --target node20 --out-dir dist --clean --external @agent-play/node-tools && node ../../scripts/copy-root-file.mjs cli && cp README.md dist/README.md",
25
+ "test": "vitest run",
26
+ "smoke:initialize": "node ./scripts/smoke-initialize.mjs"
24
27
  },
25
28
  "dependencies": {
26
29
  "@agent-play/node-tools": "1.0.0"
@@ -28,6 +31,7 @@
28
31
  "devDependencies": {
29
32
  "@types/node": "^22.10.0",
30
33
  "tsup": "^8.5.1",
31
- "typescript": "~5.9.3"
34
+ "typescript": "~5.9.3",
35
+ "vitest": "^4.1.1"
32
36
  }
33
37
  }
@@ -0,0 +1,6 @@
1
+ AGENT_PLAY_WEB_UI_URL=https://agent-play.com
2
+ AGENT_PLAY_MAIN_NODE_ID=
3
+ AGENT_PLAY_AGENT_NODE_ID_1=
4
+ AGENT_PLAY_AGENT_NODE_ID_2=
5
+ OPENAI_API_KEY=
6
+ P2A_WEBRTC_ENABLED=1