@highway1/cli 0.1.9 → 0.1.12
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/bin/clawiverse.js +2 -0
- package/dist/index.js +24 -18
- package/dist/index.js.map +1 -1
- package/package.json +21 -9
- package/src/commands/card.ts +82 -0
- package/src/commands/discover.ts +168 -0
- package/src/commands/identity.ts +37 -0
- package/src/commands/init.ts +79 -0
- package/src/commands/join.ts +176 -0
- package/src/commands/send.ts +137 -0
- package/src/commands/status.ts +45 -0
- package/src/commands/trust.ts +215 -0
- package/src/config.ts +74 -0
- package/src/index.ts +49 -0
- package/src/ui.ts +38 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import {
|
|
3
|
+
createNode,
|
|
4
|
+
importKeyPair,
|
|
5
|
+
createDHTOperations,
|
|
6
|
+
} from '@clawiverse/core';
|
|
7
|
+
import { getIdentity, getBootstrapPeers } from '../config.js';
|
|
8
|
+
import { error, spinner, printHeader, info, success } from '../ui.js';
|
|
9
|
+
import Table from 'cli-table3';
|
|
10
|
+
|
|
11
|
+
export function registerDiscoverCommand(program: Command): void {
|
|
12
|
+
program
|
|
13
|
+
.command('discover')
|
|
14
|
+
.description('Discover agents on the network')
|
|
15
|
+
.option('--capability <capability>', 'Filter by capability')
|
|
16
|
+
.option('--did <did>', 'Query specific DID')
|
|
17
|
+
.option('--query <text>', 'Natural language query (e.g., "translate Japanese")')
|
|
18
|
+
.option('--min-trust <score>', 'Minimum trust score (0-1)')
|
|
19
|
+
.option('--language <lang>', 'Filter by language')
|
|
20
|
+
.option('--limit <number>', 'Maximum number of results', '10')
|
|
21
|
+
.option('--bootstrap <peers...>', 'Bootstrap peer addresses')
|
|
22
|
+
.action(async (options) => {
|
|
23
|
+
try {
|
|
24
|
+
printHeader('Discover Agents');
|
|
25
|
+
|
|
26
|
+
const identity = getIdentity();
|
|
27
|
+
|
|
28
|
+
if (!identity) {
|
|
29
|
+
error('No identity found. Run "hw1 init" first.');
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const spin = spinner('Starting node...');
|
|
34
|
+
|
|
35
|
+
const keyPair = importKeyPair({
|
|
36
|
+
publicKey: identity.publicKey,
|
|
37
|
+
privateKey: identity.privateKey,
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const bootstrapPeers = options.bootstrap || getBootstrapPeers();
|
|
41
|
+
|
|
42
|
+
const node = await createNode({
|
|
43
|
+
keyPair,
|
|
44
|
+
bootstrapPeers,
|
|
45
|
+
enableDHT: true,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
await node.start();
|
|
49
|
+
|
|
50
|
+
spin.text = 'Waiting for DHT peers...';
|
|
51
|
+
// Wait until connected to at least one peer, or timeout after 15s
|
|
52
|
+
await new Promise<void>((resolve) => {
|
|
53
|
+
const timeout = setTimeout(resolve, 15000);
|
|
54
|
+
node.libp2p.addEventListener('peer:connect', () => {
|
|
55
|
+
clearTimeout(timeout);
|
|
56
|
+
setTimeout(resolve, 1000); // brief settle time after first connection
|
|
57
|
+
}, { once: true });
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
spin.text = 'Querying DHT...';
|
|
61
|
+
|
|
62
|
+
const dht = createDHTOperations(node.libp2p);
|
|
63
|
+
|
|
64
|
+
if (options.did) {
|
|
65
|
+
const card = await dht.queryAgentCard(options.did);
|
|
66
|
+
|
|
67
|
+
if (card) {
|
|
68
|
+
spin.succeed('Agent found!');
|
|
69
|
+
console.log();
|
|
70
|
+
info(`DID: ${card.did}`);
|
|
71
|
+
info(`Name: ${card.name}`);
|
|
72
|
+
info(`Description: ${card.description}`);
|
|
73
|
+
info(`Version: ${card.version}`);
|
|
74
|
+
info(`Capabilities: ${card.capabilities.join(', ') || '(none)'}`);
|
|
75
|
+
info(`Peer ID: ${card.peerId || '(unknown)'}`);
|
|
76
|
+
info(`Endpoints: ${card.endpoints.length > 0 ? card.endpoints.join('\n ') : '(none)'}`);
|
|
77
|
+
info(`Timestamp: ${new Date(card.timestamp).toISOString()}`);
|
|
78
|
+
} else {
|
|
79
|
+
spin.fail('Agent not found');
|
|
80
|
+
}
|
|
81
|
+
} else if (options.capability || options.query) {
|
|
82
|
+
// Semantic search
|
|
83
|
+
const query: any = {
|
|
84
|
+
limit: parseInt(options.limit, 10),
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
if (options.query) {
|
|
88
|
+
query.text = options.query;
|
|
89
|
+
} else if (options.capability) {
|
|
90
|
+
query.capability = options.capability;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (options.minTrust) {
|
|
94
|
+
query.filters = {
|
|
95
|
+
minTrustScore: parseFloat(options.minTrust),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (options.language) {
|
|
100
|
+
query.filters = query.filters || {};
|
|
101
|
+
query.filters.language = options.language;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const cards = await dht.searchSemantic(query);
|
|
105
|
+
|
|
106
|
+
spin.succeed(`Found ${cards.length} agents`);
|
|
107
|
+
|
|
108
|
+
if (cards.length > 0) {
|
|
109
|
+
const table = new Table({
|
|
110
|
+
head: ['DID', 'Name', 'Capabilities', 'Trust'],
|
|
111
|
+
colWidths: [40, 20, 35, 10],
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
for (const card of cards) {
|
|
115
|
+
const capabilities = Array.isArray(card.capabilities)
|
|
116
|
+
? card.capabilities.map((cap: any) =>
|
|
117
|
+
typeof cap === 'string' ? cap : cap.name
|
|
118
|
+
).join(', ')
|
|
119
|
+
: '';
|
|
120
|
+
|
|
121
|
+
const trustScore = card.trust
|
|
122
|
+
? `${(card.trust.interactionScore * 100).toFixed(0)}%`
|
|
123
|
+
: 'N/A';
|
|
124
|
+
|
|
125
|
+
table.push([
|
|
126
|
+
card.did.substring(0, 35) + '...',
|
|
127
|
+
card.name,
|
|
128
|
+
capabilities.substring(0, 30) + (capabilities.length > 30 ? '...' : ''),
|
|
129
|
+
trustScore,
|
|
130
|
+
]);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
console.log();
|
|
134
|
+
console.log(table.toString());
|
|
135
|
+
|
|
136
|
+
// Show detailed info for first result if query was used
|
|
137
|
+
if (options.query && cards.length > 0) {
|
|
138
|
+
const card = cards[0];
|
|
139
|
+
console.log();
|
|
140
|
+
info('Top match details:');
|
|
141
|
+
info(` DID: ${card.did}`);
|
|
142
|
+
info(` Name: ${card.name}`);
|
|
143
|
+
info(` Description: ${card.description}`);
|
|
144
|
+
|
|
145
|
+
if (Array.isArray(card.capabilities) && card.capabilities.length > 0) {
|
|
146
|
+
const cap = card.capabilities[0];
|
|
147
|
+
if (typeof cap === 'object' && cap !== null) {
|
|
148
|
+
info(` Capability: ${cap.name}`);
|
|
149
|
+
info(` ${cap.description}`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
} else {
|
|
155
|
+
spin.fail('Please specify --did, --capability, or --query');
|
|
156
|
+
info('Usage: hw1 discover --did <did>');
|
|
157
|
+
info(' hw1 discover --capability <capability>');
|
|
158
|
+
info(' hw1 discover --query "translate Japanese"');
|
|
159
|
+
info(' hw1 discover --query "code review" --min-trust 0.8');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
await node.stop();
|
|
163
|
+
} catch (err) {
|
|
164
|
+
error(`Failed to discover: ${(err as Error).message}`);
|
|
165
|
+
process.exit(1);
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { getIdentity, getAgentCard } from '../config.js';
|
|
3
|
+
import { error, printHeader, printKeyValue } from '../ui.js';
|
|
4
|
+
|
|
5
|
+
export function registerIdentityCommand(program: Command): void {
|
|
6
|
+
program
|
|
7
|
+
.command('identity')
|
|
8
|
+
.description('Show current identity information')
|
|
9
|
+
.action(async () => {
|
|
10
|
+
try {
|
|
11
|
+
printHeader('Clawiverse Identity');
|
|
12
|
+
|
|
13
|
+
const identity = getIdentity();
|
|
14
|
+
const card = getAgentCard();
|
|
15
|
+
|
|
16
|
+
if (!identity) {
|
|
17
|
+
error('No identity found. Run "hw1 init" first.');
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
printKeyValue('DID', identity.did);
|
|
22
|
+
printKeyValue('Public Key', identity.publicKey.substring(0, 16) + '...');
|
|
23
|
+
|
|
24
|
+
if (card) {
|
|
25
|
+
console.log();
|
|
26
|
+
printKeyValue('Name', card.name);
|
|
27
|
+
printKeyValue('Description', card.description);
|
|
28
|
+
printKeyValue('Capabilities', card.capabilities.join(', ') || 'None');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
console.log();
|
|
32
|
+
} catch (err) {
|
|
33
|
+
error(`Failed to show identity: ${(err as Error).message}`);
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { generateKeyPair, exportKeyPair, deriveDID } from '@clawiverse/core';
|
|
3
|
+
import { hasIdentity, setIdentity, setAgentCard } from '../config.js';
|
|
4
|
+
import { success, error, spinner, printHeader, printKeyValue } from '../ui.js';
|
|
5
|
+
import inquirer from 'inquirer';
|
|
6
|
+
|
|
7
|
+
export function registerInitCommand(program: Command): void {
|
|
8
|
+
program
|
|
9
|
+
.command('init')
|
|
10
|
+
.description('Initialize a new Clawiverse identity')
|
|
11
|
+
.option('--name <name>', 'Agent name')
|
|
12
|
+
.option('--description <description>', 'Agent description')
|
|
13
|
+
.option('--force', 'Overwrite existing identity')
|
|
14
|
+
.action(async (options) => {
|
|
15
|
+
try {
|
|
16
|
+
printHeader('Initialize Clawiverse Identity');
|
|
17
|
+
|
|
18
|
+
// Check if identity already exists
|
|
19
|
+
if (hasIdentity() && !options.force) {
|
|
20
|
+
error('Identity already exists. Use --force to overwrite.');
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Prompt for agent details if not provided
|
|
25
|
+
let name = options.name;
|
|
26
|
+
let description = options.description;
|
|
27
|
+
|
|
28
|
+
if (!name) name = 'My Agent';
|
|
29
|
+
if (!description) description = 'A Clawiverse agent';
|
|
30
|
+
|
|
31
|
+
if (!options.name) {
|
|
32
|
+
const { name: inputName } = await inquirer.prompt({
|
|
33
|
+
type: 'input', name: 'name', message: 'Agent name:', default: name,
|
|
34
|
+
});
|
|
35
|
+
name = inputName || name;
|
|
36
|
+
}
|
|
37
|
+
if (!options.description) {
|
|
38
|
+
const { description: inputDesc } = await inquirer.prompt({
|
|
39
|
+
type: 'input', name: 'description', message: 'Agent description:', default: description,
|
|
40
|
+
});
|
|
41
|
+
description = inputDesc || description;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const spin = spinner('Generating key pair...');
|
|
45
|
+
|
|
46
|
+
// Generate key pair
|
|
47
|
+
const keyPair = await generateKeyPair();
|
|
48
|
+
const exported = exportKeyPair(keyPair);
|
|
49
|
+
const did = deriveDID(keyPair.publicKey);
|
|
50
|
+
|
|
51
|
+
// Save identity
|
|
52
|
+
setIdentity({
|
|
53
|
+
did,
|
|
54
|
+
publicKey: exported.publicKey,
|
|
55
|
+
privateKey: exported.privateKey,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// Save agent card
|
|
59
|
+
setAgentCard({
|
|
60
|
+
name,
|
|
61
|
+
description,
|
|
62
|
+
capabilities: [],
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
spin.succeed('Identity created successfully!');
|
|
66
|
+
|
|
67
|
+
console.log();
|
|
68
|
+
printKeyValue('DID', did);
|
|
69
|
+
printKeyValue('Name', name);
|
|
70
|
+
printKeyValue('Description', description);
|
|
71
|
+
console.log();
|
|
72
|
+
|
|
73
|
+
success('Run "hw1 join" to connect to the network');
|
|
74
|
+
} catch (err) {
|
|
75
|
+
error(`Failed to initialize: ${(err as Error).message}`);
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import {
|
|
3
|
+
createNode,
|
|
4
|
+
importKeyPair,
|
|
5
|
+
createAgentCard,
|
|
6
|
+
signAgentCard,
|
|
7
|
+
createDHTOperations,
|
|
8
|
+
createMessageRouter,
|
|
9
|
+
sign,
|
|
10
|
+
} from '@clawiverse/core';
|
|
11
|
+
import { getIdentity, getAgentCard, getBootstrapPeers } from '../config.js';
|
|
12
|
+
import { success, error, spinner, printHeader, info } from '../ui.js';
|
|
13
|
+
|
|
14
|
+
export function registerJoinCommand(program: Command): void {
|
|
15
|
+
program
|
|
16
|
+
.command('join')
|
|
17
|
+
.description('Join the Clawiverse network')
|
|
18
|
+
.option('--bootstrap <peers...>', 'Bootstrap peer addresses')
|
|
19
|
+
.action(async (options) => {
|
|
20
|
+
try {
|
|
21
|
+
printHeader('Join Clawiverse Network');
|
|
22
|
+
|
|
23
|
+
const identity = getIdentity();
|
|
24
|
+
const card = getAgentCard();
|
|
25
|
+
|
|
26
|
+
if (!identity || !card) {
|
|
27
|
+
error('No identity found. Run "hw1 init" first.');
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const spin = spinner('Starting libp2p node...');
|
|
32
|
+
|
|
33
|
+
const keyPair = importKeyPair({
|
|
34
|
+
publicKey: identity.publicKey,
|
|
35
|
+
privateKey: identity.privateKey,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const bootstrapPeers = options.bootstrap || getBootstrapPeers();
|
|
39
|
+
|
|
40
|
+
const node = await createNode({
|
|
41
|
+
keyPair,
|
|
42
|
+
bootstrapPeers,
|
|
43
|
+
enableDHT: true,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
await node.start();
|
|
47
|
+
|
|
48
|
+
spin.succeed('Node started successfully!');
|
|
49
|
+
|
|
50
|
+
info(`Peer ID: ${node.getPeerId()}`);
|
|
51
|
+
info(`DID: ${identity.did}`);
|
|
52
|
+
info(`Listening on: ${node.getMultiaddrs().join(', ')}`);
|
|
53
|
+
|
|
54
|
+
// Wait for bootstrap peer connection before publishing to DHT
|
|
55
|
+
const connectSpin = spinner('Connecting to bootstrap peers...');
|
|
56
|
+
await new Promise<void>((resolve) => {
|
|
57
|
+
const timeout = setTimeout(resolve, 10000);
|
|
58
|
+
node.libp2p.addEventListener('peer:connect', () => {
|
|
59
|
+
clearTimeout(timeout);
|
|
60
|
+
setTimeout(resolve, 500);
|
|
61
|
+
}, { once: true });
|
|
62
|
+
});
|
|
63
|
+
connectSpin.succeed('Connected to network!');
|
|
64
|
+
|
|
65
|
+
// Publish Agent Card with peerId and multiaddrs so others can dial us
|
|
66
|
+
const cardSpin = spinner('Publishing Agent Card to DHT...');
|
|
67
|
+
|
|
68
|
+
// Include circuit relay addresses so nodes behind NAT can be reached
|
|
69
|
+
const directAddrs = node.getMultiaddrs();
|
|
70
|
+
const relayAddrs = bootstrapPeers.map((relayAddr) => {
|
|
71
|
+
const peerId = node.getPeerId();
|
|
72
|
+
return `${relayAddr}/p2p-circuit/p2p/${peerId}`;
|
|
73
|
+
});
|
|
74
|
+
const allAddrs = [...directAddrs, ...relayAddrs];
|
|
75
|
+
|
|
76
|
+
const agentCard = createAgentCard(
|
|
77
|
+
identity.did,
|
|
78
|
+
card.name,
|
|
79
|
+
card.description,
|
|
80
|
+
card.capabilities,
|
|
81
|
+
allAddrs,
|
|
82
|
+
node.getPeerId()
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
const signedCard = await signAgentCard(agentCard, (data) =>
|
|
86
|
+
sign(data, keyPair.privateKey)
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
const dht = createDHTOperations(node.libp2p);
|
|
90
|
+
await dht.publishAgentCard(signedCard);
|
|
91
|
+
|
|
92
|
+
cardSpin.succeed('Agent Card published!');
|
|
93
|
+
|
|
94
|
+
// Register message handlers for incoming messages
|
|
95
|
+
const router = createMessageRouter(
|
|
96
|
+
node.libp2p,
|
|
97
|
+
async () => true,
|
|
98
|
+
dht
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
// Generic message handler that accepts any protocol
|
|
102
|
+
const messageHandler = async (envelope: any) => {
|
|
103
|
+
const payload = envelope.payload as Record<string, unknown>;
|
|
104
|
+
console.log();
|
|
105
|
+
success(`>>> Received message from ${envelope.from}`);
|
|
106
|
+
info(` Message ID: ${envelope.id}`);
|
|
107
|
+
info(` Protocol: ${envelope.protocol}`);
|
|
108
|
+
info(` Type: ${envelope.type}`);
|
|
109
|
+
info(` Payload: ${JSON.stringify(payload, null, 2)}`);
|
|
110
|
+
console.log();
|
|
111
|
+
|
|
112
|
+
// If this is a request, send back a simple acknowledgment response
|
|
113
|
+
if (envelope.type === 'request') {
|
|
114
|
+
info(' Sending acknowledgment response...');
|
|
115
|
+
|
|
116
|
+
const { createEnvelope, signEnvelope, sign } = await import('@clawiverse/core');
|
|
117
|
+
const identity = getIdentity();
|
|
118
|
+
const keyPair = importKeyPair({
|
|
119
|
+
publicKey: identity!.publicKey,
|
|
120
|
+
privateKey: identity!.privateKey,
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
const responseEnvelope = createEnvelope(
|
|
124
|
+
envelope.to, // We are the recipient, now we're the sender
|
|
125
|
+
envelope.from, // Original sender is now the recipient
|
|
126
|
+
'response',
|
|
127
|
+
envelope.protocol,
|
|
128
|
+
{
|
|
129
|
+
status: 'received',
|
|
130
|
+
message: 'Message received and processed',
|
|
131
|
+
originalPayload: payload,
|
|
132
|
+
timestamp: Date.now()
|
|
133
|
+
},
|
|
134
|
+
envelope.id // replyTo: original message ID
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
const signedResponse = await signEnvelope(responseEnvelope, (data) =>
|
|
138
|
+
sign(data, keyPair.privateKey)
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
return signedResponse;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return undefined;
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
// Register handlers for common protocols
|
|
148
|
+
router.registerHandler('/clawiverse/msg/1.0.0', messageHandler);
|
|
149
|
+
router.registerHandler('/clawiverse/greet/1.0.0', messageHandler);
|
|
150
|
+
|
|
151
|
+
// Register catch-all handler for any other protocol
|
|
152
|
+
router.registerCatchAllHandler(messageHandler);
|
|
153
|
+
|
|
154
|
+
await router.start();
|
|
155
|
+
|
|
156
|
+
console.log();
|
|
157
|
+
success('Successfully joined the Clawiverse network!');
|
|
158
|
+
info('Listening for incoming messages...');
|
|
159
|
+
info('Press Ctrl+C to stop');
|
|
160
|
+
|
|
161
|
+
process.on('SIGINT', async () => {
|
|
162
|
+
console.log();
|
|
163
|
+
const stopSpin = spinner('Stopping node...');
|
|
164
|
+
await router.stop();
|
|
165
|
+
await node.stop();
|
|
166
|
+
stopSpin.succeed('Node stopped');
|
|
167
|
+
process.exit(0);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
await new Promise(() => {});
|
|
171
|
+
} catch (err) {
|
|
172
|
+
error(`Failed to join network: ${(err as Error).message}`);
|
|
173
|
+
process.exit(1);
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import {
|
|
3
|
+
createNode,
|
|
4
|
+
importKeyPair,
|
|
5
|
+
createEnvelope,
|
|
6
|
+
signEnvelope,
|
|
7
|
+
createMessageRouter,
|
|
8
|
+
createDHTOperations,
|
|
9
|
+
sign,
|
|
10
|
+
} from '@clawiverse/core';
|
|
11
|
+
import { getIdentity, getBootstrapPeers } from '../config.js';
|
|
12
|
+
import { success, error, spinner, printHeader, info } from '../ui.js';
|
|
13
|
+
|
|
14
|
+
export function registerSendCommand(program: Command): void {
|
|
15
|
+
program
|
|
16
|
+
.command('send')
|
|
17
|
+
.description('Send a message to another agent')
|
|
18
|
+
.requiredOption('--to <did>', 'Recipient DID')
|
|
19
|
+
.requiredOption('--protocol <protocol>', 'Protocol identifier')
|
|
20
|
+
.requiredOption('--payload <json>', 'Message payload (JSON)')
|
|
21
|
+
.option('--type <type>', 'Message type (request|notification)', 'request')
|
|
22
|
+
.option('--bootstrap <peers...>', 'Bootstrap peer addresses')
|
|
23
|
+
.option('--peer <multiaddr>', 'Direct peer multiaddr (bypasses DHT lookup)')
|
|
24
|
+
.action(async (options) => {
|
|
25
|
+
try {
|
|
26
|
+
printHeader('Send Message');
|
|
27
|
+
|
|
28
|
+
const identity = getIdentity();
|
|
29
|
+
|
|
30
|
+
if (!identity) {
|
|
31
|
+
error('No identity found. Run "hw1 init" first.');
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let payload;
|
|
36
|
+
try {
|
|
37
|
+
payload = JSON.parse(options.payload);
|
|
38
|
+
} catch {
|
|
39
|
+
error('Invalid JSON payload');
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const spin = spinner('Starting node...');
|
|
44
|
+
|
|
45
|
+
const keyPair = importKeyPair({
|
|
46
|
+
publicKey: identity.publicKey,
|
|
47
|
+
privateKey: identity.privateKey,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const bootstrapPeers = options.bootstrap || getBootstrapPeers();
|
|
51
|
+
|
|
52
|
+
const node = await createNode({
|
|
53
|
+
keyPair,
|
|
54
|
+
bootstrapPeers,
|
|
55
|
+
enableDHT: true,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
await node.start();
|
|
59
|
+
|
|
60
|
+
spin.text = 'Connecting to network...';
|
|
61
|
+
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
62
|
+
|
|
63
|
+
const dht = createDHTOperations(node.libp2p);
|
|
64
|
+
|
|
65
|
+
const router = createMessageRouter(
|
|
66
|
+
node.libp2p,
|
|
67
|
+
async () => true,
|
|
68
|
+
dht,
|
|
69
|
+
bootstrapPeers
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
await router.start();
|
|
73
|
+
|
|
74
|
+
spin.text = 'Creating message...';
|
|
75
|
+
|
|
76
|
+
const envelope = createEnvelope(
|
|
77
|
+
identity.did,
|
|
78
|
+
options.to,
|
|
79
|
+
options.type,
|
|
80
|
+
options.protocol,
|
|
81
|
+
payload
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
const signedEnvelope = await signEnvelope(envelope, (data) =>
|
|
85
|
+
sign(data, keyPair.privateKey)
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
spin.text = 'Sending message...';
|
|
89
|
+
|
|
90
|
+
// Build peer hint from --peer option if provided
|
|
91
|
+
let peerHint = undefined;
|
|
92
|
+
if (options.peer) {
|
|
93
|
+
// Extract peerId from multiaddr (last component after /p2p/)
|
|
94
|
+
const parts = options.peer.split('/p2p/');
|
|
95
|
+
if (parts.length === 2) {
|
|
96
|
+
peerHint = {
|
|
97
|
+
peerId: parts[1],
|
|
98
|
+
multiaddrs: [options.peer],
|
|
99
|
+
};
|
|
100
|
+
info(`Direct peer: ${options.peer}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const response = await router.sendMessage(signedEnvelope, peerHint);
|
|
105
|
+
|
|
106
|
+
spin.succeed('Message sent successfully!');
|
|
107
|
+
|
|
108
|
+
console.log();
|
|
109
|
+
info(`Message ID: ${signedEnvelope.id}`);
|
|
110
|
+
info(`To: ${options.to}`);
|
|
111
|
+
info(`Protocol: ${options.protocol}`);
|
|
112
|
+
info(`Type: ${options.type}`);
|
|
113
|
+
info(`Payload: ${JSON.stringify(payload)}`);
|
|
114
|
+
|
|
115
|
+
// Display response if received
|
|
116
|
+
if (response) {
|
|
117
|
+
console.log();
|
|
118
|
+
success('>>> Received response from recipient');
|
|
119
|
+
info(`Response ID: ${response.id}`);
|
|
120
|
+
info(`Reply To: ${response.replyTo}`);
|
|
121
|
+
info(`Protocol: ${response.protocol}`);
|
|
122
|
+
info(`Payload: ${JSON.stringify(response.payload, null, 2)}`);
|
|
123
|
+
} else if (options.type === 'request') {
|
|
124
|
+
console.log();
|
|
125
|
+
info('No response received (recipient may not have returned a response)');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
await router.stop();
|
|
129
|
+
await node.stop();
|
|
130
|
+
|
|
131
|
+
success('Done');
|
|
132
|
+
} catch (err) {
|
|
133
|
+
error(`Failed to send message: ${(err as Error).message}`);
|
|
134
|
+
process.exit(1);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
}
|
|
@@ -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
|
+
}
|