@acnlabs/acn-cli 0.12.0 → 0.13.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +464 -73
- package/package.json +4 -2
package/dist/index.js
CHANGED
|
@@ -1,16 +1,37 @@
|
|
|
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;
|
|
3
6
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
4
9
|
var __commonJS = (cb, mod) => function __require() {
|
|
5
10
|
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
6
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
|
+
));
|
|
7
28
|
|
|
8
29
|
// package.json
|
|
9
30
|
var require_package = __commonJS({
|
|
10
31
|
"package.json"(exports2, module2) {
|
|
11
32
|
module2.exports = {
|
|
12
33
|
name: "@acnlabs/acn-cli",
|
|
13
|
-
version: "0.
|
|
34
|
+
version: "0.13.2",
|
|
14
35
|
description: "Official CLI for ACN (Agent Collaboration Network) \u2014 zero-integration agent access",
|
|
15
36
|
main: "dist/index.js",
|
|
16
37
|
bin: {
|
|
@@ -48,10 +69,12 @@ var require_package = __commonJS({
|
|
|
48
69
|
url: "https://github.com/acnlabs/ACN/issues"
|
|
49
70
|
},
|
|
50
71
|
dependencies: {
|
|
51
|
-
commander: "^12.0.0"
|
|
72
|
+
commander: "^12.0.0",
|
|
73
|
+
ws: "^8.18.0"
|
|
52
74
|
},
|
|
53
75
|
devDependencies: {
|
|
54
76
|
"@types/node": "^20.0.0",
|
|
77
|
+
"@types/ws": "^8.5.10",
|
|
55
78
|
tsup: "^8.0.0",
|
|
56
79
|
typescript: "^5.0.0",
|
|
57
80
|
vitest: "^3.0.5"
|
|
@@ -64,7 +87,7 @@ var require_package = __commonJS({
|
|
|
64
87
|
});
|
|
65
88
|
|
|
66
89
|
// src/index.ts
|
|
67
|
-
var
|
|
90
|
+
var import_commander16 = require("commander");
|
|
68
91
|
|
|
69
92
|
// src/output.ts
|
|
70
93
|
var jsonMode = false;
|
|
@@ -101,32 +124,91 @@ var import_path = require("path");
|
|
|
101
124
|
var import_fs = require("fs");
|
|
102
125
|
var CONFIG_DIR = (0, import_path.join)((0, import_os.homedir)(), ".acn");
|
|
103
126
|
var CONFIG_FILE = (0, import_path.join)(CONFIG_DIR, "config.json");
|
|
104
|
-
var
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
127
|
+
var REGION_BASE_URLS = {
|
|
128
|
+
global: "https://api.acnlabs.dev",
|
|
129
|
+
cn: "https://acn.acnlabs.cn"
|
|
130
|
+
};
|
|
131
|
+
var DEFAULT_BASE_URL = REGION_BASE_URLS.global;
|
|
132
|
+
function normalizeBaseUrl(url) {
|
|
133
|
+
let u = url.trim().replace(/\/+$/, "");
|
|
134
|
+
u = u.replace(/\/api\/v1$/i, "");
|
|
135
|
+
return u.replace(/\/+$/, "");
|
|
136
|
+
}
|
|
137
|
+
function baseUrlForRegion(region) {
|
|
138
|
+
const key = region.trim().toLowerCase();
|
|
139
|
+
if (key === "global" || key === "cn") {
|
|
140
|
+
return REGION_BASE_URLS[key];
|
|
108
141
|
}
|
|
142
|
+
throw new Error(`Unknown region "${region}". Valid: global | cn`);
|
|
143
|
+
}
|
|
144
|
+
function inferRegion(baseUrl) {
|
|
145
|
+
const u = normalizeBaseUrl(baseUrl);
|
|
146
|
+
if (u === REGION_BASE_URLS.global) return "global";
|
|
147
|
+
if (u === REGION_BASE_URLS.cn) return "cn";
|
|
148
|
+
return void 0;
|
|
149
|
+
}
|
|
150
|
+
function readConfigFile() {
|
|
151
|
+
if (!(0, import_fs.existsSync)(CONFIG_FILE)) return {};
|
|
109
152
|
try {
|
|
110
153
|
const raw = (0, import_fs.readFileSync)(CONFIG_FILE, "utf-8");
|
|
111
|
-
|
|
112
|
-
return {
|
|
113
|
-
base_url: parsed.base_url ?? DEFAULT_BASE_URL,
|
|
114
|
-
api_key: parsed.api_key,
|
|
115
|
-
agent_id: parsed.agent_id
|
|
116
|
-
};
|
|
154
|
+
return JSON.parse(raw);
|
|
117
155
|
} catch {
|
|
118
|
-
return {
|
|
156
|
+
return {};
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
function resolveBaseUrl(overrides) {
|
|
160
|
+
if (overrides?.base_url) {
|
|
161
|
+
return normalizeBaseUrl(overrides.base_url);
|
|
162
|
+
}
|
|
163
|
+
if (overrides?.region) {
|
|
164
|
+
return baseUrlForRegion(overrides.region);
|
|
165
|
+
}
|
|
166
|
+
const env = process.env.ACN_BASE_URL?.trim();
|
|
167
|
+
if (env) {
|
|
168
|
+
return normalizeBaseUrl(env);
|
|
169
|
+
}
|
|
170
|
+
const file = readConfigFile();
|
|
171
|
+
if (file.base_url) {
|
|
172
|
+
return normalizeBaseUrl(file.base_url);
|
|
173
|
+
}
|
|
174
|
+
if (file.region) {
|
|
175
|
+
return baseUrlForRegion(file.region);
|
|
119
176
|
}
|
|
177
|
+
return DEFAULT_BASE_URL;
|
|
178
|
+
}
|
|
179
|
+
function loadConfig() {
|
|
180
|
+
const file = readConfigFile();
|
|
181
|
+
const base_url = resolveBaseUrl();
|
|
182
|
+
return {
|
|
183
|
+
base_url,
|
|
184
|
+
api_key: file.api_key,
|
|
185
|
+
agent_id: file.agent_id,
|
|
186
|
+
region: inferRegion(base_url)
|
|
187
|
+
};
|
|
120
188
|
}
|
|
121
189
|
function saveConfig(updates) {
|
|
122
190
|
if (!(0, import_fs.existsSync)(CONFIG_DIR)) {
|
|
123
191
|
(0, import_fs.mkdirSync)(CONFIG_DIR, { recursive: true });
|
|
124
192
|
}
|
|
125
|
-
const
|
|
193
|
+
const file = readConfigFile();
|
|
194
|
+
const current = {
|
|
195
|
+
base_url: file.base_url ? normalizeBaseUrl(file.base_url) : DEFAULT_BASE_URL,
|
|
196
|
+
api_key: file.api_key,
|
|
197
|
+
agent_id: file.agent_id,
|
|
198
|
+
region: file.region === "global" || file.region === "cn" ? file.region : void 0
|
|
199
|
+
};
|
|
126
200
|
const next = { ...current, ...updates };
|
|
127
|
-
|
|
201
|
+
if (updates.region && !updates.base_url) {
|
|
202
|
+
next.base_url = baseUrlForRegion(updates.region);
|
|
203
|
+
}
|
|
204
|
+
if (updates.base_url && updates.region === void 0) {
|
|
205
|
+
next.region = inferRegion(updates.base_url);
|
|
206
|
+
}
|
|
207
|
+
const clean = { base_url: normalizeBaseUrl(next.base_url) };
|
|
128
208
|
if (next.api_key !== void 0) clean.api_key = next.api_key;
|
|
129
209
|
if (next.agent_id !== void 0) clean.agent_id = next.agent_id;
|
|
210
|
+
const region = next.region ?? inferRegion(clean.base_url);
|
|
211
|
+
if (region !== void 0) clean.region = region;
|
|
130
212
|
(0, import_fs.writeFileSync)(CONFIG_FILE, JSON.stringify(clean, null, 2), "utf-8");
|
|
131
213
|
}
|
|
132
214
|
function getConfigPath() {
|
|
@@ -134,7 +216,7 @@ function getConfigPath() {
|
|
|
134
216
|
}
|
|
135
217
|
|
|
136
218
|
// src/commands/config.ts
|
|
137
|
-
var VALID_KEYS = ["api-key", "agent-id", "base-url"];
|
|
219
|
+
var VALID_KEYS = ["api-key", "agent-id", "base-url", "region"];
|
|
138
220
|
var KEY_MAP = {
|
|
139
221
|
"api-key": "api_key",
|
|
140
222
|
"agent-id": "agent_id",
|
|
@@ -142,11 +224,25 @@ var KEY_MAP = {
|
|
|
142
224
|
};
|
|
143
225
|
function configCommand() {
|
|
144
226
|
const cmd = new import_commander.Command("config").description("Manage local ACN configuration");
|
|
145
|
-
cmd.command("set <key> <value>").description(
|
|
227
|
+
cmd.command("set <key> <value>").description(
|
|
228
|
+
`Set a config value. Keys: ${VALID_KEYS.join(", ")}. region is global|cn (sets base-url). Env ACN_BASE_URL overrides base-url at runtime.`
|
|
229
|
+
).action((key, value) => {
|
|
146
230
|
if (!VALID_KEYS.includes(key)) {
|
|
147
231
|
console.error(`Unknown key "${key}". Valid keys: ${VALID_KEYS.join(", ")}`);
|
|
148
232
|
process.exit(1);
|
|
149
233
|
}
|
|
234
|
+
if (key === "region") {
|
|
235
|
+
try {
|
|
236
|
+
const base_url = baseUrlForRegion(value);
|
|
237
|
+
const region = value.trim().toLowerCase();
|
|
238
|
+
saveConfig({ region, base_url });
|
|
239
|
+
output({ key, value: region, base_url }, `Set region = ${region} (base-url = ${base_url})`);
|
|
240
|
+
} catch (err) {
|
|
241
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
242
|
+
process.exit(1);
|
|
243
|
+
}
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
150
246
|
saveConfig({ [KEY_MAP[key]]: value });
|
|
151
247
|
output({ key, value }, `Set ${key} = ${value}`);
|
|
152
248
|
});
|
|
@@ -156,19 +252,21 @@ function configCommand() {
|
|
|
156
252
|
process.exit(1);
|
|
157
253
|
}
|
|
158
254
|
const config = loadConfig();
|
|
159
|
-
const val = config[KEY_MAP[key]];
|
|
255
|
+
const val = key === "region" ? config.region : config[KEY_MAP[key]];
|
|
160
256
|
if (val === void 0) {
|
|
161
257
|
console.error(`Key "${key}" is not set.`);
|
|
162
258
|
process.exit(1);
|
|
163
259
|
}
|
|
164
|
-
output({ key, value: val }, val);
|
|
260
|
+
output({ key, value: val }, String(val));
|
|
165
261
|
});
|
|
166
262
|
cmd.command("show").description("Show all config values").action(() => {
|
|
167
263
|
const config = loadConfig();
|
|
168
264
|
const path = getConfigPath();
|
|
265
|
+
const envOverride = process.env.ACN_BASE_URL?.trim() ? ` (ACN_BASE_URL override active)` : "";
|
|
169
266
|
output(config, [
|
|
170
267
|
`Config file: ${path}`,
|
|
171
|
-
`
|
|
268
|
+
` region : ${config.region ?? "(custom / unknown)"}`,
|
|
269
|
+
` base-url : ${config.base_url}${envOverride}`,
|
|
172
270
|
` api-key : ${config.api_key ? maskKey(config.api_key) : "(not set)"}`,
|
|
173
271
|
` agent-id : ${config.agent_id ?? "(not set)"}`
|
|
174
272
|
].join("\n"));
|
|
@@ -205,8 +303,9 @@ function extractDetail(body) {
|
|
|
205
303
|
}
|
|
206
304
|
async function acnFetch(path, options = {}) {
|
|
207
305
|
const config = loadConfig();
|
|
208
|
-
const { params, ...fetchOptions } = options;
|
|
209
|
-
const
|
|
306
|
+
const { params, baseUrl: baseUrlOverride, ...fetchOptions } = options;
|
|
307
|
+
const origin = baseUrlOverride ? normalizeBaseUrl(baseUrlOverride) : config.base_url;
|
|
308
|
+
const url = new URL(`${origin}/api/v1${path}`);
|
|
210
309
|
if (params) {
|
|
211
310
|
for (const [k, v] of Object.entries(params)) {
|
|
212
311
|
if (v !== void 0) url.searchParams.set(k, String(v));
|
|
@@ -234,54 +333,98 @@ async function acnFetch(path, options = {}) {
|
|
|
234
333
|
}
|
|
235
334
|
return res.json();
|
|
236
335
|
}
|
|
237
|
-
function acnGet(path, params) {
|
|
238
|
-
return acnFetch(path, { method: "GET", params });
|
|
336
|
+
function acnGet(path, params, opts) {
|
|
337
|
+
return acnFetch(path, { method: "GET", params, baseUrl: opts?.baseUrl });
|
|
239
338
|
}
|
|
240
|
-
function acnPost(path, body) {
|
|
339
|
+
function acnPost(path, body, opts) {
|
|
241
340
|
return acnFetch(path, {
|
|
242
341
|
method: "POST",
|
|
243
|
-
body: body !== void 0 ? JSON.stringify(body) : void 0
|
|
342
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0,
|
|
343
|
+
baseUrl: opts?.baseUrl
|
|
244
344
|
});
|
|
245
345
|
}
|
|
246
|
-
function acnPatch(path, body) {
|
|
346
|
+
function acnPatch(path, body, opts) {
|
|
247
347
|
return acnFetch(path, {
|
|
248
348
|
method: "PATCH",
|
|
249
|
-
body: body !== void 0 ? JSON.stringify(body) : void 0
|
|
349
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0,
|
|
350
|
+
baseUrl: opts?.baseUrl
|
|
250
351
|
});
|
|
251
352
|
}
|
|
252
|
-
function acnDelete(path) {
|
|
253
|
-
return acnFetch(path, { method: "DELETE" });
|
|
353
|
+
function acnDelete(path, opts) {
|
|
354
|
+
return acnFetch(path, { method: "DELETE", baseUrl: opts?.baseUrl });
|
|
254
355
|
}
|
|
255
356
|
|
|
256
357
|
// src/commands/join.ts
|
|
257
358
|
function joinCommand() {
|
|
258
|
-
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").
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
359
|
+
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(
|
|
360
|
+
"--relay",
|
|
361
|
+
"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."
|
|
362
|
+
).option(
|
|
363
|
+
"--region <region>",
|
|
364
|
+
"ACN deployment to join: global (api.acnlabs.dev) or cn (acn.acnlabs.cn). Pick by where the agent is hosted \u2014 not by user nationality."
|
|
365
|
+
).option(
|
|
366
|
+
"--base-url <url>",
|
|
367
|
+
"Override ACN origin (no /api/v1). Overrides --region and ACN_BASE_URL for this join."
|
|
368
|
+
).action(
|
|
369
|
+
async (opts) => {
|
|
370
|
+
if (opts.region && opts.baseUrl) {
|
|
371
|
+
console.error("Use either --region or --base-url, not both.");
|
|
372
|
+
process.exit(1);
|
|
373
|
+
}
|
|
374
|
+
if (opts.region) {
|
|
375
|
+
const r = opts.region.trim().toLowerCase();
|
|
376
|
+
if (r !== "global" && r !== "cn") {
|
|
377
|
+
console.error(`Unknown --region "${opts.region}". Valid: global | cn`);
|
|
378
|
+
process.exit(1);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
const base_url = resolveBaseUrl({
|
|
382
|
+
base_url: opts.baseUrl,
|
|
383
|
+
region: opts.region
|
|
384
|
+
});
|
|
385
|
+
const region = opts.region?.trim().toLowerCase() === "cn" || opts.region?.trim().toLowerCase() === "global" ? opts.region.trim().toLowerCase() : inferRegion(base_url);
|
|
386
|
+
const tags = opts.tags.split(",").map((s) => s.trim()).filter(Boolean);
|
|
387
|
+
const body = {
|
|
388
|
+
name: opts.name,
|
|
389
|
+
description: opts.description ?? `${opts.name} \u2014 registered via acn-cli`,
|
|
390
|
+
tags,
|
|
391
|
+
...opts.endpoint ? { endpoint: opts.endpoint } : {},
|
|
392
|
+
// ADR-0012 Mode B: relay delivery needs a push mode (open) so the
|
|
393
|
+
// gateway actually pushes inbound messages down the WebSocket; the
|
|
394
|
+
// server-side validator then waives the public-URL requirement.
|
|
395
|
+
...opts.relay ? { delivery: "relay", communication_policy: { mode: "open" } } : {}
|
|
396
|
+
};
|
|
397
|
+
try {
|
|
398
|
+
const res = await acnPost("/agents/join", body, {
|
|
399
|
+
baseUrl: base_url
|
|
400
|
+
});
|
|
401
|
+
saveConfig({
|
|
402
|
+
api_key: res.api_key,
|
|
403
|
+
agent_id: res.agent_id,
|
|
404
|
+
base_url,
|
|
405
|
+
...region ? { region } : {}
|
|
406
|
+
});
|
|
407
|
+
const claimLine = res.claim_url ? `
|
|
270
408
|
Claim URL: ${res.claim_url}` : "";
|
|
271
|
-
|
|
409
|
+
const verifyLine = res.verification_code ? `
|
|
272
410
|
Verify : ${res.verification_code}` : "";
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
411
|
+
const regionLine = region ? `
|
|
412
|
+
Region : ${region}` : "";
|
|
413
|
+
output(res, [
|
|
414
|
+
`Registered successfully!`,
|
|
415
|
+
` Agent ID : ${res.agent_id}`,
|
|
416
|
+
` API Key : ${res.api_key}`,
|
|
417
|
+
` ACN : ${base_url}${regionLine}`,
|
|
418
|
+
` Status : ${res.status}${claimLine}${verifyLine}`,
|
|
419
|
+
``,
|
|
420
|
+
`Credentials saved to ~/.acn/config.json`,
|
|
421
|
+
`Do not reuse this api_key against another region \u2014 re-join instead.`
|
|
422
|
+
].join("\n"));
|
|
423
|
+
} catch (err) {
|
|
424
|
+
handleError(err);
|
|
425
|
+
}
|
|
283
426
|
}
|
|
284
|
-
|
|
427
|
+
);
|
|
285
428
|
}
|
|
286
429
|
|
|
287
430
|
// src/commands/heartbeat.ts
|
|
@@ -1230,8 +1373,255 @@ ${formatPolicy(res)}`);
|
|
|
1230
1373
|
return cmd;
|
|
1231
1374
|
}
|
|
1232
1375
|
|
|
1233
|
-
// src/commands/
|
|
1376
|
+
// src/commands/listen.ts
|
|
1234
1377
|
var import_commander10 = require("commander");
|
|
1378
|
+
var import_child_process = require("child_process");
|
|
1379
|
+
var import_ws = __toESM(require("ws"));
|
|
1380
|
+
var STRIP_HEADERS = /* @__PURE__ */ new Set([
|
|
1381
|
+
"host",
|
|
1382
|
+
"content-length",
|
|
1383
|
+
"connection",
|
|
1384
|
+
"transfer-encoding"
|
|
1385
|
+
]);
|
|
1386
|
+
function toWebsocketUrl(baseUrl, agentId) {
|
|
1387
|
+
const u = new URL(baseUrl);
|
|
1388
|
+
u.protocol = u.protocol === "https:" ? "wss:" : "ws:";
|
|
1389
|
+
u.pathname = `/ws/${agentId}`;
|
|
1390
|
+
u.search = "";
|
|
1391
|
+
return u.toString();
|
|
1392
|
+
}
|
|
1393
|
+
function decodeBody(frame) {
|
|
1394
|
+
if (frame.body_encoding === "base64") {
|
|
1395
|
+
return Buffer.from(frame.body ?? "", "base64");
|
|
1396
|
+
}
|
|
1397
|
+
return Buffer.from(frame.body ?? "", "utf-8");
|
|
1398
|
+
}
|
|
1399
|
+
function errorResponse(id, status, detail) {
|
|
1400
|
+
return {
|
|
1401
|
+
type: "a2a_response",
|
|
1402
|
+
id,
|
|
1403
|
+
status,
|
|
1404
|
+
headers: { "content-type": "application/json" },
|
|
1405
|
+
body: JSON.stringify({ error: detail })
|
|
1406
|
+
};
|
|
1407
|
+
}
|
|
1408
|
+
async function dispatchA2aRequest(frame, opts, send, deps = {}) {
|
|
1409
|
+
const bodyBuf = decodeBody(frame);
|
|
1410
|
+
try {
|
|
1411
|
+
if (opts.forward) {
|
|
1412
|
+
await forwardToHttp(frame, bodyBuf, opts.forward, deps.fetchFn ?? fetch, send);
|
|
1413
|
+
return;
|
|
1414
|
+
}
|
|
1415
|
+
if (opts.exec) {
|
|
1416
|
+
send(await runExec(frame, bodyBuf, opts.exec, deps.spawnFn ?? import_child_process.spawn));
|
|
1417
|
+
return;
|
|
1418
|
+
}
|
|
1419
|
+
send(errorResponse(frame.id, 500, "no handler configured"));
|
|
1420
|
+
} catch (err) {
|
|
1421
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1422
|
+
send(errorResponse(frame.id, 502, `handler failed: ${msg}`));
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
function buildForwardHeaders(frame) {
|
|
1426
|
+
const headers = {};
|
|
1427
|
+
for (const [k, v] of Object.entries(frame.headers ?? {})) {
|
|
1428
|
+
if (!STRIP_HEADERS.has(k.toLowerCase())) headers[k] = v;
|
|
1429
|
+
}
|
|
1430
|
+
return headers;
|
|
1431
|
+
}
|
|
1432
|
+
async function forwardToHttp(frame, bodyBuf, base, fetchFn, send) {
|
|
1433
|
+
const suffix = frame.path && frame.path !== "/" ? "/" + frame.path.replace(/^\//, "") : "";
|
|
1434
|
+
const targetUrl = base.replace(/\/$/, "") + suffix;
|
|
1435
|
+
const method = (frame.method ?? "POST").toUpperCase();
|
|
1436
|
+
const init = { method, headers: buildForwardHeaders(frame) };
|
|
1437
|
+
if (method !== "GET" && method !== "HEAD") {
|
|
1438
|
+
init.body = bodyBuf;
|
|
1439
|
+
}
|
|
1440
|
+
const res = await fetchFn(targetUrl, init);
|
|
1441
|
+
const contentType = res.headers.get("content-type") ?? "application/json";
|
|
1442
|
+
if (contentType.includes("text/event-stream") && res.body) {
|
|
1443
|
+
const reader = res.body.getReader();
|
|
1444
|
+
let seq = 0;
|
|
1445
|
+
try {
|
|
1446
|
+
for (; ; ) {
|
|
1447
|
+
const { done, value } = await reader.read();
|
|
1448
|
+
if (done) break;
|
|
1449
|
+
if (value && value.length > 0) {
|
|
1450
|
+
send({
|
|
1451
|
+
type: "a2a_stream_chunk",
|
|
1452
|
+
id: frame.id,
|
|
1453
|
+
seq: seq++,
|
|
1454
|
+
data: Buffer.from(value).toString("base64"),
|
|
1455
|
+
data_encoding: "base64"
|
|
1456
|
+
});
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
send({ type: "a2a_stream_end", id: frame.id, status: res.status });
|
|
1460
|
+
} catch (err) {
|
|
1461
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1462
|
+
send({ type: "a2a_stream_end", id: frame.id, error: msg });
|
|
1463
|
+
} finally {
|
|
1464
|
+
reader.releaseLock();
|
|
1465
|
+
}
|
|
1466
|
+
return;
|
|
1467
|
+
}
|
|
1468
|
+
const respText = await res.text();
|
|
1469
|
+
send({
|
|
1470
|
+
type: "a2a_response",
|
|
1471
|
+
id: frame.id,
|
|
1472
|
+
status: res.status,
|
|
1473
|
+
headers: { "content-type": contentType },
|
|
1474
|
+
body: respText
|
|
1475
|
+
});
|
|
1476
|
+
}
|
|
1477
|
+
function runExec(frame, bodyBuf, command, spawnFn) {
|
|
1478
|
+
return new Promise((resolve) => {
|
|
1479
|
+
const child = spawnFn(command, { shell: true });
|
|
1480
|
+
const out = [];
|
|
1481
|
+
const errOut = [];
|
|
1482
|
+
child.stdout?.on("data", (d) => out.push(Buffer.from(d)));
|
|
1483
|
+
child.stderr?.on("data", (d) => errOut.push(Buffer.from(d)));
|
|
1484
|
+
child.on(
|
|
1485
|
+
"error",
|
|
1486
|
+
(e) => resolve(errorResponse(frame.id, 502, `exec error: ${e.message}`))
|
|
1487
|
+
);
|
|
1488
|
+
child.on("close", (code) => {
|
|
1489
|
+
const stdout = Buffer.concat(out).toString("utf-8");
|
|
1490
|
+
if (code === 0) {
|
|
1491
|
+
resolve({
|
|
1492
|
+
type: "a2a_response",
|
|
1493
|
+
id: frame.id,
|
|
1494
|
+
status: 200,
|
|
1495
|
+
headers: { "content-type": "application/json" },
|
|
1496
|
+
body: stdout
|
|
1497
|
+
});
|
|
1498
|
+
} else {
|
|
1499
|
+
const stderr = Buffer.concat(errOut).toString("utf-8");
|
|
1500
|
+
resolve(
|
|
1501
|
+
errorResponse(frame.id, 500, `exec exited ${code}: ${stderr.slice(0, 500)}`)
|
|
1502
|
+
);
|
|
1503
|
+
}
|
|
1504
|
+
});
|
|
1505
|
+
child.stdin?.end(bodyBuf);
|
|
1506
|
+
});
|
|
1507
|
+
}
|
|
1508
|
+
function rawToString(data) {
|
|
1509
|
+
if (typeof data === "string") return data;
|
|
1510
|
+
if (Buffer.isBuffer(data)) return data.toString("utf-8");
|
|
1511
|
+
if (Array.isArray(data)) return Buffer.concat(data).toString("utf-8");
|
|
1512
|
+
return Buffer.from(data).toString("utf-8");
|
|
1513
|
+
}
|
|
1514
|
+
var KEEPALIVE_INTERVAL_MS = 3e4;
|
|
1515
|
+
var INITIAL_BACKOFF_MS = 1e3;
|
|
1516
|
+
var MAX_BACKOFF_MS = 3e4;
|
|
1517
|
+
function runListener(cfg) {
|
|
1518
|
+
const wsUrl = toWebsocketUrl(cfg.baseUrl, cfg.agentId);
|
|
1519
|
+
let backoff = INITIAL_BACKOFF_MS;
|
|
1520
|
+
let stopped = false;
|
|
1521
|
+
const connect = () => {
|
|
1522
|
+
const ws = new import_ws.default(wsUrl, {
|
|
1523
|
+
headers: { Authorization: `Bearer ${cfg.apiKey}` }
|
|
1524
|
+
});
|
|
1525
|
+
let keepalive;
|
|
1526
|
+
ws.on("open", () => {
|
|
1527
|
+
console.error(`[acn listen] connected as ${cfg.agentId} \u2192 ${wsUrl}`);
|
|
1528
|
+
backoff = INITIAL_BACKOFF_MS;
|
|
1529
|
+
keepalive = setInterval(() => {
|
|
1530
|
+
if (ws.readyState === import_ws.default.OPEN) {
|
|
1531
|
+
ws.send(JSON.stringify({ type: "ping" }));
|
|
1532
|
+
}
|
|
1533
|
+
}, KEEPALIVE_INTERVAL_MS);
|
|
1534
|
+
});
|
|
1535
|
+
ws.on("message", (data) => {
|
|
1536
|
+
let frame;
|
|
1537
|
+
try {
|
|
1538
|
+
frame = JSON.parse(rawToString(data));
|
|
1539
|
+
} catch {
|
|
1540
|
+
return;
|
|
1541
|
+
}
|
|
1542
|
+
if (!frame || typeof frame !== "object") return;
|
|
1543
|
+
const f = frame;
|
|
1544
|
+
if (f.type === "a2a_request" && typeof f.id === "string") {
|
|
1545
|
+
const send = (out) => {
|
|
1546
|
+
if (ws.readyState === import_ws.default.OPEN) {
|
|
1547
|
+
ws.send(JSON.stringify(out));
|
|
1548
|
+
}
|
|
1549
|
+
};
|
|
1550
|
+
void dispatchA2aRequest(f, cfg, send);
|
|
1551
|
+
}
|
|
1552
|
+
});
|
|
1553
|
+
ws.on("close", (code, reason) => {
|
|
1554
|
+
if (keepalive) clearInterval(keepalive);
|
|
1555
|
+
if (stopped) return;
|
|
1556
|
+
if (code === 4401 || code === 4403 || code === 4429) {
|
|
1557
|
+
console.error(
|
|
1558
|
+
`[acn listen] fatal close (code ${code}): ${reason.toString() || "see api_key / agent_id"}`
|
|
1559
|
+
);
|
|
1560
|
+
process.exit(1);
|
|
1561
|
+
}
|
|
1562
|
+
console.error(
|
|
1563
|
+
`[acn listen] disconnected (code ${code}); reconnecting in ${backoff / 1e3}s`
|
|
1564
|
+
);
|
|
1565
|
+
setTimeout(connect, backoff);
|
|
1566
|
+
backoff = Math.min(backoff * 2, MAX_BACKOFF_MS);
|
|
1567
|
+
});
|
|
1568
|
+
ws.on("error", (err) => {
|
|
1569
|
+
console.error(`[acn listen] socket error: ${err.message}`);
|
|
1570
|
+
});
|
|
1571
|
+
};
|
|
1572
|
+
process.on("SIGINT", () => {
|
|
1573
|
+
stopped = true;
|
|
1574
|
+
console.error("\n[acn listen] stopping");
|
|
1575
|
+
process.exit(0);
|
|
1576
|
+
});
|
|
1577
|
+
connect();
|
|
1578
|
+
}
|
|
1579
|
+
function listenCommand() {
|
|
1580
|
+
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). For agents with no public endpoint."
|
|
1582
|
+
).option(
|
|
1583
|
+
"--forward <url>",
|
|
1584
|
+
"Tunnel each relayed request to a local HTTP server (e.g. http://localhost:8080)"
|
|
1585
|
+
).option(
|
|
1586
|
+
"--exec <command>",
|
|
1587
|
+
"Run a shell command per request: body on stdin, stdout becomes the response"
|
|
1588
|
+
).option("-i, --agent-id <id>", "Agent ID (defaults to config)").action((opts) => {
|
|
1589
|
+
const config = loadConfig();
|
|
1590
|
+
const apiKey = config.api_key;
|
|
1591
|
+
const agentId = opts.agentId ?? config.agent_id;
|
|
1592
|
+
if (!apiKey) {
|
|
1593
|
+
console.error(
|
|
1594
|
+
"No API key found. Run `acn join` first or `acn config set api-key <key>`."
|
|
1595
|
+
);
|
|
1596
|
+
process.exit(1);
|
|
1597
|
+
}
|
|
1598
|
+
if (!agentId) {
|
|
1599
|
+
console.error(
|
|
1600
|
+
"No agent ID found. Run `acn join` first or `acn config set agent-id <id>`."
|
|
1601
|
+
);
|
|
1602
|
+
process.exit(1);
|
|
1603
|
+
}
|
|
1604
|
+
if (!opts.forward && !opts.exec) {
|
|
1605
|
+
console.error("Provide a handler: --forward <url> or --exec <command>.");
|
|
1606
|
+
process.exit(1);
|
|
1607
|
+
}
|
|
1608
|
+
if (opts.forward && opts.exec) {
|
|
1609
|
+
console.error("Use only one handler: --forward or --exec, not both.");
|
|
1610
|
+
process.exit(1);
|
|
1611
|
+
}
|
|
1612
|
+
runListener({
|
|
1613
|
+
agentId,
|
|
1614
|
+
apiKey,
|
|
1615
|
+
baseUrl: config.base_url,
|
|
1616
|
+
forward: opts.forward,
|
|
1617
|
+
exec: opts.exec
|
|
1618
|
+
});
|
|
1619
|
+
});
|
|
1620
|
+
return cmd;
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1623
|
+
// src/commands/session.ts
|
|
1624
|
+
var import_commander11 = require("commander");
|
|
1235
1625
|
function requireAgentId3() {
|
|
1236
1626
|
const config = loadConfig();
|
|
1237
1627
|
if (!config.api_key) {
|
|
@@ -1276,7 +1666,7 @@ function formatEntry2(s, index) {
|
|
|
1276
1666
|
return lines.join("\n");
|
|
1277
1667
|
}
|
|
1278
1668
|
function sessionCommand() {
|
|
1279
|
-
const cmd = new
|
|
1669
|
+
const cmd = new import_commander11.Command("session").description(
|
|
1280
1670
|
"Real-time session layer: bidirectional channel between two agents"
|
|
1281
1671
|
);
|
|
1282
1672
|
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(
|
|
@@ -1354,7 +1744,7 @@ ${formatEntry2(res)}`);
|
|
|
1354
1744
|
}
|
|
1355
1745
|
|
|
1356
1746
|
// src/commands/subnet.ts
|
|
1357
|
-
var
|
|
1747
|
+
var import_commander12 = require("commander");
|
|
1358
1748
|
function requireAgentId4() {
|
|
1359
1749
|
const config = loadConfig();
|
|
1360
1750
|
if (!config.api_key) {
|
|
@@ -1442,7 +1832,7 @@ function formatSubnet(s, index) {
|
|
|
1442
1832
|
return lines.join("\n");
|
|
1443
1833
|
}
|
|
1444
1834
|
function subnetCommand() {
|
|
1445
|
-
const cmd = new
|
|
1835
|
+
const cmd = new import_commander12.Command("subnet").description("Manage ACN subnets");
|
|
1446
1836
|
cmd.command("list").description(
|
|
1447
1837
|
"List subnets. Without --all/--parent shows only subnets you have joined."
|
|
1448
1838
|
).option("--all", "Show all public subnets on ACN (not just your own)").option(
|
|
@@ -1659,7 +2049,7 @@ ${JSON.stringify(res.agents, null, 2)}`);
|
|
|
1659
2049
|
handleError(err);
|
|
1660
2050
|
}
|
|
1661
2051
|
});
|
|
1662
|
-
const requests = new
|
|
2052
|
+
const requests = new import_commander12.Command("requests").description(
|
|
1663
2053
|
"Manage join-requests for a subnet (ADR-0004)"
|
|
1664
2054
|
);
|
|
1665
2055
|
requests.command("list <subnet_id>").description(
|
|
@@ -1801,7 +2191,7 @@ ${JSON.stringify(res.agents, null, 2)}`);
|
|
|
1801
2191
|
}
|
|
1802
2192
|
);
|
|
1803
2193
|
cmd.addCommand(requests);
|
|
1804
|
-
const invitations = new
|
|
2194
|
+
const invitations = new import_commander12.Command("invitations").description(
|
|
1805
2195
|
"Manage invitations on a subnet (ADR-0004)"
|
|
1806
2196
|
);
|
|
1807
2197
|
invitations.command("send <subnet_id>").description(
|
|
@@ -1937,7 +2327,7 @@ ${JSON.stringify(res.agents, null, 2)}`);
|
|
|
1937
2327
|
}
|
|
1938
2328
|
);
|
|
1939
2329
|
cmd.addCommand(invitations);
|
|
1940
|
-
const allowlist = new
|
|
2330
|
+
const allowlist = new import_commander12.Command("allowlist").description(
|
|
1941
2331
|
"Manage a subnet allowlist (ADR-0004)"
|
|
1942
2332
|
);
|
|
1943
2333
|
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(
|
|
@@ -2005,7 +2395,7 @@ ${JSON.stringify(res.agents, null, 2)}`);
|
|
|
2005
2395
|
}
|
|
2006
2396
|
);
|
|
2007
2397
|
cmd.addCommand(allowlist);
|
|
2008
|
-
const harness = new
|
|
2398
|
+
const harness = new import_commander12.Command("harness").description("Manage Org Harness webhook for a subnet");
|
|
2009
2399
|
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) => {
|
|
2010
2400
|
const config = loadConfig();
|
|
2011
2401
|
if (!config.api_key) {
|
|
@@ -2048,7 +2438,7 @@ ${JSON.stringify(res.agents, null, 2)}`);
|
|
|
2048
2438
|
}
|
|
2049
2439
|
|
|
2050
2440
|
// src/commands/follow.ts
|
|
2051
|
-
var
|
|
2441
|
+
var import_commander13 = require("commander");
|
|
2052
2442
|
function requireAgentId5() {
|
|
2053
2443
|
const config = loadConfig();
|
|
2054
2444
|
if (!config.api_key) {
|
|
@@ -2071,7 +2461,7 @@ function formatAgent2(a, i) {
|
|
|
2071
2461
|
].join("\n");
|
|
2072
2462
|
}
|
|
2073
2463
|
function followCommand() {
|
|
2074
|
-
const cmd = new
|
|
2464
|
+
const cmd = new import_commander13.Command("follow").description("Follow/unfollow agents and inspect follow graph");
|
|
2075
2465
|
cmd.command("add <target_id>").description("Follow another agent").option("-i, --agent-id <id>", "Agent ID (defaults to config)").action(async (targetId, opts) => {
|
|
2076
2466
|
const agentId = opts.agentId ?? requireAgentId5();
|
|
2077
2467
|
try {
|
|
@@ -2162,7 +2552,7 @@ function followCommand() {
|
|
|
2162
2552
|
}
|
|
2163
2553
|
|
|
2164
2554
|
// src/commands/wallet.ts
|
|
2165
|
-
var
|
|
2555
|
+
var import_commander14 = require("commander");
|
|
2166
2556
|
function requireAgentId6() {
|
|
2167
2557
|
const config = loadConfig();
|
|
2168
2558
|
if (!config.api_key) {
|
|
@@ -2205,7 +2595,7 @@ async function showWalletInfo(opts) {
|
|
|
2205
2595
|
}
|
|
2206
2596
|
}
|
|
2207
2597
|
function walletCommand() {
|
|
2208
|
-
const cmd = new
|
|
2598
|
+
const cmd = new import_commander14.Command("wallet").description("View and manage agent's wallet & payment info");
|
|
2209
2599
|
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);
|
|
2210
2600
|
cmd.command("set-capability").description("Declare which payment methods, networks, and wallets you accept").requiredOption(
|
|
2211
2601
|
"--methods <csv>",
|
|
@@ -2384,7 +2774,7 @@ function walletCommand() {
|
|
|
2384
2774
|
}
|
|
2385
2775
|
|
|
2386
2776
|
// src/commands/pay.ts
|
|
2387
|
-
var
|
|
2777
|
+
var import_commander15 = require("commander");
|
|
2388
2778
|
function requireAgentId7() {
|
|
2389
2779
|
const config = loadConfig();
|
|
2390
2780
|
if (!config.api_key) {
|
|
@@ -2398,8 +2788,8 @@ function requireAgentId7() {
|
|
|
2398
2788
|
return config.agent_id;
|
|
2399
2789
|
}
|
|
2400
2790
|
function payCommand() {
|
|
2401
|
-
const cmd = new
|
|
2402
|
-
const createCmd = new
|
|
2791
|
+
const cmd = new import_commander15.Command("pay").description("Manage payment tasks between agents");
|
|
2792
|
+
const createCmd = new import_commander15.Command("create").description("Create a payment task to another agent");
|
|
2403
2793
|
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(
|
|
2404
2794
|
async (opts) => {
|
|
2405
2795
|
const fromAgent = requireAgentId7();
|
|
@@ -2445,7 +2835,7 @@ function payCommand() {
|
|
|
2445
2835
|
}
|
|
2446
2836
|
}
|
|
2447
2837
|
);
|
|
2448
|
-
const confirmCmd = new
|
|
2838
|
+
const confirmCmd = new import_commander15.Command("confirm").description(
|
|
2449
2839
|
"Confirm an external payment has been made (buyer only)"
|
|
2450
2840
|
);
|
|
2451
2841
|
confirmCmd.requiredOption("--task-id <id>", "Payment task ID to confirm").requiredOption(
|
|
@@ -2467,7 +2857,7 @@ function payCommand() {
|
|
|
2467
2857
|
handleError(err);
|
|
2468
2858
|
}
|
|
2469
2859
|
});
|
|
2470
|
-
const statusCmd = new
|
|
2860
|
+
const statusCmd = new import_commander15.Command("status").description(
|
|
2471
2861
|
"Show payment tasks for the authenticated agent"
|
|
2472
2862
|
);
|
|
2473
2863
|
statusCmd.option("--status <s>", "Filter by status (e.g. created, payment_confirmed)").option("--limit <n>", "Max results (default 50)", "50").action(async (opts) => {
|
|
@@ -2490,7 +2880,7 @@ function payCommand() {
|
|
|
2490
2880
|
|
|
2491
2881
|
// src/index.ts
|
|
2492
2882
|
var { version } = require_package();
|
|
2493
|
-
var program = new
|
|
2883
|
+
var program = new import_commander16.Command();
|
|
2494
2884
|
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) => {
|
|
2495
2885
|
const opts = thisCommand.opts();
|
|
2496
2886
|
if (opts.json) setJsonMode(true);
|
|
@@ -2504,6 +2894,7 @@ program.addCommand(tasksCommand());
|
|
|
2504
2894
|
program.addCommand(messageCommand());
|
|
2505
2895
|
program.addCommand(notifyCommand());
|
|
2506
2896
|
program.addCommand(inboxCommand());
|
|
2897
|
+
program.addCommand(listenCommand());
|
|
2507
2898
|
program.addCommand(sessionCommand());
|
|
2508
2899
|
program.addCommand(subnetCommand());
|
|
2509
2900
|
program.addCommand(followCommand());
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@acnlabs/acn-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.2",
|
|
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"
|