@highway1/cli 0.1.52 → 0.1.54

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,481 +0,0 @@
1
- import { createServer, Socket } from 'net';
2
- import type { Server } from 'net';
3
- import { homedir } from 'os';
4
- import { join } from 'path';
5
- import {
6
- createRelayClient,
7
- createRelayIndexOperations,
8
- importKeyPair,
9
- createMessageRouter,
10
- sign,
11
- verify,
12
- extractPublicKey,
13
- createEnvelope,
14
- signEnvelope,
15
- createTrustSystem,
16
- MessageQueue,
17
- DefenseMiddleware,
18
- type RelayClient,
19
- type MessageRouter,
20
- type RelayIndexOperations,
21
- type TrustSystem,
22
- type MessageEnvelope,
23
- } from '@highway1/core';
24
- import { createLogger } from '@highway1/core';
25
- import { getIdentity, getBootstrapPeers } from '../config.js';
26
-
27
- const logger = createLogger('daemon');
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
-
40
- type DaemonCommand =
41
- | 'send'
42
- | 'discover'
43
- | 'status'
44
- | 'messages'
45
- | 'shutdown'
46
- // Queue commands
47
- | 'inbox'
48
- | 'get_message'
49
- | 'mark_read'
50
- | 'delete_message'
51
- | 'outbox'
52
- | 'retry_message'
53
- // Defense commands
54
- | 'block'
55
- | 'unblock'
56
- | 'allowlist'
57
- // Stats
58
- | 'queue_stats';
59
-
60
- interface DaemonRequest {
61
- id: string;
62
- command: DaemonCommand;
63
- params: any;
64
- }
65
-
66
- interface DaemonResponse {
67
- id: string;
68
- success: boolean;
69
- data?: any;
70
- error?: string;
71
- }
72
-
73
- export class ClawDaemon {
74
- private relayClient: RelayClient | null = null;
75
- private relayIndex: RelayIndexOperations | null = null;
76
- private router: MessageRouter | null = null;
77
- private server: Server | null = null;
78
- private socketPath: string;
79
- private identity: any;
80
-
81
- // Persistent queue + defense
82
- private queue: MessageQueue | null = null;
83
- private defense: DefenseMiddleware | null = null;
84
- private trustSystem: TrustSystem | null = null;
85
-
86
- constructor(socketPath: string = '/tmp/clawiverse.sock') {
87
- this.socketPath = socketPath;
88
- this.identity = getIdentity();
89
-
90
- if (!this.identity) {
91
- throw new Error('No identity found. Run "clawiverse init" first.');
92
- }
93
- }
94
-
95
- async start(): Promise<void> {
96
- try {
97
- logger.info('Starting Clawiverse daemon', { socketPath: this.socketPath });
98
-
99
- const keyPair = importKeyPair({
100
- publicKey: this.identity.publicKey,
101
- privateKey: this.identity.privateKey,
102
- });
103
-
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,
123
- keyPair,
124
- card: signedCard,
125
- });
126
-
127
- await this.relayClient.start();
128
- logger.info('Relay client started', { relays: this.relayClient.getConnectedRelays() });
129
-
130
- this.relayIndex = createRelayIndexOperations(this.relayClient);
131
-
132
- const verifyFn = async (signature: Uint8Array, data: Uint8Array): Promise<boolean> => {
133
- try {
134
- const decoded = JSON.parse(new TextDecoder().decode(data)) as { from?: string };
135
- if (!decoded.from || typeof decoded.from !== 'string') return false;
136
- const senderPublicKey = extractPublicKey(decoded.from);
137
- return verify(signature, data, senderPublicKey);
138
- } catch {
139
- return false;
140
- }
141
- };
142
-
143
- this.router = createMessageRouter(this.relayClient, verifyFn);
144
- await this.router.start();
145
- logger.info('Router started');
146
-
147
- // Initialize trust system
148
- const dataDir = join(homedir(), '.clawiverse');
149
- this.trustSystem = createTrustSystem({
150
- dbPath: join(dataDir, 'trust'),
151
- getPublicKey: async (did: string) => extractPublicKey(did),
152
- });
153
- await this.trustSystem.start();
154
- logger.info('Trust system started');
155
-
156
- // Initialize message queue (LevelDB persistence per CVP-0010 §2.3)
157
- this.queue = new MessageQueue({
158
- dbPath: join(dataDir, 'inbox'),
159
- });
160
- await this.queue.start();
161
- logger.info('Message queue started');
162
-
163
- // Initialize defense middleware
164
- this.defense = new DefenseMiddleware({
165
- trustSystem: this.trustSystem,
166
- storage: this.queue.store,
167
- minTrustScore: 0,
168
- });
169
- logger.info('Defense middleware initialized');
170
-
171
- // Register catch-all handler: defense check + queue persistence
172
- this.router.registerCatchAllHandler(async (envelope) => {
173
- return await this.handleIncomingMessage(envelope);
174
- });
175
-
176
- // Create IPC server
177
- this.server = createServer((socket) => {
178
- this.handleConnection(socket);
179
- });
180
-
181
- this.server.listen(this.socketPath);
182
- logger.info('Daemon listening', { socketPath: this.socketPath });
183
-
184
- console.log(`✓ Clawiverse daemon started`);
185
- console.log(` Socket: ${this.socketPath}`);
186
- console.log(` Relays: ${this.relayClient.getConnectedRelays().join(', ')}`);
187
- console.log(` DID: ${this.identity.did}`);
188
- } catch (error) {
189
- logger.error('Failed to start daemon', error);
190
- throw error;
191
- }
192
- }
193
-
194
- private async handleIncomingMessage(envelope: MessageEnvelope): Promise<MessageEnvelope | void> {
195
- if (!this.defense || !this.queue || !this.trustSystem) return;
196
-
197
- const result = await this.defense.checkMessage(envelope);
198
- if (!result.allowed) {
199
- logger.warn('Message rejected by defense', { id: envelope.id, reason: result.reason });
200
- return;
201
- }
202
-
203
- await this.queue.enqueueInbound(envelope, result.trustScore);
204
-
205
- await this.trustSystem.recordInteraction({
206
- agentDid: envelope.from,
207
- timestamp: Date.now(),
208
- type: 'message',
209
- success: true,
210
- responseTime: 0,
211
- });
212
-
213
- logger.info('Message queued', { id: envelope.id, from: envelope.from });
214
- }
215
-
216
- private handleConnection(socket: Socket): void {
217
- let buffer = '';
218
-
219
- socket.on('data', async (data) => {
220
- buffer += data.toString();
221
- const lines = buffer.split('\n');
222
- buffer = lines.pop() ?? '';
223
-
224
- for (const line of lines) {
225
- if (!line.trim()) continue;
226
- try {
227
- const request: DaemonRequest = JSON.parse(line);
228
- logger.debug('Received request', { command: request.command, id: request.id });
229
- const response = await this.handleRequest(request);
230
- socket.write(JSON.stringify(response) + '\n');
231
- } catch (error) {
232
- const errorResponse: DaemonResponse = {
233
- id: 'unknown',
234
- success: false,
235
- error: (error as Error).message,
236
- };
237
- socket.write(JSON.stringify(errorResponse) + '\n');
238
- }
239
- }
240
-
241
- if (buffer.trim()) {
242
- try {
243
- const request: DaemonRequest = JSON.parse(buffer);
244
- buffer = '';
245
- logger.debug('Received request', { command: request.command, id: request.id });
246
- const response = await this.handleRequest(request);
247
- socket.write(JSON.stringify(response) + '\n');
248
- } catch {
249
- // Not complete JSON yet, keep buffering
250
- }
251
- }
252
- });
253
-
254
- socket.on('error', (error) => {
255
- logger.warn('Socket error', { error: error.message });
256
- });
257
- }
258
-
259
- private async handleRequest(req: DaemonRequest): Promise<DaemonResponse> {
260
- try {
261
- switch (req.command) {
262
- case 'send': return await this.handleSend(req);
263
- case 'discover': return await this.handleDiscover(req);
264
- case 'status': return this.handleStatus(req);
265
- case 'messages': return await this.handleMessages(req);
266
- case 'shutdown':
267
- await this.shutdown();
268
- return { id: req.id, success: true };
269
-
270
- case 'inbox': return await this.handleInbox(req);
271
- case 'get_message': return await this.handleGetMessage(req);
272
- case 'mark_read': return await this.handleMarkRead(req);
273
- case 'delete_message': return await this.handleDeleteMessage(req);
274
- case 'outbox': return await this.handleOutbox(req);
275
- case 'retry_message': return await this.handleRetryMessage(req);
276
-
277
- case 'block': return await this.handleBlock(req);
278
- case 'unblock': return await this.handleUnblock(req);
279
- case 'allowlist': return await this.handleAllowlist(req);
280
-
281
- case 'queue_stats': return await this.handleQueueStats(req);
282
-
283
- default:
284
- return { id: req.id, success: false, error: 'Unknown command' };
285
- }
286
- } catch (error) {
287
- logger.error('Request handler error', { command: req.command, error });
288
- return { id: req.id, success: false, error: (error as Error).message };
289
- }
290
- }
291
-
292
- private async handleSend(req: DaemonRequest): Promise<DaemonResponse> {
293
- const { to, protocol, payload, type } = req.params;
294
-
295
- if (!this.router) {
296
- return { id: req.id, success: false, error: 'Router not initialized' };
297
- }
298
-
299
- const envelope = createEnvelope(
300
- this.identity.did,
301
- to,
302
- type || 'request',
303
- protocol,
304
- payload
305
- );
306
-
307
- const keyPair = importKeyPair({
308
- publicKey: this.identity.publicKey,
309
- privateKey: this.identity.privateKey,
310
- });
311
-
312
- const signedEnvelope = await signEnvelope(envelope, (data) =>
313
- sign(data, keyPair.privateKey)
314
- );
315
-
316
- if (this.queue) {
317
- await this.queue.enqueueOutbound(signedEnvelope);
318
- }
319
-
320
- const response = await this.router.sendMessage(signedEnvelope);
321
-
322
- if (this.queue) {
323
- await this.queue.markOutboundDelivered(signedEnvelope.id);
324
- }
325
-
326
- return {
327
- id: req.id,
328
- success: true,
329
- data: { id: signedEnvelope.id, response: response || null },
330
- };
331
- }
332
-
333
- private async handleDiscover(req: DaemonRequest): Promise<DaemonResponse> {
334
- const { query } = req.params;
335
- if (!this.relayIndex) return { id: req.id, success: false, error: 'Relay index not initialized' };
336
- const results = await this.relayIndex.searchSemantic(query);
337
- return { id: req.id, success: true, data: results };
338
- }
339
-
340
- private handleStatus(req: DaemonRequest): DaemonResponse {
341
- if (!this.relayClient) return { id: req.id, success: false, error: 'Relay client not initialized' };
342
- return {
343
- id: req.id,
344
- success: true,
345
- data: {
346
- running: true,
347
- connectedRelays: this.relayClient.getConnectedRelays(),
348
- peerCount: this.relayClient.getPeerCount(),
349
- did: this.identity.did,
350
- },
351
- };
352
- }
353
-
354
- private async handleMessages(req: DaemonRequest): Promise<DaemonResponse> {
355
- const { limit = 10 } = req.params || {};
356
- if (this.queue) {
357
- const page = await this.queue.getInbox({}, { limit });
358
- return {
359
- id: req.id,
360
- success: true,
361
- data: {
362
- messages: page.messages.map((m) => ({ ...m.envelope, receivedAt: m.receivedAt })),
363
- total: page.total,
364
- },
365
- };
366
- }
367
- return { id: req.id, success: true, data: { messages: [], total: 0 } };
368
- }
369
-
370
- // ─── Queue Handlers ───────────────────────────────────────────────────────
371
-
372
- private async handleInbox(req: DaemonRequest): Promise<DaemonResponse> {
373
- if (!this.queue) return { id: req.id, success: false, error: 'Queue not initialized' };
374
- const { filter, pagination } = req.params || {};
375
- const page = await this.queue.getInbox(filter, pagination);
376
- return { id: req.id, success: true, data: page };
377
- }
378
-
379
- private async handleGetMessage(req: DaemonRequest): Promise<DaemonResponse> {
380
- if (!this.queue) return { id: req.id, success: false, error: 'Queue not initialized' };
381
- const { id } = req.params;
382
- const msg = await this.queue.getMessage(id);
383
- if (!msg) return { id: req.id, success: false, error: 'Message not found' };
384
- return { id: req.id, success: true, data: msg };
385
- }
386
-
387
- private async handleMarkRead(req: DaemonRequest): Promise<DaemonResponse> {
388
- if (!this.queue) return { id: req.id, success: false, error: 'Queue not initialized' };
389
- await this.queue.markAsRead(req.params.id);
390
- return { id: req.id, success: true };
391
- }
392
-
393
- private async handleDeleteMessage(req: DaemonRequest): Promise<DaemonResponse> {
394
- if (!this.queue) return { id: req.id, success: false, error: 'Queue not initialized' };
395
- await this.queue.deleteMessage(req.params.id);
396
- return { id: req.id, success: true };
397
- }
398
-
399
- private async handleOutbox(req: DaemonRequest): Promise<DaemonResponse> {
400
- if (!this.queue) return { id: req.id, success: false, error: 'Queue not initialized' };
401
- const page = await this.queue.getOutbox(req.params?.pagination);
402
- return { id: req.id, success: true, data: page };
403
- }
404
-
405
- private async handleRetryMessage(req: DaemonRequest): Promise<DaemonResponse> {
406
- if (!this.queue) return { id: req.id, success: false, error: 'Queue not initialized' };
407
- await this.queue.retryMessage(req.params.id);
408
- return { id: req.id, success: true };
409
- }
410
-
411
- // ─── Defense Handlers ─────────────────────────────────────────────────────
412
-
413
- private async handleBlock(req: DaemonRequest): Promise<DaemonResponse> {
414
- if (!this.defense) return { id: req.id, success: false, error: 'Defense not initialized' };
415
- const { did, reason = 'Blocked by user' } = req.params;
416
- await this.defense.blockAgent(did, reason, this.identity.did);
417
- return { id: req.id, success: true };
418
- }
419
-
420
- private async handleUnblock(req: DaemonRequest): Promise<DaemonResponse> {
421
- if (!this.defense) return { id: req.id, success: false, error: 'Defense not initialized' };
422
- await this.defense.unblockAgent(req.params.did);
423
- return { id: req.id, success: true };
424
- }
425
-
426
- private async handleAllowlist(req: DaemonRequest): Promise<DaemonResponse> {
427
- if (!this.defense || !this.queue) return { id: req.id, success: false, error: 'Defense not initialized' };
428
- const { action, did, note } = req.params;
429
- switch (action) {
430
- case 'add':
431
- await this.defense.allowAgent(did, note);
432
- return { id: req.id, success: true };
433
- case 'remove':
434
- await this.defense.removeFromAllowlist(did);
435
- return { id: req.id, success: true };
436
- case 'list': {
437
- const entries = await this.queue.store.listAllowed();
438
- return { id: req.id, success: true, data: entries };
439
- }
440
- default:
441
- return { id: req.id, success: false, error: `Unknown allowlist action: ${action}` };
442
- }
443
- }
444
-
445
- private async handleQueueStats(req: DaemonRequest): Promise<DaemonResponse> {
446
- if (!this.queue) return { id: req.id, success: false, error: 'Queue not initialized' };
447
- const stats = await this.queue.getStats();
448
- return { id: req.id, success: true, data: stats };
449
- }
450
-
451
- async shutdown(): Promise<void> {
452
- logger.info('Shutting down daemon');
453
-
454
- if (this.router) {
455
- await this.router.stop();
456
- this.router = null;
457
- }
458
-
459
- if (this.queue) {
460
- await this.queue.stop();
461
- this.queue = null;
462
- }
463
-
464
- if (this.trustSystem) {
465
- await this.trustSystem.stop();
466
- this.trustSystem = null;
467
- }
468
-
469
- if (this.relayClient) {
470
- await this.relayClient.stop();
471
- this.relayClient = null;
472
- }
473
-
474
- if (this.server) {
475
- this.server.close();
476
- this.server = null;
477
- }
478
-
479
- logger.info('Daemon stopped');
480
- }
481
- }
package/src/index.ts DELETED
@@ -1,61 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // Polyfill for Node.js < v22.0.0
4
- if (!Promise.withResolvers) {
5
- Promise.withResolvers = function <T>() {
6
- let resolve: (value: T | PromiseLike<T>) => void;
7
- let reject: (reason?: any) => void;
8
- const promise = new Promise<T>((res, rej) => {
9
- resolve = res;
10
- reject = rej;
11
- });
12
- return { promise, resolve: resolve!, reject: reject! };
13
- };
14
- }
15
-
16
- import { Command } from 'commander';
17
- import { createRequire } from 'module';
18
- import { registerInitCommand } from './commands/init.js';
19
- import { registerJoinCommand } from './commands/join.js';
20
- import { registerDiscoverCommand } from './commands/discover.js';
21
- import { registerSendCommand } from './commands/send.js';
22
- import { registerStatusCommand } from './commands/status.js';
23
- import { registerIdentityCommand } from './commands/identity.js';
24
- import { registerCardCommand } from './commands/card.js';
25
- import { createTrustCommand } from './commands/trust.js';
26
- import { registerDaemonCommand } from './commands/daemon.js';
27
- import { createInboxCommand } from './commands/inbox.js';
28
- import { registerStopCommand } from './commands/stop.js';
29
- import { registerAskCommand } from './commands/ask.js';
30
- import { registerServeCommand } from './commands/serve.js';
31
- import { registerPeersCommand } from './commands/peers.js';
32
-
33
- const require = createRequire(import.meta.url);
34
- const { version } = require('../package.json');
35
-
36
- const program = new Command();
37
-
38
- const binName = process.argv[1]?.includes('hw1') ? 'hw1' : 'clawiverse';
39
-
40
- program
41
- .name(binName)
42
- .description('CLI tool for decentralized AI agent communication')
43
- .version(version);
44
-
45
- // Register commands
46
- registerInitCommand(program);
47
- registerJoinCommand(program);
48
- registerDiscoverCommand(program);
49
- registerSendCommand(program);
50
- registerAskCommand(program);
51
- registerServeCommand(program);
52
- registerPeersCommand(program);
53
- registerStatusCommand(program);
54
- registerIdentityCommand(program);
55
- registerCardCommand(program);
56
- registerDaemonCommand(program);
57
- registerStopCommand(program);
58
- program.addCommand(createTrustCommand());
59
- program.addCommand(createInboxCommand());
60
-
61
- program.parse();
package/src/ui.ts DELETED
@@ -1,38 +0,0 @@
1
- import chalk from 'chalk';
2
- import ora, { Ora } from 'ora';
3
-
4
- export function success(message: string): void {
5
- console.log(chalk.green('✓'), message);
6
- }
7
-
8
- export function error(message: string): void {
9
- console.log(chalk.red('✗'), message);
10
- }
11
-
12
- export function info(message: string): void {
13
- console.log(chalk.blue('ℹ'), message);
14
- }
15
-
16
- export function warn(message: string): void {
17
- console.log(chalk.yellow('⚠'), message);
18
- }
19
-
20
- export function spinner(text: string): Ora {
21
- return ora(text).start();
22
- }
23
-
24
- export function printHeader(title: string): void {
25
- console.log();
26
- console.log(chalk.bold.cyan(title));
27
- console.log(chalk.gray('─'.repeat(title.length)));
28
- console.log();
29
- }
30
-
31
- export function printKeyValue(key: string, value: string): void {
32
- console.log(chalk.gray(`${key}:`), chalk.white(value));
33
- }
34
-
35
- export function printSection(title: string): void {
36
- console.log();
37
- console.log(chalk.bold(title));
38
- }