@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.
- package/README.md +76 -2
- package/dist/.root +1 -0
- package/dist/README.md +91 -0
- package/dist/cli.js +968 -184
- package/package.json +11 -5
- 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,134 +1,564 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import {
|
|
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
|
-
|
|
10
|
-
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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 =
|
|
28
|
-
await
|
|
29
|
-
await
|
|
286
|
+
const dir = join2(homedir(), ".agent-play");
|
|
287
|
+
await mkdir2(dir, { recursive: true });
|
|
288
|
+
await writeFile2(
|
|
30
289
|
credentialsPath(),
|
|
31
|
-
JSON.stringify(
|
|
290
|
+
JSON.stringify(c, null, 2),
|
|
32
291
|
"utf8"
|
|
33
292
|
);
|
|
34
293
|
}
|
|
35
|
-
|
|
36
|
-
|
|
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
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
`
|
|
42
|
-
)
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
63
|
-
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
rl.
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
|
|
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
|
|
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("
|
|
559
|
+
console.log("Credentials removed.");
|
|
130
560
|
} catch {
|
|
131
|
-
console.log("No saved
|
|
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
|
|
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({
|
|
581
|
+
" \u2022 RemotePlayWorld({ nodeCredentials: { rootKey, passw } }) and addAgent({ nodeId, ... })."
|
|
152
582
|
);
|
|
153
583
|
console.log("");
|
|
154
584
|
}
|
|
155
|
-
async function
|
|
585
|
+
async function cmdCreateAgentNode() {
|
|
156
586
|
const cred = await loadCredentials();
|
|
157
587
|
if (cred === null) {
|
|
158
|
-
console.error(
|
|
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
|
|
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
|
-
|
|
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(`
|
|
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.
|
|
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
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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
|
|
198
|
-
|
|
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
|
-
|
|
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.
|
|
208
|
-
|
|
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
|
|
721
|
+
async function cmdInspectNode() {
|
|
219
722
|
const cred = await loadCredentials();
|
|
220
723
|
if (cred === null) {
|
|
221
|
-
console.error(
|
|
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
|
|
226
|
-
|
|
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(`
|
|
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.
|
|
250
|
-
console.error("Invalid response
|
|
746
|
+
if (typeof json.genesisNodeId !== "string") {
|
|
747
|
+
console.error("Invalid inspect response.");
|
|
251
748
|
process.exitCode = 1;
|
|
252
749
|
return;
|
|
253
750
|
}
|
|
254
|
-
|
|
255
|
-
|
|
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
|
|
795
|
+
async function cmdListAgentNodes() {
|
|
259
796
|
const cred = await loadCredentials();
|
|
260
797
|
if (cred === null) {
|
|
261
|
-
console.error(
|
|
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:
|
|
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
|
|
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
|
|
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
|
-
|
|
297
|
-
|
|
298
|
-
|
|
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:
|
|
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 === "
|
|
322
|
-
await
|
|
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 === "
|
|
326
|
-
await
|
|
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 === "
|
|
330
|
-
await
|
|
1100
|
+
if (cmd === "delete-main-node") {
|
|
1101
|
+
await cmdDeleteMainNode();
|
|
331
1102
|
return;
|
|
332
1103
|
}
|
|
333
|
-
if (cmd === "
|
|
334
|
-
await
|
|
1104
|
+
if (cmd === "validate-main-node") {
|
|
1105
|
+
await cmdValidateMainNode();
|
|
335
1106
|
return;
|
|
336
1107
|
}
|
|
337
|
-
if (cmd === "
|
|
338
|
-
await
|
|
1108
|
+
if (cmd === "validate-agent-node") {
|
|
1109
|
+
await cmdValidateAgentNode(process.argv.slice(3));
|
|
339
1110
|
return;
|
|
340
1111
|
}
|
|
341
|
-
if (cmd === "
|
|
342
|
-
await
|
|
1112
|
+
if (cmd === "initialize" || cmd === "init") {
|
|
1113
|
+
await cmdInitialize2(process.argv.slice(3));
|
|
343
1114
|
return;
|
|
344
1115
|
}
|
|
345
1116
|
console.error(
|
|
346
|
-
|
|
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
|
}
|