@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,46 +0,0 @@
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
- .option('--format <fmt>', 'Output format: text|json', 'text')
10
- .action(async (options) => {
11
- try {
12
- const identity = getIdentity();
13
- const card = getAgentCard();
14
-
15
- if (!identity) {
16
- error('No identity found. Run "hw1 init" first.');
17
- process.exit(1);
18
- }
19
-
20
- if (options.format === 'json') {
21
- console.log(JSON.stringify({
22
- did: identity.did,
23
- publicKey: identity.publicKey,
24
- agentCard: card || null,
25
- }, null, 2));
26
- return;
27
- }
28
-
29
- printHeader('Clawiverse Identity');
30
- printKeyValue('DID', identity.did);
31
- printKeyValue('Public Key', identity.publicKey.substring(0, 16) + '...');
32
-
33
- if (card) {
34
- console.log();
35
- printKeyValue('Name', card.name);
36
- printKeyValue('Description', card.description);
37
- printKeyValue('Capabilities', card.capabilities.join(', ') || 'None');
38
- }
39
-
40
- console.log();
41
- } catch (err) {
42
- error(`Failed to show identity: ${(err as Error).message}`);
43
- process.exit(1);
44
- }
45
- });
46
- }
@@ -1,222 +0,0 @@
1
- /**
2
- * Inbox Command - CVP-0010 §2.3
3
- *
4
- * hw1 inbox - List messages (newest first)
5
- * hw1 inbox --unread - Only unread
6
- * hw1 inbox read <id> - Show full message
7
- * hw1 inbox reply <id> - Reply to a message
8
- * hw1 inbox delete <id> - Delete a message
9
- */
10
-
11
- import { Command } from 'commander';
12
- import { DaemonClient } from '../daemon/client.js';
13
- import { createLogger } from '@highway1/core';
14
-
15
- const logger = createLogger('cli:inbox');
16
-
17
- function shortDid(did: string): string {
18
- if (did.startsWith('did:clawiverse:')) return did.slice(15, 29) + '…';
19
- if (did.length > 30) return did.slice(0, 14) + '…' + did.slice(-8);
20
- return did;
21
- }
22
-
23
- function formatAge(ts: number): string {
24
- const secs = Math.floor((Date.now() - ts) / 1000);
25
- if (secs < 60) return `${secs}s ago`;
26
- if (secs < 3600) return `${Math.floor(secs / 60)}m ago`;
27
- if (secs < 86400) return `${Math.floor(secs / 3600)}h ago`;
28
- return `${Math.floor(secs / 86400)}d ago`;
29
- }
30
-
31
- export function createInboxCommand(): Command {
32
- const inbox = new Command('inbox')
33
- .description('Manage your message inbox (CVP-0010 §2.3)')
34
- .option('--unread', 'Show only unread messages')
35
- .option('--from <did>', 'Filter by sender DID')
36
- .option('--protocol <protocol>', 'Filter by protocol')
37
- .option('--limit <n>', 'Max messages to show', '50')
38
- .option('--format <fmt>', 'Output format: text|json', 'text')
39
- .action(async (options) => {
40
- const client = new DaemonClient();
41
- if (!(await client.isDaemonRunning())) {
42
- console.error('Daemon not running. Start with: hw1 join');
43
- process.exit(1);
44
- }
45
-
46
- try {
47
- const filter: any = {};
48
- if (options.unread) filter.unreadOnly = true;
49
- if (options.from) filter.fromDid = options.from;
50
- if (options.protocol) filter.protocol = options.protocol;
51
-
52
- const page = await client.send('inbox', {
53
- filter,
54
- pagination: { limit: parseInt(options.limit, 10) },
55
- });
56
-
57
- if (options.format === 'json') {
58
- console.log(JSON.stringify(page, null, 2));
59
- return;
60
- }
61
-
62
- const unreadCount = page.messages.filter((m: any) => !m.readAt).length;
63
- console.log(`\nInbox (${page.total} total, ${unreadCount} unread)\n`);
64
-
65
- if (page.messages.length === 0) {
66
- console.log(' No messages.');
67
- return;
68
- }
69
-
70
- for (const msg of page.messages) {
71
- const unread = !msg.readAt ? '●' : ' ';
72
- const from = shortDid(msg.envelope.from);
73
- const age = formatAge(msg.receivedAt ?? msg.envelope.timestamp);
74
- const id = msg.envelope.id.slice(-8);
75
- const proto = msg.envelope.protocol ?? '';
76
- const text = typeof msg.envelope.payload === 'object' && msg.envelope.payload !== null
77
- ? (msg.envelope.payload as any).text ?? (msg.envelope.payload as any).message ?? ''
78
- : String(msg.envelope.payload ?? '');
79
-
80
- console.log(`${unread} [${id}] ${from} ${age} ${proto}`);
81
- if (text) {
82
- const preview = String(text).slice(0, 72);
83
- console.log(` ${preview}${text.length > 72 ? '…' : ''}`);
84
- }
85
- }
86
- console.log();
87
- } catch (error) {
88
- logger.error('Inbox list failed', error);
89
- console.error('Error:', (error as Error).message);
90
- process.exit(1);
91
- }
92
- });
93
-
94
- // hw1 inbox read <id>
95
- inbox
96
- .command('read <id>')
97
- .description('Show full message')
98
- .option('--format <fmt>', 'Output format: text|json', 'text')
99
- .action(async (id: string, options) => {
100
- const client = new DaemonClient();
101
- if (!(await client.isDaemonRunning())) {
102
- console.error('Daemon not running. Start with: hw1 join');
103
- process.exit(1);
104
- }
105
-
106
- try {
107
- // Support short IDs: find by suffix
108
- let msg = await client.send('get_message', { id }).catch(() => null);
109
-
110
- if (!msg) {
111
- // Try to find by short suffix via inbox list
112
- const page = await client.send('inbox', { filter: {}, pagination: { limit: 200 } });
113
- const match = page.messages.find((m: any) => m.envelope.id.endsWith(id));
114
- if (match) {
115
- msg = await client.send('get_message', { id: match.envelope.id });
116
- }
117
- }
118
-
119
- if (!msg) {
120
- console.error(`Message not found: ${id}`);
121
- process.exit(1);
122
- }
123
-
124
- if (options.format === 'json') {
125
- console.log(JSON.stringify(msg, null, 2));
126
- } else {
127
- console.log('\n' + '─'.repeat(60));
128
- console.log(`ID: ${msg.envelope.id}`);
129
- console.log(`From: ${msg.envelope.from}`);
130
- console.log(`Protocol: ${msg.envelope.protocol}`);
131
- console.log(`Type: ${msg.envelope.type}`);
132
- console.log(`Received: ${new Date(msg.receivedAt ?? msg.envelope.timestamp).toLocaleString()}`);
133
- if (msg.trustScore != null) {
134
- console.log(`Trust: ${(msg.trustScore * 100).toFixed(0)}%`);
135
- }
136
- console.log('─'.repeat(60));
137
- console.log(JSON.stringify(msg.envelope.payload, null, 2));
138
- console.log();
139
- }
140
-
141
- // Mark as read
142
- await client.send('mark_read', { id: msg.envelope.id });
143
- } catch (error) {
144
- logger.error('Read message failed', error);
145
- console.error('Error:', (error as Error).message);
146
- process.exit(1);
147
- }
148
- });
149
-
150
- // hw1 inbox reply <id> --message "..."
151
- inbox
152
- .command('reply <id>')
153
- .description('Reply to a message')
154
- .requiredOption('-m, --message <text>', 'Reply text')
155
- .option('--protocol <protocol>', 'Protocol to use', 'clawiverse/chat/1.0.0')
156
- .action(async (id: string, options) => {
157
- const client = new DaemonClient();
158
- if (!(await client.isDaemonRunning())) {
159
- console.error('Daemon not running. Start with: hw1 join');
160
- process.exit(1);
161
- }
162
-
163
- try {
164
- // Resolve message
165
- let msg = await client.send('get_message', { id }).catch(() => null);
166
- if (!msg) {
167
- const page = await client.send('inbox', { filter: {}, pagination: { limit: 200 } });
168
- const match = page.messages.find((m: any) => m.envelope.id.endsWith(id));
169
- if (match) msg = await client.send('get_message', { id: match.envelope.id });
170
- }
171
-
172
- if (!msg) {
173
- console.error(`Message not found: ${id}`);
174
- process.exit(1);
175
- }
176
-
177
- const result = await client.send('send', {
178
- to: msg.envelope.from,
179
- protocol: options.protocol,
180
- payload: { text: options.message, replyTo: msg.envelope.id },
181
- type: 'response',
182
- });
183
-
184
- console.log(`Reply sent (${result.id.slice(-8)})`);
185
- } catch (error) {
186
- logger.error('Reply failed', error);
187
- console.error('Error:', (error as Error).message);
188
- process.exit(1);
189
- }
190
- });
191
-
192
- // hw1 inbox delete <id>
193
- inbox
194
- .command('delete <id>')
195
- .description('Delete a message')
196
- .action(async (id: string) => {
197
- const client = new DaemonClient();
198
- if (!(await client.isDaemonRunning())) {
199
- console.error('Daemon not running. Start with: hw1 join');
200
- process.exit(1);
201
- }
202
-
203
- try {
204
- // Resolve short ID
205
- let fullId = id;
206
- if (id.length < 36) {
207
- const page = await client.send('inbox', { filter: {}, pagination: { limit: 200 } });
208
- const match = page.messages.find((m: any) => m.envelope.id.endsWith(id));
209
- if (match) fullId = match.envelope.id;
210
- }
211
-
212
- await client.send('delete_message', { id: fullId });
213
- console.log(`Deleted message ${id}`);
214
- } catch (error) {
215
- logger.error('Delete failed', error);
216
- console.error('Error:', (error as Error).message);
217
- process.exit(1);
218
- }
219
- });
220
-
221
- return inbox;
222
- }
@@ -1,54 +0,0 @@
1
- import { Command } from 'commander';
2
- import { generateKeyPair, exportKeyPair, deriveDID } from '@highway1/core';
3
- import { hasIdentity, setIdentity, setAgentCard } from '../config.js';
4
- import { success, error, spinner, printHeader, printKeyValue } from '../ui.js';
5
-
6
- export function registerInitCommand(program: Command): void {
7
- program
8
- .command('init')
9
- .description('Initialize a new Clawiverse identity')
10
- .option('--name <name>', 'Agent name', 'My Agent')
11
- .option('--description <description>', 'Agent description', 'A Clawiverse agent')
12
- .option('--force', 'Overwrite existing identity')
13
- .action(async (options) => {
14
- try {
15
- printHeader('Initialize Clawiverse Identity');
16
-
17
- if (hasIdentity() && !options.force) {
18
- error('Identity already exists. Use --force to overwrite.');
19
- process.exit(1);
20
- }
21
-
22
- const spin = spinner('Generating key pair...');
23
-
24
- const keyPair = await generateKeyPair();
25
- const exported = exportKeyPair(keyPair);
26
- const did = deriveDID(keyPair.publicKey);
27
-
28
- setIdentity({
29
- did,
30
- publicKey: exported.publicKey,
31
- privateKey: exported.privateKey,
32
- });
33
-
34
- setAgentCard({
35
- name: options.name,
36
- description: options.description,
37
- capabilities: [],
38
- });
39
-
40
- spin.succeed('Identity created successfully!');
41
-
42
- console.log();
43
- printKeyValue('DID', did);
44
- printKeyValue('Name', options.name);
45
- printKeyValue('Description', options.description);
46
- console.log();
47
-
48
- success('Run "hw1 join" to connect to the network');
49
- } catch (err) {
50
- error(`Failed to initialize: ${(err as Error).message}`);
51
- process.exit(1);
52
- }
53
- });
54
- }
@@ -1,198 +0,0 @@
1
- import { Command } from 'commander';
2
- import {
3
- createRelayClient,
4
- createRelayIndexOperations,
5
- createMessageRouter,
6
- importKeyPair,
7
- createAgentCard,
8
- signAgentCard,
9
- sign,
10
- verify,
11
- extractPublicKey,
12
- } from '@highway1/core';
13
- import { getIdentity, getAgentCard } from '../config.js';
14
- import { success, error, spinner, printHeader, info } from '../ui.js';
15
- import { writeFile, mkdir } from 'node:fs/promises';
16
- import { join, resolve } from 'node:path';
17
-
18
- // Default relay URLs (CVP-0011)
19
- const DEFAULT_RELAY_URLS = [
20
- 'ws://relay.highway1.net:8080',
21
- ];
22
-
23
- function getRelayUrls(options: any): string[] {
24
- if (options.relay) return [options.relay];
25
- const envRelays = process.env.HW1_RELAY_URLS;
26
- if (envRelays) return envRelays.split(',').map((u: string) => u.trim());
27
- return DEFAULT_RELAY_URLS;
28
- }
29
-
30
- export function registerJoinCommand(program: Command): void {
31
- program
32
- .command('join')
33
- .description('Join the Clawiverse network via relay')
34
- .option('--relay <url>', 'Relay WebSocket URL (e.g. ws://localhost:8080)')
35
- .option('--save-dir <path>', 'Directory to save received file attachments', './downloads')
36
- .action(async (options) => {
37
- try {
38
- printHeader('Join Clawiverse Network');
39
-
40
- const identity = getIdentity();
41
- const card = getAgentCard();
42
-
43
- if (!identity || !card) {
44
- error('No identity found. Run "hw1 init" first.');
45
- process.exit(1);
46
- }
47
-
48
- const spin = spinner('Connecting to relay...');
49
-
50
- const keyPair = importKeyPair({
51
- publicKey: identity.publicKey,
52
- privateKey: identity.privateKey,
53
- });
54
-
55
- const relayUrls = getRelayUrls(options);
56
-
57
- const capabilities = (card.capabilities ?? []).map((capability: string) => ({
58
- id: capability,
59
- name: capability,
60
- description: `Capability: ${capability}`,
61
- }));
62
-
63
- const agentCard = createAgentCard(
64
- identity.did,
65
- card.name,
66
- card.description,
67
- capabilities,
68
- [],
69
- );
70
-
71
- const signedCard = await signAgentCard(agentCard, (data) =>
72
- sign(data, keyPair.privateKey)
73
- );
74
-
75
- const relayClient = createRelayClient({
76
- relayUrls,
77
- did: identity.did,
78
- keyPair,
79
- card: signedCard,
80
- });
81
-
82
- await relayClient.start();
83
-
84
- spin.succeed('Connected to relay!');
85
-
86
- info(`DID: ${identity.did}`);
87
- info(`Connected relays: ${relayClient.getConnectedRelays().join(', ')}`);
88
- info(`Network peers: ${relayClient.getPeerCount()}`);
89
-
90
- const verifyFn = async (signature: Uint8Array, data: Uint8Array): Promise<boolean> => {
91
- try {
92
- const decoded = JSON.parse(new TextDecoder().decode(data)) as { from?: string };
93
- if (!decoded.from || typeof decoded.from !== 'string') return false;
94
- const senderPublicKey = extractPublicKey(decoded.from);
95
- return verify(signature, data, senderPublicKey);
96
- } catch {
97
- return false;
98
- }
99
- };
100
-
101
- const router = createMessageRouter(relayClient, verifyFn);
102
-
103
- const saveDir = resolve(options.saveDir ?? './downloads');
104
-
105
- const messageHandler = async (envelope: any) => {
106
- const payload = envelope.payload as Record<string, unknown>;
107
- console.log();
108
- success(`>>> Received message from ${envelope.from}`);
109
- info(` Message ID: ${envelope.id}`);
110
- info(` Protocol: ${envelope.protocol}`);
111
- info(` Type: ${envelope.type}`);
112
-
113
- let savedPath: string | undefined;
114
- if (payload.attachment) {
115
- const att = payload.attachment as {
116
- filename: string;
117
- mimeType: string;
118
- size: number;
119
- data: string;
120
- };
121
- try {
122
- await mkdir(saveDir, { recursive: true });
123
- const safeName = att.filename.replace(/[^a-zA-Z0-9._-]/g, '_');
124
- savedPath = join(saveDir, `${envelope.id.slice(-8)}_${safeName}`);
125
- await writeFile(savedPath, Buffer.from(att.data, 'base64'));
126
- success(` Attachment saved: ${savedPath}`);
127
- info(` File: ${att.filename} (${att.size} bytes, ${att.mimeType})`);
128
- } catch (e) {
129
- error(` Failed to save attachment: ${(e as Error).message}`);
130
- }
131
- const displayPayload = { ...payload, attachment: '[binary data omitted]' };
132
- info(` Payload: ${JSON.stringify(displayPayload, null, 2)}`);
133
- } else {
134
- info(` Payload: ${JSON.stringify(payload, null, 2)}`);
135
- }
136
- console.log();
137
-
138
- if (envelope.type === 'request') {
139
- info(' Sending acknowledgment response...');
140
-
141
- const { createEnvelope, signEnvelope, sign } = await import('@highway1/core');
142
- const identity = getIdentity();
143
- const keyPair = importKeyPair({
144
- publicKey: identity!.publicKey,
145
- privateKey: identity!.privateKey,
146
- });
147
-
148
- const responseEnvelope = createEnvelope(
149
- envelope.to,
150
- envelope.from,
151
- 'response',
152
- envelope.protocol,
153
- {
154
- status: 'received',
155
- message: 'Message received and processed',
156
- savedPath: savedPath ?? null,
157
- timestamp: Date.now(),
158
- },
159
- envelope.id
160
- );
161
-
162
- const signedResponse = await signEnvelope(responseEnvelope, (data) =>
163
- sign(data, keyPair.privateKey)
164
- );
165
-
166
- return signedResponse;
167
- }
168
-
169
- return undefined;
170
- };
171
-
172
- router.registerHandler('/clawiverse/msg/1.0.0', messageHandler);
173
- router.registerHandler('/clawiverse/greet/1.0.0', messageHandler);
174
- router.registerCatchAllHandler(messageHandler);
175
-
176
- await router.start();
177
-
178
- console.log();
179
- success('Successfully joined the Clawiverse network!');
180
- info('Listening for incoming messages...');
181
- info('Press Ctrl+C to stop');
182
-
183
- process.on('SIGINT', async () => {
184
- console.log();
185
- const stopSpin = spinner('Stopping...');
186
- await router.stop();
187
- await relayClient.stop();
188
- stopSpin.succeed('Disconnected');
189
- process.exit(0);
190
- });
191
-
192
- await new Promise(() => {});
193
- } catch (err) {
194
- error(`Failed to join network: ${(err as Error).message}`);
195
- process.exit(1);
196
- }
197
- });
198
- }
@@ -1,85 +0,0 @@
1
- /**
2
- * Peers Command - CVP-0010 §2.4
3
- *
4
- * hw1 peers - List online agents
5
- * hw1 peers --capability translate - Filter by capability
6
- * hw1 peers --format json
7
- */
8
-
9
- import { Command } from 'commander';
10
- import { DaemonClient } from '../daemon/client.js';
11
- import { createLogger } from '@highway1/core';
12
-
13
- const logger = createLogger('cli:peers');
14
-
15
- export function registerPeersCommand(program: Command): void {
16
- program
17
- .command('peers')
18
- .description('List agents on the network')
19
- .option('--capability <cap>', 'Filter by capability')
20
- .option('--query <text>', 'Natural language search')
21
- .option('--min-trust <score>', 'Minimum trust score (0-1)')
22
- .option('--limit <n>', 'Max results', '20')
23
- .option('--format <fmt>', 'Output format: text|json', 'text')
24
- .action(async (options) => {
25
- const client = new DaemonClient();
26
- if (!(await client.isDaemonRunning())) {
27
- console.error('Daemon not running. Start with: hw1 join');
28
- process.exit(1);
29
- }
30
-
31
- try {
32
- const searchQuery = options.query ?? options.capability ?? '';
33
- const params: any = { query: searchQuery };
34
- if (options.minTrust) {
35
- params.filters = { minTrustScore: parseFloat(options.minTrust) };
36
- }
37
-
38
- const results = await client.send('discover', params);
39
- const agents: any[] = results ?? [];
40
-
41
- // Filter by capability if specified
42
- const filtered = options.capability
43
- ? agents.filter((a: any) => {
44
- const caps: any[] = a.capabilities ?? [];
45
- return caps.some((c: any) => {
46
- const name = typeof c === 'string' ? c : c.name ?? c.id ?? '';
47
- return name.toLowerCase().includes(options.capability.toLowerCase());
48
- });
49
- })
50
- : agents;
51
-
52
- const limited = filtered.slice(0, parseInt(options.limit, 10));
53
-
54
- if (options.format === 'json') {
55
- console.log(JSON.stringify(limited, null, 2));
56
- return;
57
- }
58
-
59
- if (limited.length === 0) {
60
- console.log('No agents found.');
61
- return;
62
- }
63
-
64
- console.log(`\nPeers (${limited.length} found)\n`);
65
- for (const agent of limited) {
66
- const trust = agent.trust?.interactionScore ?? 0;
67
- const trustStr = `${(trust * 100).toFixed(0)}%`;
68
- const caps = (agent.capabilities ?? [])
69
- .map((c: any) => typeof c === 'string' ? c : c.name ?? c.id ?? '')
70
- .filter(Boolean)
71
- .slice(0, 3)
72
- .join(', ');
73
- const shortDid = agent.did?.slice(0, 30) + '…';
74
- console.log(` ${agent.name ?? shortDid} trust:${trustStr}`);
75
- if (caps) console.log(` capabilities: ${caps}`);
76
- console.log(` did: ${agent.did}`);
77
- }
78
- console.log();
79
- } catch (err) {
80
- logger.error('Peers failed', err);
81
- console.error('Error:', (err as Error).message);
82
- process.exit(1);
83
- }
84
- });
85
- }