@acnlabs/acn-cli 0.13.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 +143 -32
- package/package.json +1 -1
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.13.
|
|
34
|
+
version: "0.13.2",
|
|
35
35
|
description: "Official CLI for ACN (Agent Collaboration Network) \u2014 zero-integration agent access",
|
|
36
36
|
main: "dist/index.js",
|
|
37
37
|
bin: {
|
|
@@ -124,32 +124,91 @@ var import_path = require("path");
|
|
|
124
124
|
var import_fs = require("fs");
|
|
125
125
|
var CONFIG_DIR = (0, import_path.join)((0, import_os.homedir)(), ".acn");
|
|
126
126
|
var CONFIG_FILE = (0, import_path.join)(CONFIG_DIR, "config.json");
|
|
127
|
-
var
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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];
|
|
131
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 {};
|
|
132
152
|
try {
|
|
133
153
|
const raw = (0, import_fs.readFileSync)(CONFIG_FILE, "utf-8");
|
|
134
|
-
|
|
135
|
-
return {
|
|
136
|
-
base_url: parsed.base_url ?? DEFAULT_BASE_URL,
|
|
137
|
-
api_key: parsed.api_key,
|
|
138
|
-
agent_id: parsed.agent_id
|
|
139
|
-
};
|
|
154
|
+
return JSON.parse(raw);
|
|
140
155
|
} catch {
|
|
141
|
-
return {
|
|
156
|
+
return {};
|
|
142
157
|
}
|
|
143
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);
|
|
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
|
+
};
|
|
188
|
+
}
|
|
144
189
|
function saveConfig(updates) {
|
|
145
190
|
if (!(0, import_fs.existsSync)(CONFIG_DIR)) {
|
|
146
191
|
(0, import_fs.mkdirSync)(CONFIG_DIR, { recursive: true });
|
|
147
192
|
}
|
|
148
|
-
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
|
+
};
|
|
149
200
|
const next = { ...current, ...updates };
|
|
150
|
-
|
|
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) };
|
|
151
208
|
if (next.api_key !== void 0) clean.api_key = next.api_key;
|
|
152
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;
|
|
153
212
|
(0, import_fs.writeFileSync)(CONFIG_FILE, JSON.stringify(clean, null, 2), "utf-8");
|
|
154
213
|
}
|
|
155
214
|
function getConfigPath() {
|
|
@@ -157,7 +216,7 @@ function getConfigPath() {
|
|
|
157
216
|
}
|
|
158
217
|
|
|
159
218
|
// src/commands/config.ts
|
|
160
|
-
var VALID_KEYS = ["api-key", "agent-id", "base-url"];
|
|
219
|
+
var VALID_KEYS = ["api-key", "agent-id", "base-url", "region"];
|
|
161
220
|
var KEY_MAP = {
|
|
162
221
|
"api-key": "api_key",
|
|
163
222
|
"agent-id": "agent_id",
|
|
@@ -165,11 +224,25 @@ var KEY_MAP = {
|
|
|
165
224
|
};
|
|
166
225
|
function configCommand() {
|
|
167
226
|
const cmd = new import_commander.Command("config").description("Manage local ACN configuration");
|
|
168
|
-
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) => {
|
|
169
230
|
if (!VALID_KEYS.includes(key)) {
|
|
170
231
|
console.error(`Unknown key "${key}". Valid keys: ${VALID_KEYS.join(", ")}`);
|
|
171
232
|
process.exit(1);
|
|
172
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
|
+
}
|
|
173
246
|
saveConfig({ [KEY_MAP[key]]: value });
|
|
174
247
|
output({ key, value }, `Set ${key} = ${value}`);
|
|
175
248
|
});
|
|
@@ -179,19 +252,21 @@ function configCommand() {
|
|
|
179
252
|
process.exit(1);
|
|
180
253
|
}
|
|
181
254
|
const config = loadConfig();
|
|
182
|
-
const val = config[KEY_MAP[key]];
|
|
255
|
+
const val = key === "region" ? config.region : config[KEY_MAP[key]];
|
|
183
256
|
if (val === void 0) {
|
|
184
257
|
console.error(`Key "${key}" is not set.`);
|
|
185
258
|
process.exit(1);
|
|
186
259
|
}
|
|
187
|
-
output({ key, value: val }, val);
|
|
260
|
+
output({ key, value: val }, String(val));
|
|
188
261
|
});
|
|
189
262
|
cmd.command("show").description("Show all config values").action(() => {
|
|
190
263
|
const config = loadConfig();
|
|
191
264
|
const path = getConfigPath();
|
|
265
|
+
const envOverride = process.env.ACN_BASE_URL?.trim() ? ` (ACN_BASE_URL override active)` : "";
|
|
192
266
|
output(config, [
|
|
193
267
|
`Config file: ${path}`,
|
|
194
|
-
`
|
|
268
|
+
` region : ${config.region ?? "(custom / unknown)"}`,
|
|
269
|
+
` base-url : ${config.base_url}${envOverride}`,
|
|
195
270
|
` api-key : ${config.api_key ? maskKey(config.api_key) : "(not set)"}`,
|
|
196
271
|
` agent-id : ${config.agent_id ?? "(not set)"}`
|
|
197
272
|
].join("\n"));
|
|
@@ -228,8 +303,9 @@ function extractDetail(body) {
|
|
|
228
303
|
}
|
|
229
304
|
async function acnFetch(path, options = {}) {
|
|
230
305
|
const config = loadConfig();
|
|
231
|
-
const { params, ...fetchOptions } = options;
|
|
232
|
-
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}`);
|
|
233
309
|
if (params) {
|
|
234
310
|
for (const [k, v] of Object.entries(params)) {
|
|
235
311
|
if (v !== void 0) url.searchParams.set(k, String(v));
|
|
@@ -257,23 +333,25 @@ async function acnFetch(path, options = {}) {
|
|
|
257
333
|
}
|
|
258
334
|
return res.json();
|
|
259
335
|
}
|
|
260
|
-
function acnGet(path, params) {
|
|
261
|
-
return acnFetch(path, { method: "GET", params });
|
|
336
|
+
function acnGet(path, params, opts) {
|
|
337
|
+
return acnFetch(path, { method: "GET", params, baseUrl: opts?.baseUrl });
|
|
262
338
|
}
|
|
263
|
-
function acnPost(path, body) {
|
|
339
|
+
function acnPost(path, body, opts) {
|
|
264
340
|
return acnFetch(path, {
|
|
265
341
|
method: "POST",
|
|
266
|
-
body: body !== void 0 ? JSON.stringify(body) : void 0
|
|
342
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0,
|
|
343
|
+
baseUrl: opts?.baseUrl
|
|
267
344
|
});
|
|
268
345
|
}
|
|
269
|
-
function acnPatch(path, body) {
|
|
346
|
+
function acnPatch(path, body, opts) {
|
|
270
347
|
return acnFetch(path, {
|
|
271
348
|
method: "PATCH",
|
|
272
|
-
body: body !== void 0 ? JSON.stringify(body) : void 0
|
|
349
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0,
|
|
350
|
+
baseUrl: opts?.baseUrl
|
|
273
351
|
});
|
|
274
352
|
}
|
|
275
|
-
function acnDelete(path) {
|
|
276
|
-
return acnFetch(path, { method: "DELETE" });
|
|
353
|
+
function acnDelete(path, opts) {
|
|
354
|
+
return acnFetch(path, { method: "DELETE", baseUrl: opts?.baseUrl });
|
|
277
355
|
}
|
|
278
356
|
|
|
279
357
|
// src/commands/join.ts
|
|
@@ -281,8 +359,30 @@ function joinCommand() {
|
|
|
281
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(
|
|
282
360
|
"--relay",
|
|
283
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."
|
|
284
368
|
).action(
|
|
285
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);
|
|
286
386
|
const tags = opts.tags.split(",").map((s) => s.trim()).filter(Boolean);
|
|
287
387
|
const body = {
|
|
288
388
|
name: opts.name,
|
|
@@ -295,19 +395,30 @@ function joinCommand() {
|
|
|
295
395
|
...opts.relay ? { delivery: "relay", communication_policy: { mode: "open" } } : {}
|
|
296
396
|
};
|
|
297
397
|
try {
|
|
298
|
-
const res = await acnPost("/agents/join", body
|
|
299
|
-
|
|
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
|
+
});
|
|
300
407
|
const claimLine = res.claim_url ? `
|
|
301
408
|
Claim URL: ${res.claim_url}` : "";
|
|
302
409
|
const verifyLine = res.verification_code ? `
|
|
303
410
|
Verify : ${res.verification_code}` : "";
|
|
411
|
+
const regionLine = region ? `
|
|
412
|
+
Region : ${region}` : "";
|
|
304
413
|
output(res, [
|
|
305
414
|
`Registered successfully!`,
|
|
306
415
|
` Agent ID : ${res.agent_id}`,
|
|
307
416
|
` API Key : ${res.api_key}`,
|
|
417
|
+
` ACN : ${base_url}${regionLine}`,
|
|
308
418
|
` Status : ${res.status}${claimLine}${verifyLine}`,
|
|
309
419
|
``,
|
|
310
|
-
`Credentials saved to ~/.acn/config.json
|
|
420
|
+
`Credentials saved to ~/.acn/config.json`,
|
|
421
|
+
`Do not reuse this api_key against another region \u2014 re-join instead.`
|
|
311
422
|
].join("\n"));
|
|
312
423
|
} catch (err) {
|
|
313
424
|
handleError(err);
|