@krodak/clickup-cli 0.13.2 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -120,6 +120,9 @@ var ClickUpClient = class {
120
120
  async getSpaceWithStatuses(spaceId) {
121
121
  return this.request(`/space/${spaceId}`);
122
122
  }
123
+ async getListWithStatuses(listId) {
124
+ return this.request(`/list/${listId}`);
125
+ }
123
126
  async getSpaces(teamId) {
124
127
  const data = await this.request(`/team/${teamId}/space?archived=false`);
125
128
  return data.spaces ?? [];
@@ -193,6 +196,105 @@ var ClickUpClient = class {
193
196
  method: "DELETE"
194
197
  });
195
198
  }
199
+ async updateComment(commentId, text, resolved) {
200
+ const body = { comment_text: text };
201
+ if (resolved !== void 0) body.resolved = resolved;
202
+ await this.request(`/comment/${commentId}`, {
203
+ method: "PUT",
204
+ body: JSON.stringify(body)
205
+ });
206
+ }
207
+ async getListCustomFields(listId) {
208
+ const data = await this.request(`/list/${listId}/field`);
209
+ return data.fields ?? [];
210
+ }
211
+ async createChecklist(taskId, name) {
212
+ const data = await this.request(`/task/${taskId}/checklist`, {
213
+ method: "POST",
214
+ body: JSON.stringify({ name })
215
+ });
216
+ return data.checklist;
217
+ }
218
+ async deleteChecklist(checklistId) {
219
+ await this.request(`/checklist/${checklistId}`, { method: "DELETE" });
220
+ }
221
+ async createChecklistItem(checklistId, name) {
222
+ const data = await this.request(
223
+ `/checklist/${checklistId}/checklist_item`,
224
+ { method: "POST", body: JSON.stringify({ name }) }
225
+ );
226
+ return data.checklist;
227
+ }
228
+ async editChecklistItem(checklistId, checklistItemId, updates) {
229
+ const data = await this.request(
230
+ `/checklist/${checklistId}/checklist_item/${checklistItemId}`,
231
+ { method: "PUT", body: JSON.stringify(updates) }
232
+ );
233
+ return data.checklist;
234
+ }
235
+ async deleteChecklistItem(checklistId, checklistItemId) {
236
+ await this.request(
237
+ `/checklist/${checklistId}/checklist_item/${checklistItemId}`,
238
+ { method: "DELETE" }
239
+ );
240
+ }
241
+ async startTimeEntry(teamId, taskId, description) {
242
+ const body = {
243
+ tid: taskId,
244
+ start: Date.now(),
245
+ duration: -1
246
+ };
247
+ if (description) body.description = description;
248
+ const data = await this.request(`/team/${teamId}/time_entries/start`, {
249
+ method: "POST",
250
+ body: JSON.stringify(body)
251
+ });
252
+ return data.data;
253
+ }
254
+ async stopTimeEntry(teamId) {
255
+ const data = await this.request(`/team/${teamId}/time_entries/stop`, {
256
+ method: "POST"
257
+ });
258
+ return data.data;
259
+ }
260
+ async getRunningTimeEntry(teamId) {
261
+ const data = await this.request(
262
+ `/team/${teamId}/time_entries/current`
263
+ );
264
+ return data.data ?? null;
265
+ }
266
+ async createTimeEntry(teamId, taskId, duration, opts) {
267
+ const start = opts?.start ?? Date.now() - duration;
268
+ const body = {
269
+ tid: taskId,
270
+ start,
271
+ duration
272
+ };
273
+ if (opts?.description) body.description = opts.description;
274
+ const data = await this.request(`/team/${teamId}/time_entries`, {
275
+ method: "POST",
276
+ body: JSON.stringify(body)
277
+ });
278
+ return data.data;
279
+ }
280
+ async getTimeEntries(teamId, opts) {
281
+ const params = new URLSearchParams();
282
+ if (opts?.startDate != null) params.set("start_date", String(opts.startDate));
283
+ if (opts?.endDate != null) params.set("end_date", String(opts.endDate));
284
+ const query = params.toString();
285
+ const url = `/team/${teamId}/time_entries${query ? `?${query}` : ""}`;
286
+ const data = await this.request(url);
287
+ const entries = data.data ?? [];
288
+ if (opts?.taskId) {
289
+ return entries.filter((e) => e.task?.id === opts.taskId);
290
+ }
291
+ return entries;
292
+ }
293
+ async deleteTimeEntry(teamId, timeEntryId) {
294
+ await this.request(`/team/${teamId}/time_entries/${timeEntryId}`, {
295
+ method: "DELETE"
296
+ });
297
+ }
196
298
  };
