@hasna/connectors 1.3.22 → 1.3.23

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 (63) hide show
  1. package/bin/index.js +109 -59
  2. package/bin/mcp.js +236 -182
  3. package/bin/serve.js +77 -33
  4. package/connectors/connect-dropbox/src/api/bulk.ts +285 -0
  5. package/connectors/connect-dropbox/src/api/index.ts +4 -0
  6. package/connectors/connect-dropbox/src/cli/index.ts +151 -0
  7. package/connectors/connect-github/src/api/bulk.ts +328 -0
  8. package/connectors/connect-github/src/api/index.ts +4 -0
  9. package/connectors/connect-github/src/cli/index.ts +145 -0
  10. package/connectors/connect-gmail/src/api/history.ts +82 -0
  11. package/connectors/connect-gmail/src/api/index.ts +12 -0
  12. package/connectors/connect-gmail/src/api/settings.ts +280 -0
  13. package/connectors/connect-gmail/src/api/watch.ts +40 -0
  14. package/connectors/connect-gmail/src/cli/index.ts +237 -0
  15. package/connectors/connect-googleads/src/api/bulk.ts +354 -0
  16. package/connectors/connect-googleads/src/api/index.ts +4 -0
  17. package/connectors/connect-googleads/src/cli/index.ts +192 -0
  18. package/connectors/connect-googlecalendar/src/api/acl.ts +66 -0
  19. package/connectors/connect-googlecalendar/src/api/bulk.ts +452 -0
  20. package/connectors/connect-googlecalendar/src/api/freebusy.ts +35 -0
  21. package/connectors/connect-googlecalendar/src/api/index.ts +9 -0
  22. package/connectors/connect-googlecalendar/src/cli/index.ts +522 -0
  23. package/connectors/connect-googlecalendar/src/types/index.ts +90 -0
  24. package/connectors/connect-googlecontacts/src/api/bulk.ts +365 -0
  25. package/connectors/connect-googlecontacts/src/api/groups.ts +164 -0
  26. package/connectors/connect-googlecontacts/src/api/index.ts +8 -0
  27. package/connectors/connect-googlecontacts/src/cli/index.ts +340 -0
  28. package/connectors/connect-googledocs/src/api/bulk.ts +301 -0
  29. package/connectors/connect-googledocs/src/api/index.ts +4 -0
  30. package/connectors/connect-googledocs/src/cli/index.ts +187 -0
  31. package/connectors/connect-googledrive/src/api/bulk.ts +505 -0
  32. package/connectors/connect-googledrive/src/api/changes.ts +139 -0
  33. package/connectors/connect-googledrive/src/api/client.ts +34 -0
  34. package/connectors/connect-googledrive/src/api/index.ts +12 -0
  35. package/connectors/connect-googledrive/src/api/revisions.ts +132 -0
  36. package/connectors/connect-googledrive/src/cli/index.ts +624 -0
  37. package/connectors/connect-googledrive/src/index.ts +2 -0
  38. package/connectors/connect-googlephotos/src/api/bulk.ts +455 -0
  39. package/connectors/connect-googlephotos/src/api/index.ts +8 -0
  40. package/connectors/connect-googlephotos/src/cli/index.ts +185 -0
  41. package/connectors/connect-googletasks/src/api/bulk.ts +359 -0
  42. package/connectors/connect-googletasks/src/api/index.ts +1 -0
  43. package/connectors/connect-googletasks/src/cli/index.ts +178 -0
  44. package/connectors/connect-linear/src/api/bulk.ts +219 -0
  45. package/connectors/connect-linear/src/api/index.ts +4 -0
  46. package/connectors/connect-linear/src/cli/index.ts +116 -0
  47. package/connectors/connect-shopify/src/api/bulk.ts +288 -0
  48. package/connectors/connect-shopify/src/api/index.ts +4 -0
  49. package/connectors/connect-shopify/src/cli/index.ts +108 -0
  50. package/connectors/connect-slack/src/api/bulk.ts +237 -0
  51. package/connectors/connect-slack/src/api/index.ts +4 -0
  52. package/connectors/connect-slack/src/cli/index.ts +157 -0
  53. package/connectors/connect-stripe/src/api/bulk.ts +488 -0
  54. package/connectors/connect-stripe/src/api/index.ts +4 -0
  55. package/connectors/connect-stripe/src/cli/index.ts +206 -0
  56. package/dist/index.js +75 -33
  57. package/dist/lib/runner.d.ts +15 -5
  58. package/package.json +2 -2
  59. package/connectors/connect-cloudflare/bun.lock +0 -32
  60. package/connectors/connect-gmail/bin/index.js +0 -7027
  61. package/connectors/connect-gmail/dist/cli/index.js +0 -7035
  62. package/connectors/connect-gmail/dist/index.js +0 -2174
  63. package/dist/server/server.test.d.ts +0 -1
