@tjamescouch/agentchat 0.22.0 → 0.23.0

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