@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.
- package/README.md +265 -1
- package/dist/cli.js +481 -36
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +44 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +74 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/message/envelope.d.ts +1 -1
- package/dist/message/envelope.d.ts.map +1 -1
- package/dist/message/envelope.js.map +1 -1
- package/dist/message/types/paper-discovery.d.ts +28 -0
- package/dist/message/types/paper-discovery.d.ts.map +1 -0
- package/dist/message/types/paper-discovery.js +2 -0
- package/dist/message/types/paper-discovery.js.map +1 -0
- package/dist/peer/client.d.ts +50 -0
- package/dist/peer/client.d.ts.map +1 -0
- package/dist/peer/client.js +138 -0
- package/dist/peer/client.js.map +1 -0
- package/dist/peer/manager.d.ts +65 -0
- package/dist/peer/manager.d.ts.map +1 -0
- package/dist/peer/manager.js +153 -0
- package/dist/peer/manager.js.map +1 -0
- package/dist/peer/server.d.ts +65 -0
- package/dist/peer/server.d.ts.map +1 -0
- package/dist/peer/server.js +154 -0
- package/dist/peer/server.js.map +1 -0
- package/dist/relay/client.d.ts +112 -0
- package/dist/relay/client.d.ts.map +1 -0
- package/dist/relay/client.js +281 -0
- package/dist/relay/client.js.map +1 -0
- package/dist/relay/server.d.ts +60 -0
- package/dist/relay/server.d.ts.map +1 -0
- package/dist/relay/server.js +266 -0
- package/dist/relay/server.js.map +1 -0
- package/dist/relay/types.d.ts +35 -0
- package/dist/relay/types.d.ts.map +1 -0
- package/dist/relay/types.js +2 -0
- package/dist/relay/types.js.map +1 -0
- package/dist/transport/peer-config.d.ts +3 -2
- package/dist/transport/peer-config.d.ts.map +1 -1
- package/dist/transport/peer-config.js.map +1 -1
- package/dist/transport/relay.d.ts +23 -0
- package/dist/transport/relay.d.ts.map +1 -0
- package/dist/transport/relay.js +85 -0
- package/dist/transport/relay.js.map +1 -0
- 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> --
|
|
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 (!
|
|
124
|
-
console.error('Error: Missing required
|
|
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
|
-
|
|
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
|
-
|
|
153
|
+
const outputData = {
|
|
137
154
|
status: 'added',
|
|
138
155
|
name,
|
|
139
|
-
url,
|
|
140
156
|
publicKey: pubkey
|
|
141
|
-
}
|
|
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
|
-
//
|
|
234
|
-
const
|
|
235
|
-
|
|
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
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
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
|
-
|
|
255
|
-
|
|
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
|
|
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
|
}
|