@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,990 @@
1
+ #!/usr/bin/env bun
2
+ import { Command } from 'commander';
3
+ import chalk from 'chalk';
4
+ import { Todoist } from '../api';
5
+ import type { TodoistConfig, OutputFormat } from '../types';
6
+ import {
7
+ getApiKey,
8
+ setApiKey,
9
+ clearConfig,
10
+ getConfigDir,
11
+ getCurrentProfile,
12
+ setCurrentProfile,
13
+ listProfiles,
14
+ createProfile,
15
+ deleteProfile,
16
+ loadProfile,
17
+ setProfileOverride,
18
+ getActiveProfileName,
19
+ } from '../utils/config';
20
+
21
+ const program = new Command();
22
+
23
+ function getClient(): Todoist {
24
+ const apiKey = getApiKey();
25
+ if (!apiKey) {
26
+ console.error(chalk.red('Error: API key not configured.'));
27
+ console.error(chalk.yellow('Run: connect-todoist config set-key <api-key>'));
28
+ console.error(chalk.yellow('Or set TODOIST_API_KEY environment variable'));
29
+ process.exit(1);
30
+ }
31
+
32
+ const config: TodoistConfig = { apiKey };
33
+ return new Todoist(config);
34
+ }
35
+
36
+ function formatOutput(data: unknown, format: OutputFormat): void {
37
+ if (format === 'json') {
38
+ console.log(JSON.stringify(data, null, 2));
39
+ } else {
40
+ console.log(data);
41
+ }
42
+ }
43
+
44
+ function formatPriority(priority: number): string {
45
+ const labels = ['', 'P4', 'P3', 'P2', 'P1'];
46
+ const colors = [chalk.gray, chalk.gray, chalk.blue, chalk.yellow, chalk.red];
47
+ return colors[priority]?.(labels[priority] || '') || '';
48
+ }
49
+
50
+ // ============================================
51
+ // Profile Commands
52
+ // ============================================
53
+
54
+ const profileCmd = new Command('profile')
55
+ .description('Manage configuration profiles');
56
+
57
+ profileCmd
58
+ .command('list')
59
+ .description('List all profiles')
60
+ .action(() => {
61
+ const profiles = listProfiles();
62
+ const current = getCurrentProfile();
63
+
64
+ if (profiles.length === 0) {
65
+ console.log(chalk.yellow('No profiles configured.'));
66
+ console.log(chalk.gray('Create one with: connect-todoist profile create <name>'));
67
+ return;
68
+ }
69
+
70
+ console.log(chalk.bold('Profiles:'));
71
+ for (const profile of profiles) {
72
+ const marker = profile === current ? chalk.green(' (active)') : '';
73
+ console.log(` ${profile}${marker}`);
74
+ }
75
+ });
76
+
77
+ profileCmd
78
+ .command('current')
79
+ .description('Show current active profile')
80
+ .action(() => {
81
+ const current = getCurrentProfile();
82
+ console.log(current);
83
+ });
84
+
85
+ profileCmd
86
+ .command('use <name>')
87
+ .description('Switch to a profile')
88
+ .action((name: string) => {
89
+ try {
90
+ setCurrentProfile(name);
91
+ console.log(chalk.green(`Switched to profile: ${name}`));
92
+ } catch (error) {
93
+ console.error(chalk.red(`Error: ${(error as Error).message}`));
94
+ process.exit(1);
95
+ }
96
+ });
97
+
98
+ profileCmd
99
+ .command('create <name>')
100
+ .description('Create a new profile')
101
+ .action((name: string) => {
102
+ try {
103
+ const created = createProfile(name);
104
+ if (created) {
105
+ console.log(chalk.green(`Created profile: ${name}`));
106
+ } else {
107
+ console.log(chalk.yellow(`Profile already exists: ${name}`));
108
+ }
109
+ } catch (error) {
110
+ console.error(chalk.red(`Error: ${(error as Error).message}`));
111
+ process.exit(1);
112
+ }
113
+ });
114
+
115
+ profileCmd
116
+ .command('delete <name>')
117
+ .description('Delete a profile')
118
+ .action((name: string) => {
119
+ const deleted = deleteProfile(name);
120
+ if (deleted) {
121
+ console.log(chalk.green(`Deleted profile: ${name}`));
122
+ } else {
123
+ console.log(chalk.yellow(`Could not delete profile: ${name}`));
124
+ }
125
+ });
126
+
127
+ profileCmd
128
+ .command('show [name]')
129
+ .description('Show profile configuration')
130
+ .action((name?: string) => {
131
+ const profile = loadProfile(name);
132
+ const profileName = name || getCurrentProfile();
133
+ console.log(chalk.bold(`Profile: ${profileName}`));
134
+ console.log(chalk.gray('API Key:'), profile.apiKey ? '***configured***' : 'not set');
135
+ });
136
+
137
+ // ============================================
138
+ // Config Commands
139
+ // ============================================
140
+
141
+ const configCmd = new Command('config')
142
+ .description('Manage configuration');
143
+
144
+ configCmd
145
+ .command('set-key <apiKey>')
146
+ .description('Set the API key for current profile')
147
+ .action((apiKey: string) => {
148
+ setApiKey(apiKey);
149
+ const profile = getActiveProfileName();
150
+ console.log(chalk.green(`API key saved to profile: ${profile}`));
151
+ });
152
+
153
+ configCmd
154
+ .command('show')
155
+ .description('Show current configuration')
156
+ .action(() => {
157
+ const profile = getCurrentProfile();
158
+ const apiKey = getApiKey();
159
+ const configDir = getConfigDir();
160
+
161
+ console.log(chalk.bold('Current Configuration:'));
162
+ console.log(chalk.gray('Profile:'), profile);
163
+ console.log(chalk.gray('Config directory:'), configDir);
164
+ console.log(chalk.gray('API Key:'), apiKey ? '***configured***' : 'not set');
165
+ });
166
+
167
+ configCmd
168
+ .command('clear')
169
+ .description('Clear configuration for current profile')
170
+ .action(() => {
171
+ clearConfig();
172
+ console.log(chalk.green('Configuration cleared.'));
173
+ });
174
+
175
+ configCmd
176
+ .command('path')
177
+ .description('Show configuration directory path')
178
+ .action(() => {
179
+ console.log(getConfigDir());
180
+ });
181
+
182
+ // ============================================
183
+ // Project Commands
184
+ // ============================================
185
+
186
+ const projectCmd = new Command('project')
187
+ .description('Manage projects');
188
+
189
+ projectCmd
190
+ .command('list')
191
+ .description('List all projects')
192
+ .option('-f, --format <format>', 'Output format (json, pretty)', 'pretty')
193
+ .action(async (options) => {
194
+ try {
195
+ const client = getClient();
196
+ const projects = await client.listProjects();
197
+
198
+ if (options.format === 'json') {
199
+ formatOutput(projects, 'json');
200
+ } else {
201
+ if (projects.length === 0) {
202
+ console.log(chalk.yellow('No projects found.'));
203
+ return;
204
+ }
205
+ console.log(chalk.bold(`Projects (${projects.length}):\n`));
206
+ for (const project of projects) {
207
+ const favorite = project.is_favorite ? chalk.yellow('* ') : '';
208
+ const shared = project.is_shared ? chalk.blue(' [shared]') : '';
209
+ const inbox = project.is_inbox_project ? chalk.gray(' (inbox)') : '';
210
+ console.log(`${favorite}${chalk.cyan(project.name)}${shared}${inbox}`);
211
+ console.log(chalk.gray(` ID: ${project.id}`));
212
+ if (project.parent_id) {
213
+ console.log(chalk.gray(` Parent: ${project.parent_id}`));
214
+ }
215
+ console.log();
216
+ }
217
+ }
218
+ } catch (error) {
219
+ console.error(chalk.red(`Error: ${(error as Error).message}`));
220
+ process.exit(1);
221
+ }
222
+ });
223
+
224
+ projectCmd
225
+ .command('get <projectId>')
226
+ .description('Get a project by ID')
227
+ .option('-f, --format <format>', 'Output format (json, pretty)', 'pretty')
228
+ .action(async (projectId: string, options) => {
229
+ try {
230
+ const client = getClient();
231
+ const project = await client.getProject(projectId);
232
+
233
+ if (options.format === 'json') {
234
+ formatOutput(project, 'json');
235
+ } else {
236
+ console.log(chalk.bold(project.name));
237
+ console.log(chalk.gray('ID:'), project.id);
238
+ console.log(chalk.gray('Color:'), project.color);
239
+ console.log(chalk.gray('View:'), project.view_style);
240
+ console.log(chalk.gray('Favorite:'), project.is_favorite ? 'Yes' : 'No');
241
+ console.log(chalk.gray('Shared:'), project.is_shared ? 'Yes' : 'No');
242
+ console.log(chalk.gray('Comments:'), project.comment_count);
243
+ console.log(chalk.gray('URL:'), project.url);
244
+ }
245
+ } catch (error) {
246
+ console.error(chalk.red(`Error: ${(error as Error).message}`));
247
+ process.exit(1);
248
+ }
249
+ });
250
+
251
+ projectCmd
252
+ .command('create <name>')
253
+ .description('Create a new project')
254
+ .option('-p, --parent <parentId>', 'Parent project ID')
255
+ .option('-c, --color <color>', 'Project color')
256
+ .option('--favorite', 'Mark as favorite')
257
+ .option('-v, --view <view>', 'View style (list, board)', 'list')
258
+ .option('-f, --format <format>', 'Output format (json, pretty)', 'pretty')
259
+ .action(async (name: string, options) => {
260
+ try {
261
+ const client = getClient();
262
+ const project = await client.createProject({
263
+ name,
264
+ parent_id: options.parent,
265
+ color: options.color,
266
+ is_favorite: options.favorite,
267
+ view_style: options.view,
268
+ });
269
+
270
+ if (options.format === 'json') {
271
+ formatOutput(project, 'json');
272
+ } else {
273
+ console.log(chalk.green(`Created project: ${project.name}`));
274
+ console.log(chalk.gray('ID:'), project.id);
275
+ }
276
+ } catch (error) {
277
+ console.error(chalk.red(`Error: ${(error as Error).message}`));
278
+ process.exit(1);
279
+ }
280
+ });
281
+
282
+ projectCmd
283
+ .command('update <projectId>')
284
+ .description('Update a project')
285
+ .option('-n, --name <name>', 'New name')
286
+ .option('-c, --color <color>', 'New color')
287
+ .option('--favorite', 'Mark as favorite')
288
+ .option('--no-favorite', 'Remove from favorites')
289
+ .option('-v, --view <view>', 'View style (list, board)')
290
+ .option('-f, --format <format>', 'Output format (json, pretty)', 'pretty')
291
+ .action(async (projectId: string, options) => {
292
+ try {
293
+ const client = getClient();
294
+ const project = await client.updateProject(projectId, {
295
+ name: options.name,
296
+ color: options.color,
297
+ is_favorite: options.favorite,
298
+ view_style: options.view,
299
+ });
300
+
301
+ if (options.format === 'json') {
302
+ formatOutput(project, 'json');
303
+ } else {
304
+ console.log(chalk.green(`Updated project: ${project.name}`));
305
+ }
306
+ } catch (error) {
307
+ console.error(chalk.red(`Error: ${(error as Error).message}`));
308
+ process.exit(1);
309
+ }
310
+ });
311
+
312
+ projectCmd
313
+ .command('delete <projectId>')
314
+ .description('Delete a project')
315
+ .action(async (projectId: string) => {
316
+ try {
317
+ const client = getClient();
318
+ await client.deleteProject(projectId);
319
+ console.log(chalk.green('Project deleted.'));
320
+ } catch (error) {
321
+ console.error(chalk.red(`Error: ${(error as Error).message}`));
322
+ process.exit(1);
323
+ }
324
+ });
325
+
326
+ projectCmd
327
+ .command('collaborators <projectId>')
328
+ .description('List project collaborators')
329
+ .option('-f, --format <format>', 'Output format (json, pretty)', 'pretty')
330
+ .action(async (projectId: string, options) => {
331
+ try {
332
+ const client = getClient();
333
+ const collaborators = await client.getCollaborators(projectId);
334
+
335
+ if (options.format === 'json') {
336
+ formatOutput(collaborators, 'json');
337
+ } else {
338
+ if (collaborators.length === 0) {
339
+ console.log(chalk.yellow('No collaborators found.'));
340
+ return;
341
+ }
342
+ console.log(chalk.bold(`Collaborators (${collaborators.length}):\n`));
343
+ for (const collab of collaborators) {
344
+ console.log(`${chalk.cyan(collab.name)} <${collab.email}>`);
345
+ console.log(chalk.gray(` ID: ${collab.id}`));
346
+ console.log();
347
+ }
348
+ }
349
+ } catch (error) {
350
+ console.error(chalk.red(`Error: ${(error as Error).message}`));
351
+ process.exit(1);
352
+ }
353
+ });
354
+
355
+ // ============================================
356
+ // Section Commands
357
+ // ============================================
358
+
359
+ const sectionCmd = new Command('section')
360
+ .description('Manage sections');
361
+
362
+ sectionCmd
363
+ .command('list')
364
+ .description('List sections')
365
+ .option('-p, --project <projectId>', 'Filter by project ID')
366
+ .option('-f, --format <format>', 'Output format (json, pretty)', 'pretty')
367
+ .action(async (options) => {
368
+ try {
369
+ const client = getClient();
370
+ const sections = await client.listSections(options.project);
371
+
372
+ if (options.format === 'json') {
373
+ formatOutput(sections, 'json');
374
+ } else {
375
+ if (sections.length === 0) {
376
+ console.log(chalk.yellow('No sections found.'));
377
+ return;
378
+ }
379
+ console.log(chalk.bold(`Sections (${sections.length}):\n`));
380
+ for (const section of sections) {
381
+ console.log(chalk.cyan(section.name));
382
+ console.log(chalk.gray(` ID: ${section.id}`));
383
+ console.log(chalk.gray(` Project: ${section.project_id}`));
384
+ console.log();
385
+ }
386
+ }
387
+ } catch (error) {
388
+ console.error(chalk.red(`Error: ${(error as Error).message}`));
389
+ process.exit(1);
390
+ }
391
+ });
392
+
393
+ sectionCmd
394
+ .command('get <sectionId>')
395
+ .description('Get a section by ID')
396
+ .option('-f, --format <format>', 'Output format (json, pretty)', 'pretty')
397
+ .action(async (sectionId: string, options) => {
398
+ try {
399
+ const client = getClient();
400
+ const section = await client.getSection(sectionId);
401
+
402
+ if (options.format === 'json') {
403
+ formatOutput(section, 'json');
404
+ } else {
405
+ console.log(chalk.bold(section.name));
406
+ console.log(chalk.gray('ID:'), section.id);
407
+ console.log(chalk.gray('Project:'), section.project_id);
408
+ console.log(chalk.gray('Order:'), section.order);
409
+ }
410
+ } catch (error) {
411
+ console.error(chalk.red(`Error: ${(error as Error).message}`));
412
+ process.exit(1);
413
+ }
414
+ });
415
+
416
+ sectionCmd
417
+ .command('create <name>')
418
+ .description('Create a new section')
419
+ .requiredOption('-p, --project <projectId>', 'Project ID')
420
+ .option('-o, --order <order>', 'Section order', parseInt)
421
+ .option('-f, --format <format>', 'Output format (json, pretty)', 'pretty')
422
+ .action(async (name: string, options) => {
423
+ try {
424
+ const client = getClient();
425
+ const section = await client.createSection({
426
+ name,
427
+ project_id: options.project,
428
+ order: options.order,
429
+ });
430
+
431
+ if (options.format === 'json') {
432
+ formatOutput(section, 'json');
433
+ } else {
434
+ console.log(chalk.green(`Created section: ${section.name}`));
435
+ console.log(chalk.gray('ID:'), section.id);
436
+ }
437
+ } catch (error) {
438
+ console.error(chalk.red(`Error: ${(error as Error).message}`));
439
+ process.exit(1);
440
+ }
441
+ });
442
+
443
+ sectionCmd
444
+ .command('update <sectionId>')
445
+ .description('Update a section')
446
+ .requiredOption('-n, --name <name>', 'New name')
447
+ .option('-f, --format <format>', 'Output format (json, pretty)', 'pretty')
448
+ .action(async (sectionId: string, options) => {
449
+ try {
450
+ const client = getClient();
451
+ const section = await client.updateSection(sectionId, {
452
+ name: options.name,
453
+ });
454
+
455
+ if (options.format === 'json') {
456
+ formatOutput(section, 'json');
457
+ } else {
458
+ console.log(chalk.green(`Updated section: ${section.name}`));
459
+ }
460
+ } catch (error) {
461
+ console.error(chalk.red(`Error: ${(error as Error).message}`));
462
+ process.exit(1);
463
+ }
464
+ });
465
+
466
+ sectionCmd
467
+ .command('delete <sectionId>')
468
+ .description('Delete a section')
469
+ .action(async (sectionId: string) => {
470
+ try {
471
+ const client = getClient();
472
+ await client.deleteSection(sectionId);
473
+ console.log(chalk.green('Section deleted.'));
474
+ } catch (error) {
475
+ console.error(chalk.red(`Error: ${(error as Error).message}`));
476
+ process.exit(1);
477
+ }
478
+ });
479
+
480
+ // ============================================
481
+ // Task Commands
482
+ // ============================================
483
+
484
+ const taskCmd = new Command('task')
485
+ .description('Manage tasks');
486
+
487
+ taskCmd
488
+ .command('list')
489
+ .description('List tasks')
490
+ .option('-p, --project <projectId>', 'Filter by project ID')
491
+ .option('-s, --section <sectionId>', 'Filter by section ID')
492
+ .option('-l, --label <label>', 'Filter by label')
493
+ .option('--filter <filter>', 'Filter expression')
494
+ .option('-f, --format <format>', 'Output format (json, pretty)', 'pretty')
495
+ .action(async (options) => {
496
+ try {
497
+ const client = getClient();
498
+ const tasks = await client.listTasks({
499
+ project_id: options.project,
500
+ section_id: options.section,
501
+ label: options.label,
502
+ filter: options.filter,
503
+ });
504
+
505
+ if (options.format === 'json') {
506
+ formatOutput(tasks, 'json');
507
+ } else {
508
+ if (tasks.length === 0) {
509
+ console.log(chalk.yellow('No tasks found.'));
510
+ return;
511
+ }
512
+ console.log(chalk.bold(`Tasks (${tasks.length}):\n`));
513
+ for (const task of tasks) {
514
+ const priority = formatPriority(task.priority);
515
+ const due = task.due ? chalk.magenta(` [${task.due.string}]`) : '';
516
+ const labels = task.labels.length > 0 ? chalk.blue(` @${task.labels.join(' @')}`) : '';
517
+ console.log(`${priority} ${task.content}${due}${labels}`);
518
+ console.log(chalk.gray(` ID: ${task.id}`));
519
+ if (task.description) {
520
+ console.log(chalk.gray(` ${task.description}`));
521
+ }
522
+ console.log();
523
+ }
524
+ }
525
+ } catch (error) {
526
+ console.error(chalk.red(`Error: ${(error as Error).message}`));
527
+ process.exit(1);
528
+ }
529
+ });
530
+
531
+ taskCmd
532
+ .command('get <taskId>')
533
+ .description('Get a task by ID')
534
+ .option('-f, --format <format>', 'Output format (json, pretty)', 'pretty')
535
+ .action(async (taskId: string, options) => {
536
+ try {
537
+ const client = getClient();
538
+ const task = await client.getTask(taskId);
539
+
540
+ if (options.format === 'json') {
541
+ formatOutput(task, 'json');
542
+ } else {
543
+ const priority = formatPriority(task.priority);
544
+ console.log(`${priority} ${chalk.bold(task.content)}`);
545
+ console.log(chalk.gray('ID:'), task.id);
546
+ if (task.description) {
547
+ console.log(chalk.gray('Description:'), task.description);
548
+ }
549
+ console.log(chalk.gray('Project:'), task.project_id);
550
+ if (task.section_id) {
551
+ console.log(chalk.gray('Section:'), task.section_id);
552
+ }
553
+ if (task.due) {
554
+ console.log(chalk.gray('Due:'), task.due.string);
555
+ }
556
+ if (task.labels.length > 0) {
557
+ console.log(chalk.gray('Labels:'), task.labels.join(', '));
558
+ }
559
+ console.log(chalk.gray('Comments:'), task.comment_count);
560
+ console.log(chalk.gray('URL:'), task.url);
561
+ }
562
+ } catch (error) {
563
+ console.error(chalk.red(`Error: ${(error as Error).message}`));
564
+ process.exit(1);
565
+ }
566
+ });
567
+
568
+ taskCmd
569
+ .command('create <content>')
570
+ .description('Create a new task')
571
+ .option('-d, --description <description>', 'Task description')
572
+ .option('-p, --project <projectId>', 'Project ID')
573
+ .option('-s, --section <sectionId>', 'Section ID')
574
+ .option('--parent <parentId>', 'Parent task ID')
575
+ .option('-l, --labels <labels>', 'Labels (comma-separated)')
576
+ .option('--priority <priority>', 'Priority (1-4, 4 is highest)', parseInt)
577
+ .option('--due <due>', 'Due date string (e.g., "tomorrow", "next monday")')
578
+ .option('--due-date <date>', 'Due date (YYYY-MM-DD)')
579
+ .option('--due-datetime <datetime>', 'Due datetime (RFC3339)')
580
+ .option('-f, --format <format>', 'Output format (json, pretty)', 'pretty')
581
+ .action(async (content: string, options) => {
582
+ try {
583
+ const client = getClient();
584
+ const task = await client.createTask({
585
+ content,
586
+ description: options.description,
587
+ project_id: options.project,
588
+ section_id: options.section,
589
+ parent_id: options.parent,
590
+ labels: options.labels?.split(',').map((l: string) => l.trim()),
591
+ priority: options.priority,
592
+ due_string: options.due,
593
+ due_date: options.dueDate,
594
+ due_datetime: options.dueDatetime,
595
+ });
596
+
597
+ if (options.format === 'json') {
598
+ formatOutput(task, 'json');
599
+ } else {
600
+ console.log(chalk.green(`Created task: ${task.content}`));
601
+ console.log(chalk.gray('ID:'), task.id);
602
+ }
603
+ } catch (error) {
604
+ console.error(chalk.red(`Error: ${(error as Error).message}`));
605
+ process.exit(1);
606
+ }
607
+ });
608
+
609
+ taskCmd
610
+ .command('update <taskId>')
611
+ .description('Update a task')
612
+ .option('-c, --content <content>', 'New content')
613
+ .option('-d, --description <description>', 'New description')
614
+ .option('-l, --labels <labels>', 'Labels (comma-separated)')
615
+ .option('--priority <priority>', 'Priority (1-4)', parseInt)
616
+ .option('--due <due>', 'Due date string')
617
+ .option('--due-date <date>', 'Due date (YYYY-MM-DD)')
618
+ .option('--due-datetime <datetime>', 'Due datetime (RFC3339)')
619
+ .option('-f, --format <format>', 'Output format (json, pretty)', 'pretty')
620
+ .action(async (taskId: string, options) => {
621
+ try {
622
+ const client = getClient();
623
+ const task = await client.updateTask(taskId, {
624
+ content: options.content,
625
+ description: options.description,
626
+ labels: options.labels?.split(',').map((l: string) => l.trim()),
627
+ priority: options.priority,
628
+ due_string: options.due,
629
+ due_date: options.dueDate,
630
+ due_datetime: options.dueDatetime,
631
+ });
632
+
633
+ if (options.format === 'json') {
634
+ formatOutput(task, 'json');
635
+ } else {
636
+ console.log(chalk.green(`Updated task: ${task.content}`));
637
+ }
638
+ } catch (error) {
639
+ console.error(chalk.red(`Error: ${(error as Error).message}`));
640
+ process.exit(1);
641
+ }
642
+ });
643
+
644
+ taskCmd
645
+ .command('close <taskId>')
646
+ .description('Complete a task')
647
+ .action(async (taskId: string) => {
648
+ try {
649
+ const client = getClient();
650
+ await client.closeTask(taskId);
651
+ console.log(chalk.green('Task completed.'));
652
+ } catch (error) {
653
+ console.error(chalk.red(`Error: ${(error as Error).message}`));
654
+ process.exit(1);
655
+ }
656
+ });
657
+
658
+ taskCmd
659
+ .command('reopen <taskId>')
660
+ .description('Reopen a completed task')
661
+ .action(async (taskId: string) => {
662
+ try {
663
+ const client = getClient();
664
+ await client.reopenTask(taskId);
665
+ console.log(chalk.green('Task reopened.'));
666
+ } catch (error) {
667
+ console.error(chalk.red(`Error: ${(error as Error).message}`));
668
+ process.exit(1);
669
+ }
670
+ });
671
+
672
+ taskCmd
673
+ .command('delete <taskId>')
674
+ .description('Delete a task')
675
+ .action(async (taskId: string) => {
676
+ try {
677
+ const client = getClient();
678
+ await client.deleteTask(taskId);
679
+ console.log(chalk.green('Task deleted.'));
680
+ } catch (error) {
681
+ console.error(chalk.red(`Error: ${(error as Error).message}`));
682
+ process.exit(1);
683
+ }
684
+ });
685
+
686
+ // ============================================
687
+ // Label Commands
688
+ // ============================================
689
+
690
+ const labelCmd = new Command('label')
691
+ .description('Manage labels');
692
+
693
+ labelCmd
694
+ .command('list')
695
+ .description('List all labels')
696
+ .option('-f, --format <format>', 'Output format (json, pretty)', 'pretty')
697
+ .action(async (options) => {
698
+ try {
699
+ const client = getClient();
700
+ const labels = await client.listLabels();
701
+
702
+ if (options.format === 'json') {
703
+ formatOutput(labels, 'json');
704
+ } else {
705
+ if (labels.length === 0) {
706
+ console.log(chalk.yellow('No labels found.'));
707
+ return;
708
+ }
709
+ console.log(chalk.bold(`Labels (${labels.length}):\n`));
710
+ for (const label of labels) {
711
+ const favorite = label.is_favorite ? chalk.yellow('* ') : '';
712
+ console.log(`${favorite}${chalk.blue(`@${label.name}`)}`);
713
+ console.log(chalk.gray(` ID: ${label.id}`));
714
+ console.log(chalk.gray(` Color: ${label.color}`));
715
+ console.log();
716
+ }
717
+ }
718
+ } catch (error) {
719
+ console.error(chalk.red(`Error: ${(error as Error).message}`));
720
+ process.exit(1);
721
+ }
722
+ });
723
+
724
+ labelCmd
725
+ .command('get <labelId>')
726
+ .description('Get a label by ID')
727
+ .option('-f, --format <format>', 'Output format (json, pretty)', 'pretty')
728
+ .action(async (labelId: string, options) => {
729
+ try {
730
+ const client = getClient();
731
+ const label = await client.getLabel(labelId);
732
+
733
+ if (options.format === 'json') {
734
+ formatOutput(label, 'json');
735
+ } else {
736
+ console.log(chalk.bold(`@${label.name}`));
737
+ console.log(chalk.gray('ID:'), label.id);
738
+ console.log(chalk.gray('Color:'), label.color);
739
+ console.log(chalk.gray('Favorite:'), label.is_favorite ? 'Yes' : 'No');
740
+ }
741
+ } catch (error) {
742
+ console.error(chalk.red(`Error: ${(error as Error).message}`));
743
+ process.exit(1);
744
+ }
745
+ });
746
+
747
+ labelCmd
748
+ .command('create <name>')
749
+ .description('Create a new label')
750
+ .option('-c, --color <color>', 'Label color')
751
+ .option('--favorite', 'Mark as favorite')
752
+ .option('-f, --format <format>', 'Output format (json, pretty)', 'pretty')
753
+ .action(async (name: string, options) => {
754
+ try {
755
+ const client = getClient();
756
+ const label = await client.createLabel({
757
+ name,
758
+ color: options.color,
759
+ is_favorite: options.favorite,
760
+ });
761
+
762
+ if (options.format === 'json') {
763
+ formatOutput(label, 'json');
764
+ } else {
765
+ console.log(chalk.green(`Created label: @${label.name}`));
766
+ console.log(chalk.gray('ID:'), label.id);
767
+ }
768
+ } catch (error) {
769
+ console.error(chalk.red(`Error: ${(error as Error).message}`));
770
+ process.exit(1);
771
+ }
772
+ });
773
+
774
+ labelCmd
775
+ .command('update <labelId>')
776
+ .description('Update a label')
777
+ .option('-n, --name <name>', 'New name')
778
+ .option('-c, --color <color>', 'New color')
779
+ .option('--favorite', 'Mark as favorite')
780
+ .option('--no-favorite', 'Remove from favorites')
781
+ .option('-f, --format <format>', 'Output format (json, pretty)', 'pretty')
782
+ .action(async (labelId: string, options) => {
783
+ try {
784
+ const client = getClient();
785
+ const label = await client.updateLabel(labelId, {
786
+ name: options.name,
787
+ color: options.color,
788
+ is_favorite: options.favorite,
789
+ });
790
+
791
+ if (options.format === 'json') {
792
+ formatOutput(label, 'json');
793
+ } else {
794
+ console.log(chalk.green(`Updated label: @${label.name}`));
795
+ }
796
+ } catch (error) {
797
+ console.error(chalk.red(`Error: ${(error as Error).message}`));
798
+ process.exit(1);
799
+ }
800
+ });
801
+
802
+ labelCmd
803
+ .command('delete <labelId>')
804
+ .description('Delete a label')
805
+ .action(async (labelId: string) => {
806
+ try {
807
+ const client = getClient();
808
+ await client.deleteLabel(labelId);
809
+ console.log(chalk.green('Label deleted.'));
810
+ } catch (error) {
811
+ console.error(chalk.red(`Error: ${(error as Error).message}`));
812
+ process.exit(1);
813
+ }
814
+ });
815
+
816
+ // ============================================
817
+ // Comment Commands
818
+ // ============================================
819
+
820
+ const commentCmd = new Command('comment')
821
+ .description('Manage comments');
822
+
823
+ commentCmd
824
+ .command('list')
825
+ .description('List comments')
826
+ .option('-t, --task <taskId>', 'Filter by task ID')
827
+ .option('-p, --project <projectId>', 'Filter by project ID')
828
+ .option('-f, --format <format>', 'Output format (json, pretty)', 'pretty')
829
+ .action(async (options) => {
830
+ try {
831
+ if (!options.task && !options.project) {
832
+ console.error(chalk.red('Error: Either --task or --project is required.'));
833
+ process.exit(1);
834
+ }
835
+
836
+ const client = getClient();
837
+ const comments = await client.listComments({
838
+ task_id: options.task,
839
+ project_id: options.project,
840
+ });
841
+
842
+ if (options.format === 'json') {
843
+ formatOutput(comments, 'json');
844
+ } else {
845
+ if (comments.length === 0) {
846
+ console.log(chalk.yellow('No comments found.'));
847
+ return;
848
+ }
849
+ console.log(chalk.bold(`Comments (${comments.length}):\n`));
850
+ for (const comment of comments) {
851
+ console.log(chalk.cyan(comment.content));
852
+ console.log(chalk.gray(` ID: ${comment.id}`));
853
+ console.log(chalk.gray(` Posted: ${comment.posted_at}`));
854
+ if (comment.attachment) {
855
+ console.log(chalk.gray(` Attachment: ${comment.attachment.file_name}`));
856
+ }
857
+ console.log();
858
+ }
859
+ }
860
+ } catch (error) {
861
+ console.error(chalk.red(`Error: ${(error as Error).message}`));
862
+ process.exit(1);
863
+ }
864
+ });
865
+
866
+ commentCmd
867
+ .command('get <commentId>')
868
+ .description('Get a comment by ID')
869
+ .option('-f, --format <format>', 'Output format (json, pretty)', 'pretty')
870
+ .action(async (commentId: string, options) => {
871
+ try {
872
+ const client = getClient();
873
+ const comment = await client.getComment(commentId);
874
+
875
+ if (options.format === 'json') {
876
+ formatOutput(comment, 'json');
877
+ } else {
878
+ console.log(chalk.bold(comment.content));
879
+ console.log(chalk.gray('ID:'), comment.id);
880
+ console.log(chalk.gray('Posted:'), comment.posted_at);
881
+ if (comment.task_id) {
882
+ console.log(chalk.gray('Task:'), comment.task_id);
883
+ }
884
+ if (comment.project_id) {
885
+ console.log(chalk.gray('Project:'), comment.project_id);
886
+ }
887
+ if (comment.attachment) {
888
+ console.log(chalk.gray('Attachment:'), comment.attachment.file_url);
889
+ }
890
+ }
891
+ } catch (error) {
892
+ console.error(chalk.red(`Error: ${(error as Error).message}`));
893
+ process.exit(1);
894
+ }
895
+ });
896
+
897
+ commentCmd
898
+ .command('create <content>')
899
+ .description('Create a new comment')
900
+ .option('-t, --task <taskId>', 'Task ID')
901
+ .option('-p, --project <projectId>', 'Project ID')
902
+ .option('-f, --format <format>', 'Output format (json, pretty)', 'pretty')
903
+ .action(async (content: string, options) => {
904
+ try {
905
+ if (!options.task && !options.project) {
906
+ console.error(chalk.red('Error: Either --task or --project is required.'));
907
+ process.exit(1);
908
+ }
909
+
910
+ const client = getClient();
911
+ const comment = await client.createComment({
912
+ content,
913
+ task_id: options.task,
914
+ project_id: options.project,
915
+ });
916
+
917
+ if (options.format === 'json') {
918
+ formatOutput(comment, 'json');
919
+ } else {
920
+ console.log(chalk.green('Created comment.'));
921
+ console.log(chalk.gray('ID:'), comment.id);
922
+ }
923
+ } catch (error) {
924
+ console.error(chalk.red(`Error: ${(error as Error).message}`));
925
+ process.exit(1);
926
+ }
927
+ });
928
+
929
+ commentCmd
930
+ .command('update <commentId>')
931
+ .description('Update a comment')
932
+ .requiredOption('-c, --content <content>', 'New content')
933
+ .option('-f, --format <format>', 'Output format (json, pretty)', 'pretty')
934
+ .action(async (commentId: string, options) => {
935
+ try {
936
+ const client = getClient();
937
+ const comment = await client.updateComment(commentId, {
938
+ content: options.content,
939
+ });
940
+
941
+ if (options.format === 'json') {
942
+ formatOutput(comment, 'json');
943
+ } else {
944
+ console.log(chalk.green('Updated comment.'));
945
+ }
946
+ } catch (error) {
947
+ console.error(chalk.red(`Error: ${(error as Error).message}`));
948
+ process.exit(1);
949
+ }
950
+ });
951
+
952
+ commentCmd
953
+ .command('delete <commentId>')
954
+ .description('Delete a comment')
955
+ .action(async (commentId: string) => {
956
+ try {
957
+ const client = getClient();
958
+ await client.deleteComment(commentId);
959
+ console.log(chalk.green('Comment deleted.'));
960
+ } catch (error) {
961
+ console.error(chalk.red(`Error: ${(error as Error).message}`));
962
+ process.exit(1);
963
+ }
964
+ });
965
+
966
+ // ============================================
967
+ // Main Program
968
+ // ============================================
969
+
970
+ program
971
+ .name('connect-todoist')
972
+ .description('Todoist connector - Projects, tasks, sections, labels, and comments management')
973
+ .version('0.0.1')
974
+ .option('--profile <profile>', 'Use a specific profile')
975
+ .hook('preAction', (thisCommand) => {
976
+ const opts = thisCommand.opts();
977
+ if (opts.profile) {
978
+ setProfileOverride(opts.profile);
979
+ }
980
+ });
981
+
982
+ program.addCommand(profileCmd);
983
+ program.addCommand(configCmd);
984
+ program.addCommand(projectCmd);
985
+ program.addCommand(sectionCmd);
986
+ program.addCommand(taskCmd);
987
+ program.addCommand(labelCmd);
988
+ program.addCommand(commentCmd);
989
+
990
+ program.parse();