@krodak/clickup-cli 1.19.4 → 1.19.5

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,55 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import {
3
- ClickUpClient,
4
- SPRINT_KEYWORDS,
5
- TASK_COLUMNS,
6
- addProfile,
7
- buildTypeMap,
8
- colorStatus,
9
- deleteFavorite,
10
- deleteFilter,
11
- fetchMyTasks,
12
- findRelatedSpaces,
13
- formatAssignConfirmation,
14
- formatCommentConfirmation,
15
- formatCommentsMarkdown,
16
- formatCreateConfirmation,
17
- formatDate,
18
- formatDateISO,
19
- formatDuration,
20
- formatGroupedTasksMarkdown,
21
- formatListsMarkdown,
22
- formatLongDuration,
23
- formatMarkdownTable,
24
- formatSpacesMarkdown,
25
- formatTable,
26
- formatTaskDetail,
27
- formatTaskDetailMarkdown,
28
- formatTimestamp,
29
- formatUpdateConfirmation,
30
- getConfigPath,
31
- getFavorites,
32
- getFilters,
33
- groupedTaskPicker,
34
- isCustomTaskId,
35
- isDoneStatus,
36
- isTTY,
37
- listProfiles,
38
- loadConfig,
39
- loadRawConfig,
40
- openUrl,
41
- parseSprintDates,
42
- printTasks,
43
- removeProfile,
44
- runSprintCommand,
45
- saveFavorite,
46
- saveFilter,
47
- setDefaultProfile,
48
- shouldOutputJson,
49
- showDetailsAndOpen,
50
- summarize,
51
- writeConfig
52
- } from "./chunk-HCGKTH6V.js";
53
2
 
54
3
  // src/index.ts
55
4
  import { realpathSync as realpathSync2 } from "fs";
@@ -58,6 +7,1783 @@ import { Command } from "commander";
58
7
  import { createRequire } from "module";
59
8
  import { fileURLToPath } from "url";
