@highway1/cli 0.1.43 → 0.1.45

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.
@@ -0,0 +1,45 @@
1
+ import { Command } from 'commander';
2
+ import { getIdentity, getAgentCard, getBootstrapPeers } from '../config.js';
3
+ import { error, printHeader, printKeyValue, printSection } from '../ui.js';
4
+
5
+ export function registerStatusCommand(program: Command): void {
6
+ program
7
+ .command('status')
8
+ .description('Show current status')
9
+ .action(async () => {
10
+ try {
11
+ printHeader('Clawiverse Status');
12
+
13
+ const identity = getIdentity();
14
+ const card = getAgentCard();
15
+ const bootstrapPeers = getBootstrapPeers();
16
+
17
+ if (!identity) {
18
+ error('No identity configured. Run "hw1 init" first.');
19
+ process.exit(1);
20
+ }
21
+
22
+ printSection('Identity');
23
+ printKeyValue('DID', identity.did);
24
+ printKeyValue('Public Key', identity.publicKey.substring(0, 16) + '...');
25
+
26
+ if (card) {
27
+ printSection('Agent Card');
28
+ printKeyValue('Name', card.name);
29
+ printKeyValue('Description', card.description);
30
+ printKeyValue('Capabilities', card.capabilities.join(', ') || 'None');
31
+ }
32
+
33
+ printSection('Network');
34
+ printKeyValue(
35
+ 'Bootstrap Peers',
36
+ bootstrapPeers.length > 0 ? bootstrapPeers.join(', ') : 'None configured'
37
+ );
38
+
39
+ console.log();
40
+ } catch (err) {
41
+ error(`Failed to show status: ${(err as Error).message}`);
42
+ process.exit(1);
43
+ }
44
+ });
45
+ }
@@ -0,0 +1,215 @@
1
+ /**
2
+ * Trust Command - Manage trust scores and endorsements
3
+ */
4
+
5
+ import { Command } from 'commander';
6
+ import { getConfig, getIdentity } from '../config.js';
7
+ import { createLogger, createTrustSystem } from '@highway1/core';
8
+ import { homedir } from 'os';
9
+ import { join } from 'path';
10
+
11
+ const logger = createLogger('cli:trust');
12
+
13
+ export function createTrustCommand(): Command {
14
+ const trust = new Command('trust')
15
+ .description('Manage trust scores and endorsements');
16
+
17
+ // Show trust score
18
+ trust
19
+ .command('show')
20
+ .description('Show trust score for an agent')
21
+ .argument('<did>', 'Agent DID')
22
+ .action(async (did: string) => {
23
+ try {
24
+ const dataDir = join(homedir(), '.clawiverse');
25
+ const trustSystem = createTrustSystem({
26
+ dbPath: `${dataDir}/trust`,
27
+ getPublicKey: async () => new Uint8Array(), // Placeholder
28
+ });
29
+
30
+ await trustSystem.start();
31
+
32
+ const score = await trustSystem.getTrustScore(did);
33
+ const endorsements = await trustSystem.getEndorsements(did);
34
+
35
+ console.log('\nšŸ“Š Trust Score for', did);
36
+ console.log('─'.repeat(60));
37
+ console.log(`Interaction Score: ${(score.interactionScore * 100).toFixed(1)}%`);
38
+ console.log(`Endorsements: ${score.endorsements}`);
39
+ console.log(`Completion Rate: ${(score.completionRate * 100).toFixed(1)}%`);
40
+ console.log(`Response Time: ${score.responseTime.toFixed(0)}ms`);
41
+ console.log(`Uptime: ${(score.uptime * 100).toFixed(1)}%`);
42
+ console.log(`Last Updated: ${new Date(score.lastUpdated).toLocaleString()}`);
43
+
44
+ if (endorsements.length > 0) {
45
+ console.log('\n✨ Endorsements:');
46
+ for (const e of endorsements) {
47
+ console.log(` • From: ${e.from}`);
48
+ console.log(` Score: ${(e.score * 100).toFixed(0)}%`);
49
+ console.log(` Reason: ${e.reason}`);
50
+ console.log(` Date: ${new Date(e.timestamp).toLocaleDateString()}`);
51
+ }
52
+ }
53
+
54
+ await trustSystem.stop();
55
+ } catch (error) {
56
+ logger.error('Failed to show trust score', error);
57
+ console.error('Error:', error instanceof Error ? error.message : error);
58
+ process.exit(1);
59
+ }
60
+ });
61
+
62
+ // Endorse an agent
63
+ trust
64
+ .command('endorse')
65
+ .description('Endorse an agent')
66
+ .argument('<did>', 'Agent DID to endorse')
67
+ .option('-s, --score <score>', 'Endorsement score (0-1)', '0.8')
68
+ .option('-r, --reason <reason>', 'Reason for endorsement', 'Good collaboration')
69
+ .action(async (did: string, options: { score: string; reason: string }) => {
70
+ try {
71
+ const dataDir = join(homedir(), '.clawiverse');
72
+ const trustSystem = createTrustSystem({
73
+ dbPath: `${dataDir}/trust`,
74
+ getPublicKey: async () => new Uint8Array(), // Placeholder
75
+ });
76
+
77
+ await trustSystem.start();
78
+
79
+ const score = parseFloat(options.score);
80
+ if (isNaN(score) || score < 0 || score > 1) {
81
+ throw new Error('Score must be between 0 and 1');
82
+ }
83
+
84
+ // Load identity for signing
85
+ const identity = getIdentity();
86
+ if (!identity) {
87
+ throw new Error('No identity found. Run "hw1 init" first.');
88
+ }
89
+
90
+ const { importKeyPair } = await import('@highway1/core');
91
+ const keyPair = importKeyPair({
92
+ publicKey: identity.publicKey,
93
+ privateKey: identity.privateKey,
94
+ });
95
+
96
+ const endorsement = await trustSystem.endorse(
97
+ identity.did,
98
+ did,
99
+ score,
100
+ options.reason,
101
+ keyPair.sign
102
+ );
103
+
104
+ console.log('\nāœ… Endorsement created');
105
+ console.log(`From: ${endorsement.from}`);
106
+ console.log(`To: ${endorsement.to}`);
107
+ console.log(`Score: ${(endorsement.score * 100).toFixed(0)}%`);
108
+ console.log(`Reason: ${endorsement.reason}`);
109
+
110
+ await trustSystem.stop();
111
+ } catch (error) {
112
+ logger.error('Failed to endorse agent', error);
113
+ console.error('Error:', error instanceof Error ? error.message : error);
114
+ process.exit(1);
115
+ }
116
+ });
117
+
118
+ // Show interaction history
119
+ trust
120
+ .command('history')
121
+ .description('Show interaction history with an agent')
122
+ .argument('<did>', 'Agent DID')
123
+ .option('-l, --limit <limit>', 'Number of interactions to show', '10')
124
+ .action(async (did: string, options: { limit: string }) => {
125
+ try {
126
+ const dataDir = join(homedir(), '.clawiverse');
127
+ const trustSystem = createTrustSystem({
128
+ dbPath: `${dataDir}/trust`,
129
+ getPublicKey: async () => new Uint8Array(), // Placeholder
130
+ });
131
+
132
+ await trustSystem.start();
133
+
134
+ const limit = parseInt(options.limit, 10);
135
+ const history = await trustSystem.getHistory(did, limit);
136
+
137
+ console.log(`\nšŸ“œ Interaction History with ${did}`);
138
+ console.log('─'.repeat(60));
139
+
140
+ if (history.length === 0) {
141
+ console.log('No interactions recorded');
142
+ } else {
143
+ for (const interaction of history) {
144
+ const status = interaction.success ? 'āœ…' : 'āŒ';
145
+ console.log(`${status} ${interaction.type} - ${new Date(interaction.timestamp).toLocaleString()}`);
146
+ console.log(` Response time: ${interaction.responseTime}ms`);
147
+ if (interaction.rating) {
148
+ console.log(` Rating: ${'⭐'.repeat(interaction.rating)}`);
149
+ }
150
+ if (interaction.feedback) {
151
+ console.log(` Feedback: ${interaction.feedback}`);
152
+ }
153
+ }
154
+ }
155
+
156
+ await trustSystem.stop();
157
+ } catch (error) {
158
+ logger.error('Failed to show history', error);
159
+ console.error('Error:', error instanceof Error ? error.message : error);
160
+ process.exit(1);
161
+ }
162
+ });
163
+
164
+ // Show trust statistics
165
+ trust
166
+ .command('stats')
167
+ .description('Show local trust statistics')
168
+ .action(async () => {
169
+ try {
170
+ const dataDir = join(homedir(), '.clawiverse');
171
+ const trustSystem = createTrustSystem({
172
+ dbPath: `${dataDir}/trust`,
173
+ getPublicKey: async () => new Uint8Array(), // Placeholder
174
+ });
175
+
176
+ await trustSystem.start();
177
+
178
+ // Get all agents from interaction history
179
+ const { InteractionHistory } = await import('@highway1/core');
180
+ const history = new InteractionHistory(`${dataDir}/trust/interactions`);
181
+ await history.open();
182
+
183
+ const agents = await history.getAllAgents();
184
+
185
+ console.log('\nšŸ“Š Trust Statistics');
186
+ console.log('─'.repeat(60));
187
+ console.log(`Total agents tracked: ${agents.length}`);
188
+
189
+ if (agents.length > 0) {
190
+ console.log('\nTop agents by trust score:');
191
+ const scores = await Promise.all(
192
+ agents.map(async (did) => ({
193
+ did,
194
+ score: await trustSystem.getTrustScore(did),
195
+ }))
196
+ );
197
+
198
+ scores.sort((a, b) => b.score.interactionScore - a.score.interactionScore);
199
+
200
+ for (const { did, score } of scores.slice(0, 5)) {
201
+ console.log(` ${(score.interactionScore * 100).toFixed(0)}% - ${did}`);
202
+ }
203
+ }
204
+
205
+ await history.close();
206
+ await trustSystem.stop();
207
+ } catch (error) {
208
+ logger.error('Failed to show stats', error);
209
+ console.error('Error:', error instanceof Error ? error.message : error);
210
+ process.exit(1);
211
+ }
212
+ });
213
+
214
+ return trust;
215
+ }
package/src/config.ts ADDED
@@ -0,0 +1,74 @@
1
+ import Conf from 'conf';
2
+ import { homedir } from 'os';
3
+ import { join } from 'path';
4
+
5
+ export interface ClawiverseConfig {
6
+ identity?: {
7
+ did: string;
8
+ publicKey: string;
9
+ privateKey: string;
10
+ };
11
+ network?: {
12
+ bootstrapPeers: string[];
13
+ listenAddresses: string[];
14
+ };
15
+ agentCard?: {
16
+ name: string;
17
+ description: string;
18
+ capabilities: string[];
19
+ };
20
+ }
21
+
22
+ const CLAWIVERSE_HOME = process.env.CLAWIVERSE_HOME || join(homedir(), '.clawiverse');
23
+
24
+ const config = new Conf<ClawiverseConfig>({
25
+ projectName: 'clawiverse',
26
+ cwd: CLAWIVERSE_HOME,
27
+ });
28
+
29
+ export function getConfig(): ClawiverseConfig {
30
+ return config.store;
31
+ }
32
+
33
+ export function setConfig(key: string, value: unknown): void {
34
+ config.set(key, value);
35
+ }
36
+
37
+ export function getConfigValue<T>(key: string): T | undefined {
38
+ return config.get(key) as T | undefined;
39
+ }
40
+
41
+ export function hasIdentity(): boolean {
42
+ return config.has('identity');
43
+ }
44
+
45
+ export function getIdentity() {
46
+ return config.get('identity');
47
+ }
48
+
49
+ export function setIdentity(identity: ClawiverseConfig['identity']): void {
50
+ config.set('identity', identity);
51
+ }
52
+
53
+ export function getAgentCard() {
54
+ return config.get('agentCard');
55
+ }
56
+
57
+ export function setAgentCard(card: ClawiverseConfig['agentCard']): void {
58
+ config.set('agentCard', card);
59
+ }
60
+
61
+ // Default public bootstrap nodes (update after deployment)
62
+ const DEFAULT_BOOTSTRAP_PEERS: string[] = [
63
+ '/dns4/hw1.woowot.com/tcp/4001/p2p/12D3KooWH9R8d4bhzpfyWi3aY5wLBsmLp1LU8ZGk9V7rK17sfzaW',
64
+ ];
65
+
66
+ export function getBootstrapPeers(): string[] {
67
+ const userPeers = config.get('network.bootstrapPeers') as string[] | undefined;
68
+ if (userPeers && userPeers.length > 0) return userPeers;
69
+ return DEFAULT_BOOTSTRAP_PEERS;
70
+ }
71
+
72
+ export function setBootstrapPeers(peers: string[]): void {
73
+ config.set('network.bootstrapPeers', peers);
74
+ }
@@ -0,0 +1,68 @@
1
+ import { connect, Socket } from 'net';
2
+ import { createLogger } from '@highway1/core';
3
+
4
+ const logger = createLogger('daemon-client');
5
+
6
+ export class DaemonClient {
7
+ private socketPath: string;
8
+
9
+ constructor(socketPath: string = '/tmp/clawiverse.sock') {
10
+ this.socketPath = socketPath;
11
+ }
12
+
13
+ async send(command: string, params: any): Promise<any> {
14
+ return new Promise((resolve, reject) => {
15
+ const socket = connect(this.socketPath);
16
+ const requestId = `req_${Date.now()}_${Math.random().toString(36).slice(2)}`;
17
+
18
+ let responseReceived = false;
19
+
20
+ socket.on('connect', () => {
21
+ const request = { id: requestId, command, params };
22
+ logger.debug('Sending request', { command, id: requestId });
23
+ socket.write(JSON.stringify(request));
24
+ });
25
+
26
+ socket.on('data', (data) => {
27
+ try {
28
+ const response = JSON.parse(data.toString());
29
+ responseReceived = true;
30
+ socket.end();
31
+
32
+ logger.debug('Received response', { success: response.success, id: response.id });
33
+
34
+ if (response.success) {
35
+ resolve(response.data);
36
+ } else {
37
+ reject(new Error(response.error));
38
+ }
39
+ } catch (error) {
40
+ reject(new Error(`Failed to parse response: ${(error as Error).message}`));
41
+ }
42
+ });
43
+
44
+ socket.on('error', (err) => {
45
+ if (!responseReceived) {
46
+ logger.debug('Socket error', { error: err.message });
47
+ reject(err);
48
+ }
49
+ });
50
+
51
+ socket.setTimeout(30000, () => {
52
+ if (!responseReceived) {
53
+ socket.destroy();
54
+ reject(new Error('Request timeout'));
55
+ }
56
+ });
57
+ });
58
+ }
59
+
60
+ async isDaemonRunning(): Promise<boolean> {
61
+ try {
62
+ await this.send('status', {});
63
+ return true;
64
+ } catch {
65
+ return false;
66
+ }
67
+ }
68
+ }
@@ -0,0 +1,267 @@
1
+ import { createServer, Socket } from 'net';
2
+ import type { Server } from 'net';
3
+ import {
4
+ createNode,
5
+ importKeyPair,
6
+ createMessageRouter,
7
+ createDHTOperations,
8
+ sign,
9
+ verify,
10
+ extractPublicKey,
11
+ createEnvelope,
12
+ signEnvelope,
13
+ type ClawiverseNode,
14
+ type MessageRouter,
15
+ type DHTOperations,
16
+ } from '@highway1/core';
17
+ import { createLogger } from '@highway1/core';
18
+ import { getIdentity, getBootstrapPeers } from '../config.js';
19
+
20
+ const logger = createLogger('daemon');
21
+
22
+ interface DaemonRequest {
23
+ id: string;
24
+ command: 'send' | 'discover' | 'status' | 'shutdown';
25
+ params: any;
26
+ }
27
+
28
+ interface DaemonResponse {
29
+ id: string;
30
+ success: boolean;
31
+ data?: any;
32
+ error?: string;
33
+ }
34
+
35
+ export class ClawDaemon {
36
+ private node: ClawiverseNode | null = null;
37
+ private router: MessageRouter | null = null;
38
+ private dht: DHTOperations | null = null;
39
+ private server: Server | null = null;
40
+ private socketPath: string;
41
+ private identity: any;
42
+ private bootstrapPeers: string[];
43
+
44
+ constructor(socketPath: string = '/tmp/clawiverse.sock') {
45
+ this.socketPath = socketPath;
46
+ this.identity = getIdentity();
47
+ this.bootstrapPeers = getBootstrapPeers();
48
+
49
+ if (!this.identity) {
50
+ throw new Error('No identity found. Run "clawiverse init" first.');
51
+ }
52
+ }
53
+
54
+ async start(): Promise<void> {
55
+ try {
56
+ logger.info('Starting Clawiverse daemon', { socketPath: this.socketPath });
57
+
58
+ // Initialize node once (eliminates 4s overhead per command)
59
+ const keyPair = importKeyPair({
60
+ publicKey: this.identity.publicKey,
61
+ privateKey: this.identity.privateKey,
62
+ });
63
+
64
+ this.node = await createNode({
65
+ keyPair,
66
+ bootstrapPeers: this.bootstrapPeers,
67
+ enableDHT: true,
68
+ });
69
+
70
+ await this.node.start();
71
+ logger.info('Node started', { peerId: this.node.getPeerId() });
72
+
73
+ // Initialize DHT and router
74
+ this.dht = createDHTOperations(this.node.libp2p);
75
+
76
+ const verifyFn = async (signature: Uint8Array, data: Uint8Array): Promise<boolean> => {
77
+ try {
78
+ const decoded = JSON.parse(new TextDecoder().decode(data)) as { from?: string };
79
+ if (!decoded.from || typeof decoded.from !== 'string') return false;
80
+ const senderPublicKey = extractPublicKey(decoded.from);
81
+ return verify(signature, data, senderPublicKey);
82
+ } catch {
83
+ return false;
84
+ }
85
+ };
86
+
87
+ this.router = createMessageRouter(
88
+ this.node.libp2p,
89
+ verifyFn,
90
+ this.dht,
91
+ this.bootstrapPeers
92
+ );
93
+
94
+ await this.router.start();
95
+ logger.info('Router started');
96
+
97
+ // Create IPC server
98
+ this.server = createServer((socket) => {
99
+ this.handleConnection(socket);
100
+ });
101
+
102
+ this.server.listen(this.socketPath);
103
+ logger.info('Daemon listening', { socketPath: this.socketPath });
104
+
105
+ console.log(`āœ“ Clawiverse daemon started`);
106
+ console.log(` Socket: ${this.socketPath}`);
107
+ console.log(` Peer ID: ${this.node.getPeerId()}`);
108
+ console.log(` DID: ${this.identity.did}`);
109
+ } catch (error) {
110
+ logger.error('Failed to start daemon', error);
111
+ throw error;
112
+ }
113
+ }
114
+
115
+ private handleConnection(socket: Socket): void {
116
+ socket.on('data', async (data) => {
117
+ try {
118
+ const request: DaemonRequest = JSON.parse(data.toString());
119
+ logger.debug('Received request', { command: request.command, id: request.id });
120
+
121
+ const response = await this.handleRequest(request);
122
+ socket.write(JSON.stringify(response) + '\n');
123
+ } catch (error) {
124
+ const errorResponse: DaemonResponse = {
125
+ id: 'unknown',
126
+ success: false,
127
+ error: (error as Error).message,
128
+ };
129
+ socket.write(JSON.stringify(errorResponse) + '\n');
130
+ }
131
+ });
132
+
133
+ socket.on('error', (error) => {
134
+ logger.warn('Socket error', { error: error.message });
135
+ });
136
+ }
137
+
138
+ private async handleRequest(req: DaemonRequest): Promise<DaemonResponse> {
139
+ try {
140
+ switch (req.command) {
141
+ case 'send':
142
+ return await this.handleSend(req);
143
+
144
+ case 'discover':
145
+ return await this.handleDiscover(req);
146
+
147
+ case 'status':
148
+ return this.handleStatus(req);
149
+
150
+ case 'shutdown':
151
+ await this.shutdown();
152
+ return { id: req.id, success: true };
153
+
154
+ default:
155
+ return { id: req.id, success: false, error: 'Unknown command' };
156
+ }
157
+ } catch (error) {
158
+ logger.error('Request handler error', { command: req.command, error });
159
+ return { id: req.id, success: false, error: (error as Error).message };
160
+ }
161
+ }
162
+
163
+ private async handleSend(req: DaemonRequest): Promise<DaemonResponse> {
164
+ const { to, protocol, payload, type, peer } = req.params;
165
+
166
+ if (!this.router) {
167
+ return { id: req.id, success: false, error: 'Router not initialized' };
168
+ }
169
+
170
+ // Create and sign envelope
171
+ const envelope = createEnvelope(
172
+ this.identity.did,
173
+ to,
174
+ type || 'request',
175
+ protocol,
176
+ payload
177
+ );
178
+
179
+ const keyPair = importKeyPair({
180
+ publicKey: this.identity.publicKey,
181
+ privateKey: this.identity.privateKey,
182
+ });
183
+
184
+ const signedEnvelope = await signEnvelope(envelope, (data) =>
185
+ sign(data, keyPair.privateKey)
186
+ );
187
+
188
+ // Build peer hint if provided
189
+ let peerHint = undefined;
190
+ if (peer) {
191
+ const parts = peer.split('/p2p/');
192
+ if (parts.length === 2) {
193
+ peerHint = {
194
+ peerId: parts[1],
195
+ multiaddrs: [peer],
196
+ };
197
+ }
198
+ }
199
+
200
+ // Send message
201
+ const response = await this.router.sendMessage(signedEnvelope, peerHint);
202
+
203
+ return {
204
+ id: req.id,
205
+ success: true,
206
+ data: {
207
+ id: signedEnvelope.id,
208
+ response: response || null,
209
+ },
210
+ };
211
+ }
212
+
213
+ private async handleDiscover(req: DaemonRequest): Promise<DaemonResponse> {
214
+ const { query } = req.params;
215
+
216
+ if (!this.dht) {
217
+ return { id: req.id, success: false, error: 'DHT not initialized' };
218
+ }
219
+
220
+ const results = await this.dht.searchSemantic(query);
221
+
222
+ return {
223
+ id: req.id,
224
+ success: true,
225
+ data: results,
226
+ };
227
+ }
228
+
229
+ private handleStatus(req: DaemonRequest): DaemonResponse {
230
+ if (!this.node) {
231
+ return { id: req.id, success: false, error: 'Node not initialized' };
232
+ }
233
+
234
+ return {
235
+ id: req.id,
236
+ success: true,
237
+ data: {
238
+ running: true,
239
+ peerId: this.node.getPeerId(),
240
+ did: this.identity.did,
241
+ multiaddrs: this.node.getMultiaddrs(),
242
+ bootstrapPeers: this.bootstrapPeers,
243
+ },
244
+ };
245
+ }
246
+
247
+ async shutdown(): Promise<void> {
248
+ logger.info('Shutting down daemon');
249
+
250
+ if (this.router) {
251
+ await this.router.stop();
252
+ this.router = null;
253
+ }
254
+
255
+ if (this.node) {
256
+ await this.node.stop();
257
+ this.node = null;
258
+ }
259
+
260
+ if (this.server) {
261
+ this.server.close();
262
+ this.server = null;
263
+ }
264
+
265
+ logger.info('Daemon stopped');
266
+ }
267
+ }