@acnlabs/acn-cli 0.11.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +415 -60
- package/package.json +4 -2
package/dist/index.js
CHANGED
|
@@ -1,8 +1,93 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __commonJS = (cb, mod) => function __require() {
|
|
10
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
|
|
29
|
+
// package.json
|
|
30
|
+
var require_package = __commonJS({
|
|
31
|
+
"package.json"(exports2, module2) {
|
|
32
|
+
module2.exports = {
|
|
33
|
+
name: "@acnlabs/acn-cli",
|
|
34
|
+
version: "0.13.0",
|
|
35
|
+
description: "Official CLI for ACN (Agent Collaboration Network) \u2014 zero-integration agent access",
|
|
36
|
+
main: "dist/index.js",
|
|
37
|
+
bin: {
|
|
38
|
+
acn: "dist/index.js"
|
|
39
|
+
},
|
|
40
|
+
files: [
|
|
41
|
+
"dist",
|
|
42
|
+
"README.md"
|
|
43
|
+
],
|
|
44
|
+
scripts: {
|
|
45
|
+
build: "tsup",
|
|
46
|
+
dev: "tsup --watch",
|
|
47
|
+
typecheck: "tsc --noEmit",
|
|
48
|
+
"typecheck:tests": "tsc --noEmit -p tsconfig.tests.json",
|
|
49
|
+
test: "vitest run",
|
|
50
|
+
prepublishOnly: "npm run build"
|
|
51
|
+
},
|
|
52
|
+
keywords: [
|
|
53
|
+
"acn",
|
|
54
|
+
"agent",
|
|
55
|
+
"ai",
|
|
56
|
+
"cli",
|
|
57
|
+
"collaboration",
|
|
58
|
+
"a2a"
|
|
59
|
+
],
|
|
60
|
+
author: "acnlabs",
|
|
61
|
+
license: "Apache-2.0",
|
|
62
|
+
repository: {
|
|
63
|
+
type: "git",
|
|
64
|
+
url: "https://github.com/acnlabs/ACN.git",
|
|
65
|
+
directory: "clients/cli"
|
|
66
|
+
},
|
|
67
|
+
homepage: "https://github.com/acnlabs/ACN#readme",
|
|
68
|
+
bugs: {
|
|
69
|
+
url: "https://github.com/acnlabs/ACN/issues"
|
|
70
|
+
},
|
|
71
|
+
dependencies: {
|
|
72
|
+
commander: "^12.0.0",
|
|
73
|
+
ws: "^8.18.0"
|
|
74
|
+
},
|
|
75
|
+
devDependencies: {
|
|
76
|
+
"@types/node": "^20.0.0",
|
|
77
|
+
"@types/ws": "^8.5.10",
|
|
78
|
+
tsup: "^8.0.0",
|
|
79
|
+
typescript: "^5.0.0",
|
|
80
|
+
vitest: "^3.0.5"
|
|
81
|
+
},
|
|
82
|
+
engines: {
|
|
83
|
+
node: ">=18.0.0"
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
});
|
|
3
88
|
|
|
4
89
|
// src/index.ts
|
|
5
|
-
var
|
|
90
|
+
var import_commander16 = require("commander");
|
|
6
91
|
|
|
7
92
|
// src/output.ts
|
|
8
93
|
var jsonMode = false;
|
|
@@ -132,6 +217,15 @@ var AcnApiError = class extends Error {
|
|
|
132
217
|
status;
|
|
133
218
|
body;
|
|
134
219
|
};
|
|
220
|
+
function extractDetail(body) {
|
|
221
|
+
if (typeof body !== "object" || body === null) return String(body);
|
|
222
|
+
const b = body;
|
|
223
|
+
if (typeof b["message"] === "string") {
|
|
224
|
+
return typeof b["error_code"] === "string" ? `${b["error_code"]}: ${b["message"]}` : b["message"];
|
|
225
|
+
}
|
|
226
|
+
if (typeof b["detail"] === "string") return b["detail"];
|
|
227
|
+
return JSON.stringify(b);
|
|
228
|
+
}
|
|
135
229
|
async function acnFetch(path, options = {}) {
|
|
136
230
|
const config = loadConfig();
|
|
137
231
|
const { params, ...fetchOptions } = options;
|
|
@@ -156,8 +250,7 @@ async function acnFetch(path, options = {}) {
|
|
|
156
250
|
} catch {
|
|
157
251
|
body = await res.text();
|
|
158
252
|
}
|
|
159
|
-
|
|
160
|
-
throw new AcnApiError(res.status, body, `HTTP ${res.status}: ${detail}`);
|
|
253
|
+
throw new AcnApiError(res.status, body, `HTTP ${res.status}: ${extractDetail(body)}`);
|
|
161
254
|
}
|
|
162
255
|
if (res.status === 204 || res.headers.get("content-length") === "0") {
|
|
163
256
|
return {};
|
|
@@ -185,33 +278,42 @@ function acnDelete(path) {
|
|
|
185
278
|
|
|
186
279
|
// src/commands/join.ts
|
|
187
280
|
function joinCommand() {
|
|
188
|
-
return new import_commander2.Command("join").description("Register this agent with ACN and save credentials locally").requiredOption("-n, --name <name>", "Agent name").requiredOption("-t, --tags <tags>", "Comma-separated capability tags (e.g. coding,review)").option("-e, --endpoint <url>", "Public A2A endpoint URL of this agent").option("-d, --description <text>", "Agent description").
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
tags,
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
281
|
+
return new import_commander2.Command("join").description("Register this agent with ACN and save credentials locally").requiredOption("-n, --name <name>", "Agent name").requiredOption("-t, --tags <tags>", "Comma-separated capability tags (e.g. coding,review)").option("-e, --endpoint <url>", "Public A2A endpoint URL of this agent").option("-d, --description <text>", "Agent description").option(
|
|
282
|
+
"--relay",
|
|
283
|
+
"Receive messages in real time over an outbound WebSocket (run `acn listen`) instead of hosting a public endpoint. Registers in open/push mode with no delivery URL."
|
|
284
|
+
).action(
|
|
285
|
+
async (opts) => {
|
|
286
|
+
const tags = opts.tags.split(",").map((s) => s.trim()).filter(Boolean);
|
|
287
|
+
const body = {
|
|
288
|
+
name: opts.name,
|
|
289
|
+
description: opts.description ?? `${opts.name} \u2014 registered via acn-cli`,
|
|
290
|
+
tags,
|
|
291
|
+
...opts.endpoint ? { endpoint: opts.endpoint } : {},
|
|
292
|
+
// ADR-0012 Mode B: relay delivery needs a push mode (open) so the
|
|
293
|
+
// gateway actually pushes inbound messages down the WebSocket; the
|
|
294
|
+
// server-side validator then waives the public-URL requirement.
|
|
295
|
+
...opts.relay ? { delivery: "relay", communication_policy: { mode: "open" } } : {}
|
|
296
|
+
};
|
|
297
|
+
try {
|
|
298
|
+
const res = await acnPost("/agents/join", body);
|
|
299
|
+
saveConfig({ api_key: res.api_key, agent_id: res.agent_id });
|
|
300
|
+
const claimLine = res.claim_url ? `
|
|
200
301
|
Claim URL: ${res.claim_url}` : "";
|
|
201
|
-
|
|
302
|
+
const verifyLine = res.verification_code ? `
|
|
202
303
|
Verify : ${res.verification_code}` : "";
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
304
|
+
output(res, [
|
|
305
|
+
`Registered successfully!`,
|
|
306
|
+
` Agent ID : ${res.agent_id}`,
|
|
307
|
+
` API Key : ${res.api_key}`,
|
|
308
|
+
` Status : ${res.status}${claimLine}${verifyLine}`,
|
|
309
|
+
``,
|
|
310
|
+
`Credentials saved to ~/.acn/config.json`
|
|
311
|
+
].join("\n"));
|
|
312
|
+
} catch (err) {
|
|
313
|
+
handleError(err);
|
|
314
|
+
}
|
|
213
315
|
}
|
|
214
|
-
|
|
316
|
+
);
|
|
215
317
|
}
|
|
216
318
|
|
|
217
319
|
// src/commands/heartbeat.ts
|
|
@@ -394,6 +496,7 @@ function formatTask(t) {
|
|
|
394
496
|
if (t.reward && t.reward !== "0") {
|
|
395
497
|
lines.push(` Reward : ${t.reward} ${t.reward_currency ?? ""}`);
|
|
396
498
|
}
|
|
499
|
+
if (t.subnet_slug) lines.push(` Subnet : ${t.subnet_slug}`);
|
|
397
500
|
if (t.description) lines.push(` Desc : ${t.description.slice(0, 120)}`);
|
|
398
501
|
if (t.created_at) lines.push(` Created : ${t.created_at}`);
|
|
399
502
|
if (t.deadline) lines.push(` Deadline : ${t.deadline}`);
|
|
@@ -483,7 +586,7 @@ ${formatTask(t)}`).join("\n\n")
|
|
|
483
586
|
handleError(err);
|
|
484
587
|
}
|
|
485
588
|
});
|
|
486
|
-
cmd.command("create").description("Create a new task (as agent)").requiredOption("-t, --title <title>", "Task title (min 3 chars)").requiredOption("-d, --description <text>", "Task description (min 10 chars)").requiredOption("--tags <tags>", "Required skill tags, comma-separated (e.g. coding,review)").option("--deadline <hours>", "Deadline in hours (default: 48)", "48").option("--reward <amount>", "Reward amount (default: 0)", "0").option("--currency <currency>", "Reward currency (e.g. USD, USDC, ap_points)", "ap_points").option("--type <type>", "Task type (e.g. coding, general)", "general").option("--max-participants <n>", "Max participants (default: 1)", "1").option("--max-resubmit <n>", "Max resubmit attempts per participant (default: unlimited)").action(
|
|
589
|
+
cmd.command("create").description("Create a new task (as agent)").requiredOption("-t, --title <title>", "Task title (min 3 chars)").requiredOption("-d, --description <text>", "Task description (min 10 chars)").requiredOption("--tags <tags>", "Required skill tags, comma-separated (e.g. coding,review)").option("--deadline <hours>", "Deadline in hours (default: 48)", "48").option("--reward <amount>", "Reward amount (default: 0)", "0").option("--currency <currency>", "Reward currency (e.g. USD, USDC, ap_points)", "ap_points").option("--type <type>", "Task type (e.g. coding, general)", "general").option("--max-participants <n>", "Max participants (default: 1)", "1").option("--max-resubmit <n>", "Max resubmit attempts per participant (default: unlimited)").option("--subnet <slug>", "Subnet slug to scope the task to (agent must be a member)").action(
|
|
487
590
|
async (opts) => {
|
|
488
591
|
const config = loadConfig();
|
|
489
592
|
if (!config.api_key) {
|
|
@@ -505,6 +608,9 @@ ${formatTask(t)}`).join("\n\n")
|
|
|
505
608
|
if (opts.maxResubmit !== void 0) {
|
|
506
609
|
body.max_resubmit_attempts = parseInt(opts.maxResubmit, 10);
|
|
507
610
|
}
|
|
611
|
+
if (opts.subnet) {
|
|
612
|
+
body.subnet_slug = opts.subnet;
|
|
613
|
+
}
|
|
508
614
|
try {
|
|
509
615
|
const task = await acnPost("/tasks/agent/create", body);
|
|
510
616
|
output(task, [`Task created!
|
|
@@ -1156,8 +1262,255 @@ ${formatPolicy(res)}`);
|
|
|
1156
1262
|
return cmd;
|
|
1157
1263
|
}
|
|
1158
1264
|
|
|
1159
|
-
// src/commands/
|
|
1265
|
+
// src/commands/listen.ts
|
|
1160
1266
|
var import_commander10 = require("commander");
|
|
1267
|
+
var import_child_process = require("child_process");
|
|
1268
|
+
var import_ws = __toESM(require("ws"));
|
|
1269
|
+
var STRIP_HEADERS = /* @__PURE__ */ new Set([
|
|
1270
|
+
"host",
|
|
1271
|
+
"content-length",
|
|
1272
|
+
"connection",
|
|
1273
|
+
"transfer-encoding"
|
|
1274
|
+
]);
|
|
1275
|
+
function toWebsocketUrl(baseUrl, agentId) {
|
|
1276
|
+
const u = new URL(baseUrl);
|
|
1277
|
+
u.protocol = u.protocol === "https:" ? "wss:" : "ws:";
|
|
1278
|
+
u.pathname = `/ws/${agentId}`;
|
|
1279
|
+
u.search = "";
|
|
1280
|
+
return u.toString();
|
|
1281
|
+
}
|
|
1282
|
+
function decodeBody(frame) {
|
|
1283
|
+
if (frame.body_encoding === "base64") {
|
|
1284
|
+
return Buffer.from(frame.body ?? "", "base64");
|
|
1285
|
+
}
|
|
1286
|
+
return Buffer.from(frame.body ?? "", "utf-8");
|
|
1287
|
+
}
|
|
1288
|
+
function errorResponse(id, status, detail) {
|
|
1289
|
+
return {
|
|
1290
|
+
type: "a2a_response",
|
|
1291
|
+
id,
|
|
1292
|
+
status,
|
|
1293
|
+
headers: { "content-type": "application/json" },
|
|
1294
|
+
body: JSON.stringify({ error: detail })
|
|
1295
|
+
};
|
|
1296
|
+
}
|
|
1297
|
+
async function dispatchA2aRequest(frame, opts, send, deps = {}) {
|
|
1298
|
+
const bodyBuf = decodeBody(frame);
|
|
1299
|
+
try {
|
|
1300
|
+
if (opts.forward) {
|
|
1301
|
+
await forwardToHttp(frame, bodyBuf, opts.forward, deps.fetchFn ?? fetch, send);
|
|
1302
|
+
return;
|
|
1303
|
+
}
|
|
1304
|
+
if (opts.exec) {
|
|
1305
|
+
send(await runExec(frame, bodyBuf, opts.exec, deps.spawnFn ?? import_child_process.spawn));
|
|
1306
|
+
return;
|
|
1307
|
+
}
|
|
1308
|
+
send(errorResponse(frame.id, 500, "no handler configured"));
|
|
1309
|
+
} catch (err) {
|
|
1310
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1311
|
+
send(errorResponse(frame.id, 502, `handler failed: ${msg}`));
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
function buildForwardHeaders(frame) {
|
|
1315
|
+
const headers = {};
|
|
1316
|
+
for (const [k, v] of Object.entries(frame.headers ?? {})) {
|
|
1317
|
+
if (!STRIP_HEADERS.has(k.toLowerCase())) headers[k] = v;
|
|
1318
|
+
}
|
|
1319
|
+
return headers;
|
|
1320
|
+
}
|
|
1321
|
+
async function forwardToHttp(frame, bodyBuf, base, fetchFn, send) {
|
|
1322
|
+
const suffix = frame.path && frame.path !== "/" ? "/" + frame.path.replace(/^\//, "") : "";
|
|
1323
|
+
const targetUrl = base.replace(/\/$/, "") + suffix;
|
|
1324
|
+
const method = (frame.method ?? "POST").toUpperCase();
|
|
1325
|
+
const init = { method, headers: buildForwardHeaders(frame) };
|
|
1326
|
+
if (method !== "GET" && method !== "HEAD") {
|
|
1327
|
+
init.body = bodyBuf;
|
|
1328
|
+
}
|
|
1329
|
+
const res = await fetchFn(targetUrl, init);
|
|
1330
|
+
const contentType = res.headers.get("content-type") ?? "application/json";
|
|
1331
|
+
if (contentType.includes("text/event-stream") && res.body) {
|
|
1332
|
+
const reader = res.body.getReader();
|
|
1333
|
+
let seq = 0;
|
|
1334
|
+
try {
|
|
1335
|
+
for (; ; ) {
|
|
1336
|
+
const { done, value } = await reader.read();
|
|
1337
|
+
if (done) break;
|
|
1338
|
+
if (value && value.length > 0) {
|
|
1339
|
+
send({
|
|
1340
|
+
type: "a2a_stream_chunk",
|
|
1341
|
+
id: frame.id,
|
|
1342
|
+
seq: seq++,
|
|
1343
|
+
data: Buffer.from(value).toString("base64"),
|
|
1344
|
+
data_encoding: "base64"
|
|
1345
|
+
});
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
send({ type: "a2a_stream_end", id: frame.id, status: res.status });
|
|
1349
|
+
} catch (err) {
|
|
1350
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1351
|
+
send({ type: "a2a_stream_end", id: frame.id, error: msg });
|
|
1352
|
+
} finally {
|
|
1353
|
+
reader.releaseLock();
|
|
1354
|
+
}
|
|
1355
|
+
return;
|
|
1356
|
+
}
|
|
1357
|
+
const respText = await res.text();
|
|
1358
|
+
send({
|
|
1359
|
+
type: "a2a_response",
|
|
1360
|
+
id: frame.id,
|
|
1361
|
+
status: res.status,
|
|
1362
|
+
headers: { "content-type": contentType },
|
|
1363
|
+
body: respText
|
|
1364
|
+
});
|
|
1365
|
+
}
|
|
1366
|
+
function runExec(frame, bodyBuf, command, spawnFn) {
|
|
1367
|
+
return new Promise((resolve) => {
|
|
1368
|
+
const child = spawnFn(command, { shell: true });
|
|
1369
|
+
const out = [];
|
|
1370
|
+
const errOut = [];
|
|
1371
|
+
child.stdout?.on("data", (d) => out.push(Buffer.from(d)));
|
|
1372
|
+
child.stderr?.on("data", (d) => errOut.push(Buffer.from(d)));
|
|
1373
|
+
child.on(
|
|
1374
|
+
"error",
|
|
1375
|
+
(e) => resolve(errorResponse(frame.id, 502, `exec error: ${e.message}`))
|
|
1376
|
+
);
|
|
1377
|
+
child.on("close", (code) => {
|
|
1378
|
+
const stdout = Buffer.concat(out).toString("utf-8");
|
|
1379
|
+
if (code === 0) {
|
|
1380
|
+
resolve({
|
|
1381
|
+
type: "a2a_response",
|
|
1382
|
+
id: frame.id,
|
|
1383
|
+
status: 200,
|
|
1384
|
+
headers: { "content-type": "application/json" },
|
|
1385
|
+
body: stdout
|
|
1386
|
+
});
|
|
1387
|
+
} else {
|
|
1388
|
+
const stderr = Buffer.concat(errOut).toString("utf-8");
|
|
1389
|
+
resolve(
|
|
1390
|
+
errorResponse(frame.id, 500, `exec exited ${code}: ${stderr.slice(0, 500)}`)
|
|
1391
|
+
);
|
|
1392
|
+
}
|
|
1393
|
+
});
|
|
1394
|
+
child.stdin?.end(bodyBuf);
|
|
1395
|
+
});
|
|
1396
|
+
}
|
|
1397
|
+
function rawToString(data) {
|
|
1398
|
+
if (typeof data === "string") return data;
|
|
1399
|
+
if (Buffer.isBuffer(data)) return data.toString("utf-8");
|
|
1400
|
+
if (Array.isArray(data)) return Buffer.concat(data).toString("utf-8");
|
|
1401
|
+
return Buffer.from(data).toString("utf-8");
|
|
1402
|
+
}
|
|
1403
|
+
var KEEPALIVE_INTERVAL_MS = 3e4;
|
|
1404
|
+
var INITIAL_BACKOFF_MS = 1e3;
|
|
1405
|
+
var MAX_BACKOFF_MS = 3e4;
|
|
1406
|
+
function runListener(cfg) {
|
|
1407
|
+
const wsUrl = toWebsocketUrl(cfg.baseUrl, cfg.agentId);
|
|
1408
|
+
let backoff = INITIAL_BACKOFF_MS;
|
|
1409
|
+
let stopped = false;
|
|
1410
|
+
const connect = () => {
|
|
1411
|
+
const ws = new import_ws.default(wsUrl, {
|
|
1412
|
+
headers: { Authorization: `Bearer ${cfg.apiKey}` }
|
|
1413
|
+
});
|
|
1414
|
+
let keepalive;
|
|
1415
|
+
ws.on("open", () => {
|
|
1416
|
+
console.error(`[acn listen] connected as ${cfg.agentId} \u2192 ${wsUrl}`);
|
|
1417
|
+
backoff = INITIAL_BACKOFF_MS;
|
|
1418
|
+
keepalive = setInterval(() => {
|
|
1419
|
+
if (ws.readyState === import_ws.default.OPEN) {
|
|
1420
|
+
ws.send(JSON.stringify({ type: "ping" }));
|
|
1421
|
+
}
|
|
1422
|
+
}, KEEPALIVE_INTERVAL_MS);
|
|
1423
|
+
});
|
|
1424
|
+
ws.on("message", (data) => {
|
|
1425
|
+
let frame;
|
|
1426
|
+
try {
|
|
1427
|
+
frame = JSON.parse(rawToString(data));
|
|
1428
|
+
} catch {
|
|
1429
|
+
return;
|
|
1430
|
+
}
|
|
1431
|
+
if (!frame || typeof frame !== "object") return;
|
|
1432
|
+
const f = frame;
|
|
1433
|
+
if (f.type === "a2a_request" && typeof f.id === "string") {
|
|
1434
|
+
const send = (out) => {
|
|
1435
|
+
if (ws.readyState === import_ws.default.OPEN) {
|
|
1436
|
+
ws.send(JSON.stringify(out));
|
|
1437
|
+
}
|
|
1438
|
+
};
|
|
1439
|
+
void dispatchA2aRequest(f, cfg, send);
|
|
1440
|
+
}
|
|
1441
|
+
});
|
|
1442
|
+
ws.on("close", (code, reason) => {
|
|
1443
|
+
if (keepalive) clearInterval(keepalive);
|
|
1444
|
+
if (stopped) return;
|
|
1445
|
+
if (code === 4401 || code === 4403 || code === 4429) {
|
|
1446
|
+
console.error(
|
|
1447
|
+
`[acn listen] fatal close (code ${code}): ${reason.toString() || "see api_key / agent_id"}`
|
|
1448
|
+
);
|
|
1449
|
+
process.exit(1);
|
|
1450
|
+
}
|
|
1451
|
+
console.error(
|
|
1452
|
+
`[acn listen] disconnected (code ${code}); reconnecting in ${backoff / 1e3}s`
|
|
1453
|
+
);
|
|
1454
|
+
setTimeout(connect, backoff);
|
|
1455
|
+
backoff = Math.min(backoff * 2, MAX_BACKOFF_MS);
|
|
1456
|
+
});
|
|
1457
|
+
ws.on("error", (err) => {
|
|
1458
|
+
console.error(`[acn listen] socket error: ${err.message}`);
|
|
1459
|
+
});
|
|
1460
|
+
};
|
|
1461
|
+
process.on("SIGINT", () => {
|
|
1462
|
+
stopped = true;
|
|
1463
|
+
console.error("\n[acn listen] stopping");
|
|
1464
|
+
process.exit(0);
|
|
1465
|
+
});
|
|
1466
|
+
connect();
|
|
1467
|
+
}
|
|
1468
|
+
function listenCommand() {
|
|
1469
|
+
const cmd = new import_commander10.Command("listen").description(
|
|
1470
|
+
"Hold an outbound connection to ACN and answer relayed A2A requests in real time (ADR-0012 Mode B). For agents with no public endpoint."
|
|
1471
|
+
).option(
|
|
1472
|
+
"--forward <url>",
|
|
1473
|
+
"Tunnel each relayed request to a local HTTP server (e.g. http://localhost:8080)"
|
|
1474
|
+
).option(
|
|
1475
|
+
"--exec <command>",
|
|
1476
|
+
"Run a shell command per request: body on stdin, stdout becomes the response"
|
|
1477
|
+
).option("-i, --agent-id <id>", "Agent ID (defaults to config)").action((opts) => {
|
|
1478
|
+
const config = loadConfig();
|
|
1479
|
+
const apiKey = config.api_key;
|
|
1480
|
+
const agentId = opts.agentId ?? config.agent_id;
|
|
1481
|
+
if (!apiKey) {
|
|
1482
|
+
console.error(
|
|
1483
|
+
"No API key found. Run `acn join` first or `acn config set api-key <key>`."
|
|
1484
|
+
);
|
|
1485
|
+
process.exit(1);
|
|
1486
|
+
}
|
|
1487
|
+
if (!agentId) {
|
|
1488
|
+
console.error(
|
|
1489
|
+
"No agent ID found. Run `acn join` first or `acn config set agent-id <id>`."
|
|
1490
|
+
);
|
|
1491
|
+
process.exit(1);
|
|
1492
|
+
}
|
|
1493
|
+
if (!opts.forward && !opts.exec) {
|
|
1494
|
+
console.error("Provide a handler: --forward <url> or --exec <command>.");
|
|
1495
|
+
process.exit(1);
|
|
1496
|
+
}
|
|
1497
|
+
if (opts.forward && opts.exec) {
|
|
1498
|
+
console.error("Use only one handler: --forward or --exec, not both.");
|
|
1499
|
+
process.exit(1);
|
|
1500
|
+
}
|
|
1501
|
+
runListener({
|
|
1502
|
+
agentId,
|
|
1503
|
+
apiKey,
|
|
1504
|
+
baseUrl: config.base_url,
|
|
1505
|
+
forward: opts.forward,
|
|
1506
|
+
exec: opts.exec
|
|
1507
|
+
});
|
|
1508
|
+
});
|
|
1509
|
+
return cmd;
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
// src/commands/session.ts
|
|
1513
|
+
var import_commander11 = require("commander");
|
|
1161
1514
|
function requireAgentId3() {
|
|
1162
1515
|
const config = loadConfig();
|
|
1163
1516
|
if (!config.api_key) {
|
|
@@ -1202,7 +1555,7 @@ function formatEntry2(s, index) {
|
|
|
1202
1555
|
return lines.join("\n");
|
|
1203
1556
|
}
|
|
1204
1557
|
function sessionCommand() {
|
|
1205
|
-
const cmd = new
|
|
1558
|
+
const cmd = new import_commander11.Command("session").description(
|
|
1206
1559
|
"Real-time session layer: bidirectional channel between two agents"
|
|
1207
1560
|
);
|
|
1208
1561
|
cmd.command("invite <target_agent_id>").description("Invite an agent to a real-time session").option("--ttl-seconds <s>", "Session TTL in seconds (60\u20131800, default 300)", parseInt).option("--metadata <json>", "Optional JSON object attached to the invitation (max 4KB)").action(
|
|
@@ -1280,7 +1633,7 @@ ${formatEntry2(res)}`);
|
|
|
1280
1633
|
}
|
|
1281
1634
|
|
|
1282
1635
|
// src/commands/subnet.ts
|
|
1283
|
-
var
|
|
1636
|
+
var import_commander12 = require("commander");
|
|
1284
1637
|
function requireAgentId4() {
|
|
1285
1638
|
const config = loadConfig();
|
|
1286
1639
|
if (!config.api_key) {
|
|
@@ -1304,7 +1657,7 @@ function formatJoinRequest(r, index) {
|
|
|
1304
1657
|
const prefix = index !== void 0 ? `[${index + 1}] ` : "";
|
|
1305
1658
|
const lines = [
|
|
1306
1659
|
`${prefix}${r.request_id} [${r.kind} / ${r.status}]`,
|
|
1307
|
-
` Subnet : ${r.
|
|
1660
|
+
` Subnet : ${r.slug}`,
|
|
1308
1661
|
` Agent : ${r.agent_id}`,
|
|
1309
1662
|
` InitiatedBy: ${r.initiated_by}`
|
|
1310
1663
|
];
|
|
@@ -1349,12 +1702,12 @@ function formatInviteResponse(res, subnetId, targetAgentId) {
|
|
|
1349
1702
|
function formatSubnet(s, index) {
|
|
1350
1703
|
const prefix = index !== void 0 ? `[${index + 1}] ` : "";
|
|
1351
1704
|
const privacy = s.is_private ? " [private]" : " [public]";
|
|
1352
|
-
const lines = [`${prefix}${s.
|
|
1705
|
+
const lines = [`${prefix}${s.slug}${privacy}`, ` Name : ${s.name}`];
|
|
1353
1706
|
if (s.owner) lines.push(` Owner : ${s.owner}`);
|
|
1354
1707
|
if (s.description) lines.push(` Desc : ${s.description}`);
|
|
1355
1708
|
if (s.created_at) lines.push(` Since : ${s.created_at}`);
|
|
1356
|
-
if (s.parent_subnet_id) {
|
|
1357
|
-
lines.push(` Parent: ${s.parent_subnet_id}`);
|
|
1709
|
+
if (s.parent_slug ?? s.parent_subnet_id) {
|
|
1710
|
+
lines.push(` Parent: ${s.parent_slug ?? s.parent_subnet_id}`);
|
|
1358
1711
|
}
|
|
1359
1712
|
if (s.lifecycle && s.lifecycle !== "persistent") {
|
|
1360
1713
|
const task = s.linked_task_id ? ` (task=${s.linked_task_id})` : "";
|
|
@@ -1368,7 +1721,7 @@ function formatSubnet(s, index) {
|
|
|
1368
1721
|
return lines.join("\n");
|
|
1369
1722
|
}
|
|
1370
1723
|
function subnetCommand() {
|
|
1371
|
-
const cmd = new
|
|
1724
|
+
const cmd = new import_commander12.Command("subnet").description("Manage ACN subnets");
|
|
1372
1725
|
cmd.command("list").description(
|
|
1373
1726
|
"List subnets. Without --all/--parent shows only subnets you have joined."
|
|
1374
1727
|
).option("--all", "Show all public subnets on ACN (not just your own)").option(
|
|
@@ -1524,16 +1877,16 @@ ${JSON.stringify(res.agents, null, 2)}`);
|
|
|
1524
1877
|
name: opts.name,
|
|
1525
1878
|
is_private: !!opts.private
|
|
1526
1879
|
};
|
|
1527
|
-
if (opts.id) body.
|
|
1880
|
+
if (opts.id) body.slug = opts.id;
|
|
1528
1881
|
if (opts.description) body.description = opts.description;
|
|
1529
1882
|
if (joinPolicy !== void 0) body.join_policy = joinPolicy;
|
|
1530
|
-
if (opts.parent) body.
|
|
1883
|
+
if (opts.parent) body.parent_slug = opts.parent;
|
|
1531
1884
|
body.lifecycle = lifecycle;
|
|
1532
1885
|
if (opts.task) body.linked_task_id = opts.task;
|
|
1533
1886
|
try {
|
|
1534
1887
|
const res = await acnPost("/subnets", body);
|
|
1535
1888
|
const lines = [
|
|
1536
|
-
`Subnet created: ${res.
|
|
1889
|
+
`Subnet created: ${res.slug}`,
|
|
1537
1890
|
` Visibility : ${res.is_public ? "public" : "private"}`,
|
|
1538
1891
|
` Gateway A2A: ${res.gateway_a2a_url}`,
|
|
1539
1892
|
` Gateway WS : ${res.gateway_ws_url}`
|
|
@@ -1564,7 +1917,7 @@ ${JSON.stringify(res.agents, null, 2)}`);
|
|
|
1564
1917
|
const lifecycle = res.lifecycle ?? "persistent";
|
|
1565
1918
|
output(
|
|
1566
1919
|
res,
|
|
1567
|
-
`Subnet ${res.
|
|
1920
|
+
`Subnet ${res.slug} lifecycle=${lifecycle}` + (res.linked_task_id ? ` (still linked to task ${res.linked_task_id})` : "")
|
|
1568
1921
|
);
|
|
1569
1922
|
} catch (err) {
|
|
1570
1923
|
handleError(err);
|
|
@@ -1580,12 +1933,12 @@ ${JSON.stringify(res.agents, null, 2)}`);
|
|
|
1580
1933
|
const res = await acnDelete(
|
|
1581
1934
|
`/subnets/${subnetId}`
|
|
1582
1935
|
);
|
|
1583
|
-
output(res, `Subnet ${res.
|
|
1936
|
+
output(res, `Subnet ${res.slug} ${res.status}`);
|
|
1584
1937
|
} catch (err) {
|
|
1585
1938
|
handleError(err);
|
|
1586
1939
|
}
|
|
1587
1940
|
});
|
|
1588
|
-
const requests = new
|
|
1941
|
+
const requests = new import_commander12.Command("requests").description(
|
|
1589
1942
|
"Manage join-requests for a subnet (ADR-0004)"
|
|
1590
1943
|
);
|
|
1591
1944
|
requests.command("list <subnet_id>").description(
|
|
@@ -1640,7 +1993,7 @@ ${JSON.stringify(res.agents, null, 2)}`);
|
|
|
1640
1993
|
`/subnets/${sid}/join-requests?status=pending&kind=join_request`
|
|
1641
1994
|
);
|
|
1642
1995
|
for (const r of res.items ?? []) {
|
|
1643
|
-
aggregated.push({
|
|
1996
|
+
aggregated.push({ slug: sid, request: r });
|
|
1644
1997
|
}
|
|
1645
1998
|
} catch {
|
|
1646
1999
|
continue;
|
|
@@ -1727,7 +2080,7 @@ ${JSON.stringify(res.agents, null, 2)}`);
|
|
|
1727
2080
|
}
|
|
1728
2081
|
);
|
|
1729
2082
|
cmd.addCommand(requests);
|
|
1730
|
-
const invitations = new
|
|
2083
|
+
const invitations = new import_commander12.Command("invitations").description(
|
|
1731
2084
|
"Manage invitations on a subnet (ADR-0004)"
|
|
1732
2085
|
);
|
|
1733
2086
|
invitations.command("send <subnet_id>").description(
|
|
@@ -1863,7 +2216,7 @@ ${JSON.stringify(res.agents, null, 2)}`);
|
|
|
1863
2216
|
}
|
|
1864
2217
|
);
|
|
1865
2218
|
cmd.addCommand(invitations);
|
|
1866
|
-
const allowlist = new
|
|
2219
|
+
const allowlist = new import_commander12.Command("allowlist").description(
|
|
1867
2220
|
"Manage a subnet allowlist (ADR-0004)"
|
|
1868
2221
|
);
|
|
1869
2222
|
allowlist.command("list <subnet_id>").description("Owner-only: list allowlist entries for a subnet.").option("--limit <n>", "Page size (1-500, default 100)", "100").option("--offset <n>", "Page offset (default 0)", "0").action(
|
|
@@ -1922,7 +2275,7 @@ ${JSON.stringify(res.agents, null, 2)}`);
|
|
|
1922
2275
|
`/subnets/${subnetId}/allowlist/${opts.agentId}`
|
|
1923
2276
|
);
|
|
1924
2277
|
output(
|
|
1925
|
-
{
|
|
2278
|
+
{ slug: subnetId, agent_id: opts.agentId, removed: true },
|
|
1926
2279
|
`removed ${opts.agentId} from allowlist of subnet ${subnetId}`
|
|
1927
2280
|
);
|
|
1928
2281
|
} catch (err) {
|
|
@@ -1931,7 +2284,7 @@ ${JSON.stringify(res.agents, null, 2)}`);
|
|
|
1931
2284
|
}
|
|
1932
2285
|
);
|
|
1933
2286
|
cmd.addCommand(allowlist);
|
|
1934
|
-
const harness = new
|
|
2287
|
+
const harness = new import_commander12.Command("harness").description("Manage Org Harness webhook for a subnet");
|
|
1935
2288
|
harness.command("set <subnet_id>").description("Register an Org Harness webhook on a subnet you own").requiredOption("--url <url>", "Harness webhook URL (HTTPS)").option("--secret <secret>", "HMAC-SHA256 signing secret (recommended)").action(async (subnetId, opts) => {
|
|
1936
2289
|
const config = loadConfig();
|
|
1937
2290
|
if (!config.api_key) {
|
|
@@ -1944,7 +2297,7 @@ ${JSON.stringify(res.agents, null, 2)}`);
|
|
|
1944
2297
|
harness_secret: opts.secret ?? null
|
|
1945
2298
|
});
|
|
1946
2299
|
const lines = [
|
|
1947
|
-
`Harness registered on subnet ${res.
|
|
2300
|
+
`Harness registered on subnet ${res.slug}`,
|
|
1948
2301
|
` URL : ${res.harness_url}`,
|
|
1949
2302
|
` Signed : ${opts.secret ? "yes (HMAC-SHA256)" : "no (unsigned)"}`
|
|
1950
2303
|
];
|
|
@@ -1964,7 +2317,7 @@ ${JSON.stringify(res.agents, null, 2)}`);
|
|
|
1964
2317
|
harness_url: null,
|
|
1965
2318
|
harness_secret: null
|
|
1966
2319
|
});
|
|
1967
|
-
output(res, `Harness unregistered from subnet ${res.
|
|
2320
|
+
output(res, `Harness unregistered from subnet ${res.slug}`);
|
|
1968
2321
|
} catch (err) {
|
|
1969
2322
|
handleError(err);
|
|
1970
2323
|
}
|
|
@@ -1974,7 +2327,7 @@ ${JSON.stringify(res.agents, null, 2)}`);
|
|
|
1974
2327
|
}
|
|
1975
2328
|
|
|
1976
2329
|
// src/commands/follow.ts
|
|
1977
|
-
var
|
|
2330
|
+
var import_commander13 = require("commander");
|
|
1978
2331
|
function requireAgentId5() {
|
|
1979
2332
|
const config = loadConfig();
|
|
1980
2333
|
if (!config.api_key) {
|
|
@@ -1997,7 +2350,7 @@ function formatAgent2(a, i) {
|
|
|
1997
2350
|
].join("\n");
|
|
1998
2351
|
}
|
|
1999
2352
|
function followCommand() {
|
|
2000
|
-
const cmd = new
|
|
2353
|
+
const cmd = new import_commander13.Command("follow").description("Follow/unfollow agents and inspect follow graph");
|
|
2001
2354
|
cmd.command("add <target_id>").description("Follow another agent").option("-i, --agent-id <id>", "Agent ID (defaults to config)").action(async (targetId, opts) => {
|
|
2002
2355
|
const agentId = opts.agentId ?? requireAgentId5();
|
|
2003
2356
|
try {
|
|
@@ -2088,7 +2441,7 @@ function followCommand() {
|
|
|
2088
2441
|
}
|
|
2089
2442
|
|
|
2090
2443
|
// src/commands/wallet.ts
|
|
2091
|
-
var
|
|
2444
|
+
var import_commander14 = require("commander");
|
|
2092
2445
|
function requireAgentId6() {
|
|
2093
2446
|
const config = loadConfig();
|
|
2094
2447
|
if (!config.api_key) {
|
|
@@ -2131,7 +2484,7 @@ async function showWalletInfo(opts) {
|
|
|
2131
2484
|
}
|
|
2132
2485
|
}
|
|
2133
2486
|
function walletCommand() {
|
|
2134
|
-
const cmd = new
|
|
2487
|
+
const cmd = new import_commander14.Command("wallet").description("View and manage agent's wallet & payment info");
|
|
2135
2488
|
cmd.command("info", { isDefault: true }).description("Show wallet, payment methods, pricing, and ERC-8004 status").option("-i, --agent-id <id>", "Agent ID (defaults to config)").action(showWalletInfo);
|
|
2136
2489
|
cmd.command("set-capability").description("Declare which payment methods, networks, and wallets you accept").requiredOption(
|
|
2137
2490
|
"--methods <csv>",
|
|
@@ -2310,7 +2663,7 @@ function walletCommand() {
|
|
|
2310
2663
|
}
|
|
2311
2664
|
|
|
2312
2665
|
// src/commands/pay.ts
|
|
2313
|
-
var
|
|
2666
|
+
var import_commander15 = require("commander");
|
|
2314
2667
|
function requireAgentId7() {
|
|
2315
2668
|
const config = loadConfig();
|
|
2316
2669
|
if (!config.api_key) {
|
|
@@ -2324,8 +2677,8 @@ function requireAgentId7() {
|
|
|
2324
2677
|
return config.agent_id;
|
|
2325
2678
|
}
|
|
2326
2679
|
function payCommand() {
|
|
2327
|
-
const cmd = new
|
|
2328
|
-
const createCmd = new
|
|
2680
|
+
const cmd = new import_commander15.Command("pay").description("Manage payment tasks between agents");
|
|
2681
|
+
const createCmd = new import_commander15.Command("create").description("Create a payment task to another agent");
|
|
2329
2682
|
createCmd.requiredOption("--to <agent>", "Recipient agent ID").requiredOption("--amount <n>", "Payment amount (positive number)").requiredOption("--currency <c>", "Currency code, e.g. USD, USDC").requiredOption("--method <m>", "Payment method, e.g. usdc, eth, platform_credits").requiredOption("--network <n>", "Network, e.g. ethereum, base, solana").option("--description <text>", "Free-text description for the payment task").option("--metadata <json>", "Additional metadata as JSON object").action(
|
|
2330
2683
|
async (opts) => {
|
|
2331
2684
|
const fromAgent = requireAgentId7();
|
|
@@ -2371,7 +2724,7 @@ function payCommand() {
|
|
|
2371
2724
|
}
|
|
2372
2725
|
}
|
|
2373
2726
|
);
|
|
2374
|
-
const confirmCmd = new
|
|
2727
|
+
const confirmCmd = new import_commander15.Command("confirm").description(
|
|
2375
2728
|
"Confirm an external payment has been made (buyer only)"
|
|
2376
2729
|
);
|
|
2377
2730
|
confirmCmd.requiredOption("--task-id <id>", "Payment task ID to confirm").requiredOption(
|
|
@@ -2393,7 +2746,7 @@ function payCommand() {
|
|
|
2393
2746
|
handleError(err);
|
|
2394
2747
|
}
|
|
2395
2748
|
});
|
|
2396
|
-
const statusCmd = new
|
|
2749
|
+
const statusCmd = new import_commander15.Command("status").description(
|
|
2397
2750
|
"Show payment tasks for the authenticated agent"
|
|
2398
2751
|
);
|
|
2399
2752
|
statusCmd.option("--status <s>", "Filter by status (e.g. created, payment_confirmed)").option("--limit <n>", "Max results (default 50)", "50").action(async (opts) => {
|
|
@@ -2415,8 +2768,9 @@ function payCommand() {
|
|
|
2415
2768
|
}
|
|
2416
2769
|
|
|
2417
2770
|
// src/index.ts
|
|
2418
|
-
var
|
|
2419
|
-
program
|
|
2771
|
+
var { version } = require_package();
|
|
2772
|
+
var program = new import_commander16.Command();
|
|
2773
|
+
program.name("acn").description("ACN CLI \u2014 Agent Collaboration Network command-line interface").version(version).option("--json", "Output raw JSON (useful for agent parsing)").hook("preAction", (thisCommand) => {
|
|
2420
2774
|
const opts = thisCommand.opts();
|
|
2421
2775
|
if (opts.json) setJsonMode(true);
|
|
2422
2776
|
});
|
|
@@ -2429,6 +2783,7 @@ program.addCommand(tasksCommand());
|
|
|
2429
2783
|
program.addCommand(messageCommand());
|
|
2430
2784
|
program.addCommand(notifyCommand());
|
|
2431
2785
|
program.addCommand(inboxCommand());
|
|
2786
|
+
program.addCommand(listenCommand());
|
|
2432
2787
|
program.addCommand(sessionCommand());
|
|
2433
2788
|
program.addCommand(subnetCommand());
|
|
2434
2789
|
program.addCommand(followCommand());
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@acnlabs/acn-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "Official CLI for ACN (Agent Collaboration Network) — zero-integration agent access",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -38,10 +38,12 @@
|
|
|
38
38
|
"url": "https://github.com/acnlabs/ACN/issues"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"commander": "^12.0.0"
|
|
41
|
+
"commander": "^12.0.0",
|
|
42
|
+
"ws": "^8.18.0"
|
|
42
43
|
},
|
|
43
44
|
"devDependencies": {
|
|
44
45
|
"@types/node": "^20.0.0",
|
|
46
|
+
"@types/ws": "^8.5.10",
|
|
45
47
|
"tsup": "^8.0.0",
|
|
46
48
|
"typescript": "^5.0.0",
|
|
47
49
|
"vitest": "^3.0.5"
|