@krodak/clickup-cli 0.13.1 → 0.14.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/README.md CHANGED
@@ -98,7 +98,7 @@ When output is piped (no TTY), all commands output **Markdown** by default - opt
98
98
 
99
99
  ## Commands
100
100
 
101
- 29 commands total. All support `--help` for full flag details.
101
+ 31 commands total. All support `--help` for full flag details.
102
102
 
103
103
  ### `cu init`
104
104
 
@@ -170,7 +170,7 @@ cu inbox --json
170
170
 
171
171
  ### `cu task <id>`
172
172
 
173
- Get task details including custom fields. Pretty summary in terminal, JSON when piped.
173
+ Get task details including custom fields and checklists. Pretty summary in terminal, JSON when piped.
174
174
 
175
175
  ```bash
176
176
  cu task abc123
@@ -201,6 +201,7 @@ cu update abc123 -n "New task name"
201
201
  cu update abc123 -d "Updated description with **markdown**"
202
202
  cu update abc123 --priority high
203
203
  cu update abc123 --due-date 2025-03-15
204
+ cu update abc123 --assignee me
204
205
  cu update abc123 --assignee 12345
205
206
  cu update abc123 -n "New name" -s "done" --priority urgent
206
207
  cu update abc123 --time-estimate 2h
@@ -216,7 +217,7 @@ cu update abc123 -s "in progress" --json
216
217
  | `--priority <level>` | Priority: `urgent`, `high`, `normal`, `low` (or 1-4) |
217
218
  | `--due-date <date>` | Due date (`YYYY-MM-DD`) |
218
219
  | `--time-estimate <duration>` | Time estimate (e.g. `"2h"`, `"30m"`, `"1h30m"`) |
219
- | `--assignee <userId>` | Add assignee by numeric user ID |
220
+ | `--assignee <userId>` | Add assignee by user ID or `"me"` |
220
221
  | `--parent <taskId>` | Set parent task (makes this a subtask) |
221
222
  | `--json` | Force JSON output even in terminal |
222
223
 
@@ -229,7 +230,7 @@ cu create -n "Fix login bug" -l <listId>
229
230
  cu create -n "Subtask name" -p <parentTaskId> # --list auto-detected
230
231
  cu create -n "Task" -l <listId> -d "desc" -s "open"
231
232
  cu create -n "Task" -l <listId> --priority high --due-date 2025-06-01
232
- cu create -n "Task" -l <listId> --assignee 12345 --tags "bug,frontend"
233
+ cu create -n "Task" -l <listId> --assignee me --tags "bug,frontend"
233
234
  cu create -n "Initiative" -l <listId> --custom-item-id 1
234
235
  cu create -n "Task" -l <listId> --time-estimate 2h
235
236
  cu create -n "Fix bug" -l <listId> --json
@@ -245,7 +246,7 @@ cu create -n "Fix bug" -l <listId> --json
245
246
  | `--priority <level>` | no | Priority: `urgent`, `high`, `normal`, `low` (or 1-4) |
246
247
  | `--due-date <date>` | no | Due date (`YYYY-MM-DD`) |
247
248
  | `--time-estimate <duration>` | no | Time estimate (e.g. `"2h"`, `"30m"`, `"1h30m"`) |
248
- | `--assignee <userId>` | no | Assignee by numeric user ID |
249
+ | `--assignee <userId>` | no | Assignee by user ID or `"me"` |
249
250
  | `--tags <tags>` | no | Comma-separated tag names |
250
251
  | `--custom-item-id <id>` | no | Custom task type ID (e.g. for creating initiatives) |
251
252
  | `--json` | no | Force JSON output even in terminal |
@@ -310,6 +311,24 @@ cu comments abc123
310
311
  cu comments abc123 --json
311
312
  ```
312
313
 
314
+ ### `cu comment-edit <commentId>`
315
+
316
+ Edit an existing comment on a task.
317
+
318
+ ```bash
319
+ cu comment-edit <commentId> -m "Updated text"
320
+ cu comment-edit <commentId> -m "Fixed" --resolved
321
+ cu comment-edit <commentId> -m "Reopening" --unresolved
322
+ cu comment-edit <commentId> -m "Updated" --json
323
+ ```
324
+
325
+ | Flag | Required | Description |
326
+ | --------------- | -------- | -------------------------- |
327
+ | `-m, --message` | yes | New comment text |
328
+ | `--resolved` | no | Mark comment as resolved |
329
+ | `--unresolved` | no | Mark comment as unresolved |
330
+ | `--json` | no | Force JSON output |
331
+
313
332
  ### `cu activity <id>`
314
333
 
315
334
  View task details and comment history together. Combines `cu task` and `cu comments` into a single view.
@@ -472,6 +491,32 @@ cu tag abc123 --add "bug" --json
472
491
  | `--remove <tags>` | Comma-separated tag names to remove |
473
492
  | `--json` | Force JSON output |
474
493
 
