@rookdaemon/agora 0.1.2 → 0.1.5

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 (50) hide show
  1. package/README.md +265 -1
  2. package/dist/cli.js +481 -36
  3. package/dist/cli.js.map +1 -1
  4. package/dist/config.d.ts +44 -0
  5. package/dist/config.d.ts.map +1 -0
  6. package/dist/config.js +74 -0
  7. package/dist/config.js.map +1 -0
  8. package/dist/index.d.ts +5 -0
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +5 -0
  11. package/dist/index.js.map +1 -1
  12. package/dist/message/envelope.d.ts +1 -1
  13. package/dist/message/envelope.d.ts.map +1 -1
  14. package/dist/message/envelope.js.map +1 -1
  15. package/dist/message/types/paper-discovery.d.ts +28 -0
  16. package/dist/message/types/paper-discovery.d.ts.map +1 -0
  17. package/dist/message/types/paper-discovery.js +2 -0
  18. package/dist/message/types/paper-discovery.js.map +1 -0
  19. package/dist/peer/client.d.ts +50 -0
  20. package/dist/peer/client.d.ts.map +1 -0
  21. package/dist/peer/client.js +138 -0
  22. package/dist/peer/client.js.map +1 -0
  23. package/dist/peer/manager.d.ts +65 -0
  24. package/dist/peer/manager.d.ts.map +1 -0
  25. package/dist/peer/manager.js +153 -0
  26. package/dist/peer/manager.js.map +1 -0
  27. package/dist/peer/server.d.ts +65 -0
  28. package/dist/peer/server.d.ts.map +1 -0
  29. package/dist/peer/server.js +154 -0
  30. package/dist/peer/server.js.map +1 -0
  31. package/dist/relay/client.d.ts +112 -0
  32. package/dist/relay/client.d.ts.map +1 -0
  33. package/dist/relay/client.js +281 -0
  34. package/dist/relay/client.js.map +1 -0
  35. package/dist/relay/server.d.ts +60 -0
  36. package/dist/relay/server.d.ts.map +1 -0
  37. package/dist/relay/server.js +266 -0
  38. package/dist/relay/server.js.map +1 -0
  39. package/dist/relay/types.d.ts +35 -0
  40. package/dist/relay/types.d.ts.map +1 -0
  41. package/dist/relay/types.js +2 -0
  42. package/dist/relay/types.js.map +1 -0
  43. package/dist/transport/peer-config.d.ts +3 -2
  44. package/dist/transport/peer-config.d.ts.map +1 -1
  45. package/dist/transport/peer-config.js.map +1 -1
  46. package/dist/transport/relay.d.ts +23 -0
  47. package/dist/transport/relay.d.ts.map +1 -0
  48. package/dist/transport/relay.js +85 -0
  49. package/dist/transport/relay.js.map +1 -0
  50. package/package.json +1 -38
package/dist/cli.js CHANGED
@@ -5,6 +5,9 @@ import { dirname, resolve } from 'node:path';
5
5
  import { homedir } from 'node:os';
6
6
  import { loadPeerConfig, savePeerConfig, initPeerConfig } from './transport/peer-config.js';
7
7
  import { sendToPeer, decodeInboundEnvelope } from './transport/http.js';
8
+ import { sendViaRelay } from './transport/relay.js';
9
+ import { PeerServer } from './peer/server.js';
10
+ import { RelayServer } from './relay/server.js';
8
11
  /**
9
12
  * Get the config file path from CLI options, environment, or default.
10
13
  */