197
299
 
198
300
  // src/config.ts
@@ -414,6 +516,17 @@ function formatTaskDetailMarkdown(task) {
414
516
  if (descriptionContent) {
415
517
  lines.push("", "## Description", "", descriptionContent);
416
518
  }
519
+ if (task.checklists?.length) {
520
+ lines.push("", "## Checklists", "");
521
+ for (const cl of task.checklists) {
522
+ const resolved = cl.items.filter((i) => i.resolved).length;
523
+ lines.push(`### ${cl.name} (${resolved}/${cl.items.length})`, "");
524
+ for (const item of cl.items) {
525
+ lines.push(`- [${item.resolved ? "x" : " "}] ${item.name}`);
526
+ }
527
+ lines.push("");
528
+ }
529
+ }
417
530
  return lines.join("\n");
418
531
  }
419
532
  function formatUpdateConfirmation(id, name) {
@@ -548,6 +661,18 @@ function formatTaskDetail(task) {
548
661
  }
549
662
  }
550
663
  }
664
+ if (task.checklists?.length) {
665
+ lines.push("");
666
+ lines.push(chalk2.bold("Checklists"));
667
+ for (const cl of task.checklists) {
668
+ const resolved = cl.items.filter((i) => i.resolved).length;
669
+ lines.push(` ${chalk2.bold(cl.name)} (${resolved}/${cl.items.length})`);
670
+ for (const item of cl.items) {
671
+ const check = item.resolved ? chalk2.green("[x]") : chalk2.dim("[ ]");
672
+ lines.push(` ${check} ${item.name}`);
673
+ }
674
+ }
675
+ }
551
676
  if (task.text_content?.trim()) {
552
677
  lines.push("");
553
678
  lines.push(descriptionPreview(task.text_content));
@@ -800,9 +925,8 @@ function hasUpdateFields(options) {
800
925
  }
801
926
  async function resolveStatus(client, taskId, statusInput) {
802
927
  const task = await client.getTask(taskId);
803
- if (!task.space) return statusInput;
804
- const space = await client.getSpaceWithStatuses(task.space.id);
805
- const available = space.statuses.map((s) => s.status);
928
+ const list = await client.getListWithStatuses(task.list.id);
929
+ const available = list.statuses.map((s) => s.status);
806
930
  const matched = matchStatus(statusInput, available);
807
931
  if (!matched) {
808
932
  throw new Error(`No matching status for "${statusInput}". Available: ${available.join(", ")}`);
@@ -1684,7 +1808,7 @@ function bashCompletion() {
1684
1808
  cword=$COMP_CWORD
1685
1809
  fi
1686
1810
 
1687
- local commands="init auth tasks task update create sprint sprints subtasks comment comments activity lists spaces inbox assigned open search summary overdue assign depend move field delete tag config completion"
1811
+ local commands="init auth tasks task update create sprint sprints subtasks comment comment-edit comments activity lists spaces inbox assigned open search summary overdue assign depend move field delete tag checklist time config completion"
1688
1812
 
1689
1813
  if [[ $cword -eq 1 ]]; then
1690
1814
  COMPREPLY=($(compgen -W "$commands --help --version" -- "$cur"))
@@ -1780,6 +1904,19 @@ function bashCompletion() {
1780
1904
  tag)
1781
1905
  COMPREPLY=($(compgen -W "--add --remove --json" -- "$cur"))
1782
1906
  ;;
1907
+ checklist)
1908
+ if [[ $cword -eq 2 ]]; then
1909
+ COMPREPLY=($(compgen -W "view create delete add-item edit-item delete-item" -- "$cur"))
1910
+ fi
1911
+ ;;
1912
+ time)
1913
+ if [[ $cword -eq 2 ]]; then
1914
+ COMPREPLY=($(compgen -W "start stop status log list" -- "$cur"))
1915
+ fi
1916
+ ;;
1917
+ comment-edit)
1918
+ COMPREPLY=($(compgen -W "-m --message --resolved --unresolved --json" -- "$cur"))
1919
+ ;;
1783
1920
  config)
1784
1921
  if [[ $cword -eq 2 ]]; then
1785
1922
  COMPREPLY=($(compgen -W "get set path" -- "$cur"))