494
+ ### `cu checklist`
495
+
496
+ Manage checklists on tasks. Six subcommands for full CRUD operations.
497
+
498
+ ```bash
499
+ cu checklist view abc123 # view all checklists on a task
500
+ cu checklist create abc123 "QA Checklist" # add a checklist
501
+ cu checklist delete <checklistId> # remove a checklist
502
+ cu checklist add-item <checklistId> "Run tests" # add an item
503
+ cu checklist edit-item <clId> <itemId> --resolved # mark item done
504
+ cu checklist delete-item <clId> <itemId> # remove an item
505
+ ```
506
+
507
+ | Subcommand | Arguments | Description |
508
+ | ------------- | -------------------------------- | --------------------- |
509
+ | `view` | `<taskId>` | Show all checklists |
510
+ | `create` | `<taskId> <name>` | Create a checklist |
511
+ | `delete` | `<checklistId>` | Delete a checklist |
512
+ | `add-item` | `<checklistId> <name>` | Add checklist item |
513
+ | `edit-item` | `<checklistId> <itemId> [flags]` | Edit checklist item |
514
+ | `delete-item` | `<checklistId> <itemId>` | Delete checklist item |
515
+
516
+ `edit-item` flags: `--name <text>`, `--resolved`, `--unresolved`, `--assignee <userId>`. All subcommands support `--json`.
517
+
518
+ Checklists are also shown inline in `cu task <id>` detail view.
519
+
475
520
  ### `cu auth`
476
521
 
477
522
  Check authentication status. Validates your API token and shows your user info.
package/dist/index.js CHANGED
@@ -4,72 +4,6 @@
4
4
  import { Command } from "commander";
5
5
  import { createRequire } from "module";
6
6
 
7
- // src/config.ts
8
- import fs from "fs";
9
- import { homedir } from "os";
10
- import { join } from "path";
11
- function configDir() {
12
- const xdg = process.env.XDG_CONFIG_HOME;
13
- if (xdg) return join(xdg, "cu");
14
- return join(homedir(), ".config", "cu");
15
- }
16
- function configPath() {
17
- return join(configDir(), "config.json");
18
- }
19
- function loadConfig() {
20
- const envToken = process.env.CU_API_TOKEN?.trim();
21
- const envTeamId = process.env.CU_TEAM_ID?.trim();
22
- let fileToken;
23
- let fileTeamId;
24
- const path = configPath();
25
- if (fs.existsSync(path)) {
26
- const raw = fs.readFileSync(path, "utf-8");
27
- let parsed;
28
- try {
29
- parsed = JSON.parse(raw);
30
- } catch {
31
- throw new Error(`Config file at ${path} contains invalid JSON. Please check the file syntax.`);
32
- }
33
- fileToken = parsed.apiToken?.trim();
34
- fileTeamId = parsed.teamId?.trim();
35
- }
36
- const apiToken = envToken || fileToken;
37
- if (!apiToken) {
38
- throw new Error("Config missing required field: apiToken.\nSet CU_API_TOKEN or run: cu init");
39
- }
40
- if (!apiToken.startsWith("pk_")) {
41
- throw new Error("Config apiToken must start with pk_. The configured token does not.");
42
- }
43
- const teamId = envTeamId || fileTeamId;
44
- if (!teamId) {
45
- throw new Error("Config missing required field: teamId.\nSet CU_TEAM_ID or run: cu init");
46
- }
47
- return { apiToken, teamId };
48
- }
49
- function loadRawConfig() {
50
- const path = configPath();
51
- if (!fs.existsSync(path)) return {};
52
- try {
53
- return JSON.parse(fs.readFileSync(path, "utf-8"));
54
- } catch {
55
- return {};
56
- }
57
- }
58
- function getConfigPath() {
59
- return configPath();
60
- }
61
- function writeConfig(config) {
62
- const dir = configDir();
63
- if (!fs.existsSync(dir)) {
64
- fs.mkdirSync(dir, { recursive: true, mode: 448 });
65
- }
66
- const filePath = join(dir, "config.json");
67
- fs.writeFileSync(filePath, JSON.stringify(config, null, 2) + "\n", {
68
- encoding: "utf-8",
69
- mode: 384
70
- });
71
- }
72
-
73
7
  // src/api.ts
74
8
  var BASE_URL = "https://api.clickup.com/api/v2";
75
9
  var MAX_PAGES = 100;
@@ -259,8 +193,116 @@ var ClickUpClient = class {
259
193
  method: "DELETE"
260
194
  });
261
195
  }
196
+ async updateComment(commentId, text, resolved) {
197
+ const body = { comment_text: text };
198
+ if (resolved !== void 0) body.resolved = resolved;
199
+ await this.request(`/comment/${commentId}`, {
200
+ method: "PUT",
201
+ body: JSON.stringify(body)
202
+ });
203
+ }
204
+ async getListCustomFields(listId) {
205
+ const data = await this.request(`/list/${listId}/field`);
206
+ return data.fields ?? [];
207
+ }
208
+ async createChecklist(taskId, name) {
209
+ const data = await this.request(`/task/${taskId}/checklist`, {
210
+ method: "POST",
211
+ body: JSON.stringify({ name })
212
+ });
213
+ return data.checklist;
214
+ }
215
+ async deleteChecklist(checklistId) {
216
+ await this.request(`/checklist/${checklistId}`, { method: "DELETE" });
217
+ }
218
+ async createChecklistItem(checklistId, name) {
219
+ const data = await this.request(
220
+ `/checklist/${checklistId}/checklist_item`,
221
+ { method: "POST", body: JSON.stringify({ name }) }
222
+ );
223
+ return data.checklist;
224
+ }
225
+ async editChecklistItem(checklistId, checklistItemId, updates) {
226
+ const data = await this.request(
227
+ `/checklist/${checklistId}/checklist_item/${checklistItemId}`,
228
+ { method: "PUT", body: JSON.stringify(updates) }
229
+ );
230
+ return data.checklist;
231
+ }
232
+ async deleteChecklistItem(checklistId, checklistItemId) {
233
+ await this.request(
234
+ `/checklist/${checklistId}/checklist_item/${checklistItemId}`,
235
+ { method: "DELETE" }
236
+ );
237
+ }
262
238
  };
263
239
 
