@tjamescouch/agentchat 0.22.1 → 0.23.1

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.
Files changed (153) hide show
  1. package/Dockerfile +1 -1
  2. package/dist/bin/agentchat.d.ts +7 -0
  3. package/dist/bin/agentchat.d.ts.map +1 -0
  4. package/dist/bin/agentchat.js +1511 -0
  5. package/dist/bin/agentchat.js.map +1 -0
  6. package/dist/lib/allowlist.d.ts +77 -0
  7. package/dist/lib/allowlist.d.ts.map +1 -0
  8. package/dist/lib/allowlist.js +151 -0
  9. package/dist/lib/allowlist.js.map +1 -0
  10. package/dist/lib/client.d.ts +147 -0
  11. package/dist/lib/client.d.ts.map +1 -0
  12. package/dist/lib/client.js +704 -0
  13. package/dist/lib/client.js.map +1 -0
  14. package/dist/lib/daemon.d.ts +122 -0
  15. package/dist/lib/daemon.d.ts.map +1 -0
  16. package/dist/lib/daemon.js +523 -0
  17. package/dist/lib/daemon.js.map +1 -0
  18. package/dist/lib/deploy/akash.d.ts +271 -0
  19. package/dist/lib/deploy/akash.d.ts.map +1 -0
  20. package/dist/lib/deploy/akash.js +671 -0
  21. package/dist/lib/deploy/akash.js.map +1 -0
  22. package/dist/lib/deploy/config.d.ts +62 -0
  23. package/dist/lib/deploy/config.d.ts.map +1 -0
  24. package/dist/lib/deploy/config.js +116 -0
  25. package/dist/lib/deploy/config.js.map +1 -0
  26. package/dist/lib/deploy/docker.d.ts +37 -0
  27. package/dist/lib/deploy/docker.d.ts.map +1 -0
  28. package/dist/lib/deploy/docker.js +122 -0
  29. package/dist/lib/deploy/docker.js.map +1 -0
  30. package/dist/lib/deploy/index.d.ts +11 -0
  31. package/dist/lib/deploy/index.d.ts.map +1 -0
  32. package/dist/lib/deploy/index.js +11 -0
  33. package/dist/lib/deploy/index.js.map +1 -0
  34. package/dist/lib/escrow-hooks.d.ts +199 -0
  35. package/dist/lib/escrow-hooks.d.ts.map +1 -0
  36. package/dist/lib/escrow-hooks.js +221 -0
  37. package/dist/lib/escrow-hooks.js.map +1 -0
  38. package/dist/lib/identity.d.ts +134 -0
  39. package/dist/lib/identity.d.ts.map +1 -0
  40. package/dist/lib/identity.js +334 -0
  41. package/dist/lib/identity.js.map +1 -0
  42. package/dist/lib/jitter.d.ts +42 -0
  43. package/dist/lib/jitter.d.ts.map +1 -0
  44. package/{lib/jitter.ts → dist/lib/jitter.js} +10 -18
  45. package/dist/lib/jitter.js.map +1 -0
  46. package/dist/lib/proposals.d.ts +223 -0
  47. package/dist/lib/proposals.d.ts.map +1 -0
  48. package/dist/lib/proposals.js +379 -0
  49. package/dist/lib/proposals.js.map +1 -0
  50. package/dist/lib/protocol.d.ts +220 -0
  51. package/dist/lib/protocol.d.ts.map +1 -0
  52. package/dist/lib/protocol.js +507 -0
  53. package/dist/lib/protocol.js.map +1 -0
  54. package/dist/lib/receipts.d.ts +134 -0
  55. package/dist/lib/receipts.d.ts.map +1 -0
  56. package/dist/lib/receipts.js +270 -0
  57. package/dist/lib/receipts.js.map +1 -0
  58. package/dist/lib/reputation.d.ts +250 -0
  59. package/dist/lib/reputation.d.ts.map +1 -0
  60. package/dist/lib/reputation.js +586 -0
  61. package/dist/lib/reputation.js.map +1 -0
  62. package/dist/lib/security.d.ts +27 -0
  63. package/dist/lib/security.d.ts.map +1 -0
  64. package/dist/lib/security.js +150 -0
  65. package/dist/lib/security.js.map +1 -0
  66. package/dist/lib/server/handlers/admin.d.ts +26 -0
  67. package/dist/lib/server/handlers/admin.d.ts.map +1 -0
  68. package/dist/lib/server/handlers/admin.js +76 -0
  69. package/dist/lib/server/handlers/admin.js.map +1 -0
  70. package/dist/lib/server/handlers/identity.d.ts +36 -0
  71. package/dist/lib/server/handlers/identity.d.ts.map +1 -0
  72. package/dist/lib/server/handlers/identity.js +330 -0
  73. package/dist/lib/server/handlers/identity.js.map +1 -0
  74. package/dist/lib/server/handlers/index.d.ts +10 -0
  75. package/dist/lib/server/handlers/index.d.ts.map +1 -0
  76. package/dist/lib/server/handlers/index.js +15 -0
  77. package/dist/lib/server/handlers/index.js.map +1 -0
  78. package/dist/lib/server/handlers/message.d.ts +47 -0
  79. package/dist/lib/server/handlers/message.d.ts.map +1 -0
  80. package/dist/lib/server/handlers/message.js +265 -0
  81. package/dist/lib/server/handlers/message.js.map +1 -0
  82. package/dist/lib/server/handlers/presence.d.ts +18 -0
  83. package/dist/lib/server/handlers/presence.d.ts.map +1 -0
  84. package/dist/lib/server/handlers/presence.js +35 -0
  85. package/dist/lib/server/handlers/presence.js.map +1 -0
  86. package/dist/lib/server/handlers/proposal.d.ts +38 -0
  87. package/dist/lib/server/handlers/proposal.d.ts.map +1 -0
  88. package/dist/lib/server/handlers/proposal.js +273 -0
  89. package/dist/lib/server/handlers/proposal.js.map +1 -0
  90. package/dist/lib/server/handlers/skills.d.ts +22 -0
  91. package/dist/lib/server/handlers/skills.d.ts.map +1 -0
  92. package/dist/lib/server/handlers/skills.js +119 -0
  93. package/dist/lib/server/handlers/skills.js.map +1 -0
  94. package/dist/lib/server-directory.d.ts +85 -0
  95. package/dist/lib/server-directory.d.ts.map +1 -0
  96. package/dist/lib/server-directory.js +177 -0
  97. package/dist/lib/server-directory.js.map +1 -0
  98. package/dist/lib/server.d.ts +162 -0
  99. package/dist/lib/server.d.ts.map +1 -0
  100. package/dist/lib/server.js +602 -0
  101. package/dist/lib/server.js.map +1 -0
  102. package/dist/lib/types.d.ts +461 -0
  103. package/dist/lib/types.d.ts.map +1 -0
  104. package/dist/lib/types.js +98 -0
  105. package/dist/lib/types.js.map +1 -0
  106. package/package.json +22 -13
  107. package/bin/agentchat.js +0 -1617
  108. package/bin/agentchat.ts +0 -1812
  109. package/lib/allowlist.js +0 -162
  110. package/lib/chat.py +0 -241
  111. package/lib/client.js +0 -821
  112. package/lib/client.ts +0 -877
  113. package/lib/daemon.js +0 -562
  114. package/lib/daemon.ts +0 -662
  115. package/lib/deploy/akash.js +0 -811
  116. package/lib/deploy/config.js +0 -128
  117. package/lib/deploy/docker.js +0 -132
  118. package/lib/deploy/index.js +0 -24
  119. package/lib/elo_swarm.py +0 -569
  120. package/lib/escrow-hooks.js +0 -237
  121. package/lib/escrow-hooks.ts +0 -391
  122. package/lib/identity.js +0 -376
  123. package/lib/identity.ts +0 -412
  124. package/lib/jitter.js +0 -54
  125. package/lib/proposals.js +0 -426
  126. package/lib/proposals.ts +0 -612
  127. package/lib/protocol.js +0 -516
  128. package/lib/receipts.js +0 -294
  129. package/lib/receipts.ts +0 -359
  130. package/lib/reputation.js +0 -664
  131. package/lib/reputation.ts +0 -790
  132. package/lib/security.js +0 -183
  133. package/lib/server/handlers/admin.js +0 -94
  134. package/lib/server/handlers/identity.js +0 -258
  135. package/lib/server/handlers/index.js +0 -42
  136. package/lib/server/handlers/message.js +0 -319
  137. package/lib/server/handlers/presence.js +0 -45
  138. package/lib/server/handlers/proposal.js +0 -358
  139. package/lib/server/handlers/skills.js +0 -141
  140. package/lib/server-directory.js +0 -190
  141. package/lib/server-directory.ts +0 -232
  142. package/lib/server.js +0 -633
  143. package/lib/server.ts +0 -698
  144. package/lib/supervisor/USAGE.md +0 -110
  145. package/lib/supervisor/agent-health.sh +0 -107
  146. package/lib/supervisor/agent-monitor.sh +0 -123
  147. package/lib/supervisor/agent-supervisor.sh +0 -135
  148. package/lib/supervisor/agentctl.sh +0 -266
  149. package/lib/supervisor/god-backup.sh +0 -126
  150. package/lib/supervisor/god-watchdog.sh +0 -107
  151. package/lib/supervisor/killswitch.sh +0 -43
  152. package/lib/supervisor/notify.sh +0 -19
  153. package/lib/types.ts +0 -433