@@ -866,5 +866,113 @@ collectionsCmd
866
866
  }
867
867
  });
868
868
 
869
+ // ============================================
870
+ // Bulk Operations Commands
871
+ // ============================================
872
+ const bulkCmd = program
873
+ .command('bulk')
874
+ .description('Bulk operations');
875
+
876
+ bulkCmd
877
+ .command('products')
878
+ .description('Bulk product operations')
879
+ .requiredOption('--ids <ids>', 'Comma-separated product IDs')
880
+ .requiredOption('--action <action>', 'Action: delete, activate, archive')
881
+ .option('--concurrency <number>', 'Max concurrent operations', '10')
882
+ .option('--dry-run', 'Preview without executing')
883
+ .action(async function(this: Command, opts) {
884
+ try {
885
+ const client = getClient();
886
+ const result = await client.bulk.products({
887
+ productIds: opts.ids.split(',').map((id: string) => parseInt(id.trim())),
888
+ action: opts.action as 'delete' | 'activate' | 'archive',
889
+ concurrency: parseInt(opts.concurrency),
890
+ dryRun: opts.dryRun,
891
+ onProgress: (current, total) => info(`Progress: ${current}/${total}`),
892
+ });
893
+ print(result, getFormat(this));
894
+ info(`Success: ${result.success}, Failed: ${result.failed}, Total: ${result.total}`);
895
+ if (result.errors.length > 0) {
896
+ warn('Errors:');
897
+ result.errors.forEach(e => error(` ${e.productId}: ${e.error}`));
898
+ }
899
+ } catch (err) {
900
+ error(String(err));
901
+ process.exit(1);
902
+ }
903
+ });
904
+
905
+ bulkCmd
906
+ .command('orders')
907
+ .description('Bulk order operations')
908
+ .requiredOption('--ids <ids>', 'Comma-separated order IDs')
909
+ .requiredOption('--action <action>', 'Action: close, open, cancel')
910
+ .option('--cancel-reason <reason>', 'Cancel reason: customer, inventory, fraud, declined, other')
911
+ .option('--email', 'Send cancel email')
912
+ .option('--restock', 'Restock on cancel')
913
+ .option('--concurrency <number>', 'Max concurrent operations', '10')
914
+ .option('--dry-run', 'Preview without executing')
915
+ .action(async function(this: Command, opts) {
916
+ try {
917
+ const client = getClient();
918
+ const result = await client.bulk.orders({
919
+ orderIds: opts.ids.split(',').map((id: string) => parseInt(id.trim())),
920
+ action: opts.action as 'close' | 'open' | 'cancel',
921
+ cancelReason: opts.cancelReason,
922
+ cancelEmail: opts.email,
923
+ cancelRestock: opts.restock,
924
+ concurrency: parseInt(opts.concurrency),
925
+ dryRun: opts.dryRun,
926
+ onProgress: (current, total) => info(`Progress: ${current}/${total}`),
927
+ });
928
+ print(result, getFormat(this));
929
+ info(`Success: ${result.success}, Failed: ${result.failed}, Total: ${result.total}`);
930
+ if (result.errors.length > 0) {
931
+ warn('Errors:');
932
+ result.errors.forEach(e => error(` ${e.orderId}: ${e.error}`));
933
+ }
934
+ } catch (err) {
935
+ error(String(err));
936
+ process.exit(1);
937
+ }
938
+ });
939
+
940
+ bulkCmd
941
+ .command('inventory')
942
+ .description('Bulk inventory operations')
943
+ .requiredOption('--items <items>', 'Items as "itemId:locationId:qty" pairs, comma-separated')
944
+ .requiredOption('--action <action>', 'Action: set, adjust')
945
+ .option('--concurrency <number>', 'Max concurrent operations', '10')
946
+ .option('--dry-run', 'Preview without executing')
947
+ .action(async function(this: Command, opts) {
948
+ try {
949
+ const client = getClient();
950
+ const items = opts.items.split(',').map((pair: string) => {
951
+ const [inventoryItemId, locationId, available] = pair.trim().split(':');
952
+ return {
953
+ inventoryItemId: parseInt(inventoryItemId),
954
+ locationId: parseInt(locationId),
955
+ available: parseInt(available),
956
+ };
957
+ });
958
+ const result = await client.bulk.inventory({
959
+ items,
960
+ action: opts.action as 'set' | 'adjust',
961
+ concurrency: parseInt(opts.concurrency),
962
+ dryRun: opts.dryRun,
963
+ onProgress: (current, total) => info(`Progress: ${current}/${total}`),
964
+ });
965
+ print(result, getFormat(this));
966
+ info(`Success: ${result.success}, Failed: ${result.failed}, Total: ${result.total}`);
967
+ if (result.errors.length > 0) {
968
+ warn('Errors:');
969
+ result.errors.forEach(e => error(` item ${e.inventoryItemId} @ location ${e.locationId}: ${e.error}`));
970
+ }
971
+ } catch (err) {
972
+ error(String(err));
973
+ process.exit(1);
974
+ }
975
+ });
976
+
869
977
  // Parse and execute