240
+ // src/config.ts
241
+ import fs from "fs";
242
+ import { homedir } from "os";
243
+ import { join } from "path";
244
+ function configDir() {
245
+ const xdg = process.env.XDG_CONFIG_HOME;
246
+ if (xdg) return join(xdg, "cu");
247
+ return join(homedir(), ".config", "cu");
248
+ }
249
+ function configPath() {
250
+ return join(configDir(), "config.json");
251
+ }
252
+ function loadConfig() {
253
+ const envToken = process.env.CU_API_TOKEN?.trim();
254
+ const envTeamId = process.env.CU_TEAM_ID?.trim();
255
+ let fileToken;
256
+ let fileTeamId;
257
+ const path = configPath();
258
+ if (fs.existsSync(path)) {
259
+ const raw = fs.readFileSync(path, "utf-8");
260
+ let parsed;
261
+ try {
262
+ parsed = JSON.parse(raw);
263
+ } catch {
264
+ throw new Error(`Config file at ${path} contains invalid JSON. Please check the file syntax.`);
265
+ }
266
+ fileToken = parsed.apiToken?.trim();
267
+ fileTeamId = parsed.teamId?.trim();
268
+ }
269
+ const apiToken = envToken || fileToken;
270
+ if (!apiToken) {
271
+ throw new Error("Config missing required field: apiToken.\nSet CU_API_TOKEN or run: cu init");
272
+ }
273
+ if (!apiToken.startsWith("pk_")) {
274
+ throw new Error("Config apiToken must start with pk_. The configured token does not.");
275
+ }
276
+ const teamId = envTeamId || fileTeamId;
277
+ if (!teamId) {
278
+ throw new Error("Config missing required field: teamId.\nSet CU_TEAM_ID or run: cu init");
279
+ }
280
+ return { apiToken, teamId };
281
+ }
282
+ function loadRawConfig() {
283
+ const path = configPath();
284
+ if (!fs.existsSync(path)) return {};
285
+ try {
286
+ return JSON.parse(fs.readFileSync(path, "utf-8"));
287
+ } catch {
288
+ return {};
289
+ }
290
+ }
291
+ function getConfigPath() {
292
+ return configPath();
293
+ }
294
+ function writeConfig(config) {
295
+ const dir = configDir();
296
+ if (!fs.existsSync(dir)) {
297
+ fs.mkdirSync(dir, { recursive: true, mode: 448 });
298
+ }
299
+ const filePath = join(dir, "config.json");
300
+ fs.writeFileSync(filePath, JSON.stringify(config, null, 2) + "\n", {
301
+ encoding: "utf-8",
302
+ mode: 384
303
+ });
304
+ }
305
+
264
306
  // src/date.ts
265
307
  function formatDate(ms) {
266
308
  const d = new Date(Number(ms));
@@ -414,6 +456,17 @@ function formatTaskDetailMarkdown(task) {
414
456
  if (descriptionContent) {
415
457
  lines.push("", "## Description", "", descriptionContent);
416
458
  }
459
+ if (task.checklists?.length) {
460
+ lines.push("", "## Checklists", "");
461
+ for (const cl of task.checklists) {
462
+ const resolved = cl.items.filter((i) => i.resolved).length;
463
+ lines.push(`### ${cl.name} (${resolved}/${cl.items.length})`, "");
464
+ for (const item of cl.items) {
465
+ lines.push(`- [${item.resolved ? "x" : " "}] ${item.name}`);
466
+ }
467
+ lines.push("");
468
+ }
469
+ }
417
470
  return lines.join("\n");
418
471
  }
419
472
  function formatUpdateConfirmation(id, name) {
@@ -548,6 +601,18 @@ function formatTaskDetail(task) {
548
601
  }
549
602
  }
550
603
  }