package/bin/agentchat.js DELETED
@@ -1,1617 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * AgentChat CLI
5
- * Command-line interface for agent-to-agent communication
6
- */
7
-
8
- import { program } from 'commander';
9
- import fs from 'fs/promises';
10
- import path from 'path';
11
- import { AgentChatClient, quickSend, listen } from '../lib/client.js';
12
- import { startServer } from '../lib/server.js';
13
- import { Identity, DEFAULT_IDENTITY_PATH } from '../lib/identity.js';
14
- import {
15
- AgentChatDaemon,
16
- isDaemonRunning,
17
- stopDaemon,
18
- getDaemonStatus,
19
- listDaemons,
20
- stopAllDaemons,
21
- getDaemonPaths,
22
- DEFAULT_CHANNELS,
23
- DEFAULT_INSTANCE
24
- } from '../lib/daemon.js';
25
- import {
26
- deployToDocker,
27
- generateDockerfile,
28
- generateWallet,
29
- checkBalance,
30
- generateAkashSDL,
31
- createDeployment,
32
- listDeployments,
33
- closeDeployment,
34
- queryBids,
35
- acceptBid,
36
- getDeploymentStatus,
37
- AkashWallet,
38
- AKASH_WALLET_PATH
39
- } from '../lib/deploy/index.js';
40
- import { loadConfig, DEFAULT_CONFIG, generateExampleConfig } from '../lib/deploy/config.js';
41
- import {
42
- ReceiptStore,
43
- DEFAULT_RECEIPTS_PATH,
44
- readReceipts
45
- } from '../lib/receipts.js';
46
- import {
47
- ReputationStore,
48
- DEFAULT_RATINGS_PATH,
49
- DEFAULT_RATING
50
- } from '../lib/reputation.js';
51
- import {
52
- ServerDirectory,
53
- DEFAULT_DIRECTORY_PATH
54
- } from '../lib/server-directory.js';
55
- import { enforceDirectorySafety, checkDirectorySafety } from '../lib/security.js';
56
-
57
- program
58
- .name('agentchat')
59
- .description('Real-time communication protocol for AI agents')
60
- .version('0.1.0');
61
-
62
- // Server command
63
- program
64
- .command('serve')
65
- .description('Start an agentchat relay server')
66
- .option('-p, --port <port>', 'Port to listen on', '6667')
67
- .option('-H, --host <host>', 'Host to bind to', '0.0.0.0')
68
- .option('-n, --name <name>', 'Server name', 'agentchat')
69
- .option('--log-messages', 'Log all messages (for debugging)')
70
- .option('--cert <file>', 'TLS certificate file (PEM format)')
71
- .option('--key <file>', 'TLS private key file (PEM format)')
72
- .option('--buffer-size <n>', 'Message buffer size per channel for replay on join', '20')
73
- .action((options) => {
74
- // Validate TLS options (both or neither)
75
- if ((options.cert && !options.key) || (!options.cert && options.key)) {
76
- console.error('Error: Both --cert and --key must be provided for TLS');
77
- process.exit(1);
78
- }
79
-
80
- startServer({
81
- port: parseInt(options.port),
82
- host: options.host,
83
- name: options.name,
84
- logMessages: options.logMessages,
85
- cert: options.cert,
86
- key: options.key,
87
- messageBufferSize: parseInt(options.bufferSize)
88
- });
89
- });
90
-
91
- // Send command (fire-and-forget)
92
- program
93
- .command('send <server> <target> <message>')
94
- .description('Send a message and disconnect (fire-and-forget)')
95
- .option('-n, --name <name>', 'Agent name', `agent-${process.pid}`)
96
- .option('-i, --identity <file>', 'Path to identity file')
97
- .action(async (server, target, message, options) => {
98
- try {
99
- await quickSend(server, options.name, target, message, options.identity);
100
- console.log('Message sent');
101
- process.exit(0);
102
- } catch (err) {
103
- if (err.code === 'ECONNREFUSED') {
104
- console.error('Error: Connection refused. Is the server running?');
105
- } else {
106
- console.error('Error:', err.message || err.code || err);
107
- }
108
- process.exit(1);
109
- }
110
- });
111
-
112
- // Listen command (stream messages to stdout)
113
- program
114
- .command('listen <server> [channels...]')
115
- .description('Connect and stream messages as JSON lines')
116
- .option('-n, --name <name>', 'Agent name', `agent-${process.pid}`)
117
- .option('-i, --identity <file>', 'Path to identity file')
118
- .option('-m, --max-messages <n>', 'Disconnect after receiving n messages (recommended for agents)')
119
- .action(async (server, channels, options) => {
120
- try {
121
- // Default to #general if no channels specified
122
- if (!channels || channels.length === 0) {
123
- channels = ['#general'];
124
- }
125
-
126
- let messageCount = 0;
127
- const maxMessages = options.maxMessages ? parseInt(options.maxMessages) : null;
128
-
129
- const client = await listen(server, options.name, channels, (msg) => {
130
- console.log(JSON.stringify(msg));
131
- messageCount++;
132
-
133
- if (maxMessages && messageCount >= maxMessages) {
134
- console.error(`Received ${maxMessages} messages, disconnecting`);
135
- client.disconnect();
136
- process.exit(0);
137
- }
138
- }, options.identity);
139
-
140
- console.error(`Connected as ${client.agentId}`);
141
- console.error(`Joined: ${channels.join(', ')}`);
142
- if (maxMessages) {
143
- console.error(`Will disconnect after ${maxMessages} messages`);
144
- } else {
145
- console.error('Streaming messages to stdout (Ctrl+C to stop)');
146
- }
147
-
148
- process.on('SIGINT', () => {
149
- client.disconnect();
150
- process.exit(0);
151
- });
152
-
153
- } catch (err) {
154
- if (err.code === 'ECONNREFUSED') {
155
- console.error('Error: Connection refused. Is the server running?');
156
- console.error(` Try: agentchat serve --port 8080`);
157
- } else {
158
- console.error('Error:', err.message || err.code || err);
159
- }
160
- process.exit(1);
161
- }
162
- });
163
-
164
- // Channels command (list available channels)
165
- program
166
- .command('channels <server>')
167
- .description('List available channels on a server')
168
- .option('-n, --name <name>', 'Agent name', `agent-${process.pid}`)
169
- .option('-i, --identity <file>', 'Path to identity file')
170
- .action(async (server, options) => {
171
- try {
172
- const client = new AgentChatClient({ server, name: options.name, identity: options.identity });
173
- await client.connect();
174
-
175
- const channels = await client.listChannels();
176
-
177
- console.log('Available channels:');
178
- for (const ch of channels) {
179
- console.log(` ${ch.name} (${ch.agents} agents)`);
180
- }
181
-
182
- client.disconnect();
183
- process.exit(0);
184
- } catch (err) {
185
- console.error('Error:', err.message);
186
- process.exit(1);
187
- }
188
- });
189
-
190
- // Agents command (list agents in a channel)
191
- program
192
- .command('agents <server> <channel>')
193
- .description('List agents in a channel')
194
- .option('-n, --name <name>', 'Agent name', `agent-${process.pid}`)
195
- .option('-i, --identity <file>', 'Path to identity file')
196
- .action(async (server, channel, options) => {
197
- try {
198
- const client = new AgentChatClient({ server, name: options.name, identity: options.identity });
199
- await client.connect();
200
-
201
- const agents = await client.listAgents(channel);
202
-
203
- console.log(`Agents in ${channel}:`);
204
- for (const agent of agents) {
205
- const status = agent.status_text ? ` - ${agent.status_text}` : '';
206
- console.log(` ${agent.id} (${agent.name}) [${agent.presence}]${status}`);
207
- }
208
-
209
- client.disconnect();
210
- process.exit(0);
211
- } catch (err) {
212
- console.error('Error:', err.message);
213
- process.exit(1);
214
- }
215
- });
216
-
217
- // Create channel command
218
- program
219
- .command('create <server> <channel>')
220
- .description('Create a new channel')
221
- .option('-n, --name <name>', 'Agent name', `agent-${process.pid}`)
222
- .option('-i, --identity <file>', 'Path to identity file')
223
- .option('-p, --private', 'Make channel invite-only')
224
- .action(async (server, channel, options) => {
225
- try {
226
- const client = new AgentChatClient({ server, name: options.name, identity: options.identity });
227
- await client.connect();
228
-
229
- await client.createChannel(channel, options.private);
230
- console.log(`Created ${channel}${options.private ? ' (invite-only)' : ''}`);
231
-
232
- client.disconnect();
233
- process.exit(0);
234
- } catch (err) {
235
- console.error('Error:', err.message);
236
- process.exit(1);
237
- }
238
- });
239
-
240
- // Invite command
241
- program
242
- .command('invite <server> <channel> <agent>')
243
- .description('Invite an agent to a private channel')
244
- .option('-n, --name <name>', 'Agent name', `agent-${process.pid}`)
245
- .option('-i, --identity <file>', 'Path to identity file')
246
- .action(async (server, channel, agent, options) => {
247
- try {
248
- const client = new AgentChatClient({ server, name: options.name, identity: options.identity });
249
- await client.connect();
250
- await client.join(channel);
251
-
252
- await client.invite(channel, agent);
253
- console.log(`Invited ${agent} to ${channel}`);
254
-
255
- client.disconnect();
256
- process.exit(0);
257
- } catch (err) {
258
- console.error('Error:', err.message);
259
- process.exit(1);
260
- }
261
- });
262
-
263
- // Propose command
264
- program
265
- .command('propose <server> <agent> <task>')
266
- .description('Send a work proposal to another agent')
267
- .option('-i, --identity <file>', 'Path to identity file (required)', DEFAULT_IDENTITY_PATH)
268
- .option('-a, --amount <n>', 'Payment amount')
269
- .option('-c, --currency <code>', 'Currency (SOL, USDC, AKT, etc)')
270
- .option('-p, --payment-code <code>', 'Your payment code (BIP47, address)')
271
- .option('-e, --expires <seconds>', 'Expiration time in seconds', '300')
272
- .option('-t, --terms <terms>', 'Additional terms')
273
- .option('-s, --elo-stake <n>', 'ELO points to stake on this proposal')
274
- .action(async (server, agent, task, options) => {
275
- try {
276
- const client = new AgentChatClient({ server, identity: options.identity });
277
- await client.connect();
278
-
279
- const proposal = await client.propose(agent, {
280
- task,
281
- amount: options.amount ? parseFloat(options.amount) : undefined,
282
- currency: options.currency,
283
- payment_code: options.paymentCode,
284
- terms: options.terms,
285
- expires: parseInt(options.expires),
286
- elo_stake: options.eloStake ? parseInt(options.eloStake) : undefined
287
- });
288
-
289
- console.log('Proposal sent:');
290
- console.log(` ID: ${proposal.id}`);
291
- console.log(` To: ${proposal.to}`);
292
- console.log(` Task: ${proposal.task}`);
293
- if (proposal.amount) console.log(` Amount: ${proposal.amount} ${proposal.currency || ''}`);
294
- if (proposal.elo_stake) console.log(` ELO Stake: ${proposal.elo_stake}`);
295
- if (proposal.expires) console.log(` Expires: ${new Date(proposal.expires).toISOString()}`);
296
- console.log(`\nUse this ID to track responses.`);
297
-
298
- client.disconnect();
299
- process.exit(0);
300
- } catch (err) {
301
- console.error('Error:', err.message);
302
- process.exit(1);
303
- }
304
- });
305
-
306
- // Accept proposal command
307
- program
308
- .command('accept <server> <proposal_id>')
309
- .description('Accept a proposal')
310
- .option('-i, --identity <file>', 'Path to identity file (required)', DEFAULT_IDENTITY_PATH)
311
- .option('-p, --payment-code <code>', 'Your payment code for receiving payment')
312
- .option('-s, --elo-stake <n>', 'ELO points to stake (as acceptor)')
313
- .action(async (server, proposalId, options) => {
314
- try {
315
- const client = new AgentChatClient({ server, identity: options.identity });
316
- await client.connect();
317
-
318
- const eloStake = options.eloStake ? parseInt(options.eloStake) : undefined;
319
- const response = await client.accept(proposalId, options.paymentCode, eloStake);
320
-
321
- console.log('Proposal accepted:');
322
- console.log(` Proposal ID: ${response.proposal_id}`);
323
- console.log(` Status: ${response.status}`);
324
- if (response.proposer_stake) console.log(` Proposer Stake: ${response.proposer_stake} ELO`);
325
- if (response.acceptor_stake) console.log(` Your Stake: ${response.acceptor_stake} ELO`);
326
-
327
- client.disconnect();
328
- process.exit(0);
329
- } catch (err) {
330
- console.error('Error:', err.message);
331
- process.exit(1);
332
- }
333
- });
334
-
335
- // Reject proposal command
336
- program
337
- .command('reject <server> <proposal_id>')
338
- .description('Reject a proposal')
339
- .option('-i, --identity <file>', 'Path to identity file (required)', DEFAULT_IDENTITY_PATH)
340
- .option('-r, --reason <reason>', 'Reason for rejection')
341
- .action(async (server, proposalId, options) => {
342
- try {
343
- const client = new AgentChatClient({ server, identity: options.identity });
344
- await client.connect();
345
-
346
- const response = await client.reject(proposalId, options.reason);
347
-
348
- console.log('Proposal rejected:');
349
- console.log(` Proposal ID: ${response.proposal_id}`);
350
- console.log(` Status: ${response.status}`);
351
-
352
- client.disconnect();
353
- process.exit(0);
354
- } catch (err) {
355
- console.error('Error:', err.message);
356
- process.exit(1);
357
- }
358
- });
359
-
360
- // Complete proposal command
361
- program
362
- .command('complete <server> <proposal_id>')
363
- .description('Mark a proposal as complete')
364
- .option('-i, --identity <file>', 'Path to identity file (required)', DEFAULT_IDENTITY_PATH)
365
- .option('-p, --proof <proof>', 'Proof of completion (tx hash, URL, etc)')
366
- .action(async (server, proposalId, options) => {
367
- try {
368
- const client = new AgentChatClient({ server, identity: options.identity });
369
- await client.connect();
370
-
371
- const response = await client.complete(proposalId, options.proof);
372
-
373
- console.log('Proposal completed:');
374
- console.log(` Proposal ID: ${response.proposal_id}`);
375
- console.log(` Status: ${response.status}`);
376
-
377
- client.disconnect();
378
- process.exit(0);
379
- } catch (err) {
380
- console.error('Error:', err.message);
381
- process.exit(1);
382
- }
383
- });
384
-
385
- // Dispute proposal command
386
- program
387
- .command('dispute <server> <proposal_id> <reason>')
388
- .description('Dispute a proposal')
389
- .option('-i, --identity <file>', 'Path to identity file (required)', DEFAULT_IDENTITY_PATH)
390
- .action(async (server, proposalId, reason, options) => {
391
- try {
392
- const client = new AgentChatClient({ server, identity: options.identity });
393
- await client.connect();
394
-
395
- const response = await client.dispute(proposalId, reason);
396
-
397
- console.log('Proposal disputed:');
398
- console.log(` Proposal ID: ${response.proposal_id}`);
399
- console.log(` Status: ${response.status}`);
400
- console.log(` Reason: ${reason}`);
401
-
402
- client.disconnect();
403
- process.exit(0);
404
- } catch (err) {
405
- console.error('Error:', err.message);
406
- process.exit(1);
407
- }
408
- });
409
-
410
- // Verify agent identity command
411
- program
412
- .command('verify <server> <agent>')
413
- .description('Verify another agent\'s identity via challenge-response')
414
- .option('-i, --identity <file>', 'Path to identity file (required)', DEFAULT_IDENTITY_PATH)
415
- .action(async (server, agent, options) => {
416
- try {
417
- const client = new AgentChatClient({ server, identity: options.identity });
418
- await client.connect();
419
-
420
- console.log(`Verifying identity of ${agent}...`);
421
-
422
- const result = await client.verify(agent);
423
-
424
- if (result.verified) {
425
- console.log('Identity verified!');
426
- console.log(` Agent: ${result.agent}`);
427
- console.log(` Public Key:`);
428
- console.log(result.pubkey.split('\n').map(line => ` ${line}`).join('\n'));
429
- } else {
430
- console.log('Verification failed!');
431
- console.log(` Target: ${result.target}`);
432
- console.log(` Reason: ${result.reason}`);
433
- }
434
-
435
- client.disconnect();
436
- process.exit(result.verified ? 0 : 1);
437
- } catch (err) {
438
- console.error('Error:', err.message);
439
- process.exit(1);
440
- }
441
- });
442
-
443
- // Identity management command
444
- program
445
- .command('identity')
446
- .description('Manage agent identity (Ed25519 keypair)')
447
- .option('-g, --generate', 'Generate new keypair')
448
- .option('-s, --show', 'Show current identity')
449
- .option('-e, --export', 'Export public key for sharing (JSON to stdout)')
450
- .option('-r, --rotate', 'Rotate to new keypair (signs new key with old key)')
451
- .option('--verify-chain', 'Verify the rotation chain')
452
- .option('--revoke [reason]', 'Generate signed revocation notice (outputs JSON)')
453
- .option('--verify-revocation <file>', 'Verify a revocation notice file')
454
- .option('-f, --file <path>', 'Identity file path', DEFAULT_IDENTITY_PATH)
455
- .option('-n, --name <name>', 'Agent name (for --generate)', `agent-${process.pid}`)
456
- .option('--force', 'Overwrite existing identity')
457
- .action(async (options) => {
458
- try {
459
- if (options.generate) {
460
- // Check if identity already exists
461
- const exists = await Identity.exists(options.file);
462
- if (exists && !options.force) {
463
- console.error(`Identity already exists at ${options.file}`);
464
- console.error('Use --force to overwrite');
465
- process.exit(1);
466
- }
467
-
468
- // Generate new identity
469
- const identity = Identity.generate(options.name);
470
- await identity.save(options.file);
471
-
472
- console.log('Generated new identity:');
473
- console.log(` Name: ${identity.name}`);
474
- console.log(` Fingerprint: ${identity.getFingerprint()}`);
475
- console.log(` Agent ID: ${identity.getAgentId()}`);
476
- console.log(` Saved to: ${options.file}`);
477
-
478
- } else if (options.show) {
479
- // Load and display identity
480
- const identity = await Identity.load(options.file);
481
-
482
- console.log('Current identity:');
483
- console.log(` Name: ${identity.name}`);
484
- console.log(` Fingerprint: ${identity.getFingerprint()}`);
485
- console.log(` Agent ID: ${identity.getAgentId()}`);
486
- console.log(` Created: ${identity.created}`);
487
- console.log(` File: ${options.file}`);
488
-
489
- } else if (options.export) {
490
- // Export public key info
491
- const identity = await Identity.load(options.file);
492
- console.log(JSON.stringify(identity.export(), null, 2));
493
-
494
- } else if (options.rotate) {
495
- // Rotate to new keypair
496
- const identity = await Identity.load(options.file);
497
- const oldAgentId = identity.getAgentId();
498
- const oldFingerprint = identity.getFingerprint();
499
-
500
- console.log('Rotating identity...');
501
- console.log(` Old Agent ID: ${oldAgentId}`);
502
- console.log(` Old Fingerprint: ${oldFingerprint}`);
503
-
504
- const record = identity.rotate();
505
- await identity.save(options.file);
506
-
507
- console.log('');
508
- console.log('Rotation complete:');
509
- console.log(` New Agent ID: ${identity.getAgentId()}`);
510
- console.log(` New Fingerprint: ${identity.getFingerprint()}`);
511
- console.log(` Total rotations: ${identity.rotations.length}`);
512
- console.log('');
513
- console.log('The new key has been signed by the old key for chain of custody.');
514
- console.log('Share the rotation record to prove key continuity.');
515
-
516
- } else if (options.verifyChain) {
517
- // Verify rotation chain
518
- const identity = await Identity.load(options.file);
519
-
520
- if (identity.rotations.length === 0) {
521
- console.log('No rotations to verify (original identity).');
522
- console.log(` Agent ID: ${identity.getAgentId()}`);
523
- process.exit(0);
524
- }
525
-
526
- console.log(`Verifying rotation chain (${identity.rotations.length} rotation(s))...`);
527
- const result = identity.verifyRotationChain();
528
-
529
- if (result.valid) {
530
- console.log('Chain verified successfully!');
531
- console.log(` Original Agent ID: ${identity.getOriginalAgentId()}`);
532
- console.log(` Current Agent ID: ${identity.getAgentId()}`);
533
- console.log(` Rotations: ${identity.rotations.length}`);
534
- } else {
535
- console.error('Chain verification FAILED:');
536
- for (const error of result.errors) {
537
- console.error(` - ${error}`);
538
- }
539
- process.exit(1);
540
- }
541
-
542
- } else if (options.revoke) {
543
- // Generate revocation notice
544
- const identity = await Identity.load(options.file);
545
- const reason = typeof options.revoke === 'string' ? options.revoke : 'revoked';
546
-
547
- console.error(`Generating revocation notice for identity...`);
548
- console.error(` Agent ID: ${identity.getAgentId()}`);
549
- console.error(` Reason: ${reason}`);
550
- console.error('');
551
- console.error('WARNING: Publishing this notice declares your key as untrusted.');
552
- console.error('');
553
-
554
- const notice = identity.revoke(reason);
555
- console.log(JSON.stringify(notice, null, 2));
556
-
557
- } else if (options.verifyRevocation) {
558
- // Verify a revocation notice file
559
- const noticeData = await fs.readFile(options.verifyRevocation, 'utf-8');
560
- const notice = JSON.parse(noticeData);
561
-
562
- console.log('Verifying revocation notice...');
563
- const isValid = Identity.verifyRevocation(notice);
564
-
565
- if (isValid) {
566
- console.log('Revocation notice is VALID');
567
- console.log(` Agent ID: ${notice.agent_id}`);
568
- console.log(` Fingerprint: ${notice.fingerprint}`);
569
- console.log(` Reason: ${notice.reason}`);
570
- console.log(` Timestamp: ${notice.timestamp}`);
571
- if (notice.original_agent_id) {
572
- console.log(` Original Agent ID: ${notice.original_agent_id}`);
573
- }
574
- } else {
575
- console.error('Revocation notice is INVALID');
576
- process.exit(1);
577
- }
578
-
579
- } else {
580
- // Default: show if exists, otherwise show help
581
- const exists = await Identity.exists(options.file);
582
- if (exists) {
583
- const identity = await Identity.load(options.file);
584
- console.log('Current identity:');
585
- console.log(` Name: ${identity.name}`);
586
- console.log(` Fingerprint: ${identity.getFingerprint()}`);
587
- console.log(` Agent ID: ${identity.getAgentId()}`);
588
- console.log(` Created: ${identity.created}`);
589
- if (identity.rotations.length > 0) {
590
- console.log(` Rotations: ${identity.rotations.length}`);
591
- console.log(` Original Agent ID: ${identity.getOriginalAgentId()}`);
592
- }
593
- } else {
594
- console.log('No identity found.');
595
- console.log(`Use --generate to create one at ${options.file}`);
596
- }
597
- }
598
-
599
- process.exit(0);
600
- } catch (err) {
601
- console.error('Error:', err.message);
602
- process.exit(1);
603
- }
604
- });
605
-
606
- // Daemon command
607
- program
608
- .command('daemon [server]')
609
- .description('Run persistent listener daemon with file-based inbox/outbox')
610
- .option('-n, --name <name>', 'Daemon instance name (allows multiple daemons)', DEFAULT_INSTANCE)
611
- .option('-i, --identity <file>', 'Path to identity file', DEFAULT_IDENTITY_PATH)
612
- .option('-c, --channels <channels...>', 'Channels to join', DEFAULT_CHANNELS)
613
- .option('-b, --background', 'Run in background (daemonize)')
614
- .option('-s, --status', 'Show daemon status')
615
- .option('-l, --list', 'List all daemon instances')
616
- .option('--stop', 'Stop the daemon')
617
- .option('--stop-all', 'Stop all running daemons')
618
- .option('--max-reconnect-time <minutes>', 'Max time to attempt reconnection (default: 10 minutes)', '10')
619
- .action(async (server, options) => {
620
- try {
621
- const instanceName = options.name;
622
- const paths = getDaemonPaths(instanceName);
623
-
624
- // Security check for operations that create files (not for status/list/stop)
625
- const needsSafetyCheck = !options.list && !options.status && !options.stop && !options.stopAll;
626
- if (needsSafetyCheck) {
627
- enforceDirectorySafety(process.cwd(), { allowWarnings: true, silent: false });
628
- }
629
-
630
- // List all daemons
631
- if (options.list) {
632
- const instances = await listDaemons();
633
- if (instances.length === 0) {
634
- console.log('No daemon instances found');
635
- } else {
636
- console.log('Daemon instances:');
637
- for (const inst of instances) {
638
- const status = inst.running ? `running (PID: ${inst.pid})` : 'stopped';
639
- console.log(` ${inst.name}: ${status}`);
640
- }
641
- }
642
- process.exit(0);
643
- }
644
-
645
- // Stop all daemons
646
- if (options.stopAll) {
647
- const results = await stopAllDaemons();
648
- if (results.length === 0) {
649
- console.log('No running daemons to stop');
650
- } else {
651
- for (const r of results) {
652
- console.log(`Stopped ${r.instance} (PID: ${r.pid})`);
653
- }
654
- }
655
- process.exit(0);
656
- }
657
-
658
- // Status check
659
- if (options.status) {
660
- const status = await getDaemonStatus(instanceName);
661
- if (!status.running) {
662
- console.log(`Daemon '${instanceName}' is not running`);
663
- } else {
664
- console.log(`Daemon '${instanceName}' is running:`);
665
- console.log(` PID: ${status.pid}`);
666
- console.log(` Inbox: ${status.inboxPath} (${status.inboxLines} messages)`);
667
- console.log(` Outbox: ${status.outboxPath}`);
668
- console.log(` Log: ${status.logPath}`);
669
- if (status.lastMessage) {
670
- console.log(` Last message: ${JSON.stringify(status.lastMessage).substring(0, 80)}...`);
671
- }
672
- }
673
- process.exit(0);
674
- }
675
-
676
- // Stop daemon
677
- if (options.stop) {
678
- const result = await stopDaemon(instanceName);
679
- if (result.stopped) {
680
- console.log(`Daemon '${instanceName}' stopped (PID: ${result.pid})`);
681
- } else {
682
- console.log(result.reason);
683
- }
684
- process.exit(0);
685
- }
686
-
687
- // Start daemon requires server
688
- if (!server) {
689
- console.error('Error: server URL required to start daemon');
690
- console.error('Usage: agentchat daemon ws://localhost:6667 --name myagent');
691
- process.exit(1);
692
- }
693
-
694
- // Check if already running
695
- const status = await isDaemonRunning(instanceName);
696
- if (status.running) {
697
- console.error(`Daemon '${instanceName}' already running (PID: ${status.pid})`);
698
- console.error('Use --stop to stop it first, or use a different --name');
699
- process.exit(1);
700
- }
701
-
702
- // Background mode
703
- if (options.background) {
704
- const { spawn } = await import('child_process');
705
-
706
- // Re-run ourselves without --background
707
- const args = process.argv.slice(2).filter(a => a !== '-b' && a !== '--background');
708
-
709
- const child = spawn(process.execPath, [process.argv[1], ...args], {
710
- detached: true,
711
- stdio: 'ignore'
712
- });
713
-
714
- child.unref();
715
- console.log(`Daemon '${instanceName}' started in background (PID: ${child.pid})`);
716
- console.log(` Inbox: ${paths.inbox}`);
717
- console.log(` Outbox: ${paths.outbox}`);
718
- console.log(` Log: ${paths.log}`);
719
- console.log('');
720
- console.log('To send messages, append to outbox:');
721
- console.log(` echo '{"to":"#general","content":"Hello!"}' >> ${paths.outbox}`);
722
- console.log('');
723
- console.log('To read messages:');
724
- console.log(` tail -f ${paths.inbox}`);
725
- process.exit(0);
726
- }
727
-
728
- // Foreground mode
729
- console.log('Starting daemon in foreground (Ctrl+C to stop)...');
730
- console.log(` Instance: ${instanceName}`);
731
- console.log(` Server: ${server}`);
732
- console.log(` Identity: ${options.identity}`);
733
-
734
- // Normalize channels: handle both comma-separated and space-separated formats
735
- const normalizedChannels = options.channels
736
- .flatMap(c => c.split(','))
737
- .map(c => c.trim())
738
- .filter(c => c.length > 0)
739
- .map(c => c.startsWith('#') ? c : '#' + c);
740
-
741
- console.log(` Channels: ${normalizedChannels.join(', ')}`);
742
- console.log('');
743
-
744
- const daemon = new AgentChatDaemon({
745
- server,
746
- name: instanceName,
747
- identity: options.identity,
748
- channels: normalizedChannels,
749
- maxReconnectTime: parseInt(options.maxReconnectTime) * 60 * 1000 // Convert minutes to ms
750
- });
751
-
752
- await daemon.start();
753
-
754
- // Keep process alive
755
- process.stdin.resume();
756
-
757
- } catch (err) {
758
- console.error('Error:', err.message);
759
- process.exit(1);
760
- }
761
- });
762
-
763
- // Receipts command
764
- program
765
- .command('receipts [action]')
766
- .description('Manage completion receipts for portable reputation')
767
- .option('-f, --format <format>', 'Export format (json, yaml)', 'json')
768
- .option('-i, --identity <file>', 'Path to identity file', DEFAULT_IDENTITY_PATH)
769
- .option('--file <path>', 'Receipts file path', DEFAULT_RECEIPTS_PATH)
770
- .action(async (action, options) => {
771
- try {
772
- const store = new ReceiptStore(options.file);
773
- const receipts = await store.getAll();
774
-
775
- // Load identity to get agent ID for filtering
776
- let agentId = null;
777
- try {
778
- const identity = await Identity.load(options.identity);
779
- agentId = identity.getAgentId();
780
- } catch {
781
- // Identity not available, show all receipts
782
- }
783
-
784
- switch (action) {
785
- case 'list':
786
- if (receipts.length === 0) {
787
- console.log('No receipts found.');
788
- console.log(`\nReceipts are stored in: ${options.file}`);
789
- console.log('Receipts are automatically saved when COMPLETE messages are received via daemon.');
790
- } else {
791
- console.log(`Found ${receipts.length} receipt(s):\n`);
792
- for (const r of receipts) {
793
- console.log(` Proposal: ${r.proposal_id || 'unknown'}`);
794
- console.log(` Completed: ${r.completed_at ? new Date(r.completed_at).toISOString() : 'unknown'}`);
795
- console.log(` By: ${r.completed_by || 'unknown'}`);
796
- if (r.proof) console.log(` Proof: ${r.proof}`);
797
- if (r.proposal?.task) console.log(` Task: ${r.proposal.task}`);
798
- if (r.proposal?.amount) console.log(` Amount: ${r.proposal.amount} ${r.proposal.currency || ''}`);
799
- console.log('');
800
- }
801
- }
802
- break;
803
-
804
- case 'export':
805
- const output = await store.export(options.format, agentId);
806
- console.log(output);
807
- break;
808
-
809
- case 'summary':
810
- const stats = await store.getStats(agentId);
811
- console.log('Receipt Summary:');
812
- console.log(` Total receipts: ${stats.count}`);
813
-
814
- if (stats.count > 0) {
815
- if (stats.dateRange) {
816
- console.log(` Date range: ${stats.dateRange.oldest} to ${stats.dateRange.newest}`);
817
- }
818
-
819
- if (stats.counterparties.length > 0) {
820
- console.log(` Counterparties (${stats.counterparties.length}):`);
821
- for (const cp of stats.counterparties) {
822
- console.log(` - ${cp}`);
823
- }
824
- }
825
-
826
- const currencies = Object.entries(stats.currencies);
827
- if (currencies.length > 0) {
828
- console.log(' By currency:');
829
- for (const [currency, data] of currencies) {
830
- if (currency !== 'unknown') {
831
- console.log(` ${currency}: ${data.count} receipts, ${data.totalAmount} total`);
832
- } else {
833
- console.log(` (no currency): ${data.count} receipts`);
834
- }
835
- }
836
- }
837
- }
838
-
839
- console.log(`\nReceipts file: ${options.file}`);
840
- if (agentId) {
841
- console.log(`Filtered for agent: @${agentId}`);
842
- }
843
- break;
844
-
845
- default:
846
- // Default: show help
847
- console.log('Receipt Management Commands:');
848
- console.log('');
849
- console.log(' agentchat receipts list List all stored receipts');
850
- console.log(' agentchat receipts export Export receipts (--format json|yaml)');
851
- console.log(' agentchat receipts summary Show receipt statistics');
852
- console.log('');
853
- console.log('Options:');
854
- console.log(' --format <format> Export format: json (default) or yaml');
855
- console.log(' --identity <file> Identity file for filtering by agent');
856
- console.log(' --file <path> Custom receipts file path');
857
- console.log('');
858
- console.log(`Receipts are stored in: ${DEFAULT_RECEIPTS_PATH}`);
859
- console.log('');
860
- console.log('Receipts are automatically saved by the daemon when');
861
- console.log('COMPLETE messages are received for proposals you are party to.');
862
- }
863
-
864
- process.exit(0);
865
- } catch (err) {
866
- console.error('Error:', err.message);
867
- process.exit(1);
868
- }
869
- });
870
-
871
- // Ratings command
872
- program
873
- .command('ratings [agent]')
874
- .description('View and manage ELO-based reputation ratings')
875
- .option('-i, --identity <file>', 'Path to identity file', DEFAULT_IDENTITY_PATH)
876
- .option('--file <path>', 'Ratings file path', DEFAULT_RATINGS_PATH)
877
- .option('-e, --export', 'Export all ratings as JSON')
878
- .option('-r, --recalculate', 'Recalculate ratings from receipt history')
879
- .option('-l, --leaderboard [n]', 'Show top N agents by rating')
880
- .option('-s, --stats', 'Show rating system statistics')
881
- .action(async (agent, options) => {
882
- try {
883
- const store = new ReputationStore(options.file);
884
-
885
- // Export all ratings
886
- if (options.export) {
887
- const ratings = await store.exportRatings();
888
- console.log(JSON.stringify(ratings, null, 2));
889
- process.exit(0);
890
- }
891
-
892
- // Recalculate from receipts
893
- if (options.recalculate) {
894
- console.log('Recalculating ratings from receipt history...');
895
- const receipts = await readReceipts();
896
- const ratings = await store.recalculateFromReceipts(receipts);
897
- const count = Object.keys(ratings).length;
898
- console.log(`Processed ${receipts.length} receipts, updated ${count} agents.`);
899
-
900
- const stats = await store.getStats();
901
- console.log(`\nRating Statistics:`);
902
- console.log(` Total agents: ${stats.totalAgents}`);
903
- console.log(` Average rating: ${stats.averageRating}`);
904
- console.log(` Highest: ${stats.highestRating}`);
905
- console.log(` Lowest: ${stats.lowestRating}`);
906
- process.exit(0);
907
- }
908
-
909
- // Show leaderboard
910
- if (options.leaderboard) {
911
- const limit = typeof options.leaderboard === 'string'
912
- ? parseInt(options.leaderboard)
913
- : 10;
914
- const leaderboard = await store.getLeaderboard(limit);
915
-
916
- if (leaderboard.length === 0) {
917
- console.log('No ratings recorded yet.');
918
- } else {
919
- console.log(`Top ${leaderboard.length} agents by rating:\n`);
920
- leaderboard.forEach((entry, i) => {
921
- console.log(` ${i + 1}. ${entry.agentId}`);
922
- console.log(` Rating: ${entry.rating} | Transactions: ${entry.transactions}`);
923
- });
924
- }
925
- process.exit(0);
926
- }
927
-
928
- // Show stats
929
- if (options.stats) {
930
- const stats = await store.getStats();
931
- console.log('Rating System Statistics:');
932
- console.log(` Total agents: ${stats.totalAgents}`);
933
- console.log(` Total transactions: ${stats.totalTransactions}`);
934
- console.log(` Average rating: ${stats.averageRating}`);
935
- console.log(` Highest rating: ${stats.highestRating}`);
936
- console.log(` Lowest rating: ${stats.lowestRating}`);
937
- console.log(` Default rating: ${DEFAULT_RATING}`);
938
- console.log(`\nRatings file: ${options.file}`);
939
- process.exit(0);
940
- }
941
-
942
- // Show specific agent's rating
943
- if (agent) {
944
- const rating = await store.getRating(agent);
945
- console.log(`Rating for ${rating.agentId}:`);
946
- console.log(` Rating: ${rating.rating}${rating.isNew ? ' (new agent)' : ''}`);
947
- console.log(` Transactions: ${rating.transactions}`);
948
- if (rating.updated) {
949
- console.log(` Last updated: ${rating.updated}`);
950
- }
951
-
952
- // Show K-factor
953
- const kFactor = await store.getAgentKFactor(agent);
954
- console.log(` K-factor: ${kFactor}`);
955
- process.exit(0);
956
- }
957
-
958
- // Default: show own rating (from identity)
959
- let agentId = null;
960
- try {
961
- const identity = await Identity.load(options.identity);
962
- agentId = `@${identity.getAgentId()}`;
963
- } catch {
964
- // No identity available
965
- }
966
-
967
- if (agentId) {
968
- const rating = await store.getRating(agentId);
969
- console.log(`Your rating (${agentId}):`);
970
- console.log(` Rating: ${rating.rating}${rating.isNew ? ' (new agent)' : ''}`);
971
- console.log(` Transactions: ${rating.transactions}`);
972
- if (rating.updated) {
973
- console.log(` Last updated: ${rating.updated}`);
974
- }
975
- const kFactor = await store.getAgentKFactor(agentId);
976
- console.log(` K-factor: ${kFactor}`);
977
- } else {
978
- // Show help
979
- console.log('ELO-based Reputation Rating System');
980
- console.log('');
981
- console.log('Usage:');
982
- console.log(' agentchat ratings Show your rating (requires identity)');
983
- console.log(' agentchat ratings <agent-id> Show specific agent rating');
984
- console.log(' agentchat ratings --leaderboard [n] Show top N agents');
985
- console.log(' agentchat ratings --stats Show system statistics');
986
- console.log(' agentchat ratings --export Export all ratings as JSON');
987
- console.log(' agentchat ratings --recalculate Rebuild ratings from receipts');
988
- console.log('');
989
- console.log('How it works:');
990
- console.log(` - New agents start at ${DEFAULT_RATING}`);
991
- console.log(' - On COMPLETE: both parties gain rating');
992
- console.log(' - On DISPUTE: at-fault party loses rating');
993
- console.log(' - Completing with higher-rated agents = more gain');
994
- console.log(' - K-factor: 32 (new) → 24 (intermediate) → 16 (established)');
995
- console.log('');
996
- console.log(`Ratings file: ${options.file}`);
997
- }
998
-
999
- process.exit(0);
1000
- } catch (err) {
1001
- console.error('Error:', err.message);
1002
- process.exit(1);
1003
- }
1004
- });
1005
-
1006
- // Skills command - skill discovery and announcement
1007
- program
1008
- .command('skills <action> [server]')
1009
- .description('Manage skill discovery: announce, search, list')
1010
- .option('-c, --capability <capability>', 'Skill capability for announce/search')
1011
- .option('-r, --rate <rate>', 'Rate/price for the skill', parseFloat)
1012
- .option('--currency <currency>', 'Currency for rate (e.g., SOL, TEST)', 'TEST')
1013
- .option('--description <desc>', 'Description of skill')
1014
- .option('-f, --file <file>', 'YAML file with skill definitions')
1015
- .option('-i, --identity <file>', 'Path to identity file', DEFAULT_IDENTITY_PATH)
1016
- .option('--max-rate <rate>', 'Maximum rate for search', parseFloat)
1017
- .option('-l, --limit <n>', 'Limit search results', parseInt)
1018
- .option('--json', 'Output as JSON')
1019
- .action(async (action, server, options) => {
1020
- try {
1021
- if (action === 'announce') {
1022
- if (!server) {
1023
- console.error('Server URL required: agentchat skills announce <server>');
1024
- process.exit(1);
1025
- }
1026
-
1027
- let skills = [];
1028
-
1029
- // Load from file if provided
1030
- if (options.file) {
1031
- const yaml = await import('js-yaml');
1032
- const content = await fsp.readFile(options.file, 'utf-8');
1033
- const data = yaml.default.load(content);
1034
- skills = data.skills || [data];
1035
- } else if (options.capability) {
1036
- // Single skill from CLI args
1037
- skills = [{
1038
- capability: options.capability,
1039
- rate: options.rate,
1040
- currency: options.currency,
1041
- description: options.description
1042
- }];
1043
- } else {
1044
- console.error('Either --file or --capability required');
1045
- process.exit(1);
1046
- }
1047
-
1048
- // Load identity and sign
1049
- const identity = await Identity.load(options.identity);
1050
- const skillsContent = JSON.stringify(skills);
1051
- const sig = identity.sign(skillsContent);
1052
-
1053
- // Connect and announce
1054
- const client = new AgentChatClient({ server, identity: options.identity });
1055
- await client.connect();
1056
-
1057
- await client.sendRaw({
1058
- type: 'REGISTER_SKILLS',
1059
- skills,
1060
- sig: sig.toString('base64')
1061
- });
1062
-
1063
- // Wait for response
1064
- const response = await new Promise((resolve, reject) => {
1065
- const timeout = setTimeout(() => reject(new Error('Timeout')), 5000);
1066
- client.on('message', (msg) => {
1067
- if (msg.type === 'SKILLS_REGISTERED' || msg.type === 'ERROR') {
1068
- clearTimeout(timeout);
1069
- resolve(msg);
1070
- }
1071
- });
1072
- });
1073
-
1074
- client.disconnect();
1075
-
1076
- if (response.type === 'ERROR') {
1077
- console.error('Error:', response.message);
1078
- process.exit(1);
1079
- }
1080
-
1081
- console.log(`Registered ${response.skills_count} skill(s) for ${response.agent_id}`);
1082
-
1083
- } else if (action === 'search') {
1084
- if (!server) {
1085
- console.error('Server URL required: agentchat skills search <server>');
1086
- process.exit(1);
1087
- }
1088
-
1089
- const query = {};
1090
- if (options.capability) query.capability = options.capability;
1091
- if (options.maxRate !== undefined) query.max_rate = options.maxRate;
1092
- if (options.currency) query.currency = options.currency;
1093
- if (options.limit) query.limit = options.limit;
1094
-
1095
- // Connect and search
1096
- const client = new AgentChatClient({ server });
1097
- await client.connect();
1098
-
1099
- const queryId = `q_${Date.now()}`;
1100
- await client.sendRaw({
1101
- type: 'SEARCH_SKILLS',
1102
- query,
1103
- query_id: queryId
1104
- });
1105
-
1106
- // Wait for response
1107
- const response = await new Promise((resolve, reject) => {
1108
- const timeout = setTimeout(() => reject(new Error('Timeout')), 5000);
1109
- client.on('message', (msg) => {
1110
- if (msg.type === 'SEARCH_RESULTS' || msg.type === 'ERROR') {
1111
- clearTimeout(timeout);
1112
- resolve(msg);
1113
- }
1114
- });
1115
- });
1116
-
1117
- client.disconnect();
1118
-
1119
- if (response.type === 'ERROR') {
1120
- console.error('Error:', response.message);
1121
- process.exit(1);
1122
- }
1123
-
1124
- if (options.json) {
1125
- console.log(JSON.stringify(response.results, null, 2));
1126
- } else {
1127
- console.log(`Found ${response.results.length} skill(s) (${response.total} total):\n`);
1128
- for (const skill of response.results) {
1129
- const rate = skill.rate !== undefined ? `${skill.rate} ${skill.currency || ''}` : 'negotiable';
1130
- console.log(` ${skill.agent_id}`);
1131
- console.log(` Capability: ${skill.capability}`);
1132
- console.log(` Rate: ${rate}`);
1133
- if (skill.description) console.log(` Description: ${skill.description}`);
1134
- console.log('');
1135
- }
1136
- }
1137
-
1138
- } else if (action === 'list') {
1139
- // List own registered skills (if server supports it)
1140
- console.error('List action not yet implemented');
1141
- process.exit(1);
1142
-
1143
- } else {
1144
- console.error(`Unknown action: ${action}`);
1145
- console.error('Valid actions: announce, search, list');
1146
- process.exit(1);
1147
- }
1148
-
1149
- } catch (err) {
1150
- console.error('Error:', err.message);
1151
- process.exit(1);
1152
- }
1153
- });
1154
-
1155
- // Discover command - find public AgentChat servers
1156
- program
1157
- .command('discover')
1158
- .description('Discover available AgentChat servers')
1159
- .option('--add <url>', 'Add a server to the directory')
1160
- .option('--remove <url>', 'Remove a server from the directory')
1161
- .option('--name <name>', 'Server name (for --add)')
1162
- .option('--description <desc>', 'Server description (for --add)')
1163
- .option('--region <region>', 'Server region (for --add)')
1164
- .option('--online', 'Only show online servers')
1165
- .option('--json', 'Output as JSON')
1166
- .option('--no-check', 'List servers without health check')
1167
- .option('--directory <path>', 'Custom directory file path', DEFAULT_DIRECTORY_PATH)
1168
- .action(async (options) => {
1169
- try {
1170
- const directory = new ServerDirectory({ directoryPath: options.directory });
1171
- await directory.load();
1172
-
1173
- // Add server
1174
- if (options.add) {
1175
- await directory.addServer({
1176
- url: options.add,
1177
- name: options.name || options.add,
1178
- description: options.description || '',
1179
- region: options.region || 'unknown'
1180
- });
1181
- console.log(`Added server: ${options.add}`);
1182
- process.exit(0);
1183
- }
1184
-
1185
- // Remove server
1186
- if (options.remove) {
1187
- await directory.removeServer(options.remove);
1188
- console.log(`Removed server: ${options.remove}`);
1189
- process.exit(0);
1190
- }
1191
-
1192
- // List/discover servers
1193
- let servers;
1194
- if (options.check === false) {
1195
- servers = directory.list().map(s => ({ ...s, status: 'unknown' }));
1196
- } else {
1197
- console.error('Checking server status...');
1198
- servers = await directory.discover({ onlineOnly: options.online });
1199
- }
1200
-
1201
- if (options.json) {
1202
- console.log(JSON.stringify(servers, null, 2));
1203
- } else {
1204
- if (servers.length === 0) {
1205
- console.log('No servers found.');
1206
- } else {
1207
- console.log(`\nFound ${servers.length} server(s):\n`);
1208
- for (const server of servers) {
1209
- const statusIcon = server.status === 'online' ? '\u2713' :
1210
- server.status === 'offline' ? '\u2717' : '?';
1211
- console.log(` ${statusIcon} ${server.name}`);
1212
- console.log(` URL: ${server.url}`);
1213
- console.log(` Status: ${server.status}`);
1214
- if (server.description) {
1215
- console.log(` Description: ${server.description}`);
1216
- }
1217
- if (server.region) {
1218
- console.log(` Region: ${server.region}`);
1219
- }
1220
- if (server.health) {
1221
- console.log(` Agents: ${server.health.agents?.connected || 0}`);
1222
- console.log(` Uptime: ${server.health.uptime_seconds || 0}s`);
1223
- }
1224
- if (server.error) {
1225
- console.log(` Error: ${server.error}`);
1226
- }
1227
- console.log('');
1228
- }
1229
- }
1230
- console.log(`Directory: ${options.directory}`);
1231
- }
1232
-
1233
- process.exit(0);
1234
- } catch (err) {
1235
- console.error('Error:', err.message);
1236
- process.exit(1);
1237
- }
1238
- });
1239
-
1240
- // Deploy command
1241
- program
1242
- .command('deploy')
1243
- .description('Generate deployment files for agentchat server')
1244
- .option('--provider <provider>', 'Deployment target (docker, akash)', 'docker')
1245
- .option('--config <file>', 'Deploy configuration file (deploy.yaml)')
1246
- .option('--output <dir>', 'Output directory for generated files', '.')
1247
- .option('-p, --port <port>', 'Server port')
1248
- .option('-n, --name <name>', 'Server/container name')
1249
- .option('--volumes', 'Enable volume mounts for data persistence')
1250
- .option('--no-health-check', 'Disable health check configuration')
1251
- .option('--cert <file>', 'TLS certificate file path')
1252
- .option('--key <file>', 'TLS private key file path')
1253
- .option('--network <name>', 'Docker network name')
1254
- .option('--dockerfile', 'Also generate Dockerfile')
1255
- .option('--init-config', 'Generate example deploy.yaml config file')
1256
- // Akash-specific options
1257
- .option('--generate-wallet', 'Generate a new Akash wallet')
1258
- .option('--wallet <file>', 'Path to wallet file', AKASH_WALLET_PATH)
1259
- .option('--balance', 'Check wallet balance')
1260
- .option('--testnet', 'Use Akash testnet (default)')
1261
- .option('--mainnet', 'Use Akash mainnet (real funds!)')
1262
- .option('--create', 'Create deployment on Akash')
1263
- .option('--status', 'Show deployment status')
1264
- .option('--close <dseq>', 'Close a deployment by dseq')
1265
- .option('--generate-sdl', 'Generate SDL file without deploying')
1266
- .option('--force', 'Overwrite existing wallet')
1267
- .option('--bids <dseq>', 'Query bids for a deployment')
1268
- .option('--accept-bid <dseq>', 'Accept a bid (use with --provider-address)')
1269
- .option('--provider-address <address>', 'Provider address for --accept-bid')
1270
- .option('--dseq-status <dseq>', 'Get detailed status for a specific deployment')
1271
- .action(async (options) => {
1272
- try {
1273
- const isAkash = options.provider === 'akash';
1274
- const akashNetwork = options.mainnet ? 'mainnet' : 'testnet';
1275
-
1276
- // Akash: Generate wallet
1277
- if (isAkash && options.generateWallet) {
1278
- try {
1279
- const wallet = await generateWallet(akashNetwork, options.wallet);
1280
- console.log('Generated new Akash wallet:');
1281
- console.log(` Network: ${wallet.network}`);
1282
- console.log(` Address: ${wallet.address}`);
1283
- console.log(` Saved to: ${options.wallet}`);
1284
- console.log('');
1285
- console.log('IMPORTANT: Back up your wallet file!');
1286
- console.log('The mnemonic inside is the only way to recover your funds.');
1287
- console.log('');
1288
- if (akashNetwork === 'testnet') {
1289
- console.log('To get testnet tokens, visit: https://faucet.sandbox-01.aksh.pw/');
1290
- } else {
1291
- console.log('To fund your wallet, send AKT to the address above.');
1292
- }
1293
- process.exit(0);
1294
- } catch (err) {
1295
- if (err.message.includes('already exists') && !options.force) {
1296
- console.error(err.message);
1297
- process.exit(1);
1298
- }
1299
- throw err;
1300
- }
1301
- }
1302
-
1303
- // Akash: Check balance
1304
- if (isAkash && options.balance) {
1305
- const result = await checkBalance(options.wallet);
1306
- console.log('Wallet Balance:');
1307
- console.log(` Network: ${result.wallet.network}`);
1308
- console.log(` Address: ${result.wallet.address}`);
1309
- console.log(` Balance: ${result.balance.akt} AKT (${result.balance.uakt} uakt)`);
1310
- console.log(` Status: ${result.balance.sufficient ? 'Sufficient for deployment' : 'Insufficient - need at least 5 AKT'}`);
1311
- process.exit(0);
1312
- }
1313
-
1314
- // Akash: Generate SDL only
1315
- if (isAkash && options.generateSdl) {
1316
- const sdl = generateAkashSDL({
1317
- name: options.name,
1318
- port: options.port ? parseInt(options.port) : undefined
1319
- });
1320
- const outputDir = path.resolve(options.output);
1321
- await fs.mkdir(outputDir, { recursive: true });
1322
- const sdlPath = path.join(outputDir, 'deploy.yaml');
1323
- await fs.writeFile(sdlPath, sdl);
1324
- console.log(`Generated: ${sdlPath}`);
1325
- console.log('\nThis SDL can be used with the Akash CLI or Console.');
1326
- process.exit(0);
1327
- }
1328
-
1329
- // Akash: Create deployment
1330
- if (isAkash && options.create) {
1331
- console.log('Creating Akash deployment...');
1332
- try {
1333
- const result = await createDeployment({
1334
- walletPath: options.wallet,
1335
- name: options.name,
1336
- port: options.port ? parseInt(options.port) : undefined
1337
- });
1338
- console.log('Deployment created:');
1339
- console.log(` DSEQ: ${result.dseq}`);
1340
- console.log(` Status: ${result.status}`);
1341
- if (result.endpoint) {
1342
- console.log(` Endpoint: ${result.endpoint}`);
1343
- }
1344
- } catch (err) {
1345
- console.error('Deployment failed:', err.message);
1346
- process.exit(1);
1347
- }
1348
- process.exit(0);
1349
- }
1350
-
1351
- // Akash: Show status
1352
- if (isAkash && options.status) {
1353
- const deployments = await listDeployments(options.wallet);
1354
- if (deployments.length === 0) {
1355
- console.log('No active deployments.');
1356
- } else {
1357
- console.log('Active deployments:');
1358
- for (const d of deployments) {
1359
- console.log(` DSEQ ${d.dseq}: ${d.status} - ${d.endpoint || 'pending'}`);
1360
- }
1361
- }
1362
- process.exit(0);
1363
- }
1364
-
1365
- // Akash: Close deployment
1366
- if (isAkash && options.close) {
1367
- console.log(`Closing deployment ${options.close}...`);
1368
- await closeDeployment(options.close, options.wallet);
1369
- console.log('Deployment closed.');
1370
- process.exit(0);
1371
- }
1372
-
1373
- // Akash: Query bids
1374
- if (isAkash && options.bids) {
1375
- console.log(`Querying bids for deployment ${options.bids}...`);
1376
- const bids = await queryBids(options.bids, options.wallet);
1377
- if (bids.length === 0) {
1378
- console.log('No bids received yet.');
1379
- } else {
1380
- console.log('Available bids:');
1381
- for (const b of bids) {
1382
- const bid = b.bid || {};
1383
- const price = bid.price?.amount || 'unknown';
1384
- const state = bid.state || 'unknown';
1385
- const provider = bid.bidId?.provider || 'unknown';
1386
- console.log(` Provider: ${provider}`);
1387
- console.log(` Price: ${price} uakt/block`);
1388
- console.log(` State: ${state}`);
1389
- console.log('');
1390
- }
1391
- }
1392
- process.exit(0);
1393
- }
1394
-
1395
- // Akash: Accept bid
1396
- if (isAkash && options.acceptBid) {
1397
- if (!options.providerAddress) {
1398
- console.error('Error: --provider-address is required with --accept-bid');
1399
- process.exit(1);
1400
- }
1401
- console.log(`Accepting bid from ${options.providerAddress}...`);
1402
- const lease = await acceptBid(options.acceptBid, options.providerAddress, options.wallet);
1403
- console.log('Lease created:');
1404
- console.log(` DSEQ: ${lease.dseq}`);
1405
- console.log(` Provider: ${lease.provider}`);
1406
- console.log(` TX: ${lease.txHash}`);
1407
- process.exit(0);
1408
- }
1409
-
1410
- // Akash: Get detailed deployment status
1411
- if (isAkash && options.dseqStatus) {
1412
- console.log(`Getting status for deployment ${options.dseqStatus}...`);
1413
- const status = await getDeploymentStatus(options.dseqStatus, options.wallet);
1414
- console.log('Deployment status:');
1415
- console.log(` DSEQ: ${status.dseq}`);
1416
- console.log(` Status: ${status.status}`);
1417
- console.log(` Created: ${status.createdAt}`);
1418
- if (status.provider) {
1419
- console.log(` Provider: ${status.provider}`);
1420
- }
1421
- if (status.bids) {
1422
- console.log(` Bids: ${status.bids.length}`);
1423
- for (const bid of status.bids) {
1424
- console.log(` - ${bid.provider}: ${bid.price} uakt (${bid.state})`);
1425
- }
1426
- }
1427
- if (status.leaseStatus) {
1428
- console.log(' Lease Status:', JSON.stringify(status.leaseStatus, null, 2));
1429
- }
1430
- if (status.leaseStatusError) {
1431
- console.log(` Lease Status Error: ${status.leaseStatusError}`);
1432
- }
1433
- process.exit(0);
1434
- }
1435
-
1436
- // Akash: Default action - show help
1437
- if (isAkash) {
1438
- console.log('Akash Deployment Options:');
1439
- console.log('');
1440
- console.log(' Setup:');
1441
- console.log(' --generate-wallet Generate a new wallet');
1442
- console.log(' --balance Check wallet balance');
1443
- console.log('');
1444
- console.log(' Deployment:');
1445
- console.log(' --generate-sdl Generate SDL file');
1446
- console.log(' --create Create deployment (auto-accepts best bid)');
1447
- console.log(' --status Show all deployments');
1448
- console.log(' --dseq-status <n> Get detailed status for deployment');
1449
- console.log(' --close <dseq> Close a deployment');
1450
- console.log('');
1451
- console.log(' Manual bid selection:');
1452
- console.log(' --bids <dseq> Query bids for a deployment');
1453
- console.log(' --accept-bid <dseq> --provider-address <addr>');
1454
- console.log(' Accept a specific bid');
1455
- console.log('');
1456
- console.log(' Options:');
1457
- console.log(' --testnet Use testnet (default)');
1458
- console.log(' --mainnet Use mainnet (real AKT)');
1459
- console.log(' --wallet <file> Custom wallet path');
1460
- console.log('');
1461
- console.log('Example workflow:');
1462
- console.log(' 1. agentchat deploy --provider akash --generate-wallet');
1463
- console.log(' 2. Fund wallet with AKT tokens');
1464
- console.log(' 3. agentchat deploy --provider akash --balance');
1465
- console.log(' 4. agentchat deploy --provider akash --create');
1466
- console.log('');
1467
- console.log('Manual workflow (select your own provider):');
1468
- console.log(' 1. agentchat deploy --provider akash --generate-sdl');
1469
- console.log(' 2. agentchat deploy --provider akash --create');
1470
- console.log(' 3. agentchat deploy --provider akash --bids <dseq>');
1471
- console.log(' 4. agentchat deploy --provider akash --accept-bid <dseq> --provider-address <addr>');
1472
- process.exit(0);
1473
- }
1474
-
1475
- // Generate example config
1476
- if (options.initConfig) {
1477
- const configPath = path.resolve(options.output, 'deploy.yaml');
1478
- await fs.mkdir(path.dirname(configPath), { recursive: true });
1479
- await fs.writeFile(configPath, generateExampleConfig());
1480
- console.log(`Generated: ${configPath}`);
1481
- process.exit(0);
1482
- }
1483
-
1484
- let config = { ...DEFAULT_CONFIG };
1485
-
1486
- // Load config file if provided
1487
- if (options.config) {
1488
- const fileConfig = await loadConfig(options.config);
1489
- config = { ...config, ...fileConfig };
1490
- }
1491
-
1492
- // Override with CLI options
1493
- if (options.port) config.port = parseInt(options.port);
1494
- if (options.name) config.name = options.name;
1495
- if (options.volumes) config.volumes = true;
1496
- if (options.healthCheck === false) config.healthCheck = false;
1497
- if (options.network) config.network = options.network;
1498
- if (options.cert && options.key) {
1499
- config.tls = { cert: options.cert, key: options.key };
1500
- }
1501
-
1502
- // Validate TLS
1503
- if ((options.cert && !options.key) || (!options.cert && options.key)) {
1504
- console.error('Error: Both --cert and --key must be provided for TLS');
1505
- process.exit(1);
1506
- }
1507
-
1508
- // Ensure output directory exists
1509
- const outputDir = path.resolve(options.output);
1510
- await fs.mkdir(outputDir, { recursive: true });
1511
-
1512
- // Generate based on provider (Docker)
1513
- if (options.provider === 'docker' || config.provider === 'docker') {
1514
- // Generate docker-compose.yml
1515
- const compose = await deployToDocker(config);
1516
- const composePath = path.join(outputDir, 'docker-compose.yml');
1517
- await fs.writeFile(composePath, compose);
1518
- console.log(`Generated: ${composePath}`);
1519
-
1520
- // Optionally generate Dockerfile
1521
- if (options.dockerfile) {
1522
- const dockerfile = await generateDockerfile(config);
1523
- const dockerfilePath = path.join(outputDir, 'Dockerfile.generated');
1524
- await fs.writeFile(dockerfilePath, dockerfile);
1525
- console.log(`Generated: ${dockerfilePath}`);
1526
- }
1527
-
1528
- console.log('\nTo deploy:');
1529
- console.log(` cd ${outputDir}`);
1530
- console.log(' docker-compose up -d');
1531
-
1532
- } else {
1533
- console.error(`Unknown provider: ${options.provider}`);
1534
- process.exit(1);
1535
- }
1536
-
1537
- process.exit(0);
1538
- } catch (err) {
1539
- console.error('Error:', err.message);
1540
- process.exit(1);
1541
- }
1542
- });
1543
-
1544
- // Launcher mode: if no subcommand or just a name, setup MCP + launch Claude
1545
- const subcommands = [
1546
- 'serve', 'send', 'listen', 'channels', 'agents', 'create', 'invite',
1547
- 'propose', 'accept', 'reject', 'complete', 'dispute', 'verify',
1548
- 'identity', 'daemon', 'receipts', 'ratings', 'skills', 'discover', 'deploy',
1549
- 'help', '--help', '-h', '--version', '-V'
1550
- ];
1551
-
1552
- const firstArg = process.argv[2];
1553
-
1554
- if (!firstArg || !subcommands.includes(firstArg)) {
1555
- // Launcher mode
1556
-
1557
- // Security check: prevent running in root/system directories
1558
- const safetyCheck = checkDirectorySafety(process.cwd());
1559
- if (safetyCheck.level === 'error') {
1560
- console.error(`\n❌ ERROR: ${safetyCheck.error}`);
1561
- process.exit(1);
1562
- }
1563
- if (safetyCheck.level === 'warning') {
1564
- console.error(`\n⚠️ WARNING: ${safetyCheck.warning}`);
1565
- }
1566
-
1567
- import('child_process').then(({ execSync, spawn }) => {
1568
- const name = firstArg; // May be undefined (anonymous) or a name
1569
-
1570
- // 1. Check if MCP is configured
1571
- let mcpConfigured = false;
1572
- try {
1573
- const mcpList = execSync('claude mcp list 2>&1', { encoding: 'utf-8' });
1574
- mcpConfigured = mcpList.includes('agentchat');
1575
- } catch (e) {
1576
- // claude command might not exist
1577
- }
1578
-
1579
- // 2. Setup MCP if needed
1580
- if (!mcpConfigured) {
1581
- console.log('Setting up AgentChat for Claude Code...');
1582
- try {
1583
- execSync('claude mcp add -s user agentchat -- npx -y @tjamescouch/agentchat-mcp', {
1584
- stdio: 'inherit'
1585
- });
1586
- console.log('');
1587
- console.log('AgentChat installed! Starting Claude Code...');
1588
- console.log('');
1589
- } catch (e) {
1590
- console.error('Failed to setup MCP. Is Claude Code installed?');
1591
- console.error('Install from: https://claude.ai/download');
1592
- process.exit(1);
1593
- }
1594
- }
1595
-
1596
- // 3. Launch Claude with prompt
1597
- const prompt = name
1598
- ? `Connect to agentchat with name "${name}" and introduce yourself in #general. Read SKILL.md if you need help.`
1599
- : `Connect to agentchat and introduce yourself in #general. Read SKILL.md if you need help.`;
1600
-
1601
- const claude = spawn('claude', [prompt], {
1602
- stdio: 'inherit'
1603
- });
1604
-
1605
- claude.on('error', (err) => {
1606
- console.error('Failed to start Claude Code:', err.message);
1607
- process.exit(1);
1608
- });
1609
-
1610
- claude.on('close', (code) => {
1611
- process.exit(code || 0);
1612
- });
1613
- });
1614
- } else {
1615
- // Normal CLI mode
1616
- program.parse();
1617
- }