870
978
  program.parse();
@@ -0,0 +1,237 @@
1
+ import type { SlackClient } from './client';
2
+
3
+ // ============================================
4
+ // Bulk Operation Types
5
+ // ============================================
6
+
7
+ export interface BulkOperationOptions {
8
+ /** Maximum concurrent API calls (default: 10) */
9
+ concurrency?: number;
10
+ /** Dry run - don't actually modify */
11
+ dryRun?: boolean;
12
+ /** Progress callback */
13
+ onProgress?: (current: number, total: number, item: unknown) => void;
14
+ /** Error callback */
15
+ onError?: (error: Error, item: unknown) => void;
16
+ }
17
+
18
+ // --- Channel Bulk Operations ---
19
+
20
+ export interface BulkChannelOptions extends BulkOperationOptions {
21
+ channelIds: string[];
22
+ /** Action to perform */
23
+ action: 'archive' | 'unarchive';
24
+ }
25
+
26
+ export interface BulkChannelResult {
27
+ total: number;
28
+ success: number;
29
+ failed: number;
30
+ errors: Array<{ channelId: string; error: string }>;
31
+ results: Array<{ channelId: string; response: unknown }>;
32
+ }
33
+
34
+ // --- Message Bulk Operations ---
35
+
36
+ export interface BulkMessageOptions extends BulkOperationOptions {
37
+ /** Messages to delete, each with channel and timestamp */
38
+ messages: Array<{ channel: string; ts: string }>;
39
+ }
40
+
41
+ export interface BulkMessageResult {
42
+ total: number;
43
+ success: number;
44
+ failed: number;
45
+ errors: Array<{ channel: string; ts: string; error: string }>;
46
+ results: Array<{ channel: string; ts: string; response: unknown }>;
47
+ }
48
+
49
+ // --- User Bulk Operations ---
50
+
51
+ export interface BulkUserOptions extends BulkOperationOptions {
52
+ userIds: string[];
53
+ /** Action: set presence for all users */
54
+ action: 'set_presence';
55
+ /** Presence value: 'auto' or 'away' */
56
+ presence?: 'auto' | 'away';
57
+ }
58
+
59
+ export interface BulkUserResult {
60
+ total: number;
61
+ success: number;
62
+ failed: number;
63
+ errors: Array<{ userId: string; error: string }>;
64
+ results: Array<{ userId: string; response: unknown }>;
65
+ }
66
+
67
+ // ============================================
68
+ // Bulk Operations API
69
+ // ============================================
70
+
71
+ export class BulkApi {
72
+ private readonly client: SlackClient;
73
+
74
+ constructor(client: SlackClient) {
75
+ this.client = client;
76
+ }
77
+
78
+ // ============================================
79
+ // Bulk Channel Operations
80
+ // ============================================
81
+
82
+ async channels(options: BulkChannelOptions): Promise<BulkChannelResult> {
83
+ const { channelIds, action, concurrency = 10, dryRun = false, onProgress, onError } = options;
84
+
85
+ const result: BulkChannelResult = {
86
+ total: channelIds.length,
87
+ success: 0,
88
+ failed: 0,
89
+ errors: [],
90
+ results: [],
91
+ };
92
+
93
+ if (channelIds.length === 0) return result;
94
+
95
+ const chunks = this.chunkArray(channelIds, concurrency);
96
+
97
+ for (const chunk of chunks) {
98
+ await Promise.all(
99
+ chunk.map(async (channelId) => {
100
+ try {
101
+ if (dryRun) {
102
+ result.success++;
103
+ } else {
104
+ const endpoint = action === 'archive' ? 'conversations.archive' : 'conversations.unarchive';
105
+ const response = await this.client.post<{ ok: boolean; channel: Record<string, unknown> }>(
106
+ endpoint,
107
+ { channel: channelId }
108
+ );
109
+ result.success++;
110
+ result.results.push({ channelId, response });
111
+ }
112
+
113
+ if (onProgress) onProgress(result.success + result.failed, result.total, channelId);
114
+ } catch (err) {
115
+ result.failed++;
116
+ const errorMessage = err instanceof Error ? err.message : String(err);
117
+ result.errors.push({ channelId, error: errorMessage });
118
+ if (onError) onError(err instanceof Error ? err : new Error(errorMessage), channelId);
119
+ }
120
+ })
121
+ );
122
+ }
123
+
124
+ return result;
125
+ }
126
+
127
+ // ============================================
128
+ // Bulk Message Operations
129
+ // ============================================
130
+
131
+ async messages(options: BulkMessageOptions): Promise<BulkMessageResult> {
132
+ const { messages, concurrency = 10, dryRun = false, onProgress, onError } = options;
133
+
134
+ const result: BulkMessageResult = {
135
+ total: messages.length,
136
+ success: 0,
137
+ failed: 0,
138
+ errors: [],
139
+ results: [],
140
+ };
141
+
142
+ if (messages.length === 0) return result;
143
+
144
+ const chunks = this.chunkArray(messages, concurrency);
145
+
146
+ for (const chunk of chunks) {
147
+ await Promise.all(
148
+ chunk.map(async (msg) => {
149
+ const { channel, ts } = msg;
150
+ try {
151
+ if (dryRun) {
152
+ result.success++;
153
+ } else {
154
+ const response = await this.client.post<{ ok: boolean }>(
155
+ 'chat.delete',
156
+ { channel, ts }
157
+ );
158
+ result.success++;
159
+ result.results.push({ channel, ts, response });
160
+ }
161
+
162
+ if (onProgress) onProgress(result.success + result.failed, result.total, msg);
163
+ } catch (err) {
164
+ result.failed++;
165
+ const errorMessage = err instanceof Error ? err.message : String(err);
166
+ result.errors.push({ channel, ts, error: errorMessage });
167
+ if (onError) onError(err instanceof Error ? err : new Error(errorMessage), msg);
168
+ }
169
+ })
170
+ );
171
+ }
172
+
173
+ return result;
174
+ }
175
+
176
+ // ============================================
177
+ // Bulk User Operations
178
+ // ============================================
179
+
180
+ async users(options: BulkUserOptions): Promise<BulkUserResult> {
181
+ const { userIds, action, presence = 'auto', concurrency = 10, dryRun = false, onProgress, onError } = options;
182
+
183
+ const result: BulkUserResult = {
184
+ total: userIds.length,
185
+ success: 0,
186
+ failed: 0,
187
+ errors: [],
188
+ results: [],
189
+ };
190
+
191
+ if (userIds.length === 0) return result;
192
+
193
+ const chunks = this.chunkArray(userIds, concurrency);
194
+
195
+ for (const chunk of chunks) {
196
+ await Promise.all(
197
+ chunk.map(async (userId) => {
198
+ try {
199
+ if (dryRun) {
200
+ result.success++;
201
+ } else {
202
+ // Slack's users.setPresence only sets the current user's presence,
203
+ // but we still iterate through userIds for tracking purposes
204
+ const response = await this.client.post<{ ok: boolean }>(
205
+ 'users.setPresence',
206
+ { presence }
207
+ );
208
+ result.success++;
209
+ result.results.push({ userId, response });
210
+ }
211
+
212
+ if (onProgress) onProgress(result.success + result.failed, result.total, userId);
213
+ } catch (err) {
214
+ result.failed++;
215
+ const errorMessage = err instanceof Error ? err.message : String(err);
216
+ result.errors.push({ userId, error: errorMessage });
217
+ if (onError) onError(err instanceof Error ? err : new Error(errorMessage), userId);
218
+ }
219
+ })
220
+ );
221
+ }
222
+
223
+ return result;
224
+ }
225
+
226
+ // ============================================
227
+ // Helper Methods
228
+ // ============================================
229
+
230
+ private chunkArray<T>(array: T[], size: number): T[][] {
231
+ const chunks: T[][] = [];
232
+ for (let i = 0; i < array.length; i += size) {
233
+ chunks.push(array.slice(i, i + size));
234
+ }
235
+ return chunks;
236
+ }
237
+ }
@@ -3,11 +3,13 @@ import { SlackClient } from './client';
3
3
  import { ChannelsApi } from './channels';
