@highway1/cli 0.1.50 → 0.1.53
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 +13072 -118402
- 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/src/commands/send.ts
CHANGED
|
@@ -1,21 +1,35 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import {
|
|
3
|
-
|
|
3
|
+
createRelayClient,
|
|
4
|
+
createRelayIndexOperations,
|
|
4
5
|
importKeyPair,
|
|
5
6
|
createEnvelope,
|
|
6
7
|
signEnvelope,
|
|
7
8
|
createMessageRouter,
|
|
8
|
-
|
|
9
|
+
createAgentCard,
|
|
10
|
+
signAgentCard,
|
|
9
11
|
sign,
|
|
10
12
|
verify,
|
|
11
13
|
extractPublicKey,
|
|
12
14
|
} from '@highway1/core';
|
|
13
|
-
import { getIdentity,
|
|
15
|
+
import { getIdentity, getAgentCard } from '../config.js';
|
|
14
16
|
import { success, error, spinner, printHeader, info } from '../ui.js';
|
|
15
17
|
import { readFile } from 'node:fs/promises';
|
|
16
18
|
import { basename } from 'node:path';
|
|
17
19
|
import { DaemonClient } from '../daemon/client.js';
|
|
18
20
|
|
|
21
|
+
// Default relay URLs (CVP-0011)
|
|
22
|
+
const DEFAULT_RELAY_URLS = [
|
|
23
|
+
'ws://relay.highway1.net:8080',
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
function getRelayUrls(options: any): string[] {
|
|
27
|
+
if (options.relay) return [options.relay];
|
|
28
|
+
const envRelays = process.env.HW1_RELAY_URLS;
|
|
29
|
+
if (envRelays) return envRelays.split(',').map((u: string) => u.trim());
|
|
30
|
+
return DEFAULT_RELAY_URLS;
|
|
31
|
+
}
|
|
32
|
+
|
|
19
33
|
export function registerSendCommand(program: Command): void {
|
|
20
34
|
program
|
|
21
35
|
.command('send')
|
|
@@ -26,8 +40,7 @@ export function registerSendCommand(program: Command): void {
|
|
|
26
40
|
.option('--payload <json>', 'Message payload (JSON)')
|
|
27
41
|
.option('--file <path>', 'Attach a file (image, binary, text) as payload attachment')
|
|
28
42
|
.option('--type <type>', 'Message type (request|notification)', 'request') // CVP-0010 §3.1: default type
|
|
29
|
-
.option('--
|
|
30
|
-
.option('--peer <multiaddr>', 'Direct peer multiaddr (bypasses DHT lookup)')
|
|
43
|
+
.option('--relay <url>', 'Relay WebSocket URL')
|
|
31
44
|
.option('--format <fmt>', 'Output format: text|json', 'text') // CVP-0010 §3.2: --format json
|
|
32
45
|
.action(async (options) => {
|
|
33
46
|
try {
|
|
@@ -91,7 +104,6 @@ export function registerSendCommand(program: Command): void {
|
|
|
91
104
|
protocol: options.protocol,
|
|
92
105
|
payload,
|
|
93
106
|
type: options.type,
|
|
94
|
-
peer: options.peer,
|
|
95
107
|
});
|
|
96
108
|
|
|
97
109
|
spin.succeed('Message sent successfully!');
|
|
@@ -141,43 +153,43 @@ export function registerSendCommand(program: Command): void {
|
|
|
141
153
|
}
|
|
142
154
|
} else {
|
|
143
155
|
console.log();
|
|
144
|
-
info('⚠ Daemon not running, using ephemeral
|
|
156
|
+
info('⚠ Daemon not running, using ephemeral relay connection (slower)');
|
|
145
157
|
info('Tip: Start daemon with "clawiverse daemon start" for faster messaging');
|
|
146
158
|
console.log();
|
|
147
159
|
}
|
|
148
160
|
|
|
149
|
-
// Fallback: create ephemeral
|
|
150
|
-
const spin = spinner('
|
|
161
|
+
// Fallback: create ephemeral relay connection (CVP-0011)
|
|
162
|
+
const spin = spinner('Connecting to relay...');
|
|
151
163
|
|
|
152
164
|
const keyPair = importKeyPair({
|
|
153
165
|
publicKey: identity.publicKey,
|
|
154
166
|
privateKey: identity.privateKey,
|
|
155
167
|
});
|
|
156
168
|
|
|
157
|
-
const
|
|
169
|
+
const relayUrls = getRelayUrls(options);
|
|
170
|
+
const card = getAgentCard();
|
|
171
|
+
const capabilities = (card?.capabilities ?? []).map((c: string) => ({
|
|
172
|
+
id: c, name: c, description: `Capability: ${c}`,
|
|
173
|
+
}));
|
|
174
|
+
const agentCard = createAgentCard(
|
|
175
|
+
identity.did,
|
|
176
|
+
card?.name ?? 'Clawiverse Agent',
|
|
177
|
+
card?.description ?? '',
|
|
178
|
+
capabilities,
|
|
179
|
+
[],
|
|
180
|
+
);
|
|
181
|
+
const signedCard = await signAgentCard(agentCard, (data) => sign(data, keyPair.privateKey));
|
|
158
182
|
|
|
159
|
-
const
|
|
183
|
+
const relayClient = createRelayClient({
|
|
184
|
+
relayUrls,
|
|
185
|
+
did: identity.did,
|
|
160
186
|
keyPair,
|
|
161
|
-
|
|
162
|
-
enableDHT: true,
|
|
163
|
-
reserveRelaySlot: bootstrapPeers.length > 0,
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
// Register peer:identify listener BEFORE start() so we don't miss the event
|
|
167
|
-
const identifyDone = new Promise<void>((resolve) => {
|
|
168
|
-
const timeout = setTimeout(resolve, 2000);
|
|
169
|
-
node.libp2p.addEventListener('peer:identify', () => {
|
|
170
|
-
clearTimeout(timeout);
|
|
171
|
-
resolve();
|
|
172
|
-
}, { once: true });
|
|
187
|
+
card: signedCard,
|
|
173
188
|
});
|
|
174
189
|
|
|
175
|
-
await
|
|
190
|
+
await relayClient.start();
|
|
191
|
+
spin.text = 'Connected to relay';
|
|
176
192
|
|
|
177
|
-
spin.text = 'Connecting to network...';
|
|
178
|
-
await identifyDone;
|
|
179
|
-
|
|
180
|
-
const dht = createDHTOperations(node.libp2p);
|
|
181
193
|
const verifyFn = async (signature: Uint8Array, data: Uint8Array): Promise<boolean> => {
|
|
182
194
|
try {
|
|
183
195
|
const decoded = JSON.parse(new TextDecoder().decode(data)) as { from?: string };
|
|
@@ -189,13 +201,7 @@ export function registerSendCommand(program: Command): void {
|
|
|
189
201
|
}
|
|
190
202
|
};
|
|
191
203
|
|
|
192
|
-
const router = createMessageRouter(
|
|
193
|
-
node.libp2p,
|
|
194
|
-
verifyFn,
|
|
195
|
-
dht,
|
|
196
|
-
bootstrapPeers
|
|
197
|
-
);
|
|
198
|
-
|
|
204
|
+
const router = createMessageRouter(relayClient, verifyFn);
|
|
199
205
|
await router.start();
|
|
200
206
|
|
|
201
207
|
spin.text = 'Creating message...';
|
|
@@ -214,21 +220,7 @@ export function registerSendCommand(program: Command): void {
|
|
|
214
220
|
|
|
215
221
|
spin.text = 'Sending message...';
|
|
216
222
|
|
|
217
|
-
|
|
218
|
-
let peerHint = undefined;
|
|
219
|
-
if (options.peer) {
|
|
220
|
-
// Extract peerId from multiaddr (last component after /p2p/)
|
|
221
|
-
const parts = options.peer.split('/p2p/');
|
|
222
|
-
if (parts.length >= 2) {
|
|
223
|
-
peerHint = {
|
|
224
|
-
peerId: parts[parts.length - 1],
|
|
225
|
-
multiaddrs: [options.peer],
|
|
226
|
-
};
|
|
227
|
-
info(`Direct peer: ${options.peer}`);
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
const response = await router.sendMessage(signedEnvelope, peerHint);
|
|
223
|
+
const response = await router.sendMessage(signedEnvelope);
|
|
232
224
|
|
|
233
225
|
spin.succeed('Message sent successfully!');
|
|
234
226
|
|
|
@@ -267,7 +259,7 @@ export function registerSendCommand(program: Command): void {
|
|
|
267
259
|
}
|
|
268
260
|
|
|
269
261
|
await router.stop();
|
|
270
|
-
await
|
|
262
|
+
await relayClient.stop();
|
|
271
263
|
|
|
272
264
|
success('Done');
|
|
273
265
|
} catch (err) {
|
package/src/daemon/server.ts
CHANGED
|
@@ -3,10 +3,10 @@ import type { Server } from 'net';
|
|
|
3
3
|
import { homedir } from 'os';
|
|
4
4
|
import { join } from 'path';
|
|
5
5
|
import {
|
|
6
|
-
|
|
6
|
+
createRelayClient,
|
|
7
|
+
createRelayIndexOperations,
|
|
7
8
|
importKeyPair,
|
|
8
9
|
createMessageRouter,
|
|
9
|
-
createDHTOperations,
|
|
10
10
|
sign,
|
|
11
11
|
verify,
|
|
12
12
|
extractPublicKey,
|
|
@@ -15,9 +15,9 @@ import {
|
|
|
15
15
|
createTrustSystem,
|
|
16
16
|
MessageQueue,
|
|
17
17
|
DefenseMiddleware,
|
|
18
|
-
type
|
|
18
|
+
type RelayClient,
|
|
19
19
|
type MessageRouter,
|
|
20
|
-
type
|
|
20
|
+
type RelayIndexOperations,
|
|
21
21
|
type TrustSystem,
|
|
22
22
|
type MessageEnvelope,
|
|
23
23
|
} from '@highway1/core';
|
|
@@ -26,6 +26,17 @@ import { getIdentity, getBootstrapPeers } from '../config.js';
|
|
|
26
26
|
|
|
27
27
|
const logger = createLogger('daemon');
|
|
28
28
|
|
|
29
|
+
// Default relay URLs (CVP-0011)
|
|
30
|
+
const DEFAULT_RELAY_URLS = [
|
|
31
|
+
'ws://relay.highway1.net:8080',
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
function getRelayUrls(): string[] {
|
|
35
|
+
const envRelays = process.env.HW1_RELAY_URLS;
|
|
36
|
+
if (envRelays) return envRelays.split(',').map((u) => u.trim());
|
|
37
|
+
return DEFAULT_RELAY_URLS;
|
|
38
|
+
}
|
|
39
|
+
|
|
29
40
|
type DaemonCommand =
|
|
30
41
|
| 'send'
|
|
31
42
|
| 'discover'
|
|
@@ -60,15 +71,14 @@ interface DaemonResponse {
|
|
|
60
71
|
}
|
|
61
72
|
|
|
62
73
|
export class ClawDaemon {
|
|
63
|
-
private
|
|
74
|
+
private relayClient: RelayClient | null = null;
|
|
75
|
+
private relayIndex: RelayIndexOperations | null = null;
|
|
64
76
|
private router: MessageRouter | null = null;
|
|
65
|
-
private dht: DHTOperations | null = null;
|
|
66
77
|
private server: Server | null = null;
|
|
67
78
|
private socketPath: string;
|
|
68
79
|
private identity: any;
|
|
69
|
-
private bootstrapPeers: string[];
|
|
70
80
|
|
|
71
|
-
//
|
|
81
|
+
// Persistent queue + defense
|
|
72
82
|
private queue: MessageQueue | null = null;
|
|
73
83
|
private defense: DefenseMiddleware | null = null;
|
|
74
84
|
private trustSystem: TrustSystem | null = null;
|
|
@@ -76,7 +86,6 @@ export class ClawDaemon {
|
|
|
76
86
|
constructor(socketPath: string = '/tmp/clawiverse.sock') {
|
|
77
87
|
this.socketPath = socketPath;
|
|
78
88
|
this.identity = getIdentity();
|
|
79
|
-
this.bootstrapPeers = getBootstrapPeers();
|
|
80
89
|
|
|
81
90
|
if (!this.identity) {
|
|
82
91
|
throw new Error('No identity found. Run "clawiverse init" first.');
|
|
@@ -92,17 +101,33 @@ export class ClawDaemon {
|
|
|
92
101
|
privateKey: this.identity.privateKey,
|
|
93
102
|
});
|
|
94
103
|
|
|
95
|
-
|
|
104
|
+
// Load agent card for HELLO message
|
|
105
|
+
const { getAgentCard, createAgentCard, signAgentCard } = await import('@highway1/core');
|
|
106
|
+
const cardConfig = getAgentCard();
|
|
107
|
+
const capabilities = (cardConfig?.capabilities ?? []).map((c: string) => ({
|
|
108
|
+
id: c, name: c, description: `Capability: ${c}`,
|
|
109
|
+
}));
|
|
110
|
+
const agentCard = createAgentCard(
|
|
111
|
+
this.identity.did,
|
|
112
|
+
cardConfig?.name ?? 'Clawiverse Agent',
|
|
113
|
+
cardConfig?.description ?? '',
|
|
114
|
+
capabilities,
|
|
115
|
+
[],
|
|
116
|
+
);
|
|
117
|
+
const signedCard = await signAgentCard(agentCard, (data) => sign(data, keyPair.privateKey));
|
|
118
|
+
|
|
119
|
+
const relayUrls = getRelayUrls();
|
|
120
|
+
this.relayClient = createRelayClient({
|
|
121
|
+
relayUrls,
|
|
122
|
+
did: this.identity.did,
|
|
96
123
|
keyPair,
|
|
97
|
-
|
|
98
|
-
enableDHT: true,
|
|
99
|
-
reserveRelaySlot: this.bootstrapPeers.length > 0,
|
|
124
|
+
card: signedCard,
|
|
100
125
|
});
|
|
101
126
|
|
|
102
|
-
await this.
|
|
103
|
-
logger.info('
|
|
127
|
+
await this.relayClient.start();
|
|
128
|
+
logger.info('Relay client started', { relays: this.relayClient.getConnectedRelays() });
|
|
104
129
|
|
|
105
|
-
this.
|
|
130
|
+
this.relayIndex = createRelayIndexOperations(this.relayClient);
|
|
106
131
|
|
|
107
132
|
const verifyFn = async (signature: Uint8Array, data: Uint8Array): Promise<boolean> => {
|
|
108
133
|
try {
|
|
@@ -115,13 +140,7 @@ export class ClawDaemon {
|
|
|
115
140
|
}
|
|
116
141
|
};
|
|
117
142
|
|
|
118
|
-
this.router = createMessageRouter(
|
|
119
|
-
this.node.libp2p,
|
|
120
|
-
verifyFn,
|
|
121
|
-
this.dht,
|
|
122
|
-
this.bootstrapPeers
|
|
123
|
-
);
|
|
124
|
-
|
|
143
|
+
this.router = createMessageRouter(this.relayClient, verifyFn);
|
|
125
144
|
await this.router.start();
|
|
126
145
|
logger.info('Router started');
|
|
127
146
|
|
|
@@ -145,7 +164,7 @@ export class ClawDaemon {
|
|
|
145
164
|
this.defense = new DefenseMiddleware({
|
|
146
165
|
trustSystem: this.trustSystem,
|
|
147
166
|
storage: this.queue.store,
|
|
148
|
-
minTrustScore: 0,
|
|
167
|
+
minTrustScore: 0,
|
|
149
168
|
});
|
|
150
169
|
logger.info('Defense middleware initialized');
|
|
151
170
|
|
|
@@ -164,7 +183,7 @@ export class ClawDaemon {
|
|
|
164
183
|
|
|
165
184
|
console.log(`✓ Clawiverse daemon started`);
|
|
166
185
|
console.log(` Socket: ${this.socketPath}`);
|
|
167
|
-
console.log(`
|
|
186
|
+
console.log(` Relays: ${this.relayClient.getConnectedRelays().join(', ')}`);
|
|
168
187
|
console.log(` DID: ${this.identity.did}`);
|
|
169
188
|
} catch (error) {
|
|
170
189
|
logger.error('Failed to start daemon', error);
|
|
@@ -175,17 +194,14 @@ export class ClawDaemon {
|
|
|
175
194
|
private async handleIncomingMessage(envelope: MessageEnvelope): Promise<MessageEnvelope | void> {
|
|
176
195
|
if (!this.defense || !this.queue || !this.trustSystem) return;
|
|
177
196
|
|
|
178
|
-
// Run defense checks
|
|
179
197
|
const result = await this.defense.checkMessage(envelope);
|
|
180
198
|
if (!result.allowed) {
|
|
181
199
|
logger.warn('Message rejected by defense', { id: envelope.id, reason: result.reason });
|
|
182
200
|
return;
|
|
183
201
|
}
|
|
184
202
|
|
|
185
|
-
// Persist to inbox
|
|
186
203
|
await this.queue.enqueueInbound(envelope, result.trustScore);
|
|
187
204
|
|
|
188
|
-
// Record interaction for trust scoring
|
|
189
205
|
await this.trustSystem.recordInteraction({
|
|
190
206
|
agentDid: envelope.from,
|
|
191
207
|
timestamp: Date.now(),
|
|
@@ -202,7 +218,6 @@ export class ClawDaemon {
|
|
|
202
218
|
|
|
203
219
|
socket.on('data', async (data) => {
|
|
204
220
|
buffer += data.toString();
|
|
205
|
-
// Handle newline-delimited JSON
|
|
206
221
|
const lines = buffer.split('\n');
|
|
207
222
|
buffer = lines.pop() ?? '';
|
|
208
223
|
|
|
@@ -223,7 +238,6 @@ export class ClawDaemon {
|
|
|
223
238
|
}
|
|
224
239
|
}
|
|
225
240
|
|
|
226
|
-
// Also handle single-message (no newline) for backward compat
|
|
227
241
|
if (buffer.trim()) {
|
|
228
242
|
try {
|
|
229
243
|
const request: DaemonRequest = JSON.parse(buffer);
|
|
@@ -253,7 +267,6 @@ export class ClawDaemon {
|
|
|
253
267
|
await this.shutdown();
|
|
254
268
|
return { id: req.id, success: true };
|
|
255
269
|
|
|
256
|
-
// Queue commands
|
|
257
270
|
case 'inbox': return await this.handleInbox(req);
|
|
258
271
|
case 'get_message': return await this.handleGetMessage(req);
|
|
259
272
|
case 'mark_read': return await this.handleMarkRead(req);
|
|
@@ -261,12 +274,10 @@ export class ClawDaemon {
|
|
|
261
274
|
case 'outbox': return await this.handleOutbox(req);
|
|
262
275
|
case 'retry_message': return await this.handleRetryMessage(req);
|
|
263
276
|
|
|
264
|
-
// Defense commands
|
|
265
277
|
case 'block': return await this.handleBlock(req);
|
|
266
278
|
case 'unblock': return await this.handleUnblock(req);
|
|
267
279
|
case 'allowlist': return await this.handleAllowlist(req);
|
|
268
280
|
|
|
269
|
-
// Stats
|
|
270
281
|
case 'queue_stats': return await this.handleQueueStats(req);
|
|
271
282
|
|
|
272
283
|
default:
|
|
@@ -279,7 +290,7 @@ export class ClawDaemon {
|
|
|
279
290
|
}
|
|
280
291
|
|
|
281
292
|
private async handleSend(req: DaemonRequest): Promise<DaemonResponse> {
|
|
282
|
-
const { to, protocol, payload, type
|
|
293
|
+
const { to, protocol, payload, type } = req.params;
|
|
283
294
|
|
|
284
295
|
if (!this.router) {
|
|
285
296
|
return { id: req.id, success: false, error: 'Router not initialized' };
|
|
@@ -302,20 +313,11 @@ export class ClawDaemon {
|
|
|
302
313
|
sign(data, keyPair.privateKey)
|
|
303
314
|
);
|
|
304
315
|
|
|
305
|
-
// Track outbound in queue
|
|
306
316
|
if (this.queue) {
|
|
307
317
|
await this.queue.enqueueOutbound(signedEnvelope);
|
|
308
318
|
}
|
|
309
319
|
|
|
310
|
-
|
|
311
|
-
if (peer) {
|
|
312
|
-
const parts = peer.split('/p2p/');
|
|
313
|
-
if (parts.length >= 2) {
|
|
314
|
-
peerHint = { peerId: parts[parts.length - 1], multiaddrs: [peer] };
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
const response = await this.router.sendMessage(signedEnvelope, peerHint);
|
|
320
|
+
const response = await this.router.sendMessage(signedEnvelope);
|
|
319
321
|
|
|
320
322
|
if (this.queue) {
|
|
321
323
|
await this.queue.markOutboundDelivered(signedEnvelope.id);
|
|
@@ -330,27 +332,25 @@ export class ClawDaemon {
|
|
|
330
332
|
|
|
331
333
|
private async handleDiscover(req: DaemonRequest): Promise<DaemonResponse> {
|
|
332
334
|
const { query } = req.params;
|
|
333
|
-
if (!this.
|
|
334
|
-
const results = await this.
|
|
335
|
+
if (!this.relayIndex) return { id: req.id, success: false, error: 'Relay index not initialized' };
|
|
336
|
+
const results = await this.relayIndex.searchSemantic(query);
|
|
335
337
|
return { id: req.id, success: true, data: results };
|
|
336
338
|
}
|
|
337
339
|
|
|
338
340
|
private handleStatus(req: DaemonRequest): DaemonResponse {
|
|
339
|
-
if (!this.
|
|
341
|
+
if (!this.relayClient) return { id: req.id, success: false, error: 'Relay client not initialized' };
|
|
340
342
|
return {
|
|
341
343
|
id: req.id,
|
|
342
344
|
success: true,
|
|
343
345
|
data: {
|
|
344
346
|
running: true,
|
|
345
|
-
|
|
347
|
+
connectedRelays: this.relayClient.getConnectedRelays(),
|
|
348
|
+
peerCount: this.relayClient.getPeerCount(),
|
|
346
349
|
did: this.identity.did,
|
|
347
|
-
multiaddrs: this.node.getMultiaddrs(),
|
|
348
|
-
bootstrapPeers: this.bootstrapPeers,
|
|
349
350
|
},
|
|
350
351
|
};
|
|
351
352
|
}
|
|
352
353
|
|
|
353
|
-
/** Legacy messages command — delegates to inbox for backward compat */
|
|
354
354
|
private async handleMessages(req: DaemonRequest): Promise<DaemonResponse> {
|
|
355
355
|
const { limit = 10 } = req.params || {};
|
|
356
356
|
if (this.queue) {
|
|
@@ -466,9 +466,9 @@ export class ClawDaemon {
|
|
|
466
466
|
this.trustSystem = null;
|
|
467
467
|
}
|
|
468
468
|
|
|
469
|
-
if (this.
|
|
470
|
-
await this.
|
|
471
|
-
this.
|
|
469
|
+
if (this.relayClient) {
|
|
470
|
+
await this.relayClient.stop();
|
|
471
|
+
this.relayClient = null;
|
|
472
472
|
}
|
|
473
473
|
|
|
474
474
|
if (this.server) {
|