@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.
Files changed (2) hide show
  1. package/dist/index.js +143 -32
  2. 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.0",
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 DEFAULT_BASE_URL = "https://api.acnlabs.dev";
128
- function loadConfig() {
129
- if (!(0, import_fs.existsSync)(CONFIG_FILE)) {
130
- return { base_url: DEFAULT_BASE_URL };
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
- const parsed = JSON.parse(raw);
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 { base_url: DEFAULT_BASE_URL };
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 current = loadConfig();
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
- const clean = { base_url: next.base_url };
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(`Set a config value. Keys: ${VALID_KEYS.join(", ")}`).action((key, value) => {
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
- ` base-url : ${config.base_url}`,
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 url = new URL(`${config.base_url}/api/v1${path}`);
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
- saveConfig({ api_key: res.api_key, agent_id: res.agent_id });
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@acnlabs/acn-cli",
3
- "version": "0.13.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": {