@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.
- package/bin/index.js +71 -1
- package/bin/mcp.js +71 -1
- package/bin/serve.js +70 -0
- package/connectors/connect-asana/.env.example +11 -0
- package/connectors/connect-asana/CLAUDE.md +128 -0
- package/connectors/connect-asana/README.md +193 -0
- package/connectors/connect-asana/package.json +52 -0
- package/connectors/connect-asana/src/api/client.ts +119 -0
- package/connectors/connect-asana/src/api/index.ts +319 -0
- package/connectors/connect-asana/src/cli/index.ts +731 -0
- package/connectors/connect-asana/src/index.ts +19 -0
- package/connectors/connect-asana/src/types/index.ts +270 -0
- package/connectors/connect-asana/src/utils/config.ts +171 -0
- package/connectors/connect-asana/src/utils/output.ts +119 -0
- package/connectors/connect-asana/tsconfig.json +16 -0
- package/connectors/connect-clickup/.env.example +11 -0
- package/connectors/connect-clickup/CLAUDE.md +128 -0
- package/connectors/connect-clickup/README.md +193 -0
- package/connectors/connect-clickup/package.json +52 -0
- package/connectors/connect-clickup/src/api/client.ts +116 -0
- package/connectors/connect-clickup/src/api/index.ts +400 -0
- package/connectors/connect-clickup/src/cli/index.ts +625 -0
- package/connectors/connect-clickup/src/index.ts +19 -0
- package/connectors/connect-clickup/src/types/index.ts +591 -0
- package/connectors/connect-clickup/src/utils/config.ts +157 -0
- package/connectors/connect-clickup/src/utils/output.ts +119 -0
- package/connectors/connect-clickup/tsconfig.json +16 -0
- package/connectors/connect-confluence/.env.example +11 -0
- package/connectors/connect-confluence/CLAUDE.md +272 -0
- package/connectors/connect-confluence/README.md +193 -0
- package/connectors/connect-confluence/package.json +53 -0
- package/connectors/connect-confluence/scripts/release.ts +179 -0
- package/connectors/connect-confluence/src/api/client.ts +213 -0
- package/connectors/connect-confluence/src/api/example.ts +48 -0
- package/connectors/connect-confluence/src/api/index.ts +51 -0
- package/connectors/connect-confluence/src/cli/index.ts +254 -0
- package/connectors/connect-confluence/src/index.ts +103 -0
- package/connectors/connect-confluence/src/types/index.ts +237 -0
- package/connectors/connect-confluence/src/utils/auth.ts +274 -0
- package/connectors/connect-confluence/src/utils/bulk.ts +212 -0
- package/connectors/connect-confluence/src/utils/config.ts +326 -0
- package/connectors/connect-confluence/src/utils/output.ts +175 -0
- package/connectors/connect-confluence/src/utils/settings.ts +114 -0
- package/connectors/connect-confluence/src/utils/storage.ts +198 -0
- package/connectors/connect-confluence/tsconfig.json +16 -0
- package/connectors/connect-jira/.env.example +11 -0
- package/connectors/connect-jira/CLAUDE.md +128 -0
- package/connectors/connect-jira/README.md +193 -0
- package/connectors/connect-jira/package.json +53 -0
- package/connectors/connect-jira/src/api/client.ts +131 -0
- package/connectors/connect-jira/src/api/index.ts +266 -0
- package/connectors/connect-jira/src/cli/index.ts +653 -0
- package/connectors/connect-jira/src/index.ts +23 -0
- package/connectors/connect-jira/src/types/index.ts +448 -0
- package/connectors/connect-jira/src/utils/config.ts +179 -0
- package/connectors/connect-jira/src/utils/output.ts +119 -0
- package/connectors/connect-jira/tsconfig.json +16 -0
- package/connectors/connect-linear/CLAUDE.md +88 -0
- package/connectors/connect-linear/README.md +201 -0
- package/connectors/connect-linear/package.json +45 -0
- package/connectors/connect-linear/src/api/client.ts +62 -0
- package/connectors/connect-linear/src/api/index.ts +46 -0
- package/connectors/connect-linear/src/api/issues.ts +247 -0
- package/connectors/connect-linear/src/api/projects.ts +179 -0
- package/connectors/connect-linear/src/api/teams.ts +125 -0
- package/connectors/connect-linear/src/api/users.ts +112 -0
- package/connectors/connect-linear/src/cli/index.ts +560 -0
- package/connectors/connect-linear/src/index.ts +27 -0
- package/connectors/connect-linear/src/types/index.ts +275 -0
- package/connectors/connect-linear/src/utils/config.ts +249 -0
- package/connectors/connect-linear/src/utils/output.ts +119 -0
- package/connectors/connect-linear/tsconfig.json +16 -0
- package/connectors/connect-slack/.env.example +7 -0
- package/connectors/connect-slack/CLAUDE.md +69 -0
- package/connectors/connect-slack/README.md +150 -0
- package/connectors/connect-slack/package.json +44 -0
- package/connectors/connect-slack/src/api/channels.ts +112 -0
- package/connectors/connect-slack/src/api/client.ts +97 -0
- package/connectors/connect-slack/src/api/index.ts +42 -0
- package/connectors/connect-slack/src/api/messages.ts +127 -0
- package/connectors/connect-slack/src/api/users.ts +110 -0
- package/connectors/connect-slack/src/cli/index.ts +494 -0
- package/connectors/connect-slack/src/index.ts +21 -0
- package/connectors/connect-slack/src/types/index.ts +263 -0
- package/connectors/connect-slack/src/utils/config.ts +297 -0
- package/connectors/connect-slack/src/utils/output.ts +119 -0
- package/connectors/connect-slack/tsconfig.json +16 -0
- package/connectors/connect-telegram/.env.example +2 -0
- package/connectors/connect-telegram/package.json +49 -0
- package/connectors/connect-todoist/.env.example +11 -0
- package/connectors/connect-todoist/CLAUDE.md +104 -0
- package/connectors/connect-todoist/README.md +193 -0
- package/connectors/connect-todoist/package.json +52 -0
- package/connectors/connect-todoist/src/api/client.ts +117 -0
- package/connectors/connect-todoist/src/api/index.ts +188 -0
- package/connectors/connect-todoist/src/cli/index.ts +990 -0
- package/connectors/connect-todoist/src/index.ts +21 -0
- package/connectors/connect-todoist/src/types/index.ts +240 -0
- package/connectors/connect-todoist/src/utils/config.ts +157 -0
- package/connectors/connect-todoist/src/utils/output.ts +119 -0
- package/connectors/connect-todoist/tsconfig.json +16 -0
- package/connectors/connect-trello/.env.example +11 -0
- package/connectors/connect-trello/CLAUDE.md +128 -0
- package/connectors/connect-trello/README.md +193 -0
- package/connectors/connect-trello/package.json +53 -0
- package/connectors/connect-trello/src/api/client.ts +128 -0
- package/connectors/connect-trello/src/api/index.ts +278 -0
- package/connectors/connect-trello/src/cli/index.ts +737 -0
- package/connectors/connect-trello/src/index.ts +21 -0
- package/connectors/connect-trello/src/types/index.ts +314 -0
- package/connectors/connect-trello/src/utils/config.ts +182 -0
- package/connectors/connect-trello/src/utils/output.ts +119 -0
- package/connectors/connect-trello/tsconfig.json +16 -0
- package/connectors/connect-whatsapp/.env.example +11 -0
- package/connectors/connect-whatsapp/CLAUDE.md +113 -0
- package/connectors/connect-whatsapp/README.md +193 -0
- package/connectors/connect-whatsapp/package.json +53 -0
- package/connectors/connect-whatsapp/src/api/client.ts +133 -0
- package/connectors/connect-whatsapp/src/api/index.ts +365 -0
- package/connectors/connect-whatsapp/src/cli/index.ts +686 -0
- package/connectors/connect-whatsapp/src/index.ts +25 -0
- package/connectors/connect-whatsapp/src/types/index.ts +502 -0
- package/connectors/connect-whatsapp/src/utils/config.ts +179 -0
- package/connectors/connect-whatsapp/src/utils/output.ts +119 -0
- package/connectors/connect-whatsapp/tsconfig.json +16 -0
- package/dist/index.js +70 -0
- 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();
|