604
+ if (task.checklists?.length) {
605
+ lines.push("");
606
+ lines.push(chalk2.bold("Checklists"));
607
+ for (const cl of task.checklists) {
608
+ const resolved = cl.items.filter((i) => i.resolved).length;
609
+ lines.push(` ${chalk2.bold(cl.name)} (${resolved}/${cl.items.length})`);
610
+ for (const item of cl.items) {
611
+ const check = item.resolved ? chalk2.green("[x]") : chalk2.dim("[ ]");
612
+ lines.push(` ${check} ${item.name}`);
613
+ }
614
+ }
615
+ }
551
616
  if (task.text_content?.trim()) {
552
617
  lines.push("");
553
618
  lines.push(descriptionPreview(task.text_content));
@@ -754,9 +819,16 @@ function parseDueDate(value) {
754
819
  }
755
820
  function parseAssigneeId(value) {
756
821
  const id = Number(value);
757
- if (!Number.isInteger(id)) throw new Error("Assignee must be a numeric user ID");
822
+ if (!Number.isInteger(id)) throw new Error('Assignee must be a numeric user ID or "me"');
758
823
  return id;
759
824
  }
825
+ async function resolveAssigneeId(client, value) {
826
+ if (value === "me") {
827
+ const user = await client.getMe();
828
+ return user.id;
829
+ }
830
+ return parseAssigneeId(value);
831
+ }
760
832
  function parseTimeEstimate(value) {
761
833
  const pattern = /^(?:(\d+)h)?(?:(\d+)m)?$/i;
762
834
  const match = value.match(pattern);
@@ -1583,13 +1655,6 @@ function configPath2() {
1583
1655
  }
1584
1656
 
1585
1657
  // src/commands/assign.ts
1586
- async function resolveUserId(client, value) {
1587
- if (value === "me") {
1588
- const user = await client.getMe();
1589
- return user.id;
1590
- }
1591
- return parseAssigneeId(value);
1592
- }
1593
1658
  async function assignTask(config, taskId, opts) {
1594
1659
  if (!opts.to && !opts.remove) {
1595
1660
  throw new Error("Provide at least one of: --to, --remove");
@@ -1598,10 +1663,10 @@ async function assignTask(config, taskId, opts) {
1598
1663
  const add = [];
1599
1664
  const rem = [];
1600
1665
  if (opts.to) {
1601
- add.push(await resolveUserId(client, opts.to));
1666
+ add.push(await resolveAssigneeId(client, opts.to));
1602
1667
  }
1603
1668
  if (opts.remove) {
1604
- rem.push(await resolveUserId(client, opts.remove));
1669
+ rem.push(await resolveAssigneeId(client, opts.remove));
1605
1670
  }
1606
1671
  return client.updateTask(taskId, {
1607
1672
  assignees: {
@@ -1684,7 +1749,7 @@ function bashCompletion() {
1684
1749
  cword=$COMP_CWORD
1685
1750
  fi
1686
1751
 
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"
1752
+ 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 config completion"
1688
1753
 
1689
1754
  if [[ $cword -eq 1 ]]; then
1690
1755
  COMPREPLY=($(compgen -W "$commands --help --version" -- "$cur"))
@@ -1780,6 +1845,14 @@ function bashCompletion() {
1780
1845
  tag)
1781
1846
  COMPREPLY=($(compgen -W "--add --remove --json" -- "$cur"))
1782
1847
  ;;
1848
+ checklist)
1849
+ if [[ $cword -eq 2 ]]; then
1850
+ COMPREPLY=($(compgen -W "view create delete add-item edit-item delete-item" -- "$cur"))
1851
+ fi
1852
+ ;;
1853
+ comment-edit)
1854
+ COMPREPLY=($(compgen -W "-m --message --resolved --unresolved --json" -- "$cur"))
1855
+ ;;
1783
1856
  config)
1784
1857
  if [[ $cword -eq 2 ]]; then
1785
1858
  COMPREPLY=($(compgen -W "get set path" -- "$cur"))
@@ -1832,6 +1905,8 @@ _cu() {
1832
1905
  'field:Set or remove a custom field value on a task'
1833
1906
  'delete:Delete a task'
1834
1907
  'tag:Add or remove tags from a task'
1908
+ 'checklist:Manage checklists on a task'
1909
+ 'comment-edit:Edit an existing comment'
1835
1910
  'config:Manage CLI configuration'
1836
1911
  'completion:Output shell completion script'
1837
1912
  )
@@ -2019,6 +2094,62 @@ _cu() {
2019
2094
  '--remove[Comma-separated tag names to remove]:tags:' \\
2020
2095
  '--json[Force JSON output]'
2021
2096
  ;;
2097
+ checklist)
2098
+ local -a checklist_cmds
2099
+ checklist_cmds=(
2100
+ 'view:View checklists on a task'
2101
+ 'create:Create a checklist on a task'
2102
+ 'delete:Delete a checklist'
2103
+ 'add-item:Add an item to a checklist'
2104
+ 'edit-item:Edit a checklist item'
2105
+ 'delete-item:Delete a checklist item'
2106
+ )
2107
+ _arguments -C \\
2108
+ '1:checklist command:->checklist_cmd' \\
2109
+ '*::checklist_arg:->checklist_args'
2110
+ case $state in
2111
+ checklist_cmd)
2112
+ _describe 'checklist command' checklist_cmds
2113
+ ;;
2114
+ checklist_args)
2115
+ case $words[1] in
2116
+ view)
2117
+ _arguments '1:task_id:' '--json[Force JSON output]'
2118
+ ;;
2119
+ create)
2120
+ _arguments '1:task_id:' '2:name:' '--json[Force JSON output]'
2121
+ ;;
2122
+ delete)
2123
+ _arguments '1:checklist_id:' '--json[Force JSON output]'
2124
+ ;;
2125
+ add-item)
2126
+ _arguments '1:checklist_id:' '2:name:' '--json[Force JSON output]'
2127
+ ;;
2128
+ edit-item)
2129
+ _arguments \\
2130
+ '1:checklist_id:' \\
2131
+ '2:checklist_item_id:' \\
2132
+ '--name[New item name]:name:' \\
2133
+ '--resolved[Mark item as resolved]' \\
2134
+ '--unresolved[Mark item as unresolved]' \\
2135
+ '--assignee[Assign user by ID]:user_id:' \\
2136
+ '--json[Force JSON output]'
2137
+ ;;
2138
+ delete-item)
2139
+ _arguments '1:checklist_id:' '2:checklist_item_id:' '--json[Force JSON output]'
2140
+ ;;
2141
+ esac
2142
+ ;;
2143
+ esac
2144
+ ;;
2145
+ comment-edit)
2146
+ _arguments \\
2147
+ '1:comment_id:' \\
2148
+ '(-m --message)'{-m,--message}'[New comment text]:text:' \\
2149
+ '--resolved[Mark comment as resolved]' \\
2150
+ '--unresolved[Mark comment as unresolved]' \\
2151
+ '--json[Force JSON output]'
2152
+ ;;
2022
2153
  config)
2023
2154
  local -a config_cmds
