@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,731 @@
1
+ #!/usr/bin/env bun
2
+ import { Command } from 'commander';
3
+ import chalk from 'chalk';
4
+ import { Asana } from '../api';
5
+ import {
6
+ getAccessToken,
7
+ setAccessToken,
8
+ clearConfig,
9
+ getConfigDir,
10
+ setProfileOverride,
11
+ getCurrentProfile,
12
+ setCurrentProfile,
13
+ listProfiles,
14
+ createProfile,
15
+ deleteProfile,
16
+ profileExists,
17
+ loadProfile,
18
+ } from '../utils/config';
19
+ import type { OutputFormat } from '../utils/output';
20
+ import { success, error, info, print, warn } from '../utils/output';
21
+
22
+ const CONNECTOR_NAME = 'connect-asana';
23
+ const VERSION = '0.0.1';
24
+
25
+ const program = new Command();
26
+
27
+ program
28
+ .name(CONNECTOR_NAME)
29
+ .description('Asana connector - Projects, tasks, workspaces, and teams management')
30
+ .version(VERSION)
31
+ .option('-t, --token <token>', 'Access token (overrides config)')
32
+ .option('-f, --format <format>', 'Output format (json, pretty)', 'pretty')
33
+ .option('-p, --profile <profile>', 'Use a specific profile')
34
+ .hook('preAction', (thisCommand) => {
35
+ const opts = thisCommand.opts();
36
+ if (opts.profile) {
37
+ if (!profileExists(opts.profile)) {
38
+ error(`Profile "${opts.profile}" does not exist. Create it with "${CONNECTOR_NAME} profile create ${opts.profile}"`);
39
+ process.exit(1);
40
+ }
41
+ setProfileOverride(opts.profile);
42
+ }
43
+ if (opts.token) {
44
+ process.env.ASANA_ACCESS_TOKEN = opts.token;
45
+ }
46
+ });
47
+
48
+ function getFormat(cmd: Command): OutputFormat {
49
+ const parent = cmd.parent;
50
+ return (parent?.opts().format || 'pretty') as OutputFormat;
51
+ }
52
+
53
+ function getClient(): Asana {
54
+ const accessToken = getAccessToken();
55
+ if (!accessToken) {
56
+ error(`No access token configured. Run "${CONNECTOR_NAME} config set-token <token>" or set ASANA_ACCESS_TOKEN environment variable.`);
57
+ process.exit(1);
58
+ }
59
+ return new Asana({ accessToken });
60
+ }
61
+
62
+ // ============================================
63
+ // Profile Commands
64
+ // ============================================
65
+ const profileCmd = program
66
+ .command('profile')
67
+ .description('Manage configuration profiles');
68
+
69
+ profileCmd
70
+ .command('list')
71
+ .description('List all profiles')
72
+ .action(() => {
73
+ const profiles = listProfiles();
74
+ const current = getCurrentProfile();
75
+
76
+ if (profiles.length === 0) {
77
+ info('No profiles found. Use "profile create <name>" to create one.');
78
+ return;
79
+ }
80
+
81
+ success(`Profiles:`);
82
+ profiles.forEach(p => {
83
+ const isActive = p === current ? chalk.green(' (active)') : '';
84
+ console.log(` ${p}${isActive}`);
85
+ });
86
+ });
87
+
88
+ profileCmd
89
+ .command('use <name>')
90
+ .description('Switch to a profile')
91
+ .action((name: string) => {
92
+ if (!profileExists(name)) {
93
+ error(`Profile "${name}" does not exist. Create it with "profile create ${name}"`);
94
+ process.exit(1);
95
+ }
96
+ setCurrentProfile(name);
97
+ success(`Switched to profile: ${name}`);
98
+ });
99
+
100
+ profileCmd
101
+ .command('create <name>')
102
+ .description('Create a new profile')
103
+ .option('--token <token>', 'Access token')
104
+ .option('--use', 'Switch to this profile after creation')
105
+ .action((name: string, opts) => {
106
+ if (profileExists(name)) {
107
+ error(`Profile "${name}" already exists`);
108
+ process.exit(1);
109
+ }
110
+
111
+ createProfile(name, {
112
+ accessToken: opts.token,
113
+ });
114
+ success(`Profile "${name}" created`);
115
+
116
+ if (opts.use) {
117
+ setCurrentProfile(name);
118
+ info(`Switched to profile: ${name}`);
119
+ }
120
+ });
121
+
122
+ profileCmd
123
+ .command('delete <name>')
124
+ .description('Delete a profile')
125
+ .action((name: string) => {
126
+ if (name === 'default') {
127
+ error('Cannot delete the default profile');
128
+ process.exit(1);
129
+ }
130
+ if (deleteProfile(name)) {
131
+ success(`Profile "${name}" deleted`);
132
+ } else {
133
+ error(`Profile "${name}" not found`);
134
+ process.exit(1);
135
+ }
136
+ });
137
+
138
+ profileCmd
139
+ .command('show [name]')
140
+ .description('Show profile configuration')
141
+ .action((name?: string) => {
142
+ const profileName = name || getCurrentProfile();
143
+ const config = loadProfile(profileName);
144
+ const active = getCurrentProfile();
145
+
146
+ console.log(chalk.bold(`Profile: ${profileName}${profileName === active ? chalk.green(' (active)') : ''}`));
147
+ info(`Access Token: ${config.accessToken ? `${config.accessToken.substring(0, 8)}...` : chalk.gray('not set')}`);
148
+ });
149
+
150
+ // ============================================
151
+ // Config Commands
152
+ // ============================================
153
+ const configCmd = program
154
+ .command('config')
155
+ .description('Manage CLI configuration (for active profile)');
156
+
157
+ configCmd
158
+ .command('set-token <token>')
159
+ .description('Set access token')
160
+ .action((token: string) => {
161
+ setAccessToken(token);
162
+ success(`Access token saved to profile: ${getCurrentProfile()}`);
163
+ });
164
+
165
+ configCmd
166
+ .command('show')
167
+ .description('Show current configuration')
168
+ .action(() => {
169
+ const profileName = getCurrentProfile();
170
+ const accessToken = getAccessToken();
171
+
172
+ console.log(chalk.bold(`Active Profile: ${profileName}`));
173
+ info(`Config directory: ${getConfigDir()}`);
174
+ info(`Access Token: ${accessToken ? `${accessToken.substring(0, 8)}...` : chalk.gray('not set')}`);
175
+ });
176
+
177
+ configCmd
178
+ .command('clear')
179
+ .description('Clear configuration for active profile')
180
+ .action(() => {
181
+ clearConfig();
182
+ success(`Configuration cleared for profile: ${getCurrentProfile()}`);
183
+ });
184
+
185
+ // ============================================
186
+ // Workspace Commands
187
+ // ============================================
188
+ const workspaceCmd = program
189
+ .command('workspace')
190
+ .description('Workspace management');
191
+
192
+ workspaceCmd
193
+ .command('list')
194
+ .description('List all workspaces')
195
+ .action(async () => {
196
+ try {
197
+ const client = getClient();
198
+ const result = await client.listWorkspaces();
199
+ print(result, getFormat(workspaceCmd));
200
+ } catch (err) {
201
+ error(String(err));
202
+ process.exit(1);
203
+ }
204
+ });
205
+
206
+ workspaceCmd
207
+ .command('get <gid>')
208
+ .description('Get a workspace by GID')
209
+ .action(async (gid: string) => {
210
+ try {
211
+ const client = getClient();
212
+ const result = await client.getWorkspace(gid);
213
+ print(result, getFormat(workspaceCmd));
214
+ } catch (err) {
215
+ error(String(err));
216
+ process.exit(1);
217
+ }
218
+ });
219
+
220
+ // ============================================
221
+ // User Commands
222
+ // ============================================
223
+ const userCmd = program
224
+ .command('user')
225
+ .description('User management');
226
+
227
+ userCmd
228
+ .command('me')
229
+ .description('Get current user')
230
+ .action(async () => {
231
+ try {
232
+ const client = getClient();
233
+ const result = await client.getMe();
234
+ print(result, getFormat(userCmd));
235
+ } catch (err) {
236
+ error(String(err));
237
+ process.exit(1);
238
+ }
239
+ });
240
+
241
+ userCmd
242
+ .command('get <gid>')
243
+ .description('Get a user by GID')
244
+ .action(async (gid: string) => {
245
+ try {
246
+ const client = getClient();
247
+ const result = await client.getUser(gid);
248
+ print(result, getFormat(userCmd));
249
+ } catch (err) {
250
+ error(String(err));
251
+ process.exit(1);
252
+ }
253
+ });
254
+
255
+ userCmd
256
+ .command('list')
257
+ .description('List users in a workspace')
258
+ .requiredOption('--workspace <gid>', 'Workspace GID')
259
+ .action(async (opts) => {
260
+ try {
261
+ const client = getClient();
262
+ const result = await client.listUsersInWorkspace(opts.workspace);
263
+ print(result, getFormat(userCmd));
264
+ } catch (err) {
265
+ error(String(err));
266
+ process.exit(1);
267
+ }
268
+ });
269
+
270
+ // ============================================
271
+ // Team Commands
272
+ // ============================================
273
+ const teamCmd = program
274
+ .command('team')
275
+ .description('Team management');
276
+
277
+ teamCmd
278
+ .command('list')
279
+ .description('List teams in a workspace/organization')
280
+ .requiredOption('--workspace <gid>', 'Workspace/Organization GID')
281
+ .action(async (opts) => {
282
+ try {
283
+ const client = getClient();
284
+ const result = await client.listTeamsInWorkspace(opts.workspace);
285
+ print(result, getFormat(teamCmd));
286
+ } catch (err) {
287
+ error(String(err));
288
+ process.exit(1);
289
+ }
290
+ });
291
+
292
+ teamCmd
293
+ .command('get <gid>')
294
+ .description('Get a team by GID')
295
+ .action(async (gid: string) => {
296
+ try {
297
+ const client = getClient();
298
+ const result = await client.getTeam(gid);
299
+ print(result, getFormat(teamCmd));
300
+ } catch (err) {
301
+ error(String(err));
302
+ process.exit(1);
303
+ }
304
+ });
305
+
306
+ // ============================================
307
+ // Project Commands
308
+ // ============================================
309
+ const projectCmd = program
310
+ .command('project')
311
+ .description('Project management');
312
+
313
+ projectCmd
314
+ .command('list')
315
+ .description('List projects')
316
+ .option('--workspace <gid>', 'Filter by workspace GID')
317
+ .option('--team <gid>', 'Filter by team GID')
318
+ .option('--archived', 'Include archived projects')
319
+ .option('--limit <number>', 'Maximum results', '100')
320
+ .action(async (opts) => {
321
+ try {
322
+ const client = getClient();
323
+ const result = await client.listProjects({
324
+ workspace: opts.workspace,
325
+ team: opts.team,
326
+ archived: opts.archived,
327
+ limit: parseInt(opts.limit),
328
+ });
329
+ print(result, getFormat(projectCmd));
330
+ } catch (err) {
331
+ error(String(err));
332
+ process.exit(1);
333
+ }
334
+ });
335
+
336
+ projectCmd
337
+ .command('get <gid>')
338
+ .description('Get a project by GID')
339
+ .action(async (gid: string) => {
340
+ try {
341
+ const client = getClient();
342
+ const result = await client.getProject(gid);
343
+ print(result, getFormat(projectCmd));
344
+ } catch (err) {
345
+ error(String(err));
346
+ process.exit(1);
347
+ }
348
+ });
349
+
350
+ projectCmd
351
+ .command('create')
352
+ .description('Create a new project')
353
+ .requiredOption('--name <name>', 'Project name')
354
+ .option('--workspace <gid>', 'Workspace GID')
355
+ .option('--team <gid>', 'Team GID')
356
+ .option('--notes <notes>', 'Project notes')
357
+ .option('--color <color>', 'Project color')
358
+ .option('--public', 'Make project public')
359
+ .action(async (opts) => {
360
+ try {
361
+ const client = getClient();
362
+ const result = await client.createProject({
363
+ name: opts.name,
364
+ workspace: opts.workspace,
365
+ team: opts.team,
366
+ notes: opts.notes,
367
+ color: opts.color,
368
+ public: opts.public,
369
+ });
370
+ success('Project created!');
371
+ print(result, getFormat(projectCmd));
372
+ } catch (err) {
373
+ error(String(err));
374
+ process.exit(1);
375
+ }
376
+ });
377
+
378
+ projectCmd
379
+ .command('delete <gid>')
380
+ .description('Delete a project')
381
+ .action(async (gid: string) => {
382
+ try {
383
+ const client = getClient();
384
+ await client.deleteProject(gid);
385
+ success(`Project ${gid} deleted`);
386
+ } catch (err) {
387
+ error(String(err));
388
+ process.exit(1);
389
+ }
390
+ });
391
+
392
+ // ============================================
393
+ // Section Commands
394
+ // ============================================
395
+ const sectionCmd = program
396
+ .command('section')
397
+ .description('Section management');
398
+
399
+ sectionCmd
400
+ .command('list')
401
+ .description('List sections in a project')
402
+ .requiredOption('--project <gid>', 'Project GID')
403
+ .action(async (opts) => {
404
+ try {
405
+ const client = getClient();
406
+ const result = await client.listSections(opts.project);
407
+ print(result, getFormat(sectionCmd));
408
+ } catch (err) {
409
+ error(String(err));
410
+ process.exit(1);
411
+ }
412
+ });
413
+
414
+ sectionCmd
415
+ .command('get <gid>')
416
+ .description('Get a section by GID')
417
+ .action(async (gid: string) => {
418
+ try {
419
+ const client = getClient();
420
+ const result = await client.getSection(gid);
421
+ print(result, getFormat(sectionCmd));
422
+ } catch (err) {
423
+ error(String(err));
424
+ process.exit(1);
425
+ }
426
+ });
427
+
428
+ sectionCmd
429
+ .command('create')
430
+ .description('Create a new section')
431
+ .requiredOption('--project <gid>', 'Project GID')
432
+ .requiredOption('--name <name>', 'Section name')
433
+ .action(async (opts) => {
434
+ try {
435
+ const client = getClient();
436
+ const result = await client.createSection(opts.project, { name: opts.name });
437
+ success('Section created!');
438
+ print(result, getFormat(sectionCmd));
439
+ } catch (err) {
440
+ error(String(err));
441
+ process.exit(1);
442
+ }
443
+ });
444
+
445
+ sectionCmd
446
+ .command('delete <gid>')
447
+ .description('Delete a section')
448
+ .action(async (gid: string) => {
449
+ try {
450
+ const client = getClient();
451
+ await client.deleteSection(gid);
452
+ success(`Section ${gid} deleted`);
453
+ } catch (err) {
454
+ error(String(err));
455
+ process.exit(1);
456
+ }
457
+ });
458
+
459
+ // ============================================
460
+ // Task Commands
461
+ // ============================================
462
+ const taskCmd = program
463
+ .command('task')
464
+ .description('Task management');
465
+
466
+ taskCmd
467
+ .command('list')
468
+ .description('List tasks')
469
+ .option('--project <gid>', 'Filter by project GID')
470
+ .option('--section <gid>', 'Filter by section GID')
471
+ .option('--assignee <gid>', 'Filter by assignee GID (requires --workspace)')
472
+ .option('--workspace <gid>', 'Workspace GID (required with --assignee)')
473
+ .option('--limit <number>', 'Maximum results', '100')
474
+ .action(async (opts) => {
475
+ try {
476
+ const client = getClient();
477
+ const result = await client.listTasks({
478
+ project: opts.project,
479
+ section: opts.section,
480
+ assignee: opts.assignee,
481
+ workspace: opts.workspace,
482
+ limit: parseInt(opts.limit),
483
+ });
484
+ print(result, getFormat(taskCmd));
485
+ } catch (err) {
486
+ error(String(err));
487
+ process.exit(1);
488
+ }
489
+ });
490
+
491
+ taskCmd
492
+ .command('get <gid>')
493
+ .description('Get a task by GID')
494
+ .action(async (gid: string) => {
495
+ try {
496
+ const client = getClient();
497
+ const result = await client.getTask(gid);
498
+ print(result, getFormat(taskCmd));
499
+ } catch (err) {
500
+ error(String(err));
501
+ process.exit(1);
502
+ }
503
+ });
504
+
505
+ taskCmd
506
+ .command('create')
507
+ .description('Create a new task')
508
+ .requiredOption('--name <name>', 'Task name')
509
+ .option('--workspace <gid>', 'Workspace GID')
510
+ .option('--projects <gids>', 'Project GIDs (comma-separated)')
511
+ .option('--assignee <gid>', 'Assignee GID')
512
+ .option('--notes <notes>', 'Task notes')
513
+ .option('--due-on <date>', 'Due date (YYYY-MM-DD)')
514
+ .action(async (opts) => {
515
+ try {
516
+ const client = getClient();
517
+ const result = await client.createTask({
518
+ name: opts.name,
519
+ workspace: opts.workspace,
520
+ projects: opts.projects?.split(','),
521
+ assignee: opts.assignee,
522
+ notes: opts.notes,
523
+ due_on: opts.dueOn,
524
+ });
525
+ success('Task created!');
526
+ print(result, getFormat(taskCmd));
527
+ } catch (err) {
528
+ error(String(err));
529
+ process.exit(1);
530
+ }
531
+ });
532
+
533
+ taskCmd
534
+ .command('update <gid>')
535
+ .description('Update a task')
536
+ .option('--name <name>', 'Task name')
537
+ .option('--assignee <gid>', 'Assignee GID')
538
+ .option('--notes <notes>', 'Task notes')
539
+ .option('--due-on <date>', 'Due date (YYYY-MM-DD)')
540
+ .option('--completed', 'Mark as completed')
541
+ .action(async (gid: string, opts) => {
542
+ try {
543
+ const client = getClient();
544
+ const result = await client.updateTask(gid, {
545
+ name: opts.name,
546
+ assignee: opts.assignee,
547
+ notes: opts.notes,
548
+ due_on: opts.dueOn,
549
+ completed: opts.completed,
550
+ });
551
+ success('Task updated!');
552
+ print(result, getFormat(taskCmd));
553
+ } catch (err) {
554
+ error(String(err));
555
+ process.exit(1);
556
+ }
557
+ });
558
+
559
+ taskCmd
560
+ .command('delete <gid>')
561
+ .description('Delete a task')
562
+ .action(async (gid: string) => {
563
+ try {
564
+ const client = getClient();
565
+ await client.deleteTask(gid);
566
+ success(`Task ${gid} deleted`);
567
+ } catch (err) {
568
+ error(String(err));
569
+ process.exit(1);
570
+ }
571
+ });
572
+
573
+ taskCmd
574
+ .command('subtasks <gid>')
575
+ .description('List subtasks of a task')
576
+ .action(async (gid: string) => {
577
+ try {
578
+ const client = getClient();
579
+ const result = await client.getSubtasks(gid);
580
+ print(result, getFormat(taskCmd));
581
+ } catch (err) {
582
+ error(String(err));
583
+ process.exit(1);
584
+ }
585
+ });
586
+
587
+ // ============================================
588
+ // Tag Commands
589
+ // ============================================
590
+ const tagCmd = program
591
+ .command('tag')
592
+ .description('Tag management');
593
+
594
+ tagCmd
595
+ .command('list')
596
+ .description('List tags in a workspace')
597
+ .requiredOption('--workspace <gid>', 'Workspace GID')
598
+ .action(async (opts) => {
599
+ try {
600
+ const client = getClient();
601
+ const result = await client.listTags(opts.workspace);
602
+ print(result, getFormat(tagCmd));
603
+ } catch (err) {
604
+ error(String(err));
605
+ process.exit(1);
606
+ }
607
+ });
608
+
609
+ tagCmd
610
+ .command('get <gid>')
611
+ .description('Get a tag by GID')
612
+ .action(async (gid: string) => {
613
+ try {
614
+ const client = getClient();
615
+ const result = await client.getTag(gid);
616
+ print(result, getFormat(tagCmd));
617
+ } catch (err) {
618
+ error(String(err));
619
+ process.exit(1);
620
+ }
621
+ });
622
+
623
+ tagCmd
624
+ .command('create')
625
+ .description('Create a new tag')
626
+ .requiredOption('--name <name>', 'Tag name')
627
+ .requiredOption('--workspace <gid>', 'Workspace GID')
628
+ .option('--color <color>', 'Tag color')
629
+ .action(async (opts) => {
630
+ try {
631
+ const client = getClient();
632
+ const result = await client.createTag({
633
+ name: opts.name,
634
+ workspace: opts.workspace,
635
+ color: opts.color,
636
+ });
637
+ success('Tag created!');
638
+ print(result, getFormat(tagCmd));
639
+ } catch (err) {
640
+ error(String(err));
641
+ process.exit(1);
642
+ }
643
+ });
644
+
645
+ // ============================================
646
+ // Story (Comment) Commands
647
+ // ============================================
648
+ const storyCmd = program
649
+ .command('story')
650
+ .alias('comment')
651
+ .description('Story/Comment management');
652
+
653
+ storyCmd
654
+ .command('list')
655
+ .description('List stories/comments on a task')
656
+ .requiredOption('--task <gid>', 'Task GID')
657
+ .action(async (opts) => {
658
+ try {
659
+ const client = getClient();
660
+ const result = await client.listStories(opts.task);
661
+ print(result, getFormat(storyCmd));
662
+ } catch (err) {
663
+ error(String(err));
664
+ process.exit(1);
665
+ }
666
+ });
667
+
668
+ storyCmd
669
+ .command('create')
670
+ .description('Add a comment to a task')
671
+ .requiredOption('--task <gid>', 'Task GID')
672
+ .requiredOption('--text <text>', 'Comment text')
673
+ .action(async (opts) => {
674
+ try {
675
+ const client = getClient();
676
+ const result = await client.createStory(opts.task, { text: opts.text });
677
+ success('Comment added!');
678
+ print(result, getFormat(storyCmd));
679
+ } catch (err) {
680
+ error(String(err));
681
+ process.exit(1);
682
+ }
683
+ });
684
+
685
+ storyCmd
686
+ .command('delete <gid>')
687
+ .description('Delete a story/comment')
688
+ .action(async (gid: string) => {
689
+ try {
690
+ const client = getClient();
691
+ await client.deleteStory(gid);
692
+ success(`Story ${gid} deleted`);
693
+ } catch (err) {
694
+ error(String(err));
695
+ process.exit(1);
696
+ }
697
+ });
698
+
699
+ // ============================================
700
+ // Search Commands
701
+ // ============================================
702
+ const searchCmd = program
703
+ .command('search')
704
+ .description('Search tasks');
705
+
706
+ searchCmd
707
+ .command('tasks')
708
+ .description('Search tasks in a workspace')
709
+ .requiredOption('--workspace <gid>', 'Workspace GID')
710
+ .option('--text <text>', 'Search text')
711
+ .option('--projects <gids>', 'Filter by project GIDs (comma-separated)')
712
+ .option('--assignee <gid>', 'Filter by assignee GID')
713
+ .option('--completed', 'Include completed tasks')
714
+ .action(async (opts) => {
715
+ try {
716
+ const client = getClient();
717
+ const result = await client.searchTasks(opts.workspace, {
718
+ text: opts.text,
719
+ 'projects.any': opts.projects,
720
+ 'assignee.any': opts.assignee,
721
+ completed: opts.completed,
722
+ });
723
+ print(result, getFormat(searchCmd));
724
+ } catch (err) {
725
+ error(String(err));
726
+ process.exit(1);
727
+ }
728
+ });
729
+
730
+ // Parse and execute
731
+ program.parse();