@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.
- package/README.md +43 -27
- package/dist/README.md +94 -0
- package/dist/cli.js +461 -45
- package/package.json +8 -4
- package/templates/agent-starter/langchain/.env.example +6 -0
- package/templates/agent-starter/langchain/README.md +24 -0
- package/templates/agent-starter/langchain/package.json +25 -0
- package/templates/agent-starter/langchain/src/bare-server.ts +12 -0
- package/templates/agent-starter/langchain/src/builtins/definitions.ts +66 -0
- package/templates/agent-starter/langchain/src/builtins/toolkits/starter-tools.ts +84 -0
- package/templates/agent-starter/langchain/src/express-server.ts +27 -0
- package/templates/agent-starter/langchain/src/index.ts +6 -0
- package/templates/agent-starter/langchain/src/load-env.ts +5 -0
- package/templates/agent-starter/langchain/src/register/register-builtins.ts +62 -0
- package/templates/agent-starter/langchain/src/register-agents.ts +1 -0
- package/templates/agent-starter/langchain/src/tool-handlers/assist-brainstorm.ts +8 -0
- package/templates/agent-starter/langchain/src/tool-handlers/assist-calculate-coefficient.ts +35 -0
- package/templates/agent-starter/langchain/src/tool-handlers/assist-collect-scene-details.ts +32 -0
- package/templates/agent-starter/langchain/src/tool-handlers/chat-tool.ts +6 -0
- package/templates/agent-starter/langchain/src/tool-handlers/execute-tool-capability.ts +12 -0
- package/templates/agent-starter/langchain/src/tool-handlers/tool-capability-registry.ts +49 -0
- 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
|
-
|
|
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":
|
|
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
|
|
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 =
|
|
46
|
-
await
|
|
47
|
-
await
|
|
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
|
|
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
|
|
479
|
+
return resolve2(fromEnv.trim());
|
|
145
480
|
}
|
|
146
|
-
const homeRoot =
|
|
147
|
-
if (
|
|
481
|
+
const homeRoot = join2(homedir(), ".agent-play", ".root");
|
|
482
|
+
if (existsSync2(homeRoot)) {
|
|
148
483
|
return homeRoot;
|
|
149
484
|
}
|
|
150
|
-
const cwdRoot =
|
|
151
|
-
if (
|
|
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,
|
|
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",
|
|
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 !==
|
|
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 =
|
|
202
|
-
await
|
|
203
|
-
const
|
|
204
|
-
|
|
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:
|
|
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: ${
|
|
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
|
|
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
|
-
|
|
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 !==
|
|
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 !==
|
|
629
|
+
...(cred.agentNodes ?? []).filter((n) => n.nodeId !== agentCredential.nodeId),
|
|
304
630
|
{
|
|
305
|
-
nodeId:
|
|
306
|
-
passw:
|
|
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: ${
|
|
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
|
|
4
|
-
"description": "Command-line tool for Agent Play:
|
|
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
|
}
|