2024
2155
  config_cmds=(
@@ -2085,6 +2216,8 @@ complete -c cu -n __fish_use_subcommand -a move -d 'Add or remove a task from a
2085
2216
  complete -c cu -n __fish_use_subcommand -a field -d 'Set or remove a custom field value on a task'
2086
2217
  complete -c cu -n __fish_use_subcommand -a delete -d 'Delete a task'
2087
2218
  complete -c cu -n __fish_use_subcommand -a tag -d 'Add or remove tags from a task'
2219
+ complete -c cu -n __fish_use_subcommand -a checklist -d 'Manage checklists on a task'
2220
+ complete -c cu -n __fish_use_subcommand -a comment-edit -d 'Edit an existing comment'
2088
2221
  complete -c cu -n __fish_use_subcommand -a config -d 'Manage CLI configuration'
2089
2222
  complete -c cu -n __fish_use_subcommand -a completion -d 'Output shell completion script'
2090
2223
 
@@ -2194,6 +2327,23 @@ complete -c cu -n '__fish_seen_subcommand_from tag' -l add -d 'Comma-separated t
2194
2327
  complete -c cu -n '__fish_seen_subcommand_from tag' -l remove -d 'Comma-separated tag names to remove'
2195
2328
  complete -c cu -n '__fish_seen_subcommand_from tag' -l json -d 'Force JSON output'
2196
2329
 
2330
+ 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'
2331
+ 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'
2332
+ 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'
2333
+ 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'
2334
+ 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'
2335
+ 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'
2336
+ complete -c cu -n '__fish_seen_subcommand_from view create delete add-item edit-item delete-item' -l json -d 'Force JSON output'
2337
+ complete -c cu -n '__fish_seen_subcommand_from edit-item' -l name -d 'New item name'
2338
+ complete -c cu -n '__fish_seen_subcommand_from edit-item' -l resolved -d 'Mark item as resolved'
2339
+ complete -c cu -n '__fish_seen_subcommand_from edit-item' -l unresolved -d 'Mark item as unresolved'
2340
+ complete -c cu -n '__fish_seen_subcommand_from edit-item' -l assignee -d 'Assign user by ID'
2341
+
2342
+ complete -c cu -n '__fish_seen_subcommand_from comment-edit' -s m -l message -d 'New comment text'
2343
+ complete -c cu -n '__fish_seen_subcommand_from comment-edit' -l resolved -d 'Mark comment as resolved'
2344
+ complete -c cu -n '__fish_seen_subcommand_from comment-edit' -l unresolved -d 'Mark comment as unresolved'
2345
+ complete -c cu -n '__fish_seen_subcommand_from comment-edit' -l json -d 'Force JSON output'
2346
+
2197
2347
  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
2348
  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
2349
  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 +2598,59 @@ async function manageTags(config, taskId, opts) {
2448
2598
  return { taskId, added, removed };
2449
2599
  }
2450
2600
 
2601
+ // src/commands/checklist.ts
2602
+ import chalk5 from "chalk";
2603
+ async function viewChecklists(config, taskId) {
2604
+ const client = new ClickUpClient(config);
2605
+ const task = await client.getTask(taskId);
2606
+ return task.checklists ?? [];
2607
+ }
2608
+ async function createChecklist(config, taskId, name) {
2609
+ const client = new ClickUpClient(config);
2610
+ return client.createChecklist(taskId, name);
2611
+ }
2612
+ async function deleteChecklist(config, checklistId) {
2613
+ const client = new ClickUpClient(config);
2614
+ await client.deleteChecklist(checklistId);
2615
+ return { checklistId };
2616
+ }
2617
+ async function addChecklistItem(config, checklistId, name) {
2618
+ const client = new ClickUpClient(config);
2619
+ return client.createChecklistItem(checklistId, name);
2620
+ }
2621
+ async function editChecklistItem(config, checklistId, checklistItemId, updates) {
2622
+ const client = new ClickUpClient(config);
2623
+ return client.editChecklistItem(checklistId, checklistItemId, updates);
2624
+ }
2625
+ async function deleteChecklistItem(config, checklistId, checklistItemId) {
2626
+ const client = new ClickUpClient(config);
2627
+ await client.deleteChecklistItem(checklistId, checklistItemId);
2628
+ return { checklistId, checklistItemId };
2629
+ }
2630
+ function formatChecklists(checklists) {
2631
+ if (checklists.length === 0) return "No checklists";
2632
+ const lines = [];
2633
+ for (const cl of checklists) {
2634
+ const resolved = cl.items.filter((i) => i.resolved).length;
2635
+ lines.push(chalk5.bold(`${cl.name} (${resolved}/${cl.items.length})`));
2636
+ lines.push(chalk5.dim(` ID: ${cl.id}`));
2637
+ for (const item of cl.items) {
2638
+ const check = item.resolved ? chalk5.green("[x]") : chalk5.dim("[ ]");
2639
+ const assignee = item.assignee ? chalk5.dim(` @${item.assignee.username}`) : "";
2640
+ lines.push(` ${check} ${item.name}${assignee}`);
2641
+ lines.push(chalk5.dim(` item-id: ${item.id}`));
2642
+ }
2643
+ }
2644
+ return lines.join("\n");
2645
+ }
2646
+
2647
+ // src/commands/comment-edit.ts
2648
+ async function editComment(config, commentId, text, resolved) {
2649
+ if (!text.trim()) throw new Error("Comment text cannot be empty");
2650
+ const client = new ClickUpClient(config);
2651
+ await client.updateComment(commentId, text, resolved);
2652
+ }
2653
+
2451
2654
  // src/index.ts
2452
2655
  var require2 = createRequire(import.meta.url);
2453
2656
  var { version } = require2("../package.json");
@@ -2509,9 +2712,13 @@ program.command("task <taskId>").description("Get task details").option("--json"
2509
2712
  }
2510
2713
  })
2511
2714
  );
