@highway1/cli 0.1.53 → 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.
- package/package.json +11 -22
- package/bin/clawiverse.js +0 -2
- package/src/commands/ask.ts +0 -158
- package/src/commands/card.ts +0 -104
- package/src/commands/daemon.ts +0 -207
- package/src/commands/discover.ts +0 -178
- package/src/commands/identity.ts +0 -46
- package/src/commands/inbox.ts +0 -222
- package/src/commands/init.ts +0 -54
- package/src/commands/join.ts +0 -198
- package/src/commands/peers.ts +0 -85
- package/src/commands/send.ts +0 -299
- package/src/commands/serve.ts +0 -271
- package/src/commands/status.ts +0 -60
- package/src/commands/stop.ts +0 -49
- package/src/commands/trust.ts +0 -354
- package/src/config.ts +0 -74
- package/src/daemon/client.ts +0 -90
- package/src/daemon/server.ts +0 -481
- package/src/index.ts +0 -61
- package/src/ui.ts +0 -38
package/package.json
CHANGED
|
@@ -1,32 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@highway1/cli",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "CLI tool for
|
|
3
|
+
"version": "0.1.54",
|
|
4
|
+
"description": "CLI tool for Highway 1 agent network (CVP-0010)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
},
|
|
10
|
-
"scripts": {
|
|
11
|
-
"build": "tsup",
|
|
12
|
-
"dev": "tsup --watch",
|
|
13
|
-
"clean": "rm -rf dist"
|
|
7
|
+
"hw1": "./dist/index.js",
|
|
8
|
+
"clawiverse": "./dist/index.js"
|
|
14
9
|
},
|
|
10
|
+
"keywords": ["ai", "agent", "p2p", "cli", "highway1"],
|
|
11
|
+
"license": "MIT",
|
|
15
12
|
"dependencies": {
|
|
16
|
-
"@highway1/core": "^0.1.
|
|
17
|
-
"chalk": "^5.3.0",
|
|
18
|
-
"cli-table3": "^0.6.5",
|
|
13
|
+
"@highway1/core": "^0.1.54",
|
|
19
14
|
"commander": "^12.1.0",
|
|
20
|
-
"
|
|
15
|
+
"chalk": "^5.3.0",
|
|
16
|
+
"ora": "^8.1.0",
|
|
21
17
|
"inquirer": "^13.3.0",
|
|
22
|
-
"
|
|
23
|
-
|
|
24
|
-
"devDependencies": {
|
|
25
|
-
"@types/node": "^22.10.2",
|
|
26
|
-
"tsup": "^8.3.5",
|
|
27
|
-
"typescript": "^5.7.2"
|
|
28
|
-
},
|
|
29
|
-
"engines": {
|
|
30
|
-
"node": ">=22.0.0"
|
|
18
|
+
"cli-table3": "^0.6.5",
|
|
19
|
+
"conf": "^13.0.1"
|
|
31
20
|
}
|
|
32
21
|
}
|
package/bin/clawiverse.js
DELETED
package/src/commands/ask.ts
DELETED
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Ask Command - CVP-0010 §2.1
|
|
3
|
-
*
|
|
4
|
-
* One-step network query: discover + send + wait for response.
|
|
5
|
-
*
|
|
6
|
-
* hw1 ask "translate hello to Japanese"
|
|
7
|
-
* hw1 ask "review this code" --file main.py
|
|
8
|
-
* hw1 ask "translate hello" --to kaito-translator
|
|
9
|
-
* hw1 ask "summarize" --min-trust 0.8 --format json
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import { Command } from 'commander';
|
|
13
|
-
import { DaemonClient } from '../daemon/client.js';
|
|
14
|
-
import { createLogger } from '@highway1/core';
|
|
15
|
-
import { readFile } from 'node:fs/promises';
|
|
16
|
-
import { basename } from 'node:path';
|
|
17
|
-
|
|
18
|
-
const logger = createLogger('cli:ask');
|
|
19
|
-
|
|
20
|
-
export function registerAskCommand(program: Command): void {
|
|
21
|
-
program
|
|
22
|
-
.command('ask <query>')
|
|
23
|
-
.description('Ask the network — discover an agent and send a request in one step')
|
|
24
|
-
.option('--to <name-or-did>', 'Skip discovery, send to specific agent name or DID')
|
|
25
|
-
.option('--min-trust <score>', 'Minimum trust score (0-1)', '0')
|
|
26
|
-
.option('--timeout <seconds>', 'Response timeout in seconds', '30')
|
|
27
|
-
.option('--file <path>', 'Attach a file to the request')
|
|
28
|
-
.option('--protocol <protocol>', 'Protocol to use', '/clawiverse/msg/1.0.0')
|
|
29
|
-
.option('--dry-run', 'Show what would be sent without sending')
|
|
30
|
-
.option('--format <fmt>', 'Output format: text|json', 'text')
|
|
31
|
-
.action(async (query: string, options) => {
|
|
32
|
-
const client = new DaemonClient();
|
|
33
|
-
if (!(await client.isDaemonRunning())) {
|
|
34
|
-
console.error('Daemon not running. Start with: hw1 join');
|
|
35
|
-
process.exit(1);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
try {
|
|
39
|
-
let targetDid: string | undefined;
|
|
40
|
-
|
|
41
|
-
// Resolve target
|
|
42
|
-
if (options.to) {
|
|
43
|
-
if (options.to.startsWith('did:')) {
|
|
44
|
-
targetDid = options.to;
|
|
45
|
-
} else {
|
|
46
|
-
// Name resolution via DHT
|
|
47
|
-
if (options.format !== 'json') process.stderr.write(`Searching for "${options.to}"...\n`);
|
|
48
|
-
const results = await client.send('discover', { query: options.to });
|
|
49
|
-
if (!results || results.length === 0) {
|
|
50
|
-
console.error(`No agent found matching: ${options.to}`);
|
|
51
|
-
process.exit(1);
|
|
52
|
-
}
|
|
53
|
-
targetDid = results[0].did;
|
|
54
|
-
if (options.format !== 'json') process.stderr.write(`Found: ${results[0].name ?? targetDid}\n`);
|
|
55
|
-
}
|
|
56
|
-
} else {
|
|
57
|
-
// Semantic discovery
|
|
58
|
-
if (options.format !== 'json') process.stderr.write(`Searching for agents matching: "${query}"...\n`);
|
|
59
|
-
const minTrust = parseFloat(options.minTrust);
|
|
60
|
-
const results = await client.send('discover', {
|
|
61
|
-
query,
|
|
62
|
-
filters: minTrust > 0 ? { minTrustScore: minTrust } : undefined,
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
if (!results || results.length === 0) {
|
|
66
|
-
console.error(`No agents found for: ${query}`);
|
|
67
|
-
process.exit(1);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Pick highest-trust match
|
|
71
|
-
const sorted = [...results].sort((a: any, b: any) => {
|
|
72
|
-
const ta = a.trust?.interactionScore ?? 0;
|
|
73
|
-
const tb = b.trust?.interactionScore ?? 0;
|
|
74
|
-
return tb - ta;
|
|
75
|
-
});
|
|
76
|
-
targetDid = sorted[0].did;
|
|
77
|
-
if (options.format !== 'json') {
|
|
78
|
-
process.stderr.write(`Best match: ${sorted[0].name ?? targetDid} (trust: ${((sorted[0].trust?.interactionScore ?? 0) * 100).toFixed(0)}%)\n`);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Build payload
|
|
83
|
-
const payload: Record<string, unknown> = { text: query };
|
|
84
|
-
|
|
85
|
-
if (options.file) {
|
|
86
|
-
const fileBytes = await readFile(options.file);
|
|
87
|
-
const filename = basename(options.file);
|
|
88
|
-
payload.attachment = {
|
|
89
|
-
filename,
|
|
90
|
-
mimeType: guessMimeType(filename),
|
|
91
|
-
size: fileBytes.length,
|
|
92
|
-
data: fileBytes.toString('base64'),
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
if (options.dryRun) {
|
|
97
|
-
const out = { to: targetDid, protocol: options.protocol, payload };
|
|
98
|
-
if (options.format === 'json') {
|
|
99
|
-
console.log(JSON.stringify(out, null, 2));
|
|
100
|
-
} else {
|
|
101
|
-
console.log(`[dry-run] Would send to: ${targetDid}`);
|
|
102
|
-
console.log(`[dry-run] Protocol: ${options.protocol}`);
|
|
103
|
-
console.log(`[dry-run] Payload: ${JSON.stringify(payload)}`);
|
|
104
|
-
}
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
if (options.format !== 'json') process.stderr.write(`Sending request...\n`);
|
|
109
|
-
|
|
110
|
-
const result = await client.send('send', {
|
|
111
|
-
to: targetDid,
|
|
112
|
-
protocol: options.protocol,
|
|
113
|
-
payload,
|
|
114
|
-
type: 'request',
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
if (options.format === 'json') {
|
|
118
|
-
console.log(JSON.stringify({
|
|
119
|
-
from: targetDid,
|
|
120
|
-
messageId: result.id,
|
|
121
|
-
response: result.response ?? null,
|
|
122
|
-
verified: true,
|
|
123
|
-
}, null, 2));
|
|
124
|
-
} else {
|
|
125
|
-
if (result.response) {
|
|
126
|
-
const resp = result.response;
|
|
127
|
-
const p = resp.payload as any;
|
|
128
|
-
if (p?.text) {
|
|
129
|
-
console.log(p.text);
|
|
130
|
-
} else if (p?.result) {
|
|
131
|
-
console.log(typeof p.result === 'string' ? p.result : JSON.stringify(p.result, null, 2));
|
|
132
|
-
} else {
|
|
133
|
-
console.log(JSON.stringify(p, null, 2));
|
|
134
|
-
}
|
|
135
|
-
} else {
|
|
136
|
-
console.log(`Request sent (${result.id.slice(-8)}). No response received within timeout.`);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
} catch (err) {
|
|
140
|
-
logger.error('Ask failed', err);
|
|
141
|
-
console.error('Error:', (err as Error).message);
|
|
142
|
-
process.exit(1);
|
|
143
|
-
}
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function guessMimeType(filename: string): string {
|
|
148
|
-
const ext = filename.split('.').pop()?.toLowerCase() ?? '';
|
|
149
|
-
const map: Record<string, string> = {
|
|
150
|
-
png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg',
|
|
151
|
-
gif: 'image/gif', webp: 'image/webp', svg: 'image/svg+xml',
|
|
152
|
-
pdf: 'application/pdf', txt: 'text/plain', md: 'text/markdown',
|
|
153
|
-
json: 'application/json', csv: 'text/csv',
|
|
154
|
-
mp3: 'audio/mpeg', mp4: 'video/mp4', wav: 'audio/wav',
|
|
155
|
-
zip: 'application/zip', gz: 'application/gzip',
|
|
156
|
-
};
|
|
157
|
-
return map[ext] ?? 'application/octet-stream';
|
|
158
|
-
}
|
package/src/commands/card.ts
DELETED
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
2
|
-
import { getAgentCard, setAgentCard } from '../config.js';
|
|
3
|
-
import { success, error, printHeader } from '../ui.js';
|
|
4
|
-
import inquirer from 'inquirer';
|
|
5
|
-
|
|
6
|
-
export function registerCardCommand(program: Command): void {
|
|
7
|
-
const card = program.command('card').description('Manage Agent Card');
|
|
8
|
-
|
|
9
|
-
card
|
|
10
|
-
.command('show')
|
|
11
|
-
.description('Show current Agent Card')
|
|
12
|
-
.option('--format <fmt>', 'Output format: text|json', 'text')
|
|
13
|
-
.action(async (options) => {
|
|
14
|
-
try {
|
|
15
|
-
const agentCard = getAgentCard();
|
|
16
|
-
|
|
17
|
-
if (!agentCard) {
|
|
18
|
-
error('No Agent Card found. Run "hw1 init" first.');
|
|
19
|
-
process.exit(1);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
if (options.format === 'json') {
|
|
23
|
-
console.log(JSON.stringify(agentCard, null, 2));
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
printHeader('Agent Card');
|
|
28
|
-
console.log(JSON.stringify(agentCard, null, 2));
|
|
29
|
-
} catch (err) {
|
|
30
|
-
error(`Failed to show card: ${(err as Error).message}`);
|
|
31
|
-
process.exit(1);
|
|
32
|
-
}
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
card
|
|
36
|
-
.command('edit')
|
|
37
|
-
.description('Edit Agent Card')
|
|
38
|
-
.option('--name <name>', 'Agent name')
|
|
39
|
-
.option('--description <description>', 'Agent description')
|
|
40
|
-
.option('--capabilities <capabilities>', 'Capabilities (comma-separated)')
|
|
41
|
-
.action(async (options) => {
|
|
42
|
-
try {
|
|
43
|
-
printHeader('Edit Agent Card');
|
|
44
|
-
|
|
45
|
-
const currentCard = getAgentCard();
|
|
46
|
-
|
|
47
|
-
if (!currentCard) {
|
|
48
|
-
error('No Agent Card found. Run "hw1 init" first.');
|
|
49
|
-
process.exit(1);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
let updatedCard;
|
|
53
|
-
|
|
54
|
-
// If any options provided, use non-interactive mode
|
|
55
|
-
if (options.name || options.description || options.capabilities) {
|
|
56
|
-
updatedCard = {
|
|
57
|
-
name: options.name || currentCard.name,
|
|
58
|
-
description: options.description || currentCard.description,
|
|
59
|
-
capabilities: options.capabilities
|
|
60
|
-
? options.capabilities.split(',').map((c: string) => c.trim()).filter((c: string) => c.length > 0)
|
|
61
|
-
: currentCard.capabilities,
|
|
62
|
-
};
|
|
63
|
-
} else {
|
|
64
|
-
// Interactive mode
|
|
65
|
-
const answers = await inquirer.prompt([
|
|
66
|
-
{
|
|
67
|
-
type: 'input',
|
|
68
|
-
name: 'name',
|
|
69
|
-
message: 'Agent name:',
|
|
70
|
-
default: currentCard.name,
|
|
71
|
-
},
|
|
72
|
-
{
|
|
73
|
-
type: 'input',
|
|
74
|
-
name: 'description',
|
|
75
|
-
message: 'Agent description:',
|
|
76
|
-
default: currentCard.description,
|
|
77
|
-
},
|
|
78
|
-
{
|
|
79
|
-
type: 'input',
|
|
80
|
-
name: 'capabilities',
|
|
81
|
-
message: 'Capabilities (comma-separated):',
|
|
82
|
-
default: currentCard.capabilities.join(', '),
|
|
83
|
-
},
|
|
84
|
-
]);
|
|
85
|
-
|
|
86
|
-
updatedCard = {
|
|
87
|
-
name: answers.name,
|
|
88
|
-
description: answers.description,
|
|
89
|
-
capabilities: answers.capabilities
|
|
90
|
-
.split(',')
|
|
91
|
-
.map((c: string) => c.trim())
|
|
92
|
-
.filter((c: string) => c.length > 0),
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
setAgentCard(updatedCard);
|
|
97
|
-
|
|
98
|
-
success('Agent Card updated successfully!');
|
|
99
|
-
} catch (err) {
|
|
100
|
-
error(`Failed to edit card: ${(err as Error).message}`);
|
|
101
|
-
process.exit(1);
|
|
102
|
-
}
|
|
103
|
-
});
|
|
104
|
-
}
|
package/src/commands/daemon.ts
DELETED
|
@@ -1,207 +0,0 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
2
|
-
import { ClawDaemon } from '../daemon/server.js';
|
|
3
|
-
import { DaemonClient } from '../daemon/client.js';
|
|
4
|
-
import { spawn } from 'child_process';
|
|
5
|
-
import { writeFileSync, readFileSync, unlinkSync, existsSync } from 'fs';
|
|
6
|
-
import { success, error, info } from '../ui.js';
|
|
7
|
-
|
|
8
|
-
const PID_FILE = '/tmp/clawiverse.pid';
|
|
9
|
-
const SOCKET_PATH = '/tmp/clawiverse.sock';
|
|
10
|
-
|
|
11
|
-
export function registerDaemonCommand(program: Command): void {
|
|
12
|
-
const daemonCommand = program
|
|
13
|
-
.command('daemon')
|
|
14
|
-
.description('Manage Clawiverse daemon for fast messaging');
|
|
15
|
-
|
|
16
|
-
daemonCommand
|
|
17
|
-
.command('start')
|
|
18
|
-
.description('Start daemon in background')
|
|
19
|
-
.action(async () => {
|
|
20
|
-
try {
|
|
21
|
-
const client = new DaemonClient(SOCKET_PATH);
|
|
22
|
-
if (await client.isDaemonRunning()) {
|
|
23
|
-
success('Daemon already running');
|
|
24
|
-
const status = await client.send('status', {});
|
|
25
|
-
info(`Peer ID: ${status.peerId}`);
|
|
26
|
-
info(`DID: ${status.did}`);
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Start daemon as detached background process
|
|
31
|
-
const child = spawn(process.execPath, [
|
|
32
|
-
process.argv[1],
|
|
33
|
-
'daemon',
|
|
34
|
-
'run'
|
|
35
|
-
], {
|
|
36
|
-
detached: true,
|
|
37
|
-
stdio: 'ignore'
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
child.unref();
|
|
41
|
-
writeFileSync(PID_FILE, child.pid!.toString());
|
|
42
|
-
|
|
43
|
-
// Wait for daemon to be ready
|
|
44
|
-
for (let i = 0; i < 20; i++) {
|
|
45
|
-
await new Promise(resolve => setTimeout(resolve, 500));
|
|
46
|
-
if (await client.isDaemonRunning()) {
|
|
47
|
-
success('Daemon started');
|
|
48
|
-
const status = await client.send('status', {});
|
|
49
|
-
info(`Peer ID: ${status.peerId}`);
|
|
50
|
-
info(`DID: ${status.did}`);
|
|
51
|
-
info(`Socket: ${SOCKET_PATH}`);
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
error('Failed to start daemon (timeout)');
|
|
57
|
-
process.exit(1);
|
|
58
|
-
} catch (err) {
|
|
59
|
-
error(`Failed to start daemon: ${(err as Error).message}`);
|
|
60
|
-
process.exit(1);
|
|
61
|
-
}
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
daemonCommand
|
|
65
|
-
.command('run')
|
|
66
|
-
.description('Run daemon in foreground (internal use)')
|
|
67
|
-
.action(async () => {
|
|
68
|
-
try {
|
|
69
|
-
const daemon = new ClawDaemon(SOCKET_PATH);
|
|
70
|
-
await daemon.start();
|
|
71
|
-
|
|
72
|
-
// Keep process alive
|
|
73
|
-
process.on('SIGINT', async () => {
|
|
74
|
-
console.log('\nShutting down...');
|
|
75
|
-
await daemon.shutdown();
|
|
76
|
-
process.exit(0);
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
process.on('SIGTERM', async () => {
|
|
80
|
-
await daemon.shutdown();
|
|
81
|
-
process.exit(0);
|
|
82
|
-
});
|
|
83
|
-
} catch (err) {
|
|
84
|
-
error(`Failed to run daemon: ${(err as Error).message}`);
|
|
85
|
-
process.exit(1);
|
|
86
|
-
}
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
daemonCommand
|
|
90
|
-
.command('stop')
|
|
91
|
-
.description('Stop daemon')
|
|
92
|
-
.action(async () => {
|
|
93
|
-
try {
|
|
94
|
-
const client = new DaemonClient(SOCKET_PATH);
|
|
95
|
-
|
|
96
|
-
if (!(await client.isDaemonRunning())) {
|
|
97
|
-
info('Daemon not running');
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
await client.send('shutdown', {});
|
|
102
|
-
success('Daemon stopped');
|
|
103
|
-
|
|
104
|
-
// Clean up PID file
|
|
105
|
-
try {
|
|
106
|
-
if (existsSync(PID_FILE)) {
|
|
107
|
-
unlinkSync(PID_FILE);
|
|
108
|
-
}
|
|
109
|
-
} catch {}
|
|
110
|
-
|
|
111
|
-
// Clean up socket file
|
|
112
|
-
try {
|
|
113
|
-
if (existsSync(SOCKET_PATH)) {
|
|
114
|
-
unlinkSync(SOCKET_PATH);
|
|
115
|
-
}
|
|
116
|
-
} catch {}
|
|
117
|
-
} catch (err) {
|
|
118
|
-
error(`Failed to stop daemon: ${(err as Error).message}`);
|
|
119
|
-
process.exit(1);
|
|
120
|
-
}
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
daemonCommand
|
|
124
|
-
.command('status')
|
|
125
|
-
.description('Check daemon status')
|
|
126
|
-
.action(async () => {
|
|
127
|
-
try {
|
|
128
|
-
const client = new DaemonClient(SOCKET_PATH);
|
|
129
|
-
|
|
130
|
-
if (await client.isDaemonRunning()) {
|
|
131
|
-
const status = await client.send('status', {});
|
|
132
|
-
success('Daemon running');
|
|
133
|
-
console.log();
|
|
134
|
-
info(`Peer ID: ${status.peerId}`);
|
|
135
|
-
info(`DID: ${status.did}`);
|
|
136
|
-
info(`Socket: ${SOCKET_PATH}`);
|
|
137
|
-
info(`Bootstrap peers: ${status.bootstrapPeers.length}`);
|
|
138
|
-
console.log();
|
|
139
|
-
info('Multiaddrs:');
|
|
140
|
-
status.multiaddrs.forEach((addr: string) => {
|
|
141
|
-
console.log(` ${addr}`);
|
|
142
|
-
});
|
|
143
|
-
} else {
|
|
144
|
-
info('Daemon not running');
|
|
145
|
-
console.log();
|
|
146
|
-
info('Start daemon with: clawiverse daemon start');
|
|
147
|
-
}
|
|
148
|
-
} catch (err) {
|
|
149
|
-
error(`Failed to check status: ${(err as Error).message}`);
|
|
150
|
-
process.exit(1);
|
|
151
|
-
}
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
daemonCommand
|
|
155
|
-
.command('restart')
|
|
156
|
-
.description('Restart daemon')
|
|
157
|
-
.action(async () => {
|
|
158
|
-
try {
|
|
159
|
-
const client = new DaemonClient(SOCKET_PATH);
|
|
160
|
-
|
|
161
|
-
// Stop if running
|
|
162
|
-
if (await client.isDaemonRunning()) {
|
|
163
|
-
info('Stopping daemon...');
|
|
164
|
-
await client.send('shutdown', {});
|
|
165
|
-
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// Clean up files
|
|
169
|
-
try {
|
|
170
|
-
if (existsSync(PID_FILE)) unlinkSync(PID_FILE);
|
|
171
|
-
if (existsSync(SOCKET_PATH)) unlinkSync(SOCKET_PATH);
|
|
172
|
-
} catch {}
|
|
173
|
-
|
|
174
|
-
// Start daemon
|
|
175
|
-
info('Starting daemon...');
|
|
176
|
-
const child = spawn(process.execPath, [
|
|
177
|
-
process.argv[1],
|
|
178
|
-
'daemon',
|
|
179
|
-
'run'
|
|
180
|
-
], {
|
|
181
|
-
detached: true,
|
|
182
|
-
stdio: 'ignore'
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
child.unref();
|
|
186
|
-
writeFileSync(PID_FILE, child.pid!.toString());
|
|
187
|
-
|
|
188
|
-
// Wait for daemon to be ready
|
|
189
|
-
for (let i = 0; i < 20; i++) {
|
|
190
|
-
await new Promise(resolve => setTimeout(resolve, 500));
|
|
191
|
-
if (await client.isDaemonRunning()) {
|
|
192
|
-
success('Daemon restarted');
|
|
193
|
-
const status = await client.send('status', {});
|
|
194
|
-
info(`Peer ID: ${status.peerId}`);
|
|
195
|
-
info(`DID: ${status.did}`);
|
|
196
|
-
return;
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
error('Failed to restart daemon (timeout)');
|
|
201
|
-
process.exit(1);
|
|
202
|
-
} catch (err) {
|
|
203
|
-
error(`Failed to restart daemon: ${(err as Error).message}`);
|
|
204
|
-
process.exit(1);
|
|
205
|
-
}
|
|
206
|
-
});
|
|
207
|
-
}
|
package/src/commands/discover.ts
DELETED
|
@@ -1,178 +0,0 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
2
|
-
import {
|
|
3
|
-
createRelayClient,
|
|
4
|
-
createRelayIndexOperations,
|
|
5
|
-
importKeyPair,
|
|
6
|
-
createAgentCard,
|
|
7
|
-
signAgentCard,
|
|
8
|
-
sign,
|
|
9
|
-
} from '@highway1/core';
|
|
10
|
-
import { getIdentity, getAgentCard } from '../config.js';
|
|
11
|
-
import { error, spinner, printHeader, info, success } from '../ui.js';
|
|
12
|
-
import Table from 'cli-table3';
|
|
13
|
-
|
|
14
|
-
const DEFAULT_RELAY_URLS = ['ws://relay.highway1.net:8080'];
|
|
15
|
-
|
|
16
|
-
function getRelayUrls(options: any): string[] {
|
|
17
|
-
if (options.relay) return [options.relay];
|
|
18
|
-
const envRelays = process.env.HW1_RELAY_URLS;
|
|
19
|
-
if (envRelays) return envRelays.split(',').map((u: string) => u.trim());
|
|
20
|
-
return DEFAULT_RELAY_URLS;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function registerDiscoverCommand(program: Command): void {
|
|
24
|
-
program
|
|
25
|
-
.command('discover')
|
|
26
|
-
.description('Discover agents on the network')
|
|
27
|
-
.option('--capability <capability>', 'Filter by capability')
|
|
28
|
-
.option('--did <did>', 'Query specific DID')
|
|
29
|
-
.option('--query <text>', 'Natural language query (e.g., "translate Japanese")')
|
|
30
|
-
.option('--min-trust <score>', 'Minimum trust score (0-1)')
|
|
31
|
-
.option('--language <lang>', 'Filter by language')
|
|
32
|
-
.option('--limit <number>', 'Maximum number of results', '10')
|
|
33
|
-
.option('--relay <url>', 'Relay WebSocket URL')
|
|
34
|
-
.action(async (options) => {
|
|
35
|
-
try {
|
|
36
|
-
printHeader('Discover Agents');
|
|
37
|
-
|
|
38
|
-
const identity = getIdentity();
|
|
39
|
-
|
|
40
|
-
if (!identity) {
|
|
41
|
-
error('No identity found. Run "hw1 init" first.');
|
|
42
|
-
process.exit(1);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const spin = spinner('Connecting to relay...');
|
|
46
|
-
|
|
47
|
-
const keyPair = importKeyPair({
|
|
48
|
-
publicKey: identity.publicKey,
|
|
49
|
-
privateKey: identity.privateKey,
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
const relayUrls = getRelayUrls(options);
|
|
53
|
-
const card = getAgentCard();
|
|
54
|
-
const capabilities = (card?.capabilities ?? []).map((c: string) => ({
|
|
55
|
-
id: c, name: c, description: `Capability: ${c}`,
|
|
56
|
-
}));
|
|
57
|
-
const agentCard = createAgentCard(
|
|
58
|
-
identity.did,
|
|
59
|
-
card?.name ?? 'Clawiverse Agent',
|
|
60
|
-
card?.description ?? '',
|
|
61
|
-
capabilities,
|
|
62
|
-
[],
|
|
63
|
-
);
|
|
64
|
-
const signedCard = await signAgentCard(agentCard, (data) => sign(data, keyPair.privateKey));
|
|
65
|
-
|
|
66
|
-
const relayClient = createRelayClient({
|
|
67
|
-
relayUrls,
|
|
68
|
-
did: identity.did,
|
|
69
|
-
keyPair,
|
|
70
|
-
card: signedCard,
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
await relayClient.start();
|
|
74
|
-
spin.text = 'Querying relay...';
|
|
75
|
-
|
|
76
|
-
const relayIndex = createRelayIndexOperations(relayClient);
|
|
77
|
-
|
|
78
|
-
if (options.did) {
|
|
79
|
-
const agentCard = await relayIndex.queryAgentCard(options.did);
|
|
80
|
-
|
|
81
|
-
if (agentCard) {
|
|
82
|
-
spin.succeed('Agent found!');
|
|
83
|
-
console.log();
|
|
84
|
-
info(`DID: ${agentCard.did}`);
|
|
85
|
-
info(`Name: ${agentCard.name}`);
|
|
86
|
-
info(`Description: ${agentCard.description}`);
|
|
87
|
-
info(`Version: ${agentCard.version}`);
|
|
88
|
-
const caps = agentCard.capabilities.map((c: any) =>
|
|
89
|
-
typeof c === 'string' ? c : c.name
|
|
90
|
-
).join(', ');
|
|
91
|
-
info(`Capabilities: ${caps || '(none)'}`);
|
|
92
|
-
info(`Timestamp: ${new Date(agentCard.timestamp).toISOString()}`);
|
|
93
|
-
} else {
|
|
94
|
-
spin.fail('Agent not found');
|
|
95
|
-
}
|
|
96
|
-
} else if (options.capability || options.query) {
|
|
97
|
-
const query: any = {
|
|
98
|
-
limit: parseInt(options.limit, 10),
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
if (options.query) {
|
|
102
|
-
query.text = options.query;
|
|
103
|
-
} else if (options.capability) {
|
|
104
|
-
query.capability = options.capability;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
if (options.minTrust) {
|
|
108
|
-
query.filters = { minTrustScore: parseFloat(options.minTrust) };
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
if (options.language) {
|
|
112
|
-
query.filters = query.filters || {};
|
|
113
|
-
query.filters.language = options.language;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const cards = await relayIndex.searchSemantic(query);
|
|
117
|
-
|
|
118
|
-
spin.succeed(`Found ${cards.length} agents`);
|
|
119
|
-
|
|
120
|
-
if (cards.length > 0) {
|
|
121
|
-
const table = new Table({
|
|
122
|
-
head: ['DID', 'Name', 'Capabilities', 'Trust'],
|
|
123
|
-
colWidths: [40, 20, 35, 10],
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
for (const card of cards) {
|
|
127
|
-
const capabilities = Array.isArray(card.capabilities)
|
|
128
|
-
? card.capabilities.map((cap: any) =>
|
|
129
|
-
typeof cap === 'string' ? cap : cap.name
|
|
130
|
-
).join(', ')
|
|
131
|
-
: '';
|
|
132
|
-
|
|
133
|
-
const trustScore = card.trust
|
|
134
|
-
? `${(card.trust.interactionScore * 100).toFixed(0)}%`
|
|
135
|
-
: 'N/A';
|
|
136
|
-
|
|
137
|
-
table.push([
|
|
138
|
-
card.did.substring(0, 35) + '...',
|
|
139
|
-
card.name,
|
|
140
|
-
capabilities.substring(0, 30) + (capabilities.length > 30 ? '...' : ''),
|
|
141
|
-
trustScore,
|
|
142
|
-
]);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
console.log();
|
|
146
|
-
console.log(table.toString());
|
|
147
|
-
|
|
148
|
-
if (options.query && cards.length > 0) {
|
|
149
|
-
const card = cards[0];
|
|
150
|
-
console.log();
|
|
151
|
-
info('Top match details:');
|
|
152
|
-
info(` DID: ${card.did}`);
|
|
153
|
-
info(` Name: ${card.name}`);
|
|
154
|
-
info(` Description: ${card.description}`);
|
|
155
|
-
|
|
156
|
-
if (Array.isArray(card.capabilities) && card.capabilities.length > 0) {
|
|
157
|
-
const cap = card.capabilities[0];
|
|
158
|
-
if (typeof cap === 'object' && cap !== null) {
|
|
159
|
-
info(` Capability: ${cap.name}`);
|
|
160
|
-
info(` ${cap.description}`);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
} else {
|
|
166
|
-
spin.fail('Please specify --did, --capability, or --query');
|
|
167
|
-
info('Usage: hw1 discover --did <did>');
|
|
168
|
-
info(' hw1 discover --capability <capability>');
|
|
169
|
-
info(' hw1 discover --query "translate Japanese"');
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
await relayClient.stop();
|
|
173
|
-
} catch (err) {
|
|
174
|
-
error(`Failed to discover: ${(err as Error).message}`);
|
|
175
|
-
process.exit(1);
|
|
176
|
-
}
|
|
177
|
-
});
|
|
178
|
-
}
|