@@ -1832,6 +1969,9 @@ _cu() {
1832
1969
  'field:Set or remove a custom field value on a task'
1833
1970
  'delete:Delete a task'
1834
1971
  'tag:Add or remove tags from a task'
1972
+ 'checklist:Manage checklists on a task'
1973
+ 'time:Track time on tasks'
1974
+ 'comment-edit:Edit an existing comment'
1835
1975
  'config:Manage CLI configuration'
1836
1976
  'completion:Output shell completion script'
1837
1977
  )
@@ -2019,6 +2159,109 @@ _cu() {
2019
2159
  '--remove[Comma-separated tag names to remove]:tags:' \\
2020
2160
  '--json[Force JSON output]'
2021
2161
  ;;
2162
+ checklist)
2163
+ local -a checklist_cmds
2164
+ checklist_cmds=(
2165
+ 'view:View checklists on a task'
2166
+ 'create:Create a checklist on a task'
2167
+ 'delete:Delete a checklist'
2168
+ 'add-item:Add an item to a checklist'
2169
+ 'edit-item:Edit a checklist item'
2170
+ 'delete-item:Delete a checklist item'
2171
+ )
2172
+ _arguments -C \\
2173
+ '1:checklist command:->checklist_cmd' \\
2174
+ '*::checklist_arg:->checklist_args'
2175
+ case $state in
2176
+ checklist_cmd)
2177
+ _describe 'checklist command' checklist_cmds
2178
+ ;;
2179
+ checklist_args)
2180
+ case $words[1] in
2181
+ view)
2182
+ _arguments '1:task_id:' '--json[Force JSON output]'
2183
+ ;;
2184
+ create)
2185
+ _arguments '1:task_id:' '2:name:' '--json[Force JSON output]'
2186
+ ;;
2187
+ delete)
2188
+ _arguments '1:checklist_id:' '--json[Force JSON output]'
2189
+ ;;
2190
+ add-item)
2191
+ _arguments '1:checklist_id:' '2:name:' '--json[Force JSON output]'
2192
+ ;;
2193
+ edit-item)
2194
+ _arguments \\
2195
+ '1:checklist_id:' \\
2196
+ '2:checklist_item_id:' \\
2197
+ '--name[New item name]:name:' \\
2198
+ '--resolved[Mark item as resolved]' \\
2199
+ '--unresolved[Mark item as unresolved]' \\
2200
+ '--assignee[Assign user by ID]:user_id:' \\
2201
+ '--json[Force JSON output]'
2202
+ ;;
2203
+ delete-item)
2204
+ _arguments '1:checklist_id:' '2:checklist_item_id:' '--json[Force JSON output]'
2205
+ ;;
2206
+ esac
2207
+ ;;
2208
+ esac
2209
+ ;;
2210
+ time)
2211
+ local -a time_cmds
2212
+ time_cmds=(
2213
+ 'start:Start tracking time on a task'
2214
+ 'stop:Stop the running timer'
2215
+ 'status:Show the currently running timer'
2216
+ 'log:Log a manual time entry'
2217
+ 'list:List recent time entries'
2218
+ )
2219
+ _arguments -C \\
2220
+ '1:time command:->time_cmd' \\
2221
+ '*::time_arg:->time_args'
2222
+ case $state in
2223
+ time_cmd)
2224
+ _describe 'time command' time_cmds
2225
+ ;;
2226
+ time_args)
2227
+ case $words[1] in
2228
+ start)
2229
+ _arguments \\
2230
+ '1:task_id:' \\
2231
+ '(-d --description)'{-d,--description}'[Description]:text:' \\
2232
+ '--json[Force JSON output]'
2233
+ ;;
2234
+ stop)
2235
+ _arguments '--json[Force JSON output]'
2236
+ ;;
2237
+ status)
2238
+ _arguments '--json[Force JSON output]'
2239
+ ;;
2240
+ log)
2241
+ _arguments \\
2242
+ '1:task_id:' \\
2243
+ '2:duration:' \\
2244
+ '(-d --description)'{-d,--description}'[Description]:text:' \\
2245
+ '--json[Force JSON output]'
2246
+ ;;
2247
+ list)
2248
+ _arguments \\
2249
+ '--days[Number of days to look back]:days:' \\
2250
+ '--task[Filter by task ID]:task_id:' \\
2251
+ '--json[Force JSON output]'
2252
+ ;;
2253
+ esac
2254
+ ;;
2255
+ esac
2256
+ ;;
2257
+ comment-edit)
2258
+ _arguments \\
2259
+ '1:comment_id:' \\
2260
+ '(-m --message)'{-m,--message}'[New comment text]:text:' \\
2261
+ '--resolved[Mark comment as resolved]' \\
2262
+ '--unresolved[Mark comment as unresolved]' \\
2263
+ '--json[Force JSON output]'
2264
+ ;;
2022
2265
  config)
