@highway1/cli 0.1.44 → 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.
- package/dist/index.js +742 -162
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/commands/daemon.ts +207 -0
- package/src/commands/join.ts +36 -6
- package/src/commands/send.ts +149 -10
- package/src/daemon/client.ts +68 -0
- package/src/daemon/server.ts +267 -0
- package/src/index.ts +2 -0
|
@@ -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
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -23,6 +23,7 @@ import { registerStatusCommand } from './commands/status.js';
|
|
|
23
23
|
import { registerIdentityCommand } from './commands/identity.js';
|
|
24
24
|
import { registerCardCommand } from './commands/card.js';
|
|
25
25
|
import { createTrustCommand } from './commands/trust.js';
|
|
26
|
+
import { registerDaemonCommand } from './commands/daemon.js';
|
|
26
27
|
|
|
27
28
|
const require = createRequire(import.meta.url);
|
|
28
29
|
const { version } = require('../package.json');
|
|
@@ -44,6 +45,7 @@ registerSendCommand(program);
|
|
|
44
45
|
registerStatusCommand(program);
|
|
45
46
|
registerIdentityCommand(program);
|
|
46
47
|
registerCardCommand(program);
|
|
48
|
+
registerDaemonCommand(program);
|
|
47
49
|
program.addCommand(createTrustCommand());
|
|
48
50
|
|
|
49
51
|
program.parse();
|