@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.
@@ -1,21 +1,35 @@
1
1
  import { Command } from 'commander';
2
2
  import {
3
- createNode,
3
+ createRelayClient,
4
+ createRelayIndexOperations,
4
5
  importKeyPair,
5
6
  createEnvelope,
6
7
  signEnvelope,
7
8
  createMessageRouter,
8
- createDHTOperations,
9
+ createAgentCard,
10
+ signAgentCard,
9
11
  sign,
10
12
  verify,
11
13
  extractPublicKey,
12
14
  } from '@highway1/core';
13
- import { getIdentity, getBootstrapPeers } from '../config.js';
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('--bootstrap <peers...>', 'Bootstrap peer addresses')
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 node (slower)');
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 node (original behavior)
150
- const spin = spinner('Starting node...');
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 bootstrapPeers = options.bootstrap || getBootstrapPeers();
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 node = await createNode({
183
+ const relayClient = createRelayClient({
184
+ relayUrls,
185
+ did: identity.did,
160
186
  keyPair,
161
- bootstrapPeers,
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 node.start();
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
- // Build peer hint from --peer option if provided
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 node.stop();
262
+ await relayClient.stop();
271
263
 
272
264
  success('Done');
273
265
  } catch (err) {
@@ -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
- createNode,
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 ClawiverseNode,
18
+ type RelayClient,
19
19
  type MessageRouter,
20
- type DHTOperations,
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 node: ClawiverseNode | null = null;
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
- // New: persistent queue + defense
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
- this.node = await createNode({
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
- bootstrapPeers: this.bootstrapPeers,
98
- enableDHT: true,
99
- reserveRelaySlot: this.bootstrapPeers.length > 0,
124
+ card: signedCard,
100
125
  });
101
126
 
102
- await this.node.start();
103
- logger.info('Node started', { peerId: this.node.getPeerId() });
127
+ await this.relayClient.start();
128
+ logger.info('Relay client started', { relays: this.relayClient.getConnectedRelays() });
104
129
 
105
- this.dht = createDHTOperations(this.node.libp2p);
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, // Accept all by default
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(` Peer ID: ${this.node.getPeerId()}`);
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, peer } = req.params;
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
- let peerHint = undefined;
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.dht) return { id: req.id, success: false, error: 'DHT not initialized' };
334
- const results = await this.dht.searchSemantic(query);
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.node) return { id: req.id, success: false, error: 'Node not initialized' };
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
- peerId: this.node.getPeerId(),
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.node) {
470
- await this.node.stop();
471
- this.node = null;
469
+ if (this.relayClient) {
470
+ await this.relayClient.stop();
471
+ this.relayClient = null;
472
472
  }
473
473
 
474
474
  if (this.server) {