@@ -108,7 +111,7 @@ function handleWhoami(options) {
108
111
  */
109
112
  function handlePeersAdd(args, options) {
110
113
  if (args.length < 1) {
111
- console.error('Error: Missing peer name. Usage: agora peers add <name> --url <url> --token <token> --pubkey <pubkey>');
114
+ console.error('Error: Missing peer name. Usage: agora peers add <name> --pubkey <pubkey> [--url <url> --token <token>]');
112
115
  process.exit(1);
113
116
  }
114
117
  const name = args[0];
@@ -120,25 +123,42 @@ function handlePeersAdd(args, options) {
120
123
  const url = options.url;
121
124
  const token = options.token;
122
125
  const pubkey = options.pubkey;
123
- if (!url || !token || !pubkey) {
124
- console.error('Error: Missing required options. Usage: agora peers add <name> --url <url> --token <token> --pubkey <pubkey>');
126
+ if (!pubkey) {
127
+ console.error('Error: Missing required --pubkey option.');
125
128
  process.exit(1);
126
129
  }
130
+ // Validate that if one of url/token is provided, both must be
131
+ if ((url && !token) || (!url && token)) {
132
+ console.error('Error: Both --url and --token must be provided together.');
133
+ process.exit(1);
134
+ }
135
+ // Check if we have HTTP transport or relay
127
136
  const config = loadPeerConfig(configPath);
128
- // Add the peer (name is optional but set for clarity)
137
+ const hasHttpConfig = url && token;
138
+ const hasRelay = config.relay;
139
+ if (!hasHttpConfig && !hasRelay) {
140
+ console.error('Error: Either (--url and --token) must be provided, or relay must be configured in config file.');
141
+ process.exit(1);
142
+ }
143
+ // Add the peer
129
144
  config.peers[name] = {
130
- url,
131
- token,
132
145
  publicKey: pubkey,
133
146
  name, // Set name to match the key for consistency
134
147
  };
148
+ if (url && token) {
149
+ config.peers[name].url = url;
150
+ config.peers[name].token = token;
151
+ }
135
152
  savePeerConfig(configPath, config);
136
- output({
153
+ const outputData = {
137
154
  status: 'added',
138
155
  name,
139
- url,
140
156
  publicKey: pubkey
141
- }, options.pretty || false);
157
+ };
158
+ if (url) {
159
+ outputData.url = url;
160
+ }
161
+ output(outputData, options.pretty || false);
142
162
  }
143
163
  /**
144
164
  * Handle the `agora peers list` command.
@@ -230,34 +250,74 @@ async function handleSend(args, options) {
230
250
  messageType = 'publish';
231
251
  messagePayload = { text: args.slice(1).join(' ') };
232
252
  }
233
- // Create transport config
234
- const transportConfig = {
235
- identity: config.identity,
236
- peers: new Map([[peer.publicKey, {
237
- url: peer.url,
238
- token: peer.token,
239
- publicKey: peer.publicKey,
240
- }]]),
241
- };
253
+ // Determine transport method: HTTP or relay
254
+ const hasHttpTransport = peer.url && peer.token;
255
+ const hasRelay = config.relay;
242
256
  // Send the message
243
257
  try {
244
- const result = await sendToPeer(transportConfig, peer.publicKey, messageType, messagePayload);
245
- if (result.ok) {
246
- output({
247
- status: 'sent',
248
- peer: name,
249
- type: messageType,
250
- httpStatus: result.status
251
- }, options.pretty || false);
258
+ if (hasHttpTransport) {
259
+ // Use HTTP transport (existing behavior)
260
+ // Non-null assertion: we know url and token are strings here
261
+ const transportConfig = {
262
+ identity: config.identity,
263
+ peers: new Map([[peer.publicKey, {
264
+ url: peer.url,
265
+ token: peer.token,
266
+ publicKey: peer.publicKey,
267
+ }]]),
268
+ };
269
+ const result = await sendToPeer(transportConfig, peer.publicKey, messageType, messagePayload);
270
+ if (result.ok) {
271
+ output({
272
+ status: 'sent',
273
+ peer: name,
274
+ type: messageType,
275
+ transport: 'http',
276
+ httpStatus: result.status
277
+ }, options.pretty || false);
278
+ }
279
+ else {
280
+ output({
281
+ status: 'failed',
282
+ peer: name,
283
+ type: messageType,
284
+ transport: 'http',
285
+ httpStatus: result.status,
286
+ error: result.error
287
+ }, options.pretty || false);
288
+ process.exit(1);
289
+ }
290
+ }
291
+ else if (hasRelay) {
292
+ // Use relay transport
293
+ // Non-null assertion: we know relay is a string here
294
+ const relayConfig = {
295
+ identity: config.identity,
296
+ relayUrl: config.relay,
297
+ };
298
+ const result = await sendViaRelay(relayConfig, peer.publicKey, messageType, messagePayload);
299
+ if (result.ok) {
300
+ output({
301
+ status: 'sent',
302
+ peer: name,
303
+ type: messageType,
304
+ transport: 'relay'
305
+ }, options.pretty || false);
306
+ }
307
+ else {
308
+ output({
309
+ status: 'failed',
310
+ peer: name,
311
+ type: messageType,
312
+ transport: 'relay',
313
+ error: result.error
314
+ }, options.pretty || false);
315
+ process.exit(1);
316
+ }
252
317
  }
253
318
  else {
254
- output({
255
- status: 'failed',
256
- peer: name,
257
- type: messageType,
258
- httpStatus: result.status,
259
- error: result.error
260
- }, options.pretty || false);
319
+ // Neither HTTP nor relay available
320
+ console.error(`Error: Peer '${name}' unreachable. No HTTP endpoint and no relay configured.`);
261
321
  process.exit(1);
262
322
  }
263
323
  }
@@ -282,7 +342,14 @@ function handleDecode(args, options) {
282
342
  const config = loadPeerConfig(configPath);
283
343
  const peers = new Map();
284
344
  for (const [, val] of Object.entries(config.peers)) {
285
- peers.set(val.publicKey, val);
345
+ // Only add peers with HTTP config to the map for decoding
346
+ if (val.url && val.token) {
347
+ peers.set(val.publicKey, {
348
+ url: val.url,
349
+ token: val.token,
350
+ publicKey: val.publicKey,
351
+ });
352
+ }
286
353
  }
287
354
  const message = args.join(' ');
288
355
  const result = decodeInboundEnvelope(message, peers);
@@ -305,6 +372,359 @@ function handleDecode(args, options) {
305
372
  process.exit(1);
306
373
  }
307
374
  }
375
+ /**
376
+ * Handle the `agora status` command.
377
+ */
378
+ function handleStatus(options) {
379
+ const configPath = getConfigPath(options);
380
+ if (!existsSync(configPath)) {
381
+ console.error('Error: Config file not found. Run `agora init` first.');
382
+ process.exit(1);
383
+ }
384
+ const config = loadPeerConfig(configPath);
385
+ const peerCount = Object.keys(config.peers).length;
386
+ output({
387
+ identity: config.identity.publicKey,
388
+ configPath,
389
+ relay: config.relay || 'not configured',
390
+ peerCount,
391
+ peers: Object.keys(config.peers),
392
+ }, options.pretty || false);
393
+ }
394
+ /**
395
+ * Handle the `agora announce` command.
396
+ * Broadcasts an announce message to all configured peers.
397
+ */
398
+ async function handleAnnounce(options) {
399
+ const configPath = getConfigPath(options);
400
+ if (!existsSync(configPath)) {
401
+ console.error('Error: Config file not found. Run `agora init` first.');
402
+ process.exit(1);
403
+ }
404
+ const config = loadPeerConfig(configPath);
405
+ const peerCount = Object.keys(config.peers).length;
406
+ if (peerCount === 0) {
407
+ console.error('Error: No peers configured. Use `agora peers add` to add peers first.');
408
+ process.exit(1);
409
+ }
410
+ // Create announce payload
411
+ const announcePayload = {
412
+ capabilities: [],
413
+ metadata: {
414
+ name: options.name || 'agora-node',
415
+ version: options.version || '0.1.0',
416
+ },
417
+ };
418
+ // Send announce to all peers
419
+ const results = [];
420
+ for (const [name, peer] of Object.entries(config.peers)) {
421
+ const hasHttpTransport = peer.url && peer.token;
422
+ const hasRelay = config.relay;
423
+ try {
424
+ if (hasHttpTransport) {
425
+ // Use HTTP transport
426
+ const peers = new Map();
427
+ peers.set(peer.publicKey, {
428
+ url: peer.url,
429
+ token: peer.token,
430
+ publicKey: peer.publicKey,
431
+ });
432
+ const transportConfig = {
433
+ identity: config.identity,
434
+ peers,
435
+ };
436
+ const result = await sendToPeer(transportConfig, peer.publicKey, 'announce', announcePayload);
437
+ if (result.ok) {
438
+ results.push({
439
+ peer: name,
440
+ status: 'sent',
441
+ transport: 'http',
442
+ httpStatus: result.status,
443
+ });
444
+ }
445
+ else {
446
+ results.push({
447
+ peer: name,
448
+ status: 'failed',
449
+ transport: 'http',
450
+ httpStatus: result.status,
451
+ error: result.error,
452
+ });
453
+ }
454
+ }
455
+ else if (hasRelay) {
456
+ // Use relay transport
457
+ const relayConfig = {
458
+ identity: config.identity,
459
+ relayUrl: config.relay,
460
+ };
461
+ const result = await sendViaRelay(relayConfig, peer.publicKey, 'announce', announcePayload);
462
+ if (result.ok) {
463
+ results.push({
464
+ peer: name,
465
+ status: 'sent',
466
+ transport: 'relay',
467
+ });
468
+ }
469
+ else {
470
+ results.push({
471
+ peer: name,
472
+ status: 'failed',
473
+ transport: 'relay',
474
+ error: result.error,
475
+ });
476
+ }
477
+ }
478
+ else {
479
+ results.push({
480
+ peer: name,
481
+ status: 'unreachable',
482
+ error: 'No HTTP endpoint and no relay configured',
483
+ });
484
+ }
485
+ }
486
+ catch (e) {
487
+ results.push({
488
+ peer: name,
489
+ status: 'error',
490
+ error: e instanceof Error ? e.message : String(e),
491
+ });
492
+ }
493
+ }
494
+ output({ results }, options.pretty || false);
495
+ }
496
+ /**
497
+ * Handle the `agora diagnose` command.
498
+ * Run diagnostic checks on a peer (ping, workspace, tools).
499
+ */
500
+ async function handleDiagnose(args, options) {
501
+ if (args.length < 1) {
502
+ console.error('Error: Missing peer name. Usage: agora diagnose <name> [--checks <comma-separated-list>]');
503
+ process.exit(1);
504
+ }
505
+ const name = args[0];
506
+ const configPath = getConfigPath(options);
507
+ if (!existsSync(configPath)) {
508
+ console.error('Error: Config file not found. Run `agora init` first.');
509
+ process.exit(1);
510
+ }
511
+ const config = loadPeerConfig(configPath);
512
+ if (!config.peers[name]) {
513
+ console.error(`Error: Peer '${name}' not found.`);
514
+ process.exit(1);
515
+ }
516
+ const peer = config.peers[name];
517
+ if (!peer.url) {
518
+ console.error(`Error: Peer '${name}' has no URL configured. Cannot diagnose.`);
519
+ process.exit(1);
520
+ }
521
+ // Parse checks parameter
522
+ const checksParam = options.checks || 'ping';
523
+ const requestedChecks = checksParam.split(',').map(c => c.trim());
524
+ // Validate check types
525
+ const validChecks = ['ping', 'workspace', 'tools'];
526
+ for (const check of requestedChecks) {
527
+ if (!validChecks.includes(check)) {
528
+ console.error(`Error: Invalid check type '${check}'. Valid checks: ${validChecks.join(', ')}`);
529
+ process.exit(1);
530
+ }
531
+ }
532
+ const result = {
533
+ peer: name,
534
+ status: 'unknown',
535
+ checks: {},
536
+ timestamp: new Date().toISOString(),
537
+ };
538
+ // Run ping check
539
+ if (requestedChecks.includes('ping')) {
540
+ const startTime = Date.now();
541
+ try {
542
+ // Add timeout to prevent hanging on unreachable peers
543
+ const controller = new AbortController();
544
+ const timeout = setTimeout(() => controller.abort(), 10000);
545
+ const response = await fetch(peer.url, {
546
+ method: 'GET',
547
+ headers: peer.token ? { 'Authorization': `Bearer ${peer.token}` } : {},
548
+ signal: controller.signal,
549
+ });
550
+ clearTimeout(timeout);
551
+ const latency = Date.now() - startTime;
552
+ if (response.ok || response.status === 404 || response.status === 405) {
553
+ // 404 or 405 means the endpoint exists but GET isn't supported - that's OK for a ping
554
+ result.checks.ping = { ok: true, latency_ms: latency };
555
+ }
556
+ else {
557
+ result.checks.ping = { ok: false, latency_ms: latency, error: `HTTP ${response.status}` };
558
+ }
559
+ }
560
+ catch (err) {
561
+ const latency = Date.now() - startTime;
562
+ result.checks.ping = {
563
+ ok: false,
564
+ latency_ms: latency,
565
+ error: err instanceof Error ? err.message : String(err)
566
+ };
567
+ }
568
+ }
569
+ // Run workspace check
570
+ if (requestedChecks.includes('workspace')) {
571
+ // This is a placeholder - actual implementation would depend on peer's diagnostic protocol
572
+ result.checks.workspace = {
573
+ ok: false,
574
+ implemented: false,
575
+ error: 'Workspace check requires peer diagnostic protocol support'
576
+ };
577
+ }
578
+ // Run tools check
579
+ if (requestedChecks.includes('tools')) {
580
+ // This is a placeholder - actual implementation would depend on peer's diagnostic protocol
581
+ result.checks.tools = {
582
+ ok: false,
583
+ implemented: false,
584
+ error: 'Tools check requires peer diagnostic protocol support'
585
+ };
586
+ }
587
+ // Determine overall status - only consider implemented checks
588
+ const implementedChecks = Object.values(result.checks).filter(check => check.implemented !== false);
589
+ if (implementedChecks.length === 0) {
590
+ result.status = 'unknown';
591
+ }
592
+ else {
593
+ const allOk = implementedChecks.every(check => check.ok);
594
+ const anyOk = implementedChecks.some(check => check.ok);
595
+ result.status = allOk ? 'healthy' : anyOk ? 'degraded' : 'unhealthy';
596
+ }
597
+ output(result, options.pretty || false);
598
+ }
599
+ /**
600
+ * Handle the `agora serve` command.
601
+ * Starts a persistent WebSocket server for incoming peer connections.
602
+ */
603
+ async function handleServe(options) {
604
+ const configPath = getConfigPath(options);
605
+ if (!existsSync(configPath)) {
606
+ console.error('Error: Config file not found. Run `agora init` first.');
607
+ process.exit(1);
608
+ }
609
+ const config = loadPeerConfig(configPath);
610
+ const port = parseInt(options.port || '9473', 10);
611
+ // Validate port
612
+ if (isNaN(port) || port < 1 || port > 65535) {
613
+ console.error(`Error: Invalid port number '${options.port}'. Port must be between 1 and 65535.`);
614
+ process.exit(1);
615
+ }
616
+ const serverName = options.name || 'agora-server';
617
+ // Create announce payload
618
+ const announcePayload = {
619
+ capabilities: [],
620
+ metadata: {
621
+ name: serverName,
622
+ version: '0.1.0',
623
+ },
624
+ };
625
+ // Create and configure PeerServer
626
+ const server = new PeerServer(config.identity, announcePayload);
627
+ // Setup event listeners
628
+ server.on('peer-connected', (publicKey, peer) => {
629
+ const peerName = peer.metadata?.name || publicKey.substring(0, 16);
630
+ console.log(`[${new Date().toISOString()}] Peer connected: ${peerName} (${publicKey})`);
631
+ });
632
+ server.on('peer-disconnected', (publicKey) => {
633
+ console.log(`[${new Date().toISOString()}] Peer disconnected: ${publicKey}`);
634
+ });
635
+ server.on('message-received', (envelope, fromPublicKey) => {
636
+ console.log(`[${new Date().toISOString()}] Message from ${fromPublicKey}:`);
637
+ console.log(JSON.stringify({
638
+ id: envelope.id,
639
+ type: envelope.type,
640
+ sender: envelope.sender,
641
+ timestamp: envelope.timestamp,
642
+ payload: envelope.payload,
643
+ }, null, 2));
644
+ });
645
+ server.on('error', (error) => {
646
+ console.error(`[${new Date().toISOString()}] Error:`, error.message);
647
+ });
648
+ // Start the server
649
+ try {
650
+ await server.start(port);
651
+ console.log(`[${new Date().toISOString()}] Agora server started`);
652
+ console.log(` Name: ${serverName}`);
653
+ console.log(` Public Key: ${config.identity.publicKey}`);
654
+ console.log(` WebSocket Port: ${port}`);
655
+ console.log(` Listening for peer connections...`);
656
+ console.log('');
657
+ console.log('Press Ctrl+C to stop the server');
658
+ // Keep the process alive
659
+ process.on('SIGINT', async () => {
660
+ console.log(`\n[${new Date().toISOString()}] Shutting down server...`);
661
+ await server.stop();
662
+ console.log('Server stopped');
663
+ process.exit(0);
664
+ });
665
+ process.on('SIGTERM', async () => {
666
+ console.log(`\n[${new Date().toISOString()}] Shutting down server...`);
667
+ await server.stop();
668
+ console.log('Server stopped');
669
+ process.exit(0);
670
+ });
671
+ }
672
+ catch (error) {
673
+ console.error('Failed to start server:', error instanceof Error ? error.message : String(error));
674
+ process.exit(1);
675
+ }
676
+ }
677
+ /**
678
+ * Handle the `agora relay` command.
679
+ * Starts a WebSocket relay server for routing messages between agents.
680
+ */
681
+ async function handleRelay(options) {
682
+ const port = parseInt(options.port || '9474', 10);
683
+ // Validate port
684
+ if (isNaN(port) || port < 1 || port > 65535) {
685
+ console.error(`Error: Invalid port number '${options.port}'. Port must be between 1 and 65535.`);
686
+ process.exit(1);
687
+ }
688
+ // Create and configure RelayServer
689
+ const server = new RelayServer();
690
+ // Setup event listeners
691
+ server.on('agent-registered', (publicKey) => {
692
+ console.log(`[${new Date().toISOString()}] Agent registered: ${publicKey}`);
693
+ });
694
+ server.on('agent-disconnected', (publicKey) => {
695
+ console.log(`[${new Date().toISOString()}] Agent disconnected: ${publicKey}`);
696
+ });
697
+ server.on('message-relayed', (from, to, envelope) => {
698
+ console.log(`[${new Date().toISOString()}] Message relayed: ${from.substring(0, 16)}... → ${to.substring(0, 16)}... (type: ${envelope.type})`);
699
+ });
700
+ server.on('error', (error) => {
701
+ console.error(`[${new Date().toISOString()}] Error:`, error.message);
702
+ });
703
+ // Start the server
704
+ try {
705
+ await server.start(port);
706
+ console.log(`[${new Date().toISOString()}] Agora relay server started`);
707
+ console.log(` WebSocket Port: ${port}`);
708
+ console.log(` Connected agents: 0`);
709
+ console.log(` Listening for agent connections...`);
710
+ console.log('');
711
+ console.log('Press Ctrl+C to stop the relay');
712
+ // Shared shutdown handler
713
+ const shutdown = async () => {
714
+ console.log(`\n[${new Date().toISOString()}] Shutting down relay...`);
715
+ await server.stop();
716
+ console.log('Relay stopped');
717
+ process.exit(0);
718
+ };
719
+ // Keep the process alive
720
+ process.on('SIGINT', shutdown);
721
+ process.on('SIGTERM', shutdown);
722
+ }
723
+ catch (error) {
724
+ console.error('Failed to start relay:', error instanceof Error ? error.message : String(error));
725
+ process.exit(1);
726
+ }
727
+ }
308
728
  /**
309
729
  * Parse CLI arguments and route to appropriate handler.
310
730
  */
@@ -312,7 +732,7 @@ async function main() {
312
732
  const args = process.argv.slice(2);
313
733
  if (args.length === 0) {
314
734
  console.error('Usage: agora <command> [options]');
315
- console.error('Commands: init, whoami, peers, send');
735
+ console.error('Commands: init, whoami, status, peers, announce, send, decode, serve, diagnose, relay');
316
736
  process.exit(1);
317
737
  }
318
738
  // Parse global options
@@ -326,6 +746,10 @@ async function main() {
326
746
  pubkey: { type: 'string' },
327
747
  type: { type: 'string' },
328
748
  payload: { type: 'string' },
749
+ name: { type: 'string' },
750
+ version: { type: 'string' },
751
+ port: { type: 'string' },
752
+ checks: { type: 'string' },
329
753
  },
330
754
  strict: false,
331
755
  allowPositionals: true,
@@ -341,6 +765,10 @@ async function main() {
341
765
  url: typeof parsed.values.url === 'string' ? parsed.values.url : undefined,
342
766
  token: typeof parsed.values.token === 'string' ? parsed.values.token : undefined,
343
767
  pubkey: typeof parsed.values.pubkey === 'string' ? parsed.values.pubkey : undefined,
768
+ name: typeof parsed.values.name === 'string' ? parsed.values.name : undefined,
769
+ version: typeof parsed.values.version === 'string' ? parsed.values.version : undefined,
770
+ port: typeof parsed.values.port === 'string' ? parsed.values.port : undefined,
771
+ checks: typeof parsed.values.checks === 'string' ? parsed.values.checks : undefined,
344
772
  };
345
773
  try {
346
774
  switch (command) {
@@ -350,12 +778,23 @@ async function main() {
350
778
  case 'whoami':
351
779
  handleWhoami(options);
352
780
  break;
781
+ case 'status':
782
+ handleStatus(options);
783
+ break;
784
+ case 'announce':
785
+ await handleAnnounce(options);
786
+ break;
787
+ case 'diagnose':
788
+ await handleDiagnose([subcommand, ...remainingArgs].filter(Boolean), options);
789
+ break;
353
790
  case 'peers':
354
791
  switch (subcommand) {
355
792
  case 'add':
356
793
  handlePeersAdd(remainingArgs, options);
357
794
  break;
358
795
  case 'list':
796
+ case undefined:
797
+ // Allow 'agora peers' to work like 'agora peers list'
359
798
  handlePeersList(options);
360
799
  break;
361
800
  case 'remove':
@@ -372,8 +811,14 @@ async function main() {
372
811
  case 'decode':
373
812
  handleDecode([subcommand, ...remainingArgs].filter(Boolean), options);
374
813
  break;
814
+ case 'serve':
815
+ await handleServe(options);
816
+ break;
817
+ case 'relay':
818
+ await handleRelay(options);
819
+ break;
375
820
  default:
376
- console.error(`Error: Unknown command '${command}'. Use: init, whoami, peers, send, decode`);
821
+ console.error(`Error: Unknown command '${command}'. Use: init, whoami, status, peers, announce, send, decode, serve, diagnose, relay`);
377
822
  process.exit(1);
378
823
  }
379
824
  }