@plosson/agentio 0.4.2 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/package.json +3 -1
- package/src/auth/oauth.ts +14 -2
- package/src/commands/gateway.ts +259 -0
- package/src/commands/gcal.ts +383 -0
- package/src/commands/gtasks.ts +326 -0
- package/src/commands/status.ts +85 -0
- package/src/commands/telegram.ts +209 -1
- package/src/commands/update.ts +2 -2
- package/src/commands/whatsapp.ts +853 -0
- package/src/config/config-manager.ts +1 -1
- package/src/gateway/adapters/telegram.ts +357 -0
- package/src/gateway/adapters/types.ts +147 -0
- package/src/gateway/adapters/whatsapp-auth.ts +172 -0
- package/src/gateway/adapters/whatsapp.ts +723 -0
- package/src/gateway/api.ts +791 -0
- package/src/gateway/client.ts +402 -0
- package/src/gateway/daemon.ts +461 -0
- package/src/gateway/store.ts +637 -0
- package/src/gateway/types.ts +325 -0
- package/src/gateway/webhook.ts +109 -0
- package/src/index.ts +32 -16
- package/src/polyfills.ts +10 -0
- package/src/services/gcal/client.ts +380 -0
- package/src/services/gtasks/client.ts +301 -0
- package/src/types/config.ts +36 -1
- package/src/types/gcal.ts +135 -0
- package/src/types/gtasks.ts +58 -0
- package/src/types/qrcode-terminal.d.ts +8 -0
- package/src/types/whatsapp.ts +116 -0
- package/src/utils/output.ts +505 -0
|
@@ -0,0 +1,853 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { setCredentials, getCredentials } from '../auth/token-store';
|
|
3
|
+
import { setProfile, resolveProfile, removeProfile } from '../config/config-manager';
|
|
4
|
+
import { CliError, handleError } from '../utils/errors';
|
|
5
|
+
import { readStdin, prompt, confirm } from '../utils/stdin';
|
|
6
|
+
import { getGatewayClient, isGatewayAvailable } from '../gateway/client';
|
|
7
|
+
import {
|
|
8
|
+
printInboxMessageList,
|
|
9
|
+
printInboxMessage,
|
|
10
|
+
printInboxStats,
|
|
11
|
+
printInboxAckResult,
|
|
12
|
+
printInboxReplyResult,
|
|
13
|
+
printOutboxMessageList,
|
|
14
|
+
printOutboxMessage,
|
|
15
|
+
printOutboxSendResult,
|
|
16
|
+
printWhatsAppGroupList,
|
|
17
|
+
printWhatsAppGroup,
|
|
18
|
+
printWhatsAppGroupCreated,
|
|
19
|
+
printWhatsAppGroupInvite,
|
|
20
|
+
printWhatsAppGroupJoined,
|
|
21
|
+
printWhatsAppGroupLeft,
|
|
22
|
+
printWhatsAppParticipantsResult,
|
|
23
|
+
} from '../utils/output';
|
|
24
|
+
import type { WhatsAppCredentials } from '../types/whatsapp';
|
|
25
|
+
import qrcode from 'qrcode-terminal';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Run the WhatsApp pairing flow - polls for QR code until connected
|
|
29
|
+
*/
|
|
30
|
+
async function runPairingFlow(profileName: string): Promise<boolean> {
|
|
31
|
+
const client = await getGatewayClient();
|
|
32
|
+
let lastQr = '';
|
|
33
|
+
|
|
34
|
+
console.log('\nWaiting for QR code... (Ctrl+C to cancel)\n');
|
|
35
|
+
|
|
36
|
+
while (true) {
|
|
37
|
+
const result = await client.whatsappPair(profileName);
|
|
38
|
+
|
|
39
|
+
switch (result.status) {
|
|
40
|
+
case 'connected':
|
|
41
|
+
console.log(`\nConnected to WhatsApp!`);
|
|
42
|
+
if (result.phoneNumber) {
|
|
43
|
+
console.log(`Phone: ${result.phoneNumber}`);
|
|
44
|
+
}
|
|
45
|
+
if (result.displayName) {
|
|
46
|
+
console.log(`Name: ${result.displayName}`);
|
|
47
|
+
}
|
|
48
|
+
return true;
|
|
49
|
+
|
|
50
|
+
case 'waiting_qr':
|
|
51
|
+
if (result.qrCode && result.qrCode !== lastQr) {
|
|
52
|
+
lastQr = result.qrCode;
|
|
53
|
+
console.clear();
|
|
54
|
+
console.log('\nScan this QR code with WhatsApp on your phone:\n');
|
|
55
|
+
console.log('1. Open WhatsApp on your phone');
|
|
56
|
+
console.log('2. Tap Menu or Settings');
|
|
57
|
+
console.log('3. Tap "Linked Devices"');
|
|
58
|
+
console.log('4. Tap "Link a Device"');
|
|
59
|
+
console.log('5. Point your phone at this screen\n');
|
|
60
|
+
qrcode.generate(result.qrCode, { small: true });
|
|
61
|
+
console.log('\nWaiting for scan...');
|
|
62
|
+
}
|
|
63
|
+
break;
|
|
64
|
+
|
|
65
|
+
case 'connecting':
|
|
66
|
+
console.error(`Status: ${result.message || 'Connecting...'}`);
|
|
67
|
+
break;
|
|
68
|
+
|
|
69
|
+
case 'not_configured':
|
|
70
|
+
throw new CliError('CONFIG_ERROR', result.message || 'WhatsApp not configured');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function registerWhatsAppCommands(program: Command): void {
|
|
78
|
+
const whatsapp = program
|
|
79
|
+
.command('whatsapp')
|
|
80
|
+
.description('WhatsApp operations (requires gateway)');
|
|
81
|
+
|
|
82
|
+
// Profile management
|
|
83
|
+
const profile = whatsapp.command('profile').description('Manage WhatsApp profiles');
|
|
84
|
+
|
|
85
|
+
profile
|
|
86
|
+
.command('add')
|
|
87
|
+
.description('Add a new WhatsApp profile and pair via QR code')
|
|
88
|
+
.option('--profile <name>', 'Profile name')
|
|
89
|
+
.action(async (options) => {
|
|
90
|
+
try {
|
|
91
|
+
console.error('\nWhatsApp Profile Setup\n');
|
|
92
|
+
|
|
93
|
+
const profileName = options.profile || await prompt('? Profile name: ');
|
|
94
|
+
|
|
95
|
+
if (!profileName) {
|
|
96
|
+
throw new CliError('INVALID_PARAMS', 'Profile name is required');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Check if profile already exists
|
|
100
|
+
const existing = await getCredentials<WhatsAppCredentials>('whatsapp', profileName);
|
|
101
|
+
if (existing?.paired) {
|
|
102
|
+
const overwrite = await confirm(`Profile "${profileName}" already exists and is paired. Overwrite?`);
|
|
103
|
+
if (!overwrite) {
|
|
104
|
+
console.log('Cancelled');
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Create initial credentials (not yet paired)
|
|
110
|
+
const credentials: WhatsAppCredentials = {
|
|
111
|
+
paired: false,
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
await setProfile('whatsapp', profileName);
|
|
115
|
+
await setCredentials('whatsapp', profileName, credentials);
|
|
116
|
+
|
|
117
|
+
console.log(`Profile "${profileName}" created.`);
|
|
118
|
+
|
|
119
|
+
// Check if gateway is running
|
|
120
|
+
const gatewayRunning = await isGatewayAvailable();
|
|
121
|
+
if (!gatewayRunning) {
|
|
122
|
+
console.log('\nGateway is not running.');
|
|
123
|
+
console.log('Start the gateway first, then run this command again:');
|
|
124
|
+
console.log(' agentio gateway start');
|
|
125
|
+
console.log(` agentio whatsapp profile add --profile ${profileName}`);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Gateway is running - proceed with pairing
|
|
130
|
+
await runPairingFlow(profileName);
|
|
131
|
+
|
|
132
|
+
console.log(`\nProfile "${profileName}" is ready to use.`);
|
|
133
|
+
console.log(`Try: agentio whatsapp inbox pull --profile ${profileName}`);
|
|
134
|
+
} catch (error) {
|
|
135
|
+
handleError(error);
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
profile
|
|
140
|
+
.command('list')
|
|
141
|
+
.description('List WhatsApp profiles')
|
|
142
|
+
.action(async () => {
|
|
143
|
+
try {
|
|
144
|
+
const { profiles } = await import('../config/config-manager').then(m => m.listProfiles('whatsapp').then(r => r[0]));
|
|
145
|
+
|
|
146
|
+
if (profiles.length === 0) {
|
|
147
|
+
console.log('No WhatsApp profiles configured');
|
|
148
|
+
console.log('Run: agentio whatsapp profile add');
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
console.log(`WhatsApp profiles (${profiles.length}):\n`);
|
|
153
|
+
|
|
154
|
+
for (const name of profiles) {
|
|
155
|
+
const creds = await getCredentials<WhatsAppCredentials>('whatsapp', name);
|
|
156
|
+
const status = creds?.paired ? 'paired' : 'not paired';
|
|
157
|
+
const phone = creds?.phoneNumber ? ` (${creds.phoneNumber})` : '';
|
|
158
|
+
const displayName = creds?.displayName ? ` - ${creds.displayName}` : '';
|
|
159
|
+
console.log(` ${name}${phone}${displayName} [${status}]`);
|
|
160
|
+
}
|
|
161
|
+
} catch (error) {
|
|
162
|
+
handleError(error);
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
profile
|
|
167
|
+
.command('remove')
|
|
168
|
+
.description('Remove a WhatsApp profile')
|
|
169
|
+
.option('--profile <name>', 'Profile name to remove')
|
|
170
|
+
.action(async (options) => {
|
|
171
|
+
try {
|
|
172
|
+
const profileResult = await resolveProfile('whatsapp', options.profile);
|
|
173
|
+
if (!profileResult.profile) {
|
|
174
|
+
if (profileResult.error === 'none') {
|
|
175
|
+
throw new CliError('PROFILE_NOT_FOUND', 'No WhatsApp profiles configured');
|
|
176
|
+
}
|
|
177
|
+
throw new CliError('INVALID_PARAMS', 'Multiple profiles exist. Use --profile to specify one.');
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const profileName = profileResult.profile;
|
|
181
|
+
const confirmed = await confirm(`Remove profile "${profileName}"? This will delete the session.`);
|
|
182
|
+
|
|
183
|
+
if (!confirmed) {
|
|
184
|
+
console.log('Cancelled');
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
await removeProfile('whatsapp', profileName);
|
|
189
|
+
// Note: Auth state in gateway DB will be cleaned up when gateway restarts
|
|
190
|
+
// or we could add a direct cleanup call here
|
|
191
|
+
|
|
192
|
+
console.log(`Profile "${profileName}" removed`);
|
|
193
|
+
console.log('Note: Restart the gateway to fully disconnect this session.');
|
|
194
|
+
} catch (error) {
|
|
195
|
+
handleError(error);
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
// Inbox subcommands (requires gateway)
|
|
200
|
+
const inbox = whatsapp.command('inbox').description('Inbox operations (requires gateway)');
|
|
201
|
+
|
|
202
|
+
inbox
|
|
203
|
+
.command('pull')
|
|
204
|
+
.description('Get pending messages from inbox')
|
|
205
|
+
.option('--profile <name>', 'Profile name')
|
|
206
|
+
.option('--limit <n>', 'Maximum messages to retrieve', '50')
|
|
207
|
+
.option('--status <status>', 'Filter by status: pending or done', 'pending')
|
|
208
|
+
.option('--conversation <id>', 'Filter by conversation/group (name or JID)')
|
|
209
|
+
.action(async (options) => {
|
|
210
|
+
try {
|
|
211
|
+
const profileResult = await resolveProfile('whatsapp', options.profile);
|
|
212
|
+
if (!profileResult.profile) {
|
|
213
|
+
if (profileResult.error === 'none') {
|
|
214
|
+
throw new CliError('PROFILE_NOT_FOUND', 'No WhatsApp profiles configured', 'Run: agentio whatsapp profile add');
|
|
215
|
+
}
|
|
216
|
+
throw new CliError('INVALID_PARAMS', 'Multiple profiles exist. Use --profile to specify one.');
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const client = await getGatewayClient();
|
|
220
|
+
|
|
221
|
+
// Resolve conversation name to JID if needed
|
|
222
|
+
let conversationId = options.conversation;
|
|
223
|
+
if (conversationId && !conversationId.includes('@')) {
|
|
224
|
+
const resolved = await client.whatsappGroupResolve(profileResult.profile, conversationId);
|
|
225
|
+
if (resolved.groupId) {
|
|
226
|
+
conversationId = resolved.groupId;
|
|
227
|
+
}
|
|
228
|
+
// If not found as group, keep original (might be a phone number)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const messages = await client.inboxPull({
|
|
232
|
+
service: 'whatsapp',
|
|
233
|
+
profile: profileResult.profile,
|
|
234
|
+
conversationId,
|
|
235
|
+
limit: parseInt(options.limit, 10),
|
|
236
|
+
status: options.status as 'pending' | 'done',
|
|
237
|
+
});
|
|
238
|
+
printInboxMessageList(messages);
|
|
239
|
+
} catch (error) {
|
|
240
|
+
handleError(error);
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
inbox
|
|
245
|
+
.command('get')
|
|
246
|
+
.description('Get a specific inbox message')
|
|
247
|
+
.argument('<id>', 'Message ID')
|
|
248
|
+
.action(async (id: string) => {
|
|
249
|
+
try {
|
|
250
|
+
const client = await getGatewayClient();
|
|
251
|
+
const message = await client.inboxGet(id);
|
|
252
|
+
if (!message) {
|
|
253
|
+
throw new CliError('NOT_FOUND', `Message not found: ${id}`);
|
|
254
|
+
}
|
|
255
|
+
printInboxMessage(message);
|
|
256
|
+
} catch (error) {
|
|
257
|
+
handleError(error);
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
inbox
|
|
262
|
+
.command('ack')
|
|
263
|
+
.description('Mark a message as done')
|
|
264
|
+
.argument('<id>', 'Message ID')
|
|
265
|
+
.action(async (id: string) => {
|
|
266
|
+
try {
|
|
267
|
+
const client = await getGatewayClient();
|
|
268
|
+
const success = await client.inboxAck(id);
|
|
269
|
+
printInboxAckResult(success, id);
|
|
270
|
+
} catch (error) {
|
|
271
|
+
handleError(error);
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
inbox
|
|
276
|
+
.command('reply')
|
|
277
|
+
.description('Reply to an inbox message')
|
|
278
|
+
.argument('<id>', 'Message ID to reply to')
|
|
279
|
+
.argument('[message]', 'Reply text (or pipe via stdin)')
|
|
280
|
+
.action(async (id: string, message: string | undefined) => {
|
|
281
|
+
try {
|
|
282
|
+
let text = message;
|
|
283
|
+
if (!text) {
|
|
284
|
+
text = await readStdin() || undefined;
|
|
285
|
+
}
|
|
286
|
+
if (!text) {
|
|
287
|
+
throw new CliError('INVALID_PARAMS', 'Message is required. Provide as argument or pipe via stdin.');
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const client = await getGatewayClient();
|
|
291
|
+
const result = await client.inboxReply(id, text);
|
|
292
|
+
printInboxReplyResult(result);
|
|
293
|
+
} catch (error) {
|
|
294
|
+
handleError(error);
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
inbox
|
|
299
|
+
.command('stats')
|
|
300
|
+
.description('Get inbox statistics')
|
|
301
|
+
.option('--profile <name>', 'Profile name')
|
|
302
|
+
.action(async (options) => {
|
|
303
|
+
try {
|
|
304
|
+
const profileResult = await resolveProfile('whatsapp', options.profile);
|
|
305
|
+
const client = await getGatewayClient();
|
|
306
|
+
const stats = await client.inboxStats({
|
|
307
|
+
service: 'whatsapp',
|
|
308
|
+
profile: profileResult.profile ?? undefined,
|
|
309
|
+
});
|
|
310
|
+
printInboxStats(stats);
|
|
311
|
+
} catch (error) {
|
|
312
|
+
handleError(error);
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
// Outbox subcommands (requires gateway)
|
|
317
|
+
const outbox = whatsapp.command('outbox').description('Outbox operations (requires gateway)');
|
|
318
|
+
|
|
319
|
+
outbox
|
|
320
|
+
.command('send')
|
|
321
|
+
.description('Queue a message for sending')
|
|
322
|
+
.option('--profile <name>', 'Profile name')
|
|
323
|
+
.option('--to <phone>', 'Destination phone number (with country code, e.g., +1234567890)')
|
|
324
|
+
.option('--group <name>', 'Destination group (name or JID)')
|
|
325
|
+
.option('--attachment <path>', 'Path to file attachment (image, video, audio, or document)')
|
|
326
|
+
.option('--type <type>', 'Media type: image, video, audio, document (auto-detected if not specified)')
|
|
327
|
+
.argument('[message]', 'Message text or caption (or pipe via stdin)')
|
|
328
|
+
.action(async (message: string | undefined, options) => {
|
|
329
|
+
try {
|
|
330
|
+
const profileResult = await resolveProfile('whatsapp', options.profile);
|
|
331
|
+
if (!profileResult.profile) {
|
|
332
|
+
if (profileResult.error === 'none') {
|
|
333
|
+
throw new CliError('PROFILE_NOT_FOUND', 'No WhatsApp profiles configured', 'Run: agentio whatsapp profile add');
|
|
334
|
+
}
|
|
335
|
+
throw new CliError('INVALID_PARAMS', 'Multiple profiles exist. Use --profile to specify one.');
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (!options.to && !options.group) {
|
|
339
|
+
throw new CliError('INVALID_PARAMS', 'Destination required. Use --to <phone> or --group <name>');
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if (options.to && options.group) {
|
|
343
|
+
throw new CliError('INVALID_PARAMS', 'Use either --to or --group, not both.');
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
let text = message;
|
|
347
|
+
if (!text) {
|
|
348
|
+
text = await readStdin() || undefined;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Validate: need either text or attachment
|
|
352
|
+
if (!text && !options.attachment) {
|
|
353
|
+
throw new CliError('INVALID_PARAMS', 'Message or attachment is required.');
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Auto-detect media type from file extension if not specified
|
|
357
|
+
let mediaType = options.type as 'image' | 'video' | 'audio' | 'document' | undefined;
|
|
358
|
+
if (options.attachment && !mediaType) {
|
|
359
|
+
const ext = options.attachment.toLowerCase().split('.').pop();
|
|
360
|
+
if (['jpg', 'jpeg', 'png', 'gif', 'webp'].includes(ext || '')) {
|
|
361
|
+
mediaType = 'image';
|
|
362
|
+
} else if (['mp4', 'mov', 'avi', 'mkv', 'webm'].includes(ext || '')) {
|
|
363
|
+
mediaType = 'video';
|
|
364
|
+
} else if (['mp3', 'ogg', 'wav', 'm4a', 'opus'].includes(ext || '')) {
|
|
365
|
+
mediaType = 'audio';
|
|
366
|
+
} else {
|
|
367
|
+
mediaType = 'document';
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const client = await getGatewayClient();
|
|
372
|
+
|
|
373
|
+
// Resolve destination
|
|
374
|
+
let conversationId = options.to;
|
|
375
|
+
if (options.group) {
|
|
376
|
+
// Resolve group name to JID
|
|
377
|
+
if (options.group.includes('@g.us')) {
|
|
378
|
+
conversationId = options.group;
|
|
379
|
+
} else {
|
|
380
|
+
const resolved = await client.whatsappGroupResolve(profileResult.profile, options.group);
|
|
381
|
+
if (!resolved.groupId) {
|
|
382
|
+
throw new CliError('NOT_FOUND', `Group not found: ${options.group}`, 'Run: agentio whatsapp group list');
|
|
383
|
+
}
|
|
384
|
+
conversationId = resolved.groupId;
|
|
385
|
+
console.error(`Resolved group "${options.group}" to ${resolved.groupId}`);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const result = await client.outboxSend({
|
|
390
|
+
service: 'whatsapp',
|
|
391
|
+
profile: profileResult.profile,
|
|
392
|
+
conversationId,
|
|
393
|
+
content: text,
|
|
394
|
+
mediaPath: options.attachment,
|
|
395
|
+
mediaType,
|
|
396
|
+
});
|
|
397
|
+
printOutboxSendResult(result);
|
|
398
|
+
} catch (error) {
|
|
399
|
+
handleError(error);
|
|
400
|
+
}
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
outbox
|
|
404
|
+
.command('status')
|
|
405
|
+
.description('Check send status of a message')
|
|
406
|
+
.argument('<id>', 'Outbox message ID')
|
|
407
|
+
.action(async (id: string) => {
|
|
408
|
+
try {
|
|
409
|
+
const client = await getGatewayClient();
|
|
410
|
+
const message = await client.outboxStatus(id);
|
|
411
|
+
if (!message) {
|
|
412
|
+
throw new CliError('NOT_FOUND', `Message not found: ${id}`);
|
|
413
|
+
}
|
|
414
|
+
printOutboxMessage(message);
|
|
415
|
+
} catch (error) {
|
|
416
|
+
handleError(error);
|
|
417
|
+
}
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
outbox
|
|
421
|
+
.command('list')
|
|
422
|
+
.description('List outbox messages')
|
|
423
|
+
.option('--profile <name>', 'Profile name')
|
|
424
|
+
.option('--status <status>', 'Filter by status: pending, sending, sent, or failed')
|
|
425
|
+
.option('--limit <n>', 'Maximum messages to retrieve', '50')
|
|
426
|
+
.action(async (options) => {
|
|
427
|
+
try {
|
|
428
|
+
const profileResult = await resolveProfile('whatsapp', options.profile);
|
|
429
|
+
const client = await getGatewayClient();
|
|
430
|
+
const messages = await client.outboxList({
|
|
431
|
+
service: 'whatsapp',
|
|
432
|
+
profile: profileResult.profile ?? undefined,
|
|
433
|
+
status: options.status as 'pending' | 'sending' | 'sent' | 'failed' | undefined,
|
|
434
|
+
limit: parseInt(options.limit, 10),
|
|
435
|
+
});
|
|
436
|
+
printOutboxMessageList(messages);
|
|
437
|
+
} catch (error) {
|
|
438
|
+
handleError(error);
|
|
439
|
+
}
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
// Group subcommands
|
|
443
|
+
const group = whatsapp.command('group').description('Group management (requires gateway)');
|
|
444
|
+
|
|
445
|
+
group
|
|
446
|
+
.command('list')
|
|
447
|
+
.description('List all groups')
|
|
448
|
+
.option('--profile <name>', 'Profile name')
|
|
449
|
+
.action(async (options) => {
|
|
450
|
+
try {
|
|
451
|
+
const profileResult = await resolveProfile('whatsapp', options.profile);
|
|
452
|
+
if (!profileResult.profile) {
|
|
453
|
+
if (profileResult.error === 'none') {
|
|
454
|
+
throw new CliError('PROFILE_NOT_FOUND', 'No WhatsApp profiles configured', 'Run: agentio whatsapp profile add');
|
|
455
|
+
}
|
|
456
|
+
throw new CliError('INVALID_PARAMS', 'Multiple profiles exist. Use --profile to specify one.');
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
const client = await getGatewayClient();
|
|
460
|
+
const groups = await client.whatsappGroupList(profileResult.profile);
|
|
461
|
+
printWhatsAppGroupList(groups);
|
|
462
|
+
} catch (error) {
|
|
463
|
+
handleError(error);
|
|
464
|
+
}
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
group
|
|
468
|
+
.command('get')
|
|
469
|
+
.description('Get group details')
|
|
470
|
+
.argument('<id>', 'Group ID or name')
|
|
471
|
+
.option('--profile <name>', 'Profile name')
|
|
472
|
+
.action(async (id: string, options) => {
|
|
473
|
+
try {
|
|
474
|
+
const profileResult = await resolveProfile('whatsapp', options.profile);
|
|
475
|
+
if (!profileResult.profile) {
|
|
476
|
+
if (profileResult.error === 'none') {
|
|
477
|
+
throw new CliError('PROFILE_NOT_FOUND', 'No WhatsApp profiles configured', 'Run: agentio whatsapp profile add');
|
|
478
|
+
}
|
|
479
|
+
throw new CliError('INVALID_PARAMS', 'Multiple profiles exist. Use --profile to specify one.');
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
const client = await getGatewayClient();
|
|
483
|
+
|
|
484
|
+
// Resolve name to ID if needed
|
|
485
|
+
let groupId = id;
|
|
486
|
+
if (!id.includes('@g.us')) {
|
|
487
|
+
const resolved = await client.whatsappGroupResolve(profileResult.profile, id);
|
|
488
|
+
if (!resolved.groupId) {
|
|
489
|
+
throw new CliError('NOT_FOUND', `Group not found: ${id}`);
|
|
490
|
+
}
|
|
491
|
+
groupId = resolved.groupId;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
const group = await client.whatsappGroupGet(profileResult.profile, groupId);
|
|
495
|
+
printWhatsAppGroup(group);
|
|
496
|
+
} catch (error) {
|
|
497
|
+
handleError(error);
|
|
498
|
+
}
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
group
|
|
502
|
+
.command('create')
|
|
503
|
+
.description('Create a new group')
|
|
504
|
+
.argument('<name>', 'Group name')
|
|
505
|
+
.option('--profile <name>', 'Profile name')
|
|
506
|
+
.option('--participants <phones...>', 'Participant phone numbers')
|
|
507
|
+
.option('--picture <path>', 'Path to group profile picture')
|
|
508
|
+
.action(async (name: string, options) => {
|
|
509
|
+
try {
|
|
510
|
+
const profileResult = await resolveProfile('whatsapp', options.profile);
|
|
511
|
+
if (!profileResult.profile) {
|
|
512
|
+
if (profileResult.error === 'none') {
|
|
513
|
+
throw new CliError('PROFILE_NOT_FOUND', 'No WhatsApp profiles configured', 'Run: agentio whatsapp profile add');
|
|
514
|
+
}
|
|
515
|
+
throw new CliError('INVALID_PARAMS', 'Multiple profiles exist. Use --profile to specify one.');
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
if (!options.participants || options.participants.length === 0) {
|
|
519
|
+
throw new CliError('INVALID_PARAMS', 'At least one participant is required. Use --participants <phone...>');
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
const client = await getGatewayClient();
|
|
523
|
+
const group = await client.whatsappGroupCreate(
|
|
524
|
+
profileResult.profile,
|
|
525
|
+
name,
|
|
526
|
+
options.participants,
|
|
527
|
+
options.picture
|
|
528
|
+
);
|
|
529
|
+
printWhatsAppGroupCreated(group);
|
|
530
|
+
} catch (error) {
|
|
531
|
+
handleError(error);
|
|
532
|
+
}
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
group
|
|
536
|
+
.command('update')
|
|
537
|
+
.description('Update group info')
|
|
538
|
+
.argument('<id>', 'Group ID or name')
|
|
539
|
+
.option('--profile <name>', 'Profile name')
|
|
540
|
+
.option('--name <name>', 'New group name')
|
|
541
|
+
.option('--description <text>', 'New group description')
|
|
542
|
+
.option('--picture <path>', 'Path to new group profile picture')
|
|
543
|
+
.action(async (id: string, options) => {
|
|
544
|
+
try {
|
|
545
|
+
const profileResult = await resolveProfile('whatsapp', options.profile);
|
|
546
|
+
if (!profileResult.profile) {
|
|
547
|
+
if (profileResult.error === 'none') {
|
|
548
|
+
throw new CliError('PROFILE_NOT_FOUND', 'No WhatsApp profiles configured', 'Run: agentio whatsapp profile add');
|
|
549
|
+
}
|
|
550
|
+
throw new CliError('INVALID_PARAMS', 'Multiple profiles exist. Use --profile to specify one.');
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
if (!options.name && options.description === undefined && !options.picture) {
|
|
554
|
+
throw new CliError('INVALID_PARAMS', 'Provide --name, --description, or --picture to update.');
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
const client = await getGatewayClient();
|
|
558
|
+
|
|
559
|
+
// Resolve name to ID if needed
|
|
560
|
+
let groupId = id;
|
|
561
|
+
if (!id.includes('@g.us')) {
|
|
562
|
+
const resolved = await client.whatsappGroupResolve(profileResult.profile, id);
|
|
563
|
+
if (!resolved.groupId) {
|
|
564
|
+
throw new CliError('NOT_FOUND', `Group not found: ${id}`);
|
|
565
|
+
}
|
|
566
|
+
groupId = resolved.groupId;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
await client.whatsappGroupUpdate(profileResult.profile, groupId, {
|
|
570
|
+
subject: options.name,
|
|
571
|
+
description: options.description,
|
|
572
|
+
picture: options.picture,
|
|
573
|
+
});
|
|
574
|
+
console.log('Group updated');
|
|
575
|
+
} catch (error) {
|
|
576
|
+
handleError(error);
|
|
577
|
+
}
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
group
|
|
581
|
+
.command('add')
|
|
582
|
+
.description('Add participants to group')
|
|
583
|
+
.argument('<id>', 'Group ID or name')
|
|
584
|
+
.argument('<phones...>', 'Phone numbers to add')
|
|
585
|
+
.option('--profile <name>', 'Profile name')
|
|
586
|
+
.action(async (id: string, phones: string[], options) => {
|
|
587
|
+
try {
|
|
588
|
+
const profileResult = await resolveProfile('whatsapp', options.profile);
|
|
589
|
+
if (!profileResult.profile) {
|
|
590
|
+
if (profileResult.error === 'none') {
|
|
591
|
+
throw new CliError('PROFILE_NOT_FOUND', 'No WhatsApp profiles configured', 'Run: agentio whatsapp profile add');
|
|
592
|
+
}
|
|
593
|
+
throw new CliError('INVALID_PARAMS', 'Multiple profiles exist. Use --profile to specify one.');
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
const client = await getGatewayClient();
|
|
597
|
+
|
|
598
|
+
// Resolve name to ID if needed
|
|
599
|
+
let groupId = id;
|
|
600
|
+
if (!id.includes('@g.us')) {
|
|
601
|
+
const resolved = await client.whatsappGroupResolve(profileResult.profile, id);
|
|
602
|
+
if (!resolved.groupId) {
|
|
603
|
+
throw new CliError('NOT_FOUND', `Group not found: ${id}`);
|
|
604
|
+
}
|
|
605
|
+
groupId = resolved.groupId;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
const result = await client.whatsappGroupParticipants(
|
|
609
|
+
profileResult.profile,
|
|
610
|
+
groupId,
|
|
611
|
+
phones,
|
|
612
|
+
'add'
|
|
613
|
+
);
|
|
614
|
+
if (result.results) {
|
|
615
|
+
printWhatsAppParticipantsResult('added', result.results);
|
|
616
|
+
} else {
|
|
617
|
+
console.log('Participants added');
|
|
618
|
+
}
|
|
619
|
+
} catch (error) {
|
|
620
|
+
handleError(error);
|
|
621
|
+
}
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
group
|
|
625
|
+
.command('remove')
|
|
626
|
+
.description('Remove participants from group')
|
|
627
|
+
.argument('<id>', 'Group ID or name')
|
|
628
|
+
.argument('<phones...>', 'Phone numbers to remove')
|
|
629
|
+
.option('--profile <name>', 'Profile name')
|
|
630
|
+
.action(async (id: string, phones: string[], options) => {
|
|
631
|
+
try {
|
|
632
|
+
const profileResult = await resolveProfile('whatsapp', options.profile);
|
|
633
|
+
if (!profileResult.profile) {
|
|
634
|
+
if (profileResult.error === 'none') {
|
|
635
|
+
throw new CliError('PROFILE_NOT_FOUND', 'No WhatsApp profiles configured', 'Run: agentio whatsapp profile add');
|
|
636
|
+
}
|
|
637
|
+
throw new CliError('INVALID_PARAMS', 'Multiple profiles exist. Use --profile to specify one.');
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
const client = await getGatewayClient();
|
|
641
|
+
|
|
642
|
+
// Resolve name to ID if needed
|
|
643
|
+
let groupId = id;
|
|
644
|
+
if (!id.includes('@g.us')) {
|
|
645
|
+
const resolved = await client.whatsappGroupResolve(profileResult.profile, id);
|
|
646
|
+
if (!resolved.groupId) {
|
|
647
|
+
throw new CliError('NOT_FOUND', `Group not found: ${id}`);
|
|
648
|
+
}
|
|
649
|
+
groupId = resolved.groupId;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
const result = await client.whatsappGroupParticipants(
|
|
653
|
+
profileResult.profile,
|
|
654
|
+
groupId,
|
|
655
|
+
phones,
|
|
656
|
+
'remove'
|
|
657
|
+
);
|
|
658
|
+
if (result.results) {
|
|
659
|
+
printWhatsAppParticipantsResult('removed', result.results);
|
|
660
|
+
} else {
|
|
661
|
+
console.log('Participants removed');
|
|
662
|
+
}
|
|
663
|
+
} catch (error) {
|
|
664
|
+
handleError(error);
|
|
665
|
+
}
|
|
666
|
+
});
|
|
667
|
+
|
|
668
|
+
group
|
|
669
|
+
.command('promote')
|
|
670
|
+
.description('Promote participants to admin')
|
|
671
|
+
.argument('<id>', 'Group ID or name')
|
|
672
|
+
.argument('<phones...>', 'Phone numbers to promote')
|
|
673
|
+
.option('--profile <name>', 'Profile name')
|
|
674
|
+
.action(async (id: string, phones: string[], options) => {
|
|
675
|
+
try {
|
|
676
|
+
const profileResult = await resolveProfile('whatsapp', options.profile);
|
|
677
|
+
if (!profileResult.profile) {
|
|
678
|
+
if (profileResult.error === 'none') {
|
|
679
|
+
throw new CliError('PROFILE_NOT_FOUND', 'No WhatsApp profiles configured', 'Run: agentio whatsapp profile add');
|
|
680
|
+
}
|
|
681
|
+
throw new CliError('INVALID_PARAMS', 'Multiple profiles exist. Use --profile to specify one.');
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
const client = await getGatewayClient();
|
|
685
|
+
|
|
686
|
+
// Resolve name to ID if needed
|
|
687
|
+
let groupId = id;
|
|
688
|
+
if (!id.includes('@g.us')) {
|
|
689
|
+
const resolved = await client.whatsappGroupResolve(profileResult.profile, id);
|
|
690
|
+
if (!resolved.groupId) {
|
|
691
|
+
throw new CliError('NOT_FOUND', `Group not found: ${id}`);
|
|
692
|
+
}
|
|
693
|
+
groupId = resolved.groupId;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
const result = await client.whatsappGroupParticipants(
|
|
697
|
+
profileResult.profile,
|
|
698
|
+
groupId,
|
|
699
|
+
phones,
|
|
700
|
+
'promote'
|
|
701
|
+
);
|
|
702
|
+
if (result.results) {
|
|
703
|
+
printWhatsAppParticipantsResult('promoted', result.results);
|
|
704
|
+
} else {
|
|
705
|
+
console.log('Participants promoted to admin');
|
|
706
|
+
}
|
|
707
|
+
} catch (error) {
|
|
708
|
+
handleError(error);
|
|
709
|
+
}
|
|
710
|
+
});
|
|
711
|
+
|
|
712
|
+
group
|
|
713
|
+
.command('demote')
|
|
714
|
+
.description('Demote admins to regular participants')
|
|
715
|
+
.argument('<id>', 'Group ID or name')
|
|
716
|
+
.argument('<phones...>', 'Phone numbers to demote')
|
|
717
|
+
.option('--profile <name>', 'Profile name')
|
|
718
|
+
.action(async (id: string, phones: string[], options) => {
|
|
719
|
+
try {
|
|
720
|
+
const profileResult = await resolveProfile('whatsapp', options.profile);
|
|
721
|
+
if (!profileResult.profile) {
|
|
722
|
+
if (profileResult.error === 'none') {
|
|
723
|
+
throw new CliError('PROFILE_NOT_FOUND', 'No WhatsApp profiles configured', 'Run: agentio whatsapp profile add');
|
|
724
|
+
}
|
|
725
|
+
throw new CliError('INVALID_PARAMS', 'Multiple profiles exist. Use --profile to specify one.');
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
const client = await getGatewayClient();
|
|
729
|
+
|
|
730
|
+
// Resolve name to ID if needed
|
|
731
|
+
let groupId = id;
|
|
732
|
+
if (!id.includes('@g.us')) {
|
|
733
|
+
const resolved = await client.whatsappGroupResolve(profileResult.profile, id);
|
|
734
|
+
if (!resolved.groupId) {
|
|
735
|
+
throw new CliError('NOT_FOUND', `Group not found: ${id}`);
|
|
736
|
+
}
|
|
737
|
+
groupId = resolved.groupId;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
const result = await client.whatsappGroupParticipants(
|
|
741
|
+
profileResult.profile,
|
|
742
|
+
groupId,
|
|
743
|
+
phones,
|
|
744
|
+
'demote'
|
|
745
|
+
);
|
|
746
|
+
if (result.results) {
|
|
747
|
+
printWhatsAppParticipantsResult('demoted', result.results);
|
|
748
|
+
} else {
|
|
749
|
+
console.log('Admins demoted to regular participants');
|
|
750
|
+
}
|
|
751
|
+
} catch (error) {
|
|
752
|
+
handleError(error);
|
|
753
|
+
}
|
|
754
|
+
});
|
|
755
|
+
|
|
756
|
+
group
|
|
757
|
+
.command('leave')
|
|
758
|
+
.description('Leave a group')
|
|
759
|
+
.argument('<id>', 'Group ID or name')
|
|
760
|
+
.option('--profile <name>', 'Profile name')
|
|
761
|
+
.action(async (id: string, options) => {
|
|
762
|
+
try {
|
|
763
|
+
const profileResult = await resolveProfile('whatsapp', options.profile);
|
|
764
|
+
if (!profileResult.profile) {
|
|
765
|
+
if (profileResult.error === 'none') {
|
|
766
|
+
throw new CliError('PROFILE_NOT_FOUND', 'No WhatsApp profiles configured', 'Run: agentio whatsapp profile add');
|
|
767
|
+
}
|
|
768
|
+
throw new CliError('INVALID_PARAMS', 'Multiple profiles exist. Use --profile to specify one.');
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
const client = await getGatewayClient();
|
|
772
|
+
|
|
773
|
+
// Resolve name to ID if needed
|
|
774
|
+
let groupId = id;
|
|
775
|
+
if (!id.includes('@g.us')) {
|
|
776
|
+
const resolved = await client.whatsappGroupResolve(profileResult.profile, id);
|
|
777
|
+
if (!resolved.groupId) {
|
|
778
|
+
throw new CliError('NOT_FOUND', `Group not found: ${id}`);
|
|
779
|
+
}
|
|
780
|
+
groupId = resolved.groupId;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
// Confirm before leaving
|
|
784
|
+
const confirmed = await confirm(`Leave group ${id}?`);
|
|
785
|
+
if (!confirmed) {
|
|
786
|
+
console.log('Cancelled');
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
await client.whatsappGroupLeave(profileResult.profile, groupId);
|
|
791
|
+
printWhatsAppGroupLeft(groupId);
|
|
792
|
+
} catch (error) {
|
|
793
|
+
handleError(error);
|
|
794
|
+
}
|
|
795
|
+
});
|
|
796
|
+
|
|
797
|
+
group
|
|
798
|
+
.command('invite')
|
|
799
|
+
.description('Get group invite link')
|
|
800
|
+
.argument('<id>', 'Group ID or name')
|
|
801
|
+
.option('--profile <name>', 'Profile name')
|
|
802
|
+
.action(async (id: string, options) => {
|
|
803
|
+
try {
|
|
804
|
+
const profileResult = await resolveProfile('whatsapp', options.profile);
|
|
805
|
+
if (!profileResult.profile) {
|
|
806
|
+
if (profileResult.error === 'none') {
|
|
807
|
+
throw new CliError('PROFILE_NOT_FOUND', 'No WhatsApp profiles configured', 'Run: agentio whatsapp profile add');
|
|
808
|
+
}
|
|
809
|
+
throw new CliError('INVALID_PARAMS', 'Multiple profiles exist. Use --profile to specify one.');
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
const client = await getGatewayClient();
|
|
813
|
+
|
|
814
|
+
// Resolve name to ID if needed
|
|
815
|
+
let groupId = id;
|
|
816
|
+
if (!id.includes('@g.us')) {
|
|
817
|
+
const resolved = await client.whatsappGroupResolve(profileResult.profile, id);
|
|
818
|
+
if (!resolved.groupId) {
|
|
819
|
+
throw new CliError('NOT_FOUND', `Group not found: ${id}`);
|
|
820
|
+
}
|
|
821
|
+
groupId = resolved.groupId;
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
const result = await client.whatsappGroupInvite(profileResult.profile, groupId);
|
|
825
|
+
printWhatsAppGroupInvite(result);
|
|
826
|
+
} catch (error) {
|
|
827
|
+
handleError(error);
|
|
828
|
+
}
|
|
829
|
+
});
|
|
830
|
+
|
|
831
|
+
group
|
|
832
|
+
.command('join')
|
|
833
|
+
.description('Join group via invite code or link')
|
|
834
|
+
.argument('<code>', 'Invite code or full link (https://chat.whatsapp.com/...)')
|
|
835
|
+
.option('--profile <name>', 'Profile name')
|
|
836
|
+
.action(async (code: string, options) => {
|
|
837
|
+
try {
|
|
838
|
+
const profileResult = await resolveProfile('whatsapp', options.profile);
|
|
839
|
+
if (!profileResult.profile) {
|
|
840
|
+
if (profileResult.error === 'none') {
|
|
841
|
+
throw new CliError('PROFILE_NOT_FOUND', 'No WhatsApp profiles configured', 'Run: agentio whatsapp profile add');
|
|
842
|
+
}
|
|
843
|
+
throw new CliError('INVALID_PARAMS', 'Multiple profiles exist. Use --profile to specify one.');
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
const client = await getGatewayClient();
|
|
847
|
+
const groupId = await client.whatsappGroupJoin(profileResult.profile, code);
|
|
848
|
+
printWhatsAppGroupJoined(groupId);
|
|
849
|
+
} catch (error) {
|
|
850
|
+
handleError(error);
|
|
851
|
+
}
|
|
852
|
+
});
|
|
853
|
+
}
|