2512
- program.command("update <taskId>").description("Update a task").option("-n, --name <text>", "New task name").option("-d, --description <text>", "New description (markdown supported)").option("-s, --status <status>", 'New status (e.g. "in progress", "done")').option("--priority <level>", "Priority: urgent, high, normal, low (or 1-4)").option("--due-date <date>", "Due date (YYYY-MM-DD)").option("--time-estimate <duration>", 'Time estimate (e.g. "2h", "30m", "1h30m")').option("--assignee <userId>", "Add assignee by user ID").option("--parent <taskId>", "Set parent task (makes this a subtask)").option("--json", "Force JSON output even in terminal").action(
2715
+ program.command("update <taskId>").description("Update a task").option("-n, --name <text>", "New task name").option("-d, --description <text>", "New description (markdown supported)").option("-s, --status <status>", 'New status (e.g. "in progress", "done")').option("--priority <level>", "Priority: urgent, high, normal, low (or 1-4)").option("--due-date <date>", "Due date (YYYY-MM-DD)").option("--time-estimate <duration>", 'Time estimate (e.g. "2h", "30m", "1h30m")').option("--assignee <userId>", 'Add assignee by user ID or "me"').option("--parent <taskId>", "Set parent task (makes this a subtask)").option("--json", "Force JSON output even in terminal").action(
2513
2716
  wrapAction(async (taskId, opts) => {
2514
2717
  const config = loadConfig();
2718
+ if (opts.assignee === "me") {
2719
+ const client = new ClickUpClient(config);
2720
+ opts.assignee = String(await resolveAssigneeId(client, "me"));
2721
+ }
2515
2722
  const payload = buildUpdatePayload(opts);
2516
2723
  const result = await updateTask(config, taskId, payload);
2517
2724
  if (shouldOutputJson(opts.json ?? false)) {
@@ -2521,9 +2728,13 @@ program.command("update <taskId>").description("Update a task").option("-n, --na
2521
2728
  }
2522
2729
  })
2523
2730
  );
2524
- program.command("create").description("Create a new task").option("-l, --list <listId>", "Target list ID (auto-detected from --parent if omitted)").requiredOption("-n, --name <name>", "Task name").option("-d, --description <text>", "Task description (markdown supported)").option("-p, --parent <taskId>", "Parent task ID (list auto-detected from parent)").option("-s, --status <status>", "Initial status").option("--priority <level>", "Priority: urgent, high, normal, low (or 1-4)").option("--due-date <date>", "Due date (YYYY-MM-DD)").option("--assignee <userId>", "Assignee user ID").option("--tags <tags>", "Comma-separated tag names").option("--custom-item-id <id>", "Custom task type ID (use to create initiatives)").option("--time-estimate <duration>", 'Time estimate (e.g. "2h", "30m", "1h30m")').option("--json", "Force JSON output even in terminal").action(
2731
+ program.command("create").description("Create a new task").option("-l, --list <listId>", "Target list ID (auto-detected from --parent if omitted)").requiredOption("-n, --name <name>", "Task name").option("-d, --description <text>", "Task description (markdown supported)").option("-p, --parent <taskId>", "Parent task ID (list auto-detected from parent)").option("-s, --status <status>", "Initial status").option("--priority <level>", "Priority: urgent, high, normal, low (or 1-4)").option("--due-date <date>", "Due date (YYYY-MM-DD)").option("--assignee <userId>", 'Assignee user ID or "me"').option("--tags <tags>", "Comma-separated tag names").option("--custom-item-id <id>", "Custom task type ID (use to create initiatives)").option("--time-estimate <duration>", 'Time estimate (e.g. "2h", "30m", "1h30m")').option("--json", "Force JSON output even in terminal").action(
2525
2732
  wrapAction(async (opts) => {
2526
2733
  const config = loadConfig();
2734
+ if (opts.assignee === "me") {
2735
+ const client = new ClickUpClient(config);
2736
+ opts.assignee = String(await resolveAssigneeId(client, "me"));
2737
+ }
2527
2738
  const result = await createTask(config, opts);
2528
2739
  if (shouldOutputJson(opts.json ?? false)) {
2529
2740
  console.log(JSON.stringify(result, null, 2));
@@ -2581,6 +2792,22 @@ program.command("comments <taskId>").description("List comments on a task").opti
2581
2792
  printComments(comments, opts.json ?? false);
2582
2793
  })
2583
2794
  );
2795
+ 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(
2796
+ wrapAction(
2797
+ async (commentId, opts) => {
2798
+ const config = loadConfig();
2799
+ let resolved;
2800
+ if (opts.resolved) resolved = true;
2801
+ if (opts.unresolved) resolved = false;
2802
+ await editComment(config, commentId, opts.message, resolved);
2803
+ if (shouldOutputJson(opts.json ?? false)) {
2804
+ console.log(JSON.stringify({ success: true, commentId }, null, 2));
2805
+ } else {
2806
+ console.log(`Comment ${commentId} updated`);
2807
+ }
2808
+ }
2809
+ )
2810
+ );
2584
2811
  program.command("activity <taskId>").description("Show task details and comments combined").option("--json", "Force JSON output even in terminal").action(
2585
2812
  wrapAction(async (taskId, opts) => {
2586
2813
  const config = loadConfig();
@@ -2746,6 +2973,82 @@ program.command("tag <taskId>").description("Add or remove tags from a task").op
2746
2973
  }
2747
2974
  })
2748
2975
  );
2976
+ var checklistCmd = program.command("checklist").description("Manage checklists on a task");
2977
+ checklistCmd.command("view <taskId>").description("View checklists on a task").option("--json", "Force JSON output even in terminal").action(
2978
+ wrapAction(async (taskId, opts) => {
2979
+ const config = loadConfig();
2980
+ const checklists = await viewChecklists(config, taskId);
2981
+ if (shouldOutputJson(opts.json ?? false)) {
2982
+ console.log(JSON.stringify(checklists, null, 2));
2983
+ } else {
2984
+ console.log(formatChecklists(checklists));
2985
+ }
2986
+ })
2987
+ );
2988
+ checklistCmd.command("create <taskId> <name>").description("Create a checklist on a task").option("--json", "Force JSON output even in terminal").action(
2989
+ wrapAction(async (taskId, name, opts) => {
2990
+ const config = loadConfig();
2991
+ const result = await createChecklist(config, taskId, name);
2992
+ if (shouldOutputJson(opts.json ?? false)) {
2993
+ console.log(JSON.stringify(result, null, 2));
2994
+ } else {
2995
+ console.log(`Created checklist "${result.name}" (id: ${result.id})`);
2996
+ }
2997
+ })
2998
+ );
2999
+ checklistCmd.command("delete <checklistId>").description("Delete a checklist").option("--json", "Force JSON output even in terminal").action(
3000
+ wrapAction(async (checklistId, opts) => {
3001
+ const config = loadConfig();
3002
+ const result = await deleteChecklist(config, checklistId);
3003
+ if (shouldOutputJson(opts.json ?? false)) {
3004
+ console.log(JSON.stringify(result, null, 2));
3005
+ } else {
3006
+ console.log(`Deleted checklist ${result.checklistId}`);
3007
+ }
3008
+ })
3009
+ );
3010
+ checklistCmd.command("add-item <checklistId> <name>").description("Add an item to a checklist").option("--json", "Force JSON output even in terminal").action(
3011
+ wrapAction(async (checklistId, name, opts) => {
3012
+ const config = loadConfig();
3013
+ const result = await addChecklistItem(config, checklistId, name);
3014
+ if (shouldOutputJson(opts.json ?? false)) {
3015
+ console.log(JSON.stringify(result, null, 2));
3016
+ } else {
3017
+ console.log(`Added item "${name}" to checklist ${checklistId}`);
3018
+ }
3019
+ })
3020
+ );
3021
+ 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(
3022
+ wrapAction(
3023
+ async (checklistId, checklistItemId, opts) => {
3024
+ const config = loadConfig();
3025
+ const updates = {};
3026
+ if (opts.name) updates.name = opts.name;
3027
+ if (opts.resolved) updates.resolved = true;
3028
+ if (opts.unresolved) updates.resolved = false;
3029
+ if (opts.assignee !== void 0) {
3030
+ updates.assignee = opts.assignee === "null" ? null : Number(opts.assignee);
3031
+ }
3032
+ const result = await editChecklistItem(config, checklistId, checklistItemId, updates);
3033
+ if (shouldOutputJson(opts.json ?? false)) {
3034
+ console.log(JSON.stringify(result, null, 2));
3035
+ } else {
3036
+ console.log(`Updated checklist item ${checklistItemId}`);
3037
+ }
3038
+ }
3039
+ )
3040
+ );
3041
+ checklistCmd.command("delete-item <checklistId> <checklistItemId>").description("Delete a checklist item").option("--json", "Force JSON output even in terminal").action(
3042
+ wrapAction(async (checklistId, checklistItemId, opts) => {
3043
+ const config = loadConfig();
3044
+ const result = await deleteChecklistItem(config, checklistId, checklistItemId);
3045
+ if (shouldOutputJson(opts.json ?? false)) {
3046
+ console.log(JSON.stringify(result, null, 2));
3047
+ } else {
3048
+ console.log(`Deleted checklist item ${result.checklistItemId}`);
3049
+ }
3050
+ })
3051
+ );
2749
3052
  var configCmd = program.command("config").description("Manage CLI configuration");
2750
3053
  configCmd.command("get <key>").description("Print a config value").action(
2751
3054
  wrapAction(async (key) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@krodak/clickup-cli",
3
- "version": "0.13.1",
3
+ "version": "0.14.0",
4
4
  "description": "ClickUp CLI for AI agents and humans",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: clickup
3
- description: 'Use when managing ClickUp tasks, sprints, or comments via the `cu` CLI tool. Triggers: task queries, status updates, sprint tracking, creating subtasks, posting comments, standup summaries, searching tasks, checking overdue items, assigning tasks, listing spaces and lists, opening tasks in browser, checking auth or config, setting custom fields, deleting tasks, managing tags.'
3
+ description: 'Use when managing ClickUp tasks, sprints, or comments via the `cu` CLI tool. Triggers: task queries, status updates, sprint tracking, creating subtasks, posting comments, standup summaries, searching tasks, checking overdue items, assigning tasks, listing spaces and lists, opening tasks in browser, checking auth or config, setting custom fields, deleting tasks, managing tags, managing checklists, editing comments.'
4
4
  ---
5
5
 
6
6
  # ClickUp CLI (`cu`)
@@ -55,19 +55,26 @@ All commands support `--help` for full flag details.
55
55
 
56
56
  ### Write
57
57
 
58
- | Command | What it does |
59
- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------- |
60
- | `cu update <id> [-n name] [-d desc] [-s status] [--priority p] [--due-date d] [--time-estimate t] [--assignee id] [--parent id] [--json]` | Update task fields (desc supports markdown) |
61
- | `cu create -n name [-l listId] [-p parentId] [-d desc] [-s status] [--priority p] [--due-date d] [--time-estimate t] [--assignee id] [--tags t] [--custom-item-id n] [--json]` | Create task (desc supports markdown) |
62
- | `cu comment <id> -m text [--json]` | Post comment on task |
63
- | `cu assign <id> [--to userId\|me] [--remove userId\|me] [--json]` | Assign/unassign users |
64
- | `cu depend <id> [--on taskId] [--blocks taskId] [--remove] [--json]` | Add/remove task dependencies |
65
- | `cu move <id> [--to listId] [--remove listId] [--json]` | Add/remove task from lists |
66
- | `cu field <id> [--set "Name" value] [--remove "Name"] [--json]` | Set/remove custom field values |
67
- | `cu delete <id> [--confirm] [--json]` | Delete a task (DESTRUCTIVE, irreversible) |
68
- | `cu tag <id> [--add tags] [--remove tags] [--json]` | Add/remove tags on a task |
69
- | `cu config get <key>` / `cu config set <key> <value>` / `cu config path` | Manage CLI config |
70
- | `cu completion <shell>` | Shell completions (bash/zsh/fish) |
58
+ | Command | What it does |
59
+ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- |
60
+ | `cu update <id> [-n name] [-d desc] [-s status] [--priority p] [--due-date d] [--time-estimate t] [--assignee id\|me] [--parent id] [--json]` | Update task fields (desc supports markdown) |
61
+ | `cu create -n name [-l listId] [-p parentId] [-d desc] [-s status] [--priority p] [--due-date d] [--time-estimate t] [--assignee id\|me] [--tags t] [--custom-item-id n] [--json]` | Create task (desc supports markdown) |
62
+ | `cu comment <id> -m text [--json]` | Post comment on task |
63
+ | `cu comment-edit <commentId> -m text [--resolved] [--unresolved] [--json]` | Edit an existing comment |
64
+ | `cu assign <id> [--to userId\|me] [--remove userId\|me] [--json]` | Assign/unassign users |
65
+ | `cu depend <id> [--on taskId] [--blocks taskId] [--remove] [--json]` | Add/remove task dependencies |
66
+ | `cu move <id> [--to listId] [--remove listId] [--json]` | Add/remove task from lists |
67
+ | `cu field <id> [--set "Name" value] [--remove "Name"] [--json]` | Set/remove custom field values |
68
+ | `cu delete <id> [--confirm] [--json]` | Delete a task (DESTRUCTIVE, irreversible) |
69
+ | `cu tag <id> [--add tags] [--remove tags] [--json]` | Add/remove tags on a task |
70
+ | `cu checklist view <id> [--json]` | View checklists on a task |
71
+ | `cu checklist create <id> <name> [--json]` | Create a checklist |
72
+ | `cu checklist delete <checklistId> [--json]` | Delete a checklist |
73
+ | `cu checklist add-item <checklistId> <name> [--json]` | Add item to a checklist |
74
+ | `cu checklist edit-item <checklistId> <itemId> [--name n] [--resolved] [--unresolved] [--assignee id] [--json]` | Edit a checklist item |
75
+ | `cu checklist delete-item <checklistId> <itemId> [--json]` | Delete a checklist item |
76
+ | `cu config get <key>` / `cu config set <key> <value>` / `cu config path` | Manage CLI config |
77
+ | `cu completion <shell>` | Shell completions (bash/zsh/fish) |
71
78
 
72
79
  ## Quick Reference
73
80
 
@@ -79,7 +86,7 @@ All commands support `--help` for full flag details.
79
86
  | `--status` | Fuzzy matching: exact > starts-with > contains. Prints match to stderr. |
80
87
  | `--priority` | Names (`urgent`, `high`, `normal`, `low`) or numbers (1-4) |
81
88
  | `--due-date` | `YYYY-MM-DD` format |
82
- | `--assignee` | Numeric user ID (find via `cu task <id> --json`) |
89
+ | `--assignee` | User ID or `me` (on `cu create`, `cu update`, `cu assign`) |
83
90
  | `--tags` | Comma-separated (e.g. `--tags "bug,frontend"`) |
84
91
  | `--time-estimate` | Duration format: `"2h"`, `"30m"`, `"1h30m"`, or raw milliseconds |
85
92
  | `--custom-item-id` | Custom task type ID for `cu create` (e.g. `1` for initiative) |
@@ -98,7 +105,9 @@ All commands support `--help` for full flag details.
98
105
  | `cu summary` | Categories: completed (done/complete/closed within N hours), in progress, overdue |
99
106
  | `cu overdue` | Excludes closed tasks, sorted most overdue first |
100
107
  | `cu open` | Tries task ID first, falls back to name search |
101
- | `cu task` | Shows custom fields in detail view |
108
+ | `cu checklist` | Full CRUD for task checklists: view, create, delete, add-item, edit-item, delete-item |
109
+ | `cu comment-edit` | Edit comment text and resolution status |
110
+ | `cu task` | Shows custom fields and checklists in detail view |
102
111
  | `cu lists` | Discovers list IDs needed for `--list` and `cu create -l` |
103
112
  | Errors | stderr with exit code 1 |
104
113
  | Parsing | Strict - excess/unknown arguments rejected |
@@ -150,6 +159,11 @@ cu field abc123def --set "Category" "Bug Fix"
150
159
  cu field abc123def --remove "Old Field"
151
160
  cu tag abc123def --add "bug,frontend"
152
161
  cu tag abc123def --remove "triage"
162
+ cu checklist view abc123def # view checklists
163
+ cu checklist create abc123def "QA Steps" # add checklist
164
+ cu checklist add-item <clId> "Run unit tests" # add item
165
+ cu checklist edit-item <clId> <itemId> --resolved # check off item
166
+ cu comment-edit <commentId> -m "Updated findings" # edit a comment
153
167
  cu delete abc123def --confirm # irreversible!
154
168
  ```
155
169