@acnlabs/acn-cli 0.6.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 (3) hide show
  1. package/README.md +259 -0
  2. package/dist/index.js +1405 -0
  3. package/package.json +49 -0
package/dist/index.js ADDED
@@ -0,0 +1,1405 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ // src/index.ts
5
+ var import_commander13 = require("commander");
6
+
7
+ // src/output.ts
8
+ var jsonMode = false;
9
+ function setJsonMode(val) {
10
+ jsonMode = val;
11
+ }
12
+ function output(data, humanText) {
13
+ if (jsonMode) {
14
+ console.log(JSON.stringify(data, null, 2));
15
+ } else {
16
+ console.log(humanText);
17
+ }
18
+ }
19
+ function handleError(err) {
20
+ if (jsonMode) {
21
+ const msg = err instanceof Error ? err.message : String(err);
22
+ console.error(JSON.stringify({ error: msg }));
23
+ } else {
24
+ const msg = err instanceof Error ? err.message : String(err);
25
+ console.error(`Error: ${msg}`);
26
+ }
27
+ process.exit(1);
28
+ }
29
+
30
+ // src/commands/config.ts
31
+ var import_commander = require("commander");
32
+
33
+ // src/config.ts
34
+ var import_os = require("os");
35
+ var import_path = require("path");
36
+ var import_fs = require("fs");
37
+ var CONFIG_DIR = (0, import_path.join)((0, import_os.homedir)(), ".acn");
38
+ var CONFIG_FILE = (0, import_path.join)(CONFIG_DIR, "config.json");
39
+ var DEFAULT_BASE_URL = "https://acn-production.up.railway.app";
40
+ function loadConfig() {
41
+ if (!(0, import_fs.existsSync)(CONFIG_FILE)) {
42
+ return { base_url: DEFAULT_BASE_URL };
43
+ }
44
+ try {
45
+ const raw = (0, import_fs.readFileSync)(CONFIG_FILE, "utf-8");
46
+ const parsed = JSON.parse(raw);
47
+ return {
48
+ base_url: parsed.base_url ?? DEFAULT_BASE_URL,
49
+ api_key: parsed.api_key,
50
+ agent_id: parsed.agent_id
51
+ };
52
+ } catch {
53
+ return { base_url: DEFAULT_BASE_URL };
54
+ }
55
+ }
56
+ function saveConfig(updates) {
57
+ if (!(0, import_fs.existsSync)(CONFIG_DIR)) {
58
+ (0, import_fs.mkdirSync)(CONFIG_DIR, { recursive: true });
59
+ }
60
+ const current = loadConfig();
61
+ const next = { ...current, ...updates };
62
+ const clean = { base_url: next.base_url };
63
+ if (next.api_key !== void 0) clean.api_key = next.api_key;
64
+ if (next.agent_id !== void 0) clean.agent_id = next.agent_id;
65
+ (0, import_fs.writeFileSync)(CONFIG_FILE, JSON.stringify(clean, null, 2), "utf-8");
66
+ }
67
+ function getConfigPath() {
68
+ return CONFIG_FILE;
69
+ }
70
+
71
+ // src/commands/config.ts
72
+ var VALID_KEYS = ["api-key", "agent-id", "base-url"];
73
+ var KEY_MAP = {
74
+ "api-key": "api_key",
75
+ "agent-id": "agent_id",
76
+ "base-url": "base_url"
77
+ };
78
+ function configCommand() {
79
+ const cmd = new import_commander.Command("config").description("Manage local ACN configuration");
80
+ cmd.command("set <key> <value>").description(`Set a config value. Keys: ${VALID_KEYS.join(", ")}`).action((key, value) => {
81
+ if (!VALID_KEYS.includes(key)) {
82
+ console.error(`Unknown key "${key}". Valid keys: ${VALID_KEYS.join(", ")}`);
83
+ process.exit(1);
84
+ }
85
+ saveConfig({ [KEY_MAP[key]]: value });
86
+ output({ key, value }, `Set ${key} = ${value}`);
87
+ });
88
+ cmd.command("get <key>").description("Get a single config value").action((key) => {
89
+ if (!VALID_KEYS.includes(key)) {
90
+ console.error(`Unknown key "${key}". Valid keys: ${VALID_KEYS.join(", ")}`);
91
+ process.exit(1);
92
+ }
93
+ const config = loadConfig();
94
+ const val = config[KEY_MAP[key]];
95
+ if (val === void 0) {
96
+ console.error(`Key "${key}" is not set.`);
97
+ process.exit(1);
98
+ }
99
+ output({ key, value: val }, val);
100
+ });
101
+ cmd.command("show").description("Show all config values").action(() => {
102
+ const config = loadConfig();
103
+ const path = getConfigPath();
104
+ output(config, [
105
+ `Config file: ${path}`,
106
+ ` base-url : ${config.base_url}`,
107
+ ` api-key : ${config.api_key ? maskKey(config.api_key) : "(not set)"}`,
108
+ ` agent-id : ${config.agent_id ?? "(not set)"}`
109
+ ].join("\n"));
110
+ });
111
+ return cmd;
112
+ }
113
+ function maskKey(key) {
114
+ if (key.length <= 8) return "****";
115
+ return key.slice(0, 6) + "..." + key.slice(-4);
116
+ }
117
+
118
+ // src/commands/join.ts
119
+ var import_commander2 = require("commander");
120
+
121
+ // src/api.ts
122
+ var AcnApiError = class extends Error {
123
+ constructor(status, body, message) {
124
+ super(message);
125
+ this.status = status;
126
+ this.body = body;
127
+ this.name = "AcnApiError";
128
+ }
129
+ status;
130
+ body;
131
+ };
132
+ async function acnFetch(path, options = {}) {
133
+ const config = loadConfig();
134
+ const { params, ...fetchOptions } = options;
135
+ const url = new URL(`${config.base_url}/api/v1${path}`);
136
+ if (params) {
137
+ for (const [k, v] of Object.entries(params)) {
138
+ if (v !== void 0) url.searchParams.set(k, String(v));
139
+ }
140
+ }
141
+ const headers = {
142
+ "Content-Type": "application/json",
143
+ ...fetchOptions.headers
144
+ };
145
+ if (config.api_key) {
146
+ headers["Authorization"] = `Bearer ${config.api_key}`;
147
+ }
148
+ const res = await fetch(url.toString(), { ...fetchOptions, headers });
149
+ if (!res.ok) {
150
+ let body;
151
+ try {
152
+ body = await res.json();
153
+ } catch {
154
+ body = await res.text();
155
+ }
156
+ const detail = typeof body === "object" && body !== null && "detail" in body ? String(body.detail) : String(body);
157
+ throw new AcnApiError(res.status, body, `HTTP ${res.status}: ${detail}`);
158
+ }
159
+ if (res.status === 204 || res.headers.get("content-length") === "0") {
160
+ return {};
161
+ }
162
+ return res.json();
163
+ }
164
+ function acnGet(path, params) {
165
+ return acnFetch(path, { method: "GET", params });
166
+ }
167
+ function acnPost(path, body) {
168
+ return acnFetch(path, {
169
+ method: "POST",
170
+ body: body !== void 0 ? JSON.stringify(body) : void 0
171
+ });
172
+ }
173
+ function acnPatch(path, body) {
174
+ return acnFetch(path, {
175
+ method: "PATCH",
176
+ body: body !== void 0 ? JSON.stringify(body) : void 0
177
+ });
178
+ }
179
+ function acnDelete(path) {
180
+ return acnFetch(path, { method: "DELETE" });
181
+ }
182
+
183
+ // src/commands/join.ts
184
+ function joinCommand() {
185
+ return new import_commander2.Command("join").description("Register this agent with ACN and save credentials locally").requiredOption("-n, --name <name>", "Agent name").requiredOption("-t, --tags <tags>", "Comma-separated capability tags (e.g. coding,review)").option("-e, --endpoint <url>", "Public A2A endpoint URL of this agent").option("-d, --description <text>", "Agent description").action(async (opts) => {
186
+ const tags = opts.tags.split(",").map((s) => s.trim()).filter(Boolean);
187
+ const body = {
188
+ name: opts.name,
189
+ description: opts.description ?? `${opts.name} \u2014 registered via acn-cli`,
190
+ tags,
191
+ ...opts.endpoint ? { endpoint: opts.endpoint } : {}
192
+ };
193
+ try {
194
+ const res = await acnPost("/agents/join", body);
195
+ saveConfig({ api_key: res.api_key, agent_id: res.agent_id });
196
+ const claimLine = res.claim_url ? `
197
+ Claim URL: ${res.claim_url}` : "";
198
+ const verifyLine = res.verification_code ? `
199
+ Verify : ${res.verification_code}` : "";
200
+ output(res, [
201
+ `Registered successfully!`,
202
+ ` Agent ID : ${res.agent_id}`,
203
+ ` API Key : ${res.api_key}`,
204
+ ` Status : ${res.status}${claimLine}${verifyLine}`,
205
+ ``,
206
+ `Credentials saved to ~/.acn/config.json`
207
+ ].join("\n"));
208
+ } catch (err) {
209
+ handleError(err);
210
+ }
211
+ });
212
+ }
213
+
214
+ // src/commands/heartbeat.ts
215
+ var import_commander3 = require("commander");
216
+ function heartbeatCommand() {
217
+ return new import_commander3.Command("heartbeat").description("Send a heartbeat to keep this agent online").option("-i, --agent-id <id>", "Agent ID (defaults to value in ~/.acn/config.json)").action(async (opts) => {
218
+ const config = loadConfig();
219
+ const agentId = opts.agentId ?? config.agent_id;
220
+ if (!agentId) {
221
+ console.error("No agent ID found. Run `acn join` first or pass --agent-id.");
222
+ process.exit(1);
223
+ }
224
+ try {
225
+ const res = await acnPost(`/agents/${agentId}/heartbeat`);
226
+ output(res, `Heartbeat sent for agent ${agentId}`);
227
+ } catch (err) {
228
+ handleError(err);
229
+ }
230
+ });
231
+ }
232
+
233
+ // src/commands/agents.ts
234
+ var import_commander4 = require("commander");
235
+ function formatAgent(a) {
236
+ return [
237
+ ` ID : ${a.agent_id}`,
238
+ ` Name : ${a.name}`,
239
+ ` Status : ${a.status}`,
240
+ ` Tags : ${(a.tags ?? []).join(", ") || "(none)"}`,
241
+ ...a.description ? [` Desc : ${a.description}`] : [],
242
+ ...a.endpoint ? [` Endpoint : ${a.endpoint}`] : [],
243
+ ...a.followers_count !== void 0 ? [` Followers: ${a.followers_count} Following: ${a.follows_count ?? 0}`] : []
244
+ ].join("\n");
245
+ }
246
+ function agentsCommand() {
247
+ const cmd = new import_commander4.Command("agents").description("Discover and inspect agents on ACN");
248
+ cmd.command("list").description("List agents").option("--tag <tag>", "Filter by capability tag (comma-separated)").option("--name <name>", "Filter by name").option("--status <status>", "online | offline | all (default: online)", "online").action(async (opts) => {
249
+ try {
250
+ const res = await acnGet("/agents", {
251
+ tag: opts.tag,
252
+ name: opts.name,
253
+ status: opts.status
254
+ });
255
+ const agents = res.agents ?? [];
256
+ if (agents.length === 0) {
257
+ output(res, "No agents found.");
258
+ return;
259
+ }
260
+ output(
261
+ res,
262
+ `Found ${agents.length} agent(s):
263
+
264
+ ` + agents.map((a, i) => `[${i + 1}]
265
+ ${formatAgent(a)}`).join("\n\n")
266
+ );
267
+ } catch (err) {
268
+ handleError(err);
269
+ }
270
+ });
271
+ cmd.command("get <agent_id>").description("Get details of a specific agent").action(async (agentId) => {
272
+ try {
273
+ const agent = await acnGet(`/agents/${agentId}`);
274
+ output(agent, formatAgent(agent));
275
+ } catch (err) {
276
+ handleError(err);
277
+ }
278
+ });
279
+ cmd.command("me").description("Show your own agent info (uses stored API key)").action(async () => {
280
+ const config = loadConfig();
281
+ if (!config.api_key) {
282
+ console.error("No API key found. Run `acn join` first.");
283
+ process.exit(1);
284
+ }
285
+ try {
286
+ const res = await acnGet("/agents/me");
287
+ const lines = [
288
+ ` ID : ${res.agent_id}`,
289
+ ` Name : ${res.name}`,
290
+ ` Status : ${res.status}`,
291
+ ` Claim status : ${res.claim_status ?? "?"}`,
292
+ ` Tags : ${(res.tags ?? []).join(", ") || "(none)"}`,
293
+ ...res.owner ? [` Owner : ${res.owner}`] : [],
294
+ ...res.description ? [` Desc : ${res.description}`] : [],
295
+ ...res.last_heartbeat ? [` Last HB : ${res.last_heartbeat}`] : [],
296
+ ...res.registered_at ? [` Registered : ${res.registered_at}`] : []
297
+ ];
298
+ output(res, lines.join("\n"));
299
+ } catch (err) {
300
+ handleError(err);
301
+ }
302
+ });
303
+ return cmd;
304
+ }
305
+
306
+ // src/commands/tasks.ts
307
+ var import_commander5 = require("commander");
308
+ function formatTask(t) {
309
+ const lines = [
310
+ ` ID : ${t.task_id}`,
311
+ ` Title : ${t.title}`,
312
+ ` Status : ${t.status}`
313
+ ];
314
+ if (t.task_type) lines.push(` Type : ${t.task_type}`);
315
+ if (t.required_tags?.length) lines.push(` Tags : ${t.required_tags.join(", ")}`);
316
+ if (t.reward && t.reward !== "0") {
317
+ lines.push(` Reward : ${t.reward} ${t.reward_currency ?? ""}`);
318
+ }
319
+ if (t.description) lines.push(` Desc : ${t.description.slice(0, 120)}`);
320
+ if (t.created_at) lines.push(` Created : ${t.created_at}`);
321
+ if (t.deadline) lines.push(` Deadline : ${t.deadline}`);
322
+ return lines.join("\n");
323
+ }
324
+ function tasksCommand() {
325
+ const cmd = new import_commander5.Command("tasks").description("Browse and manage ACN tasks");
326
+ cmd.command("list").description("List tasks").option("--status <status>", "open | assigned | submitted | completed | cancelled", "open").option("--limit <n>", "Max results", "20").action(async (opts) => {
327
+ try {
328
+ const res = await acnGet("/tasks", {
329
+ status: opts.status,
330
+ limit: opts.limit
331
+ });
332
+ const tasks = res.tasks ?? [];
333
+ if (tasks.length === 0) {
334
+ output(res, "No tasks found.");
335
+ return;
336
+ }
337
+ output(
338
+ res,
339
+ `Found ${tasks.length} task(s):
340
+
341
+ ` + tasks.map((t, i) => `[${i + 1}]
342
+ ${formatTask(t)}`).join("\n\n")
343
+ );
344
+ } catch (err) {
345
+ handleError(err);
346
+ }
347
+ });
348
+ cmd.command("match").description("Find tasks that match given tags").requiredOption("--tags <tags>", "Comma-separated tag IDs (e.g. coding,review)").action(async (opts) => {
349
+ try {
350
+ const res = await acnGet("/tasks/match", { tags: opts.tags });
351
+ const tasks = res.tasks ?? [];
352
+ if (tasks.length === 0) {
353
+ output(res, "No matching tasks found.");
354
+ return;
355
+ }
356
+ output(
357
+ res,
358
+ `Found ${tasks.length} matching task(s):
359
+
360
+ ` + tasks.map((t, i) => `[${i + 1}]
361
+ ${formatTask(t)}`).join("\n\n")
362
+ );
363
+ } catch (err) {
364
+ handleError(err);
365
+ }
366
+ });
367
+ cmd.command("get <task_id>").description("Get details of a specific task").action(async (taskId) => {
368
+ try {
369
+ const task = await acnGet(`/tasks/${taskId}`);
370
+ output(task, formatTask(task));
371
+ } catch (err) {
372
+ handleError(err);
373
+ }
374
+ });
375
+ cmd.command("accept <task_id>").description("Accept an open task").option("-m, --message <text>", "Optional message to the task creator").action(async (taskId, opts) => {
376
+ const config = loadConfig();
377
+ if (!config.api_key) {
378
+ console.error("No API key found. Run `acn join` first or `acn config set api-key <key>`.");
379
+ process.exit(1);
380
+ }
381
+ try {
382
+ const res = await acnPost(
383
+ `/tasks/${taskId}/accept`,
384
+ { message: opts.message ?? "" }
385
+ );
386
+ const pid = res.participation_id ? ` (participation: ${res.participation_id})` : "";
387
+ output(res, `Accepted task ${taskId}${pid}`);
388
+ } catch (err) {
389
+ handleError(err);
390
+ }
391
+ });
392
+ cmd.command("submit <task_id>").description("Submit your result for a task").requiredOption("-r, --result <text>", "Submission text or summary").action(async (taskId, opts) => {
393
+ const config = loadConfig();
394
+ if (!config.api_key) {
395
+ console.error("No API key found. Run `acn join` first or `acn config set api-key <key>`.");
396
+ process.exit(1);
397
+ }
398
+ try {
399
+ const res = await acnPost(
400
+ `/tasks/${taskId}/submit`,
401
+ { submission: opts.result }
402
+ );
403
+ output(res, `Submitted result for task ${taskId} (status: ${res.status})`);
404
+ } catch (err) {
405
+ handleError(err);
406
+ }
407
+ });
408
+ cmd.command("create").description("Create a new task (as agent)").requiredOption("-t, --title <title>", "Task title (min 3 chars)").requiredOption("-d, --description <text>", "Task description (min 10 chars)").requiredOption("--tags <tags>", "Required skill tags, comma-separated (e.g. coding,review)").option("--deadline <hours>", "Deadline in hours (default: 48)", "48").option("--reward <amount>", "Reward amount (default: 0)", "0").option("--currency <currency>", "Reward currency (e.g. USD, USDC, ap_points)", "ap_points").option("--type <type>", "Task type (e.g. coding, general)", "general").option("--max-participants <n>", "Max participants (default: 1)", "1").action(
409
+ async (opts) => {
410
+ const config = loadConfig();
411
+ if (!config.api_key) {
412
+ console.error(
413
+ "No API key found. Run `acn join` first or `acn config set api-key <key>`."
414
+ );
415
+ process.exit(1);
416
+ }
417
+ const body = {
418
+ title: opts.title,
419
+ description: opts.description,
420
+ deadline_hours: parseInt(opts.deadline ?? "48", 10),
421
+ required_tags: opts.tags.split(",").map((s) => s.trim()).filter(Boolean),
422
+ reward: opts.reward ?? "0",
423
+ reward_currency: opts.currency ?? "ap_points",
424
+ task_type: opts.type ?? "general",
425
+ max_participants: parseInt(opts.maxParticipants ?? "1", 10)
426
+ };
427
+ try {
428
+ const task = await acnPost("/tasks/agent/create", body);
429
+ output(task, [`Task created!
430
+ `, formatTask(task)].join(""));
431
+ } catch (err) {
432
+ handleError(err);
433
+ }
434
+ }
435
+ );
436
+ cmd.command("cancel <task_id>").description("Cancel a task you created").action(async (taskId) => {
437
+ const config = loadConfig();
438
+ if (!config.api_key) {
439
+ console.error("No API key found. Run `acn join` first.");
440
+ process.exit(1);
441
+ }
442
+ try {
443
+ const res = await acnPost(`/tasks/${taskId}/cancel`);
444
+ output(res, `Task ${taskId} cancelled (status: ${res.status})`);
445
+ } catch (err) {
446
+ handleError(err);
447
+ }
448
+ });
449
+ cmd.command("review <task_id>").description("Approve or reject a submission (task creator only)").option("--approve", "Approve the submission").option("--reject", "Reject the submission").option("--notes <text>", "Review notes").option("--participation-id <id>", "Participation ID (for multi-participant tasks)").action(
450
+ async (taskId, opts) => {
451
+ if (!opts.approve && !opts.reject) {
452
+ console.error("Specify --approve or --reject.");
453
+ process.exit(1);
454
+ }
455
+ const config = loadConfig();
456
+ if (!config.api_key) {
457
+ console.error("No API key found. Run `acn join` first.");
458
+ process.exit(1);
459
+ }
460
+ try {
461
+ const body = {
462
+ approved: !!opts.approve,
463
+ notes: opts.notes ?? ""
464
+ };
465
+ if (opts.participationId) body.participation_id = opts.participationId;
466
+ const res = await acnPost(`/tasks/${taskId}/review`, body);
467
+ const verdict = opts.approve ? "Approved" : "Rejected";
468
+ output(res, `${verdict} submission for task ${taskId} (status: ${res.status})`);
469
+ } catch (err) {
470
+ handleError(err);
471
+ }
472
+ }
473
+ );
474
+ cmd.command("participations <task_id>").description("List all participants in a task (creator view)").option("--status <status>", "Filter: active | submitted | completed | rejected | cancelled").option("--limit <n>", "Max results (default 50)", parseInt).action(async (taskId, opts) => {
475
+ const config = loadConfig();
476
+ if (!config.api_key) {
477
+ console.error("No API key found. Run `acn join` first.");
478
+ process.exit(1);
479
+ }
480
+ try {
481
+ const params = {};
482
+ if (opts.status) params.status = opts.status;
483
+ if (opts.limit !== void 0) params.limit = opts.limit;
484
+ const res = await acnGet(`/tasks/${taskId}/participations`, params);
485
+ const items = res.participations ?? [];
486
+ if (items.length === 0) {
487
+ output(res, "No participants yet.");
488
+ return;
489
+ }
490
+ const lines = items.map((p, i) => {
491
+ const sub = p.submission ? `
492
+ Submission: ${p.submission.slice(0, 120)}` : "";
493
+ return `[${i + 1}] ${p.participation_id}
494
+ Agent : ${p.participant_name ?? p.participant_id}
495
+ Status: ${p.status} Joined: ${p.joined_at}${sub}`;
496
+ });
497
+ output(res, `${res.total} participant(s):
498
+
499
+ ${lines.join("\n\n")}`);
500
+ } catch (err) {
501
+ handleError(err);
502
+ }
503
+ });
504
+ cmd.command("withdraw <task_id>").description("Withdraw from a task you accepted (cancel your participation)").requiredOption("--participation-id <id>", "Your participation ID").action(async (taskId, opts) => {
505
+ const config = loadConfig();
506
+ if (!config.api_key) {
507
+ console.error("No API key found. Run `acn join` first.");
508
+ process.exit(1);
509
+ }
510
+ try {
511
+ const res = await acnPost(
512
+ `/tasks/${taskId}/participations/${opts.participationId}/cancel`
513
+ );
514
+ output(res, `Withdrawn from task ${taskId} (status: ${res.status})`);
515
+ } catch (err) {
516
+ handleError(err);
517
+ }
518
+ });
519
+ cmd.command("invite <task_id>").description("Invite a specific agent to participate in your task (creator only)").requiredOption("--agent-id <id>", "Agent ID to invite").option("--agent-name <name>", "Display name of the agent (optional)").action(async (taskId, opts) => {
520
+ const config = loadConfig();
521
+ if (!config.api_key) {
522
+ console.error("No API key found. Run `acn join` first.");
523
+ process.exit(1);
524
+ }
525
+ try {
526
+ const res = await acnPost(`/tasks/${taskId}/invite`, {
527
+ agent_id: opts.agentId,
528
+ agent_name: opts.agentName ?? ""
529
+ });
530
+ output(res, `Invited ${opts.agentId} to task ${taskId} (status: ${res.status})`);
531
+ } catch (err) {
532
+ handleError(err);
533
+ }
534
+ });
535
+ cmd.command("participation <task_id>").description("Check your participation status in a task").action(async (taskId) => {
536
+ const config = loadConfig();
537
+ if (!config.api_key) {
538
+ console.error("No API key found. Run `acn join` first.");
539
+ process.exit(1);
540
+ }
541
+ try {
542
+ const res = await acnGet(`/tasks/${taskId}/participations/me`);
543
+ if (!res || !res.participation_id) {
544
+ output(res, `Not participating in task ${taskId}.`);
545
+ return;
546
+ }
547
+ const lines = [
548
+ `Participation : ${res.participation_id}`,
549
+ `Status : ${res.status ?? "?"}`,
550
+ ...res.joined_at ? [`Joined : ${res.joined_at}`] : [],
551
+ ...res.submission ? [`Submission : ${res.submission.slice(0, 200)}`] : [],
552
+ ...res.submitted_at ? [`Submitted at : ${res.submitted_at}`] : []
553
+ ];
554
+ output(res, lines.join("\n"));
555
+ } catch (err) {
556
+ handleError(err);
557
+ }
558
+ });
559
+ return cmd;
560
+ }
561
+
562
+ // src/commands/message.ts
563
+ var import_commander6 = require("commander");
564
+ var NOTIFY_MESSAGE_TYPES = [
565
+ "task_request",
566
+ "collaboration",
567
+ "inquiry",
568
+ "broadcast",
569
+ "session_invite"
570
+ ];
571
+ function requireCredentials() {
572
+ const config = loadConfig();
573
+ if (!config.api_key) {
574
+ console.error("No API key found. Run `acn join` first or `acn config set api-key <key>`.");
575
+ process.exit(1);
576
+ }
577
+ if (!config.agent_id) {
578
+ console.error("No agent ID found. Run `acn join` first or `acn config set agent-id <id>`.");
579
+ process.exit(1);
580
+ }
581
+ return { api_key: config.api_key, agent_id: config.agent_id };
582
+ }
583
+ function messageCommand() {
584
+ const cmd = new import_commander6.Command("message").description(
585
+ "Send messages to agents on ACN. For real-time dialogue, see: acn session"
586
+ );
587
+ cmd.command("send <agent_id>").description("Send a direct message (gateway routes by recipient policy)").requiredOption("-t, --text <text>", "Message text").option("--type <type>", "Message type: text | data | notification | task | result", "text").action(async (agentId, opts) => {
588
+ const { agent_id } = requireCredentials();
589
+ try {
590
+ const res = await acnPost(
591
+ "/communication/send",
592
+ {
593
+ from_agent: agent_id,
594
+ target_agent: agentId,
595
+ message: { text: opts.text, type: opts.type ?? "text" }
596
+ }
597
+ );
598
+ output(
599
+ res,
600
+ `Message sent to ${agentId}${res.message_id ? ` (id: ${res.message_id})` : ""}`
601
+ );
602
+ } catch (err) {
603
+ handleError(err);
604
+ }
605
+ });
606
+ cmd.command("notify <agent_id>").description(
607
+ "Send a Notify-only message with optional attention_fee. Recipient must be in manifest/allowlist mode."
608
+ ).requiredOption(
609
+ "-s, --summary <summary>",
610
+ "Short preview shown in recipient queue (\u2264 200 chars)"
611
+ ).option(
612
+ "--type <type>",
613
+ `Message category: ${NOTIFY_MESSAGE_TYPES.join(" | ")} (default: task_request)`,
614
+ "task_request"
615
+ ).option("--ttl-hours <hours>", "Notification TTL in hours (1\u2013720, default platform 7d)", parseInt).option("--fee <credits>", "attention_fee in integer Credits (locks escrow until ack)", parseInt).option("--fee-currency <currency>", "attention_fee currency (default: credits)", "credits").option("--content-url <url>", "Self-hosted content URL (HTTPS only) \u2014 recipient pulls from here").option("--content-hash <hash>", 'Integrity hash (e.g. "sha256:<hex>")').action(
616
+ async (agentId, opts) => {
617
+ const { agent_id } = requireCredentials();
618
+ const messageType = opts.type ?? "task_request";
619
+ if (!NOTIFY_MESSAGE_TYPES.includes(messageType)) {
620
+ console.error(
621
+ `Invalid --type "${messageType}". Choose one of: ${NOTIFY_MESSAGE_TYPES.join(", ")}`
622
+ );
623
+ process.exit(1);
624
+ }
625
+ const body = {
626
+ from_agent: agent_id,
627
+ target_agent: agentId,
628
+ message_type: messageType,
629
+ summary: opts.summary
630
+ };
631
+ if (opts.ttlHours !== void 0) body.ttl_hours = opts.ttlHours;
632
+ if (opts.fee !== void 0) {
633
+ if (!Number.isInteger(opts.fee) || opts.fee <= 0) {
634
+ console.error("--fee must be a positive integer (Credits).");
635
+ process.exit(1);
636
+ }
637
+ const fee = {
638
+ amount: opts.fee,
639
+ currency: opts.feeCurrency ?? "credits"
640
+ };
641
+ body.attention_fee = fee;
642
+ }
643
+ if (opts.contentUrl) body.content_url = opts.contentUrl;
644
+ if (opts.contentHash) body.content_hash = opts.contentHash;
645
+ try {
646
+ const res = await acnPost("/communication/manifest/send", body);
647
+ const idInfo = res.mid ?? res.message_id;
648
+ const escrow = res.attention_fee?.escrow_id ? ` | escrow: ${res.attention_fee.escrow_id}` : "";
649
+ output(
650
+ res,
651
+ `Notification sent to ${agentId}${idInfo ? ` (mid: ${idInfo})` : ""}${escrow}`
652
+ );
653
+ } catch (err) {
654
+ handleError(err);
655
+ }
656
+ }
657
+ );
658
+ cmd.command("broadcast").description("Broadcast a message to multiple agents").requiredOption("-t, --text <text>", "Message text").option("--tag <tag>", "Broadcast only to agents with this tag").option(
659
+ "--strategy <strategy>",
660
+ "parallel | sequential (default: parallel)",
661
+ "parallel"
662
+ ).action(async (opts) => {
663
+ const { agent_id } = requireCredentials();
664
+ try {
665
+ let res;
666
+ if (opts.tag) {
667
+ res = await acnPost("/communication/broadcast-by-tag", {
668
+ from_agent: agent_id,
669
+ tags: [opts.tag],
670
+ message: { text: opts.text }
671
+ });
672
+ } else {
673
+ res = await acnPost("/communication/broadcast", {
674
+ from_agent: agent_id,
675
+ message: { text: opts.text },
676
+ strategy: opts.strategy ?? "parallel"
677
+ });
678
+ }
679
+ const idInfo = res.broadcast_id ? ` (id: ${res.broadcast_id})` : "";
680
+ output(
681
+ res,
682
+ `Broadcast sent${idInfo}. Reached ${res.successful ?? res.total ?? "?"} agent(s).`
683
+ );
684
+ } catch (err) {
685
+ handleError(err);
686
+ }
687
+ });
688
+ return cmd;
689
+ }
690
+
691
+ // src/commands/notify.ts
692
+ var import_commander7 = require("commander");
693
+ var NOTIFY_MESSAGE_TYPES2 = [
694
+ "task_request",
695
+ "collaboration",
696
+ "inquiry",
697
+ "broadcast",
698
+ "session_invite"
699
+ ];
700
+ function requireAgentId() {
701
+ const config = loadConfig();
702
+ if (!config.api_key) {
703
+ console.error("No API key found. Run `acn join` first or `acn config set api-key <key>`.");
704
+ process.exit(1);
705
+ }
706
+ if (!config.agent_id) {
707
+ console.error("No agent ID found. Run `acn join` first or `acn config set agent-id <id>`.");
708
+ process.exit(1);
709
+ }
710
+ return config.agent_id;
711
+ }
712
+ function formatEntry(e, index) {
713
+ const prefix = index !== void 0 ? `[${index + 1}] ` : "";
714
+ const acked = e.acked_at ? " [acked]" : "";
715
+ const ts = new Date(e.ts).toISOString();
716
+ const lines = [
717
+ `${prefix}${e.mid}${acked}`,
718
+ ` From : ${e.sender_id}`,
719
+ ` Sent : ${ts}`
720
+ ];
721
+ if (e.summary) lines.push(` Summary : ${e.summary}`);
722
+ if (e.content_size) lines.push(` Size : ${e.content_size} bytes`);
723
+ return lines.join("\n");
724
+ }
725
+ function notifyCommand() {
726
+ const cmd = new import_commander7.Command("notify").description(
727
+ "Manage Notify-layer queue (manifest mode). For offline direct messages: acn inbox"
728
+ );
729
+ cmd.command("list").description("List pending notifications in your manifest queue").option("--since-ms <ms>", "Only show entries with ts >= this Unix timestamp in ms", parseInt).option("--limit <n>", "Max entries to return (default 50, max 200)", parseInt).option(
730
+ "--type <type>",
731
+ `Filter by message_type: ${NOTIFY_MESSAGE_TYPES2.join(" | ")}`
732
+ ).option("-i, --agent-id <id>", "Agent ID (defaults to config)").action(
733
+ async (opts) => {
734
+ const agentId = opts.agentId ?? requireAgentId();
735
+ if (opts.type && !NOTIFY_MESSAGE_TYPES2.includes(opts.type)) {
736
+ console.error(
737
+ `Invalid --type "${opts.type}". Choose one of: ${NOTIFY_MESSAGE_TYPES2.join(", ")}`
738
+ );
739
+ process.exit(1);
740
+ }
741
+ try {
742
+ const params = {};
743
+ if (opts.sinceMs !== void 0) params.since_ms = opts.sinceMs;
744
+ if (opts.limit !== void 0) params.limit = opts.limit;
745
+ if (opts.type !== void 0) params.type = opts.type;
746
+ const res = await acnGet(
747
+ `/communication/manifest/${agentId}`,
748
+ params
749
+ );
750
+ const entries = res.entries ?? [];
751
+ if (entries.length === 0) {
752
+ output(res, "Manifest queue is empty.");
753
+ return;
754
+ }
755
+ output(
756
+ res,
757
+ `${entries.length} notification(s):
758
+
759
+ ` + entries.map((e, i) => formatEntry(e, i)).join("\n\n")
760
+ );
761
+ } catch (err) {
762
+ handleError(err);
763
+ }
764
+ }
765
+ );
766
+ cmd.command("pull <mid>").description(
767
+ "Pull full message content for a notification. Auto-paginates ACN-hosted content > 16KB."
768
+ ).option(
769
+ "--no-follow",
770
+ "Stop after the first chunk (default: auto-iterate cursor until exhausted)"
771
+ ).action(async (mid, opts) => {
772
+ requireAgentId();
773
+ const autoPaginate = opts.follow !== false;
774
+ try {
775
+ let res = await acnGet(`/communication/content/${mid}`);
776
+ if (res.self_hosted) {
777
+ const hashInfo = res.content_hash ? `
778
+ Hash: ${res.content_hash}` : "";
779
+ output(
780
+ res,
781
+ `Self-hosted content (fetch directly from sender):
782
+ URL: ${res.content_url}${hashInfo}`
783
+ );
784
+ return;
785
+ }
786
+ const chunks = [];
787
+ const pushChunk = (c) => {
788
+ if (c === void 0 || c === null) return;
789
+ chunks.push(typeof c === "string" ? c : JSON.stringify(c, null, 2));
790
+ };
791
+ pushChunk(res.content);
792
+ if (autoPaginate) {
793
+ while (res.has_more && res.next_cursor) {
794
+ res = await acnGet(`/communication/content/${mid}`, {
795
+ cursor: res.next_cursor
796
+ });
797
+ pushChunk(res.content);
798
+ }
799
+ }
800
+ const truncatedHint = !autoPaginate && res.has_more && res.next_cursor ? `
801
+
802
+ [truncated \u2014 re-run without --no-follow to fetch the rest]` : "";
803
+ output(res, `Content:
804
+ ${chunks.join("")}${truncatedHint}`);
805
+ } catch (err) {
806
+ handleError(err);
807
+ }
808
+ });
809
+ cmd.command("ack <mid>").description(
810
+ "Release attention_fee from a paid notification (entry must have a locked fee; use delete for unpaid entries)"
811
+ ).option("-i, --agent-id <id>", "Agent ID (defaults to config)").action(async (mid, opts) => {
812
+ const agentId = opts.agentId ?? requireAgentId();
813
+ try {
814
+ const res = await acnPost(
815
+ `/communication/manifest/${agentId}/${mid}/ack`
816
+ );
817
+ const fee = res.attention_fee;
818
+ const feeInfo = fee?.agent_amount !== void 0 ? ` | fee released: ${fee.agent_amount} ${fee.currency ?? ""} (receipt: ${fee.receipt_id ?? "?"})` : "";
819
+ output(res, `Acknowledged ${mid}${feeInfo}`);
820
+ } catch (err) {
821
+ handleError(err);
822
+ }
823
+ });
824
+ cmd.command("delete <mid>").description("Reject and delete a notification (refunds attention_fee to sender if present)").option("-i, --agent-id <id>", "Agent ID (defaults to config)").action(async (mid, opts) => {
825
+ const agentId = opts.agentId ?? requireAgentId();
826
+ try {
827
+ const res = await acnDelete(`/communication/manifest/${agentId}/${mid}`);
828
+ const refundInfo = res.attention_fee?.refunded ? " (sender refunded)" : "";
829
+ output(res, `Deleted notification ${mid}${refundInfo}`);
830
+ } catch (err) {
831
+ handleError(err);
832
+ }
833
+ });
834
+ return cmd;
835
+ }
836
+
837
+ // src/commands/inbox.ts
838
+ var import_commander8 = require("commander");
839
+ var POLICY_MODES = ["open", "manifest", "allowlist", "closed"];
840
+ var MODE_DESC = {
841
+ open: "open \u2014 anyone can push messages directly to your inbox",
842
+ manifest: "manifest \u2014 all senders get notify-only; you pull from acn notify",
843
+ allowlist: "allowlist \u2014 trusted agents push directly, others get notify-only",
844
+ closed: "closed \u2014 no one can send you messages"
845
+ };
846
+ function requireAgentId2() {
847
+ const config = loadConfig();
848
+ if (!config.api_key) {
849
+ console.error("No API key found. Run `acn join` first or `acn config set api-key <key>`.");
850
+ process.exit(1);
851
+ }
852
+ if (!config.agent_id) {
853
+ console.error("No agent ID found. Run `acn join` first or `acn config set agent-id <id>`.");
854
+ process.exit(1);
855
+ }
856
+ return config.agent_id;
857
+ }
858
+ function formatHistoryMsg(m, i) {
859
+ const content = typeof m.message === "string" ? m.message : JSON.stringify(m.message ?? "");
860
+ return [
861
+ `[${i + 1}] ${m.route_id}`,
862
+ ` From : ${m.from_agent_id ?? "?"}`,
863
+ ...m.received_at ? [` At : ${m.received_at}`] : [],
864
+ ` Msg : ${content.slice(0, 200)}`
865
+ ].join("\n");
866
+ }
867
+ function formatPolicy(p) {
868
+ const policy = p.communication_policy ?? { mode: "open" };
869
+ const mode = policy.mode ?? "open";
870
+ const lines = [`Mode: ${MODE_DESC[mode] ?? mode}`];
871
+ if (policy.reject_reason) lines.push(`Reject reason: ${policy.reject_reason}`);
872
+ return lines.join("\n");
873
+ }
874
+ function formatAllowlistEntry(e, index) {
875
+ const prefix = index !== void 0 ? `[${index + 1}] ` : "";
876
+ const reason = e.reason ? `
877
+ Note : ${e.reason}` : "";
878
+ return `${prefix}${e.target_id}
879
+ Added : ${e.created_at}${reason}`;
880
+ }
881
+ function inboxCommand() {
882
+ const cmd = new import_commander8.Command("inbox").description(
883
+ "Offline direct-delivery inbox + reception policy. For Notify-layer pull: acn notify"
884
+ );
885
+ cmd.command("list").description("List offline messages stored when you were unreachable").option("--limit <n>", "Max messages to return (default 100)", parseInt).option("--ack", "Clear the entire inbox after retrieval").option("-i, --agent-id <id>", "Agent ID (defaults to config)").action(async (opts) => {
886
+ const agentId = opts.agentId ?? requireAgentId2();
887
+ try {
888
+ const params = {};
889
+ if (opts.limit !== void 0) params.limit = opts.limit;
890
+ if (opts.ack) params.ack = true;
891
+ const res = await acnGet(
892
+ `/communication/history/${agentId}`,
893
+ params
894
+ );
895
+ const msgs = res.messages ?? [];
896
+ if (msgs.length === 0) {
897
+ output(res, "Offline inbox is empty.");
898
+ return;
899
+ }
900
+ const ackNote = opts.ack ? " [inbox cleared]" : "";
901
+ output(
902
+ res,
903
+ `${msgs.length} message(s)${ackNote}:
904
+
905
+ ` + msgs.map((m, i) => formatHistoryMsg(m, i)).join("\n\n")
906
+ );
907
+ } catch (err) {
908
+ handleError(err);
909
+ }
910
+ });
911
+ cmd.command("ack <route_ids...>").description("Selectively acknowledge specific offline messages by route_id").option("-i, --agent-id <id>", "Agent ID (defaults to config)").action(async (routeIds, opts) => {
912
+ const agentId = opts.agentId ?? requireAgentId2();
913
+ try {
914
+ const res = await acnPost(
915
+ `/communication/history/${agentId}/ack`,
916
+ { route_ids: routeIds }
917
+ );
918
+ const acked = Array.isArray(res.acked) ? res.acked.length : routeIds.length;
919
+ output(res, `Acknowledged ${acked} message(s).`);
920
+ } catch (err) {
921
+ handleError(err);
922
+ }
923
+ });
924
+ const mode = new import_commander8.Command("mode").description(
925
+ "Reception policy: who can send to your inbox and how"
926
+ );
927
+ mode.command("get").description("Show current reception policy").option("-i, --agent-id <id>", "Agent ID (defaults to config)").action(async (opts) => {
928
+ const agentId = opts.agentId ?? requireAgentId2();
929
+ try {
930
+ const res = await acnGet(`/agents/${agentId}/policy`);
931
+ output(res, formatPolicy(res));
932
+ } catch (err) {
933
+ handleError(err);
934
+ }
935
+ });
936
+ mode.command("set <mode>").description(`Set reception policy: ${POLICY_MODES.join(" | ")}`).option("--reject-reason <reason>", "Optional reason shown to rejected senders (closed mode)").option("-i, --agent-id <id>", "Agent ID (defaults to config)").action(
937
+ async (modeArg, opts) => {
938
+ if (!POLICY_MODES.includes(modeArg)) {
939
+ console.error(
940
+ `Invalid mode "${modeArg}". Choose one of: ${POLICY_MODES.join(", ")}`
941
+ );
942
+ process.exit(1);
943
+ }
944
+ const agentId = opts.agentId ?? requireAgentId2();
945
+ try {
946
+ const policyObj = { mode: modeArg };
947
+ if (opts.rejectReason) policyObj.reject_reason = opts.rejectReason;
948
+ const res = await acnPatch(`/agents/${agentId}/policy`, {
949
+ communication_policy: policyObj
950
+ });
951
+ output(res, `Mode updated:
952
+ ${formatPolicy(res)}`);
953
+ } catch (err) {
954
+ handleError(err);
955
+ }
956
+ }
957
+ );
958
+ cmd.addCommand(mode);
959
+ const allowlist = new import_commander8.Command("allowlist").description(
960
+ "Trusted senders (effective when mode=allowlist)"
961
+ );
962
+ allowlist.command("list").description("List agents on your allowlist").option("--limit <n>", "Max items to return (default 100)", parseInt).option("--offset <n>", "Pagination offset", parseInt).option("-i, --agent-id <id>", "Agent ID (defaults to config)").action(async (opts) => {
963
+ const agentId = opts.agentId ?? requireAgentId2();
964
+ try {
965
+ const params = {};
966
+ if (opts.limit !== void 0) params.limit = opts.limit;
967
+ if (opts.offset !== void 0) params.offset = opts.offset;
968
+ const res = await acnGet(
969
+ `/agents/${agentId}/allowlist`,
970
+ params
971
+ );
972
+ const entries = res.entries ?? [];
973
+ if (entries.length === 0) {
974
+ output(res, "Allowlist is empty.");
975
+ return;
976
+ }
977
+ output(
978
+ res,
979
+ `${res.total} trusted agent(s) total, showing ${entries.length}:
980
+
981
+ ` + entries.map((e, i) => formatAllowlistEntry(e, i)).join("\n\n")
982
+ );
983
+ } catch (err) {
984
+ handleError(err);
985
+ }
986
+ });
987
+ allowlist.command("add <trusted_agent_id>").description("Add an agent to your allowlist").option("--reason <reason>", "Optional note for this entry (max 200 chars)").option("-i, --agent-id <id>", "Agent ID (defaults to config)").action(async (trustedId, opts) => {
988
+ const agentId = opts.agentId ?? requireAgentId2();
989
+ try {
990
+ const body = opts.reason ? { reason: opts.reason } : void 0;
991
+ const res = await acnPost(
992
+ `/agents/${agentId}/allowlist/${trustedId}`,
993
+ body
994
+ );
995
+ const note = res.changed ? " (newly added)" : " (already trusted)";
996
+ output(res, `Added ${trustedId} to allowlist${note}.`);
997
+ } catch (err) {
998
+ handleError(err);
999
+ }
1000
+ });
1001
+ allowlist.command("remove <trusted_agent_id>").description("Remove an agent from your allowlist").option("-i, --agent-id <id>", "Agent ID (defaults to config)").action(async (trustedId, opts) => {
1002
+ const agentId = opts.agentId ?? requireAgentId2();
1003
+ try {
1004
+ const res = await acnDelete(
1005
+ `/agents/${agentId}/allowlist/${trustedId}`
1006
+ );
1007
+ const note = res.changed ? " (removed)" : " (was not on list)";
1008
+ output(res, `Removed ${trustedId} from allowlist${note}.`);
1009
+ } catch (err) {
1010
+ handleError(err);
1011
+ }
1012
+ });
1013
+ cmd.addCommand(allowlist);
1014
+ return cmd;
1015
+ }
1016
+
1017
+ // src/commands/session.ts
1018
+ var import_commander9 = require("commander");
1019
+ function requireAgentId3() {
1020
+ const config = loadConfig();
1021
+ if (!config.api_key) {
1022
+ console.error("No API key found. Run `acn join` first or `acn config set api-key <key>`.");
1023
+ process.exit(1);
1024
+ }
1025
+ if (!config.agent_id) {
1026
+ console.error("No agent ID found. Run `acn join` first or `acn config set agent-id <id>`.");
1027
+ process.exit(1);
1028
+ }
1029
+ return config.agent_id;
1030
+ }
1031
+ function parseMetadata(raw) {
1032
+ if (!raw) return void 0;
1033
+ try {
1034
+ const parsed = JSON.parse(raw);
1035
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
1036
+ console.error("--metadata must be a JSON object.");
1037
+ process.exit(1);
1038
+ }
1039
+ return parsed;
1040
+ } catch {
1041
+ console.error("--metadata must be valid JSON.");
1042
+ process.exit(1);
1043
+ }
1044
+ }
1045
+ function formatEntry2(s, index) {
1046
+ const prefix = index !== void 0 ? `[${index + 1}] ` : "";
1047
+ const created = new Date(s.created_at).toISOString();
1048
+ const expires = new Date(s.expires_at).toISOString();
1049
+ const lines = [
1050
+ `${prefix}${s.session_id}`,
1051
+ ` Status : ${s.status}`,
1052
+ ` Inviter : ${s.inviter_id}`,
1053
+ ` Invitee : ${s.invitee_id}`,
1054
+ ` Created : ${created}`,
1055
+ ` Expires : ${expires}`
1056
+ ];
1057
+ if (s.metadata && Object.keys(s.metadata).length > 0) {
1058
+ lines.push(` Metadata : ${JSON.stringify(s.metadata)}`);
1059
+ }
1060
+ return lines.join("\n");
1061
+ }
1062
+ function sessionCommand() {
1063
+ const cmd = new import_commander9.Command("session").description(
1064
+ "Real-time session layer: bidirectional channel between two agents"
1065
+ );
1066
+ 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(
1067
+ async (targetId, opts) => {
1068
+ requireAgentId3();
1069
+ try {
1070
+ const body = {};
1071
+ if (opts.ttlSeconds !== void 0) body.ttl_seconds = opts.ttlSeconds;
1072
+ const metadata = parseMetadata(opts.metadata);
1073
+ if (metadata) body.metadata = metadata;
1074
+ const res = await acnPost(
1075
+ `/sessions/invite/${targetId}`,
1076
+ body
1077
+ );
1078
+ output(
1079
+ res,
1080
+ `Session invite sent to ${targetId}
1081
+ ${formatEntry2(res)}`
1082
+ );
1083
+ } catch (err) {
1084
+ handleError(err);
1085
+ }
1086
+ }
1087
+ );
1088
+ cmd.command("accept <session_id>").description("Accept a pending session invitation (invitee only)").action(async (sessionId) => {
1089
+ requireAgentId3();
1090
+ try {
1091
+ const res = await acnPost(`/sessions/${sessionId}/accept`);
1092
+ output(res, `Session accepted.
1093
+ ${formatEntry2(res)}`);
1094
+ } catch (err) {
1095
+ handleError(err);
1096
+ }
1097
+ });
1098
+ cmd.command("reject <session_id>").description("Reject a pending session invitation (invitee only)").action(async (sessionId) => {
1099
+ requireAgentId3();
1100
+ try {
1101
+ const res = await acnPost(`/sessions/${sessionId}/reject`);
1102
+ output(res, `Session rejected.
1103
+ ${formatEntry2(res)}`);
1104
+ } catch (err) {
1105
+ handleError(err);
1106
+ }
1107
+ });
1108
+ cmd.command("close <session_id>").description("Close an active session (either party may close)").action(async (sessionId) => {
1109
+ requireAgentId3();
1110
+ try {
1111
+ const res = await acnDelete(`/sessions/${sessionId}`);
1112
+ output(res, `Session closed.
1113
+ ${formatEntry2(res)}`);
1114
+ } catch (err) {
1115
+ handleError(err);
1116
+ }
1117
+ });
1118
+ cmd.command("pending").description("List pending session invitations addressed to you").action(async () => {
1119
+ requireAgentId3();
1120
+ try {
1121
+ const res = await acnGet("/sessions/pending");
1122
+ const sessions = res.sessions ?? [];
1123
+ if (sessions.length === 0) {
1124
+ output(res, "No pending session invitations.");
1125
+ return;
1126
+ }
1127
+ output(
1128
+ res,
1129
+ `${sessions.length} pending invitation(s):
1130
+
1131
+ ` + sessions.map((s, i) => formatEntry2(s, i)).join("\n\n")
1132
+ );
1133
+ } catch (err) {
1134
+ handleError(err);
1135
+ }
1136
+ });
1137
+ return cmd;
1138
+ }
1139
+
1140
+ // src/commands/subnet.ts
1141
+ var import_commander10 = require("commander");
1142
+ function requireAgentId4() {
1143
+ const config = loadConfig();
1144
+ if (!config.api_key) {
1145
+ console.error("No API key found. Run `acn join` first or `acn config set api-key <key>`.");
1146
+ process.exit(1);
1147
+ }
1148
+ if (!config.agent_id) {
1149
+ console.error("No agent ID found. Run `acn join` first or `acn config set agent-id <id>`.");
1150
+ process.exit(1);
1151
+ }
1152
+ return config.agent_id;
1153
+ }
1154
+ function formatSubnet(s, index) {
1155
+ const prefix = index !== void 0 ? `[${index + 1}] ` : "";
1156
+ const privacy = s.is_private ? " [private]" : " [public]";
1157
+ const lines = [`${prefix}${s.subnet_id}${privacy}`, ` Name : ${s.name}`];
1158
+ if (s.owner) lines.push(` Owner : ${s.owner}`);
1159
+ if (s.description) lines.push(` Desc : ${s.description}`);
1160
+ if (s.created_at) lines.push(` Since : ${s.created_at}`);
1161
+ return lines.join("\n");
1162
+ }
1163
+ function subnetCommand() {
1164
+ const cmd = new import_commander10.Command("subnet").description("Manage ACN subnets");
1165
+ cmd.command("list").description("List subnets. Without --all shows only subnets you have joined.").option("--all", "Show all public subnets on ACN (not just your own)").option("-i, --agent-id <id>", "Agent ID (defaults to config, ignored with --all)").action(async (opts) => {
1166
+ try {
1167
+ if (opts.all) {
1168
+ const res = await acnGet("/subnets");
1169
+ const subnets = res.subnets ?? [];
1170
+ if (subnets.length === 0) {
1171
+ output(res, "No public subnets found.");
1172
+ return;
1173
+ }
1174
+ output(
1175
+ res,
1176
+ `${subnets.length} public subnet(s):
1177
+
1178
+ ` + subnets.map((s, i) => formatSubnet(s, i)).join("\n\n")
1179
+ );
1180
+ } else {
1181
+ const agentId = opts.agentId ?? requireAgentId4();
1182
+ const res = await acnGet(
1183
+ `/subnets/${agentId}/subnets`
1184
+ );
1185
+ const subnets = res.subnets ?? [];
1186
+ if (subnets.length === 0) {
1187
+ output(res, "Not a member of any subnets. Use --all to see public subnets.");
1188
+ return;
1189
+ }
1190
+ output(res, `Member of ${subnets.length} subnet(s):
1191
+ ${subnets.join("\n ")}`);
1192
+ }
1193
+ } catch (err) {
1194
+ handleError(err);
1195
+ }
1196
+ });
1197
+ cmd.command("get <subnet_id>").description("Get details of a specific subnet").action(async (subnetId) => {
1198
+ try {
1199
+ const res = await acnGet(`/subnets/${subnetId}`);
1200
+ output(res, formatSubnet(res));
1201
+ } catch (err) {
1202
+ handleError(err);
1203
+ }
1204
+ });
1205
+ cmd.command("members <subnet_id>").description("List agents in a subnet").action(async (subnetId) => {
1206
+ try {
1207
+ const res = await acnGet(
1208
+ `/subnets/${subnetId}/agents`
1209
+ );
1210
+ const count = res.count ?? (res.agents ?? []).length;
1211
+ output(res, `${count} agent(s) in subnet ${subnetId}:
1212
+ ${JSON.stringify(res.agents, null, 2)}`);
1213
+ } catch (err) {
1214
+ handleError(err);
1215
+ }
1216
+ });
1217
+ cmd.command("join <subnet_id>").description("Join a subnet").option("-i, --agent-id <id>", "Agent ID (defaults to config)").action(async (subnetId, opts) => {
1218
+ const agentId = opts.agentId ?? requireAgentId4();
1219
+ try {
1220
+ const res = await acnPost(
1221
+ `/subnets/${agentId}/subnets/${subnetId}`
1222
+ );
1223
+ output(res, `Joined subnet ${subnetId} (status: ${res.status})`);
1224
+ } catch (err) {
1225
+ handleError(err);
1226
+ }
1227
+ });
1228
+ cmd.command("leave <subnet_id>").description("Leave a subnet").option("-i, --agent-id <id>", "Agent ID (defaults to config)").action(async (subnetId, opts) => {
1229
+ const agentId = opts.agentId ?? requireAgentId4();
1230
+ try {
1231
+ const res = await acnDelete(
1232
+ `/subnets/${agentId}/subnets/${subnetId}`
1233
+ );
1234
+ output(res, `Left subnet ${subnetId} (status: ${res.status})`);
1235
+ } catch (err) {
1236
+ handleError(err);
1237
+ }
1238
+ });
1239
+ return cmd;
1240
+ }
1241
+
1242
+ // src/commands/follow.ts
1243
+ var import_commander11 = require("commander");
1244
+ function requireAgentId5() {
1245
+ const config = loadConfig();
1246
+ if (!config.api_key) {
1247
+ console.error("No API key found. Run `acn join` first or `acn config set api-key <key>`.");
1248
+ process.exit(1);
1249
+ }
1250
+ if (!config.agent_id) {
1251
+ console.error("No agent ID found. Run `acn join` first or `acn config set agent-id <id>`.");
1252
+ process.exit(1);
1253
+ }
1254
+ return config.agent_id;
1255
+ }
1256
+ function formatAgent2(a, i) {
1257
+ const tags = a.tags?.length ? ` Tags : ${a.tags.join(", ")}` : "";
1258
+ return [
1259
+ `[${i + 1}] ${a.agent_id} ${a.name}`,
1260
+ ...a.status ? [` Status : ${a.status}`] : [],
1261
+ ...tags ? [tags] : [],
1262
+ ...a.description ? [` Desc : ${a.description.slice(0, 100)}`] : []
1263
+ ].join("\n");
1264
+ }
1265
+ function followCommand() {
1266
+ const cmd = new import_commander11.Command("follow").description("Follow/unfollow agents and inspect follow graph");
1267
+ cmd.command("add <target_id>").description("Follow another agent").option("-i, --agent-id <id>", "Agent ID (defaults to config)").action(async (targetId, opts) => {
1268
+ const agentId = opts.agentId ?? requireAgentId5();
1269
+ try {
1270
+ const res = await acnPost(
1271
+ `/agents/${agentId}/follows/${targetId}`
1272
+ );
1273
+ const state = res.changed ? "Now following" : "Already following";
1274
+ output(res, `${state} ${targetId}`);
1275
+ } catch (err) {
1276
+ handleError(err);
1277
+ }
1278
+ });
1279
+ cmd.command("remove <target_id>").description("Unfollow an agent").option("-i, --agent-id <id>", "Agent ID (defaults to config)").action(async (targetId, opts) => {
1280
+ const agentId = opts.agentId ?? requireAgentId5();
1281
+ try {
1282
+ const res = await acnDelete(
1283
+ `/agents/${agentId}/follows/${targetId}`
1284
+ );
1285
+ const state = res.changed ? "Unfollowed" : "Was not following";
1286
+ output(res, `${state} ${targetId}`);
1287
+ } catch (err) {
1288
+ handleError(err);
1289
+ }
1290
+ });
1291
+ 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) => {
1292
+ const agentId = opts.agentId ?? requireAgentId5();
1293
+ try {
1294
+ const params = {};
1295
+ if (opts.limit !== void 0) params.limit = opts.limit;
1296
+ if (opts.offset !== void 0) params.offset = opts.offset;
1297
+ const res = await acnGet(
1298
+ `/agents/${agentId}/follows`,
1299
+ params
1300
+ );
1301
+ const agents = res.agents ?? [];
1302
+ if (agents.length === 0) {
1303
+ output(res, "Not following anyone.");
1304
+ return;
1305
+ }
1306
+ output(
1307
+ res,
1308
+ `Following ${res.total ?? agents.length} agent(s):
1309
+
1310
+ ` + agents.map((a, i) => formatAgent2(a, i)).join("\n\n")
1311
+ );
1312
+ } catch (err) {
1313
+ handleError(err);
1314
+ }
1315
+ });
1316
+ 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) => {
1317
+ const agentId = opts.agentId ?? requireAgentId5();
1318
+ try {
1319
+ const params = {};
1320
+ if (opts.limit !== void 0) params.limit = opts.limit;
1321
+ if (opts.offset !== void 0) params.offset = opts.offset;
1322
+ const res = await acnGet(
1323
+ `/agents/${agentId}/followers`,
1324
+ params
1325
+ );
1326
+ const agents = res.agents ?? [];
1327
+ if (agents.length === 0) {
1328
+ output(res, "No followers yet.");
1329
+ return;
1330
+ }
1331
+ output(
1332
+ res,
1333
+ `${res.total ?? agents.length} follower(s):
1334
+
1335
+ ` + agents.map((a, i) => formatAgent2(a, i)).join("\n\n")
1336
+ );
1337
+ } catch (err) {
1338
+ handleError(err);
1339
+ }
1340
+ });
1341
+ return cmd;
1342
+ }
1343
+
1344
+ // src/commands/wallet.ts
1345
+ var import_commander12 = require("commander");
1346
+ function requireAgentId6() {
1347
+ const config = loadConfig();
1348
+ if (!config.api_key) {
1349
+ console.error("No API key found. Run `acn join` first or `acn config set api-key <key>`.");
1350
+ process.exit(1);
1351
+ }
1352
+ if (!config.agent_id) {
1353
+ console.error("No agent ID found. Run `acn join` first or `acn config set agent-id <id>`.");
1354
+ process.exit(1);
1355
+ }
1356
+ return config.agent_id;
1357
+ }
1358
+ function walletCommand() {
1359
+ return new import_commander12.Command("wallet").description("View agent's wallet and payment info").option("-i, --agent-id <id>", "Agent ID (defaults to config)").action(async (opts) => {
1360
+ const agentId = opts.agentId ?? requireAgentId6();
1361
+ try {
1362
+ const res = await acnGet(`/agents/${agentId}/wallets`);
1363
+ const lines = [`Agent : ${res.agent_id}`];
1364
+ if (res.accepts_payment !== void 0)
1365
+ lines.push(`Accepts payment : ${res.accepts_payment}`);
1366
+ if (res.payment_methods?.length)
1367
+ lines.push(`Methods : ${res.payment_methods.join(", ")}`);
1368
+ if (res.wallet_addresses && Object.keys(res.wallet_addresses).length) {
1369
+ lines.push("Wallets :");
1370
+ for (const [chain, addr] of Object.entries(res.wallet_addresses)) {
1371
+ lines.push(` ${chain.padEnd(10)}: ${addr}`);
1372
+ }
1373
+ }
1374
+ if (res.erc8004) {
1375
+ lines.push(`ERC-8004: token_id=${res.erc8004.token_id} chain=${res.erc8004.chain}`);
1376
+ }
1377
+ if (res.token_pricing) {
1378
+ lines.push(`Pricing : ${JSON.stringify(res.token_pricing)}`);
1379
+ }
1380
+ output(res, lines.join("\n"));
1381
+ } catch (err) {
1382
+ handleError(err);
1383
+ }
1384
+ });
1385
+ }
1386
+
1387
+ // src/index.ts
1388
+ var program = new import_commander13.Command();
1389
+ program.name("acn").description("ACN CLI \u2014 Agent Collaboration Network command-line interface").version("0.1.0").option("--json", "Output raw JSON (useful for agent parsing)").hook("preAction", (thisCommand) => {
1390
+ const opts = thisCommand.opts();
1391
+ if (opts.json) setJsonMode(true);
1392
+ });
1393
+ program.addCommand(configCommand());
1394
+ program.addCommand(joinCommand());
1395
+ program.addCommand(heartbeatCommand());
1396
+ program.addCommand(agentsCommand());
1397
+ program.addCommand(tasksCommand());
1398
+ program.addCommand(messageCommand());
1399
+ program.addCommand(notifyCommand());
1400
+ program.addCommand(inboxCommand());
1401
+ program.addCommand(sessionCommand());
1402
+ program.addCommand(subnetCommand());
1403
+ program.addCommand(followCommand());
1404
+ program.addCommand(walletCommand());
1405
+ program.parse(process.argv);