@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.
Files changed (2) hide show
  1. package/dist/index.js +415 -60
  2. 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 import_commander15 = require("commander");
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
- const detail = typeof body === "object" && body !== null && "detail" in body ? String(body.detail) : String(body);
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").action(async (opts) => {
189
- const tags = opts.tags.split(",").map((s) => s.trim()).filter(Boolean);
190
- const body = {
191
- name: opts.name,
192
- description: opts.description ?? `${opts.name} \u2014 registered via acn-cli`,
193
- tags,
194
- ...opts.endpoint ? { endpoint: opts.endpoint } : {}
195
- };
196
- try {
197
- const res = await acnPost("/agents/join", body);
198
- saveConfig({ api_key: res.api_key, agent_id: res.agent_id });
199
- const claimLine = res.claim_url ? `
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
- const verifyLine = res.verification_code ? `
302
+ const verifyLine = res.verification_code ? `
202
303
  Verify : ${res.verification_code}` : "";
203
- output(res, [
204
- `Registered successfully!`,
205
- ` Agent ID : ${res.agent_id}`,
206
- ` API Key : ${res.api_key}`,
207
- ` Status : ${res.status}${claimLine}${verifyLine}`,
208
- ``,
209
- `Credentials saved to ~/.acn/config.json`
210
- ].join("\n"));
211
- } catch (err) {
212
- handleError(err);
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/session.ts
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 import_commander10.Command("session").description(
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 import_commander11 = require("commander");
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.subnet_id}`,
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.subnet_id}${privacy}`, ` Name : ${s.name}`];
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 import_commander11.Command("subnet").description("Manage ACN subnets");
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.subnet_id = opts.id;
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.parent_subnet_id = opts.parent;
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.subnet_id}`,
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.subnet_id} lifecycle=${lifecycle}` + (res.linked_task_id ? ` (still linked to task ${res.linked_task_id})` : "")
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.subnet_id} ${res.status}`);
1936
+ output(res, `Subnet ${res.slug} ${res.status}`);
1584
1937
  } catch (err) {
1585
1938
  handleError(err);
1586
1939
  }
1587
1940
  });
1588
- const requests = new import_commander11.Command("requests").description(
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({ subnet_id: sid, request: r });
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 import_commander11.Command("invitations").description(
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 import_commander11.Command("allowlist").description(
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
- { subnet_id: subnetId, agent_id: opts.agentId, removed: true },
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 import_commander11.Command("harness").description("Manage Org Harness webhook for a subnet");
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.subnet_id}`,
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.subnet_id}`);
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 import_commander12 = require("commander");
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 import_commander12.Command("follow").description("Follow/unfollow agents and inspect follow graph");
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 import_commander13 = require("commander");
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 import_commander13.Command("wallet").description("View and manage agent's wallet & payment info");
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 import_commander14 = require("commander");
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 import_commander14.Command("pay").description("Manage payment tasks between agents");
2328
- const createCmd = new import_commander14.Command("create").description("Create a payment task to another agent");
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 import_commander14.Command("confirm").description(
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 import_commander14.Command("status").description(
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 program = new import_commander15.Command();
2419
- program.name("acn").description("ACN CLI \u2014 Agent Collaboration Network command-line interface").version("0.1.0").option("--json", "Output raw JSON (useful for agent parsing)").hook("preAction", (thisCommand) => {
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.11.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"