@highway1/cli 0.1.50 → 0.1.52
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/dist/index.js +12924 -118288
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/commands/discover.ts +52 -42
- package/src/commands/join.ts +36 -228
- package/src/commands/send.ts +43 -51
- package/src/daemon/server.ts +54 -54
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@highway1/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.52",
|
|
4
4
|
"description": "CLI tool for Clawiverse network",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"clean": "rm -rf dist"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@highway1/core": "^0.1.
|
|
16
|
+
"@highway1/core": "^0.1.51",
|
|
17
17
|
"chalk": "^5.3.0",
|
|
18
18
|
"cli-table3": "^0.6.5",
|
|
19
19
|
"commander": "^12.1.0",
|
package/src/commands/discover.ts
CHANGED
|
@@ -1,13 +1,25 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import {
|
|
3
|
-
|
|
3
|
+
createRelayClient,
|
|
4
|
+
createRelayIndexOperations,
|
|
4
5
|
importKeyPair,
|
|
5
|
-
|
|
6
|
+
createAgentCard,
|
|
7
|
+
signAgentCard,
|
|
8
|
+
sign,
|
|
6
9
|
} from '@highway1/core';
|
|
7
|
-
import { getIdentity,
|
|
10
|
+
import { getIdentity, getAgentCard } from '../config.js';
|
|
8
11
|
import { error, spinner, printHeader, info, success } from '../ui.js';
|
|
9
12
|
import Table from 'cli-table3';
|
|
10
13
|
|
|
14
|
+
const DEFAULT_RELAY_URLS = ['ws://relay.highway1.net:8080'];
|
|
15
|
+
|
|
16
|
+
function getRelayUrls(options: any): string[] {
|
|
17
|
+
if (options.relay) return [options.relay];
|
|
18
|
+
const envRelays = process.env.HW1_RELAY_URLS;
|
|
19
|
+
if (envRelays) return envRelays.split(',').map((u: string) => u.trim());
|
|
20
|
+
return DEFAULT_RELAY_URLS;
|
|
21
|
+
}
|
|
22
|
+
|
|
11
23
|
export function registerDiscoverCommand(program: Command): void {
|
|
12
24
|
program
|
|
13
25
|
.command('discover')
|
|
@@ -18,7 +30,7 @@ export function registerDiscoverCommand(program: Command): void {
|
|
|
18
30
|
.option('--min-trust <score>', 'Minimum trust score (0-1)')
|
|
19
31
|
.option('--language <lang>', 'Filter by language')
|
|
20
32
|
.option('--limit <number>', 'Maximum number of results', '10')
|
|
21
|
-
.option('--
|
|
33
|
+
.option('--relay <url>', 'Relay WebSocket URL')
|
|
22
34
|
.action(async (options) => {
|
|
23
35
|
try {
|
|
24
36
|
printHeader('Discover Agents');
|
|
@@ -30,56 +42,58 @@ export function registerDiscoverCommand(program: Command): void {
|
|
|
30
42
|
process.exit(1);
|
|
31
43
|
}
|
|
32
44
|
|
|
33
|
-
const spin = spinner('
|
|
45
|
+
const spin = spinner('Connecting to relay...');
|
|
34
46
|
|
|
35
47
|
const keyPair = importKeyPair({
|
|
36
48
|
publicKey: identity.publicKey,
|
|
37
49
|
privateKey: identity.privateKey,
|
|
38
50
|
});
|
|
39
51
|
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
const
|
|
52
|
+
const relayUrls = getRelayUrls(options);
|
|
53
|
+
const card = getAgentCard();
|
|
54
|
+
const capabilities = (card?.capabilities ?? []).map((c: string) => ({
|
|
55
|
+
id: c, name: c, description: `Capability: ${c}`,
|
|
56
|
+
}));
|
|
57
|
+
const agentCard = createAgentCard(
|
|
58
|
+
identity.did,
|
|
59
|
+
card?.name ?? 'Clawiverse Agent',
|
|
60
|
+
card?.description ?? '',
|
|
61
|
+
capabilities,
|
|
62
|
+
[],
|
|
63
|
+
);
|
|
64
|
+
const signedCard = await signAgentCard(agentCard, (data) => sign(data, keyPair.privateKey));
|
|
65
|
+
|
|
66
|
+
const relayClient = createRelayClient({
|
|
67
|
+
relayUrls,
|
|
68
|
+
did: identity.did,
|
|
43
69
|
keyPair,
|
|
44
|
-
|
|
45
|
-
enableDHT: true,
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
await node.start();
|
|
49
|
-
|
|
50
|
-
spin.text = 'Waiting for DHT peers...';
|
|
51
|
-
// Wait until connected to at least one peer, then give DHT time to sync
|
|
52
|
-
await new Promise<void>((resolve) => {
|
|
53
|
-
const timeout = setTimeout(resolve, 15000);
|
|
54
|
-
node.libp2p.addEventListener('peer:connect', () => {
|
|
55
|
-
clearTimeout(timeout);
|
|
56
|
-
setTimeout(resolve, 3000); // give DHT routing table time to populate
|
|
57
|
-
}, { once: true });
|
|
70
|
+
card: signedCard,
|
|
58
71
|
});
|
|
59
72
|
|
|
60
|
-
|
|
73
|
+
await relayClient.start();
|
|
74
|
+
spin.text = 'Querying relay...';
|
|
61
75
|
|
|
62
|
-
const
|
|
76
|
+
const relayIndex = createRelayIndexOperations(relayClient);
|
|
63
77
|
|
|
64
78
|
if (options.did) {
|
|
65
|
-
const
|
|
79
|
+
const agentCard = await relayIndex.queryAgentCard(options.did);
|
|
66
80
|
|
|
67
|
-
if (
|
|
81
|
+
if (agentCard) {
|
|
68
82
|
spin.succeed('Agent found!');
|
|
69
83
|
console.log();
|
|
70
|
-
info(`DID: ${
|
|
71
|
-
info(`Name: ${
|
|
72
|
-
info(`Description: ${
|
|
73
|
-
info(`Version: ${
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
info(`
|
|
84
|
+
info(`DID: ${agentCard.did}`);
|
|
85
|
+
info(`Name: ${agentCard.name}`);
|
|
86
|
+
info(`Description: ${agentCard.description}`);
|
|
87
|
+
info(`Version: ${agentCard.version}`);
|
|
88
|
+
const caps = agentCard.capabilities.map((c: any) =>
|
|
89
|
+
typeof c === 'string' ? c : c.name
|
|
90
|
+
).join(', ');
|
|
91
|
+
info(`Capabilities: ${caps || '(none)'}`);
|
|
92
|
+
info(`Timestamp: ${new Date(agentCard.timestamp).toISOString()}`);
|
|
78
93
|
} else {
|
|
79
94
|
spin.fail('Agent not found');
|
|
80
95
|
}
|
|
81
96
|
} else if (options.capability || options.query) {
|
|
82
|
-
// Semantic search
|
|
83
97
|
const query: any = {
|
|
84
98
|
limit: parseInt(options.limit, 10),
|
|
85
99
|
};
|
|
@@ -91,9 +105,7 @@ export function registerDiscoverCommand(program: Command): void {
|
|
|
91
105
|
}
|
|
92
106
|
|
|
93
107
|
if (options.minTrust) {
|
|
94
|
-
query.filters = {
|
|
95
|
-
minTrustScore: parseFloat(options.minTrust),
|
|
96
|
-
};
|
|
108
|
+
query.filters = { minTrustScore: parseFloat(options.minTrust) };
|
|
97
109
|
}
|
|
98
110
|
|
|
99
111
|
if (options.language) {
|
|
@@ -101,7 +113,7 @@ export function registerDiscoverCommand(program: Command): void {
|
|
|
101
113
|
query.filters.language = options.language;
|
|
102
114
|
}
|
|
103
115
|
|
|
104
|
-
const cards = await
|
|
116
|
+
const cards = await relayIndex.searchSemantic(query);
|
|
105
117
|
|
|
106
118
|
spin.succeed(`Found ${cards.length} agents`);
|
|
107
119
|
|
|
@@ -133,7 +145,6 @@ export function registerDiscoverCommand(program: Command): void {
|
|
|
133
145
|
console.log();
|
|
134
146
|
console.log(table.toString());
|
|
135
147
|
|
|
136
|
-
// Show detailed info for first result if query was used
|
|
137
148
|
if (options.query && cards.length > 0) {
|
|
138
149
|
const card = cards[0];
|
|
139
150
|
console.log();
|
|
@@ -156,10 +167,9 @@ export function registerDiscoverCommand(program: Command): void {
|
|
|
156
167
|
info('Usage: hw1 discover --did <did>');
|
|
157
168
|
info(' hw1 discover --capability <capability>');
|
|
158
169
|
info(' hw1 discover --query "translate Japanese"');
|
|
159
|
-
info(' hw1 discover --query "code review" --min-trust 0.8');
|
|
160
170
|
}
|
|
161
171
|
|
|
162
|
-
await
|
|
172
|
+
await relayClient.stop();
|
|
163
173
|
} catch (err) {
|
|
164
174
|
error(`Failed to discover: ${(err as Error).message}`);
|
|
165
175
|
process.exit(1);
|
package/src/commands/join.ts
CHANGED
|
@@ -1,26 +1,37 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import {
|
|
3
|
-
|
|
3
|
+
createRelayClient,
|
|
4
|
+
createRelayIndexOperations,
|
|
5
|
+
createMessageRouter,
|
|
4
6
|
importKeyPair,
|
|
5
7
|
createAgentCard,
|
|
6
8
|
signAgentCard,
|
|
7
|
-
createDHTOperations,
|
|
8
|
-
createMessageRouter,
|
|
9
9
|
sign,
|
|
10
10
|
verify,
|
|
11
11
|
extractPublicKey,
|
|
12
12
|
} from '@highway1/core';
|
|
13
|
-
import { getIdentity, getAgentCard
|
|
13
|
+
import { getIdentity, getAgentCard } from '../config.js';
|
|
14
14
|
import { success, error, spinner, printHeader, info } from '../ui.js';
|
|
15
15
|
import { writeFile, mkdir } from 'node:fs/promises';
|
|
16
16
|
import { join, resolve } from 'node:path';
|
|
17
17
|
|
|
18
|
+
// Default relay URLs (CVP-0011)
|
|
19
|
+
const DEFAULT_RELAY_URLS = [
|
|
20
|
+
'ws://relay.highway1.net:8080',
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
function getRelayUrls(options: any): string[] {
|
|
24
|
+
if (options.relay) return [options.relay];
|
|
25
|
+
const envRelays = process.env.HW1_RELAY_URLS;
|
|
26
|
+
if (envRelays) return envRelays.split(',').map((u: string) => u.trim());
|
|
27
|
+
return DEFAULT_RELAY_URLS;
|
|
28
|
+
}
|
|
29
|
+
|
|
18
30
|
export function registerJoinCommand(program: Command): void {
|
|
19
31
|
program
|
|
20
32
|
.command('join')
|
|
21
|
-
.description('Join the Clawiverse network')
|
|
22
|
-
.option('--
|
|
23
|
-
.option('--relay', 'Run as a relay server and advertise relay capability')
|
|
33
|
+
.description('Join the Clawiverse network via relay')
|
|
34
|
+
.option('--relay <url>', 'Relay WebSocket URL (e.g. ws://localhost:8080)')
|
|
24
35
|
.option('--save-dir <path>', 'Directory to save received file attachments', './downloads')
|
|
25
36
|
.action(async (options) => {
|
|
26
37
|
try {
|
|
@@ -34,236 +45,47 @@ export function registerJoinCommand(program: Command): void {
|
|
|
34
45
|
process.exit(1);
|
|
35
46
|
}
|
|
36
47
|
|
|
37
|
-
const spin = spinner('
|
|
48
|
+
const spin = spinner('Connecting to relay...');
|
|
38
49
|
|
|
39
50
|
const keyPair = importKeyPair({
|
|
40
51
|
publicKey: identity.publicKey,
|
|
41
52
|
privateKey: identity.privateKey,
|
|
42
53
|
});
|
|
43
54
|
|
|
44
|
-
const
|
|
45
|
-
const bootstrapPeerIds = bootstrapPeers
|
|
46
|
-
.map((addr: string) => addr.split('/p2p/')[1])
|
|
47
|
-
.filter((peerId: string | undefined): peerId is string => Boolean(peerId));
|
|
48
|
-
|
|
49
|
-
const node = await createNode({
|
|
50
|
-
keyPair,
|
|
51
|
-
bootstrapPeers,
|
|
52
|
-
enableDHT: true,
|
|
53
|
-
reserveRelaySlot: true,
|
|
54
|
-
enableRelay: options.relay ?? false,
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
await node.start();
|
|
58
|
-
|
|
59
|
-
spin.succeed('Node started successfully!');
|
|
60
|
-
|
|
61
|
-
info(`Peer ID: ${node.getPeerId()}`);
|
|
62
|
-
info(`DID: ${identity.did}`);
|
|
63
|
-
info(`Listening on: ${node.getMultiaddrs().join(', ')}`);
|
|
64
|
-
|
|
65
|
-
// Wait for bootstrap peer connection before publishing to DHT
|
|
66
|
-
const connectSpin = spinner('Connecting to bootstrap peers...');
|
|
67
|
-
|
|
68
|
-
// Phase 1: wait for peer:connect (up to 10s)
|
|
69
|
-
let connected = false;
|
|
70
|
-
await new Promise<void>((resolve) => {
|
|
71
|
-
const timeout = setTimeout(resolve, 10000);
|
|
72
|
-
node.libp2p.addEventListener('peer:connect', () => {
|
|
73
|
-
connected = true;
|
|
74
|
-
clearTimeout(timeout);
|
|
75
|
-
resolve();
|
|
76
|
-
}, { once: true });
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
// If discovery has not connected us yet, proactively dial bootstrap peers once.
|
|
80
|
-
if (!connected && bootstrapPeers.length > 0) {
|
|
81
|
-
info('No peer discovered yet, dialing bootstrap peers...');
|
|
82
|
-
for (const bootstrapAddr of bootstrapPeers) {
|
|
83
|
-
try {
|
|
84
|
-
await node.libp2p.dial(bootstrapAddr);
|
|
85
|
-
connected = true;
|
|
86
|
-
break;
|
|
87
|
-
} catch {
|
|
88
|
-
// try next bootstrap peer
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Phase 2: wait for relay reservation using event + polling (more reliable across libp2p versions)
|
|
94
|
-
const countRelayAddrs = () => node.getMultiaddrs().filter(a => a.includes('/p2p-circuit')).length;
|
|
95
|
-
const initialRelayCount = countRelayAddrs();
|
|
96
|
-
|
|
97
|
-
info(`Initial relay addresses: ${initialRelayCount}`);
|
|
98
|
-
info('Waiting for relay reservation...');
|
|
99
|
-
|
|
100
|
-
let reservationSucceeded = false;
|
|
101
|
-
if (initialRelayCount === 0) {
|
|
102
|
-
await new Promise<void>((resolve) => {
|
|
103
|
-
const RELAY_WAIT_MS = 30000;
|
|
104
|
-
const POLL_MS = 500;
|
|
105
|
-
let settled = false;
|
|
106
|
-
|
|
107
|
-
const finish = () => {
|
|
108
|
-
if (settled) return;
|
|
109
|
-
settled = true;
|
|
110
|
-
clearTimeout(timeout);
|
|
111
|
-
clearInterval(pollTimer);
|
|
112
|
-
(node.libp2p as any).removeEventListener('relay:reservation', onReservation);
|
|
113
|
-
(node.libp2p as any).removeEventListener('self:peer:update', onPeerUpdate);
|
|
114
|
-
resolve();
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
const onReservation = () => {
|
|
118
|
-
reservationSucceeded = true;
|
|
119
|
-
info('✓ Relay reservation successful!');
|
|
120
|
-
setTimeout(finish, 300);
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
const onPeerUpdate = () => {
|
|
124
|
-
if (countRelayAddrs() > 0) {
|
|
125
|
-
reservationSucceeded = true;
|
|
126
|
-
info('✓ Relay address detected from peer update');
|
|
127
|
-
finish();
|
|
128
|
-
}
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
const pollTimer = setInterval(() => {
|
|
132
|
-
if (countRelayAddrs() > 0) {
|
|
133
|
-
reservationSucceeded = true;
|
|
134
|
-
info('✓ Relay address detected');
|
|
135
|
-
finish();
|
|
136
|
-
}
|
|
137
|
-
}, POLL_MS);
|
|
138
|
-
|
|
139
|
-
const timeout = setTimeout(() => {
|
|
140
|
-
if (!reservationSucceeded) {
|
|
141
|
-
info(`Relay reservation timeout after ${RELAY_WAIT_MS / 1000}s.`);
|
|
142
|
-
info(`Connected peers: ${node.libp2p.getPeers().map(p => p.toString()).join(', ')}`);
|
|
143
|
-
}
|
|
144
|
-
finish();
|
|
145
|
-
}, RELAY_WAIT_MS);
|
|
146
|
-
|
|
147
|
-
// Event names vary by libp2p internals; keep as any to avoid typing mismatch.
|
|
148
|
-
(node.libp2p as any).addEventListener('relay:reservation', onReservation);
|
|
149
|
-
(node.libp2p as any).addEventListener('self:peer:update', onPeerUpdate);
|
|
150
|
-
});
|
|
151
|
-
} else {
|
|
152
|
-
reservationSucceeded = true;
|
|
153
|
-
info('✓ Relay address already present');
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
if (!reservationSucceeded) {
|
|
157
|
-
info('⚠ Relay reservation did not complete, continuing with fallback relay addresses');
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
connectSpin.succeed('Connected to network!');
|
|
161
|
-
|
|
162
|
-
// Publish Agent Card with peerId and multiaddrs so others can dial us
|
|
163
|
-
const cardSpin = spinner('Publishing Agent Card to DHT...');
|
|
164
|
-
|
|
165
|
-
// Use relay addresses from libp2p (populated after reservation), fall back to manual construction
|
|
166
|
-
const directAddrs = node.getMultiaddrs().filter(a => !a.includes('/p2p-circuit'));
|
|
167
|
-
const relayAddrs = node.getMultiaddrs().filter(a => a.includes('/p2p-circuit'));
|
|
168
|
-
const fallbackRelayAddrs = relayAddrs.length === 0
|
|
169
|
-
? bootstrapPeers.map((r: string) => `${r}/p2p-circuit/p2p/${node.getPeerId()}`)
|
|
170
|
-
: [];
|
|
171
|
-
const allAddrs = [...directAddrs, ...relayAddrs, ...fallbackRelayAddrs];
|
|
55
|
+
const relayUrls = getRelayUrls(options);
|
|
172
56
|
|
|
173
57
|
const capabilities = (card.capabilities ?? []).map((capability: string) => ({
|
|
174
58
|
id: capability,
|
|
175
59
|
name: capability,
|
|
176
60
|
description: `Capability: ${capability}`,
|
|
177
61
|
}));
|
|
178
|
-
if (options.relay) {
|
|
179
|
-
capabilities.push({
|
|
180
|
-
id: 'relay',
|
|
181
|
-
name: 'relay',
|
|
182
|
-
description: 'Provides circuit relay service for NAT traversal',
|
|
183
|
-
});
|
|
184
|
-
}
|
|
185
62
|
|
|
186
63
|
const agentCard = createAgentCard(
|
|
187
64
|
identity.did,
|
|
188
65
|
card.name,
|
|
189
66
|
card.description,
|
|
190
67
|
capabilities,
|
|
191
|
-
|
|
192
|
-
node.getPeerId()
|
|
68
|
+
[],
|
|
193
69
|
);
|
|
194
70
|
|
|
195
71
|
const signedCard = await signAgentCard(agentCard, (data) =>
|
|
196
72
|
sign(data, keyPair.privateKey)
|
|
197
73
|
);
|
|
198
74
|
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
const ensureBootstrapConnections = async () => {
|
|
206
|
-
const connections = node.libp2p.getConnections();
|
|
207
|
-
const connectedPeerIds = new Set(connections.map((conn) => conn.remotePeer.toString()));
|
|
208
|
-
|
|
209
|
-
for (const bootstrapAddr of bootstrapPeers) {
|
|
210
|
-
const targetPeerId = bootstrapAddr.split('/p2p/')[1];
|
|
211
|
-
if (!targetPeerId || connectedPeerIds.has(targetPeerId)) continue;
|
|
212
|
-
|
|
213
|
-
try {
|
|
214
|
-
await node.libp2p.dial(bootstrapAddr);
|
|
215
|
-
info(`Reconnected bootstrap peer: ${targetPeerId}`);
|
|
216
|
-
} catch {
|
|
217
|
-
// best effort; keep trying on next tick
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
};
|
|
75
|
+
const relayClient = createRelayClient({
|
|
76
|
+
relayUrls,
|
|
77
|
+
did: identity.did,
|
|
78
|
+
keyPair,
|
|
79
|
+
card: signedCard,
|
|
80
|
+
});
|
|
221
81
|
|
|
222
|
-
|
|
223
|
-
const pingInterval = setInterval(async () => {
|
|
224
|
-
const peers = node.libp2p.getPeers();
|
|
225
|
-
if (peers.length === 0) {
|
|
226
|
-
// No peers connected, try to reconnect to bootstrap
|
|
227
|
-
for (const bootstrapAddr of bootstrapPeers) {
|
|
228
|
-
try {
|
|
229
|
-
await node.libp2p.dial(bootstrapAddr);
|
|
230
|
-
info('Reconnected to bootstrap peer');
|
|
231
|
-
break;
|
|
232
|
-
} catch {
|
|
233
|
-
// try next bootstrap peer
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
} else {
|
|
237
|
-
await ensureBootstrapConnections();
|
|
238
|
-
// Ping existing peers to keep connection alive
|
|
239
|
-
for (const peer of peers) {
|
|
240
|
-
try {
|
|
241
|
-
await (node.libp2p.services.ping as any).ping(peer);
|
|
242
|
-
} catch {
|
|
243
|
-
// ignore ping failures
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
}, 15000); // ping every 15s (reduced from 30s for better stability)
|
|
82
|
+
await relayClient.start();
|
|
248
83
|
|
|
249
|
-
|
|
250
|
-
const onPeerDisconnect = async (evt: any) => {
|
|
251
|
-
const disconnectedPeerId = evt?.detail?.toString?.() ?? '';
|
|
252
|
-
if (!bootstrapPeerIds.includes(disconnectedPeerId)) return;
|
|
84
|
+
spin.succeed('Connected to relay!');
|
|
253
85
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
try {
|
|
258
|
-
await node.libp2p.dial(bootstrapAddr);
|
|
259
|
-
info(`Recovered bootstrap connection: ${disconnectedPeerId}`);
|
|
260
|
-
return;
|
|
261
|
-
} catch {
|
|
262
|
-
// Continue trying other bootstrap peers if available
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
};
|
|
266
|
-
node.libp2p.addEventListener('peer:disconnect', onPeerDisconnect);
|
|
86
|
+
info(`DID: ${identity.did}`);
|
|
87
|
+
info(`Connected relays: ${relayClient.getConnectedRelays().join(', ')}`);
|
|
88
|
+
info(`Network peers: ${relayClient.getPeerCount()}`);
|
|
267
89
|
|
|
268
90
|
const verifyFn = async (signature: Uint8Array, data: Uint8Array): Promise<boolean> => {
|
|
269
91
|
try {
|
|
@@ -276,16 +98,10 @@ export function registerJoinCommand(program: Command): void {
|
|
|
276
98
|
}
|
|
277
99
|
};
|
|
278
100
|
|
|
279
|
-
|
|
280
|
-
const router = createMessageRouter(
|
|
281
|
-
node.libp2p,
|
|
282
|
-
verifyFn,
|
|
283
|
-
dht
|
|
284
|
-
);
|
|
101
|
+
const router = createMessageRouter(relayClient, verifyFn);
|
|
285
102
|
|
|
286
103
|
const saveDir = resolve(options.saveDir ?? './downloads');
|
|
287
104
|
|
|
288
|
-
// Generic message handler that accepts any protocol
|
|
289
105
|
const messageHandler = async (envelope: any) => {
|
|
290
106
|
const payload = envelope.payload as Record<string, unknown>;
|
|
291
107
|
console.log();
|
|
@@ -294,7 +110,6 @@ export function registerJoinCommand(program: Command): void {
|
|
|
294
110
|
info(` Protocol: ${envelope.protocol}`);
|
|
295
111
|
info(` Type: ${envelope.type}`);
|
|
296
112
|
|
|
297
|
-
// Handle file attachment
|
|
298
113
|
let savedPath: string | undefined;
|
|
299
114
|
if (payload.attachment) {
|
|
300
115
|
const att = payload.attachment as {
|
|
@@ -305,7 +120,6 @@ export function registerJoinCommand(program: Command): void {
|
|
|
305
120
|
};
|
|
306
121
|
try {
|
|
307
122
|
await mkdir(saveDir, { recursive: true });
|
|
308
|
-
// Avoid filename collisions by prefixing with message id fragment
|
|
309
123
|
const safeName = att.filename.replace(/[^a-zA-Z0-9._-]/g, '_');
|
|
310
124
|
savedPath = join(saveDir, `${envelope.id.slice(-8)}_${safeName}`);
|
|
311
125
|
await writeFile(savedPath, Buffer.from(att.data, 'base64'));
|
|
@@ -321,7 +135,6 @@ export function registerJoinCommand(program: Command): void {
|
|
|
321
135
|
}
|
|
322
136
|
console.log();
|
|
323
137
|
|
|
324
|
-
// If this is a request, send back a simple acknowledgment response
|
|
325
138
|
if (envelope.type === 'request') {
|
|
326
139
|
info(' Sending acknowledgment response...');
|
|
327
140
|
|
|
@@ -356,11 +169,8 @@ export function registerJoinCommand(program: Command): void {
|
|
|
356
169
|
return undefined;
|
|
357
170
|
};
|
|
358
171
|
|
|
359
|
-
// Register handlers for common protocols
|
|
360
172
|
router.registerHandler('/clawiverse/msg/1.0.0', messageHandler);
|
|
361
173
|
router.registerHandler('/clawiverse/greet/1.0.0', messageHandler);
|
|
362
|
-
|
|
363
|
-
// Register catch-all handler for any other protocol
|
|
364
174
|
router.registerCatchAllHandler(messageHandler);
|
|
365
175
|
|
|
366
176
|
await router.start();
|
|
@@ -372,12 +182,10 @@ export function registerJoinCommand(program: Command): void {
|
|
|
372
182
|
|
|
373
183
|
process.on('SIGINT', async () => {
|
|
374
184
|
console.log();
|
|
375
|
-
const stopSpin = spinner('Stopping
|
|
376
|
-
clearInterval(pingInterval);
|
|
377
|
-
node.libp2p.removeEventListener('peer:disconnect', onPeerDisconnect);
|
|
185
|
+
const stopSpin = spinner('Stopping...');
|
|
378
186
|
await router.stop();
|
|
379
|
-
await
|
|
380
|
-
stopSpin.succeed('
|
|
187
|
+
await relayClient.stop();
|
|
188
|
+
stopSpin.succeed('Disconnected');
|
|
381
189
|
process.exit(0);
|
|
382
190
|
});
|
|
383
191
|
|