@krodak/clickup-cli 1.18.1 → 1.19.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/dist/index.js CHANGED
@@ -1,4 +1,53 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ ClickUpClient,
4
+ SPRINT_KEYWORDS,
5
+ TASK_COLUMNS,
6
+ addProfile,
7
+ buildTypeMap,
8
+ deleteFavorite,
9
+ deleteFilter,
10
+ fetchMyTasks,
11
+ findRelatedSpaces,
12
+ formatAssignConfirmation,
13
+ formatCommentConfirmation,
14
+ formatCommentsMarkdown,
15
+ formatCreateConfirmation,
16
+ formatDate,
17
+ formatDateISO,
18
+ formatDuration,
19
+ formatGroupedTasksMarkdown,
20
+ formatListsMarkdown,
21
+ formatMarkdownTable,
22
+ formatSpacesMarkdown,
23
+ formatTable,
24
+ formatTaskDetail,
25
+ formatTaskDetailMarkdown,
26
+ formatTimestamp,
27
+ formatUpdateConfirmation,
28
+ getConfigPath,
29
+ getFavorites,
30
+ getFilters,
31
+ groupedTaskPicker,
32
+ isCustomTaskId,
33
+ isDoneStatus,
34
+ isTTY,
35
+ listProfiles,
36
+ loadConfig,
37
+ loadRawConfig,
38
+ openUrl,
39
+ parseSprintDates,
40
+ printTasks,
41
+ removeProfile,
42
+ runSprintCommand,
43
+ saveFavorite,
44
+ saveFilter,
45
+ setDefaultProfile,
46
+ shouldOutputJson,
47
+ showDetailsAndOpen,
48
+ summarize,
49
+ writeConfig
50
+ } from "./chunk-XAXTPUJP.js";
2
51
 
3
52
  // src/index.ts
4
53
  import { realpathSync as realpathSync2 } from "fs";
@@ -7,1718 +56,6 @@ import { Command } from "commander";
7
56
  import { createRequire } from "module";
8
57
  import { fileURLToPath } from "url";
9
58
 
