@krodak/clickup-cli 0.6.1 → 0.8.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/.claude-plugin/plugin.json +11 -0
- package/README.md +147 -115
- package/dist/index.js +296 -23
- package/package.json +3 -1
- package/skills/clickup-cli/SKILL.md +149 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "clickup-cli",
|
|
3
|
+
"description": "Claude Code skills for the cu (ClickUp CLI) tool - manage tasks, sprints, initiatives, and comments",
|
|
4
|
+
"version": "0.6.1",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Krzysztof Rodak"
|
|
7
|
+
},
|
|
8
|
+
"homepage": "https://github.com/krodak/clickup-cli",
|
|
9
|
+
"repository": "https://github.com/krodak/clickup-cli",
|
|
10
|
+
"license": "MIT"
|
|
11
|
+
}
|
package/README.md
CHANGED
|
@@ -1,127 +1,102 @@
|
|
|
1
1
|
# cu - ClickUp CLI
|
|
2
2
|
|
|
3
|
-
A ClickUp CLI for AI agents
|
|
3
|
+
A ClickUp CLI built for AI agents that also works well for humans. Outputs JSON when piped, interactive tables when run in a terminal.
|
|
4
4
|
|
|
5
|
-
##
|
|
6
|
-
|
|
7
|
-
- Node 22+
|
|
8
|
-
- A ClickUp personal API token (`pk_...` from https://app.clickup.com/settings/apps)
|
|
9
|
-
|
|
10
|
-
## Install
|
|
5
|
+
## Quick start
|
|
11
6
|
|
|
12
7
|
```bash
|
|
13
|
-
npm install -g @krodak/clickup-cli
|
|
8
|
+
npm install -g @krodak/clickup-cli # or: brew tap krodak/tap && brew install clickup-cli
|
|
9
|
+
cu init # walks you through API token + workspace setup
|
|
14
10
|
```
|
|
15
11
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
```bash
|
|
19
|
-
cu init
|
|
20
|
-
```
|
|
12
|
+
You need Node 22+ and a ClickUp personal API token (`pk_...` from https://app.clickup.com/settings/apps).
|
|
21
13
|
|
|
22
|
-
|
|
14
|
+
## Using with AI agents
|
|
23
15
|
|
|
24
|
-
|
|
16
|
+
This is the primary use case. Install the tool, install the skill file, and your agent knows how to work with ClickUp.
|
|
25
17
|
|
|
26
|
-
###
|
|
18
|
+
### 1. Install the skill
|
|
27
19
|
|
|
28
|
-
|
|
20
|
+
The repo includes a skill file at `skills/clickup-cli/SKILL.md` that teaches agents all available commands and when to use them. It's also packaged as a Claude Code plugin.
|
|
29
21
|
|
|
30
|
-
|
|
31
|
-
2. An interactive checkbox picker (powered by @inquirer/prompts) - navigate with arrow keys, toggle selection with space, confirm with enter.
|
|
32
|
-
3. For selected tasks, a rich detail view showing: name (bold/underlined), ID, status, type, list, assignees, priority, dates, estimate, tracked time, tags, parent, URL, and a description preview (first 3 lines).
|
|
33
|
-
4. A prompt to open the selected tasks in the browser.
|
|
22
|
+
**Claude Code (plugin - recommended):**
|
|
34
23
|
|
|
35
|
-
|
|
24
|
+
The repo ships as a Claude Code plugin. Point Claude Code at the repo or installed npm package:
|
|
36
25
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
- `cu task <id> --json` returns the full JSON response for a single task.
|
|
42
|
-
- All list commands output JSON arrays.
|
|
43
|
-
- Errors go to stderr with exit code 1.
|
|
44
|
-
- Write commands (`update`, `create`, `comment`, `assign`) always output JSON regardless of mode.
|
|
45
|
-
- Set `NO_COLOR` to disable color output.
|
|
46
|
-
|
|
47
|
-
## For AI agents
|
|
26
|
+
```bash
|
|
27
|
+
claude --plugin-dir ./node_modules/@krodak/clickup-cli
|
|
28
|
+
```
|
|
48
29
|
|
|
49
|
-
|
|
30
|
+
Or copy the skill manually:
|
|
50
31
|
|
|
51
32
|
```bash
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
# Get full task details as JSON
|
|
56
|
-
cu task abc123 --json | jq '{id, name, status}'
|
|
33
|
+
mkdir -p ~/.claude/skills/clickup
|
|
34
|
+
cp skills/clickup-cli/SKILL.md ~/.claude/skills/clickup/SKILL.md
|
|
35
|
+
```
|
|
57
36
|
|
|
58
|
-
|
|
59
|
-
cu sprint --json | jq '.[] | select(.status != "done")'
|
|
37
|
+
Then reference it in your `CLAUDE.md` or project instructions.
|
|
60
38
|
|
|
61
|
-
|
|
62
|
-
INITIATIVE_ID=$(cu initiatives --json | jq -r '.[0].id')
|
|
63
|
-
cu create -n "New subtask" -p "$INITIATIVE_ID"
|
|
39
|
+
**OpenCode:**
|
|
64
40
|
|
|
65
|
-
|
|
66
|
-
|
|
41
|
+
```bash
|
|
42
|
+
mkdir -p ~/.config/opencode/skills/clickup
|
|
43
|
+
cp skills/clickup-cli/SKILL.md ~/.config/opencode/skills/clickup/SKILL.md
|
|
67
44
|
```
|
|
68
45
|
|
|
69
|
-
|
|
46
|
+
**Codex / other agents:**
|
|
70
47
|
|
|
71
|
-
|
|
48
|
+
Copy the contents of `skills/clickup-cli/SKILL.md` into your system prompt or project instructions. It's a standalone markdown document.
|
|
72
49
|
|
|
73
|
-
|
|
50
|
+
### 2. Talk to your agent
|
|
74
51
|
|
|
75
|
-
|
|
52
|
+
Once the skill is installed, you just tell the agent what you need in plain language. It figures out which `cu` commands to run.
|
|
76
53
|
|
|
77
|
-
```bash
|
|
78
|
-
mkdir -p ~/.config/opencode/skills/clickup
|
|
79
|
-
cp skill/SKILL.md ~/.config/opencode/skills/clickup/SKILL.md
|
|
80
54
|
```
|
|
55
|
+
"Read the description of task <id>, do the work, then mark it in review and leave a comment with the commit hash."
|
|
81
56
|
|
|
82
|
-
|
|
57
|
+
"Check all subtasks under initiative <id> and improve their descriptions based on what's in the codebase."
|
|
83
58
|
|
|
84
|
-
|
|
85
|
-
mkdir -p ~/.claude/skills/clickup
|
|
86
|
-
cp skill/SKILL.md ~/.claude/skills/clickup/SKILL.md
|
|
87
|
-
```
|
|
59
|
+
"What's my standup summary? What did I finish yesterday, what's in progress, what's overdue?"
|
|
88
60
|
|
|
89
|
-
|
|
61
|
+
"Do exploratory work for task <id>, update the description with your findings, and flag blockers in a comment."
|
|
90
62
|
|
|
91
|
-
|
|
63
|
+
"Create a subtask under <id> for the edge case we just found."
|
|
92
64
|
|
|
93
|
-
|
|
65
|
+
"Check my sprint and tell me what's overdue."
|
|
66
|
+
```
|
|
94
67
|
|
|
95
|
-
|
|
68
|
+
You don't need to learn the CLI commands yourself. The agent handles it.
|
|
96
69
|
|
|
97
|
-
|
|
70
|
+
### Why a CLI and not MCP?
|
|
98
71
|
|
|
99
|
-
|
|
72
|
+
A CLI + skill file has fewer moving parts. No extra server process, no protocol layer. The agent already knows how to run shell commands - the skill file just teaches it which ones exist. This matches what Peter Steinberger and the OpenClaw project have found: for tool-use with coding agents, CLI + instructions tends to work better than MCP in practice.
|
|
100
73
|
|
|
101
|
-
###
|
|
74
|
+
### Scoped output
|
|
102
75
|
|
|
103
|
-
|
|
76
|
+
Most commands return only your tasks by default. `cu tasks`, `cu sprint`, `cu overdue`, `cu summary` all scope to what's assigned to you. `cu spaces --my` filters to spaces where you have tasks. This keeps output small and relevant - important when it's going into an agent's context window.
|
|
104
77
|
|
|
105
|
-
|
|
106
|
-
{
|
|
107
|
-
"apiToken": "pk_...",
|
|
108
|
-
"teamId": "12345678"
|
|
109
|
-
}
|
|
110
|
-
```
|
|
78
|
+
## Using from the terminal
|
|
111
79
|
|
|
112
|
-
|
|
80
|
+
When you run `cu` in a terminal directly, you get an interactive mode with tables and a task picker.
|
|
113
81
|
|
|
114
|
-
|
|
82
|
+
```bash
|
|
83
|
+
cu tasks # interactive table with checkbox picker
|
|
84
|
+
cu sprint # your sprint tasks, auto-detected
|
|
85
|
+
cu summary # standup helper: completed / in progress / overdue
|
|
86
|
+
cu overdue # tasks past their due date
|
|
87
|
+
cu open "login bug" # fuzzy search, opens in browser
|
|
88
|
+
cu update abc123 -s "done" # update status
|
|
89
|
+
cu assign abc123 --to me # assign yourself
|
|
90
|
+
```
|
|
115
91
|
|
|
116
|
-
|
|
117
|
-
| -------------- | ---------------------------------- |
|
|
118
|
-
| `CU_API_TOKEN` | ClickUp personal API token (`pk_`) |
|
|
119
|
-
| `CU_TEAM_ID` | Workspace (team) ID |
|
|
92
|
+
Pass `--json` to any command to get raw JSON output instead of the interactive UI.
|
|
120
93
|
|
|
121
|
-
When
|
|
94
|
+
When output is piped (no TTY), all commands automatically output JSON. All commands support `--json` to force JSON output even in a terminal.
|
|
122
95
|
|
|
123
96
|
## Commands
|
|
124
97
|
|
|
98
|
+
24 commands total. All support `--help` for full flag details.
|
|
99
|
+
|
|
125
100
|
### `cu init`
|
|
126
101
|
|
|
127
102
|
First-time setup. Prompts for your API token, verifies it, auto-detects your workspace, and writes `~/.config/cu/config.json`.
|
|
@@ -140,7 +115,7 @@ cu tasks --status "in progress"
|
|
|
140
115
|
cu tasks --name "login"
|
|
141
116
|
cu tasks --list <listId>
|
|
142
117
|
cu tasks --space <spaceId>
|
|
143
|
-
cu tasks --json
|
|
118
|
+
cu tasks --json
|
|
144
119
|
```
|
|
145
120
|
|
|
146
121
|
### `cu initiatives`
|
|
@@ -166,6 +141,15 @@ cu sprint --status "in progress"
|
|
|
166
141
|
cu sprint --json
|
|
167
142
|
```
|
|
168
143
|
|
|
144
|
+
### `cu sprints`
|
|
145
|
+
|
|
146
|
+
List all sprints across sprint folders. Marks the currently active sprint.
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
cu sprints
|
|
150
|
+
cu sprints --json
|
|
151
|
+
```
|
|
152
|
+
|
|
169
153
|
### `cu assigned`
|
|
170
154
|
|
|
171
155
|
All tasks assigned to me, grouped by pipeline stage (code review, in progress, to do, etc.).
|
|
@@ -188,11 +172,11 @@ cu inbox --json
|
|
|
188
172
|
|
|
189
173
|
### `cu task <id>`
|
|
190
174
|
|
|
191
|
-
Get task details. Pretty summary in terminal, JSON when piped.
|
|
175
|
+
Get task details including custom fields. Pretty summary in terminal, JSON when piped.
|
|
192
176
|
|
|
193
177
|
```bash
|
|
194
178
|
cu task abc123
|
|
195
|
-
cu task abc123 --json
|
|
179
|
+
cu task abc123 --json
|
|
196
180
|
```
|
|
197
181
|
|
|
198
182
|
### `cu subtasks <id>`
|
|
@@ -216,16 +200,18 @@ cu update abc123 --priority high
|
|
|
216
200
|
cu update abc123 --due-date 2025-03-15
|
|
217
201
|
cu update abc123 --assignee 12345
|
|
218
202
|
cu update abc123 -n "New name" -s "done" --priority urgent
|
|
203
|
+
cu update abc123 -s "in progress" --json
|
|
219
204
|
```
|
|
220
205
|
|
|
221
|
-
| Flag | Description
|
|
222
|
-
| -------------------------- |
|
|
223
|
-
| `-n, --name <text>` | New task name
|
|
224
|
-
| `-d, --description <text>` | New description (markdown supported)
|
|
225
|
-
| `-s, --status <status>` | New status (e.g. `"
|
|
226
|
-
| `--priority <level>` | Priority: `urgent`, `high`, `normal`, `low` (or 1-4)
|
|
227
|
-
| `--due-date <date>` | Due date (`YYYY-MM-DD`)
|
|
228
|
-
| `--assignee <userId>` | Add assignee by numeric user ID
|
|
206
|
+
| Flag | Description |
|
|
207
|
+
| -------------------------- | --------------------------------------------------------------------------- |
|
|
208
|
+
| `-n, --name <text>` | New task name |
|
|
209
|
+
| `-d, --description <text>` | New description (markdown supported) |
|
|
210
|
+
| `-s, --status <status>` | New status, supports fuzzy matching (e.g. `"prog"` matches `"in progress"`) |
|
|
211
|
+
| `--priority <level>` | Priority: `urgent`, `high`, `normal`, `low` (or 1-4) |
|
|
212
|
+
| `--due-date <date>` | Due date (`YYYY-MM-DD`) |
|
|
213
|
+
| `--assignee <userId>` | Add assignee by numeric user ID |
|
|
214
|
+
| `--json` | Force JSON output even in terminal |
|
|
229
215
|
|
|
230
216
|
### `cu create`
|
|
231
217
|
|
|
@@ -237,6 +223,7 @@ cu create -n "Subtask name" -p <parentTaskId> # --list auto-detected
|
|
|
237
223
|
cu create -n "Task" -l <listId> -d "desc" -s "open"
|
|
238
224
|
cu create -n "Task" -l <listId> --priority high --due-date 2025-06-01
|
|
239
225
|
cu create -n "Task" -l <listId> --assignee 12345 --tags "bug,frontend"
|
|
226
|
+
cu create -n "Fix bug" -l <listId> --json
|
|
240
227
|
```
|
|
241
228
|
|
|
242
229
|
| Flag | Required | Description |
|
|
@@ -250,6 +237,7 @@ cu create -n "Task" -l <listId> --assignee 12345 --tags "bug,frontend"
|
|
|
250
237
|
| `--due-date <date>` | no | Due date (`YYYY-MM-DD`) |
|
|
251
238
|
| `--assignee <userId>` | no | Assignee by numeric user ID |
|
|
252
239
|
| `--tags <tags>` | no | Comma-separated tag names |
|
|
240
|
+
| `--json` | no | Force JSON output even in terminal |
|
|
253
241
|
|
|
254
242
|
### `cu comment <id>`
|
|
255
243
|
|
|
@@ -268,13 +256,22 @@ cu comments abc123
|
|
|
268
256
|
cu comments abc123 --json
|
|
269
257
|
```
|
|
270
258
|
|
|
259
|
+
### `cu activity <id>`
|
|
260
|
+
|
|
261
|
+
View task details and comment history together. Combines `cu task` and `cu comments` into a single view.
|
|
262
|
+
|
|
263
|
+
```bash
|
|
264
|
+
cu activity abc123
|
|
265
|
+
cu activity abc123 --json
|
|
266
|
+
```
|
|
267
|
+
|
|
271
268
|
### `cu lists <spaceId>`
|
|
272
269
|
|
|
273
270
|
List all lists in a space, including lists inside folders. Useful for discovering list IDs needed by `--list` filter and `cu create -l`.
|
|
274
271
|
|
|
275
272
|
```bash
|
|
276
273
|
cu lists <spaceId>
|
|
277
|
-
cu lists <spaceId> --name "sprint"
|
|
274
|
+
cu lists <spaceId> --name "sprint"
|
|
278
275
|
cu lists <spaceId> --json
|
|
279
276
|
```
|
|
280
277
|
|
|
@@ -289,8 +286,8 @@ List spaces in your workspace. Useful for getting space IDs for the `--space` fi
|
|
|
289
286
|
|
|
290
287
|
```bash
|
|
291
288
|
cu spaces
|
|
292
|
-
cu spaces --name "eng"
|
|
293
|
-
cu spaces --my
|
|
289
|
+
cu spaces --name "eng"
|
|
290
|
+
cu spaces --my
|
|
294
291
|
cu spaces --json
|
|
295
292
|
```
|
|
296
293
|
|
|
@@ -305,20 +302,31 @@ cu spaces --json
|
|
|
305
302
|
Open a task in the browser. Accepts a task ID or partial name.
|
|
306
303
|
|
|
307
304
|
```bash
|
|
308
|
-
cu open abc123
|
|
309
|
-
cu open "login bug"
|
|
310
|
-
cu open abc123 --json
|
|
305
|
+
cu open abc123
|
|
306
|
+
cu open "login bug"
|
|
307
|
+
cu open abc123 --json
|
|
311
308
|
```
|
|
312
309
|
|
|
313
310
|
If the query matches multiple tasks by name, all matches are listed and the first is opened.
|
|
314
311
|
|
|
312
|
+
### `cu search <query>`
|
|
313
|
+
|
|
314
|
+
Search my tasks by name. Supports multi-word queries with case-insensitive matching. Status filter supports fuzzy matching.
|
|
315
|
+
|
|
316
|
+
```bash
|
|
317
|
+
cu search "login bug"
|
|
318
|
+
cu search auth
|
|
319
|
+
cu search "payment flow" --json
|
|
320
|
+
cu search auth --status "prog" # fuzzy matches "in progress"
|
|
321
|
+
```
|
|
322
|
+
|
|
315
323
|
### `cu summary`
|
|
316
324
|
|
|
317
325
|
Daily standup helper. Shows tasks grouped into: recently completed, in progress, and overdue.
|
|
318
326
|
|
|
319
327
|
```bash
|
|
320
328
|
cu summary
|
|
321
|
-
cu summary --hours 48
|
|
329
|
+
cu summary --hours 48
|
|
322
330
|
cu summary --json
|
|
323
331
|
```
|
|
324
332
|
|
|
@@ -329,25 +337,23 @@ cu summary --json
|
|
|
329
337
|
|
|
330
338
|
### `cu overdue`
|
|
331
339
|
|
|
332
|
-
List tasks that are past their due date (excludes done/closed tasks).
|
|
340
|
+
List tasks that are past their due date (excludes done/closed tasks). Sorted most overdue first.
|
|
333
341
|
|
|
334
342
|
```bash
|
|
335
343
|
cu overdue
|
|
336
344
|
cu overdue --json
|
|
337
345
|
```
|
|
338
346
|
|
|
339
|
-
Tasks are sorted by due date ascending (most overdue first).
|
|
340
|
-
|
|
341
347
|
### `cu assign <id>`
|
|
342
348
|
|
|
343
349
|
Assign or unassign users from a task. Supports `me` as shorthand for your user ID.
|
|
344
350
|
|
|
345
351
|
```bash
|
|
346
|
-
cu assign abc123 --to 12345
|
|
347
|
-
cu assign abc123 --to me
|
|
348
|
-
cu assign abc123 --remove 12345
|
|
349
|
-
cu assign abc123 --to me --remove 67890
|
|
350
|
-
cu assign abc123 --to me --json
|
|
352
|
+
cu assign abc123 --to 12345
|
|
353
|
+
cu assign abc123 --to me
|
|
354
|
+
cu assign abc123 --remove 12345
|
|
355
|
+
cu assign abc123 --to me --remove 67890
|
|
356
|
+
cu assign abc123 --to me --json
|
|
351
357
|
```
|
|
352
358
|
|
|
353
359
|
| Flag | Description |
|
|
@@ -356,14 +362,23 @@ cu assign abc123 --to me --json # output updated task JSON
|
|
|
356
362
|
| `--remove <userId>` | Remove assignee (user ID or `me`) |
|
|
357
363
|
| `--json` | Force JSON output |
|
|
358
364
|
|
|
365
|
+
### `cu auth`
|
|
366
|
+
|
|
367
|
+
Check authentication status. Validates your API token and shows your user info.
|
|
368
|
+
|
|
369
|
+
```bash
|
|
370
|
+
cu auth
|
|
371
|
+
cu auth --json
|
|
372
|
+
```
|
|
373
|
+
|
|
359
374
|
### `cu config`
|
|
360
375
|
|
|
361
376
|
Manage CLI configuration.
|
|
362
377
|
|
|
363
378
|
```bash
|
|
364
|
-
cu config get apiToken
|
|
365
|
-
cu config set teamId 12345
|
|
366
|
-
cu config path
|
|
379
|
+
cu config get apiToken
|
|
380
|
+
cu config set teamId 12345
|
|
381
|
+
cu config path
|
|
367
382
|
```
|
|
368
383
|
|
|
369
384
|
Valid keys: `apiToken`, `teamId`. Setting `apiToken` validates the `pk_` prefix.
|
|
@@ -373,17 +388,34 @@ Valid keys: `apiToken`, `teamId`. Setting `apiToken` validates the `pk_` prefix.
|
|
|
373
388
|
Output shell completion script. Supports `bash`, `zsh`, and `fish`.
|
|
374
389
|
|
|
375
390
|
```bash
|
|
376
|
-
|
|
377
|
-
eval "$(cu completion
|
|
391
|
+
eval "$(cu completion bash)" # Bash
|
|
392
|
+
eval "$(cu completion zsh)" # Zsh
|
|
393
|
+
cu completion fish > ~/.config/fish/completions/cu.fish # Fish
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
## Config
|
|
378
397
|
|
|
379
|
-
|
|
380
|
-
|
|
398
|
+
### Config file
|
|
399
|
+
|
|
400
|
+
`~/.config/cu/config.json` (or `$XDG_CONFIG_HOME/cu/config.json`):
|
|
381
401
|
|
|
382
|
-
|
|
383
|
-
|
|
402
|
+
```json
|
|
403
|
+
{
|
|
404
|
+
"apiToken": "pk_...",
|
|
405
|
+
"teamId": "12345678"
|
|
406
|
+
}
|
|
384
407
|
```
|
|
385
408
|
|
|
386
|
-
|
|
409
|
+
### Environment variables
|
|
410
|
+
|
|
411
|
+
Environment variables override config file values:
|
|
412
|
+
|
|
413
|
+
| Variable | Description |
|
|
414
|
+
| -------------- | ---------------------------------- |
|
|
415
|
+
| `CU_API_TOKEN` | ClickUp personal API token (`pk_`) |
|
|
416
|
+
| `CU_TEAM_ID` | Workspace (team) ID |
|
|
417
|
+
|
|
418
|
+
When both are set, the config file is not required. Useful for CI/CD and containerized agents.
|
|
387
419
|
|
|
388
420
|
## Development
|
|
389
421
|
|
package/dist/index.js
CHANGED
|
@@ -61,9 +61,13 @@ function getConfigPath() {
|
|
|
61
61
|
function writeConfig(config) {
|
|
62
62
|
const dir = configDir();
|
|
63
63
|
if (!fs.existsSync(dir)) {
|
|
64
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
64
|
+
fs.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
65
65
|
}
|
|
66
|
-
|
|
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
|
+
});
|
|
67
71
|
}
|
|
68
72
|
|
|
69
73
|
// src/api.ts
|
|
@@ -187,6 +191,9 @@ var ClickUpClient = class {
|
|
|
187
191
|
const data = await this.request("/team");
|
|
188
192
|
return data.teams ?? [];
|
|
189
193
|
}
|
|
194
|
+
async getSpaceWithStatuses(spaceId) {
|
|
195
|
+
return this.request(`/space/${spaceId}`);
|
|
196
|
+
}
|
|
190
197
|
async getSpaces(teamId) {
|
|
191
198
|
const data = await this.request(`/team/${teamId}/space?archived=false`);
|
|
192
199
|
return data.spaces ?? [];
|
|
@@ -295,6 +302,40 @@ function descriptionPreview(text, maxLines = 3) {
|
|
|
295
302
|
${chalk2.dim(`... (${lines.length - maxLines} more lines)`)}`;
|
|
296
303
|
return result;
|
|
297
304
|
}
|
|
305
|
+
function stringifyFieldValue(value) {
|
|
306
|
+
if (typeof value === "string") return value;
|
|
307
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
308
|
+
return JSON.stringify(value);
|
|
309
|
+
}
|
|
310
|
+
function formatCustomFieldValue(field) {
|
|
311
|
+
if (field.value === null || field.value === void 0) return null;
|
|
312
|
+
const options = field.type_config?.options;
|
|
313
|
+
switch (field.type) {
|
|
314
|
+
case "drop_down": {
|
|
315
|
+
if (!options) return stringifyFieldValue(field.value);
|
|
316
|
+
const match = options.find((o) => o.id === Number(field.value));
|
|
317
|
+
return match?.name ?? stringifyFieldValue(field.value);
|
|
318
|
+
}
|
|
319
|
+
case "labels": {
|
|
320
|
+
if (!Array.isArray(field.value) || !options) return stringifyFieldValue(field.value);
|
|
321
|
+
const names = field.value.map((id) => options.find((o) => o.id === id)?.name).filter((n) => n !== void 0);
|
|
322
|
+
return names.length > 0 ? names.join(", ") : null;
|
|
323
|
+
}
|
|
324
|
+
case "date": {
|
|
325
|
+
const ts = Number(field.value);
|
|
326
|
+
if (!Number.isFinite(ts)) return stringifyFieldValue(field.value);
|
|
327
|
+
return new Date(ts).toLocaleDateString("en-US", {
|
|
328
|
+
month: "short",
|
|
329
|
+
day: "numeric",
|
|
330
|
+
year: "numeric"
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
case "checkbox":
|
|
334
|
+
return field.value === true || field.value === "true" ? "Yes" : "No";
|
|
335
|
+
default:
|
|
336
|
+
return stringifyFieldValue(field.value);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
298
339
|
function formatTaskDetail(task) {
|
|
299
340
|
const lines = [];
|
|
300
341
|
const isInitiative2 = (task.custom_item_id ?? 0) !== 0;
|
|
@@ -324,6 +365,16 @@ function formatTaskDetail(task) {
|
|
|
324
365
|
if (!value) continue;
|
|
325
366
|
lines.push(` ${chalk2.bold(label.padEnd(maxLabel + 1))} ${value}`);
|
|
326
367
|
}
|
|
368
|
+
if (task.custom_fields?.length) {
|
|
369
|
+
const formatted = task.custom_fields.map((f) => [f.name, formatCustomFieldValue(f)]).filter((pair) => pair[1] !== null);
|
|
370
|
+
if (formatted.length > 0) {
|
|
371
|
+
lines.push("");
|
|
372
|
+
lines.push(chalk2.bold("Custom Fields"));
|
|
373
|
+
for (const [name, value] of formatted) {
|
|
374
|
+
lines.push(` ${chalk2.bold(name)} ${value}`);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
327
378
|
if (task.text_content?.trim()) {
|
|
328
379
|
lines.push("");
|
|
329
380
|
lines.push(descriptionPreview(task.text_content));
|
|
@@ -455,6 +506,19 @@ async function printTasks(tasks, forceJson, config) {
|
|
|
455
506
|
await showDetailsAndOpen(selected, fetchTask);
|
|
456
507
|
}
|
|
457
508
|
|
|
509
|
+
// src/status.ts
|
|
510
|
+
function matchStatus(input, statuses) {
|
|
511
|
+
if (!input) return null;
|
|
512
|
+
const lower = input.toLowerCase();
|
|
513
|
+
const exact = statuses.find((s) => s.toLowerCase() === lower);
|
|
514
|
+
if (exact) return exact;
|
|
515
|
+
const startsWith = statuses.find((s) => s.toLowerCase().startsWith(lower));
|
|
516
|
+
if (startsWith) return startsWith;
|
|
517
|
+
const contains = statuses.find((s) => s.toLowerCase().includes(lower));
|
|
518
|
+
if (contains) return contains;
|
|
519
|
+
return null;
|
|
520
|
+
}
|
|
521
|
+
|
|
458
522
|
// src/commands/update.ts
|
|
459
523
|
var PRIORITY_MAP = {
|
|
460
524
|
urgent: 1,
|
|
@@ -500,12 +564,30 @@ function buildUpdatePayload(opts) {
|
|
|
500
564
|
function hasUpdateFields(options) {
|
|
501
565
|
return options.name !== void 0 || options.description !== void 0 || options.status !== void 0 || options.priority !== void 0 || options.due_date !== void 0 || options.assignees !== void 0;
|
|
502
566
|
}
|
|
567
|
+
async function resolveStatus(client, taskId, statusInput) {
|
|
568
|
+
const task = await client.getTask(taskId);
|
|
569
|
+
if (!task.space) return statusInput;
|
|
570
|
+
const space = await client.getSpaceWithStatuses(task.space.id);
|
|
571
|
+
const available = space.statuses.map((s) => s.status);
|
|
572
|
+
const matched = matchStatus(statusInput, available);
|
|
573
|
+
if (!matched) {
|
|
574
|
+
throw new Error(`No matching status for "${statusInput}". Available: ${available.join(", ")}`);
|
|
575
|
+
}
|
|
576
|
+
if (matched.toLowerCase() !== statusInput.toLowerCase()) {
|
|
577
|
+
process.stderr.write(`Status matched: "${statusInput}" -> "${matched}"
|
|
578
|
+
`);
|
|
579
|
+
}
|
|
580
|
+
return matched;
|
|
581
|
+
}
|
|
503
582
|
async function updateTask(config, taskId, options) {
|
|
504
583
|
if (!hasUpdateFields(options))
|
|
505
584
|
throw new Error(
|
|
506
585
|
"Provide at least one of: --name, --description, --status, --priority, --due-date, --assignee"
|
|
507
586
|
);
|
|
508
587
|
const client = new ClickUpClient(config);
|
|
588
|
+
if (options.status !== void 0) {
|
|
589
|
+
options.status = await resolveStatus(client, taskId, options.status);
|
|
590
|
+
}
|
|
509
591
|
const task = await client.updateTask(taskId, options);
|
|
510
592
|
return { id: task.id, name: task.name };
|
|
511
593
|
}
|
|
@@ -683,6 +765,83 @@ async function runSprintCommand(config, opts) {
|
|
|
683
765
|
await printTasks(summaries, opts.json ?? false, config);
|
|
684
766
|
}
|
|
685
767
|
|
|
768
|
+
// src/commands/sprints.ts
|
|
769
|
+
var SPRINT_COLUMNS = [
|
|
770
|
+
{ key: "id", label: "ID" },
|
|
771
|
+
{ key: "sprint", label: "SPRINT", maxWidth: 60 },
|
|
772
|
+
{ key: "dates", label: "DATES" }
|
|
773
|
+
];
|
|
774
|
+
function formatDate(d) {
|
|
775
|
+
return `${d.getMonth() + 1}/${d.getDate()}`;
|
|
776
|
+
}
|
|
777
|
+
function buildSprintInfos(lists, folderName, today) {
|
|
778
|
+
return lists.map((list) => {
|
|
779
|
+
const dates = parseSprintDates(list.name);
|
|
780
|
+
const active = dates !== null && today >= dates.start && today <= dates.end;
|
|
781
|
+
return {
|
|
782
|
+
id: list.id,
|
|
783
|
+
name: list.name,
|
|
784
|
+
folder: folderName,
|
|
785
|
+
start: dates ? dates.start.toISOString() : null,
|
|
786
|
+
end: dates ? dates.end.toISOString() : null,
|
|
787
|
+
active
|
|
788
|
+
};
|
|
789
|
+
});
|
|
790
|
+
}
|
|
791
|
+
async function listSprints(config, opts = {}) {
|
|
792
|
+
const client = new ClickUpClient(config);
|
|
793
|
+
const [myTasks, allSpaces] = await Promise.all([
|
|
794
|
+
client.getMyTasks(config.teamId),
|
|
795
|
+
client.getSpaces(config.teamId)
|
|
796
|
+
]);
|
|
797
|
+
let spaces;
|
|
798
|
+
if (opts.space) {
|
|
799
|
+
spaces = allSpaces.filter(
|
|
800
|
+
(s) => s.name.toLowerCase().includes(opts.space.toLowerCase()) || s.id === opts.space
|
|
801
|
+
);
|
|
802
|
+
if (spaces.length === 0) {
|
|
803
|
+
throw new Error(
|
|
804
|
+
`No space matching "${opts.space}" found. Use \`cu spaces\` to list available spaces.`
|
|
805
|
+
);
|
|
806
|
+
}
|
|
807
|
+
} else {
|
|
808
|
+
const mySpaceIds = new Set(
|
|
809
|
+
myTasks.map((t) => t.space?.id).filter((id) => Boolean(id))
|
|
810
|
+
);
|
|
811
|
+
spaces = findRelatedSpaces(mySpaceIds, allSpaces);
|
|
812
|
+
}
|
|
813
|
+
const foldersBySpace = await Promise.all(spaces.map((space) => client.getFolders(space.id)));
|
|
814
|
+
const sprintFolders = foldersBySpace.flat().filter((f) => f.name.toLowerCase().includes("sprint"));
|
|
815
|
+
const today = /* @__PURE__ */ new Date();
|
|
816
|
+
const allSprints = [];
|
|
817
|
+
const listsByFolder = await Promise.all(
|
|
818
|
+
sprintFolders.map((folder) => client.getFolderLists(folder.id))
|
|
819
|
+
);
|
|
820
|
+
for (let i = 0; i < sprintFolders.length; i++) {
|
|
821
|
+
const folder = sprintFolders[i];
|
|
822
|
+
const lists = listsByFolder[i];
|
|
823
|
+
allSprints.push(...buildSprintInfos(lists, folder.name, today));
|
|
824
|
+
}
|
|
825
|
+
if (opts.json || !isTTY()) {
|
|
826
|
+
console.log(JSON.stringify(allSprints, null, 2));
|
|
827
|
+
return;
|
|
828
|
+
}
|
|
829
|
+
if (allSprints.length === 0) {
|
|
830
|
+
console.log("No sprints found.");
|
|
831
|
+
return;
|
|
832
|
+
}
|
|
833
|
+
const rows = allSprints.map((s) => {
|
|
834
|
+
const dates = parseSprintDates(s.name);
|
|
835
|
+
const dateStr = dates ? `${formatDate(dates.start)} - ${formatDate(dates.end)}` : "";
|
|
836
|
+
return {
|
|
837
|
+
id: s.id,
|
|
838
|
+
sprint: s.active ? `* ${s.name}` : s.name,
|
|
839
|
+
dates: dateStr
|
|
840
|
+
};
|
|
841
|
+
});
|
|
842
|
+
console.log(formatTable(rows, SPRINT_COLUMNS));
|
|
843
|
+
}
|
|
844
|
+
|
|
686
845
|
// src/commands/subtasks.ts
|
|
687
846
|
async function fetchSubtasks(config, taskId) {
|
|
688
847
|
const client = new ClickUpClient(config);
|
|
@@ -700,7 +859,7 @@ async function postComment(config, taskId, text) {
|
|
|
700
859
|
|
|
701
860
|
// src/commands/comments.ts
|
|
702
861
|
import chalk3 from "chalk";
|
|
703
|
-
function
|
|
862
|
+
function formatDate2(timestamp) {
|
|
704
863
|
return new Date(Number(timestamp)).toLocaleString("en-US", {
|
|
705
864
|
month: "short",
|
|
706
865
|
day: "numeric",
|
|
@@ -732,7 +891,7 @@ function printComments(comments, forceJson) {
|
|
|
732
891
|
for (let i = 0; i < comments.length; i++) {
|
|
733
892
|
const c = comments[i];
|
|
734
893
|
if (i > 0) console.log(separator);
|
|
735
|
-
console.log(`${chalk3.bold(c.user)} ${chalk3.dim(
|
|
894
|
+
console.log(`${chalk3.bold(c.user)} ${chalk3.dim(formatDate2(c.date))}`);
|
|
736
895
|
console.log(c.text);
|
|
737
896
|
if (i < comments.length - 1) console.log("");
|
|
738
897
|
}
|
|
@@ -1148,6 +1307,55 @@ async function assignTask(config, taskId, opts) {
|
|
|
1148
1307
|
});
|
|
1149
1308
|
}
|
|
1150
1309
|
|
|
1310
|
+
// src/commands/activity.ts
|
|
1311
|
+
import chalk4 from "chalk";
|
|
1312
|
+
function formatDate3(timestamp) {
|
|
1313
|
+
return new Date(Number(timestamp)).toLocaleString("en-US", {
|
|
1314
|
+
month: "short",
|
|
1315
|
+
day: "numeric",
|
|
1316
|
+
year: "numeric",
|
|
1317
|
+
hour: "numeric",
|
|
1318
|
+
minute: "2-digit"
|
|
1319
|
+
});
|
|
1320
|
+
}
|
|
1321
|
+
async function fetchActivity(config, taskId) {
|
|
1322
|
+
const client = new ClickUpClient(config);
|
|
1323
|
+
const [task, rawComments] = await Promise.all([
|
|
1324
|
+
client.getTask(taskId),
|
|
1325
|
+
client.getTaskComments(taskId)
|
|
1326
|
+
]);
|
|
1327
|
+
const comments = rawComments.map((c) => ({
|
|
1328
|
+
id: c.id,
|
|
1329
|
+
user: c.user.username,
|
|
1330
|
+
date: c.date,
|
|
1331
|
+
text: c.comment_text
|
|
1332
|
+
}));
|
|
1333
|
+
return { task, comments };
|
|
1334
|
+
}
|
|
1335
|
+
function printActivity(result, forceJson) {
|
|
1336
|
+
if (forceJson || !isTTY()) {
|
|
1337
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1338
|
+
return;
|
|
1339
|
+
}
|
|
1340
|
+
console.log(formatTaskDetail(result.task));
|
|
1341
|
+
console.log("");
|
|
1342
|
+
console.log(chalk4.bold("Comments"));
|
|
1343
|
+
console.log(chalk4.dim("-".repeat(60)));
|
|
1344
|
+
if (result.comments.length === 0) {
|
|
1345
|
+
console.log("No comments.");
|
|
1346
|
+
return;
|
|
1347
|
+
}
|
|
1348
|
+
for (let i = 0; i < result.comments.length; i++) {
|
|
1349
|
+
const c = result.comments[i];
|
|
1350
|
+
if (i > 0) {
|
|
1351
|
+
console.log("");
|
|
1352
|
+
console.log(chalk4.dim("-".repeat(60)));
|
|
1353
|
+
}
|
|
1354
|
+
console.log(`${chalk4.bold(c.user)} ${chalk4.dim(formatDate3(c.date))}`);
|
|
1355
|
+
console.log(c.text);
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1151
1359
|
// src/commands/completion.ts
|
|
1152
1360
|
function bashCompletion() {
|
|
1153
1361
|
return `_cu_completions() {
|
|
@@ -1552,6 +1760,47 @@ function generateCompletion(shell) {
|
|
|
1552
1760
|
}
|
|
1553
1761
|
}
|
|
1554
1762
|
|
|
1763
|
+
// src/commands/auth.ts
|
|
1764
|
+
async function checkAuth(config) {
|
|
1765
|
+
const client = new ClickUpClient(config);
|
|
1766
|
+
try {
|
|
1767
|
+
const user = await client.getMe();
|
|
1768
|
+
return { authenticated: true, user };
|
|
1769
|
+
} catch (err) {
|
|
1770
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1771
|
+
return { authenticated: false, error: message };
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1775
|
+
// src/commands/search.ts
|
|
1776
|
+
async function searchTasks(config, query, opts = {}) {
|
|
1777
|
+
const trimmed = query.trim();
|
|
1778
|
+
if (!trimmed) {
|
|
1779
|
+
throw new Error("Search query cannot be empty");
|
|
1780
|
+
}
|
|
1781
|
+
const client = new ClickUpClient(config);
|
|
1782
|
+
const allTasks = await client.getMyTasks(config.teamId);
|
|
1783
|
+
const words = trimmed.toLowerCase().split(/\s+/);
|
|
1784
|
+
let matched = allTasks.filter((task) => {
|
|
1785
|
+
const name = task.name.toLowerCase();
|
|
1786
|
+
return words.every((word) => name.includes(word));
|
|
1787
|
+
});
|
|
1788
|
+
if (opts.status) {
|
|
1789
|
+
const availableStatuses = [...new Set(allTasks.map((t) => t.status.status))];
|
|
1790
|
+
const resolved = matchStatus(opts.status, availableStatuses);
|
|
1791
|
+
if (resolved) {
|
|
1792
|
+
if (resolved.toLowerCase() !== opts.status.toLowerCase()) {
|
|
1793
|
+
process.stderr.write(`Status matched: "${opts.status}" -> "${resolved}"
|
|
1794
|
+
`);
|
|
1795
|
+
}
|
|
1796
|
+
matched = matched.filter((t) => t.status.status.toLowerCase() === resolved.toLowerCase());
|
|
1797
|
+
} else {
|
|
1798
|
+
matched = matched.filter((t) => t.status.status.toLowerCase() === opts.status.toLowerCase());
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
return matched.map(summarize);
|
|
1802
|
+
}
|
|
1803
|
+
|
|
1555
1804
|
// src/index.ts
|
|
1556
1805
|
var require2 = createRequire(import.meta.url);
|
|
1557
1806
|
var { version } = require2("../package.json");
|
|
@@ -1570,6 +1819,20 @@ program.command("init").description("Set up cu for the first time").action(
|
|
|
1570
1819
|
await runInitCommand();
|
|
1571
1820
|
})
|
|
1572
1821
|
);
|
|
1822
|
+
program.command("auth").description("Validate API token and show current user").option("--json", "Force JSON output even in terminal").action(
|
|
1823
|
+
wrapAction(async (opts) => {
|
|
1824
|
+
const config = loadConfig();
|
|
1825
|
+
const result = await checkAuth(config);
|
|
1826
|
+
if (opts.json || !isTTY()) {
|
|
1827
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1828
|
+
} else if (result.authenticated && result.user) {
|
|
1829
|
+
console.log(`Authenticated as @${result.user.username} (id: ${result.user.id})`);
|
|
1830
|
+
} else {
|
|
1831
|
+
console.error(`Authentication failed: ${result.error ?? "unknown error"}`);
|
|
1832
|
+
process.exit(1);
|
|
1833
|
+
}
|
|
1834
|
+
})
|
|
1835
|
+
);
|
|
1573
1836
|
program.command("tasks").description("List tasks assigned to me").option("--status <status>", 'Filter by status (e.g. "in progress")').option("--list <listId>", "Filter by list ID").option("--space <spaceId>", "Filter by space ID").option("--name <partial>", "Filter by name (case-insensitive contains)").option("--json", "Force JSON output even in terminal").action(
|
|
1574
1837
|
wrapAction(async (opts) => {
|
|
1575
1838
|
const config = loadConfig();
|
|
@@ -1603,40 +1866,30 @@ program.command("task <taskId>").description("Get task details").option("--json"
|
|
|
1603
1866
|
if (opts.json || !isTTY()) {
|
|
1604
1867
|
console.log(JSON.stringify(result, null, 2));
|
|
1605
1868
|
} else {
|
|
1606
|
-
|
|
1607
|
-
`ID: ${result.id}`,
|
|
1608
|
-
`Name: ${result.name}`,
|
|
1609
|
-
`Status: ${result.status?.status ?? "unknown"}`,
|
|
1610
|
-
`Type: ${(result.custom_item_id ?? 0) !== 0 ? "initiative" : "task"}`,
|
|
1611
|
-
`List: ${result.list?.name ?? "unknown"}`,
|
|
1612
|
-
`URL: ${result.url}`,
|
|
1613
|
-
...result.parent ? [`Parent: ${result.parent}`] : [],
|
|
1614
|
-
...result.description ? ["", result.description] : []
|
|
1615
|
-
];
|
|
1616
|
-
console.log(lines.join("\n"));
|
|
1869
|
+
console.log(formatTaskDetail(result));
|
|
1617
1870
|
}
|
|
1618
1871
|
})
|
|
1619
1872
|
);
|
|
1620
|
-
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("--assignee <userId>", "Add assignee by user ID").action(
|
|
1873
|
+
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("--assignee <userId>", "Add assignee by user ID").option("--json", "Force JSON output even in terminal").action(
|
|
1621
1874
|
wrapAction(async (taskId, opts) => {
|
|
1622
1875
|
const config = loadConfig();
|
|
1623
1876
|
const payload = buildUpdatePayload(opts);
|
|
1624
1877
|
const result = await updateTask(config, taskId, payload);
|
|
1625
|
-
if (isTTY()) {
|
|
1626
|
-
console.log(`Updated task ${result.id}: "${result.name}"`);
|
|
1627
|
-
} else {
|
|
1878
|
+
if (opts.json || !isTTY()) {
|
|
1628
1879
|
console.log(JSON.stringify(result, null, 2));
|
|
1880
|
+
} else {
|
|
1881
|
+
console.log(`Updated task ${result.id}: "${result.name}"`);
|
|
1629
1882
|
}
|
|
1630
1883
|
})
|
|
1631
1884
|
);
|
|
1632
|
-
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").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").action(
|
|
1885
|
+
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").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("--json", "Force JSON output even in terminal").action(
|
|
1633
1886
|
wrapAction(async (opts) => {
|
|
1634
1887
|
const config = loadConfig();
|
|
1635
1888
|
const result = await createTask(config, opts);
|
|
1636
|
-
if (isTTY()) {
|
|
1637
|
-
console.log(`Created task ${result.id}: "${result.name}" - ${result.url}`);
|
|
1638
|
-
} else {
|
|
1889
|
+
if (opts.json || !isTTY()) {
|
|
1639
1890
|
console.log(JSON.stringify(result, null, 2));
|
|
1891
|
+
} else {
|
|
1892
|
+
console.log(`Created task ${result.id}: "${result.name}" - ${result.url}`);
|
|
1640
1893
|
}
|
|
1641
1894
|
})
|
|
1642
1895
|
);
|
|
@@ -1646,6 +1899,12 @@ program.command("sprint").description("List my tasks in the current active sprin
|
|
|
1646
1899
|
await runSprintCommand(config, opts);
|
|
1647
1900
|
})
|
|
1648
1901
|
);
|
|
1902
|
+
program.command("sprints").description("List all sprints in sprint folders").option("--space <nameOrId>", "Filter by space (partial name or ID)").option("--json", "Force JSON output even in terminal").action(
|
|
1903
|
+
wrapAction(async (opts) => {
|
|
1904
|
+
const config = loadConfig();
|
|
1905
|
+
await listSprints(config, opts);
|
|
1906
|
+
})
|
|
1907
|
+
);
|
|
1649
1908
|
program.command("subtasks <taskId>").description("List subtasks of a task or initiative").option("--json", "Force JSON output even in terminal").action(
|
|
1650
1909
|
wrapAction(async (taskId, opts) => {
|
|
1651
1910
|
const config = loadConfig();
|
|
@@ -1671,6 +1930,13 @@ program.command("comments <taskId>").description("List comments on a task").opti
|
|
|
1671
1930
|
printComments(comments, opts.json ?? false);
|
|
1672
1931
|
})
|
|
1673
1932
|
);
|
|
1933
|
+
program.command("activity <taskId>").description("Show task details and comments combined").option("--json", "Force JSON output even in terminal").action(
|
|
1934
|
+
wrapAction(async (taskId, opts) => {
|
|
1935
|
+
const config = loadConfig();
|
|
1936
|
+
const result = await fetchActivity(config, taskId);
|
|
1937
|
+
printActivity(result, opts.json ?? false);
|
|
1938
|
+
})
|
|
1939
|
+
);
|
|
1674
1940
|
program.command("lists <spaceId>").description("List all lists in a space (including lists inside folders)").option("--name <partial>", "Filter by name (case-insensitive contains)").option("--json", "Force JSON output even in terminal").action(
|
|
1675
1941
|
wrapAction(async (spaceId, opts) => {
|
|
1676
1942
|
const config = loadConfig();
|
|
@@ -1708,6 +1974,13 @@ program.command("open <query>").description("Open a task in the browser by ID or
|
|
|
1708
1974
|
await openTask(config, query, opts);
|
|
1709
1975
|
})
|
|
1710
1976
|
);
|
|
1977
|
+
program.command("search <query>").description("Search my tasks by name").option("--status <status>", "Filter by status").option("--json", "Force JSON output even in terminal").action(
|
|
1978
|
+
wrapAction(async (query, opts) => {
|
|
1979
|
+
const config = loadConfig();
|
|
1980
|
+
const tasks = await searchTasks(config, query, { status: opts.status });
|
|
1981
|
+
await printTasks(tasks, opts.json ?? false, config);
|
|
1982
|
+
})
|
|
1983
|
+
);
|
|
1711
1984
|
program.command("summary").description("Daily standup summary: completed, in-progress, overdue").option("--hours <n>", "Completed-tasks lookback in hours", "24").option("--json", "Force JSON output even in terminal").action(
|
|
1712
1985
|
wrapAction(async (opts) => {
|
|
1713
1986
|
const config = loadConfig();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@krodak/clickup-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "ClickUp CLI for AI agents and humans",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -24,6 +24,8 @@
|
|
|
24
24
|
},
|
|
25
25
|
"files": [
|
|
26
26
|
"dist",
|
|
27
|
+
".claude-plugin",
|
|
28
|
+
"skills",
|
|
27
29
|
"README.md",
|
|
28
30
|
"LICENSE"
|
|
29
31
|
],
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: clickup
|
|
3
|
+
description: 'Use when managing ClickUp tasks, initiatives, sprints, or comments via the `cu` CLI tool. Covers task queries, status updates, sprint tracking, and project management workflows.'
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# ClickUp CLI (`cu`)
|
|
7
|
+
|
|
8
|
+
Reference for AI agents using the `cu` command-line tool to interact with ClickUp. Covers task management, sprint tracking, initiatives, and comments.
|
|
9
|
+
|
|
10
|
+
Keywords: ClickUp, task management, sprint, initiative, project management, agile, backlog, subtasks
|
|
11
|
+
|
|
12
|
+
## Setup
|
|
13
|
+
|
|
14
|
+
Config at `~/.config/cu/config.json` (or `$XDG_CONFIG_HOME/cu/config.json`) with `apiToken` and `teamId`. Run `cu init` to set up.
|
|
15
|
+
|
|
16
|
+
Alternatively, set `CU_API_TOKEN` and `CU_TEAM_ID` environment variables (overrides config file, no file needed when both are set).
|
|
17
|
+
|
|
18
|
+
## Commands
|
|
19
|
+
|
|
20
|
+
All commands support `--help` for full flag details.
|
|
21
|
+
|
|
22
|
+
### Read
|
|
23
|
+
|
|
24
|
+
| Command | What it returns |
|
|
25
|
+
| -------------------------------------------------------------------------- | -------------------------------------------------- |
|
|
26
|
+
| `cu tasks [--status s] [--name q] [--list id] [--space id] [--json]` | My tasks (workspace-wide) |
|
|
27
|
+
| `cu initiatives [--status s] [--name q] [--list id] [--space id] [--json]` | My initiatives |
|
|
28
|
+
| `cu assigned [--include-closed] [--json]` | All my tasks grouped by status |
|
|
29
|
+
| `cu sprint [--status s] [--space nameOrId] [--json]` | Tasks in active sprint (auto-detected) |
|
|
30
|
+
| `cu inbox [--days n] [--json]` | Tasks updated in last n days (default 30) |
|
|
31
|
+
| `cu task <id> [--json]` | Single task details |
|
|
32
|
+
| `cu subtasks <id> [--json]` | Subtasks of a task or initiative |
|
|
33
|
+
| `cu comments <id> [--json]` | List comments on a task |
|
|
34
|
+
| `cu spaces [--name partial] [--my] [--json]` | List/filter workspace spaces |
|
|
35
|
+
| `cu lists <spaceId> [--name partial] [--json]` | List all lists in a space (including folder lists) |
|
|
36
|
+
| `cu open <query> [--json]` | Open task in browser by ID or name |
|
|
37
|
+
| `cu summary [--hours n] [--json]` | Standup helper: completed, in-progress, overdue |
|
|
38
|
+
| `cu overdue [--json]` | Tasks past their due date |
|
|
39
|
+
| `cu search <query> [--status s] [--json]` | Search my tasks by name (multi-word, fuzzy status) |
|
|
40
|
+
| `cu activity <id> [--json]` | Task details + comment history combined |
|
|
41
|
+
| `cu auth [--json]` | Check authentication status |
|
|
42
|
+
| `cu sprints [--space nameOrId] [--json]` | List all sprints (marks active with \*) |
|
|
43
|
+
|
|
44
|
+
### Write
|
|
45
|
+
|
|
46
|
+
| Command | What it does |
|
|
47
|
+
| ------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- |
|
|
48
|
+
| `cu update <id> [-n name] [-d desc] [-s status] [--priority p] [--due-date d] [--assignee id] [--json]` | Update task fields |
|
|
49
|
+
| `cu create -n name [-l listId] [-p parentId] [-d desc] [-s status] [--priority p] [--due-date d] [--assignee id] [--tags t] [--json]` | Create task (list auto-detected from parent) |
|
|
50
|
+
| `cu comment <id> -m text` | Post comment on task |
|
|
51
|
+
| `cu assign <id> [--to userId\|me] [--remove userId\|me] [--json]` | Assign/unassign users from a task |
|
|
52
|
+
| `cu config get <key>` / `cu config set <key> <value>` / `cu config path` | Manage CLI config |
|
|
53
|
+
| `cu completion <shell>` | Output shell completions (bash/zsh/fish) |
|
|
54
|
+
|
|
55
|
+
## Output modes
|
|
56
|
+
|
|
57
|
+
- **TTY (terminal)**: Interactive picker UI. Use `--json` to bypass.
|
|
58
|
+
- **Piped / non-TTY**: Always JSON. This is what agents get by default.
|
|
59
|
+
- **Agents should always pass `--json`** to guarantee machine-readable output.
|
|
60
|
+
- Set `NO_COLOR` to disable color output (tables still render, just without color).
|
|
61
|
+
|
|
62
|
+
## Key facts
|
|
63
|
+
|
|
64
|
+
- All task IDs are stable alphanumeric strings (e.g. `abc123def`)
|
|
65
|
+
- Initiatives are detected via `custom_item_id !== 0` (not `task_type`)
|
|
66
|
+
- `--list` is optional in `cu create` when `--parent` is given (list auto-detected from parent)
|
|
67
|
+
- `cu sprint` auto-detects active sprint from spaces where user has tasks, using view API and date range parsing from sprint names like "Acme Sprint 4 (3/1 - 3/14)"
|
|
68
|
+
- `--name` on tasks/initiatives filters by partial name match (case-insensitive)
|
|
69
|
+
- `--space` on sprint/tasks accepts partial name match (e.g. `--space Acm`)
|
|
70
|
+
- `--priority` accepts names (`urgent`, `high`, `normal`, `low`) or numbers (1-4)
|
|
71
|
+
- `--due-date` accepts `YYYY-MM-DD` format
|
|
72
|
+
- `--assignee` takes a numeric user ID (use `cu task <id> --json` to find assignee IDs)
|
|
73
|
+
- `cu assign` supports `me` as a shorthand for your own user ID
|
|
74
|
+
- `cu open` tries task ID lookup first, then falls back to name search
|
|
75
|
+
- `cu summary` categories: completed (done/complete/closed within N hours), in progress, overdue
|
|
76
|
+
- `cu overdue` excludes done/complete/closed tasks, sorted most overdue first
|
|
77
|
+
- `--tags` accepts comma-separated tag names (e.g. `--tags "bug,frontend"`)
|
|
78
|
+
- `cu lists <spaceId>` discovers list IDs needed for `--list` and `cu create -l`
|
|
79
|
+
- Strict argument parsing - excess/unknown arguments are rejected
|
|
80
|
+
- `cu update -s` supports fuzzy status matching (exact > starts-with > contains). Prints matched status to stderr when fuzzy-resolved.
|
|
81
|
+
- `cu task` shows custom fields in detail view (read-only)
|
|
82
|
+
- `cu search` matches all query words against task name (case-insensitive). `--status` supports fuzzy matching.
|
|
83
|
+
- Errors go to stderr with exit code 1
|
|
84
|
+
|
|
85
|
+
## Agent workflow example
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
# List my in-progress tasks
|
|
89
|
+
cu tasks --status "in progress" --json
|
|
90
|
+
|
|
91
|
+
# Find tasks by partial name
|
|
92
|
+
cu tasks --name "login" --json
|
|
93
|
+
|
|
94
|
+
# Check current sprint
|
|
95
|
+
cu sprint --json | jq '.[].name'
|
|
96
|
+
|
|
97
|
+
# Get full details on a task
|
|
98
|
+
cu task abc123def --json
|
|
99
|
+
|
|
100
|
+
# List subtasks of an initiative
|
|
101
|
+
cu subtasks abc123def --json
|
|
102
|
+
|
|
103
|
+
# Read comments on a task
|
|
104
|
+
cu comments abc123def --json
|
|
105
|
+
|
|
106
|
+
# Update task status
|
|
107
|
+
cu update abc123def -s "done"
|
|
108
|
+
|
|
109
|
+
# Update priority and due date
|
|
110
|
+
cu update abc123def --priority high --due-date 2025-03-15
|
|
111
|
+
|
|
112
|
+
# Create subtask under initiative (no --list needed)
|
|
113
|
+
cu create -n "Fix the thing" -p abc123def
|
|
114
|
+
|
|
115
|
+
# Create task with priority and tags
|
|
116
|
+
cu create -n "Fix bug" -l <listId> --priority urgent --tags "bug,frontend"
|
|
117
|
+
|
|
118
|
+
# Post a comment
|
|
119
|
+
cu comment abc123def -m "Completed in PR #42"
|
|
120
|
+
|
|
121
|
+
# Discover lists in a space
|
|
122
|
+
cu lists <spaceId> --json
|
|
123
|
+
|
|
124
|
+
# Open a task in the browser
|
|
125
|
+
cu open abc123def
|
|
126
|
+
|
|
127
|
+
# Standup summary
|
|
128
|
+
cu summary --json
|
|
129
|
+
|
|
130
|
+
# Check overdue tasks
|
|
131
|
+
cu overdue --json
|
|
132
|
+
|
|
133
|
+
# Assign yourself to a task
|
|
134
|
+
cu assign abc123def --to me
|
|
135
|
+
|
|
136
|
+
# Check/set config
|
|
137
|
+
cu config get teamId
|
|
138
|
+
cu config set apiToken pk_example_token
|
|
139
|
+
|
|
140
|
+
cu search "login bug" --json
|
|
141
|
+
|
|
142
|
+
cu activity abc123def --json
|
|
143
|
+
|
|
144
|
+
cu auth --json
|
|
145
|
+
|
|
146
|
+
cu sprints --json
|
|
147
|
+
|
|
148
|
+
cu update abc123def -s "prog"
|
|
149
|
+
```
|