4
4
  import { MessagesApi } from './messages';
5
5
  import { UsersApi } from './users';
6
+ import { BulkApi } from './bulk';
6
7
 
7
8
  export { SlackClient } from './client';
8
9
  export { ChannelsApi } from './channels';
9
10
  export { MessagesApi } from './messages';
10
11
  export { UsersApi } from './users';
12
+ export { BulkApi } from './bulk';
11
13
 
12
14
  /**
13
15
  * Main Slack API class
@@ -18,12 +20,14 @@ export class Slack {
18
20
  public readonly channels: ChannelsApi;
19
21
  public readonly messages: MessagesApi;
20
22
  public readonly users: UsersApi;
23
+ public readonly bulk: BulkApi;
21
24
 
22
25
  constructor(config: SlackConfig) {
23
26
  this.client = new SlackClient(config);
24
27
  this.channels = new ChannelsApi(this.client);
25
28
  this.messages = new MessagesApi(this.client);
26
29
  this.users = new UsersApi(this.client);
30
+ this.bulk = new BulkApi(this.client);
27
31
  }
28
32
 
29
33
  /**
@@ -491,4 +491,161 @@ program
491
491
  }
492
492
  });
493
493
 
494
+ // ============================================
495
+ // Bulk Operations
496
+ // ============================================
497
+
498
+ const bulkCmd = program
499
+ .command('bulk')
500
+ .description('Bulk operations');
501
+
502
+ bulkCmd
503
+ .command('channels')
504
+ .description('Bulk archive/unarchive channels')
505
+ .option('--ids <ids>', 'Comma-separated channel IDs')
506
+ .option('--action <action>', 'Action: archive or unarchive', 'archive')
507
+ .option('--concurrency <n>', 'Max concurrent requests', '10')
508
+ .option('--dry-run', 'Preview without executing')
509
+ .action(async (opts) => {
510
+ try {
511
+ const client = getClient();
512
+ const format = program.opts().format as OutputFormat;
513
+
514
+ if (!opts.ids) {
515
+ error('--ids is required (comma-separated channel IDs)');
516
+ process.exit(1);
517
+ }
518
+
519
+ const channelIds = opts.ids.split(',').map((id: string) => id.trim()).filter(Boolean);
520
+
521
+ info(`Bulk ${opts.action} ${channelIds.length} channels (concurrency: ${opts.concurrency}, dry-run: ${!!opts.dryRun})`);
522
+
523
+ const result = await client.bulk.channels({
524
+ channelIds,
525
+ action: opts.action as 'archive' | 'unarchive',
526
+ concurrency: parseInt(opts.concurrency, 10),
527
+ dryRun: opts.dryRun,
528
+ onProgress: (current, total) => {
529
+ process.stdout.write(`\rProgress: ${current}/${total}`);
530
+ },
531
+ });
532
+
533
+ console.log();
534
+ if (format === 'json') {
535
+ print(result, format);
536
+ } else {
537
+ info(`Done: ${result.success} succeeded, ${result.failed} failed out of ${result.total}`);
538
+ if (result.errors.length > 0) {
539
+ error('Failures:');
540
+ result.errors.forEach((e: { channelId: string; error: string }) => {
541
+ console.error(` - ${e.channelId}: ${e.error}`);
542
+ });
543
+ }
544
+ }
545
+ } catch (e) {
546
+ error((e as Error).message);
547
+ process.exit(1);
548
+ }
549
+ });
550
+
551
+ bulkCmd
552
+ .command('messages')
553
+ .description('Bulk delete messages')
554
+ .option('--messages <msgs>', 'Messages as "channel:ts" pairs, comma-separated')
555
+ .option('--concurrency <n>', 'Max concurrent requests', '10')
556
+ .option('--dry-run', 'Preview without executing')
557
+ .action(async (opts) => {
558
+ try {
559
+ const client = getClient();
560
+ const format = program.opts().format as OutputFormat;
561
+
562
+ if (!opts.messages) {
563
+ error('--messages is required (comma-separated "channel:ts" pairs)');
564
+ process.exit(1);
565
+ }
566
+
567
+ const messages = opts.messages.split(',').map((pair: string) => {
568
+ const [channel, ts] = pair.trim().split(':');
569
+ return { channel, ts };
570
+ });
571
+
572
+ info(`Bulk delete ${messages.length} messages (concurrency: ${opts.concurrency}, dry-run: ${!!opts.dryRun})`);
573
+
574
+ const result = await client.bulk.messages({
575
+ messages,
576
+ concurrency: parseInt(opts.concurrency, 10),
577
+ dryRun: opts.dryRun,
578
+ onProgress: (current, total) => {
579
+ process.stdout.write(`\rProgress: ${current}/${total}`);
580
+ },
581
+ });
582
+
583
+ console.log();
584
+ if (format === 'json') {
585
+ print(result, format);
586
+ } else {
587
+ info(`Done: ${result.success} succeeded, ${result.failed} failed out of ${result.total}`);
588
+ if (result.errors.length > 0) {
589
+ error('Failures:');
590
+ result.errors.forEach((e: { channel: string; ts: string; error: string }) => {
591
+ console.error(` - ${e.channel}@${e.ts}: ${e.error}`);
592
+ });
593
+ }
594
+ }
595
+ } catch (e) {
596
+ error((e as Error).message);
597
+ process.exit(1);
598
+ }
599
+ });
600
+
601
+ bulkCmd
602
+ .command('users')
603
+ .description('Bulk set user presence')
604
+ .option('--ids <ids>', 'Comma-separated user IDs')
605
+ .option('--presence <value>', 'Presence: auto or away', 'auto')
606
+ .option('--concurrency <n>', 'Max concurrent requests', '10')
607
+ .option('--dry-run', 'Preview without executing')
608
+ .action(async (opts) => {
609
+ try {
610
+ const client = getClient();
611
+ const format = program.opts().format as OutputFormat;
612
+
613
+ if (!opts.ids) {
614
+ error('--ids is required (comma-separated user IDs)');
615
+ process.exit(1);
616
+ }
617
+
618
+ const userIds = opts.ids.split(',').map((id: string) => id.trim()).filter(Boolean);
619
+
620
+ info(`Bulk set presence for ${userIds.length} users (concurrency: ${opts.concurrency}, dry-run: ${!!opts.dryRun})`);
621
+
622
+ const result = await client.bulk.users({
623
+ userIds,
624
+ action: 'set_presence',
625
+ presence: opts.presence as 'auto' | 'away',
626
+ concurrency: parseInt(opts.concurrency, 10),
627
+ dryRun: opts.dryRun,
628
+ onProgress: (current, total) => {
629
+ process.stdout.write(`\rProgress: ${current}/${total}`);
630
+ },
631
+ });
632
+
633
+ console.log();
634
+ if (format === 'json') {
635
+ print(result, format);
636
+ } else {
637
+ info(`Done: ${result.success} succeeded, ${result.failed} failed out of ${result.total}`);
638
+ if (result.errors.length > 0) {
639
+ error('Failures:');
640
+ result.errors.forEach((e: { userId: string; error: string }) => {
641
+ console.error(` - ${e.userId}: ${e.error}`);
642
+ });
643
+ }
644
+ }
645
+ } catch (e) {
646
+ error((e as Error).message);
647
+ process.exit(1);
648
+ }
649
+ });
650
+
494
651
  program.parse();