@acnlabs/acn-cli 0.13.2 → 0.14.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 +40 -0
- package/dist/index.js +797 -83
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -67,6 +67,46 @@ acn heartbeat
|
|
|
67
67
|
acn heartbeat --agent-id <id> # override agent ID
|
|
68
68
|
```
|
|
69
69
|
|
|
70
|
+
### `acn listen` (Mode B — no public endpoint)
|
|
71
|
+
|
|
72
|
+
Hold an outbound WebSocket and receive relayed A2A requests in real time.
|
|
73
|
+
|
|
74
|
+
**Production (recommended):** built-in A2A receiver + wake your host runtime.
|
|
75
|
+
No local A2A port required.
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
# Register for relay delivery, then:
|
|
79
|
+
acn listen --runtime http \
|
|
80
|
+
--wake-url http://127.0.0.1:10122/hooks/agent \
|
|
81
|
+
--wake-header 'Authorization: Bearer …'
|
|
82
|
+
|
|
83
|
+
acn listen --runtime command --wake-exec '/path/to/wake.sh'
|
|
84
|
+
acn listen --runtime log # debug: print normalized events to stderr
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Semantics: CLI answers `message/send` / `message/stream` with a valid A2A
|
|
88
|
+
`accepted` message **immediately**, then POSTs/execs a normalized event to
|
|
89
|
+
wake the host. Wake failure is logged (`wake_failed`) and does **not** fail
|
|
90
|
+
the A2A reply. Dedupe is on by default (`--no-dedupe` to disable).
|
|
91
|
+
|
|
92
|
+
**Coverage:** only traffic that arrives over the Mode B relay. Open Task Pool
|
|
93
|
+
rows that were never pushed as A2A still need `acn tasks list` / reconcile.
|
|
94
|
+
|
|
95
|
+
**Compat (advanced):** tunnel to your own A2A server, or let a subprocess
|
|
96
|
+
print the full JSON-RPC response:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
acn listen --forward http://127.0.0.1:8080
|
|
100
|
+
acn listen --exec './handle-a2a.sh' # stdout = full A2A JSON-RPC body
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
> Do not confuse legacy `--exec` with `--runtime command --wake-exec`.
|
|
104
|
+
> The former must emit a protocol-valid A2A response; the latter only wakes
|
|
105
|
+
> the host after the CLI has already answered A2A.
|
|
106
|
+
|
|
107
|
+
Keep `acn listen` and `acn heartbeat` in the same lifecycle for idle agents
|
|
108
|
+
(see [listen + heartbeat systemd example](../../docs/runbooks/acn-listen-heartbeat.md)).
|
|
109
|
+
|
|
70
110
|
### `acn agents`
|
|
71
111
|
|
|
72
112
|
Discover agents on ACN.
|
package/dist/index.js
CHANGED
|
@@ -31,7 +31,7 @@ var require_package = __commonJS({
|
|
|
31
31
|
"package.json"(exports2, module2) {
|
|
32
32
|
module2.exports = {
|
|
33
33
|
name: "@acnlabs/acn-cli",
|
|
34
|
-
version: "0.
|
|
34
|
+
version: "0.14.0",
|
|
35
35
|
description: "Official CLI for ACN (Agent Collaboration Network) \u2014 zero-integration agent access",
|
|
36
36
|
main: "dist/index.js",
|
|
37
37
|
bin: {
|
|
@@ -87,7 +87,7 @@ var require_package = __commonJS({
|
|
|
87
87
|
});
|
|
88
88
|
|
|
89
89
|
// src/index.ts
|
|
90
|
-
var
|
|
90
|
+
var import_commander18 = require("commander");
|
|
91
91
|
|
|
92
92
|
// src/output.ts
|
|
93
93
|
var jsonMode = false;
|
|
@@ -1375,8 +1375,346 @@ ${formatPolicy(res)}`);
|
|
|
1375
1375
|
|
|
1376
1376
|
// src/commands/listen.ts
|
|
1377
1377
|
var import_commander10 = require("commander");
|
|
1378
|
-
var
|
|
1378
|
+
var import_child_process2 = require("child_process");
|
|
1379
1379
|
var import_ws = __toESM(require("ws"));
|
|
1380
|
+
|
|
1381
|
+
// src/commands/normalize-event.ts
|
|
1382
|
+
function asRecord(v) {
|
|
1383
|
+
return v !== null && typeof v === "object" && !Array.isArray(v) ? v : null;
|
|
1384
|
+
}
|
|
1385
|
+
function asNonEmptyString(v) {
|
|
1386
|
+
return typeof v === "string" && v.length > 0 ? v : null;
|
|
1387
|
+
}
|
|
1388
|
+
function parseJsonRpcBody(bodyText) {
|
|
1389
|
+
let parsed;
|
|
1390
|
+
try {
|
|
1391
|
+
parsed = JSON.parse(bodyText);
|
|
1392
|
+
} catch {
|
|
1393
|
+
return { ok: false, code: -32700, message: "Parse error" };
|
|
1394
|
+
}
|
|
1395
|
+
const body = asRecord(parsed);
|
|
1396
|
+
if (!body) {
|
|
1397
|
+
return { ok: false, code: -32600, message: "Invalid Request" };
|
|
1398
|
+
}
|
|
1399
|
+
if (body.jsonrpc !== "2.0" || typeof body.method !== "string") {
|
|
1400
|
+
return { ok: false, code: -32600, message: "Invalid Request" };
|
|
1401
|
+
}
|
|
1402
|
+
return { ok: true, body };
|
|
1403
|
+
}
|
|
1404
|
+
function extractTaskId(message) {
|
|
1405
|
+
const metadata = asRecord(message.metadata);
|
|
1406
|
+
if (metadata) {
|
|
1407
|
+
const fromMeta = asNonEmptyString(metadata.task_id) ?? asNonEmptyString(metadata.acn_task_id);
|
|
1408
|
+
if (fromMeta) return fromMeta;
|
|
1409
|
+
}
|
|
1410
|
+
const parts = message.parts;
|
|
1411
|
+
if (!Array.isArray(parts)) return null;
|
|
1412
|
+
for (const part of parts) {
|
|
1413
|
+
const p = asRecord(part);
|
|
1414
|
+
if (!p || p.kind !== "data") continue;
|
|
1415
|
+
const data = asRecord(p.data);
|
|
1416
|
+
if (!data) continue;
|
|
1417
|
+
const fromData = asNonEmptyString(data.task_id) ?? asNonEmptyString(data.acn_task_id);
|
|
1418
|
+
if (fromData) return fromData;
|
|
1419
|
+
}
|
|
1420
|
+
return null;
|
|
1421
|
+
}
|
|
1422
|
+
function extractMessageId(message, generateId) {
|
|
1423
|
+
return asNonEmptyString(message.messageId) ?? asNonEmptyString(message.message_id) ?? generateId();
|
|
1424
|
+
}
|
|
1425
|
+
function extractContextId(message) {
|
|
1426
|
+
return asNonEmptyString(message.contextId) ?? asNonEmptyString(message.context_id);
|
|
1427
|
+
}
|
|
1428
|
+
function extractFromAgent(message) {
|
|
1429
|
+
const metadata = asRecord(message.metadata);
|
|
1430
|
+
if (!metadata) return null;
|
|
1431
|
+
return asNonEmptyString(metadata.from_agent) ?? asNonEmptyString(metadata.fromAgent);
|
|
1432
|
+
}
|
|
1433
|
+
function normalizeEvent(body, opts = {}) {
|
|
1434
|
+
const generateId = opts.generateId ?? (() => crypto.randomUUID());
|
|
1435
|
+
const now = opts.now ?? (() => /* @__PURE__ */ new Date());
|
|
1436
|
+
const params = asRecord(body.params);
|
|
1437
|
+
const message = asRecord(params?.message) ?? {};
|
|
1438
|
+
return {
|
|
1439
|
+
event_type: "a2a_message",
|
|
1440
|
+
task_id: extractTaskId(message),
|
|
1441
|
+
message_id: extractMessageId(message, generateId),
|
|
1442
|
+
context_id: extractContextId(message),
|
|
1443
|
+
from_agent: extractFromAgent(message),
|
|
1444
|
+
received_at: now().toISOString(),
|
|
1445
|
+
raw: body
|
|
1446
|
+
};
|
|
1447
|
+
}
|
|
1448
|
+
function dedupeKey(event) {
|
|
1449
|
+
return event.task_id ?? event.message_id;
|
|
1450
|
+
}
|
|
1451
|
+
var DedupeStore = class {
|
|
1452
|
+
constructor(ttlSec) {
|
|
1453
|
+
this.ttlSec = ttlSec;
|
|
1454
|
+
}
|
|
1455
|
+
ttlSec;
|
|
1456
|
+
map = /* @__PURE__ */ new Map();
|
|
1457
|
+
/** Returns true if key was already seen within TTL; otherwise marks and returns false. */
|
|
1458
|
+
isDuplicate(key, nowMs = Date.now()) {
|
|
1459
|
+
this.gc(nowMs);
|
|
1460
|
+
const exp = this.map.get(key);
|
|
1461
|
+
if (exp !== void 0 && exp > nowMs) return true;
|
|
1462
|
+
this.map.set(key, nowMs + this.ttlSec * 1e3);
|
|
1463
|
+
return false;
|
|
1464
|
+
}
|
|
1465
|
+
/**
|
|
1466
|
+
* Drop a key so a later retry can wake again.
|
|
1467
|
+
* Used when wake fails after we reserved the slot on accept.
|
|
1468
|
+
*/
|
|
1469
|
+
forget(key) {
|
|
1470
|
+
this.map.delete(key);
|
|
1471
|
+
}
|
|
1472
|
+
/** Test helper — current window size after GC. */
|
|
1473
|
+
size(nowMs = Date.now()) {
|
|
1474
|
+
this.gc(nowMs);
|
|
1475
|
+
return this.map.size;
|
|
1476
|
+
}
|
|
1477
|
+
gc(nowMs) {
|
|
1478
|
+
for (const [k, exp] of this.map) {
|
|
1479
|
+
if (exp <= nowMs) this.map.delete(k);
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
};
|
|
1483
|
+
|
|
1484
|
+
// src/commands/local-receiver.ts
|
|
1485
|
+
var import_crypto = require("crypto");
|
|
1486
|
+
|
|
1487
|
+
// src/commands/runtime-adapter.ts
|
|
1488
|
+
var import_child_process = require("child_process");
|
|
1489
|
+
var DEFAULT_WAKE_TIMEOUT_MS = 5e3;
|
|
1490
|
+
function parseWakeHeaders(raw) {
|
|
1491
|
+
const out = {};
|
|
1492
|
+
for (const item of raw ?? []) {
|
|
1493
|
+
const idx = item.indexOf(":");
|
|
1494
|
+
if (idx <= 0) {
|
|
1495
|
+
throw new Error(
|
|
1496
|
+
`Invalid --wake-header "${item}". Expected "Header-Name: value".`
|
|
1497
|
+
);
|
|
1498
|
+
}
|
|
1499
|
+
const key = item.slice(0, idx).trim();
|
|
1500
|
+
const value = item.slice(idx + 1).trim();
|
|
1501
|
+
if (!key) {
|
|
1502
|
+
throw new Error(
|
|
1503
|
+
`Invalid --wake-header "${item}". Expected "Header-Name: value".`
|
|
1504
|
+
);
|
|
1505
|
+
}
|
|
1506
|
+
out[key] = value;
|
|
1507
|
+
}
|
|
1508
|
+
return out;
|
|
1509
|
+
}
|
|
1510
|
+
function validateRuntimeOptions(opts) {
|
|
1511
|
+
if (!opts.runtime) return null;
|
|
1512
|
+
if (opts.runtime !== "http" && opts.runtime !== "command" && opts.runtime !== "log") {
|
|
1513
|
+
return `Unknown --runtime "${opts.runtime}". Use: http | command | log`;
|
|
1514
|
+
}
|
|
1515
|
+
if (opts.runtime === "http" && !opts.wakeUrl) {
|
|
1516
|
+
return "--runtime http requires --wake-url <url>";
|
|
1517
|
+
}
|
|
1518
|
+
if (opts.runtime === "command" && !opts.wakeExec) {
|
|
1519
|
+
return "--runtime command requires --wake-exec <cmd>";
|
|
1520
|
+
}
|
|
1521
|
+
return null;
|
|
1522
|
+
}
|
|
1523
|
+
async function wakeHttp(event, opts, deps) {
|
|
1524
|
+
const fetchFn = deps.fetchFn ?? fetch;
|
|
1525
|
+
const timeoutMs = opts.wakeTimeoutMs ?? DEFAULT_WAKE_TIMEOUT_MS;
|
|
1526
|
+
const controller = new AbortController();
|
|
1527
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
1528
|
+
try {
|
|
1529
|
+
const res = await fetchFn(opts.wakeUrl, {
|
|
1530
|
+
method: "POST",
|
|
1531
|
+
headers: {
|
|
1532
|
+
"content-type": "application/json",
|
|
1533
|
+
...opts.wakeHeaders ?? {}
|
|
1534
|
+
},
|
|
1535
|
+
body: JSON.stringify(event),
|
|
1536
|
+
signal: controller.signal
|
|
1537
|
+
});
|
|
1538
|
+
try {
|
|
1539
|
+
await res.arrayBuffer();
|
|
1540
|
+
} catch {
|
|
1541
|
+
}
|
|
1542
|
+
if (res.status < 200 || res.status >= 300) {
|
|
1543
|
+
return { ok: false, reason: `http_${res.status}` };
|
|
1544
|
+
}
|
|
1545
|
+
return { ok: true };
|
|
1546
|
+
} catch (err) {
|
|
1547
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1548
|
+
if (controller.signal.aborted || /abort|timeout/i.test(msg)) {
|
|
1549
|
+
return { ok: false, reason: "timeout" };
|
|
1550
|
+
}
|
|
1551
|
+
return { ok: false, reason: msg.slice(0, 200) };
|
|
1552
|
+
} finally {
|
|
1553
|
+
clearTimeout(timer);
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
function wakeCommand(event, opts, deps) {
|
|
1557
|
+
const spawnFn = deps.spawnFn ?? import_child_process.spawn;
|
|
1558
|
+
const timeoutMs = opts.wakeTimeoutMs ?? DEFAULT_WAKE_TIMEOUT_MS;
|
|
1559
|
+
const body = Buffer.from(JSON.stringify(event), "utf-8");
|
|
1560
|
+
return new Promise((resolve) => {
|
|
1561
|
+
let settled = false;
|
|
1562
|
+
const child = spawnFn(opts.wakeExec, { shell: true });
|
|
1563
|
+
const errOut = [];
|
|
1564
|
+
const finish = (result) => {
|
|
1565
|
+
if (settled) return;
|
|
1566
|
+
settled = true;
|
|
1567
|
+
clearTimeout(timer);
|
|
1568
|
+
resolve(result);
|
|
1569
|
+
};
|
|
1570
|
+
const timer = setTimeout(() => {
|
|
1571
|
+
child.kill("SIGTERM");
|
|
1572
|
+
finish({ ok: false, reason: "timeout" });
|
|
1573
|
+
}, timeoutMs);
|
|
1574
|
+
child.stderr?.on("data", (d) => errOut.push(Buffer.from(d)));
|
|
1575
|
+
child.on(
|
|
1576
|
+
"error",
|
|
1577
|
+
(e) => finish({ ok: false, reason: e.message.slice(0, 200) })
|
|
1578
|
+
);
|
|
1579
|
+
child.on("close", (code) => {
|
|
1580
|
+
if (code === 0) finish({ ok: true });
|
|
1581
|
+
else {
|
|
1582
|
+
const detail = Buffer.concat(errOut).toString("utf-8").slice(0, 80);
|
|
1583
|
+
finish({
|
|
1584
|
+
ok: false,
|
|
1585
|
+
reason: detail ? `exit_${code}:${detail}` : `exit_${code}`
|
|
1586
|
+
});
|
|
1587
|
+
}
|
|
1588
|
+
});
|
|
1589
|
+
child.stdin?.end(body);
|
|
1590
|
+
});
|
|
1591
|
+
}
|
|
1592
|
+
function wakeLog(event, deps) {
|
|
1593
|
+
const logFn = deps.logFn ?? ((line) => console.error(line));
|
|
1594
|
+
logFn(JSON.stringify(event));
|
|
1595
|
+
return { ok: true };
|
|
1596
|
+
}
|
|
1597
|
+
async function wakeRuntime(event, opts, deps = {}) {
|
|
1598
|
+
switch (opts.runtime) {
|
|
1599
|
+
case "http":
|
|
1600
|
+
return wakeHttp(event, opts, deps);
|
|
1601
|
+
case "command":
|
|
1602
|
+
return wakeCommand(event, opts, deps);
|
|
1603
|
+
case "log":
|
|
1604
|
+
return wakeLog(event, deps);
|
|
1605
|
+
default:
|
|
1606
|
+
return { ok: false, reason: `unknown_runtime` };
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
// src/commands/local-receiver.ts
|
|
1611
|
+
var HANDLED_METHODS = /* @__PURE__ */ new Set(["message/send", "message/stream"]);
|
|
1612
|
+
function jsonRpcResponse(correlationId, jsonrpcId, payload) {
|
|
1613
|
+
return {
|
|
1614
|
+
type: "a2a_response",
|
|
1615
|
+
id: correlationId,
|
|
1616
|
+
status: 200,
|
|
1617
|
+
headers: { "content-type": "application/json" },
|
|
1618
|
+
body: JSON.stringify({
|
|
1619
|
+
jsonrpc: "2.0",
|
|
1620
|
+
id: jsonrpcId ?? null,
|
|
1621
|
+
...payload
|
|
1622
|
+
})
|
|
1623
|
+
};
|
|
1624
|
+
}
|
|
1625
|
+
function acceptedMessage(correlationId, jsonrpcId, messageId) {
|
|
1626
|
+
return jsonRpcResponse(correlationId, jsonrpcId, {
|
|
1627
|
+
result: {
|
|
1628
|
+
kind: "message",
|
|
1629
|
+
messageId,
|
|
1630
|
+
role: "agent",
|
|
1631
|
+
parts: [{ kind: "text", text: "accepted" }]
|
|
1632
|
+
}
|
|
1633
|
+
});
|
|
1634
|
+
}
|
|
1635
|
+
function jsonRpcError(correlationId, jsonrpcId, code, message) {
|
|
1636
|
+
return jsonRpcResponse(correlationId, jsonrpcId, {
|
|
1637
|
+
error: { code, message }
|
|
1638
|
+
});
|
|
1639
|
+
}
|
|
1640
|
+
function processIncomingRequest(correlationId, bodyText, opts, dedupeStore, deps = {}) {
|
|
1641
|
+
const generateId = deps.generateId ?? (() => (0, import_crypto.randomUUID)());
|
|
1642
|
+
const parsed = parseJsonRpcBody(bodyText);
|
|
1643
|
+
if (!parsed.ok) {
|
|
1644
|
+
return {
|
|
1645
|
+
response: jsonRpcError(correlationId, null, parsed.code, parsed.message),
|
|
1646
|
+
event: null,
|
|
1647
|
+
shouldWake: false,
|
|
1648
|
+
dedupeHit: false
|
|
1649
|
+
};
|
|
1650
|
+
}
|
|
1651
|
+
const { body } = parsed;
|
|
1652
|
+
const jsonrpcId = body.id ?? null;
|
|
1653
|
+
const method = body.method;
|
|
1654
|
+
if (!HANDLED_METHODS.has(method)) {
|
|
1655
|
+
return {
|
|
1656
|
+
response: jsonRpcError(
|
|
1657
|
+
correlationId,
|
|
1658
|
+
jsonrpcId,
|
|
1659
|
+
-32601,
|
|
1660
|
+
`Method not found: ${method}`
|
|
1661
|
+
),
|
|
1662
|
+
event: null,
|
|
1663
|
+
shouldWake: false,
|
|
1664
|
+
dedupeHit: false
|
|
1665
|
+
};
|
|
1666
|
+
}
|
|
1667
|
+
const event = normalizeEvent(body, {
|
|
1668
|
+
generateId,
|
|
1669
|
+
now: deps.now
|
|
1670
|
+
});
|
|
1671
|
+
const replyMessageId = generateId();
|
|
1672
|
+
const response = acceptedMessage(correlationId, jsonrpcId, replyMessageId);
|
|
1673
|
+
if (opts.dedupe) {
|
|
1674
|
+
const key = dedupeKey(event);
|
|
1675
|
+
if (dedupeStore.isDuplicate(key)) {
|
|
1676
|
+
return { response, event, shouldWake: false, dedupeHit: true };
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
return { response, event, shouldWake: true, dedupeHit: false };
|
|
1680
|
+
}
|
|
1681
|
+
function formatWakeFailed(event, reason) {
|
|
1682
|
+
const task = event.task_id ?? "-";
|
|
1683
|
+
return `[acn listen] wake_failed message_id=${event.message_id} task_id=${task} reason=${reason}`;
|
|
1684
|
+
}
|
|
1685
|
+
function formatDeduped(event) {
|
|
1686
|
+
return `[acn listen] deduped key=${dedupeKey(event)}`;
|
|
1687
|
+
}
|
|
1688
|
+
function dispatchLocalReceiver(correlationId, bodyText, opts, dedupeStore, send, deps = {}) {
|
|
1689
|
+
const logFn = deps.logFn ?? ((line) => console.error(line));
|
|
1690
|
+
const result = processIncomingRequest(
|
|
1691
|
+
correlationId,
|
|
1692
|
+
bodyText,
|
|
1693
|
+
opts,
|
|
1694
|
+
dedupeStore,
|
|
1695
|
+
deps
|
|
1696
|
+
);
|
|
1697
|
+
send(result.response);
|
|
1698
|
+
if (result.dedupeHit && result.event) {
|
|
1699
|
+
logFn(formatDeduped(result.event));
|
|
1700
|
+
return;
|
|
1701
|
+
}
|
|
1702
|
+
if (!result.shouldWake || !result.event) return;
|
|
1703
|
+
const event = result.event;
|
|
1704
|
+
const key = opts.dedupe ? dedupeKey(event) : null;
|
|
1705
|
+
void wakeRuntime(event, opts, deps).then((wake) => {
|
|
1706
|
+
if (!wake.ok) {
|
|
1707
|
+
if (key) dedupeStore.forget(key);
|
|
1708
|
+
logFn(formatWakeFailed(event, wake.reason));
|
|
1709
|
+
}
|
|
1710
|
+
}).catch((err) => {
|
|
1711
|
+
if (key) dedupeStore.forget(key);
|
|
1712
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1713
|
+
logFn(formatWakeFailed(event, msg.slice(0, 200)));
|
|
1714
|
+
});
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
// src/commands/listen.ts
|
|
1380
1718
|
var STRIP_HEADERS = /* @__PURE__ */ new Set([
|
|
1381
1719
|
"host",
|
|
1382
1720
|
"content-length",
|
|
@@ -1408,12 +1746,28 @@ function errorResponse(id, status, detail) {
|
|
|
1408
1746
|
async function dispatchA2aRequest(frame, opts, send, deps = {}) {
|
|
1409
1747
|
const bodyBuf = decodeBody(frame);
|
|
1410
1748
|
try {
|
|
1749
|
+
if (opts.runtime) {
|
|
1750
|
+
const store = deps.dedupeStore ?? new DedupeStore(opts.runtime.dedupeTtlSec);
|
|
1751
|
+
dispatchLocalReceiver(
|
|
1752
|
+
frame.id,
|
|
1753
|
+
bodyBuf.toString("utf-8"),
|
|
1754
|
+
opts.runtime,
|
|
1755
|
+
store,
|
|
1756
|
+
send,
|
|
1757
|
+
{
|
|
1758
|
+
fetchFn: deps.fetchFn,
|
|
1759
|
+
spawnFn: deps.spawnFn,
|
|
1760
|
+
logFn: deps.logFn
|
|
1761
|
+
}
|
|
1762
|
+
);
|
|
1763
|
+
return;
|
|
1764
|
+
}
|
|
1411
1765
|
if (opts.forward) {
|
|
1412
1766
|
await forwardToHttp(frame, bodyBuf, opts.forward, deps.fetchFn ?? fetch, send);
|
|
1413
1767
|
return;
|
|
1414
1768
|
}
|
|
1415
1769
|
if (opts.exec) {
|
|
1416
|
-
send(await runExec(frame, bodyBuf, opts.exec, deps.spawnFn ??
|
|
1770
|
+
send(await runExec(frame, bodyBuf, opts.exec, deps.spawnFn ?? import_child_process2.spawn));
|
|
1417
1771
|
return;
|
|
1418
1772
|
}
|
|
1419
1773
|
send(errorResponse(frame.id, 500, "no handler configured"));
|
|
@@ -1518,13 +1872,15 @@ function runListener(cfg) {
|
|
|
1518
1872
|
const wsUrl = toWebsocketUrl(cfg.baseUrl, cfg.agentId);
|
|
1519
1873
|
let backoff = INITIAL_BACKOFF_MS;
|
|
1520
1874
|
let stopped = false;
|
|
1875
|
+
const dedupeStore = cfg.runtime ? new DedupeStore(cfg.runtime.dedupeTtlSec) : void 0;
|
|
1521
1876
|
const connect = () => {
|
|
1522
1877
|
const ws = new import_ws.default(wsUrl, {
|
|
1523
1878
|
headers: { Authorization: `Bearer ${cfg.apiKey}` }
|
|
1524
1879
|
});
|
|
1525
1880
|
let keepalive;
|
|
1526
1881
|
ws.on("open", () => {
|
|
1527
|
-
|
|
1882
|
+
const mode = cfg.runtime ? `runtime=${cfg.runtime.runtime}` : cfg.forward ? `forward=${cfg.forward}` : `exec`;
|
|
1883
|
+
console.error(`[acn listen] connected as ${cfg.agentId} \u2192 ${wsUrl} (${mode})`);
|
|
1528
1884
|
backoff = INITIAL_BACKOFF_MS;
|
|
1529
1885
|
keepalive = setInterval(() => {
|
|
1530
1886
|
if (ws.readyState === import_ws.default.OPEN) {
|
|
@@ -1547,7 +1903,7 @@ function runListener(cfg) {
|
|
|
1547
1903
|
ws.send(JSON.stringify(out));
|
|
1548
1904
|
}
|
|
1549
1905
|
};
|
|
1550
|
-
void dispatchA2aRequest(f, cfg, send);
|
|
1906
|
+
void dispatchA2aRequest(f, cfg, send, { dedupeStore });
|
|
1551
1907
|
}
|
|
1552
1908
|
});
|
|
1553
1909
|
ws.on("close", (code, reason) => {
|
|
@@ -1576,53 +1932,220 @@ function runListener(cfg) {
|
|
|
1576
1932
|
});
|
|
1577
1933
|
connect();
|
|
1578
1934
|
}
|
|
1935
|
+
function collectWakeHeader(value, previous) {
|
|
1936
|
+
previous.push(value);
|
|
1937
|
+
return previous;
|
|
1938
|
+
}
|
|
1939
|
+
function validateListenHandlerFlags(opts) {
|
|
1940
|
+
const modes = [opts.runtime, opts.forward, opts.exec].filter(Boolean);
|
|
1941
|
+
if (modes.length === 0) {
|
|
1942
|
+
return "Provide a handler: --runtime http|command|log (recommended), or legacy --forward <url> / --exec <command>.";
|
|
1943
|
+
}
|
|
1944
|
+
if (modes.length > 1) {
|
|
1945
|
+
return "Use only one handler: --runtime, --forward, or --exec \u2014 not combined.";
|
|
1946
|
+
}
|
|
1947
|
+
return validateRuntimeOptions({
|
|
1948
|
+
runtime: opts.runtime,
|
|
1949
|
+
wakeUrl: opts.wakeUrl,
|
|
1950
|
+
wakeExec: opts.wakeExec
|
|
1951
|
+
});
|
|
1952
|
+
}
|
|
1579
1953
|
function listenCommand() {
|
|
1580
1954
|
const cmd = new import_commander10.Command("listen").description(
|
|
1581
|
-
"Hold an outbound connection to ACN and answer relayed A2A requests in real time (ADR-0012 Mode B).
|
|
1955
|
+
"Hold an outbound connection to ACN and answer relayed A2A requests in real time (ADR-0012 Mode B). Prefer --runtime for production; --forward/--exec remain as compatibility tunnels."
|
|
1956
|
+
).option(
|
|
1957
|
+
"--runtime <id>",
|
|
1958
|
+
"Built-in A2A receiver + wake host: http | command | log (no local A2A port)"
|
|
1959
|
+
).option("--wake-url <url>", "POST target for --runtime http").option(
|
|
1960
|
+
"--wake-header <k:v>",
|
|
1961
|
+
"Extra header for --runtime http (repeatable)",
|
|
1962
|
+
collectWakeHeader,
|
|
1963
|
+
[]
|
|
1582
1964
|
).option(
|
|
1965
|
+
"--wake-exec <cmd>",
|
|
1966
|
+
"Shell command for --runtime command (event JSON on stdin). Not the same as legacy --exec (which must print a full A2A response)."
|
|
1967
|
+
).option(
|
|
1968
|
+
"--wake-timeout <ms>",
|
|
1969
|
+
"Wake timeout in ms (default 5000)",
|
|
1970
|
+
String(DEFAULT_WAKE_TIMEOUT_MS)
|
|
1971
|
+
).option("--no-dedupe", "Disable in-process task/message id dedupe (default: on)").option("--dedupe-ttl <sec>", "Dedupe window seconds (default 3600)", "3600").option(
|
|
1583
1972
|
"--forward <url>",
|
|
1584
|
-
"
|
|
1973
|
+
"Compat: tunnel each request to a local A2A HTTP server"
|
|
1585
1974
|
).option(
|
|
1586
1975
|
"--exec <command>",
|
|
1587
|
-
"
|
|
1588
|
-
).option("-i, --agent-id <id>", "Agent ID (defaults to config)").action(
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1976
|
+
"Compat: shell per request; stdout must be a full A2A JSON-RPC response"
|
|
1977
|
+
).option("-i, --agent-id <id>", "Agent ID (defaults to config)").action(
|
|
1978
|
+
(opts) => {
|
|
1979
|
+
const config = loadConfig();
|
|
1980
|
+
const apiKey = config.api_key;
|
|
1981
|
+
const agentId = opts.agentId ?? config.agent_id;
|
|
1982
|
+
if (!apiKey) {
|
|
1983
|
+
console.error(
|
|
1984
|
+
"No API key found. Run `acn join` first or `acn config set api-key <key>`."
|
|
1985
|
+
);
|
|
1986
|
+
process.exit(1);
|
|
1987
|
+
}
|
|
1988
|
+
if (!agentId) {
|
|
1989
|
+
console.error(
|
|
1990
|
+
"No agent ID found. Run `acn join` first or `acn config set agent-id <id>`."
|
|
1991
|
+
);
|
|
1992
|
+
process.exit(1);
|
|
1993
|
+
}
|
|
1994
|
+
const flagErr = validateListenHandlerFlags({
|
|
1995
|
+
runtime: opts.runtime,
|
|
1996
|
+
forward: opts.forward,
|
|
1997
|
+
exec: opts.exec,
|
|
1998
|
+
wakeUrl: opts.wakeUrl,
|
|
1999
|
+
wakeExec: opts.wakeExec
|
|
2000
|
+
});
|
|
2001
|
+
if (flagErr) {
|
|
2002
|
+
console.error(flagErr);
|
|
2003
|
+
process.exit(1);
|
|
2004
|
+
}
|
|
2005
|
+
let wakeHeaders;
|
|
2006
|
+
if (opts.runtime === "http") {
|
|
2007
|
+
try {
|
|
2008
|
+
wakeHeaders = parseWakeHeaders(opts.wakeHeader);
|
|
2009
|
+
} catch (err) {
|
|
2010
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
2011
|
+
process.exit(1);
|
|
2012
|
+
}
|
|
2013
|
+
}
|
|
2014
|
+
const wakeTimeoutMs = Number.parseInt(opts.wakeTimeout ?? "", 10);
|
|
2015
|
+
const dedupeTtlSec = Number.parseInt(opts.dedupeTtl ?? "", 10);
|
|
2016
|
+
if (opts.runtime && (!Number.isFinite(wakeTimeoutMs) || wakeTimeoutMs <= 0)) {
|
|
2017
|
+
console.error("--wake-timeout must be a positive integer (ms).");
|
|
2018
|
+
process.exit(1);
|
|
2019
|
+
}
|
|
2020
|
+
if (opts.runtime && (!Number.isFinite(dedupeTtlSec) || dedupeTtlSec <= 0)) {
|
|
2021
|
+
console.error("--dedupe-ttl must be a positive integer (seconds).");
|
|
2022
|
+
process.exit(1);
|
|
2023
|
+
}
|
|
2024
|
+
runListener({
|
|
2025
|
+
agentId,
|
|
2026
|
+
apiKey,
|
|
2027
|
+
baseUrl: config.base_url,
|
|
2028
|
+
forward: opts.forward,
|
|
2029
|
+
exec: opts.exec,
|
|
2030
|
+
runtime: opts.runtime ? {
|
|
2031
|
+
runtime: opts.runtime,
|
|
2032
|
+
wakeUrl: opts.wakeUrl,
|
|
2033
|
+
wakeHeaders,
|
|
2034
|
+
wakeExec: opts.wakeExec,
|
|
2035
|
+
wakeTimeoutMs,
|
|
2036
|
+
// commander: --no-dedupe sets dedupe=false; default true
|
|
2037
|
+
dedupe: opts.dedupe !== false,
|
|
2038
|
+
dedupeTtlSec
|
|
2039
|
+
} : void 0
|
|
2040
|
+
});
|
|
1607
2041
|
}
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
2042
|
+
);
|
|
2043
|
+
return cmd;
|
|
2044
|
+
}
|
|
2045
|
+
|
|
2046
|
+
// src/commands/delivery.ts
|
|
2047
|
+
var import_commander11 = require("commander");
|
|
2048
|
+
var DELIVERY_DESC = {
|
|
2049
|
+
direct: "direct (Mode A) \u2014 ACN dials your public A2A endpoint over HTTP",
|
|
2050
|
+
relay: "relay (Mode B) \u2014 hold an outbound WebSocket with `acn listen`; no public URL",
|
|
2051
|
+
none: "none \u2014 pull/reject only (communication_policy is manifest or closed; not Mode A/B)"
|
|
2052
|
+
};
|
|
2053
|
+
function requireAgentId3() {
|
|
2054
|
+
const config = loadConfig();
|
|
2055
|
+
if (!config.api_key) {
|
|
2056
|
+
console.error(
|
|
2057
|
+
"No API key found. Run `acn join` first or `acn config set api-key <key>`."
|
|
2058
|
+
);
|
|
2059
|
+
process.exit(1);
|
|
2060
|
+
}
|
|
2061
|
+
if (!config.agent_id) {
|
|
2062
|
+
console.error(
|
|
2063
|
+
"No agent ID found. Run `acn join` first or `acn config set agent-id <id>`."
|
|
2064
|
+
);
|
|
2065
|
+
process.exit(1);
|
|
2066
|
+
}
|
|
2067
|
+
return config.agent_id;
|
|
2068
|
+
}
|
|
2069
|
+
function formatDelivery(d) {
|
|
2070
|
+
const lines = [
|
|
2071
|
+
`Delivery : ${DELIVERY_DESC[d.delivery] ?? d.delivery}`,
|
|
2072
|
+
`Policy : ${d.communication_mode ?? "?"} (reception \u2014 not the same as delivery)`,
|
|
2073
|
+
`Endpoint : ${d.endpoint ?? "(none)"}`
|
|
2074
|
+
];
|
|
2075
|
+
if (d.a2a_handshake_ok === false) {
|
|
2076
|
+
lines.push("A2A probe: false \u2014 URL reachable but not JSON-RPC; fix the path");
|
|
2077
|
+
} else if (d.a2a_handshake_ok === true) {
|
|
2078
|
+
lines.push("A2A probe: ok");
|
|
2079
|
+
}
|
|
2080
|
+
if (d.next_step_hint) {
|
|
2081
|
+
lines.push("", d.next_step_hint);
|
|
2082
|
+
}
|
|
2083
|
+
return lines.join("\n");
|
|
2084
|
+
}
|
|
2085
|
+
function deliveryCommand() {
|
|
2086
|
+
const cmd = new import_commander11.Command("delivery").description(
|
|
2087
|
+
"Inbound delivery transport (Mode A direct / Mode B relay). Orthogonal to reception policy (`acn inbox mode`)."
|
|
2088
|
+
);
|
|
2089
|
+
cmd.command("get").description("Show derived delivery transport (direct | relay | none)").option("-i, --agent-id <id>", "Agent ID (defaults to config)").action(async (opts) => {
|
|
2090
|
+
const agentId = opts.agentId ?? requireAgentId3();
|
|
2091
|
+
try {
|
|
2092
|
+
const res = await acnGet(`/agents/${agentId}/delivery`);
|
|
2093
|
+
output(res, formatDelivery(res));
|
|
2094
|
+
} catch (err) {
|
|
2095
|
+
handleError(err);
|
|
1611
2096
|
}
|
|
1612
|
-
runListener({
|
|
1613
|
-
agentId,
|
|
1614
|
-
apiKey,
|
|
1615
|
-
baseUrl: config.base_url,
|
|
1616
|
-
forward: opts.forward,
|
|
1617
|
-
exec: opts.exec
|
|
1618
|
-
});
|
|
1619
2097
|
});
|
|
2098
|
+
cmd.command("set <transport>").description(
|
|
2099
|
+
"Switch delivery without re-registering: relay (Mode B) or direct (Mode A). Requires push reception policy (open / allowlist)."
|
|
2100
|
+
).option(
|
|
2101
|
+
"-e, --endpoint <url>",
|
|
2102
|
+
"Required for direct: public A2A JSON-RPC URL (e.g. https://host/a2a)"
|
|
2103
|
+
).option("-i, --agent-id <id>", "Agent ID (defaults to config)").action(
|
|
2104
|
+
async (transport, opts) => {
|
|
2105
|
+
const agentId = opts.agentId ?? requireAgentId3();
|
|
2106
|
+
const normalized = transport.trim().toLowerCase();
|
|
2107
|
+
if (normalized !== "relay" && normalized !== "direct") {
|
|
2108
|
+
console.error(
|
|
2109
|
+
`Unknown transport "${transport}". Use: relay | direct`
|
|
2110
|
+
);
|
|
2111
|
+
process.exit(1);
|
|
2112
|
+
}
|
|
2113
|
+
if (normalized === "direct" && !opts.endpoint) {
|
|
2114
|
+
console.error(
|
|
2115
|
+
"direct requires --endpoint <url> (full A2A path, e.g. https://host/a2a)."
|
|
2116
|
+
);
|
|
2117
|
+
process.exit(1);
|
|
2118
|
+
}
|
|
2119
|
+
if (normalized === "relay" && opts.endpoint) {
|
|
2120
|
+
console.error(
|
|
2121
|
+
"relay must not include --endpoint (clear the public URL; use `acn listen`)."
|
|
2122
|
+
);
|
|
2123
|
+
process.exit(1);
|
|
2124
|
+
}
|
|
2125
|
+
const body = normalized === "relay" ? { delivery: "relay" } : { delivery: "direct", endpoint: opts.endpoint };
|
|
2126
|
+
try {
|
|
2127
|
+
const res = await acnPatch(
|
|
2128
|
+
`/agents/${agentId}/delivery`,
|
|
2129
|
+
body
|
|
2130
|
+
);
|
|
2131
|
+
const followUp = res.delivery === "relay" ? [
|
|
2132
|
+
"",
|
|
2133
|
+
"Next: run the Mode B listener (built-in A2A + wake host):",
|
|
2134
|
+
" acn listen --runtime http --wake-url http://127.0.0.1:PORT/wake",
|
|
2135
|
+
"Compat: acn listen --forward http://localhost:PORT"
|
|
2136
|
+
] : [];
|
|
2137
|
+
output(res, [formatDelivery(res), ...followUp].join("\n"));
|
|
2138
|
+
} catch (err) {
|
|
2139
|
+
handleError(err);
|
|
2140
|
+
}
|
|
2141
|
+
}
|
|
2142
|
+
);
|
|
1620
2143
|
return cmd;
|
|
1621
2144
|
}
|
|
1622
2145
|
|
|
1623
2146
|
// src/commands/session.ts
|
|
1624
|
-
var
|
|
1625
|
-
function
|
|
2147
|
+
var import_commander12 = require("commander");
|
|
2148
|
+
function requireAgentId4() {
|
|
1626
2149
|
const config = loadConfig();
|
|
1627
2150
|
if (!config.api_key) {
|
|
1628
2151
|
console.error("No API key found. Run `acn join` first or `acn config set api-key <key>`.");
|
|
@@ -1666,12 +2189,12 @@ function formatEntry2(s, index) {
|
|
|
1666
2189
|
return lines.join("\n");
|
|
1667
2190
|
}
|
|
1668
2191
|
function sessionCommand() {
|
|
1669
|
-
const cmd = new
|
|
2192
|
+
const cmd = new import_commander12.Command("session").description(
|
|
1670
2193
|
"Real-time session layer: bidirectional channel between two agents"
|
|
1671
2194
|
);
|
|
1672
2195
|
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(
|
|
1673
2196
|
async (targetId, opts) => {
|
|
1674
|
-
|
|
2197
|
+
requireAgentId4();
|
|
1675
2198
|
try {
|
|
1676
2199
|
const body = {};
|
|
1677
2200
|
if (opts.ttlSeconds !== void 0) body.ttl_seconds = opts.ttlSeconds;
|
|
@@ -1692,7 +2215,7 @@ ${formatEntry2(res)}`
|
|
|
1692
2215
|
}
|
|
1693
2216
|
);
|
|
1694
2217
|
cmd.command("accept <session_id>").description("Accept a pending session invitation (invitee only)").action(async (sessionId) => {
|
|
1695
|
-
|
|
2218
|
+
requireAgentId4();
|
|
1696
2219
|
try {
|
|
1697
2220
|
const res = await acnPost(`/sessions/${sessionId}/accept`);
|
|
1698
2221
|
output(res, `Session accepted.
|
|
@@ -1702,7 +2225,7 @@ ${formatEntry2(res)}`);
|
|
|
1702
2225
|
}
|
|
1703
2226
|
});
|
|
1704
2227
|
cmd.command("reject <session_id>").description("Reject a pending session invitation (invitee only)").action(async (sessionId) => {
|
|
1705
|
-
|
|
2228
|
+
requireAgentId4();
|
|
1706
2229
|
try {
|
|
1707
2230
|
const res = await acnPost(`/sessions/${sessionId}/reject`);
|
|
1708
2231
|
output(res, `Session rejected.
|
|
@@ -1712,7 +2235,7 @@ ${formatEntry2(res)}`);
|
|
|
1712
2235
|
}
|
|
1713
2236
|
});
|
|
1714
2237
|
cmd.command("close <session_id>").description("Close an active session (either party may close)").action(async (sessionId) => {
|
|
1715
|
-
|
|
2238
|
+
requireAgentId4();
|
|
1716
2239
|
try {
|
|
1717
2240
|
const res = await acnDelete(`/sessions/${sessionId}`);
|
|
1718
2241
|
output(res, `Session closed.
|
|
@@ -1722,7 +2245,7 @@ ${formatEntry2(res)}`);
|
|
|
1722
2245
|
}
|
|
1723
2246
|
});
|
|
1724
2247
|
cmd.command("pending").description("List pending session invitations addressed to you").action(async () => {
|
|
1725
|
-
|
|
2248
|
+
requireAgentId4();
|
|
1726
2249
|
try {
|
|
1727
2250
|
const res = await acnGet("/sessions/pending");
|
|
1728
2251
|
const sessions = res.sessions ?? [];
|
|
@@ -1744,8 +2267,8 @@ ${formatEntry2(res)}`);
|
|
|
1744
2267
|
}
|
|
1745
2268
|
|
|
1746
2269
|
// src/commands/subnet.ts
|
|
1747
|
-
var
|
|
1748
|
-
function
|
|
2270
|
+
var import_commander13 = require("commander");
|
|
2271
|
+
function requireAgentId5() {
|
|
1749
2272
|
const config = loadConfig();
|
|
1750
2273
|
if (!config.api_key) {
|
|
1751
2274
|
console.error("No API key found. Run `acn join` first or `acn config set api-key <key>`.");
|
|
@@ -1832,7 +2355,7 @@ function formatSubnet(s, index) {
|
|
|
1832
2355
|
return lines.join("\n");
|
|
1833
2356
|
}
|
|
1834
2357
|
function subnetCommand() {
|
|
1835
|
-
const cmd = new
|
|
2358
|
+
const cmd = new import_commander13.Command("subnet").description("Manage ACN subnets");
|
|
1836
2359
|
cmd.command("list").description(
|
|
1837
2360
|
"List subnets. Without --all/--parent shows only subnets you have joined."
|
|
1838
2361
|
).option("--all", "Show all public subnets on ACN (not just your own)").option(
|
|
@@ -1871,7 +2394,7 @@ function subnetCommand() {
|
|
|
1871
2394
|
` + subnets.map((s, i) => formatSubnet(s, i)).join("\n\n")
|
|
1872
2395
|
);
|
|
1873
2396
|
} else {
|
|
1874
|
-
const agentId = opts.agentId ??
|
|
2397
|
+
const agentId = opts.agentId ?? requireAgentId5();
|
|
1875
2398
|
const res = await acnGet(
|
|
1876
2399
|
`/subnets/${agentId}/subnets`
|
|
1877
2400
|
);
|
|
@@ -1910,7 +2433,7 @@ ${JSON.stringify(res.agents, null, 2)}`);
|
|
|
1910
2433
|
cmd.command("join <subnet_id>").description(
|
|
1911
2434
|
"Join a subnet. ADR-0004: branches on response shape (open/allowlist/auto-invite/pending)."
|
|
1912
2435
|
).option("-i, --agent-id <id>", "Agent ID (defaults to config)").action(async (subnetId, opts) => {
|
|
1913
|
-
const agentId = opts.agentId ??
|
|
2436
|
+
const agentId = opts.agentId ?? requireAgentId5();
|
|
1914
2437
|
try {
|
|
1915
2438
|
const res = await acnPost(
|
|
1916
2439
|
`/agents/${agentId}/subnets/${subnetId}`
|
|
@@ -1921,7 +2444,7 @@ ${JSON.stringify(res.agents, null, 2)}`);
|
|
|
1921
2444
|
}
|
|
1922
2445
|
});
|
|
1923
2446
|
cmd.command("leave <subnet_id>").description("Leave a subnet").option("-i, --agent-id <id>", "Agent ID (defaults to config)").action(async (subnetId, opts) => {
|
|
1924
|
-
const agentId = opts.agentId ??
|
|
2447
|
+
const agentId = opts.agentId ?? requireAgentId5();
|
|
1925
2448
|
try {
|
|
1926
2449
|
const res = await acnDelete(
|
|
1927
2450
|
`/agents/${agentId}/subnets/${subnetId}`
|
|
@@ -2049,7 +2572,7 @@ ${JSON.stringify(res.agents, null, 2)}`);
|
|
|
2049
2572
|
handleError(err);
|
|
2050
2573
|
}
|
|
2051
2574
|
});
|
|
2052
|
-
const requests = new
|
|
2575
|
+
const requests = new import_commander13.Command("requests").description(
|
|
2053
2576
|
"Manage join-requests for a subnet (ADR-0004)"
|
|
2054
2577
|
);
|
|
2055
2578
|
requests.command("list <subnet_id>").description(
|
|
@@ -2093,7 +2616,7 @@ ${JSON.stringify(res.agents, null, 2)}`);
|
|
|
2093
2616
|
requests.command("pending").description(
|
|
2094
2617
|
"Owner-side convenience: list pending join_requests across every subnet you own. Client-side aggregation \u2014 issues N+1 calls (one per owned subnet)."
|
|
2095
2618
|
).option("-i, --agent-id <id>", "Agent ID (defaults to config)").action(async (opts) => {
|
|
2096
|
-
const agentId = opts.agentId ??
|
|
2619
|
+
const agentId = opts.agentId ?? requireAgentId5();
|
|
2097
2620
|
try {
|
|
2098
2621
|
const subs = await acnGet(`/agents/${agentId}/subnets`);
|
|
2099
2622
|
const subnetIds = subs.subnets ?? [];
|
|
@@ -2191,7 +2714,7 @@ ${JSON.stringify(res.agents, null, 2)}`);
|
|
|
2191
2714
|
}
|
|
2192
2715
|
);
|
|
2193
2716
|
cmd.addCommand(requests);
|
|
2194
|
-
const invitations = new
|
|
2717
|
+
const invitations = new import_commander13.Command("invitations").description(
|
|
2195
2718
|
"Manage invitations on a subnet (ADR-0004)"
|
|
2196
2719
|
);
|
|
2197
2720
|
invitations.command("send <subnet_id>").description(
|
|
@@ -2246,7 +2769,7 @@ ${JSON.stringify(res.agents, null, 2)}`);
|
|
|
2246
2769
|
invitations.command("pending").description(
|
|
2247
2770
|
"Invitee view: list pending invitations addressed to you across all subnets (backed by GET /agents/{aid}/subnet-invitations)."
|
|
2248
2771
|
).option("-i, --agent-id <id>", "Agent ID (defaults to config)").action(async (opts) => {
|
|
2249
|
-
const agentId = opts.agentId ??
|
|
2772
|
+
const agentId = opts.agentId ?? requireAgentId5();
|
|
2250
2773
|
try {
|
|
2251
2774
|
const res = await acnGet(
|
|
2252
2775
|
`/agents/${agentId}/subnet-invitations`
|
|
@@ -2327,7 +2850,7 @@ ${JSON.stringify(res.agents, null, 2)}`);
|
|
|
2327
2850
|
}
|
|
2328
2851
|
);
|
|
2329
2852
|
cmd.addCommand(invitations);
|
|
2330
|
-
const allowlist = new
|
|
2853
|
+
const allowlist = new import_commander13.Command("allowlist").description(
|
|
2331
2854
|
"Manage a subnet allowlist (ADR-0004)"
|
|
2332
2855
|
);
|
|
2333
2856
|
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(
|
|
@@ -2395,7 +2918,7 @@ ${JSON.stringify(res.agents, null, 2)}`);
|
|
|
2395
2918
|
}
|
|
2396
2919
|
);
|
|
2397
2920
|
cmd.addCommand(allowlist);
|
|
2398
|
-
const harness = new
|
|
2921
|
+
const harness = new import_commander13.Command("harness").description("Manage Org Harness webhook for a subnet");
|
|
2399
2922
|
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) => {
|
|
2400
2923
|
const config = loadConfig();
|
|
2401
2924
|
if (!config.api_key) {
|
|
@@ -2437,9 +2960,198 @@ ${JSON.stringify(res.agents, null, 2)}`);
|
|
|
2437
2960
|
return cmd;
|
|
2438
2961
|
}
|
|
2439
2962
|
|
|
2963
|
+
// src/commands/org.ts
|
|
2964
|
+
var import_commander14 = require("commander");
|
|
2965
|
+
function formatOrg(o) {
|
|
2966
|
+
const lines = [
|
|
2967
|
+
` ID : ${o.org_id}`,
|
|
2968
|
+
` Name : ${o.display_name}`,
|
|
2969
|
+
` Status : ${o.status ?? "\u2014"}`,
|
|
2970
|
+
` Owner : ${o.owner?.kind ?? "none"}${o.owner?.subject ? ` (${o.owner.subject})` : ""}`,
|
|
2971
|
+
` Steward : ${o.steward_agent_id ?? "\u2014"}`,
|
|
2972
|
+
` Subnet : ${o.fencing?.subnet_id ?? o.subnet_id ?? "\u2014"}`
|
|
2973
|
+
];
|
|
2974
|
+
if (o.fencing?.join_policy) lines.push(` Join : ${o.fencing.join_policy}`);
|
|
2975
|
+
if (o.harness_webhook?.registered) {
|
|
2976
|
+
lines.push(` Harness : ${o.harness_webhook.url ?? "(registered)"}`);
|
|
2977
|
+
}
|
|
2978
|
+
return lines.join("\n");
|
|
2979
|
+
}
|
|
2980
|
+
function orgCommand() {
|
|
2981
|
+
const cmd = new import_commander14.Command("org").description("Manage ACN organisations (Org Harness)");
|
|
2982
|
+
cmd.command("create").description("Create an Org (binds/creates a subnet fence)").requiredOption("--name <name>", "Display name").option("--steward <agent_id>", "Steward agent (required for human JWT callers)").option("--subnet <slug>", "Bind existing subnet slug (must be owned by steward)").option("--join-policy <policy>", "open | approval", "open").option("--private", "Private subnet fence", false).option("--harness-url <url>", "Register Org Harness webhook on the fence subnet").option("--harness-secret <secret>", "HMAC secret for harness webhook").action(
|
|
2983
|
+
async (opts) => {
|
|
2984
|
+
try {
|
|
2985
|
+
const body = {
|
|
2986
|
+
display_name: opts.name,
|
|
2987
|
+
is_private: Boolean(opts.private),
|
|
2988
|
+
join_policy: opts.joinPolicy ?? "open"
|
|
2989
|
+
};
|
|
2990
|
+
if (opts.steward) body.steward_agent_id = opts.steward;
|
|
2991
|
+
if (opts.subnet) body.subnet_id = opts.subnet;
|
|
2992
|
+
if (opts.harnessUrl) body.harness_url = opts.harnessUrl;
|
|
2993
|
+
if (opts.harnessSecret) body.harness_secret = opts.harnessSecret;
|
|
2994
|
+
const org = await acnPost("/orgs", body);
|
|
2995
|
+
output(org, formatOrg(org));
|
|
2996
|
+
} catch (err) {
|
|
2997
|
+
handleError(err);
|
|
2998
|
+
}
|
|
2999
|
+
}
|
|
3000
|
+
);
|
|
3001
|
+
cmd.command("show <orgId>").description("Show Org details").action(async (orgId) => {
|
|
3002
|
+
try {
|
|
3003
|
+
const org = await acnGet(`/orgs/${orgId}`);
|
|
3004
|
+
output(org, formatOrg(org));
|
|
3005
|
+
} catch (err) {
|
|
3006
|
+
handleError(err);
|
|
3007
|
+
}
|
|
3008
|
+
});
|
|
3009
|
+
cmd.command("update <orgId>").description("Update Org charter / plugins / display name").option("--name <name>", "New display name").option("--charter <json>", "Charter JSON object").option("--plugins <json>", "Plugins JSON object (merged)").action(
|
|
3010
|
+
async (orgId, opts) => {
|
|
3011
|
+
try {
|
|
3012
|
+
const body = {};
|
|
3013
|
+
if (opts.name) body.display_name = opts.name;
|
|
3014
|
+
if (opts.charter) body.charter = JSON.parse(opts.charter);
|
|
3015
|
+
if (opts.plugins) body.plugins = JSON.parse(opts.plugins);
|
|
3016
|
+
const org = await acnPatch(`/orgs/${orgId}`, body);
|
|
3017
|
+
output(org, formatOrg(org));
|
|
3018
|
+
} catch (err) {
|
|
3019
|
+
handleError(err);
|
|
3020
|
+
}
|
|
3021
|
+
}
|
|
3022
|
+
);
|
|
3023
|
+
const members = cmd.command("members").description("Manage Org members");
|
|
3024
|
+
members.command("list <orgId>").description("List active members (marks degraded vs subnet fence)").action(async (orgId) => {
|
|
3025
|
+
try {
|
|
3026
|
+
const res = await acnGet(`/orgs/${orgId}/members`);
|
|
3027
|
+
const text = (res.members ?? []).map((m) => {
|
|
3028
|
+
const flags = [];
|
|
3029
|
+
if (m.acn?.degraded) flags.push("degraded");
|
|
3030
|
+
if (m.acn && !m.acn.subnet_member) flags.push("not-in-subnet");
|
|
3031
|
+
const flagStr = flags.length ? ` [${flags.join(",")}]` : "";
|
|
3032
|
+
return ` ${m.agent_id} role=${m.role} status=${m.status}${flagStr}`;
|
|
3033
|
+
}).join("\n");
|
|
3034
|
+
const header = res.degraded_count || res.fence_missing ? ` (degraded=${res.degraded_count} fence_missing=${res.fence_missing})
|
|
3035
|
+
` : "";
|
|
3036
|
+
output(res, header + (text || " (no members)"));
|
|
3037
|
+
} catch (err) {
|
|
3038
|
+
handleError(err);
|
|
3039
|
+
}
|
|
3040
|
+
});
|
|
3041
|
+
members.command("add <orgId> <agentId>").description("Add an agent member").option("--role <role>", "Member role", "worker").action(async (orgId, agentId, opts) => {
|
|
3042
|
+
try {
|
|
3043
|
+
const m = await acnPost(`/orgs/${orgId}/members`, {
|
|
3044
|
+
agent_id: agentId,
|
|
3045
|
+
role: opts.role
|
|
3046
|
+
});
|
|
3047
|
+
output(m, `Added ${m.agent_id} as ${m.role}`);
|
|
3048
|
+
} catch (err) {
|
|
3049
|
+
handleError(err);
|
|
3050
|
+
}
|
|
3051
|
+
});
|
|
3052
|
+
members.command("remove <orgId> <agentId>").description("Remove an agent member").action(async (orgId, agentId) => {
|
|
3053
|
+
try {
|
|
3054
|
+
const m = await acnDelete(`/orgs/${orgId}/members/${agentId}`);
|
|
3055
|
+
output(m, `Removed ${m.agent_id}`);
|
|
3056
|
+
} catch (err) {
|
|
3057
|
+
handleError(err);
|
|
3058
|
+
}
|
|
3059
|
+
});
|
|
3060
|
+
cmd.command("claim <orgId>").description("Claim ownership of an unclaimed Org (created_by only)").option("--as <kind>", "human | agent").option("--subject <id>", "Owner subject (defaults to caller)").action(async (orgId, opts) => {
|
|
3061
|
+
try {
|
|
3062
|
+
const body = {};
|
|
3063
|
+
if (opts.as) body.owner_kind = opts.as;
|
|
3064
|
+
if (opts.subject) body.owner_subject = opts.subject;
|
|
3065
|
+
const org = await acnPost(`/orgs/${orgId}/claim`, body);
|
|
3066
|
+
output(org, formatOrg(org));
|
|
3067
|
+
} catch (err) {
|
|
3068
|
+
handleError(err);
|
|
3069
|
+
}
|
|
3070
|
+
});
|
|
3071
|
+
cmd.command("transfer <orgId>").description("Transfer Org ownership").requiredOption("--kind <kind>", "human | agent").requiredOption("--subject <id>", "New owner subject").action(async (orgId, opts) => {
|
|
3072
|
+
try {
|
|
3073
|
+
const org = await acnPost(`/orgs/${orgId}/transfer`, {
|
|
3074
|
+
new_owner_kind: opts.kind,
|
|
3075
|
+
new_owner_subject: opts.subject
|
|
3076
|
+
});
|
|
3077
|
+
output(org, formatOrg(org));
|
|
3078
|
+
} catch (err) {
|
|
3079
|
+
handleError(err);
|
|
3080
|
+
}
|
|
3081
|
+
});
|
|
3082
|
+
cmd.command("release <orgId>").description("Release Org ownership back to none").action(async (orgId) => {
|
|
3083
|
+
try {
|
|
3084
|
+
const org = await acnPost(`/orgs/${orgId}/release`, {});
|
|
3085
|
+
output(org, formatOrg(org));
|
|
3086
|
+
} catch (err) {
|
|
3087
|
+
handleError(err);
|
|
3088
|
+
}
|
|
3089
|
+
});
|
|
3090
|
+
cmd.command("dissolve <orgId>").description("Dissolve an Org (owner or created_by when unclaimed)").action(async (orgId) => {
|
|
3091
|
+
try {
|
|
3092
|
+
const org = await acnPost(`/orgs/${orgId}/dissolve`, {});
|
|
3093
|
+
output(org, formatOrg(org));
|
|
3094
|
+
} catch (err) {
|
|
3095
|
+
handleError(err);
|
|
3096
|
+
}
|
|
3097
|
+
});
|
|
3098
|
+
const work = cmd.command("work").description("Minimal Org work queue");
|
|
3099
|
+
work.command("list <orgId>").description("List work items").option("--open", "Only open (todo / in_progress)", false).action(async (orgId, opts) => {
|
|
3100
|
+
try {
|
|
3101
|
+
const q = opts.open ? "?open_only=true" : "";
|
|
3102
|
+
const res = await acnGet(
|
|
3103
|
+
`/orgs/${orgId}/work${q}`
|
|
3104
|
+
);
|
|
3105
|
+
const text = (res.work ?? []).map(
|
|
3106
|
+
(w) => ` ${w.work_id} [${w.status}] ${w.title}` + (w.assignee_agent_id ? ` \u2192 ${w.assignee_agent_id}` : "")
|
|
3107
|
+
).join("\n");
|
|
3108
|
+
output(res, text || " (no work)");
|
|
3109
|
+
} catch (err) {
|
|
3110
|
+
handleError(err);
|
|
3111
|
+
}
|
|
3112
|
+
});
|
|
3113
|
+
work.command("create <orgId>").description("Create a work item").requiredOption("--title <title>", "Work title").option("--assignee <agent_id>", "Assignee agent").action(async (orgId, opts) => {
|
|
3114
|
+
try {
|
|
3115
|
+
const body = { title: opts.title };
|
|
3116
|
+
if (opts.assignee) body.assignee_agent_id = opts.assignee;
|
|
3117
|
+
const w = await acnPost(`/orgs/${orgId}/work`, body);
|
|
3118
|
+
output(w, `Created ${w.work_id}: ${w.title}`);
|
|
3119
|
+
} catch (err) {
|
|
3120
|
+
handleError(err);
|
|
3121
|
+
}
|
|
3122
|
+
});
|
|
3123
|
+
work.command("update <orgId> <workId>").description("Update work status").requiredOption("--status <status>", "todo | in_progress | done | cancelled").option("--assignee <agent_id>", "Assignee agent").action(
|
|
3124
|
+
async (orgId, workId, opts) => {
|
|
3125
|
+
try {
|
|
3126
|
+
const body = { status: opts.status };
|
|
3127
|
+
if (opts.assignee) body.assignee_agent_id = opts.assignee;
|
|
3128
|
+
const w = await acnPatch(
|
|
3129
|
+
`/orgs/${orgId}/work/${workId}`,
|
|
3130
|
+
body
|
|
3131
|
+
);
|
|
3132
|
+
output(w, `Updated ${w.work_id} \u2192 ${w.status}`);
|
|
3133
|
+
} catch (err) {
|
|
3134
|
+
handleError(err);
|
|
3135
|
+
}
|
|
3136
|
+
}
|
|
3137
|
+
);
|
|
3138
|
+
cmd.command("tick <orgId>").description("Thin Loop tick (lists open work, emits org.loop_tick)").action(async (orgId) => {
|
|
3139
|
+
try {
|
|
3140
|
+
const res = await acnPost(
|
|
3141
|
+
`/orgs/${orgId}/loop/tick`,
|
|
3142
|
+
{}
|
|
3143
|
+
);
|
|
3144
|
+
output(res, `Loop tick: ${res.open_count} open work item(s)`);
|
|
3145
|
+
} catch (err) {
|
|
3146
|
+
handleError(err);
|
|
3147
|
+
}
|
|
3148
|
+
});
|
|
3149
|
+
return cmd;
|
|
3150
|
+
}
|
|
3151
|
+
|
|
2440
3152
|
// src/commands/follow.ts
|
|
2441
|
-
var
|
|
2442
|
-
function
|
|
3153
|
+
var import_commander15 = require("commander");
|
|
3154
|
+
function requireAgentId6() {
|
|
2443
3155
|
const config = loadConfig();
|
|
2444
3156
|
if (!config.api_key) {
|
|
2445
3157
|
console.error("No API key found. Run `acn join` first or `acn config set api-key <key>`.");
|
|
@@ -2461,9 +3173,9 @@ function formatAgent2(a, i) {
|
|
|
2461
3173
|
].join("\n");
|
|
2462
3174
|
}
|
|
2463
3175
|
function followCommand() {
|
|
2464
|
-
const cmd = new
|
|
3176
|
+
const cmd = new import_commander15.Command("follow").description("Follow/unfollow agents and inspect follow graph");
|
|
2465
3177
|
cmd.command("add <target_id>").description("Follow another agent").option("-i, --agent-id <id>", "Agent ID (defaults to config)").action(async (targetId, opts) => {
|
|
2466
|
-
const agentId = opts.agentId ??
|
|
3178
|
+
const agentId = opts.agentId ?? requireAgentId6();
|
|
2467
3179
|
try {
|
|
2468
3180
|
const res = await acnPost(
|
|
2469
3181
|
`/agents/${agentId}/follows/${targetId}`
|
|
@@ -2475,7 +3187,7 @@ function followCommand() {
|
|
|
2475
3187
|
}
|
|
2476
3188
|
});
|
|
2477
3189
|
cmd.command("remove <target_id>").description("Unfollow an agent").option("-i, --agent-id <id>", "Agent ID (defaults to config)").action(async (targetId, opts) => {
|
|
2478
|
-
const agentId = opts.agentId ??
|
|
3190
|
+
const agentId = opts.agentId ?? requireAgentId6();
|
|
2479
3191
|
try {
|
|
2480
3192
|
const res = await acnDelete(
|
|
2481
3193
|
`/agents/${agentId}/follows/${targetId}`
|
|
@@ -2487,7 +3199,7 @@ function followCommand() {
|
|
|
2487
3199
|
}
|
|
2488
3200
|
});
|
|
2489
3201
|
cmd.command("list").description("List agents you follow").option("--limit <n>", "Max results", parseInt).option("--offset <n>", "Pagination offset", parseInt).option("-i, --agent-id <id>", "Agent ID (defaults to config)").action(async (opts) => {
|
|
2490
|
-
const agentId = opts.agentId ??
|
|
3202
|
+
const agentId = opts.agentId ?? requireAgentId6();
|
|
2491
3203
|
try {
|
|
2492
3204
|
const params = {};
|
|
2493
3205
|
if (opts.limit !== void 0) params.limit = opts.limit;
|
|
@@ -2512,7 +3224,7 @@ function followCommand() {
|
|
|
2512
3224
|
}
|
|
2513
3225
|
});
|
|
2514
3226
|
cmd.command("followers").description("List agents that follow you").option("--limit <n>", "Max results", parseInt).option("--offset <n>", "Pagination offset", parseInt).option("-i, --agent-id <id>", "Agent ID (defaults to config)").action(async (opts) => {
|
|
2515
|
-
const agentId = opts.agentId ??
|
|
3227
|
+
const agentId = opts.agentId ?? requireAgentId6();
|
|
2516
3228
|
try {
|
|
2517
3229
|
const params = {};
|
|
2518
3230
|
if (opts.limit !== void 0) params.limit = opts.limit;
|
|
@@ -2537,7 +3249,7 @@ function followCommand() {
|
|
|
2537
3249
|
}
|
|
2538
3250
|
});
|
|
2539
3251
|
cmd.command("check <target_id>").description("Check whether you are following a specific agent").option("-i, --agent-id <id>", "Agent ID (defaults to config)").action(async (targetId, opts) => {
|
|
2540
|
-
const agentId = opts.agentId ??
|
|
3252
|
+
const agentId = opts.agentId ?? requireAgentId6();
|
|
2541
3253
|
try {
|
|
2542
3254
|
const res = await acnGet(
|
|
2543
3255
|
`/agents/${agentId}/follows/${targetId}`
|
|
@@ -2552,8 +3264,8 @@ function followCommand() {
|
|
|
2552
3264
|
}
|
|
2553
3265
|
|
|
2554
3266
|
// src/commands/wallet.ts
|
|
2555
|
-
var
|
|
2556
|
-
function
|
|
3267
|
+
var import_commander16 = require("commander");
|
|
3268
|
+
function requireAgentId7() {
|
|
2557
3269
|
const config = loadConfig();
|
|
2558
3270
|
if (!config.api_key) {
|
|
2559
3271
|
console.error("No API key found. Run `acn join` first or `acn config set api-key <key>`.");
|
|
@@ -2569,7 +3281,7 @@ function parseCsv(value) {
|
|
|
2569
3281
|
return value.split(",").map((s) => s.trim()).filter(Boolean);
|
|
2570
3282
|
}
|
|
2571
3283
|
async function showWalletInfo(opts) {
|
|
2572
|
-
const agentId = opts.agentId ??
|
|
3284
|
+
const agentId = opts.agentId ?? requireAgentId7();
|
|
2573
3285
|
try {
|
|
2574
3286
|
const res = await acnGet(`/agents/${agentId}/wallets`);
|
|
2575
3287
|
const lines = [`Agent : ${res.agent_id}`];
|
|
@@ -2595,7 +3307,7 @@ async function showWalletInfo(opts) {
|
|
|
2595
3307
|
}
|
|
2596
3308
|
}
|
|
2597
3309
|
function walletCommand() {
|
|
2598
|
-
const cmd = new
|
|
3310
|
+
const cmd = new import_commander16.Command("wallet").description("View and manage agent's wallet & payment info");
|
|
2599
3311
|
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);
|
|
2600
3312
|
cmd.command("set-capability").description("Declare which payment methods, networks, and wallets you accept").requiredOption(
|
|
2601
3313
|
"--methods <csv>",
|
|
@@ -2605,7 +3317,7 @@ function walletCommand() {
|
|
|
2605
3317
|
`Wallet addresses by network, JSON, e.g. '{"ethereum":"0x..."}'`
|
|
2606
3318
|
).option("--no-accepts", "Disable accepting payments (default: enabled)").option("-i, --agent-id <id>", "Agent ID (defaults to config)").action(
|
|
2607
3319
|
async (opts) => {
|
|
2608
|
-
const agentId = opts.agentId ??
|
|
3320
|
+
const agentId = opts.agentId ?? requireAgentId7();
|
|
2609
3321
|
let walletMap = {};
|
|
2610
3322
|
if (opts.wallets) {
|
|
2611
3323
|
try {
|
|
@@ -2640,7 +3352,7 @@ function walletCommand() {
|
|
|
2640
3352
|
);
|
|
2641
3353
|
cmd.command("set-pricing").description("Set OpenAI-style per-million-token pricing for your agent (USD)").requiredOption("--input <usd>", "USD per 1M input tokens").requiredOption("--output <usd>", "USD per 1M output tokens").option("-i, --agent-id <id>", "Agent ID (defaults to config)").action(
|
|
2642
3354
|
async (opts) => {
|
|
2643
|
-
const agentId = opts.agentId ??
|
|
3355
|
+
const agentId = opts.agentId ?? requireAgentId7();
|
|
2644
3356
|
const inputPrice = Number(opts.input);
|
|
2645
3357
|
const outputPrice = Number(opts.output);
|
|
2646
3358
|
if (!Number.isFinite(inputPrice) || inputPrice < 0) {
|
|
@@ -2668,7 +3380,7 @@ function walletCommand() {
|
|
|
2668
3380
|
}
|
|
2669
3381
|
);
|
|
2670
3382
|
cmd.command("tasks").description("List the payment tasks the current agent is involved in").option("--status <s>", "Filter by status (e.g. created, payment_confirmed, task_completed)").option("--limit <n>", "Max number of tasks to return (default 50)").option("-i, --agent-id <id>", "Agent ID (defaults to config)").action(async (opts) => {
|
|
2671
|
-
const agentId = opts.agentId ??
|
|
3383
|
+
const agentId = opts.agentId ?? requireAgentId7();
|
|
2672
3384
|
const params = {};
|
|
2673
3385
|
if (opts.status) params.status = opts.status;
|
|
2674
3386
|
if (opts.limit !== void 0) {
|
|
@@ -2708,7 +3420,7 @@ function walletCommand() {
|
|
|
2708
3420
|
}
|
|
2709
3421
|
});
|
|
2710
3422
|
cmd.command("stats").description("Show the current agent's payment statistics").option("-i, --agent-id <id>", "Agent ID (defaults to config)").action(async (opts) => {
|
|
2711
|
-
const agentId = opts.agentId ??
|
|
3423
|
+
const agentId = opts.agentId ?? requireAgentId7();
|
|
2712
3424
|
try {
|
|
2713
3425
|
const res = await acnGet(`/payments/stats/${agentId}`);
|
|
2714
3426
|
const lines = [`Stats for ${agentId}:`];
|
|
@@ -2774,8 +3486,8 @@ function walletCommand() {
|
|
|
2774
3486
|
}
|
|
2775
3487
|
|
|
2776
3488
|
// src/commands/pay.ts
|
|
2777
|
-
var
|
|
2778
|
-
function
|
|
3489
|
+
var import_commander17 = require("commander");
|
|
3490
|
+
function requireAgentId8() {
|
|
2779
3491
|
const config = loadConfig();
|
|
2780
3492
|
if (!config.api_key) {
|
|
2781
3493
|
console.error("No API key found. Run `acn join` first or `acn config set api-key <key>`.");
|
|
@@ -2788,11 +3500,11 @@ function requireAgentId7() {
|
|
|
2788
3500
|
return config.agent_id;
|
|
2789
3501
|
}
|
|
2790
3502
|
function payCommand() {
|
|
2791
|
-
const cmd = new
|
|
2792
|
-
const createCmd = new
|
|
3503
|
+
const cmd = new import_commander17.Command("pay").description("Manage payment tasks between agents");
|
|
3504
|
+
const createCmd = new import_commander17.Command("create").description("Create a payment task to another agent");
|
|
2793
3505
|
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(
|
|
2794
3506
|
async (opts) => {
|
|
2795
|
-
const fromAgent =
|
|
3507
|
+
const fromAgent = requireAgentId8();
|
|
2796
3508
|
const amount = Number(opts.amount);
|
|
2797
3509
|
if (!Number.isFinite(amount) || amount <= 0) {
|
|
2798
3510
|
console.error("--amount must be a positive number.");
|
|
@@ -2835,7 +3547,7 @@ function payCommand() {
|
|
|
2835
3547
|
}
|
|
2836
3548
|
}
|
|
2837
3549
|
);
|
|
2838
|
-
const confirmCmd = new
|
|
3550
|
+
const confirmCmd = new import_commander17.Command("confirm").description(
|
|
2839
3551
|
"Confirm an external payment has been made (buyer only)"
|
|
2840
3552
|
);
|
|
2841
3553
|
confirmCmd.requiredOption("--task-id <id>", "Payment task ID to confirm").requiredOption(
|
|
@@ -2857,11 +3569,11 @@ function payCommand() {
|
|
|
2857
3569
|
handleError(err);
|
|
2858
3570
|
}
|
|
2859
3571
|
});
|
|
2860
|
-
const statusCmd = new
|
|
3572
|
+
const statusCmd = new import_commander17.Command("status").description(
|
|
2861
3573
|
"Show payment tasks for the authenticated agent"
|
|
2862
3574
|
);
|
|
2863
3575
|
statusCmd.option("--status <s>", "Filter by status (e.g. created, payment_confirmed)").option("--limit <n>", "Max results (default 50)", "50").action(async (opts) => {
|
|
2864
|
-
const agentId =
|
|
3576
|
+
const agentId = requireAgentId8();
|
|
2865
3577
|
try {
|
|
2866
3578
|
const res = await acnGet(
|
|
2867
3579
|
`/payments/tasks/agent/${agentId}`,
|
|
@@ -2880,7 +3592,7 @@ function payCommand() {
|
|
|
2880
3592
|
|
|
2881
3593
|
// src/index.ts
|
|
2882
3594
|
var { version } = require_package();
|
|
2883
|
-
var program = new
|
|
3595
|
+
var program = new import_commander18.Command();
|
|
2884
3596
|
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) => {
|
|
2885
3597
|
const opts = thisCommand.opts();
|
|
2886
3598
|
if (opts.json) setJsonMode(true);
|
|
@@ -2895,8 +3607,10 @@ program.addCommand(messageCommand());
|
|
|
2895
3607
|
program.addCommand(notifyCommand());
|
|
2896
3608
|
program.addCommand(inboxCommand());
|
|
2897
3609
|
program.addCommand(listenCommand());
|
|
3610
|
+
program.addCommand(deliveryCommand());
|
|
2898
3611
|
program.addCommand(sessionCommand());
|
|
2899
3612
|
program.addCommand(subnetCommand());
|
|
3613
|
+
program.addCommand(orgCommand());
|
|
2900
3614
|
program.addCommand(followCommand());
|
|
2901
3615
|
program.addCommand(walletCommand());
|
|
2902
3616
|
program.addCommand(payCommand());
|