60
9
 
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 requestV3Array(path) {
130
+ const res = await fetch(`${BASE_URL_V3}${path}`, {
131
+ signal: AbortSignal.timeout(3e4),
132
+ headers: { Authorization: this.apiToken }
133
+ });
134
+ if (res.status === 204 || res.headers.get("content-length") === "0") {
135
+ if (!res.ok) {
136
+ throw new Error(`ClickUp API error ${res.status}: ${res.statusText}`);
137
+ }
138
+ return [];
139
+ }
140
+ let parsed;
141
+ try {
142
+ parsed = await res.json();
143
+ } catch {
144
+ throw new Error(`ClickUp API error ${res.status}: response was not valid JSON`);
145
+ }
146
+ if (!res.ok) {
147
+ let errMsg = res.statusText;
148
+ if (isRecord(parsed)) {
149
+ const raw = parsed.err ?? parsed.error ?? parsed.ECODE;
150
+ if (typeof raw === "string") errMsg = raw;
151
+ }
152
+ throw new Error(`ClickUp API error ${res.status}: ${errMsg}`);
153
+ }
154
+ if (!Array.isArray(parsed)) {
155
+ throw new Error("Unexpected API response: expected JSON array");
156
+ }
157
+ return parsed;
158
+ }
159
+ async getMe() {
160
+ if (this.meCache) return this.meCache;
161
+ const data = await this.request(
162
+ "/user"
163
+ );
164
+ const user = expectRecordField(data, "user", "user");
165
+ const timezone = typeof user.timezone === "string" && user.timezone ? user.timezone : void 0;
166
+ this.meCache = {
167
+ id: expectNumericField(user, "id", "user"),
168
+ username: expectStringField(user, "username", "user"),
169
+ ...timezone ? { timezone } : {}
170
+ };
171
+ return this.meCache;
172
+ }
173
+ async getUserTimezone() {
174
+ const me = await this.getMe();
175
+ return me.timezone;
176
+ }
177
+ async paginate(buildPath) {
178
+ const allTasks = [];
179
+ let page = 0;
180
+ let lastPage = false;
181
+ while (!lastPage && page < MAX_PAGES) {
182
+ const data = await this.request(buildPath(page));
183
+ const taskPage = expectPaginatedCollectionField(
184
+ data,
185
+ "tasks",
186
+ "task page"
187
+ );
188
+ allTasks.push(...taskPage.items);
189
+ lastPage = taskPage.lastPage;
190
+ page++;
191
+ }
192
+ if (page >= MAX_PAGES && !lastPage) {
193
+ process.stderr.write(
194
+ `Warning: reached maximum page limit (${MAX_PAGES}), results may be incomplete
195
+ `
196
+ );
197
+ }
198
+ return allTasks;
199
+ }
200
+ async getMyTasks(teamId, filters = {}) {
201
+ const baseParams = new URLSearchParams({
202
+ subtasks: String(filters.subtasks ?? true)
203
+ });
204
+ if (filters.includeClosed) baseParams.set("include_closed", "true");
205
+ if (!filters.all) {
206
+ const me = await this.getMe();
207
+ baseParams.append("assignees[]", String(me.id));
208
+ }
209
+ if (filters.assignees) {
210
+ for (const id of filters.assignees) baseParams.append("assignees[]", String(id));
211
+ }
212
+ for (const s of filters.statuses ?? []) baseParams.append("statuses[]", s);
213
+ for (const id of filters.listIds ?? []) baseParams.append("list_ids[]", id);
214
+ for (const id of filters.spaceIds ?? []) baseParams.append("space_ids[]", id);
215
+ for (const tag of filters.tags ?? []) baseParams.append("tags[]", tag);
216
+ if (filters.dueDateGt) baseParams.set("due_date_gt", String(filters.dueDateGt));
217
+ if (filters.dueDateLt) baseParams.set("due_date_lt", String(filters.dueDateLt));
218
+ if (filters.dateCreatedGt) baseParams.set("date_created_gt", String(filters.dateCreatedGt));
219
+ if (filters.dateCreatedLt) baseParams.set("date_created_lt", String(filters.dateCreatedLt));
220
+ if (filters.dateUpdatedGt) baseParams.set("date_updated_gt", String(filters.dateUpdatedGt));
221
+ if (filters.dateUpdatedLt) baseParams.set("date_updated_lt", String(filters.dateUpdatedLt));
222
+ if (filters.customFields?.length) {
223
+ baseParams.set("custom_fields", JSON.stringify(filters.customFields));
224
+ }
225
+ return this.paginate((page) => {
226
+ const params = new URLSearchParams(baseParams);
227
+ params.set("page", String(page));
228
+ return `/team/${teamId}/task?${params.toString()}`;
229
+ });
230
+ }
231
+ async updateTask(taskId, options) {
232
+ return this.request(this.taskPath(taskId), {
233
+ method: "PUT",
234
+ body: JSON.stringify(options)
235
+ });
236
+ }
237
+ async postComment(taskId, commentText, notifyAll) {
238
+ const body = { comment_text: commentText };
239
+ if (notifyAll) body.notify_all = true;
240
+ return this.request(this.taskPath(taskId, "/comment"), {
241
+ method: "POST",
242
+ body: JSON.stringify(body)
243
+ });
244
+ }
245
+ async getTaskComments(taskId) {
246
+ const data = await this.request(this.taskPath(taskId, "/comment"));
247
+ return readCollectionField(
248
+ data,
249
+ "comments",
250
+ "task comments"
251
+ );
252
+ }
253
+ async getTasksFromList(listId, params = {}, options = {}) {
254
+ return this.paginate((page) => {
255
+ const base = { subtasks: "true", page: String(page), ...params };
256
+ if (options.includeClosed) base["include_closed"] = "true";
257
+ const qs = new URLSearchParams(base).toString();
258
+ return `/list/${listId}/task?${qs}`;
259
+ });
260
+ }
261
+ async getTask(taskId) {
262
+ return this.request(this.taskPath(taskId, "?include_markdown_description=true"));
263
+ }
264
+ async getTimeInStatus(taskId) {
265
+ return this.request(this.taskPath(taskId, "/time_in_status"));
266
+ }
267
+ async createTask(listId, options) {
268
+ return this.request(`/list/${listId}/task`, {
269
+ method: "POST",
270
+ body: JSON.stringify(options)
271
+ });
272
+ }
273
+ async getTeams() {
274
+ const data = await this.request("/team");
275
+ return readCollectionField(data, "teams", "teams");
276
+ }
277
+ async getSpaceWithStatuses(spaceId) {
278
+ return this.request(`/space/${spaceId}`);
279
+ }
280
+ async getListWithStatuses(listId) {
281
+ return this.request(`/list/${listId}`);
282
+ }
283
+ async createSpace(teamId, name) {
284
+ return this.request(`/team/${teamId}/space`, {
285
+ method: "POST",
286
+ body: JSON.stringify({ name, multiple_assignees: true })
287
+ });
288
+ }
289
+ async getSpaces(teamId) {
290
+ const data = await this.request(`/team/${teamId}/space?archived=false`);
291
+ return readCollectionField(data, "spaces", "spaces");
292
+ }
293
+ async getCustomTaskTypes(teamId) {
294
+ const data = await this.request(
295
+ `/team/${teamId}/custom_item`
296
+ );
297
+ return readCollectionField(
298
+ data,
299
+ "custom_items",
300
+ "custom task types"
301
+ );
302
+ }
303
+ async createList(spaceId, name) {
304
+ return this.request(`/space/${spaceId}/list`, {
305
+ method: "POST",
306
+ body: JSON.stringify({ name })
307
+ });
308
+ }
309
+ async createFolderList(folderId, name) {
310
+ return this.request(`/folder/${folderId}/list`, {
311
+ method: "POST",
312
+ body: JSON.stringify({ name })
313
+ });
314
+ }
315
+ async updateList(listId, payload) {
316
+ return this.request(`/list/${listId}`, {
317
+ method: "PUT",
318
+ body: JSON.stringify(payload)
319
+ });
320
+ }
321
+ async createFolder(spaceId, name) {
322
+ return this.request(`/space/${spaceId}/folder`, {
323
+ method: "POST",
324
+ body: JSON.stringify({ name })
325
+ });
326
+ }
327
+ async getLists(spaceId) {
328
+ const data = await this.request(`/space/${spaceId}/list?archived=false`);
329
+ return readCollectionField(data, "lists", "space lists");
330
+ }
331
+ async getFolders(spaceId) {
332
+ const data = await this.request(
333
+ `/space/${spaceId}/folder?archived=false`
334
+ );
335
+ return readCollectionField(data, "folders", "space folders");
336
+ }
337
+ async getFolderLists(folderId) {
338
+ const data = await this.request(`/folder/${folderId}/list?archived=false`);
339
+ return readCollectionField(data, "lists", "folder lists");
340
+ }
341
+ async getListViews(listId) {
342
+ return this.request(`/list/${listId}/view`);
343
+ }
344
+ async getSpaceViews(spaceId) {
345
+ const data = await this.request(`/space/${spaceId}/view`);
346
+ return readCollectionField(data, "views", "views");
347
+ }
348
+ async getFolderViews(folderId) {
349
+ const data = await this.request(`/folder/${folderId}/view`);
350
+ return readCollectionField(data, "views", "views");
351
+ }
352
+ async getWorkspaceViews(teamId) {
353
+ const data = await this.request(`/team/${teamId}/view`);
354
+ return readCollectionField(data, "views", "views");
355
+ }
356
+ async getViewTasks(viewId) {
357
+ return this.paginate((page) => `/view/${viewId}/task?page=${page}`);
358
+ }
359
+ async getView(viewId) {
360
+ const data = await this.request(`/view/${viewId}`);
361
+ return expectRecordField(data, "view", "view");
362
+ }
363
+ async createListView(listId, payload) {
364
+ const data = await this.request(`/list/${listId}/view`, {
365
+ method: "POST",
366
+ body: JSON.stringify(payload)
367
+ });
368
+ return expectRecordField(data, "view", "view");
369
+ }
370
+ async updateView(viewId, payload) {
371
+ const data = await this.request(`/view/${viewId}`, {
372
+ method: "PUT",
373
+ body: JSON.stringify(payload)
374
+ });
375
+ return expectRecordField(data, "view", "view");
376
+ }
377
+ async deleteView(viewId) {
378
+ await this.request(`/view/${viewId}`, { method: "DELETE" });
379
+ }
380
+ async getListTemplates(teamId) {
381
+ const data = await this.request(`/team/${teamId}/list_template`);
382
+ return readCollectionField(
383
+ data,
384
+ "templates",
385
+ "list templates"
386
+ );
387
+ }
388
+ async getFolderTemplates(teamId) {
389
+ const data = await this.request(
390
+ `/team/${teamId}/folder_template`
391
+ );
392
+ return readCollectionField(
393
+ data,
394
+ "templates",
395
+ "folder templates"
396
+ );
397
+ }
398
+ async createListFromTemplate(containerId, templateId, name, containerType) {
399
+ return this.request(
400
+ `/${containerType}/${containerId}/list_template/${templateId}`,
401
+ { method: "POST", body: JSON.stringify({ name }) }
402
+ );
403
+ }
404
+ async addTaskToList(taskId, listId) {
405
+ await this.request(`/list/${listId}/task/${taskId}`, { method: "POST" });
406
+ }
407
+ async removeTaskFromList(taskId, listId) {
408
+ await this.request(`/list/${listId}/task/${taskId}`, { method: "DELETE" });
409
+ }
410
+ async setCustomFieldValue(taskId, fieldId, value) {
411
+ await this.request(this.taskPath(taskId, `/field/${fieldId}`), {
412
+ method: "POST",
413
+ body: JSON.stringify({ value })
414
+ });
415
+ }
416
+ async removeCustomFieldValue(taskId, fieldId) {
417
+ await this.request(this.taskPath(taskId, `/field/${fieldId}`), { method: "DELETE" });
418
+ }
419
+ async deleteTask(taskId) {
420
+ await this.request(this.taskPath(taskId), { method: "DELETE" });
421
+ }
422
+ async addTagToTask(taskId, tagName) {
423
+ await this.request(this.taskPath(taskId, `/tag/${encodeURIComponent(tagName)}`), {
424
+ method: "POST"
425
+ });
426
+ }
427
+ async removeTagFromTask(taskId, tagName) {
428
+ await this.request(this.taskPath(taskId, `/tag/${encodeURIComponent(tagName)}`), {
429
+ method: "DELETE"
430
+ });
431
+ }
432
+ async addDependency(taskId, opts) {
433
+ const body = {};
434
+ if (opts.dependsOn) body.depends_on = opts.dependsOn;
435
+ if (opts.dependencyOf) body.dependency_of = opts.dependencyOf;
436
+ await this.request(this.taskPath(taskId, "/dependency"), {
437
+ method: "POST",
438
+ body: JSON.stringify(body)
439
+ });
440
+ }
441
+ async deleteDependency(taskId, opts) {
442
+ const params = new URLSearchParams();
443
+ if (opts.dependsOn) params.set("depends_on", opts.dependsOn);
444
+ if (opts.dependencyOf) params.set("dependency_of", opts.dependencyOf);
445
+ await this.request(this.taskPath(taskId, `/dependency?${params.toString()}`), {
446
+ method: "DELETE"
447
+ });
448
+ }
449
+ async updateComment(commentId, text, resolved) {
450
+ const body = { comment_text: text };
451
+ if (resolved !== void 0) body.resolved = resolved;
452
+ await this.request(`/comment/${commentId}`, {
453
+ method: "PUT",
454
+ body: JSON.stringify(body)
455
+ });
456
+ }
457
+ async deleteComment(commentId) {
458
+ await this.request(`/comment/${commentId}`, { method: "DELETE" });
459
+ }
460
+ async getThreadedComments(commentId) {
461
+ const data = await this.request(`/comment/${commentId}/reply`);
462
+ return readCollectionField(
463
+ data,
464
+ "comments",
465
+ "threaded comments"
466
+ );
467
+ }
468
+ async createThreadedComment(commentId, text, notifyAll) {
469
+ const body = { comment_text: text };
470
+ if (notifyAll) body.notify_all = true;
471
+ await this.request(`/comment/${commentId}/reply`, {
472
+ method: "POST",
473
+ body: JSON.stringify(body)
474
+ });
475
+ }
476
+ async addTaskLink(taskId, linksTo) {
477
+ await this.request(this.taskPath(taskId, `/link/${linksTo}`), {
478
+ method: "POST"
479
+ });
480
+ }
481
+ async deleteTaskLink(taskId, linksTo) {
482
+ await this.request(this.taskPath(taskId, `/link/${linksTo}`), {
483
+ method: "DELETE"
484
+ });
485
+ }
486
+ async getListCustomFields(listId) {
487
+ const data = await this.request(`/list/${listId}/field`);
488
+ return readCollectionField(
489
+ data,
490
+ "fields",
491
+ "list custom fields"
492
+ );
493
+ }
494
+ async createChecklist(taskId, name) {
495
+ const data = await this.request(this.taskPath(taskId, "/checklist"), {
496
+ method: "POST",
497
+ body: JSON.stringify({ name })
498
+ });
499
+ return expectRecordField(
500
+ data,
501
+ "checklist",
502
+ "checklist"
503
+ );
504
+ }
505
+ async deleteChecklist(checklistId) {
506
+ await this.request(`/checklist/${checklistId}`, { method: "DELETE" });
507
+ }
508
+ async createChecklistItem(checklistId, name) {
509
+ const data = await this.request(
510
+ `/checklist/${checklistId}/checklist_item`,
511
+ { method: "POST", body: JSON.stringify({ name }) }
512
+ );
513
+ return expectRecordField(
514
+ data,
515
+ "checklist",
516
+ "checklist"
517
+ );
518
+ }
519
+ async editChecklistItem(checklistId, checklistItemId, updates) {
520
+ const data = await this.request(
521
+ `/checklist/${checklistId}/checklist_item/${checklistItemId}`,
522
+ { method: "PUT", body: JSON.stringify(updates) }
523
+ );
524
+ return expectRecordField(
525
+ data,
526
+ "checklist",
527
+ "checklist"
528
+ );
529
+ }
530
+ async deleteChecklistItem(checklistId, checklistItemId) {
531
+ await this.request(
532
+ `/checklist/${checklistId}/checklist_item/${checklistItemId}`,
533
+ { method: "DELETE" }
534
+ );
535
+ }
536
+ async startTimeEntry(teamId, taskId, description) {
537
+ const body = {
538
+ tid: taskId,
539
+ start: Date.now(),
540
+ duration: -1
541
+ };
542
+ if (description) body.description = description;
543
+ const data = await this.request(
544
+ `/team/${teamId}/time_entries/start${this.customIdQueryParams(taskId)}`,
545
+ {
546
+ method: "POST",
547
+ body: JSON.stringify(body)
548
+ }
549
+ );
550
+ return data.data;
551
+ }
552
+ async stopTimeEntry(teamId) {
553
+ const data = await this.request(`/team/${teamId}/time_entries/stop`, {
554
+ method: "POST"
555
+ });
556
+ return data.data;
557
+ }
558
+ async getRunningTimeEntry(teamId) {
559
+ const data = await this.request(
560
+ `/team/${teamId}/time_entries/current`
561
+ );
562
+ return data.data ?? null;
563
+ }
564
+ async createTimeEntry(teamId, taskId, duration, opts) {
565
+ const start = opts?.start ?? Date.now() - duration;
566
+ const body = {
567
+ tid: taskId,
568
+ start,
569
+ duration
570
+ };
571
+ if (opts?.description) body.description = opts.description;
572
+ const data = await this.request(
573
+ `/team/${teamId}/time_entries${this.customIdQueryParams(taskId)}`,
574
+ {
575
+ method: "POST",
576
+ body: JSON.stringify(body)
577
+ }
578
+ );
579
+ return data.data;
580
+ }
581
+ async getTimeEntries(teamId, opts) {
582
+ const params = new URLSearchParams();
583
+ if (opts?.startDate != null) params.set("start_date", String(opts.startDate));
584
+ if (opts?.endDate != null) params.set("end_date", String(opts.endDate));
585
+ if (opts?.spaceId) params.set("space_id", opts.spaceId);
586
+ if (opts?.listId) params.set("list_id", opts.listId);
587
+ if (opts?.assigneeId) params.set("assignee", opts.assigneeId);
588
+ const query = params.toString();
589
+ const url = `/team/${teamId}/time_entries${query ? `?${query}` : ""}`;
590
+ const data = await this.request(url);
591
+ const entries = readCollectionField(
592
+ data,
593
+ "data",
594
+ "time entries"
595
+ );
596
+ if (opts?.taskId) {
597
+ return entries.filter((e) => e.task?.id === opts.taskId);
598
+ }
599
+ return entries;
600
+ }
601
+ async updateTimeEntry(teamId, timeEntryId, updates) {
602
+ const data = await this.request(
603
+ `/team/${teamId}/time_entries/${timeEntryId}`,
604
+ { method: "PUT", body: JSON.stringify(updates) }
605
+ );
606
+ return data.data;
607
+ }
608
+ async getSpaceTags(spaceId) {
609
+ const data = await this.request(`/space/${spaceId}/tag`);
610
+ return readCollectionField(data, "tags", "space tags");
611
+ }
612
+ async createSpaceTag(spaceId, name, fg, bg) {
613
+ await this.request(`/space/${spaceId}/tag`, {
614
+ method: "POST",
615
+ body: JSON.stringify({
616
+ tag: { name, tag_fg: fg ?? "#000000", tag_bg: bg ?? "#04A9F4" }
617
+ })
618
+ });
619
+ }
620
+ async deleteSpaceTag(spaceId, tagName) {
621
+ await this.request(
622
+ `/space/${spaceId}/tag/${encodeURIComponent(tagName)}`,
623
+ { method: "DELETE" }
624
+ );
625
+ }
626
+ async getWorkspaceMembers(teamId) {
627
+ const data = await this.request("/team");
628
+ const team = readCollectionField(
629
+ data,
630
+ "teams",
631
+ "workspace members"
632
+ ).find((t) => t.id === teamId);
633
+ return team?.members?.map((m) => m.user) ?? [];
634
+ }
635
+ async deleteTimeEntry(teamId, timeEntryId) {
636
+ await this.request(`/team/${teamId}/time_entries/${timeEntryId}`, {
637
+ method: "DELETE"
638
+ });
639
+ }
640
+ async createTaskAttachment(taskId, filePath) {
641
+ const { readFile } = await import("fs/promises");
642
+ const { basename: basename2 } = await import("path");
643
+ const fileBuffer = await readFile(filePath);
644
+ const fileName = basename2(filePath);
645
+ const formData = new FormData();
646
+ formData.append("attachment", new Blob([fileBuffer]), fileName);
647
+ const res = await fetch(`${BASE_URL}${this.taskPath(taskId, "/attachment")}`, {
648
+ method: "POST",
649
+ headers: { Authorization: this.apiToken },
650
+ body: formData,
651
+ signal: AbortSignal.timeout(6e4)
652
+ });
653
+ if (!res.ok) {
654
+ let msg;
655
+ try {
656
+ const data2 = await res.json();
657
+ msg = data2.err ?? `HTTP ${res.status}`;
658
+ } catch {
659
+ msg = `HTTP ${res.status}`;
660
+ }
661
+ throw new Error(`ClickUp API error ${res.status}: ${msg}`);
662
+ }
663
+ let data;
664
+ try {
665
+ data = await res.json();
666
+ } catch {
667
+ throw new Error(`ClickUp API error ${res.status}: response was not valid JSON`);
668
+ }
669
+ return data;
670
+ }
671
+ async getDocs(workspaceId) {
672
+ const data = await this.requestV3(`/workspaces/${workspaceId}/docs`);
673
+ return readCollectionField(data, "docs", "docs");
674
+ }
675
+ async getDocPage(workspaceId, docId, pageId) {
676
+ return this.requestV3(
677
+ `/workspaces/${workspaceId}/docs/${docId}/pages/${pageId}?content_format=text/md`
678
+ );
679
+ }
680
+ async createDoc(workspaceId, title, content, parentId) {
681
+ const body = { title };
682
+ if (content) body.content = content;
683
+ if (parentId) {
684
+ body.parent_id = parentId;
685
+ body.parent_type = "doc";
686
+ }
687
+ return this.requestV3(`/workspaces/${workspaceId}/docs`, {
688
+ method: "POST",
689
+ body: JSON.stringify(body)
690
+ });
691
+ }
692
+ async createDocPage(workspaceId, docId, name, content, parentPageId) {
693
+ const body = { name, content_format: "text/md" };
694
+ if (content) body.content = content;
695
+ if (parentPageId) body.parent_page_id = parentPageId;
696
+ return this.requestV3(`/workspaces/${workspaceId}/docs/${docId}/pages`, {
697
+ method: "POST",
698
+ body: JSON.stringify(body)
699
+ });
700
+ }
701
+ async editDocPage(workspaceId, docId, pageId, updates) {
702
+ return this.requestV3(`/workspaces/${workspaceId}/docs/${docId}/pages/${pageId}`, {
703
+ method: "PUT",
704
+ body: JSON.stringify(updates)
705
+ });
706
+ }
707
+ async getDoc(workspaceId, docId) {
708
+ return this.requestV3(`/workspaces/${workspaceId}/docs/${docId}`);
709
+ }
710
+ async getDocPageListing(workspaceId, docId) {
711
+ return this.requestV3Array(`/workspaces/${workspaceId}/docs/${docId}/pages`);
712
+ }
713
+ async getDocPages(workspaceId, docId) {
714
+ return this.requestV3Array(
715
+ `/workspaces/${workspaceId}/docs/${docId}/pages?content_format=text/md`
716
+ );
717
+ }
718
+ async getGoals(teamId) {
719
+ const data = await this.request(`/team/${teamId}/goal`);
720
+ return readCollectionField(data, "goals", "goals");
721
+ }
722
+ async createGoal(teamId, name, opts) {
723
+ const body = { name, multiple_owners: true };
724
+ if (opts?.description) body.description = opts.description;
725
+ if (opts?.dueDate != null) body.due_date = opts.dueDate;
726
+ if (opts?.color) body.color = opts.color;
727
+ const data = await this.request(`/team/${teamId}/goal`, {
728
+ method: "POST",
729
+ body: JSON.stringify(body)
730
+ });
731
+ return data.goal;
732
+ }
733
+ async updateGoal(goalId, updates) {
734
+ const data = await this.request(`/goal/${goalId}`, {
735
+ method: "PUT",
736
+ body: JSON.stringify(updates)
737
+ });
738
+ return data.goal;
739
+ }
740
+ async getKeyResults(goalId) {
741
+ const data = await this.request(`/goal/${goalId}`);
742
+ return data.goal?.key_results ?? [];
743
+ }
744
+ async createKeyResult(goalId, name, type, stepsEnd) {
745
+ const data = await this.request(`/goal/${goalId}/key_result`, {
746
+ method: "POST",
747
+ body: JSON.stringify({
748
+ name,
749
+ type,
750
+ steps_start: 0,
751
+ steps_end: stepsEnd,
752
+ unit: type === "number" ? "items" : "%"
753
+ })
754
+ });
755
+ return data.key_result;
756
+ }
757
+ async updateKeyResult(keyResultId, updates) {
758
+ const data = await this.request(`/key_result/${keyResultId}`, {
759
+ method: "PUT",
760
+ body: JSON.stringify(updates)
761
+ });
762
+ return data.key_result;
763
+ }
764
+ async deleteGoal(goalId) {
765
+ await this.request(`/goal/${goalId}`, { method: "DELETE" });
766
+ }
767
+ async deleteKeyResult(keyResultId) {
768
+ await this.request(`/key_result/${keyResultId}`, { method: "DELETE" });
769
+ }
770
+ async deleteDoc(workspaceId, docId) {
771
+ await this.requestV3(`/workspaces/${workspaceId}/docs/${docId}`, {
772
+ method: "DELETE"
773
+ });
774
+ }
775
+ async deleteDocPage(workspaceId, docId, pageId) {
776
+ await this.requestV3(
777
+ `/workspaces/${workspaceId}/docs/${docId}/pages/${pageId}`,
778
+ { method: "DELETE" }
779
+ );
780
+ }
781
+ async updateSpaceTag(spaceId, tagName, updates) {
782
+ await this.request(
783
+ `/space/${spaceId}/tag/${encodeURIComponent(tagName)}`,
784
+ {
785
+ method: "PUT",
786
+ body: JSON.stringify({
787
+ tag: {
788
+ name: updates.name,
789
+ tag_fg: updates.tag_fg ?? "#000000",
790
+ tag_bg: updates.tag_bg ?? "#04A9F4"
791
+ }
792
+ })
793
+ }
794
+ );
795
+ }
796
+ async getTaskTemplates(teamId) {
797
+ const data = await this.request(
798
+ `/team/${teamId}/taskTemplate?page=0`
799
+ );
800
+ return readCollectionField(
801
+ data,
802
+ "templates",
803
+ "task templates"
804
+ );
805
+ }
806
+ async createTaskFromTemplate(listId, templateId, name) {
807
+ return this.request(`/list/${listId}/taskTemplate/${templateId}`, {
808
+ method: "POST",
809
+ body: JSON.stringify({ name })
810
+ });
811
+ }
812
+ async createCustomField(teamId, name, type, opts) {
813
+ const typeConfig = {};
814
+ if (opts?.options?.length) {
815
+ typeConfig.options = opts.options.map((optName, i) => ({
816
+ name: optName,
817
+ orderindex: i
818
+ }));
819
+ }
820
+ const body = {
821
+ name,
822
+ type,
823
+ type_config: typeConfig,
824
+ description: opts?.description ?? "",
825
+ required: opts?.required ?? false,
826
+ pinned: false,
827
+ hide_from_guests: false,
828
+ required_on_subtasks: false,
829
+ private: false,
830
+ permission_level: null,
831
+ members: [],
832
+ groups: []
833
+ };
834
+ const data = await this.request(
835
+ `/field?workspace_id=${teamId}`,
836
+ { method: "POST", body: JSON.stringify(body) }
837
+ );
838
+ return data.data;
839
+ }
840
+ };
841
+
842
+ // src/config.ts
843
+ import fs from "fs";
844
+ import { homedir } from "os";
845
+ import { join } from "path";
846
+ function isRecord2(value) {
847
+ return typeof value === "object" && value !== null && !Array.isArray(value);
848
+ }
849
+ function readConfigString(parsed, key, path, strict) {
850
+ const value = parsed[key];
851
+ if (value === void 0) return void 0;
852
+ if (typeof value !== "string") {
853
+ if (strict) {
854
+ throw new Error(`Config field ${key} must be a string in ${path}.`);
855
+ }
856
+ return void 0;
857
+ }
858
+ const trimmed = value.trim();
859
+ return trimmed || void 0;
860
+ }
861
+ function parseConfigFile(raw, path, strictFields, strictRoot = strictFields) {
862
+ let parsed;
863
+ try {
864
+ parsed = JSON.parse(raw);
865
+ } catch {
866
+ if (strictRoot) {
867
+ throw new Error(`Config file at ${path} contains invalid JSON. Please check the file syntax.`);
868
+ }
869
+ return {};
870
+ }
871
+ if (!isRecord2(parsed)) {
872
+ if (strictRoot) {
873
+ throw new Error(`Config file at ${path} must contain a JSON object.`);
874
+ }
875
+ return {};
876
+ }
877
+ const apiToken = readConfigString(parsed, "apiToken", path, strictFields);
878
+ const teamId = readConfigString(parsed, "teamId", path, strictFields);
879
+ const sprintFolderId = readConfigString(parsed, "sprintFolderId", path, strictFields);
880
+ return {
881
+ ...apiToken ? { apiToken } : {},
882
+ ...teamId ? { teamId } : {},
883
+ ...sprintFolderId ? { sprintFolderId } : {}
884
+ };
885
+ }
886
+ function trimConfigValue(value) {
887
+ const trimmed = value?.trim();
888
+ return trimmed || void 0;
889
+ }
890
+ function configDir() {
891
+ const xdg = process.env.XDG_CONFIG_HOME;
892
+ if (xdg) return join(xdg, "cup");
893
+ return join(homedir(), ".config", "cup");
894
+ }
895
+ function legacyConfigDir() {
896
+ const xdg = process.env.XDG_CONFIG_HOME;
897
+ if (xdg) return join(xdg, "cu");
898
+ return join(homedir(), ".config", "cu");
899
+ }
900
+ var migrationChecked = false;
901
+ function migrateFromLegacy() {
902
+ if (migrationChecked) return;
903
+ migrationChecked = true;
904
+ const legacy = legacyConfigDir();
905
+ const current = configDir();
906
+ if (fs.existsSync(join(legacy, "config.json")) && !fs.existsSync(join(current, "config.json"))) {
907
+ fs.mkdirSync(current, { recursive: true, mode: 448 });
908
+ fs.copyFileSync(join(legacy, "config.json"), join(current, "config.json"));
909
+ }
910
+ }
911
+ function configPath() {
912
+ return join(configDir(), "config.json");
913
+ }
914
+ function migrateToMultiProfile(parsed, filePath) {
915
+ if (typeof parsed.apiToken === "string" && !parsed.profiles) {
916
+ const profile = {};
917
+ const token = trimConfigValue(parsed.apiToken);
918
+ if (token) profile.apiToken = token;
919
+ const team = typeof parsed.teamId === "string" ? trimConfigValue(parsed.teamId) : void 0;
920
+ if (team) profile.teamId = team;
921
+ const sprint = typeof parsed.sprintFolderId === "string" ? trimConfigValue(parsed.sprintFolderId) : void 0;
922
+ if (sprint) profile.sprintFolderId = sprint;
923
+ const migrated = {
924
+ defaultProfile: "default",
925
+ profiles: { default: profile }
926
+ };
927
+ const dir = configDir();
928
+ if (!fs.existsSync(dir)) {
929
+ fs.mkdirSync(dir, { recursive: true, mode: 448 });
930
+ }
931
+ fs.writeFileSync(join(dir, "config.json"), JSON.stringify(migrated, null, 2) + "\n", {
932
+ encoding: "utf-8",
933
+ mode: 384
934
+ });
935
+ return migrated;
936
+ }
937
+ if (isRecord2(parsed.profiles)) {
938
+ const profiles = {};
939
+ for (const [name, value] of Object.entries(parsed.profiles)) {
940
+ if (isRecord2(value)) {
941
+ const p = {};
942
+ if (typeof value.apiToken === "string" && value.apiToken.trim())
943
+ p.apiToken = value.apiToken.trim();
944
+ if (typeof value.teamId === "string" && value.teamId.trim()) p.teamId = value.teamId.trim();
945
+ if (typeof value.sprintFolderId === "string" && value.sprintFolderId.trim())
946
+ p.sprintFolderId = value.sprintFolderId.trim();
947
+ if (isRecord2(value.filters)) p.filters = value.filters;
948
+ if (isRecord2(value.favorites)) p.favorites = value.favorites;
949
+ profiles[name] = p;
950
+ }
951
+ }
952
+ return {
953
+ defaultProfile: typeof parsed.defaultProfile === "string" ? parsed.defaultProfile : "",
954
+ profiles
955
+ };
956
+ }
957
+ throw new Error(`Config file at ${filePath} has unrecognized format.`);
958
+ }
959
+ function parseRawConfig(filePath) {
960
+ const raw = fs.readFileSync(filePath, "utf-8");
961
+ let parsed;
962
+ try {
963
+ parsed = JSON.parse(raw);
964
+ } catch {
965
+ throw new Error(
966
+ `Config file at ${filePath} contains invalid JSON. Please check the file syntax.`
967
+ );
968
+ }
969
+ if (!isRecord2(parsed)) {
970
+ throw new Error(`Config file at ${filePath} must contain a JSON object.`);
971
+ }
972
+ return { parsed, raw };
973
+ }
974
+ function isOldFormat(parsed) {
975
+ return typeof parsed.apiToken === "string" && !parsed.profiles;
976
+ }
977
+ function loadConfig(profileName) {
978
+ migrateFromLegacy();
979
+ const envToken = process.env.CU_API_TOKEN?.trim();
980
+ const envTeamId = process.env.CU_TEAM_ID?.trim();
981
+ if (envToken && envTeamId) {
982
+ if (!envToken.startsWith("pk_")) {
983
+ throw new Error("CU_API_TOKEN must start with pk_.");
984
+ }
985
+ return { apiToken: envToken, teamId: envTeamId };
986
+ }
987
+ const path = configPath();
988
+ if (!fs.existsSync(path)) {
989
+ if (envToken || envTeamId) {
990
+ throw new Error("Both CU_API_TOKEN and CU_TEAM_ID must be set, or run: cup init");
991
+ }
992
+ throw new Error("Config missing required field: apiToken.\nSet CU_API_TOKEN or run: cup init");
993
+ }
994
+ const { parsed } = parseRawConfig(path);
995
+ if (isOldFormat(parsed)) {
996
+ const fileConfig = parseConfigFile(JSON.stringify(parsed), path, true);
997
+ const apiToken2 = envToken ?? fileConfig.apiToken;
998
+ if (!apiToken2) {
999
+ throw new Error("Config missing required field: apiToken.\nSet CU_API_TOKEN or run: cup init");
1000
+ }
1001
+ if (!apiToken2.startsWith("pk_")) {
1002
+ throw new Error("Config apiToken must start with pk_. The configured token does not.");
1003
+ }
1004
+ const teamId2 = envTeamId ?? fileConfig.teamId;
1005
+ if (!teamId2) {
1006
+ throw new Error("Config missing required field: teamId.\nSet CU_TEAM_ID or run: cup init");
1007
+ }
1008
+ migrateToMultiProfile(parsed, path);
1009
+ return {
1010
+ apiToken: apiToken2,
1011
+ teamId: teamId2,
1012
+ ...fileConfig.sprintFolderId ? { sprintFolderId: fileConfig.sprintFolderId } : {}
1013
+ };
1014
+ }
1015
+ const multi = loadMultiProfileConfig();
1016
+ const resolvedProfile = profileName ?? process.env.CU_PROFILE?.trim() ?? multi.defaultProfile;
1017
+ if (!resolvedProfile) {
1018
+ throw new Error("No default profile set. Run: cup profile use <name>");
1019
+ }
1020
+ const profile = multi.profiles[resolvedProfile];
1021
+ if (!profile) {
1022
+ const available = Object.keys(multi.profiles).join(", ");
1023
+ throw new Error(`Profile "${resolvedProfile}" not found. Available: ${available}`);
1024
+ }
1025
+ const apiToken = envToken ?? profile.apiToken?.trim();
1026
+ if (!apiToken) {
1027
+ throw new Error(
1028
+ `Profile "${resolvedProfile}" missing apiToken. Run: cup profile add ${resolvedProfile}`
1029
+ );
1030
+ }
1031
+ if (!apiToken.startsWith("pk_")) {
1032
+ throw new Error("Config apiToken must start with pk_. The configured token does not.");
1033
+ }
1034
+ const teamId = envTeamId ?? profile.teamId?.trim();
1035
+ if (!teamId) {
1036
+ throw new Error(`Profile "${resolvedProfile}" missing teamId.`);
1037
+ }
1038
+ return {
1039
+ apiToken,
1040
+ teamId,
1041
+ ...profile.sprintFolderId ? { sprintFolderId: profile.sprintFolderId } : {}
1042
+ };
1043
+ }
1044
+ function loadMultiProfileConfig() {
1045
+ migrateFromLegacy();
1046
+ const path = configPath();
1047
+ if (!fs.existsSync(path)) {
1048
+ return { defaultProfile: "", profiles: {} };
1049
+ }
1050
+ let parsed;
1051
+ try {
1052
+ const raw = fs.readFileSync(path, "utf-8");
1053
+ parsed = JSON.parse(raw);
1054
+ } catch {
1055
+ return { defaultProfile: "", profiles: {} };
1056
+ }
1057
+ if (!isRecord2(parsed)) return { defaultProfile: "", profiles: {} };
1058
+ if (isOldFormat(parsed)) {
1059
+ return migrateToMultiProfile(parsed, path);
1060
+ }
1061
+ return migrateToMultiProfile(parsed, path);
1062
+ }
1063
+ function saveMultiProfileConfig(config) {
1064
+ const dir = configDir();
1065
+ if (!fs.existsSync(dir)) {
1066
+ fs.mkdirSync(dir, { recursive: true, mode: 448 });
1067
+ }
1068
+ fs.writeFileSync(join(dir, "config.json"), JSON.stringify(config, null, 2) + "\n", {
1069
+ encoding: "utf-8",
1070
+ mode: 384
1071
+ });
1072
+ }
1073
+ function addProfile(name, profile) {
1074
+ const multi = loadMultiProfileConfig();
1075
+ multi.profiles[name] = profile;
1076
+ if (!multi.defaultProfile) multi.defaultProfile = name;
1077
+ saveMultiProfileConfig(multi);
1078
+ }
1079
+ function removeProfile(name) {
1080
+ const multi = loadMultiProfileConfig();
1081
+ if (!multi.profiles[name]) {
1082
+ throw new Error(`Profile "${name}" not found.`);
1083
+ }
1084
+ const keys = Object.keys(multi.profiles);
1085
+ if (keys.length <= 1) {
1086
+ throw new Error("Cannot remove the last profile.");
1087
+ }
1088
+ delete multi.profiles[name];
1089
+ if (multi.defaultProfile === name) {
1090
+ multi.defaultProfile = Object.keys(multi.profiles)[0] ?? "";
1091
+ }
1092
+ saveMultiProfileConfig(multi);
1093
+ }
1094
+ function setDefaultProfile(name) {
1095
+ const multi = loadMultiProfileConfig();
1096
+ if (!multi.profiles[name]) {
1097
+ const available = Object.keys(multi.profiles).join(", ");
1098
+ throw new Error(`Profile "${name}" not found. Available: ${available}`);
1099
+ }
1100
+ multi.defaultProfile = name;
1101
+ saveMultiProfileConfig(multi);
1102
+ }
1103
+ function listProfiles() {
1104
+ const multi = loadMultiProfileConfig();
1105
+ return Object.entries(multi.profiles).map(([name, profile]) => ({
1106
+ name,
1107
+ isDefault: name === multi.defaultProfile,
1108
+ teamId: profile.teamId
1109
+ }));
1110
+ }
1111
+ function loadRawConfig(profileName) {
1112
+ migrateFromLegacy();
1113
+ const path = configPath();
1114
+ if (!fs.existsSync(path)) return {};
1115
+ let parsed;
1116
+ try {
1117
+ const raw = fs.readFileSync(path, "utf-8");
1118
+ parsed = JSON.parse(raw);
1119
+ } catch {
1120
+ return {};
1121
+ }
1122
+ if (!isRecord2(parsed)) {
1123
+ throw new Error(`Config file at ${path} must contain a JSON object.`);
1124
+ }
1125
+ if (isOldFormat(parsed)) {
1126
+ return parseConfigFile(JSON.stringify(parsed), path, false, true);
1127
+ }
1128
+ const multi = migrateToMultiProfile(parsed, path);
1129
+ const name = profileName || multi.defaultProfile || "default";
1130
+ return multi.profiles[name] ?? {};
1131
+ }
1132
+ function getConfigPath() {
1133
+ migrateFromLegacy();
1134
+ return configPath();
1135
+ }
1136
+ function getFilters(profileName) {
1137
+ const multi = loadMultiProfileConfig();
1138
+ const name = profileName ?? process.env.CU_PROFILE?.trim() ?? multi.defaultProfile;
1139
+ const profile = name ? multi.profiles[name] ?? {} : {};
1140
+ return profile.filters ?? {};
1141
+ }
1142
+ function saveFilter(name, entry, profileName) {
1143
+ const multi = loadMultiProfileConfig();
1144
+ const pName = profileName ?? process.env.CU_PROFILE?.trim() ?? multi.defaultProfile ?? "default";
1145
+ const profile = multi.profiles[pName] ?? {};
1146
+ const filters = { ...profile.filters ?? {}, [name]: entry };
1147
+ multi.profiles[pName] = { ...profile, filters };
1148
+ if (!multi.defaultProfile) multi.defaultProfile = pName;
1149
+ saveMultiProfileConfig(multi);
1150
+ }
1151
+ function deleteFilter(name, profileName) {
1152
+ const multi = loadMultiProfileConfig();
1153
+ const pName = profileName ?? process.env.CU_PROFILE?.trim() ?? multi.defaultProfile ?? "default";
1154
+ const profile = multi.profiles[pName] ?? {};
1155
+ const filters = { ...profile.filters ?? {} };
1156
+ if (!(name in filters)) {
1157
+ throw new Error(`Filter "${name}" not found.`);
1158
+ }
1159
+ delete filters[name];
1160
+ multi.profiles[pName] = { ...profile, filters };
1161
+ saveMultiProfileConfig(multi);
1162
+ }
1163
+ function getFavorites(profileName) {
1164
+ const multi = loadMultiProfileConfig();
1165
+ const name = profileName ?? process.env.CU_PROFILE?.trim() ?? multi.defaultProfile;
1166
+ const profile = name ? multi.profiles[name] ?? {} : {};
1167
+ return profile.favorites ?? {};
1168
+ }
1169
+ function saveFavorite(alias, entry, profileName) {
1170
+ const multi = loadMultiProfileConfig();
1171
+ const pName = profileName ?? process.env.CU_PROFILE?.trim() ?? multi.defaultProfile ?? "default";
1172
+ const profile = multi.profiles[pName] ?? {};
1173
+ const favorites = { ...profile.favorites ?? {}, [alias]: entry };
1174
+ multi.profiles[pName] = { ...profile, favorites };
1175
+ if (!multi.defaultProfile) multi.defaultProfile = pName;
1176
+ saveMultiProfileConfig(multi);
1177
+ }
1178
+ function deleteFavorite(alias, profileName) {
1179
+ const multi = loadMultiProfileConfig();
1180
+ const pName = profileName ?? process.env.CU_PROFILE?.trim() ?? multi.defaultProfile ?? "default";
1181
+ const profile = multi.profiles[pName] ?? {};
1182
+ const favorites = { ...profile.favorites ?? {} };
1183
+ if (!(alias in favorites)) {
1184
+ throw new Error(`Favorite "${alias}" not found.`);
1185
+ }
1186
+ delete favorites[alias];
1187
+ multi.profiles[pName] = { ...profile, favorites };
1188
+ saveMultiProfileConfig(multi);
1189
+ }
1190
+ function writeConfig(config, profileName) {
1191
+ const multi = loadMultiProfileConfig();
1192
+ const name = profileName || multi.defaultProfile || "default";
1193
+ const apiToken = trimConfigValue(config.apiToken) ?? void 0;
1194
+ const teamId = trimConfigValue(config.teamId) ?? void 0;
1195
+ const sprintFolderId = trimConfigValue(config.sprintFolderId);
1196
+ const normalizedConfig = {
1197
+ ...apiToken ? { apiToken } : {},
1198
+ ...teamId ? { teamId } : {},
1199
+ ...sprintFolderId ? { sprintFolderId } : {}
1200
+ };
1201
+ multi.profiles[name] = {
1202
+ ...multi.profiles[name],
1203
+ ...normalizedConfig
1204
+ };
1205
+ if (!multi.defaultProfile) multi.defaultProfile = name;
1206
+ saveMultiProfileConfig(multi);
1207
+ }
1208
+
1209
+ // src/date.ts
1210
+ function formatDate(ms) {
1211
+ return new Date(Number(ms)).toLocaleDateString("en-US", {
1212
+ month: "short",
1213
+ day: "numeric",
1214
+ year: "numeric"
1215
+ });
1216
+ }
1217
+ function formatTimestamp(ms) {
1218
+ return new Date(Number(ms)).toLocaleString("en-US", {
1219
+ month: "short",
1220
+ day: "numeric",
1221
+ hour: "numeric",
1222
+ minute: "2-digit"
1223
+ });
1224
+ }
1225
+ function formatDuration(ms) {
1226
+ const totalMinutes = Math.round(Math.abs(ms) / 6e4);
1227
+ const hours = Math.floor(totalMinutes / 60);
1228
+ const minutes = totalMinutes % 60;
1229
+ if (hours > 0 && minutes > 0) return `${hours}h ${minutes}m`;
1230
+ if (hours > 0) return `${hours}h`;
1231
+ return `${minutes}m`;
1232
+ }
1233
+ function formatLongDuration(ms) {
1234
+ const totalMinutes = Math.round(Math.abs(ms) / 6e4);
1235
+ if (totalMinutes === 0) return "< 1m";
1236
+ const days = Math.floor(totalMinutes / 1440);
1237
+ const hours = Math.floor(totalMinutes % 1440 / 60);
1238
+ const minutes = totalMinutes % 60;
1239
+ const parts = [];
1240
+ if (days > 0) parts.push(`${days}d`);
1241
+ if (hours > 0) parts.push(`${hours}h`);
1242
+ if (minutes > 0) parts.push(`${minutes}m`);
1243
+ return parts.join(" ");
1244
+ }
1245
+ function formatDateISO(ms) {
1246
+ const d = new Date(Number(ms));
1247
+ const year = d.getUTCFullYear();
1248
+ const month = String(d.getUTCMonth() + 1).padStart(2, "0");
1249
+ const day = String(d.getUTCDate()).padStart(2, "0");
1250
+ return `${year}-${month}-${day}`;
1251
+ }
1252
+
1253
+ // src/output.ts
1254
+ import chalk from "chalk";
1255
+ function isTTY() {
1256
+ return Boolean(process.stdout.isTTY);
1257
+ }
1258
+ function shouldOutputJson(forceJson) {
1259
+ if (forceJson) return true;
1260
+ if (process.env["CU_OUTPUT"] === "json") return true;
1261
+ return false;
1262
+ }
1263
+ function cell(value, width) {
1264
+ if (value.length > width) return value.slice(0, width - 1) + "\u2026";
1265
+ return value.padEnd(width);
1266
+ }
1267
+ function computeWidths(rows, columns) {
1268
+ return columns.map((col) => {
1269
+ const headerLen = col.label.length;
1270
+ const maxDataLen = rows.reduce((max, row) => {
1271
+ const val = String(row[col.key] ?? "");
1272
+ return Math.max(max, val.length);
1273
+ }, 0);
1274
+ const natural = Math.max(headerLen, maxDataLen);
1275
+ return col.maxWidth ? Math.min(natural, col.maxWidth) : natural;
1276
+ });
1277
+ }
1278
+ function formatTable(rows, columns) {
1279
+ const widths = computeWidths(rows, columns);
1280
+ const header = columns.map((c, i) => cell(c.label, widths[i])).join(" ");
1281
+ const divider = chalk.dim("-".repeat(header.replace(/\x1b\[[0-9;]*m/g, "").length));
1282
+ const lines = [chalk.bold(header), divider];
1283
+ for (const row of rows) {
1284
+ lines.push(
1285
+ columns.map((c, i) => {
1286
+ const raw = String(row[c.key] ?? "");
1287
+ const width = widths[i];
1288
+ const truncated = raw.length > width ? raw.slice(0, width - 1) + "\u2026" : raw;
1289
+ const padding = " ".repeat(Math.max(0, width - truncated.length));
1290
+ return c.format ? c.format(truncated, row) + padding : truncated + padding;
1291
+ }).join(" ")
1292
+ );
1293
+ }
1294
+ return lines.join("\n");
1295
+ }
1296
+ function colorStatus(status) {
1297
+ const lower = status.toLowerCase();
1298
+ if (lower.includes("done") || lower.includes("complete") || lower.includes("closed"))
1299
+ return chalk.green(status);
1300
+ if (lower.includes("progress") || lower.includes("review") || lower.includes("active"))
1301
+ return chalk.yellow(status);
1302
+ if (lower.includes("block") || lower.includes("stuck")) return chalk.red(status);
1303
+ return chalk.dim(status);
1304
+ }
1305
+ function colorPriority(priority) {
1306
+ const lower = priority.toLowerCase();
1307
+ if (lower === "urgent") return chalk.red(priority);
1308
+ if (lower === "high") return chalk.yellow(priority);
1309
+ if (lower === "normal") return priority;
1310
+ if (lower === "low") return chalk.dim(priority);
1311
+ return priority;
1312
+ }
1313
+ function colorDueDate(dateStr, rawTimestamp) {
1314
+ if (!dateStr) return dateStr;
1315
+ if (rawTimestamp) {
1316
+ const ts = Number(rawTimestamp);
1317
+ if (Number.isFinite(ts) && ts < Date.now()) return chalk.red(dateStr);
1318
+ }
1319
+ return dateStr;
1320
+ }
1321
+ var TASK_COLUMNS = [
1322
+ { key: "id", label: "ID" },
1323
+ { key: "name", label: "NAME", maxWidth: 60 },
1324
+ { key: "status", label: "STATUS", maxWidth: 20, format: (v) => colorStatus(v) },
1325
+ { key: "priority", label: "PRIORITY", maxWidth: 10, format: (v) => colorPriority(v) },
1326
+ {
1327
+ key: "due_date",
1328
+ label: "DUE",
1329
+ maxWidth: 15,
1330
+ format: (v, row) => v ? colorDueDate(v, row.dueRaw) : ""
1331
+ },
1332
+ { key: "list", label: "LIST" }
1333
+ ];
1334
+
1335
+ // src/markdown.ts
1336
+ function escapeCell(value) {
1337
+ return value.replace(/\|/g, "\\|");
1338
+ }
1339
+ function formatMarkdownTable(rows, columns) {
1340
+ const header = "| " + columns.map((c) => c.label).join(" | ") + " |";
1341
+ const divider = "| " + columns.map(() => "---").join(" | ") + " |";
1342
+ const lines = [header, divider];
1343
+ for (const row of rows) {
1344
+ const cells = columns.map((c) => escapeCell(String(row[c.key] ?? "")));
1345
+ lines.push("| " + cells.join(" | ") + " |");
1346
+ }
1347
+ return lines.join("\n");
1348
+ }
1349
+ var TASK_MD_COLUMNS = [
1350
+ { key: "id", label: "ID" },
1351
+ { key: "name", label: "Name" },
1352
+ { key: "status", label: "Status" },
1353
+ { key: "priority", label: "Priority" },
1354
+ { key: "due_date", label: "Due" },
1355
+ { key: "list", label: "List" }
1356
+ ];
1357
+ function formatTasksMarkdown(tasks) {
1358
+ if (tasks.length === 0) return "No tasks found.";
1359
+ return formatMarkdownTable(tasks, TASK_MD_COLUMNS);
1360
+ }
1361
+ function formatCommentsMarkdown(comments) {
1362
+ if (comments.length === 0) return "No comments found.";
1363
+ return comments.map((c) => `**${c.user}** (${formatDateISO(c.date)})
1364
+
1365
+ ${c.text}`).join("\n\n---\n\n");
1366
+ }
1367
+ var LIST_MD_COLUMNS = [
1368
+ { key: "id", label: "ID" },
1369
+ { key: "name", label: "Name" },
1370
+ { key: "folder", label: "Folder" }
1371
+ ];
1372
+ function formatListsMarkdown(lists) {
1373
+ if (lists.length === 0) return "No lists found.";
1374
+ return formatMarkdownTable(lists, LIST_MD_COLUMNS);
1375
+ }
1376
+ var SPACE_MD_COLUMNS = [
1377
+ { key: "id", label: "ID" },
1378
+ { key: "name", label: "Name" }
1379
+ ];
1380
+ function formatSpacesMarkdown(spaces) {
1381
+ if (spaces.length === 0) return "No spaces found.";
1382
+ return formatMarkdownTable(spaces, SPACE_MD_COLUMNS);
1383
+ }
1384
+ function formatGroupedTasksMarkdown(groups) {
1385
+ const sections = groups.filter((g) => g.tasks.length > 0).map((g) => `## ${g.label}
1386
+
1387
+ ${formatMarkdownTable(g.tasks, TASK_MD_COLUMNS)}`);
1388
+ if (sections.length === 0) return "No tasks found.";
1389
+ return sections.join("\n\n");
1390
+ }
1391
+ function formatTaskDetailMarkdown(task) {
1392
+ const lines = [`# ${task.name}`, ""];
1393
+ const isInitiative = (task.custom_item_id ?? 0) !== 0;
1394
+ const fields = [
1395
+ ["ID", task.id],
1396
+ ["Status", task.status.status],
1397
+ ["Type", isInitiative ? "initiative" : "task"],
1398
+ ["List", task.list.name],
1399
+ ["URL", task.url],
1400
+ [
1401
+ "Assignees",
1402
+ task.assignees.length > 0 ? task.assignees.map((a) => a.username).join(", ") : void 0
1403
+ ],
1404
+ ["Priority", task.priority?.priority],
1405
+ ["Parent", task.parent ?? void 0],
1406
+ ["Start Date", task.start_date ? formatDateISO(task.start_date) : void 0],
1407
+ ["Due Date", task.due_date ? formatDateISO(task.due_date) : void 0],
1408
+ [
1409
+ "Time Estimate",
1410
+ task.time_estimate != null && task.time_estimate > 0 ? formatDuration(task.time_estimate) : void 0
1411
+ ],
1412
+ [
1413
+ "Time Spent",
1414
+ task.time_spent != null && task.time_spent > 0 ? formatDuration(task.time_spent) : void 0
1415
+ ],
1416
+ ["Tags", task.tags && task.tags.length > 0 ? task.tags.map((t) => t.name).join(", ") : void 0],
1417
+ [
1418
+ "Lists",
1419
+ task.locations && task.locations.length > 0 ? task.locations.map((l) => l.name).join(", ") : void 0
1420
+ ],
1421
+ ["Created", task.date_created ? formatDateISO(task.date_created) : void 0],
1422
+ ["Updated", task.date_updated ? formatDateISO(task.date_updated) : void 0]
1423
+ ];
1424
+ for (const [label, value] of fields) {
1425
+ if (value != null && value !== "") {
1426
+ lines.push(`**${label}:** ${value}`);
1427
+ }
1428
+ }
1429
+ const descriptionContent = task.markdown_content ?? task.description;
1430
+ if (descriptionContent) {
1431
+ lines.push("", "## Description", "", descriptionContent);
1432
+ }
1433
+ if (task.checklists?.length) {
1434
+ lines.push("", "## Checklists", "");
1435
+ for (const cl of task.checklists) {
1436
+ const resolved = cl.items.filter((i) => i.resolved).length;
1437
+ lines.push(`### ${cl.name} (${resolved}/${cl.items.length})`, "");
1438
+ for (const item of cl.items) {
1439
+ lines.push(`- [${item.resolved ? "x" : " "}] ${item.name}`);
1440
+ }
1441
+ lines.push("");
1442
+ }
1443
+ }
1444
+ if (task.attachments?.length) {
1445
+ lines.push("", "## Attachments", "");
1446
+ for (const att of task.attachments) {
1447
+ lines.push(`- [${att.title}](${att.url})`);
1448
+ }
1449
+ }
1450
+ if (task.dependencies?.length) {
1451
+ lines.push("", "## Dependencies", "");
1452
+ for (const dep of task.dependencies) {
1453
+ const direction = dep.depends_on === task.id ? "blocks" : "depends on";
1454
+ const otherId = dep.depends_on === task.id ? dep.task_id : dep.depends_on;
1455
+ lines.push(`- ${direction} ${otherId}`);
1456
+ }
1457
+ }
1458
+ if (task.linked_tasks?.length) {
1459
+ lines.push("", "## Linked Tasks", "");
1460
+ for (const lt of task.linked_tasks) {
1461
+ lines.push(`- ${lt.task_id}`);
1462
+ }
1463
+ }
1464
+ return lines.join("\n");
1465
+ }
1466
+ function formatUpdateConfirmation(id, name) {
1467
+ return `Updated task ${id}: "${name}"`;
1468
+ }
1469
+ function formatCreateConfirmation(id, name, url) {
1470
+ return `Created task ${id}: "${name}" - ${url}`;
1471
+ }
1472
+ function formatCommentConfirmation(id) {
1473
+ return `Comment posted (id: ${id})`;
1474
+ }
1475
+ function formatAssignConfirmation(taskId, opts) {
1476
+ const parts = [];
1477
+ if (opts.to) parts.push(`Assigned ${opts.to} to ${taskId}`);
1478
+ if (opts.remove) parts.push(`Removed ${opts.remove} from ${taskId}`);
1479
+ return parts.join("; ");
1480
+ }
1481
+
1482
+ // src/interactive.ts
1483
+ import { execFileSync } from "child_process";
1484
+ import { checkbox, confirm, Separator } from "@inquirer/prompts";
1485
+ import chalk2 from "chalk";
1486
+ function openUrl(url) {
1487
+ switch (process.platform) {
1488
+ case "darwin":
1489
+ execFileSync("open", [url]);
1490
+ break;
1491
+ case "linux":
1492
+ execFileSync("xdg-open", [url]);
1493
+ break;
1494
+ case "win32":
1495
+ execFileSync("cmd", ["/c", "start", "", url]);
1496
+ break;
1497
+ default:
1498
+ process.stderr.write(`Cannot open browser on ${process.platform}. Visit: ${url}
1499
+ `);
1500
+ }
1501
+ }
1502
+ function descriptionPreview(text, maxLines = 3) {
1503
+ const lines = text.split("\n").filter((l) => l.trim().length > 0);
1504
+ const preview = lines.slice(0, maxLines);
1505
+ const result = preview.map((l) => ` ${chalk2.dim(l.length > 100 ? l.slice(0, 99) + "\u2026" : l)}`).join("\n");
1506
+ if (lines.length > maxLines)
1507
+ return result + `
1508
+ ${chalk2.dim(`... (${lines.length - maxLines} more lines)`)}`;
1509
+ return result;
1510
+ }
1511
+ function stringifyFieldValue(value) {
1512
+ if (typeof value === "string") return value;
1513
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
1514
+ return JSON.stringify(value);
1515
+ }
1516
+ function formatCustomFieldValue(field) {
1517
+ if (field.value === null || field.value === void 0) return null;
1518
+ const options = field.type_config?.options;
1519
+ switch (field.type) {
1520
+ case "drop_down": {
1521
+ if (!options) return stringifyFieldValue(field.value);
1522
+ const match = options.find((o) => o.id === Number(field.value));
1523
+ return match?.name ?? stringifyFieldValue(field.value);
1524
+ }
1525
+ case "labels": {
1526
+ if (!Array.isArray(field.value) || !options) return stringifyFieldValue(field.value);
1527
+ const names = field.value.map((id) => options.find((o) => o.id === id)?.name).filter((n) => n !== void 0);
1528
+ return names.length > 0 ? names.join(", ") : null;
1529
+ }
1530
+ case "date": {
1531
+ const ts = Number(field.value);
1532
+ if (!Number.isFinite(ts)) return stringifyFieldValue(field.value);
1533
+ return formatDate(String(ts));
1534
+ }
1535
+ case "checkbox":
1536
+ return field.value === true || field.value === "true" ? "Yes" : "No";
1537
+ default:
1538
+ return stringifyFieldValue(field.value);
1539
+ }
1540
+ }
1541
+ function formatTaskDetail(task) {
1542
+ const lines = [];
1543
+ const isInitiative = (task.custom_item_id ?? 0) !== 0;
1544
+ const typeLabel = isInitiative ? "initiative" : "task";
1545
+ lines.push(chalk2.bold.underline(task.name));
1546
+ lines.push("");
1547
+ const fields = [
1548
+ ["ID", task.id],
1549
+ ["Status", task.status?.status ? colorStatus(task.status.status) : void 0],
1550
+ ["Type", typeLabel],
1551
+ ["List", task.list?.name],
1552
+ [
1553
+ "Assignees",
1554
+ task.assignees?.length ? task.assignees.map((a) => a.username).join(", ") : void 0
1555
+ ],
1556
+ ["Priority", task.priority?.priority ? colorPriority(task.priority.priority) : void 0],
1557
+ ["Start", task.start_date ? formatDate(task.start_date) : void 0],
1558
+ ["Due", task.due_date ? colorDueDate(formatDate(task.due_date), task.due_date) : void 0],
1559
+ ["Estimate", task.time_estimate ? formatDuration(task.time_estimate) : void 0],
1560
+ ["Tracked", task.time_spent ? formatDuration(task.time_spent) : void 0],
1561
+ ["Tags", task.tags?.length ? task.tags.map((t) => t.name).join(", ") : void 0],
1562
+ ["Lists", task.locations?.length ? task.locations.map((l) => l.name).join(", ") : void 0],
1563
+ ["Parent", task.parent || void 0],
1564
+ ["URL", task.url]
1565
+ ];
1566
+ const maxLabel = Math.max(...fields.filter(([, v]) => v).map(([k]) => k.length));
1567
+ for (const [label, value] of fields) {
1568
+ if (!value) continue;
1569
+ lines.push(` ${chalk2.bold(label.padEnd(maxLabel + 1))} ${value}`);
1570
+ }
1571
+ if (task.custom_fields?.length) {
1572
+ const formatted = task.custom_fields.map((f) => [f.name, formatCustomFieldValue(f)]).filter((pair) => pair[1] !== null);
1573
+ if (formatted.length > 0) {
1574
+ lines.push("");
1575
+ lines.push(chalk2.bold("Custom Fields"));
1576
+ for (const [name, value] of formatted) {
1577
+ lines.push(` ${chalk2.bold(name)} ${value}`);
1578
+ }
1579
+ }
1580
+ }
1581
+ if (task.checklists?.length) {
1582
+ lines.push("");
1583
+ lines.push(chalk2.bold("Checklists"));
1584
+ for (const cl of task.checklists) {
1585
+ const resolved = cl.items.filter((i) => i.resolved).length;
1586
+ lines.push(` ${chalk2.bold(cl.name)} (${resolved}/${cl.items.length})`);
1587
+ for (const item of cl.items) {
1588
+ const check = item.resolved ? chalk2.green("[x]") : chalk2.dim("[ ]");
1589
+ lines.push(` ${check} ${item.name}`);
1590
+ }
1591
+ }
1592
+ }
1593
+ if (task.attachments?.length) {
1594
+ lines.push("");
1595
+ lines.push(chalk2.bold("Attachments"));
1596
+ for (const att of task.attachments) {
1597
+ lines.push(` ${att.title} ${chalk2.dim(att.url)}`);
1598
+ }
1599
+ }
1600
+ if (task.dependencies?.length) {
1601
+ lines.push("");
1602
+ lines.push(chalk2.bold("Dependencies"));
1603
+ for (const dep of task.dependencies) {
1604
+ const direction = dep.depends_on === task.id ? "blocks" : "depends on";
1605
+ const otherId = dep.depends_on === task.id ? dep.task_id : dep.depends_on;
1606
+ lines.push(` ${direction} ${chalk2.dim(otherId)}`);
1607
+ }
1608
+ }
1609
+ if (task.linked_tasks?.length) {
1610
+ lines.push("");
1611
+ lines.push(chalk2.bold("Linked Tasks"));
1612
+ for (const lt of task.linked_tasks) {
1613
+ lines.push(` ${chalk2.dim(lt.task_id)}`);
1614
+ }
1615
+ }
1616
+ if (task.text_content?.trim()) {
1617
+ lines.push("");
1618
+ lines.push(descriptionPreview(task.text_content));
1619
+ }
1620
+ return lines.join("\n");
1621
+ }
1622
+ function formatChoiceName(task) {
1623
+ const id = task.id.padEnd(12);
1624
+ const name = task.name.length > 50 ? task.name.slice(0, 49) + "\u2026" : task.name.padEnd(50);
1625
+ const status = colorStatus(task.status);
1626
+ const priority = task.priority !== "none" ? colorPriority(task.priority) : "";
1627
+ return `${id} ${name} ${status}${priority ? " " + priority : ""}`;
1628
+ }
1629
+ async function interactiveTaskPicker(tasks) {
1630
+ if (tasks.length === 0) return [];
1631
+ const selected = await checkbox({
1632
+ message: `${tasks.length} task(s) found. Select to view details / open in browser:`,
1633
+ choices: tasks.map((t) => ({
1634
+ name: formatChoiceName(t),
1635
+ value: t.id
1636
+ })),
1637
+ pageSize: 20
1638
+ });
1639
+ return tasks.filter((t) => selected.includes(t.id));
1640
+ }
1641
+ async function groupedTaskPicker(groups) {
1642
+ const allTasks = groups.flatMap((g) => g.tasks);
1643
+ const totalCount = allTasks.length;
1644
+ if (totalCount === 0) return [];
1645
+ const choices = [];
1646
+ for (const group of groups) {
1647
+ if (group.tasks.length === 0) continue;
1648
+ choices.push(new Separator(chalk2.bold(`${group.label} (${group.tasks.length})`)));
1649
+ for (const task of group.tasks) {
1650
+ choices.push({ name: formatChoiceName(task), value: task.id });
1651
+ }
1652
+ }
1653
+ const selected = await checkbox({
1654
+ message: `${totalCount} task(s) found. Select to view details / open in browser:`,
1655
+ choices,
1656
+ pageSize: 20
1657
+ });
1658
+ return allTasks.filter((t) => selected.includes(t.id));
1659
+ }
1660
+ async function showDetailsAndOpen(tasks, fetchTask) {
1661
+ if (tasks.length === 0) return;
1662
+ const separator = chalk2.dim("\u2500".repeat(60));
1663
+ for (let i = 0; i < tasks.length; i++) {
1664
+ const task = tasks[i];
1665
+ if (i > 0) {
1666
+ console.log("");
1667
+ console.log(separator);
1668
+ }
1669
+ console.log("");
1670
+ if (fetchTask) {
1671
+ const full = await fetchTask(task.id);
1672
+ console.log(formatTaskDetail(full));
1673
+ } else {
1674
+ const fallback = {
1675
+ id: task.id,
1676
+ name: task.name,
1677
+ status: { status: task.status, color: "" },
1678
+ custom_item_id: task.task_type === "initiative" ? 1 : 0,
1679
+ assignees: [],
1680
+ url: task.url,
1681
+ list: { id: "", name: task.list },
1682
+ parent: task.parent
1683
+ };
1684
+ console.log(formatTaskDetail(fallback));
1685
+ }
1686
+ }
1687
+ const urls = tasks.map((t) => t.url);
1688
+ console.log("");
1689
+ const shouldOpen = await confirm({
1690
+ message: `Open ${urls.length} task(s) in browser?`,
1691
+ default: true
1692
+ });
1693
+ if (shouldOpen) {
1694
+ for (const url of urls) {
1695
+ openUrl(url);
1696
+ }
1697
+ }
1698
+ }
1699
+
1700
+ // src/commands/tasks.ts
1701
+ var DONE_PATTERNS = ["done", "complete", "closed"];
1702
+ function isDoneStatus(status) {
1703
+ const lower = status.toLowerCase();
1704
+ return DONE_PATTERNS.some((p) => lower.includes(p));
1705
+ }
1706
+ function formatDueDate(ms) {
1707
+ if (!ms) return "";
1708
+ return formatDate(ms);
1709
+ }
1710
+ function resolveTaskType(task, typeMap) {
1711
+ const id = task.custom_item_id ?? 0;
1712
+ if (id === 0) return "task";
1713
+ return typeMap.get(id) ?? `type_${id}`;
1714
+ }
1715
+ function summarize(task, typeMap) {
1716
+ return {
1717
+ id: task.id,
1718
+ name: task.name,
1719
+ status: task.status.status,
1720
+ task_type: resolveTaskType(task, typeMap ?? /* @__PURE__ */ new Map()),
1721
+ priority: task.priority?.priority ?? "none",
1722
+ due_date: formatDueDate(task.due_date),
1723
+ ...task.due_date ? { dueRaw: task.due_date } : {},
1724
+ list: task.list.name,
1725
+ url: task.url,
1726
+ ...task.parent ? { parent: task.parent } : {}
1727
+ };
1728
+ }
1729
+ function buildTypeMap(types) {
1730
+ const map = /* @__PURE__ */ new Map();
1731
+ for (const t of types) {
1732
+ map.set(t.id, t.name);
1733
+ }
1734
+ return map;
1735
+ }
1736
+ function resolveTypeFilter(typeFilter, typeMap) {
1737
+ if (typeFilter === "task") return 0;
1738
+ const asNum = Number(typeFilter);
1739
+ if (Number.isFinite(asNum)) return asNum;
1740
+ const lower = typeFilter.toLowerCase();
1741
+ for (const [id, name] of typeMap) {
1742
+ if (name.toLowerCase() === lower) return id;
1743
+ }
1744
+ const available = ["task", ...Array.from(typeMap.values())].join(", ");
1745
+ throw new Error(`Unknown task type "${typeFilter}". Available types: ${available}`);
1746
+ }
1747
+ async function fetchMyTasks(config, opts = {}) {
1748
+ const client = new ClickUpClient(config);
1749
+ const { typeFilter, name, ...apiFilters } = opts;
1750
+ const [allTasks, customTypes] = await Promise.all([
1751
+ client.getMyTasks(config.teamId, apiFilters),
1752
+ client.getCustomTaskTypes(config.teamId)
1753
+ ]);
1754
+ const typeMap = buildTypeMap(customTypes);
1755
+ let filtered = allTasks;
1756
+ if (typeFilter) {
1757
+ const targetId = resolveTypeFilter(typeFilter, typeMap);
1758
+ filtered = allTasks.filter((t) => (t.custom_item_id ?? 0) === targetId);
1759
+ }
1760
+ if (name) {
1761
+ const query = name.toLowerCase();
1762
+ filtered = filtered.filter((t) => t.name.toLowerCase().includes(query));
1763
+ }
1764
+ return filtered.map((t) => summarize(t, typeMap));
1765
+ }
1766
+ async function printTasks(tasks, forceJson, config) {
1767
+ if (shouldOutputJson(forceJson)) {
1768
+ console.log(JSON.stringify(tasks, null, 2));
1769
+ return;
1770
+ }
1771
+ if (!isTTY()) {
1772
+ console.log(formatTasksMarkdown(tasks));
1773
+ return;
1774
+ }
1775
+ if (tasks.length === 0) {
1776
+ console.log("No tasks found.");
1777
+ return;
1778
+ }
1779
+ const fetchTask = config ? (() => {
1780
+ const client = new ClickUpClient(config);
1781
+ return (id) => client.getTask(id);
1782
+ })() : void 0;
1783
+ const selected = await interactiveTaskPicker(tasks);
1784
+ await showDetailsAndOpen(selected, fetchTask);
1785
+ }
1786
+
61
1787
  // src/status.ts
62
1788
  function matchStatus(input, statuses) {
63
1789
  if (!input) return null;
@@ -286,13 +2012,13 @@ async function getTask(config, taskId) {
286
2012
  }
287
2013
 
288
2014
  // src/commands/init.ts
289
- import { password, select, confirm } from "@inquirer/prompts";
290
- import fs from "fs";
2015
+ import { password, select, confirm as confirm2 } from "@inquirer/prompts";
2016
+ import fs2 from "fs";
291
2017
  async function runInitCommand() {
292
- const configPath2 = getConfigPath();
293
- if (fs.existsSync(configPath2)) {
294
- const overwrite = await confirm({
295
- message: `Config already exists at ${configPath2}. Overwrite?`,
2018
+ const configPath3 = getConfigPath();
2019
+ if (fs2.existsSync(configPath3)) {
2020
+ const overwrite = await confirm2({
2021
+ message: `Config already exists at ${configPath3}. Overwrite?`,
296
2022
  default: false
297
2023
  });
298
2024
  if (!overwrite) {
@@ -330,19 +2056,240 @@ async function runInitCommand() {
330
2056
  });
331
2057
  }
332
2058
  writeConfig({ apiToken, teamId });
333
- process.stdout.write(`Config written to ${configPath2}
2059
+ process.stdout.write(`Config written to ${configPath3}
2060
+ `);
2061
+ }
2062
+
2063
+ // src/commands/sprint.ts
2064
+ import { select as select2 } from "@inquirer/prompts";
2065
+ var SPRINT_KEYWORDS = ["sprint", "iteration", "cycle", "scrum"];
2066
+ function parseUSDateRange(name) {
2067
+ const m = name.match(/\((\d{1,2})\/(\d{1,2})\s*[-–]\s*(\d{1,2})\/(\d{1,2})\)/);
2068
+ if (!m) return null;
2069
+ const year = (/* @__PURE__ */ new Date()).getFullYear();
2070
+ const start = new Date(year, Number(m[1]) - 1, Number(m[2]));
2071
+ const end = new Date(year, Number(m[3]) - 1, Number(m[4]), 23, 59, 59);
2072
+ if (end < start) end.setFullYear(end.getFullYear() + 1);
2073
+ return { start, end };
2074
+ }
2075
+ function parseISODateRange(name) {
2076
+ const m = name.match(/\((\d{4}-\d{2}-\d{2})\s*[-–]\s*(\d{4}-\d{2}-\d{2})\)/);
2077
+ if (!m) return null;
2078
+ const [sy, sm, sd] = m[1].split("-").map(Number);
2079
+ const [ey, em, ed] = m[2].split("-").map(Number);
2080
+ const start = new Date(sy, sm - 1, sd);
2081
+ const end = new Date(ey, em - 1, ed, 23, 59, 59);
2082
+ return { start, end };
2083
+ }
2084
+ function parseMonthDayRange(name) {
2085
+ const months = {
2086
+ jan: 0,
2087
+ feb: 1,
2088
+ mar: 2,
2089
+ apr: 3,
2090
+ may: 4,
2091
+ jun: 5,
2092
+ jul: 6,
2093
+ aug: 7,
2094
+ sep: 8,
2095
+ oct: 9,
2096
+ nov: 10,
2097
+ dec: 11
2098
+ };
2099
+ const m = name.match(/\(([A-Za-z]{3})\s+(\d{1,2})\s*[-–]\s*([A-Za-z]{3})\s+(\d{1,2})\)/);
2100
+ if (!m) return null;
2101
+ const sm = months[m[1].toLowerCase()];
2102
+ const em = months[m[3].toLowerCase()];
2103
+ if (sm === void 0 || em === void 0) return null;
2104
+ const year = (/* @__PURE__ */ new Date()).getFullYear();
2105
+ const start = new Date(year, sm, Number(m[2]));
2106
+ const end = new Date(year, em, Number(m[4]), 23, 59, 59);
2107
+ if (end < start) end.setFullYear(end.getFullYear() + 1);
2108
+ return { start, end };
2109
+ }
2110
+ function parseEuropeanDateRange(name) {
2111
+ const m = name.match(/\((\d{1,2})\.(\d{1,2})\s*[-–]\s*(\d{1,2})\.(\d{1,2})\)/);
2112
+ if (!m) return null;
2113
+ const year = (/* @__PURE__ */ new Date()).getFullYear();
2114
+ const start = new Date(year, Number(m[2]) - 1, Number(m[1]));
2115
+ const end = new Date(year, Number(m[4]) - 1, Number(m[3]), 23, 59, 59);
2116
+ if (end < start) end.setFullYear(end.getFullYear() + 1);
2117
+ return { start, end };
2118
+ }
2119
+ function parseSprintDates(name) {
2120
+ return parseUSDateRange(name) ?? parseISODateRange(name) ?? parseMonthDayRange(name) ?? parseEuropeanDateRange(name);
2121
+ }
2122
+ function findActiveSprintList(lists, today = /* @__PURE__ */ new Date()) {
2123
+ if (lists.length === 0) return null;
2124
+ for (const list of lists) {
2125
+ const dates = parseSprintDates(list.name);
2126
+ if (dates && today >= dates.start && today <= dates.end) return list;
2127
+ }
2128
+ for (const list of lists) {
2129
+ if (list.start_date && list.due_date) {
2130
+ const start = new Date(Number(list.start_date));
2131
+ const end = new Date(Number(list.due_date));
2132
+ if (today >= start && today <= end) return list;
2133
+ }
2134
+ }
2135
+ return lists[lists.length - 1] ?? null;
2136
+ }
2137
+ var NOISE_WORDS = /* @__PURE__ */ new Set(["product", "team", "the", "and", "for", "test"]);
2138
+ function extractSpaceKeywords(spaceName) {
2139
+ return spaceName.replace(/[^a-zA-Z0-9\s]/g, "").split(/\s+/).map((w) => w.toLowerCase()).filter((w) => w.length >= 3 && !NOISE_WORDS.has(w));
2140
+ }
2141
+ function findRelatedSpaces(mySpaceIds, allSpaces) {
2142
+ const mySpaces = allSpaces.filter((s) => mySpaceIds.has(s.id));
2143
+ const keywords = mySpaces.flatMap((s) => extractSpaceKeywords(s.name));
2144
+ if (keywords.length === 0) return allSpaces;
2145
+ return allSpaces.filter(
2146
+ (s) => mySpaceIds.has(s.id) || keywords.some((kw) => s.name.toLowerCase().includes(kw))
2147
+ );
2148
+ }
2149
+ function resolveSprintFolderId(config, opts) {
2150
+ const folderId = opts?.folder ?? config.sprintFolderId;
2151
+ if (folderId) return folderId;
2152
+ const favorites = getFavorites();
2153
+ const favoriteFolderIds = Object.values(favorites).filter((f) => f.type === "sprint-folder").map((f) => f.id);
2154
+ return favoriteFolderIds[0];
2155
+ }
2156
+ async function resolveActiveSprintListId(config, opts) {
2157
+ const client = new ClickUpClient(config);
2158
+ const folderId = resolveSprintFolderId(config, opts);
2159
+ let sprintLists;
2160
+ if (folderId) {
2161
+ sprintLists = await client.getFolderLists(folderId);
2162
+ } else {
2163
+ const [myTasks, allSpaces] = await Promise.all([
2164
+ client.getMyTasks(config.teamId),
2165
+ client.getSpaces(config.teamId)
2166
+ ]);
2167
+ let spaces;
2168
+ if (opts?.space) {
2169
+ spaces = allSpaces.filter(
2170
+ (s) => s.name.toLowerCase().includes(opts.space.toLowerCase()) || s.id === opts.space
2171
+ );
2172
+ if (spaces.length === 0) {
2173
+ throw new Error(`No space matching "${opts.space}" found.`);
2174
+ }
2175
+ } else {
2176
+ const mySpaceIds = new Set(
2177
+ myTasks.map((t) => t.space?.id).filter((id) => Boolean(id))
2178
+ );
2179
+ spaces = findRelatedSpaces(mySpaceIds, allSpaces);
2180
+ }
2181
+ const foldersBySpace = await Promise.all(spaces.map((space) => client.getFolders(space.id)));
2182
+ const sprintFolders = foldersBySpace.flat().filter((f) => {
2183
+ const lower = f.name.toLowerCase();
2184
+ return SPRINT_KEYWORDS.some((kw) => lower.includes(kw));
2185
+ });
2186
+ const listsByFolder = await Promise.all(
2187
+ sprintFolders.map((folder) => client.getFolderLists(folder.id))
2188
+ );
2189
+ sprintLists = listsByFolder.flat();
2190
+ }
2191
+ const activeList = findActiveSprintList(sprintLists);
2192
+ if (!activeList) {
2193
+ throw new Error(
2194
+ 'No active sprint list found. Ensure sprint folders contain "sprint", "iteration", "cycle", or "scrum" in their name.'
2195
+ );
2196
+ }
2197
+ return activeList.id;
2198
+ }
2199
+ async function runSprintCommand(config, opts) {
2200
+ const client = new ClickUpClient(config);
2201
+ process.stderr.write("Detecting active sprint...\n");
2202
+ const folderId = resolveSprintFolderId(config, opts);
2203
+ const [myTasks, allSpaces, customTypes] = await Promise.all([
2204
+ client.getMyTasks(config.teamId),
2205
+ folderId ? Promise.resolve([]) : client.getSpaces(config.teamId),
2206
+ client.getCustomTaskTypes(config.teamId)
2207
+ ]);
2208
+ const typeMap = buildTypeMap(customTypes);
2209
+ let sprintLists;
2210
+ if (folderId) {
2211
+ sprintLists = await client.getFolderLists(folderId);
2212
+ } else {
2213
+ let spaces;
2214
+ if (opts.space) {
2215
+ spaces = allSpaces.filter(
2216
+ (s) => s.name.toLowerCase().includes(opts.space.toLowerCase()) || s.id === opts.space
2217
+ );
2218
+ if (spaces.length === 0) {
2219
+ throw new Error(
2220
+ `No space matching "${opts.space}" found. Use \`cup spaces\` to list available spaces.`
2221
+ );
2222
+ }
2223
+ } else {
2224
+ const mySpaceIds = new Set(
2225
+ myTasks.map((t) => t.space?.id).filter((id) => Boolean(id))
2226
+ );
2227
+ spaces = findRelatedSpaces(mySpaceIds, allSpaces);
2228
+ }
2229
+ const foldersBySpace = await Promise.all(spaces.map((space) => client.getFolders(space.id)));
2230
+ const sprintFolders = foldersBySpace.flat().filter((f) => {
2231
+ const lower = f.name.toLowerCase();
2232
+ return SPRINT_KEYWORDS.some((kw) => lower.includes(kw));
2233
+ });
2234
+ const listsByFolder = await Promise.all(
2235
+ sprintFolders.map((folder) => client.getFolderLists(folder.id))
2236
+ );
2237
+ sprintLists = listsByFolder.flat();
2238
+ }
2239
+ let activeList = findActiveSprintList(sprintLists);
2240
+ if (!activeList && sprintLists.length > 1 && isTTY()) {
2241
+ const choice = await select2({
2242
+ message: "Multiple sprint lists found. Which one?",
2243
+ choices: sprintLists.map((l) => ({
2244
+ name: `${l.name} (${l.id})`,
2245
+ value: l
2246
+ }))
2247
+ });
2248
+ activeList = choice;
2249
+ }
2250
+ if (!activeList && sprintLists.length > 1) {
2251
+ process.stderr.write(
2252
+ `Multiple sprint lists found:
2253
+ ${sprintLists.map((l) => ` - ${l.name} (${l.id})`).join("\n")}
2254
+ Using: ${sprintLists[sprintLists.length - 1].name}
2255
+ `
2256
+ );
2257
+ activeList = sprintLists[sprintLists.length - 1] ?? null;
2258
+ }
2259
+ if (!activeList) {
2260
+ throw new Error(
2261
+ 'No sprint list found. Ensure sprint folders contain "sprint", "iteration", "cycle", or "scrum" in their name.'
2262
+ );
2263
+ }
2264
+ process.stderr.write(`Active sprint: ${activeList.name}
334
2265
  `);
2266
+ const me = await client.getMe();
2267
+ const viewData = await client.getListViews(activeList.id);
2268
+ const listView = viewData.required_views?.list;
2269
+ let allTasks;
2270
+ if (listView) {
2271
+ allTasks = await client.getViewTasks(listView.id);
2272
+ } else {
2273
+ allTasks = await client.getTasksFromList(activeList.id);
2274
+ }
2275
+ let sprintTasks = allTasks.filter((t) => t.assignees.some((a) => Number(a.id) === me.id));
2276
+ if (!opts.includeClosed) {
2277
+ sprintTasks = sprintTasks.filter((t) => !isDoneStatus(t.status.status));
2278
+ }
2279
+ const filtered = opts.status ? sprintTasks.filter((t) => t.status.status.toLowerCase() === opts.status.toLowerCase()) : sprintTasks;
2280
+ const summaries = filtered.map((t) => summarize(t, typeMap));
2281
+ await printTasks(summaries, opts.json ?? false, config);
335
2282
  }
336
2283
 
337
2284
  // src/commands/sprints.ts
338
- import chalk from "chalk";
2285
+ import chalk3 from "chalk";
339
2286
  var SPRINT_COLUMNS = [
340
2287
  { key: "id", label: "ID" },
341
2288
  {
342
2289
  key: "sprint",
343
2290
  label: "SPRINT",
344
2291
  maxWidth: 60,
345
- format: (v, row) => row.active ? chalk.green(v) : v
2292
+ format: (v, row) => row.active ? chalk3.green(v) : v
346
2293
  },
347
2294
  { key: "dates", label: "DATES" }
348
2295
  ];
@@ -468,7 +2415,7 @@ async function postComment(config, taskId, text, notifyAll) {
468
2415
  }
469
2416
 
470
2417
  // src/commands/comments.ts
471
- import chalk2 from "chalk";
2418
+ import chalk4 from "chalk";
472
2419
  async function fetchComments(config, taskId) {
473
2420
  const client = new ClickUpClient(config);
474
2421
  const comments = await client.getTaskComments(taskId);
@@ -492,11 +2439,11 @@ function printComments(comments, forceJson) {
492
2439
  console.log("No comments found.");
493
2440
  return;
494
2441
  }
495
- const separator = chalk2.dim("-".repeat(60));
2442
+ const separator = chalk4.dim("-".repeat(60));
496
2443
  for (let i = 0; i < comments.length; i++) {
497
2444
  const c = comments[i];
498
2445
  if (i > 0) console.log(separator);
499
- console.log(`${chalk2.bold(c.user)} ${chalk2.dim(formatTimestamp(c.date))}`);
2446
+ console.log(`${chalk4.bold(c.user)} ${chalk4.dim(formatTimestamp(c.date))}`);
500
2447
  console.log(c.text);
501
2448
  if (i < comments.length - 1) console.log("");
502
2449
  }
@@ -806,7 +2753,7 @@ async function openTask(config, query, opts = {}) {
806
2753
  }
807
2754
 
808
2755
  // src/commands/summary.ts
809
- import chalk3 from "chalk";
2756
+ import chalk5 from "chalk";
810
2757
  var IN_PROGRESS_PATTERNS = ["in progress", "in review", "code review", "doing"];
811
2758
  function isCompletedRecently(task, cutoff) {
812
2759
  if (!isDoneStatus(task.status.status)) return false;
@@ -844,9 +2791,9 @@ function categorizeTasks(tasks, hoursBack, typeMap) {
844
2791
  }
845
2792
  function colorSectionLabel(label) {
846
2793
  const lower = label.toLowerCase();
847
- if (lower.includes("completed")) return chalk3.green(label);
848
- if (lower.includes("progress")) return chalk3.yellow(label);
849
- if (lower.includes("overdue")) return chalk3.red(label);
2794
+ if (lower.includes("completed")) return chalk5.green(label);
2795
+ if (lower.includes("progress")) return chalk5.yellow(label);
2796
+ if (lower.includes("overdue")) return chalk5.red(label);
850
2797
  return label;
851
2798
  }
852
2799
  function printSection(label, tasks) {
@@ -948,7 +2895,7 @@ function setConfigValue(key, value, profileName) {
948
2895
  }
949
2896
  writeConfig(merged, profileName);
950
2897
  }
951
- function configPath() {
2898
+ function configPath2() {
952
2899
  return getConfigPath();
953
2900
  }
954
2901
 
@@ -975,7 +2922,7 @@ async function assignTask(config, taskId, opts) {
975
2922
  }
976
2923
 
977
2924
  // src/commands/activity.ts
978
- import chalk4 from "chalk";
2925
+ import chalk6 from "chalk";
979
2926
  async function fetchActivity(config, taskId) {
980
2927
  const client = new ClickUpClient(config);
981
2928
  const [task, rawComments] = await Promise.all([
@@ -1007,8 +2954,8 @@ ${commentsMd}`);
1007
2954
  }
1008
2955
  console.log(formatTaskDetail(result.task));
1009
2956
  console.log("");
1010
- console.log(chalk4.bold("Comments"));
1011
- console.log(chalk4.dim("-".repeat(60)));
2957
+ console.log(chalk6.bold("Comments"));
2958
+ console.log(chalk6.dim("-".repeat(60)));
1012
2959
  if (result.comments.length === 0) {
1013
2960
  console.log("No comments.");
1014
2961
  return;
@@ -1017,15 +2964,15 @@ ${commentsMd}`);
1017
2964
  const c = result.comments[i];
1018
2965
  if (i > 0) {
1019
2966
  console.log("");
1020
- console.log(chalk4.dim("-".repeat(60)));
2967
+ console.log(chalk6.dim("-".repeat(60)));
1021
2968
  }
1022
- console.log(`${chalk4.bold(c.user)} ${chalk4.dim(formatTimestamp(c.date))}`);
2969
+ console.log(`${chalk6.bold(c.user)} ${chalk6.dim(formatTimestamp(c.date))}`);
1023
2970
  console.log(c.text);
1024
2971
  }
1025
2972
  }
1026
2973
 
1027
2974
  // src/commands/time-in-status.ts
1028
- import chalk5 from "chalk";
2975
+ import chalk7 from "chalk";
1029
2976
  function transformResponse(taskId, data) {
1030
2977
  const entries = [];
1031
2978
  for (const entry of data.status_history ?? []) {
@@ -1095,11 +3042,11 @@ function printTimeInStatus(result, forceJson) {
1095
3042
  const columns = [
1096
3043
  { key: "status", label: "STATUS", maxWidth: 25, format: (v) => colorStatus(v) },
1097
3044
  { key: "duration", label: "DURATION" },
1098
- { key: "current", label: "", format: (v) => v ? chalk5.green("\u25C0") : "" }
3045
+ { key: "current", label: "", format: (v) => v ? chalk7.green("\u25C0") : "" }
1099
3046
  ];
1100
3047
  console.log(formatTable(rows, columns));
1101
3048
  console.log("");
1102
- console.log(`${chalk5.bold("Total:")} ${result.total}`);
3049
+ console.log(`${chalk7.bold("Total:")} ${result.total}`);
1103
3050
  }
1104
3051
 
1105
3052
  // src/commands/metadata.ts
@@ -1244,12 +3191,12 @@ var commandMetadata = [
1244
3191
  },
1245
3192
  {
1246
3193
  name: "comment-delete",
1247
- description: "Delete a comment",
1248
- flags: ["--mine", "--match", "--json"],
3194
+ description: "Delete a comment by ID, or use --task with --mine to find and delete your comment",
3195
+ flags: ["--task", "--mine", "--match", "--json"],
1249
3196
  quickReference: [
1250
3197
  {
1251
3198
  section: "write",
1252
- usage: "comment-delete <commentId>",
3199
+ usage: "comment-delete [commentId]",
1253
3200
  description: "Delete a comment"
1254
3201
  }
1255
3202
  ]
@@ -2483,8 +4430,9 @@ ${renderZshTopLevelCommands(name)}
2483
4430
  comment-delete)
2484
4431
  _arguments \\
2485
4432
  '1:comment_id:' \\
4433
+ '--task[Task to search for your comment (requires --mine)]:task_id:' \\
2486
4434
  '--mine[Delete one of my comments from the specified task]' \\
2487
- '--match[Only match comments containing this text]:text:' \\
4435
+ '--match[Only match comments containing this text (requires --mine)]:text:' \\
2488
4436
  '--json[Force JSON output]'
2489
4437
  ;;
2490
4438
  replies)
@@ -3014,18 +4962,18 @@ function generateCompletion(shell, name = "cup") {
3014
4962
 
3015
4963
  // src/commands/skill.ts
3016
4964
  import { readFileSync, realpathSync, mkdirSync, copyFileSync, existsSync } from "fs";
3017
- import { join, dirname } from "path";
3018
- import { homedir } from "os";
3019
- import chalk6 from "chalk";
4965
+ import { join as join2, dirname } from "path";
4966
+ import { homedir as homedir2 } from "os";
4967
+ import chalk8 from "chalk";
3020
4968
  function skillPath() {
3021
4969
  if (!process.argv[1]) {
3022
4970
  throw new Error("Cannot determine install path. Run with: cup skill");
3023
4971
  }
3024
4972
  const entryPoint = realpathSync(process.argv[1]);
3025
4973
  const packageRoot = dirname(dirname(entryPoint));
3026
- const candidate = join(packageRoot, "skills", "clickup-cli", "SKILL.md");
4974
+ const candidate = join2(packageRoot, "skills", "clickup-cli", "SKILL.md");
3027
4975
  if (existsSync(candidate)) return candidate;
3028
- const altCandidate = join(dirname(entryPoint), "..", "skills", "clickup-cli", "SKILL.md");
4976
+ const altCandidate = join2(dirname(entryPoint), "..", "skills", "clickup-cli", "SKILL.md");
3029
4977
  if (existsSync(altCandidate)) return altCandidate;
3030
4978
  throw new Error("SKILL.md not found. Reinstall with: npm install -g @krodak/clickup-cli");
3031
4979
  }
@@ -3033,11 +4981,11 @@ function printSkill() {
3033
4981
  return readFileSync(skillPath(), "utf-8");
3034
4982
  }
3035
4983
  function getAgentTargets() {
3036
- const home = homedir();
4984
+ const home = homedir2();
3037
4985
  const targets = [
3038
- { name: "Claude Code", dir: join(home, ".claude", "skills", "clickup") },
3039
- { name: "Codex", dir: join(home, ".agents", "skills", "clickup") },
3040
- { name: "OpenCode", dir: join(home, ".config", "opencode", "skills", "clickup") }
4986
+ { name: "Claude Code", dir: join2(home, ".claude", "skills", "clickup") },
4987
+ { name: "Codex", dir: join2(home, ".agents", "skills", "clickup") },
4988
+ { name: "OpenCode", dir: join2(home, ".config", "opencode", "skills", "clickup") }
3041
4989
  ];
3042
4990
  return targets.map((t) => ({
3043
4991
  ...t,
@@ -3049,11 +4997,11 @@ async function installSkillInteractive() {
3049
4997
  const source = skillPath();
3050
4998
  const installed = [];
3051
4999
  if (isTTY()) {
3052
- const { checkbox } = await import("@inquirer/prompts");
3053
- const selected = await checkbox({
5000
+ const { checkbox: checkbox2 } = await import("@inquirer/prompts");
5001
+ const selected = await checkbox2({
3054
5002
  message: "Install skill for which agents?",
3055
5003
  choices: targets.map((t) => ({
3056
- name: `${t.name}${t.detected ? chalk6.dim(" (detected)") : ""}`,
5004
+ name: `${t.name}${t.detected ? chalk8.dim(" (detected)") : ""}`,
3057
5005
  value: t.name,
3058
5006
  checked: t.detected
3059
5007
  }))
@@ -3065,7 +5013,7 @@ async function installSkillInteractive() {
3065
5013
  const target = targets.find((t) => t.name === name);
3066
5014
  if (!target) continue;
3067
5015
  if (!existsSync(target.dir)) mkdirSync(target.dir, { recursive: true });
3068
- const dest = join(target.dir, "SKILL.md");
5016
+ const dest = join2(target.dir, "SKILL.md");
3069
5017
  copyFileSync(source, dest);
3070
5018
  installed.push(`${target.name}: ${dest}`);
3071
5019
  }
@@ -3078,7 +5026,7 @@ async function installSkillInteractive() {
3078
5026
  }
3079
5027
  for (const target of detected) {
3080
5028
  if (!existsSync(target.dir)) mkdirSync(target.dir, { recursive: true });
3081
- const dest = join(target.dir, "SKILL.md");
5029
+ const dest = join2(target.dir, "SKILL.md");
3082
5030
  copyFileSync(source, dest);
3083
5031
  installed.push(`${target.name}: ${dest}`);
3084
5032
  }
@@ -3298,8 +5246,8 @@ async function deleteTaskCommand(config, taskId, opts) {
3298
5246
  throw new Error("Destructive operation requires --confirm flag in non-interactive mode");
3299
5247
  }
3300
5248
  const task = await client.getTask(taskId);
3301
- const { confirm: confirm2 } = await import("@inquirer/prompts");
3302
- const confirmed = await confirm2({
5249
+ const { confirm: confirm3 } = await import("@inquirer/prompts");
5250
+ const confirmed = await confirm3({
3303
5251
  message: `Delete task "${task.name}" (${task.id})? This cannot be undone.`,
3304
5252
  default: false
3305
5253
  });
@@ -3319,8 +5267,8 @@ async function archiveTaskCommand(config, taskId, opts) {
3319
5267
  throw new Error(`Destructive operation requires --confirm flag in non-interactive mode`);
3320
5268
  }
3321
5269
  const task = await client.getTask(taskId);
3322
- const { confirm: confirm2 } = await import("@inquirer/prompts");
3323
- const confirmed = await confirm2({
5270
+ const { confirm: confirm3 } = await import("@inquirer/prompts");
5271
+ const confirmed = await confirm3({
3324
5272
  message: `${opts.unarchive ? "Unarchive" : "Archive"} task "${task.name}" (${task.id})?`,
3325
5273
  default: false
3326
5274
  });
@@ -3371,7 +5319,7 @@ async function manageTags(config, taskId, opts) {
3371
5319
  }
3372
5320
 
3373
5321
  // src/commands/checklist.ts
3374
- import chalk7 from "chalk";
5322
+ import chalk9 from "chalk";
3375
5323
  async function viewChecklists(config, taskId) {
3376
5324
  const client = new ClickUpClient(config);
3377
5325
  const task = await client.getTask(taskId);
@@ -3404,14 +5352,14 @@ function formatChecklists(checklists) {
3404
5352
  const lines = [];
3405
5353
  for (const cl of checklists) {
3406
5354
  const resolved = cl.items.filter((i) => i.resolved).length;
3407
- lines.push(chalk7.bold(`${cl.name} (${resolved}/${cl.items.length})`));
3408
- lines.push(chalk7.dim(` ID: ${cl.id}`));
5355
+ lines.push(chalk9.bold(`${cl.name} (${resolved}/${cl.items.length})`));
5356
+ lines.push(chalk9.dim(` ID: ${cl.id}`));
3409
5357
  for (const item of cl.items) {
3410
- const check = item.resolved ? chalk7.green("[x]") : chalk7.dim("[ ]");
3411
- const name = item.resolved ? chalk7.dim(item.name) : item.name;
3412
- const assignee = item.assignee ? chalk7.dim(` @${item.assignee.username}`) : "";
5358
+ const check = item.resolved ? chalk9.green("[x]") : chalk9.dim("[ ]");
5359
+ const name = item.resolved ? chalk9.dim(item.name) : item.name;
5360
+ const assignee = item.assignee ? chalk9.dim(` @${item.assignee.username}`) : "";
3413
5361
  lines.push(` ${check} ${name}${assignee}`);
3414
- lines.push(chalk7.dim(` item-id: ${item.id}`));
5362
+ lines.push(chalk9.dim(` item-id: ${item.id}`));
3415
5363
  }
3416
5364
  }
3417
5365
  return lines.join("\n");
@@ -3470,7 +5418,7 @@ async function deleteCommentByTaskSelection(config, taskId, options) {
3470
5418
  }
3471
5419
 
3472
5420
  // src/commands/replies.ts
3473
- import chalk8 from "chalk";
5421
+ import chalk10 from "chalk";
3474
5422
  async function getReplies(config, commentId) {
3475
5423
  const client = new ClickUpClient(config);
3476
5424
  return client.getThreadedComments(commentId);
@@ -3485,7 +5433,7 @@ function formatReplies(replies) {
3485
5433
  return replies.map((r) => {
3486
5434
  const user = r.user?.username ?? "Unknown";
3487
5435
  const date = formatTimestamp(Number(r.date));
3488
- return `${chalk8.bold(user)} ${chalk8.dim(date)}
5436
+ return `${chalk10.bold(user)} ${chalk10.dim(date)}
3489
5437
  ${r.comment_text}`;
3490
5438
  }).join("\n\n");
3491
5439
  }
@@ -3548,7 +5496,7 @@ function formatDocsMarkdown(docs) {
3548
5496
  }
3549
5497
 
3550
5498
  // src/commands/doc.ts
3551
- import chalk9 from "chalk";
5499
+ import chalk11 from "chalk";
3552
5500
  async function getDocInfo(config, docId) {
3553
5501
  const client = new ClickUpClient(config);
3554
5502
  const [doc, pages] = await Promise.all([
@@ -3560,14 +5508,14 @@ async function getDocInfo(config, docId) {
3560
5508
  function formatDocInfo(doc, pages, indent = 0) {
3561
5509
  const lines = [];
3562
5510
  if (indent === 0) {
3563
- lines.push(`${chalk9.bold(doc.name)} ${chalk9.dim(doc.id)}`);
5511
+ lines.push(`${chalk11.bold(doc.name)} ${chalk11.dim(doc.id)}`);
3564
5512
  if (pages.length === 0) {
3565
5513
  lines.push(" (no pages)");
3566
5514
  }
3567
5515
  }
3568
5516
  for (const page of pages) {
3569
5517
  const prefix = " ".repeat(indent + 1);
3570
- lines.push(`${prefix}${page.name} ${chalk9.dim(page.id)}`);
5518
+ lines.push(`${prefix}${page.name} ${chalk11.dim(page.id)}`);
3571
5519
  if (page.pages && page.pages.length > 0) {
3572
5520
  lines.push(formatDocInfo(doc, page.pages, indent + 1));
3573
5521
  }
@@ -3646,7 +5594,7 @@ async function deleteDocPage(config, docId, pageId) {
3646
5594
  }
3647
5595
 
3648
5596
  // src/commands/folders.ts
3649
- import chalk10 from "chalk";
5597
+ import chalk12 from "chalk";
3650
5598
  async function listFolders(config, spaceId, nameFilter) {
3651
5599
  const client = new ClickUpClient(config);
3652
5600
  const folders = await client.getFolders(spaceId);
@@ -3665,9 +5613,9 @@ async function listFolders(config, spaceId, nameFilter) {
3665
5613
  function formatFolders(folders) {
3666
5614
  if (folders.length === 0) return "No folders found";
3667
5615
  return folders.map((f) => {
3668
- const header = `${chalk10.bold(f.name)} ${chalk10.dim(f.id)}`;
5616
+ const header = `${chalk12.bold(f.name)} ${chalk12.dim(f.id)}`;
3669
5617
  if (f.lists.length === 0) return header;
3670
- const listLines = f.lists.map((l) => ` ${chalk10.dim(">")} ${l.name} ${chalk10.dim(l.id)}`);
5618
+ const listLines = f.lists.map((l) => ` ${chalk12.dim(">")} ${l.name} ${chalk12.dim(l.id)}`);
3671
5619
  return [header, ...listLines].join("\n");
3672
5620
  }).join("\n\n");
3673
5621
  }
@@ -3682,13 +5630,13 @@ function formatFoldersMarkdown(folders) {
3682
5630
  }
3683
5631
 
3684
5632
  // src/commands/time.ts
3685
- import chalk11 from "chalk";
5633
+ import chalk13 from "chalk";
3686
5634
  var TIME_COLUMNS = [
3687
5635
  { key: "task", label: "Task", maxWidth: 35 },
3688
5636
  { key: "duration", label: "Duration", maxWidth: 10 },
3689
5637
  { key: "date", label: "Date", maxWidth: 20 },
3690
5638
  { key: "description", label: "Description", maxWidth: 30 },
3691
- { key: "status", label: "", maxWidth: 10, format: (v) => v === "RUNNING" ? chalk11.green(v) : "" }
5639
+ { key: "status", label: "", maxWidth: 10, format: (v) => v === "RUNNING" ? chalk13.green(v) : "" }
3692
5640
  ];
3693
5641
  async function startTimer(config, taskId, description) {
3694
5642
  const client = new ClickUpClient(config);
@@ -3784,7 +5732,7 @@ function formatTimeEntriesMarkdown(entries) {
3784
5732
  }
3785
5733
 
3786
5734
  // src/commands/tags.ts
3787
- import chalk12 from "chalk";
5735
+ import chalk14 from "chalk";
3788
5736
  var TAG_COLUMNS = [
3789
5737
  { key: "name", label: "Name", maxWidth: 40 },
3790
5738
  { key: "fg", label: "FG", maxWidth: 10 },
@@ -3817,13 +5765,13 @@ function formatTags(tags) {
3817
5765
  if (tags.length === 0) return "No tags found";
3818
5766
  if (isTTY()) {
3819
5767
  const rows = tags.map((t) => ({
3820
- name: t.tag_bg ? chalk12.bgHex(t.tag_bg).hex(t.tag_fg || "#ffffff")(` ${t.name} `) : chalk12.bold(t.name),
5768
+ name: t.tag_bg ? chalk14.bgHex(t.tag_bg).hex(t.tag_fg || "#ffffff")(` ${t.name} `) : chalk14.bold(t.name),
3821
5769
  fg: t.tag_fg || "",
3822
5770
  bg: t.tag_bg || ""
3823
5771
  }));
3824
5772
  return formatTable(rows, TAG_COLUMNS);
3825
5773
  }
3826
- return tags.map((t) => chalk12.bold(t.name)).join(", ");
5774
+ return tags.map((t) => chalk14.bold(t.name)).join(", ");
3827
5775
  }
3828
5776
  function formatTagsMarkdown(tags) {
3829
5777
  if (tags.length === 0) return "No tags found";
@@ -3858,7 +5806,7 @@ function formatMembersMarkdown(members) {
3858
5806
  }
3859
5807
 
3860
5808
  // src/commands/fields.ts
3861
- import chalk13 from "chalk";
5809
+ import chalk15 from "chalk";
3862
5810
  var FIELD_COLUMNS = [
3863
5811
  { key: "id", label: "ID", maxWidth: 20 },
3864
5812
  { key: "name", label: "Name", maxWidth: 30 },
@@ -3867,7 +5815,7 @@ var FIELD_COLUMNS = [
3867
5815
  key: "required",
3868
5816
  label: "Required",
3869
5817
  maxWidth: 10,
3870
- format: (v) => v === "yes" ? chalk13.yellow(v) : chalk13.dim(v)
5818
+ format: (v) => v === "yes" ? chalk15.yellow(v) : chalk15.dim(v)
3871
5819
  },
3872
5820
  { key: "options", label: "Options", maxWidth: 40 }
3873
5821
  ];
@@ -3974,13 +5922,13 @@ async function bulkTag(config, tagName, taskIds, action) {
3974
5922
  }
3975
5923
 
3976
5924
  // src/commands/goals.ts
3977
- import chalk14 from "chalk";
5925
+ import chalk16 from "chalk";
3978
5926
  function colorProgress(value) {
3979
5927
  const num = parseInt(value, 10);
3980
5928
  if (isNaN(num)) return value;
3981
- if (num >= 75) return chalk14.green(value);
3982
- if (num >= 25) return chalk14.yellow(value);
3983
- return chalk14.red(value);
5929
+ if (num >= 75) return chalk16.green(value);
5930
+ if (num >= 25) return chalk16.yellow(value);
5931
+ return chalk16.red(value);
3984
5932
  }
3985
5933
  var GOAL_COLUMNS = [
3986
5934
  { key: "id", label: "ID", maxWidth: 15 },
@@ -4074,14 +6022,14 @@ function formatKeyResultsMarkdown(keyResults) {
4074
6022
  }
4075
6023
 
4076
6024
  // src/commands/task-types.ts
4077
- import chalk15 from "chalk";
6025
+ import chalk17 from "chalk";
4078
6026
  async function listTaskTypes(config) {
4079
6027
  const client = new ClickUpClient(config);
4080
6028
  return client.getCustomTaskTypes(config.teamId);
4081
6029
  }
4082
6030
  function formatTaskTypes(types) {
4083
6031
  if (types.length === 0) return "No custom task types";
4084
- return types.map((t) => `${chalk15.bold(t.name)} ${chalk15.dim(`(${t.id})`)}`).join("\n");
6032
+ return types.map((t) => `${chalk17.bold(t.name)} ${chalk17.dim(`(${t.id})`)}`).join("\n");
4085
6033
  }
4086
6034
  function formatTaskTypesMarkdown(types) {
4087
6035
  if (types.length === 0) return "No custom task types";
@@ -4089,14 +6037,14 @@ function formatTaskTypesMarkdown(types) {
4089
6037
  }
4090
6038
 
4091
6039
  // src/commands/templates.ts
4092
- import chalk16 from "chalk";
6040
+ import chalk18 from "chalk";
4093
6041
  async function listTemplates(config) {
4094
6042
  const client = new ClickUpClient(config);
4095
6043
  return client.getTaskTemplates(config.teamId);
4096
6044
  }
4097
6045
  function formatTemplates(templates) {
4098
6046
  if (templates.length === 0) return "No task templates";
4099
- return templates.map((t) => `${chalk16.bold(t.name)} ${chalk16.dim(`(${t.id})`)}`).join("\n");
6047
+ return templates.map((t) => `${chalk18.bold(t.name)} ${chalk18.dim(`(${t.id})`)}`).join("\n");
4100
6048
  }
4101
6049
  function formatTemplatesMarkdown(templates) {
4102
6050
  if (templates.length === 0) return "No task templates";
@@ -4104,14 +6052,14 @@ function formatTemplatesMarkdown(templates) {
4104
6052
  }
4105
6053
 
4106
6054
  // src/commands/list-templates.ts
4107
- import chalk17 from "chalk";
6055
+ import chalk19 from "chalk";
4108
6056
  async function listListTemplates(config) {
4109
6057
  const client = new ClickUpClient(config);
4110
6058
  return client.getListTemplates(config.teamId);
4111
6059
  }
4112
6060
  function formatListTemplates(templates) {
4113
6061
  if (templates.length === 0) return "No list templates";
4114
- return templates.map((t) => `${chalk17.bold(t.name)} ${chalk17.dim(`(${t.id})`)}`).join("\n");
6062
+ return templates.map((t) => `${chalk19.bold(t.name)} ${chalk19.dim(`(${t.id})`)}`).join("\n");
4115
6063
  }
4116
6064
  function formatListTemplatesMarkdown(templates) {
4117
6065
  if (templates.length === 0) return "No list templates";
@@ -4119,14 +6067,14 @@ function formatListTemplatesMarkdown(templates) {
4119
6067
  }
4120
6068
 
4121
6069
  // src/commands/folder-templates.ts
4122
- import chalk18 from "chalk";
6070
+ import chalk20 from "chalk";
4123
6071
  async function listFolderTemplates(config) {
4124
6072
  const client = new ClickUpClient(config);
4125
6073
  return client.getFolderTemplates(config.teamId);
4126
6074
  }
4127
6075
  function formatFolderTemplates(templates) {
4128
6076
  if (templates.length === 0) return "No folder templates";
4129
- return templates.map((t) => `${chalk18.bold(t.name)} ${chalk18.dim(`(${t.id})`)}`).join("\n");
6077
+ return templates.map((t) => `${chalk20.bold(t.name)} ${chalk20.dim(`(${t.id})`)}`).join("\n");
4130
6078
  }
4131
6079
  function formatFolderTemplatesMarkdown(templates) {
4132
6080
  if (templates.length === 0) return "No folder templates";
@@ -4149,7 +6097,7 @@ async function createListFromTemplate(config, name, opts) {
4149
6097
  }
4150
6098
 
4151
6099
  // src/commands/views.ts
4152
- import chalk19 from "chalk";
6100
+ import chalk21 from "chalk";
4153
6101
  async function listViews(config, id, container = "list") {
4154
6102
  const client = new ClickUpClient(config);
4155
6103
  if (container === "space") return client.getSpaceViews(id);
@@ -4160,7 +6108,7 @@ async function listViews(config, id, container = "list") {
4160
6108
  }
4161
6109
  function formatViews(views) {
4162
6110
  if (views.length === 0) return "No views";
4163
- return views.map((v) => `${chalk19.bold(v.name)} ${chalk19.dim(`(${v.id})`)} ${chalk19.dim(v.type)}`).join("\n");
6111
+ return views.map((v) => `${chalk21.bold(v.name)} ${chalk21.dim(`(${v.id})`)} ${chalk21.dim(v.type)}`).join("\n");
4164
6112
  }
4165
6113
  function formatViewsMarkdown(views) {
4166
6114
  if (views.length === 0) return "No views";
@@ -4168,20 +6116,20 @@ function formatViewsMarkdown(views) {
4168
6116
  }
4169
6117
 
4170
6118
  // src/commands/view.ts
4171
- import chalk20 from "chalk";
6119
+ import chalk22 from "chalk";
4172
6120
  async function getView(config, viewId) {
4173
6121
  const client = new ClickUpClient(config);
4174
6122
  return client.getView(viewId);
4175
6123
  }
4176
6124
  function formatView(view) {
4177
6125
  const lines = [];
4178
- lines.push(chalk20.bold.underline(view.name));
6126
+ lines.push(chalk22.bold.underline(view.name));
4179
6127
  lines.push("");
4180
- lines.push(` ${chalk20.bold("ID")} ${view.id}`);
4181
- lines.push(` ${chalk20.bold("Type")} ${view.type}`);
4182
- if (view.visibility) lines.push(` ${chalk20.bold("Visibility")} ${view.visibility}`);
4183
- if (view.date_created) lines.push(` ${chalk20.bold("Created")} ${formatDate(view.date_created)}`);
4184
- if (view.protected !== void 0) lines.push(` ${chalk20.bold("Protected")} ${view.protected}`);
6128
+ lines.push(` ${chalk22.bold("ID")} ${view.id}`);
6129
+ lines.push(` ${chalk22.bold("Type")} ${view.type}`);
6130
+ if (view.visibility) lines.push(` ${chalk22.bold("Visibility")} ${view.visibility}`);
6131
+ if (view.date_created) lines.push(` ${chalk22.bold("Created")} ${formatDate(view.date_created)}`);
6132
+ if (view.protected !== void 0) lines.push(` ${chalk22.bold("Protected")} ${view.protected}`);
4185
6133
  return lines.join("\n");
4186
6134
  }
4187
6135
  function formatViewMarkdown(view) {
@@ -4265,8 +6213,8 @@ async function deleteViewCommand(config, viewId, opts) {
4265
6213
  throw new Error("Destructive operation requires --confirm flag in non-interactive mode");
4266
6214
  }
4267
6215
  const view = await client.getView(viewId);
4268
- const { confirm: confirm2 } = await import("@inquirer/prompts");
4269
- const confirmed = await confirm2({
6216
+ const { confirm: confirm3 } = await import("@inquirer/prompts");
6217
+ const confirmed = await confirm3({
4270
6218
  message: `Delete view "${view.name}" (${viewId})? This cannot be undone.`,
4271
6219
  default: false
4272
6220
  });
@@ -4625,7 +6573,6 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
4625
6573
  wrapAction(async (opts) => {
4626
6574
  const config = loadConfig(getProfileName());
4627
6575
  if (opts.list === "sprint:current") {
4628
- const { resolveActiveSprintListId } = await import("./sprint-NLMMC4RB.js");
4629
6576
  opts.list = await resolveActiveSprintListId(config);
4630
6577
  }
4631
6578
  if (opts.assignee === "me") {
@@ -4707,14 +6654,33 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
4707
6654
  }
4708
6655
  )
4709
6656
  );
4710
- 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(
6657
+ program.command("comment-delete [commentId]").description(
6658
+ "Delete a comment by ID, or use --task with --mine to find and delete your comment"
6659
+ ).option("--task <taskId>", "Task to search for your comment (requires --mine)").option("--mine", "Delete one of my comments from the specified task").option("--match <text>", "Only match comments containing this text (requires --mine)").option("--json", "Force JSON output even in terminal").action(
4711
6660
  wrapAction(
4712
6661
  async (commentId, opts) => {
6662
+ if (opts.mine && !opts.task) {
6663
+ throw new Error("--mine requires --task <taskId>");
6664
+ }
6665
+ if (opts.match && !opts.mine) {
6666
+ throw new Error("--match requires --mine");
6667
+ }
4713
6668
  const config = loadConfig(getProfileName());
4714
- const result = opts.mine || opts.match ? await deleteCommentByTaskSelection(config, commentId, {
4715
- mine: opts.mine,
4716
- match: opts.match
4717
- }) : (await deleteComment(config, commentId), { commentId });
6669
+ let result;
6670
+ if (opts.task) {
6671
+ if (!opts.mine) {
6672
+ throw new Error("--task requires --mine");
6673
+ }
6674
+ result = await deleteCommentByTaskSelection(config, opts.task, {
6675
+ mine: opts.mine,
6676
+ match: opts.match
6677
+ });
6678
+ } else if (commentId) {
6679
+ await deleteComment(config, commentId);
6680
+ result = { commentId };
6681
+ } else {
6682
+ throw new Error("Provide a comment ID or use --task <taskId> --mine");
6683
+ }
4718
6684
  if (shouldOutputJson(opts.json ?? false)) {
4719
6685
  console.log(JSON.stringify({ success: true, ...result }, null, 2));
4720
6686
  } else {
@@ -4946,7 +6912,6 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
4946
6912
  wrapAction(async (taskId, opts) => {
4947
6913
  const config = loadConfig(getProfileName());
4948
6914
  if (opts.to === "sprint:current") {
4949
- const { resolveActiveSprintListId } = await import("./sprint-NLMMC4RB.js");
4950
6915
  opts.to = await resolveActiveSprintListId(config);
4951
6916
  }
4952
6917
  const message = await moveTask(config, taskId, opts);
@@ -5988,7 +7953,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5988
7953
  );
5989
7954
  profileCmd.command("add <name>").description("Add a new profile").action(
5990
7955
  wrapAction(async (name) => {
5991
- const { password: password2, select: select2 } = await import("@inquirer/prompts");
7956
+ const { password: password2, select: select3 } = await import("@inquirer/prompts");
5992
7957
  const apiToken = (await password2({ message: "ClickUp API token (pk_...):" })).trim();
5993
7958
  if (!apiToken.startsWith("pk_")) throw new Error("Token must start with pk_");
5994
7959
  const client = new ClickUpClient({ apiToken });
@@ -6003,7 +7968,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
6003
7968
  process.stdout.write(`Workspace: ${teams[0].name}
6004
7969
  `);
6005
7970
  } else {
6006
- teamId = await select2({
7971
+ teamId = await select3({
6007
7972
  message: "Select workspace:",
6008
7973
  choices: teams.map((t) => ({ name: t.name, value: t.id }))
6009
7974
  });
@@ -6041,7 +8006,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
6041
8006
  );
6042
8007
  configCmd.command("path").description("Print config file path").action(
6043
8008
  wrapAction(async () => {
6044
- console.log(configPath());
8009
+ console.log(configPath2());
6045
8010
  })
6046
8011
  );
6047
8012
  program.command("completion <shell>").description("Output shell completion script (bash, zsh, fish)").action(