@adcp/client 3.15.0 → 3.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,736 @@
1
+ /**
2
+ * AdCP CLI Registry Commands
3
+ *
4
+ * Look up, save, and manage brands, properties, agents, and publishers
5
+ * in the AdCP registry.
6
+ */
7
+
8
+ const { readFileSync } = require('fs');
9
+ const { RegistryClient } = require('../dist/lib/registry/index.js');
10
+
11
+ const VALID_COMMANDS = [
12
+ // Lookup
13
+ 'brand',
14
+ 'brands',
15
+ 'brand-json',
16
+ 'enrich-brand',
17
+ 'property',
18
+ 'properties',
19
+ // Save
20
+ 'save-brand',
21
+ 'save-property',
22
+ // List & Search
23
+ 'list-brands',
24
+ 'list-properties',
25
+ 'search',
26
+ 'agents',
27
+ 'publishers',
28
+ 'stats',
29
+ // Validation
30
+ 'validate',
31
+ 'validate-publisher',
32
+ // Discovery
33
+ 'lookup',
34
+ 'discover',
35
+ 'agent-formats',
36
+ 'agent-products',
37
+ // Authorization
38
+ 'check-auth',
39
+ ];
40
+
41
+ /**
42
+ * Parse flags and their values out of an args array.
43
+ * Returns { flags, positional }.
44
+ */
45
+ function parseArgs(args) {
46
+ const flags = {
47
+ auth: undefined,
48
+ registryUrl: undefined,
49
+ json: false,
50
+ search: undefined,
51
+ type: undefined,
52
+ health: false,
53
+ capabilities: false,
54
+ properties: false,
55
+ limit: undefined,
56
+ offset: undefined,
57
+ };
58
+ const positional = [];
59
+
60
+ for (let i = 0; i < args.length; i++) {
61
+ const arg = args[i];
62
+ if (arg === '--auth' && i + 1 < args.length) {
63
+ flags.auth = args[++i];
64
+ } else if (arg === '--registry-url' && i + 1 < args.length) {
65
+ flags.registryUrl = args[++i];
66
+ } else if (arg === '--search' && i + 1 < args.length) {
67
+ flags.search = args[++i];
68
+ } else if (arg === '--type' && i + 1 < args.length) {
69
+ flags.type = args[++i];
70
+ } else if (arg === '--limit' && i + 1 < args.length) {
71
+ flags.limit = parseInt(args[++i], 10);
72
+ } else if (arg === '--offset' && i + 1 < args.length) {
73
+ flags.offset = parseInt(args[++i], 10);
74
+ } else if (arg === '--json') {
75
+ flags.json = true;
76
+ } else if (arg === '--health') {
77
+ flags.health = true;
78
+ } else if (arg === '--capabilities') {
79
+ flags.capabilities = true;
80
+ } else if (arg === '--properties') {
81
+ flags.properties = true;
82
+ } else if (arg.startsWith('--')) {
83
+ // Unknown flag — skip
84
+ } else {
85
+ positional.push(arg);
86
+ }
87
+ }
88
+
89
+ return { flags, positional };
90
+ }
91
+
92
+ /**
93
+ * Parse a JSON payload from a positional arg.
94
+ * Supports inline JSON strings and @file references.
95
+ */
96
+ function parsePayload(arg) {
97
+ if (!arg) return undefined;
98
+ try {
99
+ if (arg.startsWith('@')) {
100
+ const filePath = arg.substring(1);
101
+ return JSON.parse(readFileSync(filePath, 'utf-8'));
102
+ }
103
+ return JSON.parse(arg);
104
+ } catch (err) {
105
+ throw new Error(`Failed to parse payload: ${err.message}`);
106
+ }
107
+ }
108
+
109
+ function prettyPrintBrand(brand, domain) {
110
+ if (!brand) {
111
+ console.log(`No brand found for '${domain}'`);
112
+ return;
113
+ }
114
+
115
+ console.log(`Brand: ${brand.brand_name}`);
116
+ console.log(` Domain: ${brand.canonical_domain}`);
117
+ console.log(` Canonical ID: ${brand.canonical_id}`);
118
+ if (brand.keller_type) {
119
+ console.log(` Keller Type: ${brand.keller_type}`);
120
+ }
121
+ console.log(` Source: ${brand.source}`);
122
+ if (brand.parent_brand) {
123
+ console.log(` Parent: ${brand.parent_brand}`);
124
+ }
125
+ if (brand.house_name || brand.house_domain) {
126
+ console.log(` House: ${brand.house_name || ''}${brand.house_domain ? ' (' + brand.house_domain + ')' : ''}`);
127
+ }
128
+ if (brand.brand_agent_url) {
129
+ console.log(` Agent URL: ${brand.brand_agent_url}`);
130
+ }
131
+ if (brand.brand_manifest) {
132
+ const m = brand.brand_manifest;
133
+ if (m.description) {
134
+ console.log(` Description: ${String(m.description).slice(0, 120)}`);
135
+ }
136
+ if (m.colors) {
137
+ const colorParts = Object.entries(m.colors).map(([k, v]) => `${k}: ${v}`);
138
+ console.log(` Colors: ${colorParts.join(', ')}`);
139
+ }
140
+ if (m.logos) {
141
+ console.log(` Logos: ${m.logos.length}`);
142
+ }
143
+ }
144
+ }
145
+
146
+ function prettyPrintProperty(property, domain) {
147
+ if (!property) {
148
+ console.log(`No property found for '${domain}'`);
149
+ return;
150
+ }
151
+
152
+ console.log(`Property: ${property.publisher_domain}`);
153
+ console.log(` Verified: ${property.verified ? 'Yes' : 'No'}`);
154
+ console.log(` Source: ${property.source}`);
155
+
156
+ if (property.authorized_agents && property.authorized_agents.length > 0) {
157
+ console.log(` Authorized Agents: ${property.authorized_agents.length}`);
158
+ for (const agent of property.authorized_agents) {
159
+ console.log(` - ${agent.url}`);
160
+ }
161
+ } else {
162
+ console.log(` Authorized Agents: 0`);
163
+ }
164
+
165
+ if (property.properties && property.properties.length > 0) {
166
+ console.log(` Properties: ${property.properties.length}`);
167
+ for (const prop of property.properties.slice(0, 10)) {
168
+ const ids = prop.identifiers.map(id => id.value).join(', ');
169
+ const propType = prop.property_type || prop.type || '';
170
+ console.log(` - ${prop.name} (${propType}) [${ids}]`);
171
+ }
172
+ if (property.properties.length > 10) {
173
+ console.log(` ... and ${property.properties.length - 10} more`);
174
+ }
175
+ } else {
176
+ console.log(` Properties: 0`);
177
+ }
178
+ }
179
+
180
+ function prettyPrintSaveResult(result) {
181
+ console.log(`Saved successfully`);
182
+ if (result.message) console.log(` Message: ${result.message}`);
183
+ if (result.domain) console.log(` Domain: ${result.domain}`);
184
+ if (result.id) console.log(` ID: ${result.id}`);
185
+ if (result.revision_number != null) console.log(` Revision: ${result.revision_number}`);
186
+ }
187
+
188
+ function prettyPrintAgent(agent) {
189
+ const name = agent.name || agent.agent_url || 'Unknown';
190
+ const url = agent.agent_url || '';
191
+ const type = agent.type || '';
192
+ console.log(` ${name}${type ? ' (' + type + ')' : ''}`);
193
+ if (url) console.log(` URL: ${url}`);
194
+ if (agent.health) {
195
+ console.log(` Health: ${agent.health.status || 'unknown'}`);
196
+ }
197
+ }
198
+
199
+ function printRegistryUsage() {
200
+ console.log(`AdCP Registry - Brand, Property & Agent Management
201
+
202
+ USAGE:
203
+ adcp registry <command> <args> [options]
204
+
205
+ LOOKUP COMMANDS:
206
+ brand <domain> Look up a brand by domain
207
+ brands <domain> [domain...] Bulk brand lookup (max 100)
208
+ brand-json <domain> Get raw brand.json data for a domain
209
+ enrich-brand <domain> Enrich brand data via Brandfetch
210
+ property <domain> Look up a property by domain
211
+ properties <domain> [d...] Bulk property lookup (max 100)
212
+
213
+ SAVE COMMANDS (requires --auth):
214
+ save-brand <domain> <name> [manifest-json]
215
+ Save or update a community brand
216
+ save-brand <domain> <name> @manifest.json
217
+ Save brand with manifest from file
218
+ save-property <domain> <agent-url> [payload-json]
219
+ Save or update a hosted property
220
+ save-property <domain> <agent-url> @property.json
221
+ Save property with full payload from file
222
+
223
+ LIST & SEARCH:
224
+ list-brands [--search term] List/search brands in the registry
225
+ list-properties [--search t] List/search properties in the registry
226
+ search <query> Search brands, publishers, and properties
227
+ agents [--type sales] List registered agents
228
+ publishers List publishers
229
+ stats Registry statistics
230
+
231
+ VALIDATION:
232
+ validate <domain> Validate domain's adagents.json
233
+ validate-publisher <domain> Validate publisher configuration
234
+
235
+ DISCOVERY:
236
+ lookup <domain> Look up authorized agents for domain
237
+ discover <agent-url> Probe a live agent endpoint
238
+ agent-formats <agent-url> List agent's creative formats
239
+ agent-products <agent-url> List agent's products
240
+
241
+ AUTHORIZATION:
242
+ check-auth <agent-url> <type> <value>
243
+ Check if agent is authorized for property
244
+
245
+ OPTIONS:
246
+ --auth TOKEN API key for authenticated access (required for save)
247
+ --registry-url URL Custom registry URL (default: https://adcontextprotocol.org)
248
+ --json Output raw JSON
249
+ --search TERM Search filter for list commands
250
+ --type TYPE Agent type filter (creative, signals, sales, governance, si)
251
+ --health Include agent health data
252
+ --capabilities Include agent capabilities data
253
+ --properties Include agent property data
254
+ --limit N Limit number of results
255
+ --offset N Offset for pagination
256
+
257
+ ENVIRONMENT VARIABLES:
258
+ ADCP_REGISTRY_API_KEY Default API key for registry operations
259
+
260
+ EXAMPLES:
261
+ # Lookups
262
+ adcp registry brand nike.com
263
+ adcp registry brands nike.com adidas.com --json
264
+ adcp registry brand-json nike.com
265
+ adcp registry enrich-brand nike.com --json
266
+ adcp registry property nytimes.com
267
+
268
+ # List & Search
269
+ adcp registry list-brands --search nike
270
+ adcp registry search nike --json
271
+ adcp registry agents --type sales --health
272
+ adcp registry publishers
273
+ adcp registry stats
274
+
275
+ # Validation
276
+ adcp registry validate nytimes.com
277
+ adcp registry validate-publisher nytimes.com
278
+
279
+ # Discovery
280
+ adcp registry lookup nytimes.com
281
+ adcp registry discover https://test-agent.adcontextprotocol.org
282
+ adcp registry agent-formats https://test-agent.adcontextprotocol.org
283
+ adcp registry agent-products https://test-agent.adcontextprotocol.org
284
+
285
+ # Authorization
286
+ adcp registry check-auth https://agent.example.com domain nytimes.com
287
+
288
+ # Save a brand
289
+ adcp registry save-brand acme.com "Acme Corp" --auth sk_your_key
290
+ adcp registry save-brand acme.com "Acme Corp" '{"colors":{"primary":"#FF0000"}}' --auth sk_your_key
291
+
292
+ # Save a property
293
+ adcp registry save-property example.com https://agent.example.com --auth sk_your_key`);
294
+ }
295
+
296
+ /**
297
+ * Handle the 'registry' subcommand.
298
+ * @param {string[]} args - Arguments after 'registry'
299
+ * @returns {Promise<number>} Exit code (0 = success, 1 = error, 2 = usage error)
300
+ */
301
+ async function handleRegistryCommand(args) {
302
+ if (args.length === 0 || args.includes('--help') || args.includes('-h')) {
303
+ printRegistryUsage();
304
+ return 2;
305
+ }
306
+
307
+ const { flags, positional } = parseArgs(args);
308
+ const subcommand = positional[0];
309
+
310
+ if (!VALID_COMMANDS.includes(subcommand)) {
311
+ console.error(`Unknown registry command: '${subcommand}'\n`);
312
+ printRegistryUsage();
313
+ return 2;
314
+ }
315
+
316
+ const apiKey = flags.auth || process.env.ADCP_REGISTRY_API_KEY;
317
+ const client = new RegistryClient({
318
+ ...(flags.registryUrl && { baseUrl: flags.registryUrl }),
319
+ ...(apiKey && { apiKey }),
320
+ });
321
+
322
+ try {
323
+ switch (subcommand) {
324
+ case 'brand': {
325
+ const domain = positional[1];
326
+ if (!domain) {
327
+ console.error('Error: domain is required\n');
328
+ return 2;
329
+ }
330
+ const result = await client.lookupBrand(domain);
331
+ if (flags.json) {
332
+ console.log(JSON.stringify(result, null, 2));
333
+ } else {
334
+ prettyPrintBrand(result, domain);
335
+ }
336
+ break;
337
+ }
338
+ case 'brands': {
339
+ const domains = positional.slice(1);
340
+ if (domains.length === 0) {
341
+ console.error('Error: at least one domain is required\n');
342
+ return 2;
343
+ }
344
+ const results = await client.lookupBrands(domains);
345
+ if (flags.json) {
346
+ console.log(JSON.stringify(results, null, 2));
347
+ } else {
348
+ const entries = Object.entries(results);
349
+ for (let i = 0; i < entries.length; i++) {
350
+ const [domain, brand] = entries[i];
351
+ prettyPrintBrand(brand, domain);
352
+ if (i < entries.length - 1) console.log('');
353
+ }
354
+ }
355
+ break;
356
+ }
357
+ case 'brand-json': {
358
+ const domain = positional[1];
359
+ if (!domain) {
360
+ console.error('Error: domain is required\n');
361
+ return 2;
362
+ }
363
+ const result = await client.getBrandJson(domain);
364
+ if (flags.json) {
365
+ console.log(JSON.stringify(result, null, 2));
366
+ } else if (!result) {
367
+ console.log(`No brand.json found for '${domain}'`);
368
+ } else {
369
+ console.log(`Brand JSON: ${domain}`);
370
+ for (const [key, value] of Object.entries(result)) {
371
+ console.log(` ${key}: ${JSON.stringify(value)}`);
372
+ }
373
+ }
374
+ break;
375
+ }
376
+ case 'enrich-brand': {
377
+ const domain = positional[1];
378
+ if (!domain) {
379
+ console.error('Error: domain is required\n');
380
+ return 2;
381
+ }
382
+ const result = await client.enrichBrand(domain);
383
+ if (flags.json) {
384
+ console.log(JSON.stringify(result, null, 2));
385
+ } else {
386
+ console.log(`Enriched brand: ${domain}`);
387
+ for (const [key, value] of Object.entries(result)) {
388
+ console.log(` ${key}: ${JSON.stringify(value)}`);
389
+ }
390
+ }
391
+ break;
392
+ }
393
+ case 'property': {
394
+ const domain = positional[1];
395
+ if (!domain) {
396
+ console.error('Error: domain is required\n');
397
+ return 2;
398
+ }
399
+ const result = await client.lookupProperty(domain);
400
+ if (flags.json) {
401
+ console.log(JSON.stringify(result, null, 2));
402
+ } else {
403
+ prettyPrintProperty(result, domain);
404
+ }
405
+ break;
406
+ }
407
+ case 'properties': {
408
+ const domains = positional.slice(1);
409
+ if (domains.length === 0) {
410
+ console.error('Error: at least one domain is required\n');
411
+ return 2;
412
+ }
413
+ const results = await client.lookupProperties(domains);
414
+ if (flags.json) {
415
+ console.log(JSON.stringify(results, null, 2));
416
+ } else {
417
+ const entries = Object.entries(results);
418
+ for (let i = 0; i < entries.length; i++) {
419
+ const [domain, prop] = entries[i];
420
+ prettyPrintProperty(prop, domain);
421
+ if (i < entries.length - 1) console.log('');
422
+ }
423
+ }
424
+ break;
425
+ }
426
+ case 'save-brand': {
427
+ const domain = positional[1];
428
+ const brandName = positional[2];
429
+ if (!domain || !brandName) {
430
+ console.error('Error: domain and brand name are required\n');
431
+ console.error('Usage: adcp registry save-brand <domain> <brand-name> [manifest-json]\n');
432
+ return 2;
433
+ }
434
+ const payload = { domain, brand_name: brandName };
435
+ const manifestArg = positional[3];
436
+ if (manifestArg) {
437
+ payload.brand_manifest = parsePayload(manifestArg);
438
+ }
439
+ const result = await client.saveBrand(payload);
440
+ if (flags.json) {
441
+ console.log(JSON.stringify(result, null, 2));
442
+ } else {
443
+ prettyPrintSaveResult(result);
444
+ }
445
+ break;
446
+ }
447
+ case 'save-property': {
448
+ const domain = positional[1];
449
+ const agentUrl = positional[2];
450
+ if (!domain || !agentUrl) {
451
+ console.error('Error: domain and agent URL are required\n');
452
+ console.error('Usage: adcp registry save-property <domain> <agent-url> [payload-json]\n');
453
+ return 2;
454
+ }
455
+ const extraArg = positional[3];
456
+ let payload;
457
+ if (extraArg) {
458
+ const extra = parsePayload(extraArg);
459
+ payload = {
460
+ publisher_domain: domain,
461
+ authorized_agents: [{ url: agentUrl }],
462
+ ...extra,
463
+ };
464
+ } else {
465
+ payload = {
466
+ publisher_domain: domain,
467
+ authorized_agents: [{ url: agentUrl }],
468
+ };
469
+ }
470
+ const result = await client.saveProperty(payload);
471
+ if (flags.json) {
472
+ console.log(JSON.stringify(result, null, 2));
473
+ } else {
474
+ prettyPrintSaveResult(result);
475
+ }
476
+ break;
477
+ }
478
+
479
+ // ====== List & Search ======
480
+
481
+ case 'list-brands': {
482
+ const options = {};
483
+ if (flags.search) options.search = flags.search;
484
+ if (flags.limit != null) options.limit = flags.limit;
485
+ if (flags.offset != null) options.offset = flags.offset;
486
+ const result = await client.listBrands(Object.keys(options).length ? options : undefined);
487
+ if (flags.json) {
488
+ console.log(JSON.stringify(result, null, 2));
489
+ } else {
490
+ const brands = result.brands || [];
491
+ console.log(`Brands: ${brands.length} results`);
492
+ for (const b of brands) {
493
+ console.log(` ${b.brand_name || b.canonical_domain || b.domain} (${b.canonical_domain || b.domain})`);
494
+ }
495
+ }
496
+ break;
497
+ }
498
+ case 'list-properties': {
499
+ const options = {};
500
+ if (flags.search) options.search = flags.search;
501
+ if (flags.limit != null) options.limit = flags.limit;
502
+ if (flags.offset != null) options.offset = flags.offset;
503
+ const result = await client.listProperties(Object.keys(options).length ? options : undefined);
504
+ if (flags.json) {
505
+ console.log(JSON.stringify(result, null, 2));
506
+ } else {
507
+ const props = result.properties || [];
508
+ console.log(`Properties: ${props.length} results`);
509
+ for (const p of props) {
510
+ console.log(` ${p.publisher_domain || p.domain} (${p.source || 'unknown'})`);
511
+ }
512
+ }
513
+ break;
514
+ }
515
+ case 'search': {
516
+ const query = positional[1];
517
+ if (!query) {
518
+ console.error('Error: query is required\n');
519
+ return 2;
520
+ }
521
+ const result = await client.search(query);
522
+ if (flags.json) {
523
+ console.log(JSON.stringify(result, null, 2));
524
+ } else {
525
+ const brandCount = (result.brands || []).length;
526
+ const pubCount = (result.publishers || []).length;
527
+ const propCount = (result.properties || []).length;
528
+ console.log(`Search results for '${query}':`);
529
+ console.log(` Brands: ${brandCount}`);
530
+ console.log(` Publishers: ${pubCount}`);
531
+ console.log(` Properties: ${propCount}`);
532
+ }
533
+ break;
534
+ }
535
+ case 'agents': {
536
+ const options = {};
537
+ if (flags.type) options.type = flags.type;
538
+ if (flags.health) options.health = true;
539
+ if (flags.capabilities) options.capabilities = true;
540
+ if (flags.properties) options.properties = true;
541
+ const result = await client.listAgents(Object.keys(options).length ? options : undefined);
542
+ if (flags.json) {
543
+ console.log(JSON.stringify(result, null, 2));
544
+ } else {
545
+ const agents = result.agents || [];
546
+ console.log(`Agents: ${result.count || agents.length} registered`);
547
+ for (const agent of agents) {
548
+ prettyPrintAgent(agent);
549
+ }
550
+ }
551
+ break;
552
+ }
553
+ case 'publishers': {
554
+ const result = await client.listPublishers();
555
+ if (flags.json) {
556
+ console.log(JSON.stringify(result, null, 2));
557
+ } else {
558
+ const pubs = result.publishers || [];
559
+ console.log(`Publishers: ${result.count || pubs.length} registered`);
560
+ for (const pub of pubs) {
561
+ console.log(` ${pub.domain || pub.publisher_domain || 'unknown'}`);
562
+ }
563
+ }
564
+ break;
565
+ }
566
+ case 'stats': {
567
+ const result = await client.getRegistryStats();
568
+ if (flags.json) {
569
+ console.log(JSON.stringify(result, null, 2));
570
+ } else {
571
+ console.log('Registry Statistics:');
572
+ for (const [key, value] of Object.entries(result)) {
573
+ console.log(` ${key}: ${JSON.stringify(value)}`);
574
+ }
575
+ }
576
+ break;
577
+ }
578
+
579
+ // ====== Validation ======
580
+
581
+ case 'validate': {
582
+ const domain = positional[1];
583
+ if (!domain) {
584
+ console.error('Error: domain is required\n');
585
+ return 2;
586
+ }
587
+ const result = await client.validateAdagents(domain);
588
+ if (flags.json) {
589
+ console.log(JSON.stringify(result, null, 2));
590
+ } else {
591
+ const valid = result.valid !== false;
592
+ console.log(`Validation: ${valid ? 'PASS' : 'FAIL'}`);
593
+ console.log(` Domain: ${domain}`);
594
+ if (result.errors && result.errors.length > 0) {
595
+ console.log(` Errors: ${result.errors.length}`);
596
+ for (const err of result.errors) {
597
+ console.log(` - ${typeof err === 'string' ? err : err.message || JSON.stringify(err)}`);
598
+ }
599
+ }
600
+ if (result.warnings && result.warnings.length > 0) {
601
+ console.log(` Warnings: ${result.warnings.length}`);
602
+ for (const w of result.warnings) {
603
+ console.log(` - ${typeof w === 'string' ? w : w.message || JSON.stringify(w)}`);
604
+ }
605
+ }
606
+ }
607
+ break;
608
+ }
609
+ case 'validate-publisher': {
610
+ const domain = positional[1];
611
+ if (!domain) {
612
+ console.error('Error: domain is required\n');
613
+ return 2;
614
+ }
615
+ const result = await client.validatePublisher(domain);
616
+ if (flags.json) {
617
+ console.log(JSON.stringify(result, null, 2));
618
+ } else {
619
+ console.log(`Publisher validation: ${domain}`);
620
+ for (const [key, value] of Object.entries(result)) {
621
+ console.log(` ${key}: ${JSON.stringify(value)}`);
622
+ }
623
+ }
624
+ break;
625
+ }
626
+
627
+ // ====== Discovery ======
628
+
629
+ case 'lookup': {
630
+ const domain = positional[1];
631
+ if (!domain) {
632
+ console.error('Error: domain is required\n');
633
+ return 2;
634
+ }
635
+ const result = await client.lookupDomain(domain);
636
+ if (flags.json) {
637
+ console.log(JSON.stringify(result, null, 2));
638
+ } else {
639
+ console.log(`Domain lookup: ${domain}`);
640
+ const agents = result.authorized_agents || [];
641
+ console.log(` Authorized agents: ${agents.length}`);
642
+ for (const agent of agents) {
643
+ console.log(` - ${agent.url || agent.agent_url || JSON.stringify(agent)}`);
644
+ }
645
+ }
646
+ break;
647
+ }
648
+ case 'discover': {
649
+ const url = positional[1];
650
+ if (!url) {
651
+ console.error('Error: agent URL is required\n');
652
+ return 2;
653
+ }
654
+ const result = await client.discoverAgent(url);
655
+ if (flags.json) {
656
+ console.log(JSON.stringify(result, null, 2));
657
+ } else {
658
+ console.log(`Agent discovery: ${url}`);
659
+ for (const [key, value] of Object.entries(result)) {
660
+ console.log(` ${key}: ${JSON.stringify(value)}`);
661
+ }
662
+ }
663
+ break;
664
+ }
665
+ case 'agent-formats': {
666
+ const url = positional[1];
667
+ if (!url) {
668
+ console.error('Error: agent URL is required\n');
669
+ return 2;
670
+ }
671
+ const result = await client.getAgentFormats(url);
672
+ if (flags.json) {
673
+ console.log(JSON.stringify(result, null, 2));
674
+ } else {
675
+ console.log(`Agent formats: ${url}`);
676
+ const formats = result.formats || [];
677
+ console.log(` Formats: ${formats.length}`);
678
+ for (const f of formats) {
679
+ console.log(` - ${f.name || f.format_id || JSON.stringify(f)}`);
680
+ }
681
+ }
682
+ break;
683
+ }
684
+ case 'agent-products': {
685
+ const url = positional[1];
686
+ if (!url) {
687
+ console.error('Error: agent URL is required\n');
688
+ return 2;
689
+ }
690
+ const result = await client.getAgentProducts(url);
691
+ if (flags.json) {
692
+ console.log(JSON.stringify(result, null, 2));
693
+ } else {
694
+ console.log(`Agent products: ${url}`);
695
+ const products = result.products || [];
696
+ console.log(` Products: ${products.length}`);
697
+ for (const p of products) {
698
+ console.log(` - ${p.name || p.product_id || JSON.stringify(p)}`);
699
+ }
700
+ }
701
+ break;
702
+ }
703
+
704
+ // ====== Authorization ======
705
+
706
+ case 'check-auth': {
707
+ const agentUrl = positional[1];
708
+ const identifierType = positional[2];
709
+ const identifierValue = positional[3];
710
+ if (!agentUrl || !identifierType || !identifierValue) {
711
+ console.error('Error: agent URL, identifier type, and identifier value are required\n');
712
+ console.error('Usage: adcp registry check-auth <agent-url> <type> <value>\n');
713
+ return 2;
714
+ }
715
+ const result = await client.validatePropertyAuthorization(agentUrl, identifierType, identifierValue);
716
+ if (flags.json) {
717
+ console.log(JSON.stringify(result, null, 2));
718
+ } else {
719
+ console.log(`Authorization check:`);
720
+ console.log(` Agent: ${result.agent_url}`);
721
+ console.log(` Type: ${result.identifier_type}`);
722
+ console.log(` Value: ${result.identifier_value}`);
723
+ console.log(` Authorized: ${result.authorized ? 'Yes' : 'No'}`);
724
+ console.log(` Checked at: ${result.checked_at}`);
725
+ }
726
+ break;
727
+ }
728
+ }
729
+ return 0;
730
+ } catch (err) {
731
+ console.error(`Error: ${err.message}`);
732
+ return 1;
733
+ }
734
+ }
735
+
736
+ module.exports = { handleRegistryCommand, printRegistryUsage };