@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.
@@ -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 and humans. Two operating modes: interactive tables with a task picker in terminals, raw JSON when piped.
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
- ## Requirements
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
- ## Getting started
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
- Prompts for your API token, verifies it, auto-detects your workspace, and writes `~/.config/cu/config.json`.
14
+ ## Using with AI agents
23
15
 
24
- ## Operating Modes
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
- ### Interactive TTY mode (for humans)
18
+ ### 1. Install the skill
27
19
 
28
- When run in a terminal (TTY detected), list commands (`cu tasks`, `cu initiatives`, `cu sprint`, `cu inbox`, `cu subtasks`, `cu overdue`) display:
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
- 1. A formatted table with auto-sized columns showing task ID, name, status, type, list, and URL.
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
- Pass `--json` to any list command to bypass interactive mode and get raw JSON output in a terminal.
24
+ The repo ships as a Claude Code plugin. Point Claude Code at the repo or installed npm package:
36
25
 
37
- ### JSON piped mode (for AI agents and scripts)
38
-
39
- When output is piped (no TTY), all list commands output JSON arrays to stdout. No interactive UI is shown.
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
- Always use the `--json` flag or pipe output to ensure you get JSON. Parse with `jq` or programmatically.
30
+ Or copy the skill manually:
50
31
 
51
32
  ```bash
52
- # List my in-progress tasks
53
- cu tasks --status "in progress" --json | jq '.[] | {id, name}'
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
- # Check current sprint
59
- cu sprint --json | jq '.[] | select(.status != "done")'
37
+ Then reference it in your `CLAUDE.md` or project instructions.
60
38
 
61
- # Create subtask under initiative
62
- INITIATIVE_ID=$(cu initiatives --json | jq -r '.[0].id')
63
- cu create -n "New subtask" -p "$INITIATIVE_ID"
39
+ **OpenCode:**
64
40
 
65
- # Update status when done
66
- cu update abc123 -s "done"
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
- Write commands (`update`, `create`, `comment`) always return JSON, no `--json` flag needed.
46
+ **Codex / other agents:**
70
47
 
71
- ## AI Agents Skill
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
- A skill file is included at `skill/SKILL.md` that teaches AI agents how to use `cu`. Install it for your agent of choice:
50
+ ### 2. Talk to your agent
74
51
 
75
- ### OpenCode
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
- ### Claude Code
57
+ "Check all subtasks under initiative <id> and improve their descriptions based on what's in the codebase."
83
58
 
84
- ```bash
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
- Then reference it in your `CLAUDE.md` or project instructions.
61
+ "Do exploratory work for task <id>, update the description with your findings, and flag blockers in a comment."
90
62
 
91
- ### Codex
63
+ "Create a subtask under <id> for the edge case we just found."
92
64
 
93
- Copy the contents of `skill/SKILL.md` into your Codex system prompt or project instructions file.
65
+ "Check my sprint and tell me what's overdue."
66
+ ```
94
67
 
95
- ### Other agents
68
+ You don't need to learn the CLI commands yourself. The agent handles it.
96
69
 
97
- The skill file is a standalone markdown document. Feed it to any agent that supports custom instructions or tool documentation.
70
+ ### Why a CLI and not MCP?
98
71
 
99
- ## Config
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
- ### Config file
74
+ ### Scoped output
102
75
 
103
- `~/.config/cu/config.json` (or `$XDG_CONFIG_HOME/cu/config.json`):
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
- ```json
106
- {
107
- "apiToken": "pk_...",
108
- "teamId": "12345678"
109
- }
110
- ```
78
+ ## Using from the terminal
111
79
 
112
- ### Environment variables
80
+ When you run `cu` in a terminal directly, you get an interactive mode with tables and a task picker.
113
81
 
114
- Environment variables override config file values:
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
- | Variable | Description |
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 both are set, the config file is not required. Useful for CI/CD and containerized agents.
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 # force JSON output
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 # full JSON response
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. `"in progress"`, `"done"`) |
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" # filter by partial name
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" # filter by partial name match (case-insensitive)
293
- cu spaces --my # show only spaces you are a member of
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 # open by task ID
309
- cu open "login bug" # search by name, open first match
310
- cu open abc123 --json # output task JSON instead of opening
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 # extend completed window to 48 hours
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 # add assignee
347
- cu assign abc123 --to me # assign yourself
348
- cu assign abc123 --remove 12345 # remove assignee
349
- cu assign abc123 --to me --remove 67890 # both at once
350
- cu assign abc123 --to me --json # output updated task 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 # print a config value
365
- cu config set teamId 12345 # set a config value
366
- cu config path # print config file 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
- # Bash - add to ~/.bashrc
377
- eval "$(cu completion bash)"
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
- # Zsh - add to ~/.zshrc
380
- eval "$(cu completion zsh)"
398
+ ### Config file
399
+
400
+ `~/.config/cu/config.json` (or `$XDG_CONFIG_HOME/cu/config.json`):
381
401
 
382
- # Fish - save to completions directory
383
- cu completion fish > ~/.config/fish/completions/cu.fish
402
+ ```json
403
+ {
404
+ "apiToken": "pk_...",
405
+ "teamId": "12345678"
406
+ }
384
407
  ```
385
408
 
386
- Completions cover all commands, flags, and known values (priority levels, status names, config keys).
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
- fs.writeFileSync(join(dir, "config.json"), JSON.stringify(config, null, 2) + "\n", "utf-8");
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 formatDate(timestamp) {
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(formatDate(c.date))}`);
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
- const lines = [
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.6.1",
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
+ ```