@hasna/connectors 0.3.16 → 0.4.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 (127) hide show
  1. package/bin/index.js +71 -1
  2. package/bin/mcp.js +71 -1
  3. package/bin/serve.js +70 -0
  4. package/connectors/connect-asana/.env.example +11 -0
  5. package/connectors/connect-asana/CLAUDE.md +128 -0
  6. package/connectors/connect-asana/README.md +193 -0
  7. package/connectors/connect-asana/package.json +52 -0
  8. package/connectors/connect-asana/src/api/client.ts +119 -0
  9. package/connectors/connect-asana/src/api/index.ts +319 -0
  10. package/connectors/connect-asana/src/cli/index.ts +731 -0
  11. package/connectors/connect-asana/src/index.ts +19 -0
  12. package/connectors/connect-asana/src/types/index.ts +270 -0
  13. package/connectors/connect-asana/src/utils/config.ts +171 -0
  14. package/connectors/connect-asana/src/utils/output.ts +119 -0
  15. package/connectors/connect-asana/tsconfig.json +16 -0
  16. package/connectors/connect-clickup/.env.example +11 -0
  17. package/connectors/connect-clickup/CLAUDE.md +128 -0
  18. package/connectors/connect-clickup/README.md +193 -0
  19. package/connectors/connect-clickup/package.json +52 -0
  20. package/connectors/connect-clickup/src/api/client.ts +116 -0
  21. package/connectors/connect-clickup/src/api/index.ts +400 -0
  22. package/connectors/connect-clickup/src/cli/index.ts +625 -0
  23. package/connectors/connect-clickup/src/index.ts +19 -0
  24. package/connectors/connect-clickup/src/types/index.ts +591 -0
  25. package/connectors/connect-clickup/src/utils/config.ts +157 -0
  26. package/connectors/connect-clickup/src/utils/output.ts +119 -0
  27. package/connectors/connect-clickup/tsconfig.json +16 -0
  28. package/connectors/connect-confluence/.env.example +11 -0
  29. package/connectors/connect-confluence/CLAUDE.md +272 -0
  30. package/connectors/connect-confluence/README.md +193 -0
  31. package/connectors/connect-confluence/package.json +53 -0
  32. package/connectors/connect-confluence/scripts/release.ts +179 -0
  33. package/connectors/connect-confluence/src/api/client.ts +213 -0
  34. package/connectors/connect-confluence/src/api/example.ts +48 -0
  35. package/connectors/connect-confluence/src/api/index.ts +51 -0
  36. package/connectors/connect-confluence/src/cli/index.ts +254 -0
  37. package/connectors/connect-confluence/src/index.ts +103 -0
  38. package/connectors/connect-confluence/src/types/index.ts +237 -0
  39. package/connectors/connect-confluence/src/utils/auth.ts +274 -0
  40. package/connectors/connect-confluence/src/utils/bulk.ts +212 -0
  41. package/connectors/connect-confluence/src/utils/config.ts +326 -0
  42. package/connectors/connect-confluence/src/utils/output.ts +175 -0
  43. package/connectors/connect-confluence/src/utils/settings.ts +114 -0
  44. package/connectors/connect-confluence/src/utils/storage.ts +198 -0
  45. package/connectors/connect-confluence/tsconfig.json +16 -0
  46. package/connectors/connect-jira/.env.example +11 -0
  47. package/connectors/connect-jira/CLAUDE.md +128 -0
  48. package/connectors/connect-jira/README.md +193 -0
  49. package/connectors/connect-jira/package.json +53 -0
  50. package/connectors/connect-jira/src/api/client.ts +131 -0
  51. package/connectors/connect-jira/src/api/index.ts +266 -0
  52. package/connectors/connect-jira/src/cli/index.ts +653 -0
  53. package/connectors/connect-jira/src/index.ts +23 -0
  54. package/connectors/connect-jira/src/types/index.ts +448 -0
  55. package/connectors/connect-jira/src/utils/config.ts +179 -0
  56. package/connectors/connect-jira/src/utils/output.ts +119 -0
  57. package/connectors/connect-jira/tsconfig.json +16 -0
  58. package/connectors/connect-linear/CLAUDE.md +88 -0
  59. package/connectors/connect-linear/README.md +201 -0
  60. package/connectors/connect-linear/package.json +45 -0
  61. package/connectors/connect-linear/src/api/client.ts +62 -0
  62. package/connectors/connect-linear/src/api/index.ts +46 -0
  63. package/connectors/connect-linear/src/api/issues.ts +247 -0
  64. package/connectors/connect-linear/src/api/projects.ts +179 -0
  65. package/connectors/connect-linear/src/api/teams.ts +125 -0
  66. package/connectors/connect-linear/src/api/users.ts +112 -0
  67. package/connectors/connect-linear/src/cli/index.ts +560 -0
  68. package/connectors/connect-linear/src/index.ts +27 -0
  69. package/connectors/connect-linear/src/types/index.ts +275 -0
  70. package/connectors/connect-linear/src/utils/config.ts +249 -0
  71. package/connectors/connect-linear/src/utils/output.ts +119 -0
  72. package/connectors/connect-linear/tsconfig.json +16 -0
  73. package/connectors/connect-slack/.env.example +7 -0
  74. package/connectors/connect-slack/CLAUDE.md +69 -0
  75. package/connectors/connect-slack/README.md +150 -0
  76. package/connectors/connect-slack/package.json +44 -0
  77. package/connectors/connect-slack/src/api/channels.ts +112 -0
  78. package/connectors/connect-slack/src/api/client.ts +97 -0
  79. package/connectors/connect-slack/src/api/index.ts +42 -0
  80. package/connectors/connect-slack/src/api/messages.ts +127 -0
  81. package/connectors/connect-slack/src/api/users.ts +110 -0
  82. package/connectors/connect-slack/src/cli/index.ts +494 -0
  83. package/connectors/connect-slack/src/index.ts +21 -0
  84. package/connectors/connect-slack/src/types/index.ts +263 -0
  85. package/connectors/connect-slack/src/utils/config.ts +297 -0
  86. package/connectors/connect-slack/src/utils/output.ts +119 -0
  87. package/connectors/connect-slack/tsconfig.json +16 -0
  88. package/connectors/connect-telegram/.env.example +2 -0
  89. package/connectors/connect-telegram/package.json +49 -0
  90. package/connectors/connect-todoist/.env.example +11 -0
  91. package/connectors/connect-todoist/CLAUDE.md +104 -0
  92. package/connectors/connect-todoist/README.md +193 -0
  93. package/connectors/connect-todoist/package.json +52 -0
  94. package/connectors/connect-todoist/src/api/client.ts +117 -0
  95. package/connectors/connect-todoist/src/api/index.ts +188 -0
  96. package/connectors/connect-todoist/src/cli/index.ts +990 -0
  97. package/connectors/connect-todoist/src/index.ts +21 -0
  98. package/connectors/connect-todoist/src/types/index.ts +240 -0
  99. package/connectors/connect-todoist/src/utils/config.ts +157 -0
  100. package/connectors/connect-todoist/src/utils/output.ts +119 -0
  101. package/connectors/connect-todoist/tsconfig.json +16 -0
  102. package/connectors/connect-trello/.env.example +11 -0
  103. package/connectors/connect-trello/CLAUDE.md +128 -0
  104. package/connectors/connect-trello/README.md +193 -0
  105. package/connectors/connect-trello/package.json +53 -0
  106. package/connectors/connect-trello/src/api/client.ts +128 -0
  107. package/connectors/connect-trello/src/api/index.ts +278 -0
  108. package/connectors/connect-trello/src/cli/index.ts +737 -0
  109. package/connectors/connect-trello/src/index.ts +21 -0
  110. package/connectors/connect-trello/src/types/index.ts +314 -0
  111. package/connectors/connect-trello/src/utils/config.ts +182 -0
  112. package/connectors/connect-trello/src/utils/output.ts +119 -0
  113. package/connectors/connect-trello/tsconfig.json +16 -0
  114. package/connectors/connect-whatsapp/.env.example +11 -0
  115. package/connectors/connect-whatsapp/CLAUDE.md +113 -0
  116. package/connectors/connect-whatsapp/README.md +193 -0
  117. package/connectors/connect-whatsapp/package.json +53 -0
  118. package/connectors/connect-whatsapp/src/api/client.ts +133 -0
  119. package/connectors/connect-whatsapp/src/api/index.ts +365 -0
  120. package/connectors/connect-whatsapp/src/cli/index.ts +686 -0
  121. package/connectors/connect-whatsapp/src/index.ts +25 -0
  122. package/connectors/connect-whatsapp/src/types/index.ts +502 -0
  123. package/connectors/connect-whatsapp/src/utils/config.ts +179 -0
  124. package/connectors/connect-whatsapp/src/utils/output.ts +119 -0
  125. package/connectors/connect-whatsapp/tsconfig.json +16 -0
  126. package/dist/index.js +70 -0
  127. package/package.json +1 -1