2023
2266
  local -a config_cmds
2024
2267
  config_cmds=(
@@ -2085,6 +2328,9 @@ complete -c cu -n __fish_use_subcommand -a move -d 'Add or remove a task from a
2085
2328
  complete -c cu -n __fish_use_subcommand -a field -d 'Set or remove a custom field value on a task'
2086
2329
  complete -c cu -n __fish_use_subcommand -a delete -d 'Delete a task'
2087
2330
  complete -c cu -n __fish_use_subcommand -a tag -d 'Add or remove tags from a task'
2331
+ complete -c cu -n __fish_use_subcommand -a checklist -d 'Manage checklists on a task'
2332
+ complete -c cu -n __fish_use_subcommand -a time -d 'Track time on tasks'
2333
+ complete -c cu -n __fish_use_subcommand -a comment-edit -d 'Edit an existing comment'
2088
2334
  complete -c cu -n __fish_use_subcommand -a config -d 'Manage CLI configuration'
2089
2335
  complete -c cu -n __fish_use_subcommand -a completion -d 'Output shell completion script'
2090
2336
 
@@ -2194,6 +2440,34 @@ complete -c cu -n '__fish_seen_subcommand_from tag' -l add -d 'Comma-separated t
2194
2440
  complete -c cu -n '__fish_seen_subcommand_from tag' -l remove -d 'Comma-separated tag names to remove'
2195
2441
  complete -c cu -n '__fish_seen_subcommand_from tag' -l json -d 'Force JSON output'
2196
2442
 
2443
+ complete -c cu -n '__fish_seen_subcommand_from checklist; and not __fish_seen_subcommand_from view create delete add-item edit-item delete-item' -a view -d 'View checklists on a task'
2444
+ complete -c cu -n '__fish_seen_subcommand_from checklist; and not __fish_seen_subcommand_from view create delete add-item edit-item delete-item' -a create -d 'Create a checklist on a task'
2445
+ complete -c cu -n '__fish_seen_subcommand_from checklist; and not __fish_seen_subcommand_from view create delete add-item edit-item delete-item' -a delete -d 'Delete a checklist'
2446
+ complete -c cu -n '__fish_seen_subcommand_from checklist; and not __fish_seen_subcommand_from view create delete add-item edit-item delete-item' -a add-item -d 'Add an item to a checklist'
2447
+ complete -c cu -n '__fish_seen_subcommand_from checklist; and not __fish_seen_subcommand_from view create delete add-item edit-item delete-item' -a edit-item -d 'Edit a checklist item'
2448
+ complete -c cu -n '__fish_seen_subcommand_from checklist; and not __fish_seen_subcommand_from view create delete add-item edit-item delete-item' -a delete-item -d 'Delete a checklist item'
2449
+ complete -c cu -n '__fish_seen_subcommand_from view create delete add-item edit-item delete-item' -l json -d 'Force JSON output'
2450
+ complete -c cu -n '__fish_seen_subcommand_from edit-item' -l name -d 'New item name'
2451
+ complete -c cu -n '__fish_seen_subcommand_from edit-item' -l resolved -d 'Mark item as resolved'
2452
+ complete -c cu -n '__fish_seen_subcommand_from edit-item' -l unresolved -d 'Mark item as unresolved'
2453
+ complete -c cu -n '__fish_seen_subcommand_from edit-item' -l assignee -d 'Assign user by ID'
2454
+
2455
+ complete -c cu -n '__fish_seen_subcommand_from time; and not __fish_seen_subcommand_from start stop status log list' -a start -d 'Start tracking time on a task'
2456
+ complete -c cu -n '__fish_seen_subcommand_from time; and not __fish_seen_subcommand_from start stop status log list' -a stop -d 'Stop the running timer'
2457
+ complete -c cu -n '__fish_seen_subcommand_from time; and not __fish_seen_subcommand_from start stop status log list' -a status -d 'Show the currently running timer'
2458
+ complete -c cu -n '__fish_seen_subcommand_from time; and not __fish_seen_subcommand_from start stop status log list' -a log -d 'Log a manual time entry'
2459
+ complete -c cu -n '__fish_seen_subcommand_from time; and not __fish_seen_subcommand_from start stop status log list' -a list -d 'List recent time entries'
2460
+ complete -c cu -n '__fish_seen_subcommand_from start stop status log list; and __fish_seen_subcommand_from time' -l json -d 'Force JSON output'
2461
+ complete -c cu -n '__fish_seen_subcommand_from start; and __fish_seen_subcommand_from time' -s d -l description -d 'Description'
2462
+ complete -c cu -n '__fish_seen_subcommand_from log; and __fish_seen_subcommand_from time' -s d -l description -d 'Description'
2463
+ complete -c cu -n '__fish_seen_subcommand_from list; and __fish_seen_subcommand_from time' -l days -d 'Number of days to look back'
2464
+ complete -c cu -n '__fish_seen_subcommand_from list; and __fish_seen_subcommand_from time' -l task -d 'Filter by task ID'
2465
+
2466
+ complete -c cu -n '__fish_seen_subcommand_from comment-edit' -s m -l message -d 'New comment text'
2467
+ complete -c cu -n '__fish_seen_subcommand_from comment-edit' -l resolved -d 'Mark comment as resolved'
2468
+ complete -c cu -n '__fish_seen_subcommand_from comment-edit' -l unresolved -d 'Mark comment as unresolved'
2469
+ complete -c cu -n '__fish_seen_subcommand_from comment-edit' -l json -d 'Force JSON output'
2470
+
2197
2471
  complete -c cu -n '__fish_seen_subcommand_from config; and not __fish_seen_subcommand_from get set path' -a get -d 'Print a config value'
2198
2472
  complete -c cu -n '__fish_seen_subcommand_from config; and not __fish_seen_subcommand_from get set path' -a set -d 'Set a config value'
2199
2473
  complete -c cu -n '__fish_seen_subcommand_from config; and not __fish_seen_subcommand_from get set path' -a path -d 'Print config file path'
@@ -2448,6 +2722,124 @@ async function manageTags(config, taskId, opts) {
2448
2722
  return { taskId, added, removed };
2449
2723
  }
2450
2724
 
2725
+ // src/commands/checklist.ts
2726
+ import chalk5 from "chalk";
2727
+ async function viewChecklists(config, taskId) {
2728
+ const client = new ClickUpClient(config);
2729
+ const task = await client.getTask(taskId);
2730
+ return task.checklists ?? [];
2731
+ }
2732
+ async function createChecklist(config, taskId, name) {
2733
+ const client = new ClickUpClient(config);
2734
+ return client.createChecklist(taskId, name);
2735
+ }
2736
+ async function deleteChecklist(config, checklistId) {
2737
+ const client = new ClickUpClient(config);
2738
+ await client.deleteChecklist(checklistId);
2739
+ return { checklistId };
2740
+ }
2741
+ async function addChecklistItem(config, checklistId, name) {
2742
+ const client = new ClickUpClient(config);
2743
+ return client.createChecklistItem(checklistId, name);
2744
+ }
2745
+ async function editChecklistItem(config, checklistId, checklistItemId, updates) {
2746
+ const client = new ClickUpClient(config);
2747
+ return client.editChecklistItem(checklistId, checklistItemId, updates);
2748
+ }
2749
+ async function deleteChecklistItem(config, checklistId, checklistItemId) {
2750
+ const client = new ClickUpClient(config);
2751
+ await client.deleteChecklistItem(checklistId, checklistItemId);
2752
+ return { checklistId, checklistItemId };
2753
+ }
2754
+ function formatChecklists(checklists) {
2755
+ if (checklists.length === 0) return "No checklists";
2756
+ const lines = [];
2757
+ for (const cl of checklists) {
2758
+ const resolved = cl.items.filter((i) => i.resolved).length;
2759
+ lines.push(chalk5.bold(`${cl.name} (${resolved}/${cl.items.length})`));
2760
+ lines.push(chalk5.dim(` ID: ${cl.id}`));
2761
+ for (const item of cl.items) {
2762
+ const check = item.resolved ? chalk5.green("[x]") : chalk5.dim("[ ]");
2763
+ const assignee = item.assignee ? chalk5.dim(` @${item.assignee.username}`) : "";
2764
+ lines.push(` ${check} ${item.name}${assignee}`);
2765
+ lines.push(chalk5.dim(` item-id: ${item.id}`));
2766
+ }
2767
+ }
2768
+ return lines.join("\n");
2769
+ }
2770
+
2771
+ // src/commands/comment-edit.ts
2772
+ async function editComment(config, commentId, text, resolved) {
2773
+ if (!text.trim()) throw new Error("Comment text cannot be empty");
2774
+ const client = new ClickUpClient(config);
2775
+ await client.updateComment(commentId, text, resolved);
2776
+ }
2777
+
2778
+ // src/commands/time.ts
2779
+ import chalk6 from "chalk";
2780
+ function formatDuration2(ms) {
2781
+ const totalMinutes = Math.round(Math.abs(ms) / 6e4);
2782
+ const hours = Math.floor(totalMinutes / 60);
2783
+ const minutes = totalMinutes % 60;
2784
+ if (hours > 0 && minutes > 0) return `${hours}h ${minutes}m`;
2785
+ if (hours > 0) return `${hours}h`;
2786
+ return `${minutes}m`;
2787
+ }
2788
+ function formatTimestamp2(ms) {
2789
+ return new Date(Number(ms)).toLocaleString("en-US", {
2790
+ month: "short",
2791
+ day: "numeric",
2792
+ hour: "numeric",
2793
+ minute: "2-digit"
2794
+ });
2795
+ }
2796
+ async function startTimer(config, taskId, description) {
2797
+ const client = new ClickUpClient(config);
2798
+ return client.startTimeEntry(config.teamId, taskId, description);
2799
+ }
2800
+ async function stopTimer(config) {
2801
+ const client = new ClickUpClient(config);
2802
+ return client.stopTimeEntry(config.teamId);
2803
+ }
2804
+ async function timerStatus(config) {
2805
+ const client = new ClickUpClient(config);
2806
+ return client.getRunningTimeEntry(config.teamId);
2807
+ }
2808
+ async function logTime(config, taskId, durationStr, description) {
2809
+ const client = new ClickUpClient(config);
2810
+ const duration = parseTimeEstimate(durationStr);
2811
+ return client.createTimeEntry(config.teamId, taskId, duration, { description });
2812
+ }
2813
+ async function listTimeEntries(config, opts) {
2814
+ const client = new ClickUpClient(config);
2815
+ const days = opts?.days ?? 7;
2816
+ const endDate = Date.now();
2817
+ const startDate = endDate - days * 24 * 60 * 60 * 1e3;
2818
+ return client.getTimeEntries(config.teamId, {
2819
+ startDate,
2820
+ endDate,
2821
+ taskId: opts?.taskId
2822
+ });
2823
+ }
2824
+ function formatTimeEntry(entry) {
2825
+ const lines = [];
2826
+ const taskName = entry.task?.name ?? "No task";
2827
+ const taskId = entry.task?.id ?? "";
2828
+ const isRunning = entry.duration < 0;
2829
+ const elapsed = isRunning ? Date.now() - Number(entry.start) : entry.duration;
2830
+ const durationStr = formatDuration2(elapsed);
2831
+ const status = isRunning ? chalk6.green("RUNNING") : "";
2832
+ lines.push(`${chalk6.bold(taskName)} ${chalk6.dim(taskId)} ${status}`);
2833
+ lines.push(
2834
+ ` ${durationStr} - ${formatTimestamp2(entry.start)}${entry.description ? ` - ${entry.description}` : ""}`
2835
+ );
2836
+ return lines.join("\n");
2837
+ }
2838
+ function formatTimeEntries(entries) {
2839
+ if (entries.length === 0) return "No time entries";
2840
+ return entries.map(formatTimeEntry).join("\n");
2841
+ }
2842
+
2451
2843
  // src/index.ts
2452
2844
  var require2 = createRequire(import.meta.url);
2453
2845
  var { version } = require2("../package.json");
@@ -2589,6 +2981,22 @@ program.command("comments <taskId>").description("List comments on a task").opti
2589
2981
  printComments(comments, opts.json ?? false);
2590
2982
  })