10
- // src/api.ts
11
- var BASE_URL = "https://api.clickup.com/api/v2";
12
- var BASE_URL_V3 = "https://api.clickup.com/api/v3";
13
- var MAX_PAGES = 100;
14
- function isRecord(value) {
15
- return typeof value === "object" && value !== null && !Array.isArray(value);
16
- }
17
- function expectRecord(value, context) {
18
- if (!isRecord(value)) {
19
- throw new Error(`Unexpected API response: expected ${context} object`);
20
- }
21
- return value;
22
- }
23
- function expectRecordField(data, key, context) {
24
- return expectRecord(data[key], context);
25
- }
26
- function expectNumericField(data, key, context) {
27
- const value = Number(data[key]);
28
- if (!Number.isInteger(value)) {
29
- throw new Error(`Unexpected API response: expected ${context}.${key} to be numeric`);
30
- }
31
- return value;
32
- }
33
- function expectStringField(data, key, context) {
34
- const value = data[key];
35
- if (typeof value !== "string") {
36
- throw new Error(`Unexpected API response: expected ${context}.${key} to be a string`);
37
- }
38
- return value;
39
- }
40
- function expectArrayField(data, key, context) {
41
- const value = data[key];
42
- if (!Array.isArray(value)) {
43
- throw new Error(`Unexpected API response: expected ${context}.${key} to be an array`);
44
- }
45
- return value;
46
- }
47
- function readCollectionField(data, key, context) {
48
- if (data[key] === void 0) return [];
49
- return expectArrayField(data, key, context);
50
- }
51
- function expectBooleanField(data, key, context) {
52
- const value = data[key];
53
- if (typeof value !== "boolean") {
54
- throw new Error(`Unexpected API response: expected ${context}.${key} to be a boolean`);
55
- }
56
- return value;
57
- }
58
- function expectPaginatedCollectionField(data, key, context) {
59
- const items = data[key];
60
- if (!Array.isArray(items)) {
61
- throw new Error(`Unexpected API response: expected ${key} array`);
62
- }
63
- return {
64
- items,
65
- lastPage: expectBooleanField(data, "last_page", context)
66
- };
67
- }
68
- function isCustomTaskId(id) {
69
- return /^[A-Z]+-\d+$/i.test(id);
70
- }
71
- var ClickUpClient = class {
72
- apiToken;
73
- teamId;
74
- meCache = null;
75
- constructor(config) {
76
- this.apiToken = config.apiToken;
77
- this.teamId = config.teamId;
78
- }
79
- taskPath(taskId, suffix = "") {
80
- const base = `/task/${taskId}${suffix}`;
81
- if (isCustomTaskId(taskId) && this.teamId) {
82
- const sep = base.includes("?") ? "&" : "?";
83
- return `${base}${sep}custom_task_ids=true&team_id=${this.teamId}`;
84
- }
85
- return base;
86
- }
87
- customIdQueryParams(taskId) {
88
- if (isCustomTaskId(taskId) && this.teamId) {
89
- return `?custom_task_ids=true&team_id=${this.teamId}`;
90
- }
91
- return "";
92
- }
93
- async _fetch(baseUrl, path, options = {}) {
94
- const res = await fetch(`${baseUrl}${path}`, {
95
- ...options,
96
- signal: AbortSignal.timeout(3e4),
97
- headers: {
98
- Authorization: this.apiToken,
99
- ...options.body ? { "Content-Type": "application/json" } : {},
100
- ...options.headers
101
- }
102
- });
103
- if (res.status === 204 || res.headers.get("content-length") === "0") {
104
- if (!res.ok) {
105
- throw new Error(`ClickUp API error ${res.status}: ${res.statusText}`);
106
- }
107
- return {};
108
- }
109
- let parsed;
110
- try {
111
- parsed = await res.json();
112
- } catch {
113
- throw new Error(`ClickUp API error ${res.status}: response was not valid JSON`);
114
- }
115
- const data = expectRecord(parsed, "JSON");
116
- if (!res.ok) {
117
- const raw = data.err ?? data.error ?? data.ECODE ?? res.statusText;
118
- const errMsg = typeof raw === "string" ? raw : JSON.stringify(raw);
119
- throw new Error(`ClickUp API error ${res.status}: ${errMsg}`);
120
- }
121
- return data;
122
- }
123
- async request(path, options = {}) {
124
- return this._fetch(BASE_URL, path, options);
125
- }
126
- async requestV3(path, options = {}) {
127
- return this._fetch(BASE_URL_V3, path, options);
128
- }
129
- async getMe() {
130
- if (this.meCache) return this.meCache;
131
- const data = await this.request(
132
- "/user"
133
- );
134
- const user = expectRecordField(data, "user", "user");
135
- const timezone = typeof user.timezone === "string" && user.timezone ? user.timezone : void 0;
136
- this.meCache = {
137
- id: expectNumericField(user, "id", "user"),
138
- username: expectStringField(user, "username", "user"),
139
- ...timezone ? { timezone } : {}
140
- };
141
- return this.meCache;
142
- }
143
- async getUserTimezone() {
144
- const me = await this.getMe();
145
- return me.timezone;
146
- }
147
- async paginate(buildPath) {
148
- const allTasks = [];
149
- let page = 0;
150
- let lastPage = false;
151
- while (!lastPage && page < MAX_PAGES) {
152
- const data = await this.request(buildPath(page));
153
- const taskPage = expectPaginatedCollectionField(
154
- data,
155
- "tasks",
156
- "task page"
157
- );
158
- allTasks.push(...taskPage.items);
159
- lastPage = taskPage.lastPage;
160
- page++;
161
- }
162
- if (page >= MAX_PAGES && !lastPage) {
163
- process.stderr.write(
164
- `Warning: reached maximum page limit (${MAX_PAGES}), results may be incomplete
165
- `
166
- );
167
- }
168
- return allTasks;
169
- }
170
- async getMyTasks(teamId, filters = {}) {
171
- const baseParams = new URLSearchParams({
172
- subtasks: String(filters.subtasks ?? true)
173
- });
174
- if (filters.includeClosed) baseParams.set("include_closed", "true");
175
- if (!filters.all) {
176
- const me = await this.getMe();
177
- baseParams.append("assignees[]", String(me.id));
178
- }
179
- if (filters.assignees) {
180
- for (const id of filters.assignees) baseParams.append("assignees[]", String(id));
181
- }
182
- for (const s of filters.statuses ?? []) baseParams.append("statuses[]", s);
183
- for (const id of filters.listIds ?? []) baseParams.append("list_ids[]", id);
184
- for (const id of filters.spaceIds ?? []) baseParams.append("space_ids[]", id);
185
- for (const tag of filters.tags ?? []) baseParams.append("tags[]", tag);
186
- if (filters.dueDateGt) baseParams.set("due_date_gt", String(filters.dueDateGt));
187
- if (filters.dueDateLt) baseParams.set("due_date_lt", String(filters.dueDateLt));
188
- if (filters.dateCreatedGt) baseParams.set("date_created_gt", String(filters.dateCreatedGt));
189
- if (filters.dateCreatedLt) baseParams.set("date_created_lt", String(filters.dateCreatedLt));
190
- if (filters.dateUpdatedGt) baseParams.set("date_updated_gt", String(filters.dateUpdatedGt));
191
- if (filters.dateUpdatedLt) baseParams.set("date_updated_lt", String(filters.dateUpdatedLt));
192
- if (filters.customFields?.length) {
193
- baseParams.set("custom_fields", JSON.stringify(filters.customFields));
194
- }
195
- return this.paginate((page) => {
196
- const params = new URLSearchParams(baseParams);
197
- params.set("page", String(page));
198
- return `/team/${teamId}/task?${params.toString()}`;
199
- });
200
- }
201
- async updateTask(taskId, options) {
202
- return this.request(this.taskPath(taskId), {
203
- method: "PUT",
204
- body: JSON.stringify(options)
205
- });
206
- }
207
- async postComment(taskId, commentText, notifyAll) {
208
- const body = { comment_text: commentText };
209
- if (notifyAll) body.notify_all = true;
210
- return this.request(this.taskPath(taskId, "/comment"), {
211
- method: "POST",
212
- body: JSON.stringify(body)
213
- });
214
- }
215
- async getTaskComments(taskId) {
216
- const data = await this.request(this.taskPath(taskId, "/comment"));
217
- return readCollectionField(
218
- data,
219
- "comments",
220
- "task comments"
221
- );
222
- }
223
- async getTasksFromList(listId, params = {}, options = {}) {
224
- return this.paginate((page) => {
225
- const base = { subtasks: "true", page: String(page), ...params };
226
- if (options.includeClosed) base["include_closed"] = "true";
227
- const qs = new URLSearchParams(base).toString();
228
- return `/list/${listId}/task?${qs}`;
229
- });
230
- }
231
- async getTask(taskId) {
232
- return this.request(this.taskPath(taskId, "?include_markdown_description=true"));
233
- }
234
- async createTask(listId, options) {
235
- return this.request(`/list/${listId}/task`, {
236
- method: "POST",
237
- body: JSON.stringify(options)
238
- });
239
- }
240
- async getTeams() {
241
- const data = await this.request("/team");
242
- return readCollectionField(data, "teams", "teams");
243
- }
244
- async getSpaceWithStatuses(spaceId) {
245
- return this.request(`/space/${spaceId}`);
246
- }
247
- async getListWithStatuses(listId) {
248
- return this.request(`/list/${listId}`);
249
- }
250
- async createSpace(teamId, name) {
251
- return this.request(`/team/${teamId}/space`, {
252
- method: "POST",
253
- body: JSON.stringify({ name, multiple_assignees: true })
254
- });
255
- }
256
- async getSpaces(teamId) {
257
- const data = await this.request(`/team/${teamId}/space?archived=false`);
258
- return readCollectionField(data, "spaces", "spaces");
259
- }
260
- async getCustomTaskTypes(teamId) {
261
- const data = await this.request(
262
- `/team/${teamId}/custom_item`
263
- );
264
- return readCollectionField(
265
- data,
266
- "custom_items",
267
- "custom task types"
268
- );
269
- }
270
- async createList(spaceId, name) {
271
- return this.request(`/space/${spaceId}/list`, {
272
- method: "POST",
273
- body: JSON.stringify({ name })
274
- });
275
- }
276
- async createFolderList(folderId, name) {
277
- return this.request(`/folder/${folderId}/list`, {
278
- method: "POST",
279
- body: JSON.stringify({ name })
280
- });
281
- }
282
- async updateList(listId, payload) {
283
- return this.request(`/list/${listId}`, {
284
- method: "PUT",
285
- body: JSON.stringify(payload)
286
- });
287
- }
288
- async createFolder(spaceId, name) {
289
- return this.request(`/space/${spaceId}/folder`, {
290
- method: "POST",
291
- body: JSON.stringify({ name })
292
- });
293
- }
294
- async getLists(spaceId) {
295
- const data = await this.request(`/space/${spaceId}/list?archived=false`);
296
- return readCollectionField(data, "lists", "space lists");
297
- }
298
- async getFolders(spaceId) {
299
- const data = await this.request(
300
- `/space/${spaceId}/folder?archived=false`
301
- );
302
- return readCollectionField(data, "folders", "space folders");
303
- }
304
- async getFolderLists(folderId) {
305
- const data = await this.request(`/folder/${folderId}/list?archived=false`);
306
- return readCollectionField(data, "lists", "folder lists");
307
- }
308
- async getListViews(listId) {
309
- return this.request(`/list/${listId}/view`);
310
- }
311
- async getSpaceViews(spaceId) {
312
- const data = await this.request(`/space/${spaceId}/view`);
313
- return readCollectionField(data, "views", "views");
314
- }
315
- async getFolderViews(folderId) {
316
- const data = await this.request(`/folder/${folderId}/view`);
317
- return readCollectionField(data, "views", "views");
318
- }
319
- async getWorkspaceViews(teamId) {
320
- const data = await this.request(`/team/${teamId}/view`);
321
- return readCollectionField(data, "views", "views");
322
- }
323
- async getViewTasks(viewId) {
324
- return this.paginate((page) => `/view/${viewId}/task?page=${page}`);
325
- }
326
- async getView(viewId) {
327
- const data = await this.request(`/view/${viewId}`);
328
- return expectRecordField(data, "view", "view");
329
- }
330
- async createListView(listId, payload) {
331
- const data = await this.request(`/list/${listId}/view`, {
332
- method: "POST",
333
- body: JSON.stringify(payload)
334
- });
335
- return expectRecordField(data, "view", "view");
336
- }
337
- async updateView(viewId, payload) {
338
- const data = await this.request(`/view/${viewId}`, {
339
- method: "PUT",
340
- body: JSON.stringify(payload)
341
- });
342
- return expectRecordField(data, "view", "view");
343
- }
344
- async deleteView(viewId) {
345
- await this.request(`/view/${viewId}`, { method: "DELETE" });
346
- }
347
- async getListTemplates(teamId) {
348
- const data = await this.request(`/team/${teamId}/list_template`);
349
- return readCollectionField(
350
- data,
351
- "templates",
352
- "list templates"
353
- );
354
- }
355
- async getFolderTemplates(teamId) {
356
- const data = await this.request(
357
- `/team/${teamId}/folder_template`
358
- );
359
- return readCollectionField(
360
- data,
361
- "templates",
362
- "folder templates"
363
- );
364
- }
365
- async createListFromTemplate(containerId, templateId, name, containerType) {
366
- return this.request(
367
- `/${containerType}/${containerId}/list_template/${templateId}`,
368
- { method: "POST", body: JSON.stringify({ name }) }
369
- );
370
- }
371
- async addTaskToList(taskId, listId) {
372
- await this.request(`/list/${listId}/task/${taskId}`, { method: "POST" });
373
- }
374
- async removeTaskFromList(taskId, listId) {
375
- await this.request(`/list/${listId}/task/${taskId}`, { method: "DELETE" });
376
- }
377
- async setCustomFieldValue(taskId, fieldId, value) {
378
- await this.request(this.taskPath(taskId, `/field/${fieldId}`), {
379
- method: "POST",
380
- body: JSON.stringify({ value })
381
- });
382
- }
383
- async removeCustomFieldValue(taskId, fieldId) {
384
- await this.request(this.taskPath(taskId, `/field/${fieldId}`), { method: "DELETE" });
385
- }
386
- async deleteTask(taskId) {
387
- await this.request(this.taskPath(taskId), { method: "DELETE" });
388
- }
389
- async addTagToTask(taskId, tagName) {
390
- await this.request(this.taskPath(taskId, `/tag/${encodeURIComponent(tagName)}`), {
391
- method: "POST"
392
- });
393
- }
394
- async removeTagFromTask(taskId, tagName) {
395
- await this.request(this.taskPath(taskId, `/tag/${encodeURIComponent(tagName)}`), {
396
- method: "DELETE"
397
- });
398
- }
399
- async addDependency(taskId, opts) {
400
- const body = {};
401
- if (opts.dependsOn) body.depends_on = opts.dependsOn;
402
- if (opts.dependencyOf) body.dependency_of = opts.dependencyOf;
403
- await this.request(this.taskPath(taskId, "/dependency"), {
404
- method: "POST",
405
- body: JSON.stringify(body)
406
- });
407
- }
408
- async deleteDependency(taskId, opts) {
409
- const params = new URLSearchParams();
410
- if (opts.dependsOn) params.set("depends_on", opts.dependsOn);
411
- if (opts.dependencyOf) params.set("dependency_of", opts.dependencyOf);
412
- await this.request(this.taskPath(taskId, `/dependency?${params.toString()}`), {
413
- method: "DELETE"
414
- });
415
- }
416
- async updateComment(commentId, text, resolved) {
417
- const body = { comment_text: text };
418
- if (resolved !== void 0) body.resolved = resolved;
419
- await this.request(`/comment/${commentId}`, {
420
- method: "PUT",
421
- body: JSON.stringify(body)
422
- });
423
- }
424
- async deleteComment(commentId) {
425
- await this.request(`/comment/${commentId}`, { method: "DELETE" });
426
- }
427
- async getThreadedComments(commentId) {
428
- const data = await this.request(`/comment/${commentId}/reply`);
429
- return readCollectionField(
430
- data,
431
- "comments",
432
- "threaded comments"
433
- );
434
- }
435
- async createThreadedComment(commentId, text, notifyAll) {
436
- const body = { comment_text: text };
437
- if (notifyAll) body.notify_all = true;
438
- await this.request(`/comment/${commentId}/reply`, {
439
- method: "POST",
440
- body: JSON.stringify(body)
441
- });
442
- }
443
- async addTaskLink(taskId, linksTo) {
444
- await this.request(this.taskPath(taskId, `/link/${linksTo}`), {
445
- method: "POST"
446
- });
447
- }
448
- async deleteTaskLink(taskId, linksTo) {
449
- await this.request(this.taskPath(taskId, `/link/${linksTo}`), {
450
- method: "DELETE"
451
- });
452
- }
453
- async getListCustomFields(listId) {
454
- const data = await this.request(`/list/${listId}/field`);
455
- return readCollectionField(
456
- data,
457
- "fields",
458
- "list custom fields"
459
- );
460
- }
461
- async createChecklist(taskId, name) {
462
- const data = await this.request(this.taskPath(taskId, "/checklist"), {
463
- method: "POST",
464
- body: JSON.stringify({ name })
465
- });
466
- return expectRecordField(
467
- data,
468
- "checklist",
469
- "checklist"
470
- );
471
- }
472
- async deleteChecklist(checklistId) {
473
- await this.request(`/checklist/${checklistId}`, { method: "DELETE" });
474
- }
475
- async createChecklistItem(checklistId, name) {
476
- const data = await this.request(
477
- `/checklist/${checklistId}/checklist_item`,
478
- { method: "POST", body: JSON.stringify({ name }) }
479
- );
480
- return expectRecordField(
481
- data,
482
- "checklist",
483
- "checklist"
484
- );
485
- }
486
- async editChecklistItem(checklistId, checklistItemId, updates) {
487
- const data = await this.request(
488
- `/checklist/${checklistId}/checklist_item/${checklistItemId}`,
489
- { method: "PUT", body: JSON.stringify(updates) }
490
- );
491
- return expectRecordField(
492
- data,
493
- "checklist",
494
- "checklist"
495
- );
496
- }
497
- async deleteChecklistItem(checklistId, checklistItemId) {
498
- await this.request(
499
- `/checklist/${checklistId}/checklist_item/${checklistItemId}`,
500
- { method: "DELETE" }
501
- );
502
- }
503
- async startTimeEntry(teamId, taskId, description) {
504
- const body = {
505
- tid: taskId,
506
- start: Date.now(),
507
- duration: -1
508
- };
509
- if (description) body.description = description;
510
- const data = await this.request(
511
- `/team/${teamId}/time_entries/start${this.customIdQueryParams(taskId)}`,
512
- {
513
- method: "POST",
514
- body: JSON.stringify(body)
515
- }
516
- );
517
- return data.data;
518
- }
519
- async stopTimeEntry(teamId) {
520
- const data = await this.request(`/team/${teamId}/time_entries/stop`, {
521
- method: "POST"
522
- });
523
- return data.data;
524
- }
525
- async getRunningTimeEntry(teamId) {
526
- const data = await this.request(
527
- `/team/${teamId}/time_entries/current`
528
- );
529
- return data.data ?? null;
530
- }
531
- async createTimeEntry(teamId, taskId, duration, opts) {
532
- const start = opts?.start ?? Date.now() - duration;
533
- const body = {
534
- tid: taskId,
535
- start,
536
- duration
537
- };
538
- if (opts?.description) body.description = opts.description;
539
- const data = await this.request(
540
- `/team/${teamId}/time_entries${this.customIdQueryParams(taskId)}`,
541
- {
542
- method: "POST",
543
- body: JSON.stringify(body)
544
- }
545
- );
546
- return data.data;
547
- }
548
- async getTimeEntries(teamId, opts) {
549
- const params = new URLSearchParams();
550
- if (opts?.startDate != null) params.set("start_date", String(opts.startDate));
551
- if (opts?.endDate != null) params.set("end_date", String(opts.endDate));
552
- if (opts?.spaceId) params.set("space_id", opts.spaceId);
553
- if (opts?.listId) params.set("list_id", opts.listId);
554
- if (opts?.assigneeId) params.set("assignee", opts.assigneeId);
555
- const query = params.toString();
556
- const url = `/team/${teamId}/time_entries${query ? `?${query}` : ""}`;
557
- const data = await this.request(url);
558
- const entries = readCollectionField(
559
- data,
560
- "data",
561
- "time entries"
562
- );
563
- if (opts?.taskId) {
564
- return entries.filter((e) => e.task?.id === opts.taskId);
565
- }
566
- return entries;
567
- }
568
- async updateTimeEntry(teamId, timeEntryId, updates) {
569
- const data = await this.request(
570
- `/team/${teamId}/time_entries/${timeEntryId}`,
571
- { method: "PUT", body: JSON.stringify(updates) }
572
- );
573
- return data.data;
574
- }
575
- async getSpaceTags(spaceId) {
576
- const data = await this.request(`/space/${spaceId}/tag`);
577
- return readCollectionField(data, "tags", "space tags");
578
- }
579
- async createSpaceTag(spaceId, name, fg, bg) {
580
- await this.request(`/space/${spaceId}/tag`, {
581
- method: "POST",
582
- body: JSON.stringify({
583
- tag: { name, tag_fg: fg ?? "#000000", tag_bg: bg ?? "#04A9F4" }
584
- })
585
- });
586
- }
587
- async deleteSpaceTag(spaceId, tagName) {
588
- await this.request(
589
- `/space/${spaceId}/tag/${encodeURIComponent(tagName)}`,
590
- { method: "DELETE" }
591
- );
592
- }
593
- async getWorkspaceMembers(teamId) {
594
- const data = await this.request("/team");
595
- const team = readCollectionField(
596
- data,
597
- "teams",
598
- "workspace members"
599
- ).find((t) => t.id === teamId);
600
- return team?.members?.map((m) => m.user) ?? [];
601
- }
602
- async deleteTimeEntry(teamId, timeEntryId) {
603
- await this.request(`/team/${teamId}/time_entries/${timeEntryId}`, {
604
- method: "DELETE"
605
- });
606
- }
607
- async createTaskAttachment(taskId, filePath) {
608
- const { readFile } = await import("fs/promises");
609
- const { basename: basename2 } = await import("path");
610
- const fileBuffer = await readFile(filePath);
611
- const fileName = basename2(filePath);
612
- const formData = new FormData();
613
- formData.append("attachment", new Blob([fileBuffer]), fileName);
614
- const res = await fetch(`${BASE_URL}${this.taskPath(taskId, "/attachment")}`, {
615
- method: "POST",
616
- headers: { Authorization: this.apiToken },
617
- body: formData,
618
- signal: AbortSignal.timeout(6e4)
619
- });
620
- if (!res.ok) {
621
- let msg;
622
- try {
623
- const data2 = await res.json();
624
- msg = data2.err ?? `HTTP ${res.status}`;
625
- } catch {
626
- msg = `HTTP ${res.status}`;
627
- }
628
- throw new Error(`ClickUp API error ${res.status}: ${msg}`);
629
- }
630
- let data;
631
- try {
632
- data = await res.json();
633
- } catch {
634
- throw new Error(`ClickUp API error ${res.status}: response was not valid JSON`);
635
- }
636
- return data;
637
- }
638
- async getDocs(workspaceId) {
639
- const data = await this.requestV3(`/workspaces/${workspaceId}/docs`);
640
- return readCollectionField(data, "docs", "docs");
641
- }
642
- async getDocPage(workspaceId, docId, pageId) {
643
- return this.requestV3(
644
- `/workspaces/${workspaceId}/docs/${docId}/pages/${pageId}?content_format=text/md`
645
- );
646
- }
647
- async createDoc(workspaceId, title, content, parentId) {
648
- const body = { title };
649
- if (content) body.content = content;
650
- if (parentId) {
651
- body.parent_id = parentId;
652
- body.parent_type = "doc";
653
- }
654
- return this.requestV3(`/workspaces/${workspaceId}/docs`, {
655
- method: "POST",
656
- body: JSON.stringify(body)
657
- });
658
- }
659
- async createDocPage(workspaceId, docId, name, content, parentPageId) {
660
- const body = { name, content_format: "text/md" };
661
- if (content) body.content = content;
662
- if (parentPageId) body.parent_page_id = parentPageId;
663
- return this.requestV3(`/workspaces/${workspaceId}/docs/${docId}/pages`, {
664
- method: "POST",
665
- body: JSON.stringify(body)
666
- });
667
- }
668
- async editDocPage(workspaceId, docId, pageId, updates) {
669
- return this.requestV3(`/workspaces/${workspaceId}/docs/${docId}/pages/${pageId}`, {
670
- method: "PUT",
671
- body: JSON.stringify(updates)
672
- });
673
- }
674
- async getDoc(workspaceId, docId) {
675
- return this.requestV3(`/workspaces/${workspaceId}/docs/${docId}`);
676
- }
677
- async getDocPageListing(workspaceId, docId) {
678
- const data = await this.requestV3(
679
- `/workspaces/${workspaceId}/docs/${docId}/pagelisting`
680
- );
681
- return readCollectionField(
682
- data,
683
- "pages",
684
- "doc page listing"
685
- );
686
- }
687
- async getDocPages(workspaceId, docId) {
688
- const data = await this.requestV3(
689
- `/workspaces/${workspaceId}/docs/${docId}/pages?content_format=text/md`
690
- );
691
- return readCollectionField(data, "pages", "doc pages");
692
- }
693
- async getGoals(teamId) {
694
- const data = await this.request(`/team/${teamId}/goal`);
695
- return readCollectionField(data, "goals", "goals");
696
- }
697
- async createGoal(teamId, name, opts) {
698
- const body = { name, multiple_owners: true };
699
- if (opts?.description) body.description = opts.description;
700
- if (opts?.dueDate != null) body.due_date = opts.dueDate;
701
- if (opts?.color) body.color = opts.color;
702
- const data = await this.request(`/team/${teamId}/goal`, {
703
- method: "POST",
704
- body: JSON.stringify(body)
705
- });
706
- return data.goal;
707
- }
708
- async updateGoal(goalId, updates) {
709
- const data = await this.request(`/goal/${goalId}`, {
710
- method: "PUT",
711
- body: JSON.stringify(updates)
712
- });
713
- return data.goal;
714
- }
715
- async getKeyResults(goalId) {
716
- const data = await this.request(`/goal/${goalId}`);
717
- return data.goal?.key_results ?? [];
718
- }
719
- async createKeyResult(goalId, name, type, stepsEnd) {
720
- const data = await this.request(`/goal/${goalId}/key_result`, {
721
- method: "POST",
722
- body: JSON.stringify({
723
- name,
724
- type,
725
- steps_start: 0,
726
- steps_end: stepsEnd,
727
- unit: type === "number" ? "items" : "%"
728
- })
729
- });
730
- return data.key_result;
731
- }
732
- async updateKeyResult(keyResultId, updates) {
733
- const data = await this.request(`/key_result/${keyResultId}`, {
734
- method: "PUT",
735
- body: JSON.stringify(updates)
736
- });
737
- return data.key_result;
738
- }
739
- async deleteGoal(goalId) {
740
- await this.request(`/goal/${goalId}`, { method: "DELETE" });
741
- }
742
- async deleteKeyResult(keyResultId) {
743
- await this.request(`/key_result/${keyResultId}`, { method: "DELETE" });
744
- }
745
- async deleteDoc(workspaceId, docId) {
746
- await this.requestV3(`/workspaces/${workspaceId}/docs/${docId}`, {
747
- method: "DELETE"
748
- });
749
- }
750
- async deleteDocPage(workspaceId, docId, pageId) {
751
- await this.requestV3(
752
- `/workspaces/${workspaceId}/docs/${docId}/pages/${pageId}`,
753
- { method: "DELETE" }
754
- );
755
- }
756
- async updateSpaceTag(spaceId, tagName, updates) {
757
- await this.request(
758
- `/space/${spaceId}/tag/${encodeURIComponent(tagName)}`,
759
- {
760
- method: "PUT",
761
- body: JSON.stringify({
762
- tag: {
763
- name: updates.name,
764
- tag_fg: updates.tag_fg ?? "#000000",
765
- tag_bg: updates.tag_bg ?? "#04A9F4"
766
- }
767
- })
768
- }
769
- );
770
- }
771
- async getTaskTemplates(teamId) {
772
- const data = await this.request(
773
- `/team/${teamId}/taskTemplate?page=0`
774
- );
775
- return readCollectionField(
776
- data,
777
- "templates",
778
- "task templates"
779
- );
780
- }
781
- async createTaskFromTemplate(listId, templateId, name) {
782
- return this.request(`/list/${listId}/taskTemplate/${templateId}`, {
783
- method: "POST",
784
- body: JSON.stringify({ name })
785
- });
786
- }
787
- async createCustomField(teamId, name, type, opts) {
788
- const typeConfig = {};
789
- if (opts?.options?.length) {
790
- typeConfig.options = opts.options.map((optName, i) => ({
791
- name: optName,
792
- orderindex: i
793
- }));
794
- }
795
- const body = {
796
- name,
797
- type,
798
- type_config: typeConfig,
799
- description: opts?.description ?? "",
800
- required: opts?.required ?? false,
801
- pinned: false,
802
- hide_from_guests: false,
803
- required_on_subtasks: false,
804
- private: false,
805
- permission_level: null,
806
- members: [],
807
- groups: []
808
- };
809
- const data = await this.request(
810
- `/field?workspace_id=${teamId}`,
811
- { method: "POST", body: JSON.stringify(body) }
812
- );
813
- return data.data;
814
- }
815
- };
816
-
817
- // src/config.ts
818
- import fs from "fs";
819
- import { homedir } from "os";
820
- import { join } from "path";
821
- function isRecord2(value) {
822
- return typeof value === "object" && value !== null && !Array.isArray(value);
823
- }
824
- function readConfigString(parsed, key, path, strict) {
825
- const value = parsed[key];
826
- if (value === void 0) return void 0;
827
- if (typeof value !== "string") {
828
- if (strict) {
829
- throw new Error(`Config field ${key} must be a string in ${path}.`);
830
- }
831
- return void 0;
832
- }
833
- const trimmed = value.trim();
834
- return trimmed || void 0;
835
- }
836
- function parseConfigFile(raw, path, strictFields, strictRoot = strictFields) {
837
- let parsed;
838
- try {
839
- parsed = JSON.parse(raw);
840
- } catch {
841
- if (strictRoot) {
842
- throw new Error(`Config file at ${path} contains invalid JSON. Please check the file syntax.`);
843
- }
844
- return {};
845
- }
846
- if (!isRecord2(parsed)) {
847
- if (strictRoot) {
848
- throw new Error(`Config file at ${path} must contain a JSON object.`);
849
- }
850
- return {};
851
- }
852
- const apiToken = readConfigString(parsed, "apiToken", path, strictFields);
853
- const teamId = readConfigString(parsed, "teamId", path, strictFields);
854
- const sprintFolderId = readConfigString(parsed, "sprintFolderId", path, strictFields);
855
- return {
856
- ...apiToken ? { apiToken } : {},
857
- ...teamId ? { teamId } : {},
858
- ...sprintFolderId ? { sprintFolderId } : {}
859
- };
860
- }
861
- function trimConfigValue(value) {
862
- const trimmed = value?.trim();
863
- return trimmed || void 0;
864
- }
865
- function configDir() {
866
- const xdg = process.env.XDG_CONFIG_HOME;
867
- if (xdg) return join(xdg, "cup");
868
- return join(homedir(), ".config", "cup");
869
- }
870
- function legacyConfigDir() {
871
- const xdg = process.env.XDG_CONFIG_HOME;
872
- if (xdg) return join(xdg, "cu");
873
- return join(homedir(), ".config", "cu");
874
- }
875
- var migrationChecked = false;
876
- function migrateFromLegacy() {
877
- if (migrationChecked) return;
878
- migrationChecked = true;
879
- const legacy = legacyConfigDir();
880
- const current = configDir();
881
- if (fs.existsSync(join(legacy, "config.json")) && !fs.existsSync(join(current, "config.json"))) {
882
- fs.mkdirSync(current, { recursive: true, mode: 448 });
883
- fs.copyFileSync(join(legacy, "config.json"), join(current, "config.json"));
884
- }
885
- }
886
- function configPath() {
887
- return join(configDir(), "config.json");
888
- }
889
- function migrateToMultiProfile(parsed, filePath) {
890
- if (typeof parsed.apiToken === "string" && !parsed.profiles) {
891
- const profile = {};
892
- const token = trimConfigValue(parsed.apiToken);
893
- if (token) profile.apiToken = token;
894
- const team = typeof parsed.teamId === "string" ? trimConfigValue(parsed.teamId) : void 0;
895
- if (team) profile.teamId = team;
896
- const sprint = typeof parsed.sprintFolderId === "string" ? trimConfigValue(parsed.sprintFolderId) : void 0;
897
- if (sprint) profile.sprintFolderId = sprint;
898
- const migrated = {
899
- defaultProfile: "default",
900
- profiles: { default: profile }
901
- };
902
- const dir = configDir();
903
- if (!fs.existsSync(dir)) {
904
- fs.mkdirSync(dir, { recursive: true, mode: 448 });
905
- }
906
- fs.writeFileSync(join(dir, "config.json"), JSON.stringify(migrated, null, 2) + "\n", {
907
- encoding: "utf-8",
908
- mode: 384
909
- });
910
- return migrated;
911
- }
912
- if (isRecord2(parsed.profiles)) {
913
- const profiles = {};
914
- for (const [name, value] of Object.entries(parsed.profiles)) {
915
- if (isRecord2(value)) {
916
- const p = {};
917
- if (typeof value.apiToken === "string" && value.apiToken.trim())
918
- p.apiToken = value.apiToken.trim();
919
- if (typeof value.teamId === "string" && value.teamId.trim()) p.teamId = value.teamId.trim();
920
- if (typeof value.sprintFolderId === "string" && value.sprintFolderId.trim())
921
- p.sprintFolderId = value.sprintFolderId.trim();
922
- if (isRecord2(value.filters)) p.filters = value.filters;
923
- profiles[name] = p;
924
- }
925
- }
926
- return {
927
- defaultProfile: typeof parsed.defaultProfile === "string" ? parsed.defaultProfile : "",
928
- profiles
929
- };
930
- }
931
- throw new Error(`Config file at ${filePath} has unrecognized format.`);
932
- }
933
- function parseRawConfig(filePath) {
934
- const raw = fs.readFileSync(filePath, "utf-8");
935
- let parsed;
936
- try {
937
- parsed = JSON.parse(raw);
938
- } catch {
939
- throw new Error(
940
- `Config file at ${filePath} contains invalid JSON. Please check the file syntax.`
941
- );
942
- }
943
- if (!isRecord2(parsed)) {
944
- throw new Error(`Config file at ${filePath} must contain a JSON object.`);
945
- }
946
- return { parsed, raw };
947
- }
948
- function isOldFormat(parsed) {
949
- return typeof parsed.apiToken === "string" && !parsed.profiles;
950
- }
951
- function loadConfig(profileName) {
952
- migrateFromLegacy();
953
- const envToken = process.env.CU_API_TOKEN?.trim();
954
- const envTeamId = process.env.CU_TEAM_ID?.trim();
955
- if (envToken && envTeamId) {
956
- if (!envToken.startsWith("pk_")) {
957
- throw new Error("CU_API_TOKEN must start with pk_.");
958
- }
959
- return { apiToken: envToken, teamId: envTeamId };
960
- }
961
- const path = configPath();
962
- if (!fs.existsSync(path)) {
963
- if (envToken || envTeamId) {
964
- throw new Error("Both CU_API_TOKEN and CU_TEAM_ID must be set, or run: cup init");
965
- }
966
- throw new Error("Config missing required field: apiToken.\nSet CU_API_TOKEN or run: cup init");
967
- }
968
- const { parsed } = parseRawConfig(path);
969
- if (isOldFormat(parsed)) {
970
- const fileConfig = parseConfigFile(JSON.stringify(parsed), path, true);
971
- const apiToken2 = envToken ?? fileConfig.apiToken;
972
- if (!apiToken2) {
973
- throw new Error("Config missing required field: apiToken.\nSet CU_API_TOKEN or run: cup init");
974
- }
975
- if (!apiToken2.startsWith("pk_")) {
976
- throw new Error("Config apiToken must start with pk_. The configured token does not.");
977
- }
978
- const teamId2 = envTeamId ?? fileConfig.teamId;
979
- if (!teamId2) {
980
- throw new Error("Config missing required field: teamId.\nSet CU_TEAM_ID or run: cup init");
981
- }
982
- migrateToMultiProfile(parsed, path);
983
- return {
984
- apiToken: apiToken2,
985
- teamId: teamId2,
986
- ...fileConfig.sprintFolderId ? { sprintFolderId: fileConfig.sprintFolderId } : {}
987
- };
988
- }
989
- const multi = loadMultiProfileConfig();
990
- const resolvedProfile = profileName ?? process.env.CU_PROFILE?.trim() ?? multi.defaultProfile;
991
- if (!resolvedProfile) {
992
- throw new Error("No default profile set. Run: cup profile use <name>");
993
- }
994
- const profile = multi.profiles[resolvedProfile];
995
- if (!profile) {
996
- const available = Object.keys(multi.profiles).join(", ");
997
- throw new Error(`Profile "${resolvedProfile}" not found. Available: ${available}`);
998
- }
999
- const apiToken = envToken ?? profile.apiToken?.trim();
1000
- if (!apiToken) {
1001
- throw new Error(
1002
- `Profile "${resolvedProfile}" missing apiToken. Run: cup profile add ${resolvedProfile}`
1003
- );
1004
- }
1005
- if (!apiToken.startsWith("pk_")) {
1006
- throw new Error("Config apiToken must start with pk_. The configured token does not.");
1007
- }
1008
- const teamId = envTeamId ?? profile.teamId?.trim();
1009
- if (!teamId) {
1010
- throw new Error(`Profile "${resolvedProfile}" missing teamId.`);
1011
- }
1012
- return {
1013
- apiToken,
1014
- teamId,
1015
- ...profile.sprintFolderId ? { sprintFolderId: profile.sprintFolderId } : {}
1016
- };
1017
- }
1018
- function loadMultiProfileConfig() {
1019
- migrateFromLegacy();
1020
- const path = configPath();
1021
- if (!fs.existsSync(path)) {
1022
- return { defaultProfile: "", profiles: {} };
1023
- }
1024
- let parsed;
1025
- try {
1026
- const raw = fs.readFileSync(path, "utf-8");
1027
- parsed = JSON.parse(raw);
1028
- } catch {
1029
- return { defaultProfile: "", profiles: {} };
1030
- }
1031
- if (!isRecord2(parsed)) return { defaultProfile: "", profiles: {} };
1032
- if (isOldFormat(parsed)) {
1033
- return migrateToMultiProfile(parsed, path);
1034
- }
1035
- return migrateToMultiProfile(parsed, path);
1036
- }
1037
- function saveMultiProfileConfig(config) {
1038
- const dir = configDir();
1039
- if (!fs.existsSync(dir)) {
1040
- fs.mkdirSync(dir, { recursive: true, mode: 448 });
1041
- }
1042
- fs.writeFileSync(join(dir, "config.json"), JSON.stringify(config, null, 2) + "\n", {
1043
- encoding: "utf-8",
1044
- mode: 384
1045
- });
1046
- }
1047
- function addProfile(name, profile) {
1048
- const multi = loadMultiProfileConfig();
1049
- multi.profiles[name] = profile;
1050
- if (!multi.defaultProfile) multi.defaultProfile = name;
1051
- saveMultiProfileConfig(multi);
1052
- }
1053
- function removeProfile(name) {
1054
- const multi = loadMultiProfileConfig();
1055
- if (!multi.profiles[name]) {
1056
- throw new Error(`Profile "${name}" not found.`);
1057
- }
1058
- const keys = Object.keys(multi.profiles);
1059
- if (keys.length <= 1) {
1060
- throw new Error("Cannot remove the last profile.");
1061
- }
1062
- delete multi.profiles[name];
1063
- if (multi.defaultProfile === name) {
1064
- multi.defaultProfile = Object.keys(multi.profiles)[0] ?? "";
1065
- }
1066
- saveMultiProfileConfig(multi);
1067
- }
1068
- function setDefaultProfile(name) {
1069
- const multi = loadMultiProfileConfig();
1070
- if (!multi.profiles[name]) {
1071
- const available = Object.keys(multi.profiles).join(", ");
1072
- throw new Error(`Profile "${name}" not found. Available: ${available}`);
1073
- }
1074
- multi.defaultProfile = name;
1075
- saveMultiProfileConfig(multi);
1076
- }
1077
- function listProfiles() {
1078
- const multi = loadMultiProfileConfig();
1079
- return Object.entries(multi.profiles).map(([name, profile]) => ({
1080
- name,
1081
- isDefault: name === multi.defaultProfile,
1082
- teamId: profile.teamId
1083
- }));
1084
- }
1085
- function loadRawConfig(profileName) {
1086
- migrateFromLegacy();
1087
- const path = configPath();
1088
- if (!fs.existsSync(path)) return {};
1089
- let parsed;
1090
- try {
1091
- const raw = fs.readFileSync(path, "utf-8");
1092
- parsed = JSON.parse(raw);
1093
- } catch {
1094
- return {};
1095
- }
1096
- if (!isRecord2(parsed)) {
1097
- throw new Error(`Config file at ${path} must contain a JSON object.`);
1098
- }
1099
- if (isOldFormat(parsed)) {
1100
- return parseConfigFile(JSON.stringify(parsed), path, false, true);
1101
- }
1102
- const multi = migrateToMultiProfile(parsed, path);
1103
- const name = profileName || multi.defaultProfile || "default";
1104
- return multi.profiles[name] ?? {};
1105
- }
1106
- function getConfigPath() {
1107
- migrateFromLegacy();
1108
- return configPath();
1109
- }
1110
- function getFilters(profileName) {
1111
- const multi = loadMultiProfileConfig();
1112
- const name = profileName ?? process.env.CU_PROFILE?.trim() ?? multi.defaultProfile;
1113
- const profile = name ? multi.profiles[name] ?? {} : {};
1114
- return profile.filters ?? {};
1115
- }
1116
- function saveFilter(name, entry, profileName) {
1117
- const multi = loadMultiProfileConfig();
1118
- const pName = profileName ?? process.env.CU_PROFILE?.trim() ?? multi.defaultProfile ?? "default";
1119
- const profile = multi.profiles[pName] ?? {};
1120
- const filters = { ...profile.filters ?? {}, [name]: entry };
1121
- multi.profiles[pName] = { ...profile, filters };
1122
- if (!multi.defaultProfile) multi.defaultProfile = pName;
1123
- saveMultiProfileConfig(multi);
1124
- }
1125
- function deleteFilter(name, profileName) {
1126
- const multi = loadMultiProfileConfig();
1127
- const pName = profileName ?? process.env.CU_PROFILE?.trim() ?? multi.defaultProfile ?? "default";
1128
- const profile = multi.profiles[pName] ?? {};
1129
- const filters = { ...profile.filters ?? {} };
1130
- if (!(name in filters)) {
1131
- throw new Error(`Filter "${name}" not found.`);
1132
- }
1133
- delete filters[name];
1134
- multi.profiles[pName] = { ...profile, filters };
1135
- saveMultiProfileConfig(multi);
1136
- }
1137
- function writeConfig(config, profileName) {
1138
- const multi = loadMultiProfileConfig();
1139
- const name = profileName || multi.defaultProfile || "default";
1140
- const apiToken = trimConfigValue(config.apiToken) ?? void 0;
1141
- const teamId = trimConfigValue(config.teamId) ?? void 0;
1142
- const sprintFolderId = trimConfigValue(config.sprintFolderId);
1143
- const normalizedConfig = {
1144
- ...apiToken ? { apiToken } : {},
1145
- ...teamId ? { teamId } : {},
1146
- ...sprintFolderId ? { sprintFolderId } : {}
1147
- };
1148
- multi.profiles[name] = {
1149
- ...multi.profiles[name],
1150
- ...normalizedConfig
1151
- };
1152
- if (!multi.defaultProfile) multi.defaultProfile = name;
1153
- saveMultiProfileConfig(multi);
1154
- }
1155
-
1156
- // src/date.ts
1157
- function formatDate(ms) {
1158
- return new Date(Number(ms)).toLocaleDateString("en-US", {
1159
- month: "short",
1160
- day: "numeric",
1161
- year: "numeric"
1162
- });
1163
- }
1164
- function formatTimestamp(ms) {
1165
- return new Date(Number(ms)).toLocaleString("en-US", {
1166
- month: "short",
1167
- day: "numeric",
1168
- hour: "numeric",
1169
- minute: "2-digit"
1170
- });
1171
- }
1172
- function formatDuration(ms) {
1173
- const totalMinutes = Math.round(Math.abs(ms) / 6e4);
1174
- const hours = Math.floor(totalMinutes / 60);
1175
- const minutes = totalMinutes % 60;
1176
- if (hours > 0 && minutes > 0) return `${hours}h ${minutes}m`;
1177
- if (hours > 0) return `${hours}h`;
1178
- return `${minutes}m`;
1179
- }
1180
- function formatDateISO(ms) {
1181
- const d = new Date(Number(ms));
1182
- const year = d.getUTCFullYear();
1183
- const month = String(d.getUTCMonth() + 1).padStart(2, "0");
1184
- const day = String(d.getUTCDate()).padStart(2, "0");
1185
- return `${year}-${month}-${day}`;
1186
- }
1187
-
1188
- // src/output.ts
1189
- import chalk from "chalk";
1190
- function isTTY() {
1191
- return Boolean(process.stdout.isTTY);
1192
- }
1193
- function shouldOutputJson(forceJson) {
1194
- if (forceJson) return true;
1195
- if (process.env["CU_OUTPUT"] === "json") return true;
1196
- return false;
1197
- }
1198
- function cell(value, width) {
1199
- if (value.length > width) return value.slice(0, width - 1) + "\u2026";
1200
- return value.padEnd(width);
1201
- }
1202
- function computeWidths(rows, columns) {
1203
- return columns.map((col) => {
1204
- const headerLen = col.label.length;
1205
- const maxDataLen = rows.reduce((max, row) => {
1206
- const val = String(row[col.key] ?? "");
1207
- return Math.max(max, val.length);
1208
- }, 0);
1209
- const natural = Math.max(headerLen, maxDataLen);
1210
- return col.maxWidth ? Math.min(natural, col.maxWidth) : natural;
1211
- });
1212
- }
1213
- function formatTable(rows, columns) {
1214
- const widths = computeWidths(rows, columns);
1215
- const header = columns.map((c, i) => cell(c.label, widths[i])).join(" ");
1216
- const divider = chalk.dim("-".repeat(header.replace(/\x1b\[[0-9;]*m/g, "").length));
1217
- const lines = [chalk.bold(header), divider];
1218
- for (const row of rows) {
1219
- lines.push(
1220
- columns.map((c, i) => {
1221
- const raw = String(row[c.key] ?? "");
1222
- const width = widths[i];
1223
- const truncated = raw.length > width ? raw.slice(0, width - 1) + "\u2026" : raw;
1224
- const padding = " ".repeat(Math.max(0, width - truncated.length));
1225
- return c.format ? c.format(truncated, row) + padding : truncated + padding;
1226
- }).join(" ")
1227
- );
1228
- }
1229
- return lines.join("\n");
1230
- }
1231
- function colorStatus(status) {
1232
- const lower = status.toLowerCase();
1233
- if (lower.includes("done") || lower.includes("complete") || lower.includes("closed"))
1234
- return chalk.green(status);
1235
- if (lower.includes("progress") || lower.includes("review") || lower.includes("active"))
1236
- return chalk.yellow(status);
1237
- if (lower.includes("block") || lower.includes("stuck")) return chalk.red(status);
1238
- return chalk.dim(status);
1239
- }
1240
- function colorPriority(priority) {
1241
- const lower = priority.toLowerCase();
1242
- if (lower === "urgent") return chalk.red(priority);
1243
- if (lower === "high") return chalk.yellow(priority);
1244
- if (lower === "normal") return priority;
1245
- if (lower === "low") return chalk.dim(priority);
1246
- return priority;
1247
- }
1248
- function colorDueDate(dateStr, rawTimestamp) {
1249
- if (!dateStr) return dateStr;
1250
- if (rawTimestamp) {
1251
- const ts = Number(rawTimestamp);
1252
- if (Number.isFinite(ts) && ts < Date.now()) return chalk.red(dateStr);
1253
- }
1254
- return dateStr;
1255
- }
1256
- var TASK_COLUMNS = [
1257
- { key: "id", label: "ID" },
1258
- { key: "name", label: "NAME", maxWidth: 60 },
1259
- { key: "status", label: "STATUS", maxWidth: 20, format: (v) => colorStatus(v) },
1260
- { key: "priority", label: "PRIORITY", maxWidth: 10, format: (v) => colorPriority(v) },
1261
- {
1262
- key: "due_date",
1263
- label: "DUE",
1264
- maxWidth: 15,
1265
- format: (v, row) => v ? colorDueDate(v, row.dueRaw) : ""
1266
- },
1267
- { key: "list", label: "LIST" }
1268
- ];
1269
-
1270
- // src/markdown.ts
1271
- function escapeCell(value) {
1272
- return value.replace(/\|/g, "\\|");
1273
- }
1274
- function formatMarkdownTable(rows, columns) {
1275
- const header = "| " + columns.map((c) => c.label).join(" | ") + " |";
1276
- const divider = "| " + columns.map(() => "---").join(" | ") + " |";
1277
- const lines = [header, divider];
1278
- for (const row of rows) {
1279
- const cells = columns.map((c) => escapeCell(String(row[c.key] ?? "")));
1280
- lines.push("| " + cells.join(" | ") + " |");
1281
- }
1282
- return lines.join("\n");
1283
- }
1284
- var TASK_MD_COLUMNS = [
1285
- { key: "id", label: "ID" },
1286
- { key: "name", label: "Name" },
1287
- { key: "status", label: "Status" },
1288
- { key: "priority", label: "Priority" },
1289
- { key: "due_date", label: "Due" },
1290
- { key: "list", label: "List" }
1291
- ];
1292
- function formatTasksMarkdown(tasks) {
1293
- if (tasks.length === 0) return "No tasks found.";
1294
- return formatMarkdownTable(tasks, TASK_MD_COLUMNS);
1295
- }
1296
- function formatCommentsMarkdown(comments) {
1297
- if (comments.length === 0) return "No comments found.";
1298
- return comments.map((c) => `**${c.user}** (${formatDateISO(c.date)})
1299
-
1300
- ${c.text}`).join("\n\n---\n\n");
1301
- }
1302
- var LIST_MD_COLUMNS = [
1303
- { key: "id", label: "ID" },
1304
- { key: "name", label: "Name" },
1305
- { key: "folder", label: "Folder" }
1306
- ];
1307
- function formatListsMarkdown(lists) {
1308
- if (lists.length === 0) return "No lists found.";
1309
- return formatMarkdownTable(lists, LIST_MD_COLUMNS);
1310
- }
1311
- var SPACE_MD_COLUMNS = [
1312
- { key: "id", label: "ID" },
1313
- { key: "name", label: "Name" }
1314
- ];
1315
- function formatSpacesMarkdown(spaces) {
1316
- if (spaces.length === 0) return "No spaces found.";
1317
- return formatMarkdownTable(spaces, SPACE_MD_COLUMNS);
1318
- }
1319
- function formatGroupedTasksMarkdown(groups) {
1320
- const sections = groups.filter((g) => g.tasks.length > 0).map((g) => `## ${g.label}
1321
-
1322
- ${formatMarkdownTable(g.tasks, TASK_MD_COLUMNS)}`);
1323
- if (sections.length === 0) return "No tasks found.";
1324
- return sections.join("\n\n");
1325
- }
1326
- function formatTaskDetailMarkdown(task) {
1327
- const lines = [`# ${task.name}`, ""];
1328
- const isInitiative = (task.custom_item_id ?? 0) !== 0;
1329
- const fields = [
1330
- ["ID", task.id],
1331
- ["Status", task.status.status],
1332
- ["Type", isInitiative ? "initiative" : "task"],
1333
- ["List", task.list.name],
1334
- ["URL", task.url],
1335
- [
1336
- "Assignees",
1337
- task.assignees.length > 0 ? task.assignees.map((a) => a.username).join(", ") : void 0
1338
- ],
1339
- ["Priority", task.priority?.priority],
1340
- ["Parent", task.parent ?? void 0],
1341
- ["Start Date", task.start_date ? formatDateISO(task.start_date) : void 0],
1342
- ["Due Date", task.due_date ? formatDateISO(task.due_date) : void 0],
1343
- [
1344
- "Time Estimate",
1345
- task.time_estimate != null && task.time_estimate > 0 ? formatDuration(task.time_estimate) : void 0
1346
- ],
1347
- [
1348
- "Time Spent",
1349
- task.time_spent != null && task.time_spent > 0 ? formatDuration(task.time_spent) : void 0
1350
- ],
1351
- ["Tags", task.tags && task.tags.length > 0 ? task.tags.map((t) => t.name).join(", ") : void 0],
1352
- [
1353
- "Lists",
1354
- task.locations && task.locations.length > 0 ? task.locations.map((l) => l.name).join(", ") : void 0
1355
- ],
1356
- ["Created", task.date_created ? formatDateISO(task.date_created) : void 0],
1357
- ["Updated", task.date_updated ? formatDateISO(task.date_updated) : void 0]
1358
- ];
1359
- for (const [label, value] of fields) {
1360
- if (value != null && value !== "") {
1361
- lines.push(`**${label}:** ${value}`);
1362
- }
1363
- }
1364
- const descriptionContent = task.markdown_content ?? task.description;
1365
- if (descriptionContent) {
1366
- lines.push("", "## Description", "", descriptionContent);
1367
- }
1368
- if (task.checklists?.length) {
1369
- lines.push("", "## Checklists", "");
1370
- for (const cl of task.checklists) {
1371
- const resolved = cl.items.filter((i) => i.resolved).length;
1372
- lines.push(`### ${cl.name} (${resolved}/${cl.items.length})`, "");
1373
- for (const item of cl.items) {
1374
- lines.push(`- [${item.resolved ? "x" : " "}] ${item.name}`);
1375
- }
1376
- lines.push("");
1377
- }
1378
- }
1379
- if (task.attachments?.length) {
1380
- lines.push("", "## Attachments", "");
1381
- for (const att of task.attachments) {
1382
- lines.push(`- [${att.title}](${att.url})`);
1383
- }
1384
- }
1385
- if (task.dependencies?.length) {
1386
- lines.push("", "## Dependencies", "");
1387
- for (const dep of task.dependencies) {
1388
- const direction = dep.depends_on === task.id ? "blocks" : "depends on";
1389
- const otherId = dep.depends_on === task.id ? dep.task_id : dep.depends_on;
1390
- lines.push(`- ${direction} ${otherId}`);
1391
- }
1392
- }
1393
- if (task.linked_tasks?.length) {
1394
- lines.push("", "## Linked Tasks", "");
1395
- for (const lt of task.linked_tasks) {
1396
- lines.push(`- ${lt.task_id}`);
1397
- }
1398
- }
1399
- return lines.join("\n");
1400
- }
1401
- function formatUpdateConfirmation(id, name) {
1402
- return `Updated task ${id}: "${name}"`;
1403
- }
1404
- function formatCreateConfirmation(id, name, url) {
1405
- return `Created task ${id}: "${name}" - ${url}`;
1406
- }
1407
- function formatCommentConfirmation(id) {
1408
- return `Comment posted (id: ${id})`;
1409
- }
1410
- function formatAssignConfirmation(taskId, opts) {
1411
- const parts = [];
1412
- if (opts.to) parts.push(`Assigned ${opts.to} to ${taskId}`);
1413
- if (opts.remove) parts.push(`Removed ${opts.remove} from ${taskId}`);
1414
- return parts.join("; ");
1415
- }
1416
-
1417
- // src/interactive.ts
1418
- import { execFileSync } from "child_process";
1419
- import { checkbox, confirm, Separator } from "@inquirer/prompts";
1420
- import chalk2 from "chalk";
1421
- function openUrl(url) {
1422
- switch (process.platform) {
1423
- case "darwin":
1424
- execFileSync("open", [url]);
1425
- break;
1426
- case "linux":
1427
- execFileSync("xdg-open", [url]);
1428
- break;
1429
- case "win32":
1430
- execFileSync("cmd", ["/c", "start", "", url]);
1431
- break;
1432
- default:
1433
- process.stderr.write(`Cannot open browser on ${process.platform}. Visit: ${url}
1434
- `);
1435
- }
1436
- }
1437
- function descriptionPreview(text, maxLines = 3) {
1438
- const lines = text.split("\n").filter((l) => l.trim().length > 0);
1439
- const preview = lines.slice(0, maxLines);
1440
- const result = preview.map((l) => ` ${chalk2.dim(l.length > 100 ? l.slice(0, 99) + "\u2026" : l)}`).join("\n");
1441
- if (lines.length > maxLines)
1442
- return result + `
1443
- ${chalk2.dim(`... (${lines.length - maxLines} more lines)`)}`;
1444
- return result;
1445
- }
1446
- function stringifyFieldValue(value) {
1447
- if (typeof value === "string") return value;
1448
- if (typeof value === "number" || typeof value === "boolean") return String(value);
1449
- return JSON.stringify(value);
1450
- }
1451
- function formatCustomFieldValue(field) {
1452
- if (field.value === null || field.value === void 0) return null;
1453
- const options = field.type_config?.options;
1454
- switch (field.type) {
1455
- case "drop_down": {
1456
- if (!options) return stringifyFieldValue(field.value);
1457
- const match = options.find((o) => o.id === Number(field.value));
1458
- return match?.name ?? stringifyFieldValue(field.value);
1459
- }
1460
- case "labels": {
1461
- if (!Array.isArray(field.value) || !options) return stringifyFieldValue(field.value);
1462
- const names = field.value.map((id) => options.find((o) => o.id === id)?.name).filter((n) => n !== void 0);
1463
- return names.length > 0 ? names.join(", ") : null;
1464
- }
1465
- case "date": {
1466
- const ts = Number(field.value);
1467
- if (!Number.isFinite(ts)) return stringifyFieldValue(field.value);
1468
- return formatDate(String(ts));
1469
- }
1470
- case "checkbox":
1471
- return field.value === true || field.value === "true" ? "Yes" : "No";
1472
- default:
1473
- return stringifyFieldValue(field.value);
1474
- }
1475
- }
1476
- function formatTaskDetail(task) {
1477
- const lines = [];
1478
- const isInitiative = (task.custom_item_id ?? 0) !== 0;
1479
- const typeLabel = isInitiative ? "initiative" : "task";
1480
- lines.push(chalk2.bold.underline(task.name));
1481
- lines.push("");
1482
- const fields = [
1483
- ["ID", task.id],
1484
- ["Status", task.status?.status ? colorStatus(task.status.status) : void 0],
1485
- ["Type", typeLabel],
1486
- ["List", task.list?.name],
1487
- [
1488
- "Assignees",
1489
- task.assignees?.length ? task.assignees.map((a) => a.username).join(", ") : void 0
1490
- ],
1491
- ["Priority", task.priority?.priority ? colorPriority(task.priority.priority) : void 0],
1492
- ["Start", task.start_date ? formatDate(task.start_date) : void 0],
1493
- ["Due", task.due_date ? colorDueDate(formatDate(task.due_date), task.due_date) : void 0],
1494
- ["Estimate", task.time_estimate ? formatDuration(task.time_estimate) : void 0],
1495
- ["Tracked", task.time_spent ? formatDuration(task.time_spent) : void 0],
1496
- ["Tags", task.tags?.length ? task.tags.map((t) => t.name).join(", ") : void 0],
1497
- ["Lists", task.locations?.length ? task.locations.map((l) => l.name).join(", ") : void 0],
1498
- ["Parent", task.parent || void 0],
1499
- ["URL", task.url]
1500
- ];
1501
- const maxLabel = Math.max(...fields.filter(([, v]) => v).map(([k]) => k.length));
1502
- for (const [label, value] of fields) {
1503
- if (!value) continue;
1504
- lines.push(` ${chalk2.bold(label.padEnd(maxLabel + 1))} ${value}`);
1505
- }
1506
- if (task.custom_fields?.length) {
1507
- const formatted = task.custom_fields.map((f) => [f.name, formatCustomFieldValue(f)]).filter((pair) => pair[1] !== null);
1508
- if (formatted.length > 0) {
1509
- lines.push("");
1510
- lines.push(chalk2.bold("Custom Fields"));
1511
- for (const [name, value] of formatted) {
1512
- lines.push(` ${chalk2.bold(name)} ${value}`);
1513
- }
1514
- }
1515
- }
1516
- if (task.checklists?.length) {
1517
- lines.push("");
1518
- lines.push(chalk2.bold("Checklists"));
1519
- for (const cl of task.checklists) {
1520
- const resolved = cl.items.filter((i) => i.resolved).length;
1521
- lines.push(` ${chalk2.bold(cl.name)} (${resolved}/${cl.items.length})`);
1522
- for (const item of cl.items) {
1523
- const check = item.resolved ? chalk2.green("[x]") : chalk2.dim("[ ]");
1524
- lines.push(` ${check} ${item.name}`);
1525
- }
1526
- }
1527
- }
1528
- if (task.attachments?.length) {
1529
- lines.push("");
1530
- lines.push(chalk2.bold("Attachments"));
1531
- for (const att of task.attachments) {
1532
- lines.push(` ${att.title} ${chalk2.dim(att.url)}`);
1533
- }
1534
- }
1535
- if (task.dependencies?.length) {
1536
- lines.push("");
1537
- lines.push(chalk2.bold("Dependencies"));
1538
- for (const dep of task.dependencies) {
1539
- const direction = dep.depends_on === task.id ? "blocks" : "depends on";
1540
- const otherId = dep.depends_on === task.id ? dep.task_id : dep.depends_on;
1541
- lines.push(` ${direction} ${chalk2.dim(otherId)}`);
1542
- }
1543
- }
1544
- if (task.linked_tasks?.length) {
1545
- lines.push("");
1546
- lines.push(chalk2.bold("Linked Tasks"));
1547
- for (const lt of task.linked_tasks) {
1548
- lines.push(` ${chalk2.dim(lt.task_id)}`);
1549
- }
1550
- }
1551
- if (task.text_content?.trim()) {
1552
- lines.push("");
1553
- lines.push(descriptionPreview(task.text_content));
1554
- }
1555
- return lines.join("\n");
1556
- }
1557
- function formatChoiceName(task) {
1558
- const id = task.id.padEnd(12);
1559
- const name = task.name.length > 50 ? task.name.slice(0, 49) + "\u2026" : task.name.padEnd(50);
1560
- const status = colorStatus(task.status);
1561
- const priority = task.priority !== "none" ? colorPriority(task.priority) : "";
1562
- return `${id} ${name} ${status}${priority ? " " + priority : ""}`;
1563
- }
1564
- async function interactiveTaskPicker(tasks) {
1565
- if (tasks.length === 0) return [];
1566
- const selected = await checkbox({
1567
- message: `${tasks.length} task(s) found. Select to view details / open in browser:`,
1568
- choices: tasks.map((t) => ({
1569
- name: formatChoiceName(t),
1570
- value: t.id
1571
- })),
1572
- pageSize: 20
1573
- });
1574
- return tasks.filter((t) => selected.includes(t.id));
1575
- }
1576
- async function groupedTaskPicker(groups) {
1577
- const allTasks = groups.flatMap((g) => g.tasks);
1578
- const totalCount = allTasks.length;
1579
- if (totalCount === 0) return [];
1580
- const choices = [];
1581
- for (const group of groups) {
1582
- if (group.tasks.length === 0) continue;
1583
- choices.push(new Separator(chalk2.bold(`${group.label} (${group.tasks.length})`)));
1584
- for (const task of group.tasks) {
1585
- choices.push({ name: formatChoiceName(task), value: task.id });
1586
- }
1587
- }
1588
- const selected = await checkbox({
1589
- message: `${totalCount} task(s) found. Select to view details / open in browser:`,
1590
- choices,
1591
- pageSize: 20
1592
- });
1593
- return allTasks.filter((t) => selected.includes(t.id));
1594
- }
1595
- async function showDetailsAndOpen(tasks, fetchTask) {
1596
- if (tasks.length === 0) return;
1597
- const separator = chalk2.dim("\u2500".repeat(60));
1598
- for (let i = 0; i < tasks.length; i++) {
1599
- const task = tasks[i];
1600
- if (i > 0) {
1601
- console.log("");
1602
- console.log(separator);
1603
- }
1604
- console.log("");
1605
- if (fetchTask) {
1606
- const full = await fetchTask(task.id);
1607
- console.log(formatTaskDetail(full));
1608
- } else {
1609
- const fallback = {
1610
- id: task.id,
1611
- name: task.name,
1612
- status: { status: task.status, color: "" },
1613
- custom_item_id: task.task_type === "initiative" ? 1 : 0,
1614
- assignees: [],
1615
- url: task.url,
1616
- list: { id: "", name: task.list },
1617
- parent: task.parent
1618
- };
1619
- console.log(formatTaskDetail(fallback));
1620
- }
1621
- }
1622
- const urls = tasks.map((t) => t.url);
1623
- console.log("");
1624
- const shouldOpen = await confirm({
1625
- message: `Open ${urls.length} task(s) in browser?`,
1626
- default: true
1627
- });
1628
- if (shouldOpen) {
1629
- for (const url of urls) {
1630
- openUrl(url);
1631
- }
1632
- }
1633
- }
1634
-
1635
- // src/commands/tasks.ts
1636
- var DONE_PATTERNS = ["done", "complete", "closed"];
1637
- function isDoneStatus(status) {
1638
- const lower = status.toLowerCase();
1639
- return DONE_PATTERNS.some((p) => lower.includes(p));
1640
- }
1641
- function formatDueDate(ms) {
1642
- if (!ms) return "";
1643
- return formatDate(ms);
1644
- }
1645
- function resolveTaskType(task, typeMap) {
1646
- const id = task.custom_item_id ?? 0;
1647
- if (id === 0) return "task";
1648
- return typeMap.get(id) ?? `type_${id}`;
1649
- }
1650
- function summarize(task, typeMap) {
1651
- return {
1652
- id: task.id,
1653
- name: task.name,
1654
- status: task.status.status,
1655
- task_type: resolveTaskType(task, typeMap ?? /* @__PURE__ */ new Map()),
1656
- priority: task.priority?.priority ?? "none",
1657
- due_date: formatDueDate(task.due_date),
1658
- ...task.due_date ? { dueRaw: task.due_date } : {},
1659
- list: task.list.name,
1660
- url: task.url,
1661
- ...task.parent ? { parent: task.parent } : {}
1662
- };
1663
- }
1664
- function buildTypeMap(types) {
1665
- const map = /* @__PURE__ */ new Map();
1666
- for (const t of types) {
1667
- map.set(t.id, t.name);
1668
- }
1669
- return map;
1670
- }
1671
- function resolveTypeFilter(typeFilter, typeMap) {
1672
- if (typeFilter === "task") return 0;
1673
- const asNum = Number(typeFilter);
1674
- if (Number.isFinite(asNum)) return asNum;
1675
- const lower = typeFilter.toLowerCase();
1676
- for (const [id, name] of typeMap) {
1677
- if (name.toLowerCase() === lower) return id;
1678
- }
1679
- const available = ["task", ...Array.from(typeMap.values())].join(", ");
1680
- throw new Error(`Unknown task type "${typeFilter}". Available types: ${available}`);
1681
- }
1682
- async function fetchMyTasks(config, opts = {}) {
1683
- const client = new ClickUpClient(config);
1684
- const { typeFilter, name, ...apiFilters } = opts;
1685
- const [allTasks, customTypes] = await Promise.all([
1686
- client.getMyTasks(config.teamId, apiFilters),
1687
- client.getCustomTaskTypes(config.teamId)
1688
- ]);
1689
- const typeMap = buildTypeMap(customTypes);
1690
- let filtered = allTasks;
1691
- if (typeFilter) {
1692
- const targetId = resolveTypeFilter(typeFilter, typeMap);
1693
- filtered = allTasks.filter((t) => (t.custom_item_id ?? 0) === targetId);
1694
- }
1695
- if (name) {
1696
- const query = name.toLowerCase();
1697
- filtered = filtered.filter((t) => t.name.toLowerCase().includes(query));
1698
- }
1699
- return filtered.map((t) => summarize(t, typeMap));
1700
- }
1701
- async function printTasks(tasks, forceJson, config) {
1702
- if (shouldOutputJson(forceJson)) {
1703
- console.log(JSON.stringify(tasks, null, 2));
1704
- return;
1705
- }
1706
- if (!isTTY()) {
1707
- console.log(formatTasksMarkdown(tasks));
1708
- return;
1709
- }
1710
- if (tasks.length === 0) {
1711
- console.log("No tasks found.");
1712
- return;
1713
- }
1714
- const fetchTask = config ? (() => {
1715
- const client = new ClickUpClient(config);
1716
- return (id) => client.getTask(id);
1717
- })() : void 0;
1718
- const selected = await interactiveTaskPicker(tasks);
1719
- await showDetailsAndOpen(selected, fetchTask);
1720
- }
1721
-
1722
59
  // src/status.ts
1723
60
  function matchStatus(input, statuses) {
1724
61
  if (!input) return null;
@@ -1947,13 +284,13 @@ async function getTask(config, taskId) {
1947
284
  }
1948
285
 
1949
286
  // src/commands/init.ts
1950
- import { password, select, confirm as confirm2 } from "@inquirer/prompts";
1951
- import fs2 from "fs";
287
+ import { password, select, confirm } from "@inquirer/prompts";
288
+ import fs from "fs";
1952
289
  async function runInitCommand() {
1953
- const configPath3 = getConfigPath();
1954
- if (fs2.existsSync(configPath3)) {
1955
- const overwrite = await confirm2({
1956
- message: `Config already exists at ${configPath3}. Overwrite?`,
290
+ const configPath2 = getConfigPath();
291
+ if (fs.existsSync(configPath2)) {
292
+ const overwrite = await confirm({
293
+ message: `Config already exists at ${configPath2}. Overwrite?`,
1957
294
  default: false
1958
295
  });
1959
296
  if (!overwrite) {
@@ -1991,190 +328,19 @@ async function runInitCommand() {
1991
328
  });
1992
329
  }
1993
330
  writeConfig({ apiToken, teamId });
1994
- process.stdout.write(`Config written to ${configPath3}
1995
- `);
1996
- }
1997
-
1998
- // src/commands/sprint.ts
1999
- import { select as select2 } from "@inquirer/prompts";
2000
- var SPRINT_KEYWORDS = ["sprint", "iteration", "cycle", "scrum"];
2001
- function parseUSDateRange(name) {
2002
- const m = name.match(/\((\d{1,2})\/(\d{1,2})\s*[-–]\s*(\d{1,2})\/(\d{1,2})\)/);
2003
- if (!m) return null;
2004
- const year = (/* @__PURE__ */ new Date()).getFullYear();
2005
- const start = new Date(year, Number(m[1]) - 1, Number(m[2]));
2006
- const end = new Date(year, Number(m[3]) - 1, Number(m[4]), 23, 59, 59);
2007
- if (end < start) end.setFullYear(end.getFullYear() + 1);
2008
- return { start, end };
2009
- }
2010
- function parseISODateRange(name) {
2011
- const m = name.match(/\((\d{4}-\d{2}-\d{2})\s*[-–]\s*(\d{4}-\d{2}-\d{2})\)/);
2012
- if (!m) return null;
2013
- const [sy, sm, sd] = m[1].split("-").map(Number);
2014
- const [ey, em, ed] = m[2].split("-").map(Number);
2015
- const start = new Date(sy, sm - 1, sd);
2016
- const end = new Date(ey, em - 1, ed, 23, 59, 59);
2017
- return { start, end };
2018
- }
2019
- function parseMonthDayRange(name) {
2020
- const months = {
2021
- jan: 0,
2022
- feb: 1,
2023
- mar: 2,
2024
- apr: 3,
2025
- may: 4,
2026
- jun: 5,
2027
- jul: 6,
2028
- aug: 7,
2029
- sep: 8,
2030
- oct: 9,
2031
- nov: 10,
2032
- dec: 11
2033
- };
2034
- const m = name.match(/\(([A-Za-z]{3})\s+(\d{1,2})\s*[-–]\s*([A-Za-z]{3})\s+(\d{1,2})\)/);
2035
- if (!m) return null;
2036
- const sm = months[m[1].toLowerCase()];
2037
- const em = months[m[3].toLowerCase()];
2038
- if (sm === void 0 || em === void 0) return null;
2039
- const year = (/* @__PURE__ */ new Date()).getFullYear();
2040
- const start = new Date(year, sm, Number(m[2]));
2041
- const end = new Date(year, em, Number(m[4]), 23, 59, 59);
2042
- if (end < start) end.setFullYear(end.getFullYear() + 1);
2043
- return { start, end };
2044
- }
2045
- function parseEuropeanDateRange(name) {
2046
- const m = name.match(/\((\d{1,2})\.(\d{1,2})\s*[-–]\s*(\d{1,2})\.(\d{1,2})\)/);
2047
- if (!m) return null;
2048
- const year = (/* @__PURE__ */ new Date()).getFullYear();
2049
- const start = new Date(year, Number(m[2]) - 1, Number(m[1]));
2050
- const end = new Date(year, Number(m[4]) - 1, Number(m[3]), 23, 59, 59);
2051
- if (end < start) end.setFullYear(end.getFullYear() + 1);
2052
- return { start, end };
2053
- }
2054
- function parseSprintDates(name) {
2055
- return parseUSDateRange(name) ?? parseISODateRange(name) ?? parseMonthDayRange(name) ?? parseEuropeanDateRange(name);
2056
- }
2057
- function findActiveSprintList(lists, today = /* @__PURE__ */ new Date()) {
2058
- if (lists.length === 0) return null;
2059
- for (const list of lists) {
2060
- const dates = parseSprintDates(list.name);
2061
- if (dates && today >= dates.start && today <= dates.end) return list;
2062
- }
2063
- for (const list of lists) {
2064
- if (list.start_date && list.due_date) {
2065
- const start = new Date(Number(list.start_date));
2066
- const end = new Date(Number(list.due_date));
2067
- if (today >= start && today <= end) return list;
2068
- }
2069
- }
2070
- return lists[lists.length - 1] ?? null;
2071
- }
2072
- var NOISE_WORDS = /* @__PURE__ */ new Set(["product", "team", "the", "and", "for", "test"]);
2073
- function extractSpaceKeywords(spaceName) {
2074
- return spaceName.replace(/[^a-zA-Z0-9\s]/g, "").split(/\s+/).map((w) => w.toLowerCase()).filter((w) => w.length >= 3 && !NOISE_WORDS.has(w));
2075
- }
2076
- function findRelatedSpaces(mySpaceIds, allSpaces) {
2077
- const mySpaces = allSpaces.filter((s) => mySpaceIds.has(s.id));
2078
- const keywords = mySpaces.flatMap((s) => extractSpaceKeywords(s.name));
2079
- if (keywords.length === 0) return allSpaces;
2080
- return allSpaces.filter(
2081
- (s) => mySpaceIds.has(s.id) || keywords.some((kw) => s.name.toLowerCase().includes(kw))
2082
- );
2083
- }
2084
- async function runSprintCommand(config, opts) {
2085
- const client = new ClickUpClient(config);
2086
- process.stderr.write("Detecting active sprint...\n");
2087
- const folderId = opts.folder ?? config.sprintFolderId;
2088
- const [myTasks, allSpaces, customTypes] = await Promise.all([
2089
- client.getMyTasks(config.teamId),
2090
- folderId ? Promise.resolve([]) : client.getSpaces(config.teamId),
2091
- client.getCustomTaskTypes(config.teamId)
2092
- ]);
2093
- const typeMap = buildTypeMap(customTypes);
2094
- let sprintLists;
2095
- if (folderId) {
2096
- sprintLists = await client.getFolderLists(folderId);
2097
- } else {
2098
- let spaces;
2099
- if (opts.space) {
2100
- spaces = allSpaces.filter(
2101
- (s) => s.name.toLowerCase().includes(opts.space.toLowerCase()) || s.id === opts.space
2102
- );
2103
- if (spaces.length === 0) {
2104
- throw new Error(
2105
- `No space matching "${opts.space}" found. Use \`cup spaces\` to list available spaces.`
2106
- );
2107
- }
2108
- } else {
2109
- const mySpaceIds = new Set(
2110
- myTasks.map((t) => t.space?.id).filter((id) => Boolean(id))
2111
- );
2112
- spaces = findRelatedSpaces(mySpaceIds, allSpaces);
2113
- }
2114
- const foldersBySpace = await Promise.all(spaces.map((space) => client.getFolders(space.id)));
2115
- const sprintFolders = foldersBySpace.flat().filter((f) => {
2116
- const lower = f.name.toLowerCase();
2117
- return SPRINT_KEYWORDS.some((kw) => lower.includes(kw));
2118
- });
2119
- const listsByFolder = await Promise.all(
2120
- sprintFolders.map((folder) => client.getFolderLists(folder.id))
2121
- );
2122
- sprintLists = listsByFolder.flat();
2123
- }
2124
- let activeList = findActiveSprintList(sprintLists);
2125
- if (!activeList && sprintLists.length > 1 && isTTY()) {
2126
- const choice = await select2({
2127
- message: "Multiple sprint lists found. Which one?",
2128
- choices: sprintLists.map((l) => ({
2129
- name: `${l.name} (${l.id})`,
2130
- value: l
2131
- }))
2132
- });
2133
- activeList = choice;
2134
- }
2135
- if (!activeList && sprintLists.length > 1) {
2136
- process.stderr.write(
2137
- `Multiple sprint lists found:
2138
- ${sprintLists.map((l) => ` - ${l.name} (${l.id})`).join("\n")}
2139
- Using: ${sprintLists[sprintLists.length - 1].name}
2140
- `
2141
- );
2142
- activeList = sprintLists[sprintLists.length - 1] ?? null;
2143
- }
2144
- if (!activeList) {
2145
- throw new Error(
2146
- 'No sprint list found. Ensure sprint folders contain "sprint", "iteration", "cycle", or "scrum" in their name.'
2147
- );
2148
- }
2149
- process.stderr.write(`Active sprint: ${activeList.name}
331
+ process.stdout.write(`Config written to ${configPath2}
2150
332
  `);
2151
- const me = await client.getMe();
2152
- const viewData = await client.getListViews(activeList.id);
2153
- const listView = viewData.required_views?.list;
2154
- let allTasks;
2155
- if (listView) {
2156
- allTasks = await client.getViewTasks(listView.id);
2157
- } else {
2158
- allTasks = await client.getTasksFromList(activeList.id);
2159
- }
2160
- let sprintTasks = allTasks.filter((t) => t.assignees.some((a) => Number(a.id) === me.id));
2161
- if (!opts.includeClosed) {
2162
- sprintTasks = sprintTasks.filter((t) => !isDoneStatus(t.status.status));
2163
- }
2164
- const filtered = opts.status ? sprintTasks.filter((t) => t.status.status.toLowerCase() === opts.status.toLowerCase()) : sprintTasks;
2165
- const summaries = filtered.map((t) => summarize(t, typeMap));
2166
- await printTasks(summaries, opts.json ?? false, config);
2167
333
  }
2168
334
 
2169
335
  // src/commands/sprints.ts
2170
- import chalk3 from "chalk";
336
+ import chalk from "chalk";
2171
337
  var SPRINT_COLUMNS = [
2172
338
  { key: "id", label: "ID" },
2173
339
  {
2174
340
  key: "sprint",
2175
341
  label: "SPRINT",
2176
342
  maxWidth: 60,
2177
- format: (v, row) => row.active ? chalk3.green(v) : v
343
+ format: (v, row) => row.active ? chalk.green(v) : v
2178
344
  },
2179
345
  { key: "dates", label: "DATES" }
2180
346
  ];
@@ -2217,11 +383,15 @@ async function listSprints(config, opts = {}) {
2217
383
  );
2218
384
  spaces = findRelatedSpaces(mySpaceIds, allSpaces);
2219
385
  }
386
+ const favorites = getFavorites();
387
+ const favoriteFolderIds = Object.values(favorites).filter((f) => f.type === "sprint-folder").map((f) => f.id);
2220
388
  const foldersBySpace = await Promise.all(spaces.map((space) => client.getFolders(space.id)));
2221
389
  const sprintFolders = foldersBySpace.flat().filter((f) => {
2222
390
  const lower = f.name.toLowerCase();
2223
391
  return SPRINT_KEYWORDS.some((kw) => lower.includes(kw));
2224
392
  });
393
+ const detectedFolderIds = new Set(sprintFolders.map((f) => f.id));
394
+ const extraFavoriteIds = favoriteFolderIds.filter((id) => !detectedFolderIds.has(id));
2225
395
  const today = /* @__PURE__ */ new Date();
2226
396
  const allSprints = [];
2227
397
  const listsByFolder = await Promise.all(
@@ -2232,6 +402,16 @@ async function listSprints(config, opts = {}) {
2232
402
  const lists = listsByFolder[i];
2233
403
  allSprints.push(...buildSprintInfos(lists, folder.name, today));
2234
404
  }
405
+ const extraListsByFolder = await Promise.all(
406
+ extraFavoriteIds.map((id) => client.getFolderLists(id))
407
+ );
408
+ for (let i = 0; i < extraFavoriteIds.length; i++) {
409
+ const lists = extraListsByFolder[i];
410
+ const alias = Object.entries(favorites).find(
411
+ ([_, f]) => f.type === "sprint-folder" && f.id === extraFavoriteIds[i]
412
+ )?.[1]?.name ?? `Folder ${extraFavoriteIds[i]}`;
413
+ allSprints.push(...buildSprintInfos(lists, alias, today));
414
+ }
2235
415
  if (shouldOutputJson(opts.json ?? false)) {
2236
416
  console.log(JSON.stringify(allSprints, null, 2));
2237
417
  return;
@@ -2286,7 +466,7 @@ async function postComment(config, taskId, text, notifyAll) {
2286
466
  }
2287
467
 
2288
468
  // src/commands/comments.ts
2289
- import chalk4 from "chalk";
469
+ import chalk2 from "chalk";
2290
470
  async function fetchComments(config, taskId) {
2291
471
  const client = new ClickUpClient(config);
2292
472
  const comments = await client.getTaskComments(taskId);
@@ -2310,11 +490,11 @@ function printComments(comments, forceJson) {
2310
490
  console.log("No comments found.");
2311
491
  return;
2312
492
  }
2313
- const separator = chalk4.dim("-".repeat(60));
493
+ const separator = chalk2.dim("-".repeat(60));
2314
494
  for (let i = 0; i < comments.length; i++) {
2315
495
  const c = comments[i];
2316
496
  if (i > 0) console.log(separator);
2317
- console.log(`${chalk4.bold(c.user)} ${chalk4.dim(formatTimestamp(c.date))}`);
497
+ console.log(`${chalk2.bold(c.user)} ${chalk2.dim(formatTimestamp(c.date))}`);
2318
498
  console.log(c.text);
2319
499
  if (i < comments.length - 1) console.log("");
2320
500
  }
@@ -2624,7 +804,7 @@ async function openTask(config, query, opts = {}) {
2624
804
  }
2625
805
 
2626
806
  // src/commands/summary.ts
2627
- import chalk5 from "chalk";
807
+ import chalk3 from "chalk";
2628
808
  var IN_PROGRESS_PATTERNS = ["in progress", "in review", "code review", "doing"];
2629
809
  function isCompletedRecently(task, cutoff) {
2630
810
  if (!isDoneStatus(task.status.status)) return false;
@@ -2662,9 +842,9 @@ function categorizeTasks(tasks, hoursBack, typeMap) {
2662
842
  }
2663
843
  function colorSectionLabel(label) {
2664
844
  const lower = label.toLowerCase();
2665
- if (lower.includes("completed")) return chalk5.green(label);
2666
- if (lower.includes("progress")) return chalk5.yellow(label);
2667
- if (lower.includes("overdue")) return chalk5.red(label);
845
+ if (lower.includes("completed")) return chalk3.green(label);
846
+ if (lower.includes("progress")) return chalk3.yellow(label);
847
+ if (lower.includes("overdue")) return chalk3.red(label);
2668
848
  return label;
2669
849
  }
2670
850
  function printSection(label, tasks) {
@@ -2766,7 +946,7 @@ function setConfigValue(key, value, profileName) {
2766
946
  }
2767
947
  writeConfig(merged, profileName);
2768
948
  }
2769
- function configPath2() {
949
+ function configPath() {
2770
950
  return getConfigPath();
2771
951
  }
2772
952
 
@@ -2793,7 +973,7 @@ async function assignTask(config, taskId, opts) {
2793
973
  }
2794
974
 
2795
975
  // src/commands/activity.ts
2796
- import chalk6 from "chalk";
976
+ import chalk4 from "chalk";
2797
977
  async function fetchActivity(config, taskId) {
2798
978
  const client = new ClickUpClient(config);
2799
979
  const [task, rawComments] = await Promise.all([
@@ -2825,8 +1005,8 @@ ${commentsMd}`);
2825
1005
  }
2826
1006
  console.log(formatTaskDetail(result.task));
2827
1007
  console.log("");
2828
- console.log(chalk6.bold("Comments"));
2829
- console.log(chalk6.dim("-".repeat(60)));
1008
+ console.log(chalk4.bold("Comments"));
1009
+ console.log(chalk4.dim("-".repeat(60)));
2830
1010
  if (result.comments.length === 0) {
2831
1011
  console.log("No comments.");
2832
1012
  return;
@@ -2835,9 +1015,9 @@ ${commentsMd}`);
2835
1015
  const c = result.comments[i];
2836
1016
  if (i > 0) {
2837
1017
  console.log("");
2838
- console.log(chalk6.dim("-".repeat(60)));
1018
+ console.log(chalk4.dim("-".repeat(60)));
2839
1019
  }
2840
- console.log(`${chalk6.bold(c.user)} ${chalk6.dim(formatTimestamp(c.date))}`);
1020
+ console.log(`${chalk4.bold(c.user)} ${chalk4.dim(formatTimestamp(c.date))}`);
2841
1021
  console.log(c.text);
2842
1022
  }
2843
1023
  }
@@ -2985,7 +1165,7 @@ var commandMetadata = [
2985
1165
  {
2986
1166
  name: "comment-delete",
2987
1167
  description: "Delete a comment",
2988
- flags: ["--json"],
1168
+ flags: ["--mine", "--match", "--json"],
2989
1169
  quickReference: [
2990
1170
  {
2991
1171
  section: "write",
@@ -3648,6 +1828,23 @@ var commandMetadata = [
3648
1828
  { section: "read", usage: "filter run <name>", description: "Run a saved shortcut" }
3649
1829
  ]
3650
1830
  },
1831
+ {
1832
+ name: "favorite",
1833
+ description: "Manage local favorites (sprint folders, spaces, lists, etc.)",
1834
+ quickReference: [
1835
+ {
1836
+ section: "configuration",
1837
+ usage: "favorite add <type> <id> [alias]",
1838
+ description: "Add a favorite"
1839
+ },
1840
+ {
1841
+ section: "configuration",
1842
+ usage: "favorite remove <alias>",
1843
+ description: "Remove a favorite"
1844
+ },
1845
+ { section: "read", usage: "favorite list", description: "List saved favorites" }
1846
+ ]
1847
+ },
3651
1848
  {
3652
1849
  name: "profile",
3653
1850
  description: "Manage profiles",
@@ -3716,6 +1913,7 @@ var bashSpecialCaseCommands = /* @__PURE__ */ new Set([
3716
1913
  "time",
3717
1914
  "bulk",
3718
1915
  "filter",
1916
+ "favorite",
3719
1917
  "config",
3720
1918
  "profile",
3721
1919
  "completion"
@@ -3812,6 +2010,18 @@ ${renderBashCommandCases()}
3812
2010
  COMPREPLY=($(compgen -W "status assign due-date tag" -- "$cur"))
3813
2011
  fi
3814
2012
  ;;
2013
+ favorite)
2014
+ if [[ $cword -eq 2 ]]; then
2015
+ COMPREPLY=($(compgen -W "add remove list" -- "$cur"))
2016
+ elif [[ $cword -eq 3 ]]; then
2017
+ local subcmd="\${words[2]}"
2018
+ case "$subcmd" in
2019
+ add)
2020
+ COMPREPLY=($(compgen -W "sprint-folder space list folder view task" -- "$cur"))
2021
+ ;;
2022
+ esac
2023
+ fi
2024
+ ;;
3815
2025
  profile)
3816
2026
  if [[ $cword -eq 2 ]]; then
3817
2027
  COMPREPLY=($(compgen -W "list add remove use" -- "$cur"))
@@ -4181,6 +2391,8 @@ ${renderZshTopLevelCommands(name)}
4181
2391
  comment-delete)
4182
2392
  _arguments \\
4183
2393
  '1:comment_id:' \\
2394
+ '--mine[Delete one of my comments from the specified task]' \\
2395
+ '--match[Only match comments containing this text]:text:' \\
4184
2396
  '--json[Force JSON output]'
4185
2397
  ;;
4186
2398
  replies)
@@ -4469,6 +2681,40 @@ ${renderZshTopLevelCommands(name)}
4469
2681
  '(-c --content)'{-c,--content}'[New page content]:text:' \\
4470
2682
  '--json[Force JSON output]'
4471
2683
  ;;
2684
+ favorite)
2685
+ local -a favorite_cmds
2686
+ favorite_cmds=(
2687
+ 'add:Add a favorite (types: sprint-folder, space, list, folder, view, task)'
2688
+ 'remove:Remove a favorite by alias'
2689
+ 'list:List saved favorites'
2690
+ )
2691
+ _arguments -C \\
2692
+ '1:favorite command:->favorite_cmd' \\
2693
+ '*::favorite_arg:->favorite_args'
2694
+ case $state in
2695
+ favorite_cmd)
2696
+ _describe 'favorite command' favorite_cmds
2697
+ ;;
2698
+ favorite_args)
2699
+ case $words[1] in
2700
+ add)
2701
+ _arguments \\
2702
+ '1:type:(sprint-folder space list folder view task)' \\
2703
+ '2:id:' \\
2704
+ '3:alias:' \\
2705
+ '(-n --name)'{-n,--name}'[Display name]:name:' \\
2706
+ '--json[Force JSON output]'
2707
+ ;;
2708
+ remove)
2709
+ _arguments '1:alias:' '--json[Force JSON output]'
2710
+ ;;
2711
+ list)
2712
+ _arguments '--type[Filter by entity type]:type:(sprint-folder space list folder view task)' '--json[Force JSON output]'
2713
+ ;;
2714
+ esac
2715
+ ;;
2716
+ esac
2717
+ ;;
4472
2718
  filter)
4473
2719
  local -a filter_cmds
4474
2720
  filter_cmds=(
@@ -4651,6 +2897,14 @@ complete -c ${name} -n '__fish_seen_subcommand_from filter; and not __fish_seen_
4651
2897
  complete -c ${name} -n '__fish_seen_subcommand_from filter; and not __fish_seen_subcommand_from save run list delete show' -a show -d 'Show details of a saved shortcut'
4652
2898
  complete -c ${name} -n '__fish_seen_subcommand_from save run list delete show; and __fish_seen_subcommand_from filter' -l json -d 'Force JSON output'
4653
2899
  complete -c ${name} -n '__fish_seen_subcommand_from save; and __fish_seen_subcommand_from filter' -s d -l description -d 'Filter description'
2900
+
2901
+ complete -c ${name} -n '__fish_seen_subcommand_from favorite; and not __fish_seen_subcommand_from add remove list' -a add -d 'Add a favorite'
2902
+ complete -c ${name} -n '__fish_seen_subcommand_from favorite; and not __fish_seen_subcommand_from add remove list' -a remove -d 'Remove a favorite'
2903
+ complete -c ${name} -n '__fish_seen_subcommand_from favorite; and not __fish_seen_subcommand_from add remove list' -a list -d 'List saved favorites'
2904
+ complete -c ${name} -n '__fish_seen_subcommand_from add; and __fish_seen_subcommand_from favorite' -a 'sprint-folder space list folder view task' -d 'Entity type'
2905
+ complete -c ${name} -n '__fish_seen_subcommand_from add remove list; and __fish_seen_subcommand_from favorite' -l json -d 'Force JSON output'
2906
+ complete -c ${name} -n '__fish_seen_subcommand_from add; and __fish_seen_subcommand_from favorite' -s n -l name -d 'Display name'
2907
+ complete -c ${name} -n '__fish_seen_subcommand_from list; and __fish_seen_subcommand_from favorite' -l type -d 'Filter by entity type'
4654
2908
  `;
4655
2909
  }
4656
2910
  function generateCompletion(shell, name = "cup") {
@@ -4668,15 +2922,18 @@ function generateCompletion(shell, name = "cup") {
4668
2922
 
4669
2923
  // src/commands/skill.ts
4670
2924
  import { readFileSync, realpathSync, mkdirSync, copyFileSync, existsSync } from "fs";
4671
- import { join as join2, dirname } from "path";
4672
- import { homedir as homedir2 } from "os";
4673
- import chalk7 from "chalk";
2925
+ import { join, dirname } from "path";
2926
+ import { homedir } from "os";
2927
+ import chalk5 from "chalk";
4674
2928
  function skillPath() {
4675
- const entryPoint = realpathSync(process.argv[1] ?? "");
2929
+ if (!process.argv[1]) {
2930
+ throw new Error("Cannot determine install path. Run with: cup skill");
2931
+ }
2932
+ const entryPoint = realpathSync(process.argv[1]);
4676
2933
  const packageRoot = dirname(dirname(entryPoint));
4677
- const candidate = join2(packageRoot, "skills", "clickup-cli", "SKILL.md");
2934
+ const candidate = join(packageRoot, "skills", "clickup-cli", "SKILL.md");
4678
2935
  if (existsSync(candidate)) return candidate;
4679
- const altCandidate = join2(dirname(entryPoint), "..", "skills", "clickup-cli", "SKILL.md");
2936
+ const altCandidate = join(dirname(entryPoint), "..", "skills", "clickup-cli", "SKILL.md");
4680
2937
  if (existsSync(altCandidate)) return altCandidate;
4681
2938
  throw new Error("SKILL.md not found. Reinstall with: npm install -g @krodak/clickup-cli");
4682
2939
  }
@@ -4684,11 +2941,11 @@ function printSkill() {
4684
2941
  return readFileSync(skillPath(), "utf-8");
4685
2942
  }
4686
2943
  function getAgentTargets() {
4687
- const home = homedir2();
2944
+ const home = homedir();
4688
2945
  const targets = [
4689
- { name: "Claude Code", dir: join2(home, ".claude", "skills", "clickup") },
4690
- { name: "Codex", dir: join2(home, ".agents", "skills", "clickup") },
4691
- { name: "OpenCode", dir: join2(home, ".config", "opencode", "skills", "clickup") }
2946
+ { name: "Claude Code", dir: join(home, ".claude", "skills", "clickup") },
2947
+ { name: "Codex", dir: join(home, ".agents", "skills", "clickup") },
2948
+ { name: "OpenCode", dir: join(home, ".config", "opencode", "skills", "clickup") }
4692
2949
  ];
4693
2950
  return targets.map((t) => ({
4694
2951
  ...t,
@@ -4700,12 +2957,12 @@ async function installSkillInteractive() {
4700
2957
  const source = skillPath();
4701
2958
  const installed = [];
4702
2959
  if (isTTY()) {
4703
- const { checkbox: checkbox2 } = await import("@inquirer/prompts");
2960
+ const { checkbox } = await import("@inquirer/prompts");
4704
2961
  const preselected = targets.filter((t) => t.detected).map((t) => t.name);
4705
- const selected = await checkbox2({
2962
+ const selected = await checkbox({
4706
2963
  message: "Install skill for which agents?",
4707
2964
  choices: targets.map((t) => ({
4708
- name: `${t.name}${t.detected ? chalk7.dim(" (detected)") : ""}`,
2965
+ name: `${t.name}${t.detected ? chalk5.dim(" (detected)") : ""}`,
4709
2966
  value: t.name,
4710
2967
  checked: t.detected
4711
2968
  }))
@@ -4715,25 +2972,25 @@ async function installSkillInteractive() {
4715
2972
  }
4716
2973
  for (const name of selected) {
4717
2974
  const target = targets.find((t) => t.name === name);
2975
+ if (!target) continue;
4718
2976
  if (!existsSync(target.dir)) mkdirSync(target.dir, { recursive: true });
4719
- const dest = join2(target.dir, "SKILL.md");
2977
+ const dest = join(target.dir, "SKILL.md");
4720
2978
  copyFileSync(source, dest);
4721
2979
  installed.push(`${target.name}: ${dest}`);
4722
2980
  }
4723
2981
  } else {
4724
- for (const target of targets.filter((t) => t.detected)) {
2982
+ const detected = targets.filter((t) => t.detected);
2983
+ if (detected.length === 0) {
2984
+ throw new Error(
2985
+ "No agents detected. Use --path to specify install location:\n cup skill --path ~/.claude/skills/clickup/SKILL.md"
2986
+ );
2987
+ }
2988
+ for (const target of detected) {
4725
2989
  if (!existsSync(target.dir)) mkdirSync(target.dir, { recursive: true });
4726
- const dest = join2(target.dir, "SKILL.md");
2990
+ const dest = join(target.dir, "SKILL.md");
4727
2991
  copyFileSync(source, dest);
4728
2992
  installed.push(`${target.name}: ${dest}`);
4729
2993
  }
4730
- if (installed.length === 0) {
4731
- const fallback = targets[0];
4732
- mkdirSync(fallback.dir, { recursive: true });
4733
- const dest = join2(fallback.dir, "SKILL.md");
4734
- copyFileSync(source, dest);
4735
- installed.push(`${fallback.name}: ${dest}`);
4736
- }
4737
2994
  }
4738
2995
  return installed;
4739
2996
  }
@@ -4950,8 +3207,8 @@ async function deleteTaskCommand(config, taskId, opts) {
4950
3207
  throw new Error("Destructive operation requires --confirm flag in non-interactive mode");
4951
3208
  }
4952
3209
  const task = await client.getTask(taskId);
4953
- const { confirm: confirm3 } = await import("@inquirer/prompts");
4954
- const confirmed = await confirm3({
3210
+ const { confirm: confirm2 } = await import("@inquirer/prompts");
3211
+ const confirmed = await confirm2({
4955
3212
  message: `Delete task "${task.name}" (${task.id})? This cannot be undone.`,
4956
3213
  default: false
4957
3214
  });
@@ -4971,8 +3228,8 @@ async function archiveTaskCommand(config, taskId, opts) {
4971
3228
  throw new Error(`Destructive operation requires --confirm flag in non-interactive mode`);
4972
3229
  }
4973
3230
  const task = await client.getTask(taskId);
4974
- const { confirm: confirm3 } = await import("@inquirer/prompts");
4975
- const confirmed = await confirm3({
3231
+ const { confirm: confirm2 } = await import("@inquirer/prompts");
3232
+ const confirmed = await confirm2({
4976
3233
  message: `${opts.unarchive ? "Unarchive" : "Archive"} task "${task.name}" (${task.id})?`,
4977
3234
  default: false
4978
3235
  });
@@ -5023,7 +3280,7 @@ async function manageTags(config, taskId, opts) {
5023
3280
  }
5024
3281
 
5025
3282
  // src/commands/checklist.ts
5026
- import chalk8 from "chalk";
3283
+ import chalk6 from "chalk";
5027
3284
  async function viewChecklists(config, taskId) {
5028
3285
  const client = new ClickUpClient(config);
5029
3286
  const task = await client.getTask(taskId);
@@ -5056,14 +3313,14 @@ function formatChecklists(checklists) {
5056
3313
  const lines = [];
5057
3314
  for (const cl of checklists) {
5058
3315
  const resolved = cl.items.filter((i) => i.resolved).length;
5059
- lines.push(chalk8.bold(`${cl.name} (${resolved}/${cl.items.length})`));
5060
- lines.push(chalk8.dim(` ID: ${cl.id}`));
3316
+ lines.push(chalk6.bold(`${cl.name} (${resolved}/${cl.items.length})`));
3317
+ lines.push(chalk6.dim(` ID: ${cl.id}`));
5061
3318
  for (const item of cl.items) {
5062
- const check = item.resolved ? chalk8.green("[x]") : chalk8.dim("[ ]");
5063
- const name = item.resolved ? chalk8.dim(item.name) : item.name;
5064
- const assignee = item.assignee ? chalk8.dim(` @${item.assignee.username}`) : "";
3319
+ const check = item.resolved ? chalk6.green("[x]") : chalk6.dim("[ ]");
3320
+ const name = item.resolved ? chalk6.dim(item.name) : item.name;
3321
+ const assignee = item.assignee ? chalk6.dim(` @${item.assignee.username}`) : "";
5065
3322
  lines.push(` ${check} ${name}${assignee}`);
5066
- lines.push(chalk8.dim(` item-id: ${item.id}`));
3323
+ lines.push(chalk6.dim(` item-id: ${item.id}`));
5067
3324
  }
5068
3325
  }
5069
3326
  return lines.join("\n");
@@ -5095,9 +3352,34 @@ async function deleteComment(config, commentId) {
5095
3352
  const client = new ClickUpClient(config);
5096
3353
  await client.deleteComment(commentId);
5097
3354
  }
3355
+ function matchesCommentText(commentText, match) {
3356
+ if (!match) return true;
3357
+ return commentText.toLowerCase().includes(match.toLowerCase());
3358
+ }
3359
+ async function deleteCommentByTaskSelection(config, taskId, options) {
3360
+ if (!options.mine) {
3361
+ throw new Error("Task-scoped comment deletion requires --mine");
3362
+ }
3363
+ const client = new ClickUpClient(config);
3364
+ const me = await client.getMe();
3365
+ const comments = await client.getTaskComments(taskId);
3366
+ const matches = comments.filter((comment2) => {
3367
+ const authorId = "id" in comment2.user ? comment2.user.id : void 0;
3368
+ return authorId === me.id && matchesCommentText(comment2.comment_text, options.match);
3369
+ });
3370
+ if (matches.length === 0) {
3371
+ throw new Error("No matching comments found for the current user");
3372
+ }
3373
+ if (matches.length > 1) {
3374
+ throw new Error("Multiple matching comments found - refine with --match or use a comment ID");
3375
+ }
3376
+ const comment = matches[0];
3377
+ await client.deleteComment(comment.id);
3378
+ return { commentId: comment.id, taskId };
3379
+ }
5098
3380
 
5099
3381
  // src/commands/replies.ts
5100
- import chalk9 from "chalk";
3382
+ import chalk7 from "chalk";
5101
3383
  async function getReplies(config, commentId) {
5102
3384
  const client = new ClickUpClient(config);
5103
3385
  return client.getThreadedComments(commentId);
@@ -5112,7 +3394,7 @@ function formatReplies(replies) {
5112
3394
  return replies.map((r) => {
5113
3395
  const user = r.user?.username ?? "Unknown";
5114
3396
  const date = formatTimestamp(Number(r.date));
5115
- return `${chalk9.bold(user)} ${chalk9.dim(date)}
3397
+ return `${chalk7.bold(user)} ${chalk7.dim(date)}
5116
3398
  ${r.comment_text}`;
5117
3399
  }).join("\n\n");
5118
3400
  }
@@ -5175,7 +3457,7 @@ function formatDocsMarkdown(docs) {
5175
3457
  }
5176
3458
 
5177
3459
  // src/commands/doc.ts
5178
- import chalk10 from "chalk";
3460
+ import chalk8 from "chalk";
5179
3461
  async function getDocInfo(config, docId) {
5180
3462
  const client = new ClickUpClient(config);
5181
3463
  const [doc, pages] = await Promise.all([
@@ -5187,14 +3469,14 @@ async function getDocInfo(config, docId) {
5187
3469
  function formatDocInfo(doc, pages, indent = 0) {
5188
3470
  const lines = [];
5189
3471
  if (indent === 0) {
5190
- lines.push(`${chalk10.bold(doc.name)} ${chalk10.dim(doc.id)}`);
3472
+ lines.push(`${chalk8.bold(doc.name)} ${chalk8.dim(doc.id)}`);
5191
3473
  if (pages.length === 0) {
5192
3474
  lines.push(" (no pages)");
5193
3475
  }
5194
3476
  }
5195
3477
  for (const page of pages) {
5196
3478
  const prefix = " ".repeat(indent + 1);
5197
- lines.push(`${prefix}${page.name} ${chalk10.dim(page.id)}`);
3479
+ lines.push(`${prefix}${page.name} ${chalk8.dim(page.id)}`);
5198
3480
  if (page.pages && page.pages.length > 0) {
5199
3481
  lines.push(formatDocInfo(doc, page.pages, indent + 1));
5200
3482
  }
@@ -5273,7 +3555,7 @@ async function deleteDocPage(config, docId, pageId) {
5273
3555
  }
5274
3556
 
5275
3557
  // src/commands/folders.ts
5276
- import chalk11 from "chalk";
3558
+ import chalk9 from "chalk";
5277
3559
  async function listFolders(config, spaceId, nameFilter) {
5278
3560
  const client = new ClickUpClient(config);
5279
3561
  const folders = await client.getFolders(spaceId);
@@ -5292,9 +3574,9 @@ async function listFolders(config, spaceId, nameFilter) {
5292
3574
  function formatFolders(folders) {
5293
3575
  if (folders.length === 0) return "No folders found";
5294
3576
  return folders.map((f) => {
5295
- const header = `${chalk11.bold(f.name)} ${chalk11.dim(f.id)}`;
3577
+ const header = `${chalk9.bold(f.name)} ${chalk9.dim(f.id)}`;
5296
3578
  if (f.lists.length === 0) return header;
5297
- const listLines = f.lists.map((l) => ` ${chalk11.dim(">")} ${l.name} ${chalk11.dim(l.id)}`);
3579
+ const listLines = f.lists.map((l) => ` ${chalk9.dim(">")} ${l.name} ${chalk9.dim(l.id)}`);
5298
3580
  return [header, ...listLines].join("\n");
5299
3581
  }).join("\n\n");
5300
3582
  }
@@ -5309,13 +3591,13 @@ function formatFoldersMarkdown(folders) {
5309
3591
  }
5310
3592
 
5311
3593
  // src/commands/time.ts
5312
- import chalk12 from "chalk";
3594
+ import chalk10 from "chalk";
5313
3595
  var TIME_COLUMNS = [
5314
3596
  { key: "task", label: "Task", maxWidth: 35 },
5315
3597
  { key: "duration", label: "Duration", maxWidth: 10 },
5316
3598
  { key: "date", label: "Date", maxWidth: 20 },
5317
3599
  { key: "description", label: "Description", maxWidth: 30 },
5318
- { key: "status", label: "", maxWidth: 10, format: (v) => v === "RUNNING" ? chalk12.green(v) : "" }
3600
+ { key: "status", label: "", maxWidth: 10, format: (v) => v === "RUNNING" ? chalk10.green(v) : "" }
5319
3601
  ];
5320
3602
  async function startTimer(config, taskId, description) {
5321
3603
  const client = new ClickUpClient(config);
@@ -5411,7 +3693,7 @@ function formatTimeEntriesMarkdown(entries) {
5411
3693
  }
5412
3694
 
5413
3695
  // src/commands/tags.ts
5414
- import chalk13 from "chalk";
3696
+ import chalk11 from "chalk";
5415
3697
  var TAG_COLUMNS = [
5416
3698
  { key: "name", label: "Name", maxWidth: 40 },
5417
3699
  { key: "fg", label: "FG", maxWidth: 10 },
@@ -5444,13 +3726,13 @@ function formatTags(tags) {
5444
3726
  if (tags.length === 0) return "No tags found";
5445
3727
  if (isTTY()) {
5446
3728
  const rows = tags.map((t) => ({
5447
- name: t.tag_bg ? chalk13.bgHex(t.tag_bg).hex(t.tag_fg || "#ffffff")(` ${t.name} `) : chalk13.bold(t.name),
3729
+ name: t.tag_bg ? chalk11.bgHex(t.tag_bg).hex(t.tag_fg || "#ffffff")(` ${t.name} `) : chalk11.bold(t.name),
5448
3730
  fg: t.tag_fg || "",
5449
3731
  bg: t.tag_bg || ""
5450
3732
  }));
5451
3733
  return formatTable(rows, TAG_COLUMNS);
5452
3734
  }
5453
- return tags.map((t) => chalk13.bold(t.name)).join(", ");
3735
+ return tags.map((t) => chalk11.bold(t.name)).join(", ");
5454
3736
  }
5455
3737
  function formatTagsMarkdown(tags) {
5456
3738
  if (tags.length === 0) return "No tags found";
@@ -5485,7 +3767,7 @@ function formatMembersMarkdown(members) {
5485
3767
  }
5486
3768
 
5487
3769
  // src/commands/fields.ts
5488
- import chalk14 from "chalk";
3770
+ import chalk12 from "chalk";
5489
3771
  var FIELD_COLUMNS = [
5490
3772
  { key: "id", label: "ID", maxWidth: 20 },
5491
3773
  { key: "name", label: "Name", maxWidth: 30 },
@@ -5494,7 +3776,7 @@ var FIELD_COLUMNS = [
5494
3776
  key: "required",
5495
3777
  label: "Required",
5496
3778
  maxWidth: 10,
5497
- format: (v) => v === "yes" ? chalk14.yellow(v) : chalk14.dim(v)
3779
+ format: (v) => v === "yes" ? chalk12.yellow(v) : chalk12.dim(v)
5498
3780
  },
5499
3781
  { key: "options", label: "Options", maxWidth: 40 }
5500
3782
  ];
@@ -5601,13 +3883,13 @@ async function bulkTag(config, tagName, taskIds, action) {
5601
3883
  }
5602
3884
 
5603
3885
  // src/commands/goals.ts
5604
- import chalk15 from "chalk";
3886
+ import chalk13 from "chalk";
5605
3887
  function colorProgress(value) {
5606
3888
  const num = parseInt(value, 10);
5607
3889
  if (isNaN(num)) return value;
5608
- if (num >= 75) return chalk15.green(value);
5609
- if (num >= 25) return chalk15.yellow(value);
5610
- return chalk15.red(value);
3890
+ if (num >= 75) return chalk13.green(value);
3891
+ if (num >= 25) return chalk13.yellow(value);
3892
+ return chalk13.red(value);
5611
3893
  }
5612
3894
  var GOAL_COLUMNS = [
5613
3895
  { key: "id", label: "ID", maxWidth: 15 },
@@ -5701,14 +3983,14 @@ function formatKeyResultsMarkdown(keyResults) {
5701
3983
  }
5702
3984
 
5703
3985
  // src/commands/task-types.ts
5704
- import chalk16 from "chalk";
3986
+ import chalk14 from "chalk";
5705
3987
  async function listTaskTypes(config) {
5706
3988
  const client = new ClickUpClient(config);
5707
3989
  return client.getCustomTaskTypes(config.teamId);
5708
3990
  }
5709
3991
  function formatTaskTypes(types) {
5710
3992
  if (types.length === 0) return "No custom task types";
5711
- return types.map((t) => `${chalk16.bold(t.name)} ${chalk16.dim(`(${t.id})`)}`).join("\n");
3993
+ return types.map((t) => `${chalk14.bold(t.name)} ${chalk14.dim(`(${t.id})`)}`).join("\n");
5712
3994
  }
5713
3995
  function formatTaskTypesMarkdown(types) {
5714
3996
  if (types.length === 0) return "No custom task types";
@@ -5716,14 +3998,14 @@ function formatTaskTypesMarkdown(types) {
5716
3998
  }
5717
3999
 
5718
4000
  // src/commands/templates.ts
5719
- import chalk17 from "chalk";
4001
+ import chalk15 from "chalk";
5720
4002
  async function listTemplates(config) {
5721
4003
  const client = new ClickUpClient(config);
5722
4004
  return client.getTaskTemplates(config.teamId);
5723
4005
  }
5724
4006
  function formatTemplates(templates) {
5725
4007
  if (templates.length === 0) return "No task templates";
5726
- return templates.map((t) => `${chalk17.bold(t.name)} ${chalk17.dim(`(${t.id})`)}`).join("\n");
4008
+ return templates.map((t) => `${chalk15.bold(t.name)} ${chalk15.dim(`(${t.id})`)}`).join("\n");
5727
4009
  }
5728
4010
  function formatTemplatesMarkdown(templates) {
5729
4011
  if (templates.length === 0) return "No task templates";
@@ -5731,14 +4013,14 @@ function formatTemplatesMarkdown(templates) {
5731
4013
  }
5732
4014
 
5733
4015
  // src/commands/list-templates.ts
5734
- import chalk18 from "chalk";
4016
+ import chalk16 from "chalk";
5735
4017
  async function listListTemplates(config) {
5736
4018
  const client = new ClickUpClient(config);
5737
4019
  return client.getListTemplates(config.teamId);
5738
4020
  }
5739
4021
  function formatListTemplates(templates) {
5740
4022
  if (templates.length === 0) return "No list templates";
5741
- return templates.map((t) => `${chalk18.bold(t.name)} ${chalk18.dim(`(${t.id})`)}`).join("\n");
4023
+ return templates.map((t) => `${chalk16.bold(t.name)} ${chalk16.dim(`(${t.id})`)}`).join("\n");
5742
4024
  }
5743
4025
  function formatListTemplatesMarkdown(templates) {
5744
4026
  if (templates.length === 0) return "No list templates";
@@ -5746,14 +4028,14 @@ function formatListTemplatesMarkdown(templates) {
5746
4028
  }
5747
4029
 
5748
4030
  // src/commands/folder-templates.ts
5749
- import chalk19 from "chalk";
4031
+ import chalk17 from "chalk";
5750
4032
  async function listFolderTemplates(config) {
5751
4033
  const client = new ClickUpClient(config);
5752
4034
  return client.getFolderTemplates(config.teamId);
5753
4035
  }
5754
4036
  function formatFolderTemplates(templates) {
5755
4037
  if (templates.length === 0) return "No folder templates";
5756
- return templates.map((t) => `${chalk19.bold(t.name)} ${chalk19.dim(`(${t.id})`)}`).join("\n");
4038
+ return templates.map((t) => `${chalk17.bold(t.name)} ${chalk17.dim(`(${t.id})`)}`).join("\n");
5757
4039
  }
5758
4040
  function formatFolderTemplatesMarkdown(templates) {
5759
4041
  if (templates.length === 0) return "No folder templates";
@@ -5776,7 +4058,7 @@ async function createListFromTemplate(config, name, opts) {
5776
4058
  }
5777
4059
 
5778
4060
  // src/commands/views.ts
5779
- import chalk20 from "chalk";
4061
+ import chalk18 from "chalk";
5780
4062
  async function listViews(config, id, container = "list") {
5781
4063
  const client = new ClickUpClient(config);
5782
4064
  if (container === "space") return client.getSpaceViews(id);
@@ -5787,7 +4069,7 @@ async function listViews(config, id, container = "list") {
5787
4069
  }
5788
4070
  function formatViews(views) {
5789
4071
  if (views.length === 0) return "No views";
5790
- return views.map((v) => `${chalk20.bold(v.name)} ${chalk20.dim(`(${v.id})`)} ${chalk20.dim(v.type)}`).join("\n");
4072
+ return views.map((v) => `${chalk18.bold(v.name)} ${chalk18.dim(`(${v.id})`)} ${chalk18.dim(v.type)}`).join("\n");
5791
4073
  }
5792
4074
  function formatViewsMarkdown(views) {
5793
4075
  if (views.length === 0) return "No views";
@@ -5795,20 +4077,20 @@ function formatViewsMarkdown(views) {
5795
4077
  }
5796
4078
 
5797
4079
  // src/commands/view.ts
5798
- import chalk21 from "chalk";
4080
+ import chalk19 from "chalk";
5799
4081
  async function getView(config, viewId) {
5800
4082
  const client = new ClickUpClient(config);
5801
4083
  return client.getView(viewId);
5802
4084
  }
5803
4085
  function formatView(view) {
5804
4086
  const lines = [];
5805
- lines.push(chalk21.bold.underline(view.name));
4087
+ lines.push(chalk19.bold.underline(view.name));
5806
4088
  lines.push("");
5807
- lines.push(` ${chalk21.bold("ID")} ${view.id}`);
5808
- lines.push(` ${chalk21.bold("Type")} ${view.type}`);
5809
- if (view.visibility) lines.push(` ${chalk21.bold("Visibility")} ${view.visibility}`);
5810
- if (view.date_created) lines.push(` ${chalk21.bold("Created")} ${formatDate(view.date_created)}`);
5811
- if (view.protected !== void 0) lines.push(` ${chalk21.bold("Protected")} ${view.protected}`);
4089
+ lines.push(` ${chalk19.bold("ID")} ${view.id}`);
4090
+ lines.push(` ${chalk19.bold("Type")} ${view.type}`);
4091
+ if (view.visibility) lines.push(` ${chalk19.bold("Visibility")} ${view.visibility}`);
4092
+ if (view.date_created) lines.push(` ${chalk19.bold("Created")} ${formatDate(view.date_created)}`);
4093
+ if (view.protected !== void 0) lines.push(` ${chalk19.bold("Protected")} ${view.protected}`);
5812
4094
  return lines.join("\n");
5813
4095
  }
5814
4096
  function formatViewMarkdown(view) {
@@ -5892,8 +4174,8 @@ async function deleteViewCommand(config, viewId, opts) {
5892
4174
  throw new Error("Destructive operation requires --confirm flag in non-interactive mode");
5893
4175
  }
5894
4176
  const view = await client.getView(viewId);
5895
- const { confirm: confirm3 } = await import("@inquirer/prompts");
5896
- const confirmed = await confirm3({
4177
+ const { confirm: confirm2 } = await import("@inquirer/prompts");
4178
+ const confirmed = await confirm2({
5897
4179
  message: `Delete view "${view.name}" (${viewId})? This cannot be undone.`,
5898
4180
  default: false
5899
4181
  });
@@ -6032,6 +4314,50 @@ async function copyStatusesFrom(client, sourceId) {
6032
4314
  }
6033
4315
  }
6034
4316
 
4317
+ // src/commands/favorite.ts
4318
+ var VALID_TYPES = /* @__PURE__ */ new Set([
4319
+ "sprint-folder",
4320
+ "space",
4321
+ "list",
4322
+ "folder",
4323
+ "view",
4324
+ "task"
4325
+ ]);
4326
+ function validateFavoriteType(type) {
4327
+ if (!VALID_TYPES.has(type)) {
4328
+ throw new Error(`Invalid favorite type: "${type}". Valid types: ${[...VALID_TYPES].join(", ")}`);
4329
+ }
4330
+ }
4331
+ function slugify(value) {
4332
+ return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
4333
+ }
4334
+ var FAVORITE_COLUMNS = [
4335
+ { key: "alias", label: "ALIAS", maxWidth: 30 },
4336
+ { key: "type", label: "TYPE", maxWidth: 20 },
4337
+ { key: "id", label: "ID", maxWidth: 20 },
4338
+ { key: "name", label: "NAME", maxWidth: 40 }
4339
+ ];
4340
+ function formatFavoritesTable(favorites) {
4341
+ const entries = Object.entries(favorites);
4342
+ if (entries.length === 0) return "No favorites saved";
4343
+ const rows = entries.map(([alias, entry]) => ({
4344
+ alias,
4345
+ type: entry.type,
4346
+ id: entry.id,
4347
+ name: entry.name ?? ""
4348
+ }));
4349
+ return formatTable(rows, FAVORITE_COLUMNS);
4350
+ }
4351
+ function formatFavoritesMarkdown(favorites) {
4352
+ const entries = Object.entries(favorites);
4353
+ if (entries.length === 0) return "No favorites saved";
4354
+ const lines = ["| Alias | Type | ID | Name |", "| --- | --- | --- | --- |"];
4355
+ for (const [alias, entry] of entries) {
4356
+ lines.push(`| ${alias} | ${entry.type} | ${entry.id} | ${entry.name ?? ""} |`);
4357
+ }
4358
+ return lines.join("\n");
4359
+ }
4360
+
6035
4361
  // src/index.ts
6036
4362
  var require2 = createRequire(import.meta.url);
6037
4363
  var { version } = require2("../package.json");
@@ -6201,6 +4527,10 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
6201
4527
  program.command("create").description("Create a new task").option("-l, --list <listId>", "Target list ID (auto-detected from --parent if omitted)").requiredOption("-n, --name <name>", "Task name").option("-d, --description <text>", "Task description (markdown supported)").option("-p, --parent <taskId>", "Parent task ID (list auto-detected from parent)").option("-s, --status <status>", "Initial status").option("--priority <level>", "Priority: urgent, high, normal, low (or 1-4)").option("--due-date <date>", "Due date (YYYY-MM-DD)").option("--start-date <date>", "Start date (YYYY-MM-DD)").option("--assignee <userId>", 'Assignee user ID or "me"').option("--tags <tags>", "Comma-separated tag names").option("--custom-item-id <id>", "Custom task type ID (use to create initiatives)").option("--time-estimate <duration>", 'Time estimate (e.g. "2h", "30m", "1h30m")').option("--template <id>", "Create from a task template").option("--json", "Force JSON output even in terminal").action(
6202
4528
  wrapAction(async (opts) => {
6203
4529
  const config = loadConfig(getProfileName());
4530
+ if (opts.list === "sprint:current") {
4531
+ const { resolveActiveSprintListId } = await import("./sprint-7S5UOUQX.js");
4532
+ opts.list = await resolveActiveSprintListId(config);
4533
+ }
6204
4534
  if (opts.assignee === "me") {
6205
4535
  const client = new ClickUpClient(config);
6206
4536
  opts.assignee = String(await resolveAssigneeId(client, "me"));
@@ -6280,16 +4610,23 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
6280
4610
  }
6281
4611
  )
6282
4612
  );
6283
- program.command("comment-delete <commentId>").description("Delete a comment").option("--json", "Force JSON output even in terminal").action(
6284
- wrapAction(async (commentId, opts) => {
6285
- const config = loadConfig(getProfileName());
6286
- await deleteComment(config, commentId);
6287
- if (shouldOutputJson(opts.json ?? false)) {
6288
- console.log(JSON.stringify({ success: true, commentId }, null, 2));
6289
- } else {
6290
- console.log(`Deleted comment ${commentId}`);
4613
+ program.command("comment-delete <commentId>").description("Delete a comment").option("--mine", "Delete one of my comments from the specified task instead of by comment ID").option("--match <text>", "Only match my task comments containing this text").option("--json", "Force JSON output even in terminal").action(
4614
+ wrapAction(
4615
+ async (commentId, opts) => {
4616
+ const config = loadConfig(getProfileName());
4617
+ const result = opts.mine || opts.match ? await deleteCommentByTaskSelection(config, commentId, {
4618
+ mine: opts.mine,
4619
+ match: opts.match
4620
+ }) : (await deleteComment(config, commentId), { commentId });
4621
+ if (shouldOutputJson(opts.json ?? false)) {
4622
+ console.log(JSON.stringify({ success: true, ...result }, null, 2));
4623
+ } else {
4624
+ console.log(
4625
+ result.taskId ? `Deleted comment ${result.commentId} from task ${result.taskId}` : `Deleted comment ${result.commentId}`
4626
+ );
4627
+ }
6291
4628
  }
6292
- })
4629
+ )
6293
4630
  );
6294
4631
  program.command("replies <commentId>").description("List threaded replies on a comment").option("--json", "Force JSON output even in terminal").action(
6295
4632
  wrapAction(async (commentId, opts) => {
@@ -6504,6 +4841,10 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
6504
4841
  program.command("move <taskId>").description("Add or remove a task from a list").option("--to <listId>", "Add task to this list").option("--remove <listId>", "Remove task from this list").option("--json", "Force JSON output even in terminal").action(
6505
4842
  wrapAction(async (taskId, opts) => {
6506
4843
  const config = loadConfig(getProfileName());
4844
+ if (opts.to === "sprint:current") {
4845
+ const { resolveActiveSprintListId } = await import("./sprint-7S5UOUQX.js");
4846
+ opts.to = await resolveActiveSprintListId(config);
4847
+ }
6507
4848
  const message = await moveTask(config, taskId, opts);
6508
4849
  if (shouldOutputJson(opts.json ?? false)) {
6509
4850
  console.log(
@@ -7482,6 +5823,49 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
7482
5823
  }
7483
5824
  })
7484
5825
  );
5826
+ const favoriteCmd = program.command("favorite").description("Manage local favorites (sprint folders, spaces, lists, etc.)");
5827
+ favoriteCmd.command("add <type> <id> [alias]").description("Add a favorite (types: sprint-folder, space, list, folder, view, task)").option("-n, --name <name>", "Display name for the favorite").option("--json", "Force JSON output even in terminal").action(
5828
+ wrapAction(
5829
+ async (type, id, alias, opts) => {
5830
+ validateFavoriteType(type);
5831
+ const resolvedAlias = alias ?? slugify(opts.name ?? id);
5832
+ const entry = { type, id, ...opts.name ? { name: opts.name } : {} };
5833
+ saveFavorite(resolvedAlias, entry, getProfileName());
5834
+ if (shouldOutputJson(opts.json ?? false)) {
5835
+ console.log(JSON.stringify({ alias: resolvedAlias, ...entry }, null, 2));
5836
+ } else {
5837
+ console.log(`Added favorite "${resolvedAlias}" (${type} ${id})`);
5838
+ }
5839
+ }
5840
+ )
5841
+ );
5842
+ favoriteCmd.command("remove <alias>").description("Remove a favorite by alias").option("--json", "Force JSON output even in terminal").action(
5843
+ wrapAction(async (alias, opts) => {
5844
+ deleteFavorite(alias, getProfileName());
5845
+ if (shouldOutputJson(opts.json ?? false)) {
5846
+ console.log(JSON.stringify({ success: true, alias }, null, 2));
5847
+ } else {
5848
+ console.log(`Removed favorite "${alias}"`);
5849
+ }
5850
+ })
5851
+ );
5852
+ favoriteCmd.command("list").description("List saved favorites").option("--type <type>", "Filter by entity type").option("--json", "Force JSON output even in terminal").action(
5853
+ wrapAction(async (opts) => {
5854
+ let favorites = getFavorites(getProfileName());
5855
+ if (opts.type) {
5856
+ favorites = Object.fromEntries(
5857
+ Object.entries(favorites).filter(([, entry]) => entry.type === opts.type)
5858
+ );
5859
+ }
5860
+ if (shouldOutputJson(opts.json ?? false)) {
5861
+ console.log(JSON.stringify(favorites, null, 2));
5862
+ } else if (isTTY()) {
5863
+ console.log(formatFavoritesTable(favorites));
5864
+ } else {
5865
+ console.log(formatFavoritesMarkdown(favorites));
5866
+ }
5867
+ })
5868
+ );
7485
5869
  const profileCmd = program.command("profile").description("Manage profiles");
7486
5870
  profileCmd.command("list").description("List all profiles").option("--json", "Force JSON output even in terminal").action(
7487
5871
  wrapAction(async (opts) => {
@@ -7500,7 +5884,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
7500
5884
  );
7501
5885
  profileCmd.command("add <name>").description("Add a new profile").action(
7502
5886
  wrapAction(async (name) => {
7503
- const { password: password2, select: select3 } = await import("@inquirer/prompts");
5887
+ const { password: password2, select: select2 } = await import("@inquirer/prompts");
7504
5888
  const apiToken = (await password2({ message: "ClickUp API token (pk_...):" })).trim();
7505
5889
  if (!apiToken.startsWith("pk_")) throw new Error("Token must start with pk_");
7506
5890
  const client = new ClickUpClient({ apiToken });
@@ -7515,7 +5899,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
7515
5899
  process.stdout.write(`Workspace: ${teams[0].name}
7516
5900
  `);
7517
5901
  } else {
7518
- teamId = await select3({
5902
+ teamId = await select2({
7519
5903
  message: "Select workspace:",
7520
5904
  choices: teams.map((t) => ({ name: t.name, value: t.id }))
7521
5905
  });
@@ -7553,7 +5937,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
7553
5937
  );
7554
5938
  configCmd.command("path").description("Print config file path").action(
7555
5939
  wrapAction(async () => {
7556
- console.log(configPath2());
5940
+ console.log(configPath());
7557
5941
  })
7558
5942
  );
7559
5943
  program.command("completion <shell>").description("Output shell completion script (bash, zsh, fish)").action(