@@ -0,0 +1,686 @@
1
+ #!/usr/bin/env bun
2
+ import { Command } from 'commander';
3
+ import chalk from 'chalk';
4
+ import { WhatsApp } from '../api';
5
+ import type { WhatsAppConfig, OutputFormat } from '../types';
6
+ import {
7
+ getAccessToken,
8
+ setAccessToken,
9
+ getPhoneNumberId,
10
+ setPhoneNumberId,
11
+ getBusinessAccountId,
12
+ setBusinessAccountId,
13
+ clearConfig,
14
+ getConfigDir,
15
+ getCurrentProfile,
16
+ setCurrentProfile,
17
+ listProfiles,
18
+ createProfile,
19
+ deleteProfile,
20
+ loadProfile,
21
+ setProfileOverride,
22
+ getActiveProfileName,
23
+ } from '../utils/config';
24
+
25
+ const program = new Command();
26
+
27
+ function getClient(): WhatsApp {
28
+ const accessToken = getAccessToken();
29
+ const phoneNumberId = getPhoneNumberId();
30
+
31
+ if (!accessToken) {
32
+ console.error(chalk.red('Error: Access token not configured.'));
33
+ console.error(chalk.yellow('Run: connect-whatsapp config set-token <access-token>'));
34
+ console.error(chalk.yellow('Or set WHATSAPP_ACCESS_TOKEN environment variable'));
35
+ process.exit(1);
36
+ }
37
+
38
+ if (!phoneNumberId) {
39
+ console.error(chalk.red('Error: Phone number ID not configured.'));
40
+ console.error(chalk.yellow('Run: connect-whatsapp config set-phone <phone-number-id>'));
41
+ console.error(chalk.yellow('Or set WHATSAPP_PHONE_NUMBER_ID environment variable'));
42
+ process.exit(1);
43
+ }
44
+
45
+ const config: WhatsAppConfig = {
46
+ accessToken,
47
+ phoneNumberId,
48
+ businessAccountId: getBusinessAccountId(),
49
+ };
50
+ return new WhatsApp(config);
51
+ }
52
+
53
+ function formatOutput(data: unknown, format: OutputFormat): void {
54
+ if (format === 'json') {
55
+ console.log(JSON.stringify(data, null, 2));
56
+ } else {
57
+ console.log(data);
58
+ }
59
+ }
60
+
61
+ // ============================================
62
+ // Profile Commands
63
+ // ============================================
64
+
65
+ const profileCmd = new Command('profile')
66
+ .description('Manage configuration profiles');
67
+
68
+ profileCmd
69
+ .command('list')
70
+ .description('List all profiles')
71
+ .action(() => {
72
+ const profiles = listProfiles();
73
+ const current = getCurrentProfile();
74
+
75
+ if (profiles.length === 0) {
76
+ console.log(chalk.yellow('No profiles configured.'));
77
+ console.log(chalk.gray('Create one with: connect-whatsapp profile create <name>'));
78
+ return;
79
+ }
80
+
81
+ console.log(chalk.bold('Profiles:'));
82
+ for (const profile of profiles) {
83
+ const marker = profile === current ? chalk.green(' (active)') : '';
84
+ console.log(` ${profile}${marker}`);
85
+ }
86
+ });
87
+
88
+ profileCmd
89
+ .command('current')
90
+ .description('Show current active profile')
91
+ .action(() => {
92
+ const current = getCurrentProfile();
93
+ console.log(current);
94
+ });
95
+
96
+ profileCmd
97
+ .command('use <name>')
98
+ .description('Switch to a profile')
99
+ .action((name: string) => {
100
+ try {
101
+ setCurrentProfile(name);
102
+ console.log(chalk.green(`Switched to profile: ${name}`));
103
+ } catch (error) {
104
+ console.error(chalk.red(`Error: ${(error as Error).message}`));
105
+ process.exit(1);
106
+ }
107
+ });
108
+
109
+ profileCmd
110
+ .command('create <name>')
111
+ .description('Create a new profile')
112
+ .action((name: string) => {
113
+ try {
114
+ const created = createProfile(name);
115
+ if (created) {
116
+ console.log(chalk.green(`Created profile: ${name}`));
117
+ } else {
118
+ console.log(chalk.yellow(`Profile already exists: ${name}`));
119
+ }
120
+ } catch (error) {
121
+ console.error(chalk.red(`Error: ${(error as Error).message}`));
122
+ process.exit(1);
123
+ }
124
+ });
125
+
126
+ profileCmd
127
+ .command('delete <name>')
128
+ .description('Delete a profile')
129
+ .action((name: string) => {
130
+ const deleted = deleteProfile(name);
131
+ if (deleted) {
132
+ console.log(chalk.green(`Deleted profile: ${name}`));
133
+ } else {
134
+ console.log(chalk.yellow(`Could not delete profile: ${name}`));
135
+ }
136
+ });
137
+
138
+ profileCmd
139
+ .command('show [name]')
140
+ .description('Show profile configuration')
141
+ .action((name?: string) => {
142
+ const profile = loadProfile(name);
143
+ const profileName = name || getCurrentProfile();
144
+ console.log(chalk.bold(`Profile: ${profileName}`));
145
+ console.log(chalk.gray('Access Token:'), profile.accessToken ? '***configured***' : 'not set');
146
+ console.log(chalk.gray('Phone Number ID:'), profile.phoneNumberId || 'not set');
147
+ console.log(chalk.gray('Business Account ID:'), profile.businessAccountId || 'not set');
148
+ });
149
+
150
+ // ============================================
151
+ // Config Commands
152
+ // ============================================
153
+
154
+ const configCmd = new Command('config')
155
+ .description('Manage configuration');
156
+
157
+ configCmd
158
+ .command('set-token <accessToken>')
159
+ .description('Set the access token for current profile')
160
+ .action((accessToken: string) => {
161
+ setAccessToken(accessToken);
162
+ const profile = getActiveProfileName();
163
+ console.log(chalk.green(`Access token saved to profile: ${profile}`));
164
+ });
165
+
166
+ configCmd
167
+ .command('set-phone <phoneNumberId>')
168
+ .description('Set the phone number ID for current profile')
169
+ .action((phoneNumberId: string) => {
170
+ setPhoneNumberId(phoneNumberId);
171
+ const profile = getActiveProfileName();
172
+ console.log(chalk.green(`Phone number ID saved to profile: ${profile}`));
173
+ });
174
+
175
+ configCmd
176
+ .command('set-business <businessAccountId>')
177
+ .description('Set the business account ID for current profile')
178
+ .action((businessAccountId: string) => {
179
+ setBusinessAccountId(businessAccountId);
180
+ const profile = getActiveProfileName();
181
+ console.log(chalk.green(`Business account ID saved to profile: ${profile}`));
182
+ });
183
+
184
+ configCmd
185
+ .command('show')
186
+ .description('Show current configuration')
187
+ .action(() => {
188
+ const profile = getCurrentProfile();
189
+ const accessToken = getAccessToken();
190
+ const phoneNumberId = getPhoneNumberId();
191
+ const businessAccountId = getBusinessAccountId();
192
+ const configDir = getConfigDir();
193
+
194
+ console.log(chalk.bold('Current Configuration:'));
195
+ console.log(chalk.gray('Profile:'), profile);
196
+ console.log(chalk.gray('Config directory:'), configDir);
197
+ console.log(chalk.gray('Access Token:'), accessToken ? '***configured***' : 'not set');
198
+ console.log(chalk.gray('Phone Number ID:'), phoneNumberId || 'not set');
199
+ console.log(chalk.gray('Business Account ID:'), businessAccountId || 'not set');
200
+ });
201
+
202
+ configCmd
203
+ .command('clear')
204
+ .description('Clear configuration for current profile')
205
+ .action(() => {
206
+ clearConfig();
207
+ console.log(chalk.green('Configuration cleared.'));
208
+ });
209
+
210
+ configCmd
211
+ .command('path')
212
+ .description('Show configuration directory path')
213
+ .action(() => {
214
+ console.log(getConfigDir());
215
+ });
216
+
217
+ // ============================================
218
+ // Message Commands
219
+ // ============================================
220
+
221
+ const messageCmd = new Command('message')
222
+ .description('Send messages');
223
+
224
+ messageCmd
225
+ .command('text <to> <text>')
226
+ .description('Send a text message')
227
+ .option('--preview', 'Enable URL preview')
228
+ .option('-r, --reply-to <messageId>', 'Reply to message ID')
229
+ .option('-f, --format <format>', 'Output format (json, pretty)', 'pretty')
230
+ .action(async (to: string, text: string, options) => {
231
+ try {
232
+ const client = getClient();
233
+ const result = await client.sendText(to, text, {
234
+ previewUrl: options.preview,
235
+ replyToMessageId: options.replyTo,
236
+ });
237
+
238
+ if (options.format === 'json') {
239
+ formatOutput(result, 'json');
240
+ } else {
241
+ console.log(chalk.green('Message sent.'));
242
+ if (result.messages && result.messages.length > 0) {
243
+ console.log(chalk.gray('Message ID:'), result.messages[0].id);
244
+ }
245
+ }
246
+ } catch (error) {
247
+ console.error(chalk.red(`Error: ${(error as Error).message}`));
248
+ process.exit(1);
249
+ }
250
+ });
251
+
252
+ messageCmd
253
+ .command('image <to> <urlOrId>')
254
+ .description('Send an image (URL or media ID)')
255
+ .option('-c, --caption <caption>', 'Image caption')
256
+ .option('-r, --reply-to <messageId>', 'Reply to message ID')
257
+ .option('-f, --format <format>', 'Output format (json, pretty)', 'pretty')
258
+ .action(async (to: string, urlOrId: string, options) => {
259
+ try {
260
+ const client = getClient();
261
+ const media = urlOrId.startsWith('http') ? { link: urlOrId, caption: options.caption } : { id: urlOrId, caption: options.caption };
262
+ const result = await client.sendImage(to, media, {
263
+ replyToMessageId: options.replyTo,
264
+ });
265
+
266
+ if (options.format === 'json') {
267
+ formatOutput(result, 'json');
268
+ } else {
269
+ console.log(chalk.green('Image sent.'));
270
+ if (result.messages && result.messages.length > 0) {
271
+ console.log(chalk.gray('Message ID:'), result.messages[0].id);
272
+ }
273
+ }
274
+ } catch (error) {
275
+ console.error(chalk.red(`Error: ${(error as Error).message}`));
276
+ process.exit(1);
277
+ }
278
+ });
279
+
280
+ messageCmd
281
+ .command('document <to> <urlOrId>')
282
+ .description('Send a document (URL or media ID)')
283
+ .option('-c, --caption <caption>', 'Document caption')
284
+ .option('-n, --filename <filename>', 'File name')
285
+ .option('-r, --reply-to <messageId>', 'Reply to message ID')
286
+ .option('-f, --format <format>', 'Output format (json, pretty)', 'pretty')
287
+ .action(async (to: string, urlOrId: string, options) => {
288
+ try {
289
+ const client = getClient();
290
+ const media = urlOrId.startsWith('http')
291
+ ? { link: urlOrId, caption: options.caption, filename: options.filename }
292
+ : { id: urlOrId, caption: options.caption, filename: options.filename };
293
+ const result = await client.sendDocument(to, media, {
294
+ replyToMessageId: options.replyTo,
295
+ });
296
+
297
+ if (options.format === 'json') {
298
+ formatOutput(result, 'json');
299
+ } else {
300
+ console.log(chalk.green('Document sent.'));
301
+ if (result.messages && result.messages.length > 0) {
302
+ console.log(chalk.gray('Message ID:'), result.messages[0].id);
303
+ }
304
+ }
305
+ } catch (error) {
306
+ console.error(chalk.red(`Error: ${(error as Error).message}`));
307
+ process.exit(1);
308
+ }
309
+ });
310
+
311
+ messageCmd
312
+ .command('location <to> <latitude> <longitude>')
313
+ .description('Send a location')
314
+ .option('-n, --name <name>', 'Location name')
315
+ .option('-a, --address <address>', 'Location address')
316
+ .option('-r, --reply-to <messageId>', 'Reply to message ID')
317
+ .option('-f, --format <format>', 'Output format (json, pretty)', 'pretty')
318
+ .action(async (to: string, latitude: string, longitude: string, options) => {
319
+ try {
320
+ const client = getClient();
321
+ const result = await client.sendLocation(to, {
322
+ latitude: Number(latitude),
323
+ longitude: Number(longitude),
324
+ name: options.name,
325
+ address: options.address,
326
+ }, {
327
+ replyToMessageId: options.replyTo,
328
+ });
329
+
330
+ if (options.format === 'json') {
331
+ formatOutput(result, 'json');
332
+ } else {
333
+ console.log(chalk.green('Location sent.'));
334
+ if (result.messages && result.messages.length > 0) {
335
+ console.log(chalk.gray('Message ID:'), result.messages[0].id);
336
+ }
337
+ }
338
+ } catch (error) {
339
+ console.error(chalk.red(`Error: ${(error as Error).message}`));
340
+ process.exit(1);
341
+ }
342
+ });
343
+
344
+ messageCmd
345
+ .command('template <to> <templateName> <languageCode>')
346
+ .description('Send a template message')
347
+ .option('-r, --reply-to <messageId>', 'Reply to message ID')
348
+ .option('-f, --format <format>', 'Output format (json, pretty)', 'pretty')
349
+ .action(async (to: string, templateName: string, languageCode: string, options) => {
350
+ try {
351
+ const client = getClient();
352
+ const result = await client.sendTemplate(to, {
353
+ name: templateName,
354
+ language: { code: languageCode },
355
+ }, {
356
+ replyToMessageId: options.replyTo,
357
+ });
358
+
359
+ if (options.format === 'json') {
360
+ formatOutput(result, 'json');
361
+ } else {
362
+ console.log(chalk.green('Template message sent.'));
363
+ if (result.messages && result.messages.length > 0) {
364
+ console.log(chalk.gray('Message ID:'), result.messages[0].id);
365
+ }
366
+ }
367
+ } catch (error) {
368
+ console.error(chalk.red(`Error: ${(error as Error).message}`));
369
+ process.exit(1);
370
+ }
371
+ });
372
+
373
+ messageCmd
374
+ .command('react <to> <messageId> <emoji>')
375
+ .description('React to a message')
376
+ .option('-f, --format <format>', 'Output format (json, pretty)', 'pretty')
377
+ .action(async (to: string, messageId: string, emoji: string, options) => {
378
+ try {
379
+ const client = getClient();
380
+ const result = await client.sendReaction(to, {
381
+ message_id: messageId,
382
+ emoji,
383
+ });
384
+
385
+ if (options.format === 'json') {
386
+ formatOutput(result, 'json');
387
+ } else {
388
+ console.log(chalk.green('Reaction sent.'));
389
+ }
390
+ } catch (error) {
391
+ console.error(chalk.red(`Error: ${(error as Error).message}`));
392
+ process.exit(1);
393
+ }
394
+ });
395
+
396
+ messageCmd
397
+ .command('read <messageId>')
398
+ .description('Mark a message as read')
399
+ .action(async (messageId: string) => {
400
+ try {
401
+ const client = getClient();
402
+ await client.markAsRead(messageId);
403
+ console.log(chalk.green('Message marked as read.'));
404
+ } catch (error) {
405
+ console.error(chalk.red(`Error: ${(error as Error).message}`));
406
+ process.exit(1);
407
+ }
408
+ });
409
+
410
+ // ============================================
411
+ // Media Commands
412
+ // ============================================
413
+
414
+ const mediaCmd = new Command('media')
415
+ .description('Manage media');
416
+
417
+ mediaCmd
418
+ .command('get <mediaId>')
419
+ .description('Get media URL')
420
+ .option('-f, --format <format>', 'Output format (json, pretty)', 'pretty')
421
+ .action(async (mediaId: string, options) => {
422
+ try {
423
+ const client = getClient();
424
+ const result = await client.getMediaUrl(mediaId);
425
+
426
+ if (options.format === 'json') {
427
+ formatOutput(result, 'json');
428
+ } else {
429
+ console.log(chalk.bold('Media:'));
430
+ console.log(chalk.gray('ID:'), result.id);
431
+ console.log(chalk.gray('URL:'), result.url);
432
+ console.log(chalk.gray('MIME Type:'), result.mime_type);
433
+ console.log(chalk.gray('Size:'), result.file_size);
434
+ }
435
+ } catch (error) {
436
+ console.error(chalk.red(`Error: ${(error as Error).message}`));
437
+ process.exit(1);
438
+ }
439
+ });
440
+
441
+ mediaCmd
442
+ .command('delete <mediaId>')
443
+ .description('Delete media')
444
+ .action(async (mediaId: string) => {
445
+ try {
446
+ const client = getClient();
447
+ await client.deleteMedia(mediaId);
448
+ console.log(chalk.green('Media deleted.'));
449
+ } catch (error) {
450
+ console.error(chalk.red(`Error: ${(error as Error).message}`));
451
+ process.exit(1);
452
+ }
453
+ });
454
+
455
+ // ============================================
456
+ // Business Profile Commands
457
+ // ============================================
458
+
459
+ const businessCmd = new Command('business')
460
+ .description('Manage business profile');
461
+
462
+ businessCmd
463
+ .command('get')
464
+ .description('Get business profile')
465
+ .option('-f, --format <format>', 'Output format (json, pretty)', 'pretty')
466
+ .action(async (options) => {
467
+ try {
468
+ const client = getClient();
469
+ const result = await client.getBusinessProfile();
470
+
471
+ if (options.format === 'json') {
472
+ formatOutput(result, 'json');
473
+ } else {
474
+ if (result.data && result.data.length > 0) {
475
+ const profile = result.data[0];
476
+ console.log(chalk.bold('Business Profile:'));
477
+ if (profile.about) console.log(chalk.gray('About:'), profile.about);
478
+ if (profile.address) console.log(chalk.gray('Address:'), profile.address);
479
+ if (profile.description) console.log(chalk.gray('Description:'), profile.description);
480
+ if (profile.email) console.log(chalk.gray('Email:'), profile.email);
481
+ if (profile.vertical) console.log(chalk.gray('Industry:'), profile.vertical);
482
+ if (profile.websites && profile.websites.length > 0) {
483
+ console.log(chalk.gray('Websites:'), profile.websites.join(', '));
484
+ }
485
+ }
486
+ }
487
+ } catch (error) {
488
+ console.error(chalk.red(`Error: ${(error as Error).message}`));
489
+ process.exit(1);
490
+ }
491
+ });
492
+
493
+ businessCmd
494
+ .command('update')
495
+ .description('Update business profile')
496
+ .option('-a, --about <about>', 'About text')
497
+ .option('--address <address>', 'Business address')
498
+ .option('-d, --description <description>', 'Business description')
499
+ .option('-e, --email <email>', 'Business email')
500
+ .option('-v, --vertical <vertical>', 'Business vertical/industry')
501
+ .action(async (options) => {
502
+ try {
503
+ const client = getClient();
504
+ const profile: Record<string, string | string[] | undefined> = {};
505
+ if (options.about) profile.about = options.about;
506
+ if (options.address) profile.address = options.address;
507
+ if (options.description) profile.description = options.description;
508
+ if (options.email) profile.email = options.email;
509
+ if (options.vertical) profile.vertical = options.vertical;
510
+
511
+ await client.updateBusinessProfile(profile);
512
+ console.log(chalk.green('Business profile updated.'));
513
+ } catch (error) {
514
+ console.error(chalk.red(`Error: ${(error as Error).message}`));
515
+ process.exit(1);
516
+ }
517
+ });
518
+
519
+ // ============================================
520
+ // Phone Number Commands
521
+ // ============================================
522
+
523
+ const phoneCmd = new Command('phone')
524
+ .description('Manage phone numbers');
525
+
526
+ phoneCmd
527
+ .command('get')
528
+ .description('Get current phone number info')
529
+ .option('-f, --format <format>', 'Output format (json, pretty)', 'pretty')
530
+ .action(async (options) => {
531
+ try {
532
+ const client = getClient();
533
+ const result = await client.getPhoneNumber();
534
+
535
+ if (options.format === 'json') {
536
+ formatOutput(result, 'json');
537
+ } else {
538
+ console.log(chalk.bold('Phone Number:'));
539
+ console.log(chalk.gray('ID:'), result.id);
540
+ console.log(chalk.gray('Display:'), result.display_phone_number);
541
+ console.log(chalk.gray('Verified Name:'), result.verified_name);
542
+ console.log(chalk.gray('Quality:'), result.quality_rating);
543
+ console.log(chalk.gray('Status:'), result.code_verification_status);
544
+ }
545
+ } catch (error) {
546
+ console.error(chalk.red(`Error: ${(error as Error).message}`));
547
+ process.exit(1);
548
+ }
549
+ });
550
+
551
+ phoneCmd
552
+ .command('list')
553
+ .description('List all phone numbers')
554
+ .option('-f, --format <format>', 'Output format (json, pretty)', 'pretty')
555
+ .action(async (options) => {
556
+ try {
557
+ const client = getClient();
558
+ const result = await client.listPhoneNumbers();
559
+
560
+ if (options.format === 'json') {
561
+ formatOutput(result, 'json');
562
+ } else {
563
+ if (result.data.length === 0) {
564
+ console.log(chalk.yellow('No phone numbers found.'));
565
+ return;
566
+ }
567
+ console.log(chalk.bold(`Phone Numbers (${result.data.length}):\n`));
568
+ for (const phone of result.data) {
569
+ console.log(chalk.cyan(phone.display_phone_number));
570
+ console.log(chalk.gray(` ID: ${phone.id}`));
571
+ console.log(chalk.gray(` Name: ${phone.verified_name}`));
572
+ console.log(chalk.gray(` Quality: ${phone.quality_rating}`));
573
+ console.log();
574
+ }
575
+ }
576
+ } catch (error) {
577
+ console.error(chalk.red(`Error: ${(error as Error).message}`));
578
+ process.exit(1);
579
+ }
580
+ });
581
+
582
+ // ============================================
583
+ // Template Commands
584
+ // ============================================
585
+
586
+ const templateCmd = new Command('template')
587
+ .description('Manage message templates');
588
+
589
+ templateCmd
590
+ .command('list')
591
+ .description('List message templates')
592
+ .option('-l, --limit <limit>', 'Maximum number of templates', '25')
593
+ .option('-f, --format <format>', 'Output format (json, pretty)', 'pretty')
594
+ .action(async (options) => {
595
+ try {
596
+ const client = getClient();
597
+ const result = await client.listTemplates({
598
+ limit: parseInt(options.limit),
599
+ });
600
+
601
+ if (options.format === 'json') {
602
+ formatOutput(result, 'json');
603
+ } else {
604
+ if (result.data.length === 0) {
605
+ console.log(chalk.yellow('No templates found.'));
606
+ return;
607
+ }
608
+ console.log(chalk.bold(`Templates (${result.data.length}):\n`));
609
+ for (const template of result.data) {
610
+ const statusColor = template.status === 'APPROVED' ? chalk.green : template.status === 'PENDING' ? chalk.yellow : chalk.red;
611
+ console.log(`${chalk.cyan(template.name)} ${statusColor(`[${template.status}]`)}`);
612
+ console.log(chalk.gray(` ID: ${template.id}`));
613
+ console.log(chalk.gray(` Category: ${template.category}`));
614
+ console.log(chalk.gray(` Language: ${template.language}`));
615
+ console.log();
616
+ }
617
+ }
618
+ } catch (error) {
619
+ console.error(chalk.red(`Error: ${(error as Error).message}`));
620
+ process.exit(1);
621
+ }
622
+ });
623
+
624
+ templateCmd
625
+ .command('get <templateId>')
626
+ .description('Get template details')
627
+ .option('-f, --format <format>', 'Output format (json, pretty)', 'pretty')
628
+ .action(async (templateId: string, options) => {
629
+ try {
630
+ const client = getClient();
631
+ const result = await client.getTemplate(templateId);
632
+
633
+ if (options.format === 'json') {
634
+ formatOutput(result, 'json');
635
+ } else {
636
+ console.log(chalk.bold(result.name));
637
+ console.log(chalk.gray('ID:'), result.id);
638
+ console.log(chalk.gray('Status:'), result.status);
639
+ console.log(chalk.gray('Category:'), result.category);
640
+ console.log(chalk.gray('Language:'), result.language);
641
+ }
642
+ } catch (error) {
643
+ console.error(chalk.red(`Error: ${(error as Error).message}`));
644
+ process.exit(1);
645
+ }
646
+ });
647
+
648
+ templateCmd
649
+ .command('delete <templateName>')
650
+ .description('Delete a template')
651
+ .action(async (templateName: string) => {
652
+ try {
653
+ const client = getClient();
654
+ await client.deleteTemplate(templateName);
655
+ console.log(chalk.green('Template deleted.'));
656
+ } catch (error) {
657
+ console.error(chalk.red(`Error: ${(error as Error).message}`));
658
+ process.exit(1);
659
+ }
660
+ });
661
+
662
+ // ============================================
663
+ // Main Program
664
+ // ============================================
665
+
666
+ program
667
+ .name('connect-whatsapp')
668
+ .description('WhatsApp Business Cloud connector - Send messages, manage templates, and handle webhooks')
669
+ .version('0.0.1')
670
+ .option('--profile <profile>', 'Use a specific profile')
671
+ .hook('preAction', (thisCommand) => {
672
+ const opts = thisCommand.opts();
673
+ if (opts.profile) {
674
+ setProfileOverride(opts.profile);
675
+ }
676
+ });
677
+
678
+ program.addCommand(profileCmd);
679
+ program.addCommand(configCmd);
680
+ program.addCommand(messageCmd);
681
+ program.addCommand(mediaCmd);
682
+ program.addCommand(businessCmd);
683
+ program.addCommand(phoneCmd);
684
+ program.addCommand(templateCmd);
685
+
686
+ program.parse();
@@ -0,0 +1,25 @@
1
+ // WhatsApp Business Cloud Connector
2
+ // Send messages, manage templates, and handle webhooks
3
+
4
+ export { WhatsApp, WhatsAppClient } from './api';
5
+ export * from './types';
6
+
7
+ // Export config utilities
8
+ export {
9
+ getAccessToken,
10
+ setAccessToken,
11
+ getPhoneNumberId,
12
+ setPhoneNumberId,
13
+ getBusinessAccountId,
14
+ setBusinessAccountId,
15
+ getCurrentProfile,
16
+ setCurrentProfile,
17
+ listProfiles,
18
+ createProfile,
19
+ deleteProfile,
20
+ loadProfile,
21
+ saveProfile,
22
+ clearConfig,
23
+ getConfigDir,
24
+ getActiveProfileName,
25
+ } from './utils/config';