2591
2983
  );
2984
+ program.command("comment-edit <commentId>").description("Edit an existing comment").requiredOption("-m, --message <text>", "New comment text").option("--resolved", "Mark comment as resolved").option("--unresolved", "Mark comment as unresolved").option("--json", "Force JSON output even in terminal").action(
2985
+ wrapAction(
2986
+ async (commentId, opts) => {
2987
+ const config = loadConfig();
2988
+ let resolved;
2989
+ if (opts.resolved) resolved = true;
2990
+ if (opts.unresolved) resolved = false;
2991
+ await editComment(config, commentId, opts.message, resolved);
2992
+ if (shouldOutputJson(opts.json ?? false)) {
2993
+ console.log(JSON.stringify({ success: true, commentId }, null, 2));
2994
+ } else {
2995
+ console.log(`Comment ${commentId} updated`);
2996
+ }
2997
+ }
2998
+ )
2999
+ );
2592
3000
  program.command("activity <taskId>").description("Show task details and comments combined").option("--json", "Force JSON output even in terminal").action(
2593
3001
  wrapAction(async (taskId, opts) => {
2594
3002
  const config = loadConfig();
@@ -2754,6 +3162,147 @@ program.command("tag <taskId>").description("Add or remove tags from a task").op
2754
3162
  }
2755
3163
  })
2756
3164
  );
3165
+ var checklistCmd = program.command("checklist").description("Manage checklists on a task");
3166
+ checklistCmd.command("view <taskId>").description("View checklists on a task").option("--json", "Force JSON output even in terminal").action(
3167
+ wrapAction(async (taskId, opts) => {
3168
+ const config = loadConfig();
3169
+ const checklists = await viewChecklists(config, taskId);
3170
+ if (shouldOutputJson(opts.json ?? false)) {
3171
+ console.log(JSON.stringify(checklists, null, 2));
3172
+ } else {
3173
+ console.log(formatChecklists(checklists));
3174
+ }
3175
+ })
3176
+ );
3177
+ checklistCmd.command("create <taskId> <name>").description("Create a checklist on a task").option("--json", "Force JSON output even in terminal").action(
3178
+ wrapAction(async (taskId, name, opts) => {
3179
+ const config = loadConfig();
3180
+ const result = await createChecklist(config, taskId, name);
3181
+ if (shouldOutputJson(opts.json ?? false)) {
3182
+ console.log(JSON.stringify(result, null, 2));
3183
+ } else {
3184
+ console.log(`Created checklist "${result.name}" (id: ${result.id})`);
3185
+ }
3186
+ })
3187
+ );
3188
+ checklistCmd.command("delete <checklistId>").description("Delete a checklist").option("--json", "Force JSON output even in terminal").action(
3189
+ wrapAction(async (checklistId, opts) => {
3190
+ const config = loadConfig();
3191
+ const result = await deleteChecklist(config, checklistId);
3192
+ if (shouldOutputJson(opts.json ?? false)) {
3193
+ console.log(JSON.stringify(result, null, 2));
3194
+ } else {
3195
+ console.log(`Deleted checklist ${result.checklistId}`);
3196
+ }
3197
+ })
3198
+ );
3199
+ checklistCmd.command("add-item <checklistId> <name>").description("Add an item to a checklist").option("--json", "Force JSON output even in terminal").action(
3200
+ wrapAction(async (checklistId, name, opts) => {
3201
+ const config = loadConfig();
3202
+ const result = await addChecklistItem(config, checklistId, name);
3203
+ if (shouldOutputJson(opts.json ?? false)) {
3204
+ console.log(JSON.stringify(result, null, 2));
3205
+ } else {
3206
+ console.log(`Added item "${name}" to checklist ${checklistId}`);
3207
+ }
3208
+ })
3209
+ );
3210
+ checklistCmd.command("edit-item <checklistId> <checklistItemId>").description("Edit a checklist item").option("--name <name>", "New item name").option("--resolved", "Mark item as resolved").option("--unresolved", "Mark item as unresolved").option("--assignee <userId>", 'Assign user by ID (use "null" to unassign)').option("--json", "Force JSON output even in terminal").action(
3211
+ wrapAction(
3212
+ async (checklistId, checklistItemId, opts) => {
3213
+ const config = loadConfig();
3214
+ const updates = {};
3215
+ if (opts.name) updates.name = opts.name;
3216
+ if (opts.resolved) updates.resolved = true;
3217
+ if (opts.unresolved) updates.resolved = false;
3218
+ if (opts.assignee !== void 0) {
3219
+ updates.assignee = opts.assignee === "null" ? null : Number(opts.assignee);
3220
+ }
3221
+ const result = await editChecklistItem(config, checklistId, checklistItemId, updates);
3222
+ if (shouldOutputJson(opts.json ?? false)) {
3223
+ console.log(JSON.stringify(result, null, 2));
3224
+ } else {
3225
+ console.log(`Updated checklist item ${checklistItemId}`);
3226
+ }
3227
+ }
3228
+ )
3229
+ );
3230
+ checklistCmd.command("delete-item <checklistId> <checklistItemId>").description("Delete a checklist item").option("--json", "Force JSON output even in terminal").action(
3231
+ wrapAction(async (checklistId, checklistItemId, opts) => {
3232
+ const config = loadConfig();
3233
+ const result = await deleteChecklistItem(config, checklistId, checklistItemId);
3234
+ if (shouldOutputJson(opts.json ?? false)) {
3235
+ console.log(JSON.stringify(result, null, 2));
3236
+ } else {
3237
+ console.log(`Deleted checklist item ${result.checklistItemId}`);
3238
+ }
3239
+ })
3240
+ );
3241
+ var timeCmd = program.command("time").description("Track time on tasks");
3242
+ timeCmd.command("start <taskId>").description("Start tracking time on a task").option("-d, --description <text>", "Description for the time entry").option("--json", "Force JSON output even in terminal").action(
3243
+ wrapAction(async (taskId, opts) => {
3244
+ const config = loadConfig();
3245
+ const result = await startTimer(config, taskId, opts.description);
3246
+ if (shouldOutputJson(opts.json ?? false)) {
3247
+ console.log(JSON.stringify(result, null, 2));
3248
+ } else {
3249
+ const taskName = result.task?.name ?? taskId;
3250
+ console.log(`Started timer on "${taskName}"`);
3251
+ }
3252
+ })
3253
+ );
3254
+ timeCmd.command("stop").description("Stop the running timer").option("--json", "Force JSON output even in terminal").action(
3255
+ wrapAction(async (opts) => {
3256
+ const config = loadConfig();
3257
+ const result = await stopTimer(config);
3258
+ if (shouldOutputJson(opts.json ?? false)) {
3259
+ console.log(JSON.stringify(result, null, 2));
3260
+ } else {
3261
+ console.log(formatTimeEntry(result));
3262
+ }
3263
+ })
3264
+ );
3265
+ timeCmd.command("status").description("Show the currently running timer").option("--json", "Force JSON output even in terminal").action(
3266
+ wrapAction(async (opts) => {
3267
+ const config = loadConfig();
3268
+ const result = await timerStatus(config);
3269
+ if (shouldOutputJson(opts.json ?? false)) {
3270
+ console.log(JSON.stringify(result, null, 2));
3271
+ } else if (result) {
3272
+ console.log(formatTimeEntry(result));
3273
+ } else {
3274
+ console.log("No timer running");
3275
+ }
3276
+ })
3277
+ );
3278
+ timeCmd.command("log <taskId> <duration>").description('Log a manual time entry (e.g. "2h", "30m", "1h30m")').option("-d, --description <text>", "Description for the time entry").option("--json", "Force JSON output even in terminal").action(
3279
+ wrapAction(
3280
+ async (taskId, duration, opts) => {
3281
+ const config = loadConfig();
3282
+ const result = await logTime(config, taskId, duration, opts.description);
3283
+ if (shouldOutputJson(opts.json ?? false)) {
3284
+ console.log(JSON.stringify(result, null, 2));
3285
+ } else {
3286
+ console.log(`Logged ${duration} on task ${taskId}`);
3287
+ }
3288
+ }
3289
+ )
3290
+ );
3291
+ timeCmd.command("list").description("List recent time entries (default: last 7 days)").option("--days <n>", "Number of days to look back", "7").option("--task <taskId>", "Filter by task ID").option("--json", "Force JSON output even in terminal").action(
3292
+ wrapAction(async (opts) => {
3293
+ const config = loadConfig();
3294
+ const days = opts.days ? Number(opts.days) : 7;
3295
+ if (!Number.isFinite(days) || days <= 0) {
3296
+ throw new Error("--days must be a positive number");
3297
+ }
3298
+ const entries = await listTimeEntries(config, { days, taskId: opts.task });
3299
+ if (shouldOutputJson(opts.json ?? false)) {
3300
+ console.log(JSON.stringify(entries, null, 2));
3301
+ } else {
3302
+ console.log(formatTimeEntries(entries));
3303
+ }
3304
+ })
3305
+ );
2757
3306
  var configCmd = program.command("config").description("Manage CLI configuration");
2758
3307
  configCmd.command("get <key>").description("Print a config value").action(
2759
3308
  wrapAction(async (key) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@krodak/clickup-cli",
3
- "version": "0.13.2",
3
+ "version": "0.15.0",
4
4
  "description": "ClickUp CLI for AI agents and humans",
5
5
  "type": "module",
6
6
  "license": "MIT",