@krodak/clickup-cli 0.7.0 → 0.9.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 +65 -15
- package/dist/index.js +290 -17
- package/package.json +3 -1
- package/skills/clickup-cli/SKILL.md +142 -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
|
@@ -20,13 +20,21 @@ This is the primary use case. Install the tool, install the skill file, and your
|
|
|
20
20
|
|
|
21
21
|
### 1. Install the skill
|
|
22
22
|
|
|
23
|
-
The repo includes a skill file at `
|
|
23
|
+
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.
|
|
24
24
|
|
|
25
|
-
**Claude Code:**
|
|
25
|
+
**Claude Code (plugin - recommended):**
|
|
26
|
+
|
|
27
|
+
The repo ships as a Claude Code plugin. Point Claude Code at the repo or installed npm package:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
claude --plugin-dir ./node_modules/@krodak/clickup-cli
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Or copy the skill manually:
|
|
26
34
|
|
|
27
35
|
```bash
|
|
28
36
|
mkdir -p ~/.claude/skills/clickup
|
|
29
|
-
cp
|
|
37
|
+
cp skills/clickup-cli/SKILL.md ~/.claude/skills/clickup/SKILL.md
|
|
30
38
|
```
|
|
31
39
|
|
|
32
40
|
Then reference it in your `CLAUDE.md` or project instructions.
|
|
@@ -35,12 +43,12 @@ Then reference it in your `CLAUDE.md` or project instructions.
|
|
|
35
43
|
|
|
36
44
|
```bash
|
|
37
45
|
mkdir -p ~/.config/opencode/skills/clickup
|
|
38
|
-
cp
|
|
46
|
+
cp skills/clickup-cli/SKILL.md ~/.config/opencode/skills/clickup/SKILL.md
|
|
39
47
|
```
|
|
40
48
|
|
|
41
49
|
**Codex / other agents:**
|
|
42
50
|
|
|
43
|
-
Copy the contents of `
|
|
51
|
+
Copy the contents of `skills/clickup-cli/SKILL.md` into your system prompt or project instructions. It's a standalone markdown document.
|
|
44
52
|
|
|
45
53
|
### 2. Talk to your agent
|
|
46
54
|
|
|
@@ -90,7 +98,7 @@ When output is piped (no TTY), all commands output **Markdown** by default - opt
|
|
|
90
98
|
|
|
91
99
|
## Commands
|
|
92
100
|
|
|
93
|
-
|
|
101
|
+
24 commands total. All support `--help` for full flag details.
|
|
94
102
|
|
|
95
103
|
### `cu init`
|
|
96
104
|
|
|
@@ -136,6 +144,15 @@ cu sprint --status "in progress"
|
|
|
136
144
|
cu sprint --json
|
|
137
145
|
```
|
|
138
146
|
|
|
147
|
+
### `cu sprints`
|
|
148
|
+
|
|
149
|
+
List all sprints across sprint folders. Marks the currently active sprint.
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
cu sprints
|
|
153
|
+
cu sprints --json
|
|
154
|
+
```
|
|
155
|
+
|
|
139
156
|
### `cu assigned`
|
|
140
157
|
|
|
141
158
|
All tasks assigned to me, grouped by pipeline stage (code review, in progress, to do, etc.).
|
|
@@ -158,7 +175,7 @@ cu inbox --json
|
|
|
158
175
|
|
|
159
176
|
### `cu task <id>`
|
|
160
177
|
|
|
161
|
-
Get task details. Pretty summary in terminal, JSON when piped.
|
|
178
|
+
Get task details including custom fields. Pretty summary in terminal, JSON when piped.
|
|
162
179
|
|
|
163
180
|
```bash
|
|
164
181
|
cu task abc123
|
|
@@ -188,16 +205,18 @@ cu update abc123 --priority high
|
|
|
188
205
|
cu update abc123 --due-date 2025-03-15
|
|
189
206
|
cu update abc123 --assignee 12345
|
|
190
207
|
cu update abc123 -n "New name" -s "done" --priority urgent
|
|
208
|
+
cu update abc123 -s "in progress" --json
|
|
191
209
|
```
|
|
192
210
|
|
|
193
|
-
| Flag | Description
|
|
194
|
-
| -------------------------- |
|
|
195
|
-
| `-n, --name <text>` | New task name
|
|
196
|
-
| `-d, --description <text>` | New description (markdown supported)
|
|
197
|
-
| `-s, --status <status>` | New status (e.g. `"
|
|
198
|
-
| `--priority <level>` | Priority: `urgent`, `high`, `normal`, `low` (or 1-4)
|
|
199
|
-
| `--due-date <date>` | Due date (`YYYY-MM-DD`)
|
|
200
|
-
| `--assignee <userId>` | Add assignee by numeric user ID
|
|
211
|
+
| Flag | Description |
|
|
212
|
+
| -------------------------- | --------------------------------------------------------------------------- |
|
|
213
|
+
| `-n, --name <text>` | New task name |
|
|
214
|
+
| `-d, --description <text>` | New description (markdown supported) |
|
|
215
|
+
| `-s, --status <status>` | New status, supports fuzzy matching (e.g. `"prog"` matches `"in progress"`) |
|
|
216
|
+
| `--priority <level>` | Priority: `urgent`, `high`, `normal`, `low` (or 1-4) |
|
|
217
|
+
| `--due-date <date>` | Due date (`YYYY-MM-DD`) |
|
|
218
|
+
| `--assignee <userId>` | Add assignee by numeric user ID |
|
|
219
|
+
| `--json` | Force JSON output even in terminal |
|
|
201
220
|
|
|
202
221
|
### `cu create`
|
|
203
222
|
|
|
@@ -209,6 +228,7 @@ cu create -n "Subtask name" -p <parentTaskId> # --list auto-detected
|
|
|
209
228
|
cu create -n "Task" -l <listId> -d "desc" -s "open"
|
|
210
229
|
cu create -n "Task" -l <listId> --priority high --due-date 2025-06-01
|
|
211
230
|
cu create -n "Task" -l <listId> --assignee 12345 --tags "bug,frontend"
|
|
231
|
+
cu create -n "Fix bug" -l <listId> --json
|
|
212
232
|
```
|
|
213
233
|
|
|
214
234
|
| Flag | Required | Description |
|
|
@@ -222,6 +242,7 @@ cu create -n "Task" -l <listId> --assignee 12345 --tags "bug,frontend"
|
|
|
222
242
|
| `--due-date <date>` | no | Due date (`YYYY-MM-DD`) |
|
|
223
243
|
| `--assignee <userId>` | no | Assignee by numeric user ID |
|
|
224
244
|
| `--tags <tags>` | no | Comma-separated tag names |
|
|
245
|
+
| `--json` | no | Force JSON output even in terminal |
|
|
225
246
|
|
|
226
247
|
### `cu comment <id>`
|
|
227
248
|
|
|
@@ -240,6 +261,15 @@ cu comments abc123
|
|
|
240
261
|
cu comments abc123 --json
|
|
241
262
|
```
|
|
242
263
|
|
|
264
|
+
### `cu activity <id>`
|
|
265
|
+
|
|
266
|
+
View task details and comment history together. Combines `cu task` and `cu comments` into a single view.
|
|
267
|
+
|
|
268
|
+
```bash
|
|
269
|
+
cu activity abc123
|
|
270
|
+
cu activity abc123 --json
|
|
271
|
+
```
|
|
272
|
+
|
|
243
273
|
### `cu lists <spaceId>`
|
|
244
274
|
|
|
245
275
|
List all lists in a space, including lists inside folders. Useful for discovering list IDs needed by `--list` filter and `cu create -l`.
|
|
@@ -284,6 +314,17 @@ cu open abc123 --json
|
|
|
284
314
|
|
|
285
315
|
If the query matches multiple tasks by name, all matches are listed and the first is opened.
|
|
286
316
|
|
|
317
|
+
### `cu search <query>`
|
|
318
|
+
|
|
319
|
+
Search my tasks by name. Supports multi-word queries with case-insensitive matching. Status filter supports fuzzy matching.
|
|
320
|
+
|
|
321
|
+
```bash
|
|
322
|
+
cu search "login bug"
|
|
323
|
+
cu search auth
|
|
324
|
+
cu search "payment flow" --json
|
|
325
|
+
cu search auth --status "prog" # fuzzy matches "in progress"
|
|
326
|
+
```
|
|
327
|
+
|
|
287
328
|
### `cu summary`
|
|
288
329
|
|
|
289
330
|
Daily standup helper. Shows tasks grouped into: recently completed, in progress, and overdue.
|
|
@@ -326,6 +367,15 @@ cu assign abc123 --to me --json
|
|
|
326
367
|
| `--remove <userId>` | Remove assignee (user ID or `me`) |
|
|
327
368
|
| `--json` | Force JSON output |
|
|
328
369
|
|
|
370
|
+
### `cu auth`
|
|
371
|
+
|
|
372
|
+
Check authentication status. Validates your API token and shows your user info.
|
|
373
|
+
|
|
374
|
+
```bash
|
|
375
|
+
cu auth
|
|
376
|
+
cu auth --json
|
|
377
|
+
```
|
|
378
|
+
|
|
329
379
|
### `cu config`
|
|
330
380
|
|
|
331
381
|
Manage CLI configuration.
|
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 ?? [];
|
|
@@ -422,6 +429,40 @@ function descriptionPreview(text, maxLines = 3) {
|
|
|
422
429
|
${chalk2.dim(`... (${lines.length - maxLines} more lines)`)}`;
|
|
423
430
|
return result;
|
|
424
431
|
}
|
|
432
|
+
function stringifyFieldValue(value) {
|
|
433
|
+
if (typeof value === "string") return value;
|
|
434
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
435
|
+
return JSON.stringify(value);
|
|
436
|
+
}
|
|
437
|
+
function formatCustomFieldValue(field) {
|
|
438
|
+
if (field.value === null || field.value === void 0) return null;
|
|
439
|
+
const options = field.type_config?.options;
|
|
440
|
+
switch (field.type) {
|
|
441
|
+
case "drop_down": {
|
|
442
|
+
if (!options) return stringifyFieldValue(field.value);
|
|
443
|
+
const match = options.find((o) => o.id === Number(field.value));
|
|
444
|
+
return match?.name ?? stringifyFieldValue(field.value);
|
|
445
|
+
}
|
|
446
|
+
case "labels": {
|
|
447
|
+
if (!Array.isArray(field.value) || !options) return stringifyFieldValue(field.value);
|
|
448
|
+
const names = field.value.map((id) => options.find((o) => o.id === id)?.name).filter((n) => n !== void 0);
|
|
449
|
+
return names.length > 0 ? names.join(", ") : null;
|
|
450
|
+
}
|
|
451
|
+
case "date": {
|
|
452
|
+
const ts = Number(field.value);
|
|
453
|
+
if (!Number.isFinite(ts)) return stringifyFieldValue(field.value);
|
|
454
|
+
return new Date(ts).toLocaleDateString("en-US", {
|
|
455
|
+
month: "short",
|
|
456
|
+
day: "numeric",
|
|
457
|
+
year: "numeric"
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
case "checkbox":
|
|
461
|
+
return field.value === true || field.value === "true" ? "Yes" : "No";
|
|
462
|
+
default:
|
|
463
|
+
return stringifyFieldValue(field.value);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
425
466
|
function formatTaskDetail(task) {
|
|
426
467
|
const lines = [];
|
|
427
468
|
const isInitiative2 = (task.custom_item_id ?? 0) !== 0;
|
|
@@ -451,6 +492,16 @@ function formatTaskDetail(task) {
|
|
|
451
492
|
if (!value) continue;
|
|
452
493
|
lines.push(` ${chalk2.bold(label.padEnd(maxLabel + 1))} ${value}`);
|
|
453
494
|
}
|
|
495
|
+
if (task.custom_fields?.length) {
|
|
496
|
+
const formatted = task.custom_fields.map((f) => [f.name, formatCustomFieldValue(f)]).filter((pair) => pair[1] !== null);
|
|
497
|
+
if (formatted.length > 0) {
|
|
498
|
+
lines.push("");
|
|
499
|
+
lines.push(chalk2.bold("Custom Fields"));
|
|
500
|
+
for (const [name, value] of formatted) {
|
|
501
|
+
lines.push(` ${chalk2.bold(name)} ${value}`);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
454
505
|
if (task.text_content?.trim()) {
|
|
455
506
|
lines.push("");
|
|
456
507
|
lines.push(descriptionPreview(task.text_content));
|
|
@@ -586,6 +637,19 @@ async function printTasks(tasks, forceJson, config) {
|
|
|
586
637
|
await showDetailsAndOpen(selected, fetchTask);
|
|
587
638
|
}
|
|
588
639
|
|
|
640
|
+
// src/status.ts
|
|
641
|
+
function matchStatus(input, statuses) {
|
|
642
|
+
if (!input) return null;
|
|
643
|
+
const lower = input.toLowerCase();
|
|
644
|
+
const exact = statuses.find((s) => s.toLowerCase() === lower);
|
|
645
|
+
if (exact) return exact;
|
|
646
|
+
const startsWith = statuses.find((s) => s.toLowerCase().startsWith(lower));
|
|
647
|
+
if (startsWith) return startsWith;
|
|
648
|
+
const contains = statuses.find((s) => s.toLowerCase().includes(lower));
|
|
649
|
+
if (contains) return contains;
|
|
650
|
+
return null;
|
|
651
|
+
}
|
|
652
|
+
|
|
589
653
|
// src/commands/update.ts
|
|
590
654
|
var PRIORITY_MAP = {
|
|
591
655
|
urgent: 1,
|
|
@@ -631,12 +695,30 @@ function buildUpdatePayload(opts) {
|
|
|
631
695
|
function hasUpdateFields(options) {
|
|
632
696
|
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;
|
|
633
697
|
}
|
|
698
|
+
async function resolveStatus(client, taskId, statusInput) {
|
|
699
|
+
const task = await client.getTask(taskId);
|
|
700
|
+
if (!task.space) return statusInput;
|
|
701
|
+
const space = await client.getSpaceWithStatuses(task.space.id);
|
|
702
|
+
const available = space.statuses.map((s) => s.status);
|
|
703
|
+
const matched = matchStatus(statusInput, available);
|
|
704
|
+
if (!matched) {
|
|
705
|
+
throw new Error(`No matching status for "${statusInput}". Available: ${available.join(", ")}`);
|
|
706
|
+
}
|
|
707
|
+
if (matched.toLowerCase() !== statusInput.toLowerCase()) {
|
|
708
|
+
process.stderr.write(`Status matched: "${statusInput}" -> "${matched}"
|
|
709
|
+
`);
|
|
710
|
+
}
|
|
711
|
+
return matched;
|
|
712
|
+
}
|
|
634
713
|
async function updateTask(config, taskId, options) {
|
|
635
714
|
if (!hasUpdateFields(options))
|
|
636
715
|
throw new Error(
|
|
637
716
|
"Provide at least one of: --name, --description, --status, --priority, --due-date, --assignee"
|
|
638
717
|
);
|
|
639
718
|
const client = new ClickUpClient(config);
|
|
719
|
+
if (options.status !== void 0) {
|
|
720
|
+
options.status = await resolveStatus(client, taskId, options.status);
|
|
721
|
+
}
|
|
640
722
|
const task = await client.updateTask(taskId, options);
|
|
641
723
|
return { id: task.id, name: task.name };
|
|
642
724
|
}
|
|
@@ -814,6 +896,83 @@ async function runSprintCommand(config, opts) {
|
|
|
814
896
|
await printTasks(summaries, opts.json ?? false, config);
|
|
815
897
|
}
|
|
816
898
|
|
|
899
|
+
// src/commands/sprints.ts
|
|
900
|
+
var SPRINT_COLUMNS = [
|
|
901
|
+
{ key: "id", label: "ID" },
|
|
902
|
+
{ key: "sprint", label: "SPRINT", maxWidth: 60 },
|
|
903
|
+
{ key: "dates", label: "DATES" }
|
|
904
|
+
];
|
|
905
|
+
function formatDate2(d) {
|
|
906
|
+
return `${d.getMonth() + 1}/${d.getDate()}`;
|
|
907
|
+
}
|
|
908
|
+
function buildSprintInfos(lists, folderName, today) {
|
|
909
|
+
return lists.map((list) => {
|
|
910
|
+
const dates = parseSprintDates(list.name);
|
|
911
|
+
const active = dates !== null && today >= dates.start && today <= dates.end;
|
|
912
|
+
return {
|
|
913
|
+
id: list.id,
|
|
914
|
+
name: list.name,
|
|
915
|
+
folder: folderName,
|
|
916
|
+
start: dates ? dates.start.toISOString() : null,
|
|
917
|
+
end: dates ? dates.end.toISOString() : null,
|
|
918
|
+
active
|
|
919
|
+
};
|
|
920
|
+
});
|
|
921
|
+
}
|
|
922
|
+
async function listSprints(config, opts = {}) {
|
|
923
|
+
const client = new ClickUpClient(config);
|
|
924
|
+
const [myTasks, allSpaces] = await Promise.all([
|
|
925
|
+
client.getMyTasks(config.teamId),
|
|
926
|
+
client.getSpaces(config.teamId)
|
|
927
|
+
]);
|
|
928
|
+
let spaces;
|
|
929
|
+
if (opts.space) {
|
|
930
|
+
spaces = allSpaces.filter(
|
|
931
|
+
(s) => s.name.toLowerCase().includes(opts.space.toLowerCase()) || s.id === opts.space
|
|
932
|
+
);
|
|
933
|
+
if (spaces.length === 0) {
|
|
934
|
+
throw new Error(
|
|
935
|
+
`No space matching "${opts.space}" found. Use \`cu spaces\` to list available spaces.`
|
|
936
|
+
);
|
|
937
|
+
}
|
|
938
|
+
} else {
|
|
939
|
+
const mySpaceIds = new Set(
|
|
940
|
+
myTasks.map((t) => t.space?.id).filter((id) => Boolean(id))
|
|
941
|
+
);
|
|
942
|
+
spaces = findRelatedSpaces(mySpaceIds, allSpaces);
|
|
943
|
+
}
|
|
944
|
+
const foldersBySpace = await Promise.all(spaces.map((space) => client.getFolders(space.id)));
|
|
945
|
+
const sprintFolders = foldersBySpace.flat().filter((f) => f.name.toLowerCase().includes("sprint"));
|
|
946
|
+
const today = /* @__PURE__ */ new Date();
|
|
947
|
+
const allSprints = [];
|
|
948
|
+
const listsByFolder = await Promise.all(
|
|
949
|
+
sprintFolders.map((folder) => client.getFolderLists(folder.id))
|
|
950
|
+
);
|
|
951
|
+
for (let i = 0; i < sprintFolders.length; i++) {
|
|
952
|
+
const folder = sprintFolders[i];
|
|
953
|
+
const lists = listsByFolder[i];
|
|
954
|
+
allSprints.push(...buildSprintInfos(lists, folder.name, today));
|
|
955
|
+
}
|
|
956
|
+
if (opts.json || !isTTY()) {
|
|
957
|
+
console.log(JSON.stringify(allSprints, null, 2));
|
|
958
|
+
return;
|
|
959
|
+
}
|
|
960
|
+
if (allSprints.length === 0) {
|
|
961
|
+
console.log("No sprints found.");
|
|
962
|
+
return;
|
|
963
|
+
}
|
|
964
|
+
const rows = allSprints.map((s) => {
|
|
965
|
+
const dates = parseSprintDates(s.name);
|
|
966
|
+
const dateStr = dates ? `${formatDate2(dates.start)} - ${formatDate2(dates.end)}` : "";
|
|
967
|
+
return {
|
|
968
|
+
id: s.id,
|
|
969
|
+
sprint: s.active ? `* ${s.name}` : s.name,
|
|
970
|
+
dates: dateStr
|
|
971
|
+
};
|
|
972
|
+
});
|
|
973
|
+
console.log(formatTable(rows, SPRINT_COLUMNS));
|
|
974
|
+
}
|
|
975
|
+
|
|
817
976
|
// src/commands/subtasks.ts
|
|
818
977
|
async function fetchSubtasks(config, taskId) {
|
|
819
978
|
const client = new ClickUpClient(config);
|
|
@@ -831,7 +990,7 @@ async function postComment(config, taskId, text) {
|
|
|
831
990
|
|
|
832
991
|
// src/commands/comments.ts
|
|
833
992
|
import chalk3 from "chalk";
|
|
834
|
-
function
|
|
993
|
+
function formatDate3(timestamp) {
|
|
835
994
|
return new Date(Number(timestamp)).toLocaleString("en-US", {
|
|
836
995
|
month: "short",
|
|
837
996
|
day: "numeric",
|
|
@@ -867,7 +1026,7 @@ function printComments(comments, forceJson) {
|
|
|
867
1026
|
for (let i = 0; i < comments.length; i++) {
|
|
868
1027
|
const c = comments[i];
|
|
869
1028
|
if (i > 0) console.log(separator);
|
|
870
|
-
console.log(`${chalk3.bold(c.user)} ${chalk3.dim(
|
|
1029
|
+
console.log(`${chalk3.bold(c.user)} ${chalk3.dim(formatDate3(c.date))}`);
|
|
871
1030
|
console.log(c.text);
|
|
872
1031
|
if (i < comments.length - 1) console.log("");
|
|
873
1032
|
}
|
|
@@ -1317,6 +1476,55 @@ async function assignTask(config, taskId, opts) {
|
|
|
1317
1476
|
});
|
|
1318
1477
|
}
|
|
1319
1478
|
|
|
1479
|
+
// src/commands/activity.ts
|
|
1480
|
+
import chalk4 from "chalk";
|
|
1481
|
+
function formatDate4(timestamp) {
|
|
1482
|
+
return new Date(Number(timestamp)).toLocaleString("en-US", {
|
|
1483
|
+
month: "short",
|
|
1484
|
+
day: "numeric",
|
|
1485
|
+
year: "numeric",
|
|
1486
|
+
hour: "numeric",
|
|
1487
|
+
minute: "2-digit"
|
|
1488
|
+
});
|
|
1489
|
+
}
|
|
1490
|
+
async function fetchActivity(config, taskId) {
|
|
1491
|
+
const client = new ClickUpClient(config);
|
|
1492
|
+
const [task, rawComments] = await Promise.all([
|
|
1493
|
+
client.getTask(taskId),
|
|
1494
|
+
client.getTaskComments(taskId)
|
|
1495
|
+
]);
|
|
1496
|
+
const comments = rawComments.map((c) => ({
|
|
1497
|
+
id: c.id,
|
|
1498
|
+
user: c.user.username,
|
|
1499
|
+
date: c.date,
|
|
1500
|
+
text: c.comment_text
|
|
1501
|
+
}));
|
|
1502
|
+
return { task, comments };
|
|
1503
|
+
}
|
|
1504
|
+
function printActivity(result, forceJson) {
|
|
1505
|
+
if (forceJson || !isTTY()) {
|
|
1506
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1507
|
+
return;
|
|
1508
|
+
}
|
|
1509
|
+
console.log(formatTaskDetail(result.task));
|
|
1510
|
+
console.log("");
|
|
1511
|
+
console.log(chalk4.bold("Comments"));
|
|
1512
|
+
console.log(chalk4.dim("-".repeat(60)));
|
|
1513
|
+
if (result.comments.length === 0) {
|
|
1514
|
+
console.log("No comments.");
|
|
1515
|
+
return;
|
|
1516
|
+
}
|
|
1517
|
+
for (let i = 0; i < result.comments.length; i++) {
|
|
1518
|
+
const c = result.comments[i];
|
|
1519
|
+
if (i > 0) {
|
|
1520
|
+
console.log("");
|
|
1521
|
+
console.log(chalk4.dim("-".repeat(60)));
|
|
1522
|
+
}
|
|
1523
|
+
console.log(`${chalk4.bold(c.user)} ${chalk4.dim(formatDate4(c.date))}`);
|
|
1524
|
+
console.log(c.text);
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1320
1528
|
// src/commands/completion.ts
|
|
1321
1529
|
function bashCompletion() {
|
|
1322
1530
|
return `_cu_completions() {
|
|
@@ -1721,6 +1929,47 @@ function generateCompletion(shell) {
|
|
|
1721
1929
|
}
|
|
1722
1930
|
}
|
|
1723
1931
|
|
|
1932
|
+
// src/commands/auth.ts
|
|
1933
|
+
async function checkAuth(config) {
|
|
1934
|
+
const client = new ClickUpClient(config);
|
|
1935
|
+
try {
|
|
1936
|
+
const user = await client.getMe();
|
|
1937
|
+
return { authenticated: true, user };
|
|
1938
|
+
} catch (err) {
|
|
1939
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1940
|
+
return { authenticated: false, error: message };
|
|
1941
|
+
}
|
|
1942
|
+
}
|
|
1943
|
+
|
|
1944
|
+
// src/commands/search.ts
|
|
1945
|
+
async function searchTasks(config, query, opts = {}) {
|
|
1946
|
+
const trimmed = query.trim();
|
|
1947
|
+
if (!trimmed) {
|
|
1948
|
+
throw new Error("Search query cannot be empty");
|
|
1949
|
+
}
|
|
1950
|
+
const client = new ClickUpClient(config);
|
|
1951
|
+
const allTasks = await client.getMyTasks(config.teamId);
|
|
1952
|
+
const words = trimmed.toLowerCase().split(/\s+/);
|
|
1953
|
+
let matched = allTasks.filter((task) => {
|
|
1954
|
+
const name = task.name.toLowerCase();
|
|
1955
|
+
return words.every((word) => name.includes(word));
|
|
1956
|
+
});
|
|
1957
|
+
if (opts.status) {
|
|
1958
|
+
const availableStatuses = [...new Set(allTasks.map((t) => t.status.status))];
|
|
1959
|
+
const resolved = matchStatus(opts.status, availableStatuses);
|
|
1960
|
+
if (resolved) {
|
|
1961
|
+
if (resolved.toLowerCase() !== opts.status.toLowerCase()) {
|
|
1962
|
+
process.stderr.write(`Status matched: "${opts.status}" -> "${resolved}"
|
|
1963
|
+
`);
|
|
1964
|
+
}
|
|
1965
|
+
matched = matched.filter((t) => t.status.status.toLowerCase() === resolved.toLowerCase());
|
|
1966
|
+
} else {
|
|
1967
|
+
matched = matched.filter((t) => t.status.status.toLowerCase() === opts.status.toLowerCase());
|
|
1968
|
+
}
|
|
1969
|
+
}
|
|
1970
|
+
return matched.map(summarize);
|
|
1971
|
+
}
|
|
1972
|
+
|
|
1724
1973
|
// src/index.ts
|
|
1725
1974
|
var require2 = createRequire(import.meta.url);
|
|
1726
1975
|
var { version } = require2("../package.json");
|
|
@@ -1739,6 +1988,20 @@ program.command("init").description("Set up cu for the first time").action(
|
|
|
1739
1988
|
await runInitCommand();
|
|
1740
1989
|
})
|
|
1741
1990
|
);
|
|
1991
|
+
program.command("auth").description("Validate API token and show current user").option("--json", "Force JSON output even in terminal").action(
|
|
1992
|
+
wrapAction(async (opts) => {
|
|
1993
|
+
const config = loadConfig();
|
|
1994
|
+
const result = await checkAuth(config);
|
|
1995
|
+
if (opts.json || !isTTY()) {
|
|
1996
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1997
|
+
} else if (result.authenticated && result.user) {
|
|
1998
|
+
console.log(`Authenticated as @${result.user.username} (id: ${result.user.id})`);
|
|
1999
|
+
} else {
|
|
2000
|
+
console.error(`Authentication failed: ${result.error ?? "unknown error"}`);
|
|
2001
|
+
process.exit(1);
|
|
2002
|
+
}
|
|
2003
|
+
})
|
|
2004
|
+
);
|
|
1742
2005
|
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(
|
|
1743
2006
|
wrapAction(async (opts) => {
|
|
1744
2007
|
const config = loadConfig();
|
|
@@ -1774,21 +2037,11 @@ program.command("task <taskId>").description("Get task details").option("--json"
|
|
|
1774
2037
|
} else if (!isTTY()) {
|
|
1775
2038
|
console.log(formatTaskDetailMarkdown(result));
|
|
1776
2039
|
} else {
|
|
1777
|
-
|
|
1778
|
-
`ID: ${result.id}`,
|
|
1779
|
-
`Name: ${result.name}`,
|
|
1780
|
-
`Status: ${result.status?.status ?? "unknown"}`,
|
|
1781
|
-
`Type: ${(result.custom_item_id ?? 0) !== 0 ? "initiative" : "task"}`,
|
|
1782
|
-
`List: ${result.list?.name ?? "unknown"}`,
|
|
1783
|
-
`URL: ${result.url}`,
|
|
1784
|
-
...result.parent ? [`Parent: ${result.parent}`] : [],
|
|
1785
|
-
...result.description ? ["", result.description] : []
|
|
1786
|
-
];
|
|
1787
|
-
console.log(lines.join("\n"));
|
|
2040
|
+
console.log(formatTaskDetail(result));
|
|
1788
2041
|
}
|
|
1789
2042
|
})
|
|
1790
2043
|
);
|
|
1791
|
-
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(
|
|
2044
|
+
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(
|
|
1792
2045
|
wrapAction(async (taskId, opts) => {
|
|
1793
2046
|
const config = loadConfig();
|
|
1794
2047
|
const payload = buildUpdatePayload(opts);
|
|
@@ -1800,7 +2053,7 @@ program.command("update <taskId>").description("Update a task").option("-n, --na
|
|
|
1800
2053
|
}
|
|
1801
2054
|
})
|
|
1802
2055
|
);
|
|
1803
|
-
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(
|
|
2056
|
+
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(
|
|
1804
2057
|
wrapAction(async (opts) => {
|
|
1805
2058
|
const config = loadConfig();
|
|
1806
2059
|
const result = await createTask(config, opts);
|
|
@@ -1817,6 +2070,12 @@ program.command("sprint").description("List my tasks in the current active sprin
|
|
|
1817
2070
|
await runSprintCommand(config, opts);
|
|
1818
2071
|
})
|
|
1819
2072
|
);
|
|
2073
|
+
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(
|
|
2074
|
+
wrapAction(async (opts) => {
|
|
2075
|
+
const config = loadConfig();
|
|
2076
|
+
await listSprints(config, opts);
|
|
2077
|
+
})
|
|
2078
|
+
);
|
|
1820
2079
|
program.command("subtasks <taskId>").description("List subtasks of a task or initiative").option("--json", "Force JSON output even in terminal").action(
|
|
1821
2080
|
wrapAction(async (taskId, opts) => {
|
|
1822
2081
|
const config = loadConfig();
|
|
@@ -1842,6 +2101,13 @@ program.command("comments <taskId>").description("List comments on a task").opti
|
|
|
1842
2101
|
printComments(comments, opts.json ?? false);
|
|
1843
2102
|
})
|
|
1844
2103
|
);
|
|
2104
|
+
program.command("activity <taskId>").description("Show task details and comments combined").option("--json", "Force JSON output even in terminal").action(
|
|
2105
|
+
wrapAction(async (taskId, opts) => {
|
|
2106
|
+
const config = loadConfig();
|
|
2107
|
+
const result = await fetchActivity(config, taskId);
|
|
2108
|
+
printActivity(result, opts.json ?? false);
|
|
2109
|
+
})
|
|
2110
|
+
);
|
|
1845
2111
|
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(
|
|
1846
2112
|
wrapAction(async (spaceId, opts) => {
|
|
1847
2113
|
const config = loadConfig();
|
|
@@ -1879,6 +2145,13 @@ program.command("open <query>").description("Open a task in the browser by ID or
|
|
|
1879
2145
|
await openTask(config, query, opts);
|
|
1880
2146
|
})
|
|
1881
2147
|
);
|
|
2148
|
+
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(
|
|
2149
|
+
wrapAction(async (query, opts) => {
|
|
2150
|
+
const config = loadConfig();
|
|
2151
|
+
const tasks = await searchTasks(config, query, { status: opts.status });
|
|
2152
|
+
await printTasks(tasks, opts.json ?? false, config);
|
|
2153
|
+
})
|
|
2154
|
+
);
|
|
1882
2155
|
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(
|
|
1883
2156
|
wrapAction(async (opts) => {
|
|
1884
2157
|
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.9.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,142 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: clickup
|
|
3
|
+
description: 'Use when managing ClickUp tasks, initiatives, sprints, or comments via the `cu` CLI tool. Triggers: task queries, status updates, sprint tracking, creating subtasks, posting comments, standup summaries, searching tasks, checking overdue items.'
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# ClickUp CLI (`cu`)
|
|
7
|
+
|
|
8
|
+
Reference for AI agents using the `cu` CLI tool. Covers task management, sprint tracking, initiatives, comments, and project workflows.
|
|
9
|
+
|
|
10
|
+
Keywords: ClickUp, task management, sprint, initiative, project management, agile, backlog, subtasks, standup, overdue, search
|
|
11
|
+
|
|
12
|
+
## Setup
|
|
13
|
+
|
|
14
|
+
Config at `~/.config/cu/config.json` with `apiToken` and `teamId`. Run `cu init` to set up interactively.
|
|
15
|
+
|
|
16
|
+
Environment variables `CU_API_TOKEN` and `CU_TEAM_ID` override config file when both are set.
|
|
17
|
+
|
|
18
|
+
## Output Modes
|
|
19
|
+
|
|
20
|
+
| Context | Default output | Override |
|
|
21
|
+
| --------------- | --------------------- | ----------------- |
|
|
22
|
+
| Terminal (TTY) | Interactive picker UI | `--json` for JSON |
|
|
23
|
+
| Piped / non-TTY | Markdown tables | `--json` for JSON |
|
|
24
|
+
|
|
25
|
+
- Default piped output is **Markdown** - optimized for agent context windows
|
|
26
|
+
- `cu task <id>` outputs a Markdown summary when piped; use `--json` for the full raw API object (custom fields, checklists, etc.)
|
|
27
|
+
- Set `CU_OUTPUT=json` to always get JSON when piped
|
|
28
|
+
- Set `NO_COLOR` to disable color (tables still render, just uncolored)
|
|
29
|
+
- Agents typically don't need `--json` unless parsing structured data with `jq`
|
|
30
|
+
|
|
31
|
+
## Commands
|
|
32
|
+
|
|
33
|
+
All commands support `--help` for full flag details.
|
|
34
|
+
|
|
35
|
+
### Read
|
|
36
|
+
|
|
37
|
+
| Command | What it returns |
|
|
38
|
+
| -------------------------------------------------------------------------- | -------------------------------------------------- |
|
|
39
|
+
| `cu tasks [--status s] [--name q] [--list id] [--space id] [--json]` | My tasks (workspace-wide) |
|
|
40
|
+
| `cu initiatives [--status s] [--name q] [--list id] [--space id] [--json]` | My initiatives |
|
|
41
|
+
| `cu assigned [--include-closed] [--json]` | All my tasks grouped by status |
|
|
42
|
+
| `cu sprint [--status s] [--space nameOrId] [--json]` | Tasks in active sprint (auto-detected) |
|
|
43
|
+
| `cu sprints [--space nameOrId] [--json]` | List all sprints (marks active with \*) |
|
|
44
|
+
| `cu search <query> [--status s] [--json]` | Search my tasks by name (multi-word, fuzzy status) |
|
|
45
|
+
| `cu task <id> [--json]` | Single task details |
|
|
46
|
+
| `cu subtasks <id> [--json]` | Subtasks of a task or initiative |
|
|
47
|
+
| `cu comments <id> [--json]` | Comments on a task |
|
|
48
|
+
| `cu activity <id> [--json]` | Task details + comment history combined |
|
|
49
|
+
| `cu inbox [--days n] [--json]` | Tasks updated in last n days (default 30) |
|
|
50
|
+
| `cu summary [--hours n] [--json]` | Standup helper: completed, in-progress, overdue |
|
|
51
|
+
| `cu overdue [--json]` | Tasks past their due date |
|
|
52
|
+
| `cu spaces [--name partial] [--my] [--json]` | List/filter workspace spaces |
|
|
53
|
+
| `cu lists <spaceId> [--name partial] [--json]` | Lists in a space (including folder lists) |
|
|
54
|
+
| `cu open <query> [--json]` | Open task in browser by ID or name |
|
|
55
|
+
| `cu auth [--json]` | Check authentication status |
|
|
56
|
+
|
|
57
|
+
### Write
|
|
58
|
+
|
|
59
|
+
| Command | What it does |
|
|
60
|
+
| ------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- |
|
|
61
|
+
| `cu update <id> [-n name] [-d desc] [-s status] [--priority p] [--due-date d] [--assignee id] [--json]` | Update task fields |
|
|
62
|
+
| `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) |
|
|
63
|
+
| `cu comment <id> -m text` | Post comment on task |
|
|
64
|
+
| `cu assign <id> [--to userId\|me] [--remove userId\|me] [--json]` | Assign/unassign users |
|
|
65
|
+
| `cu config get <key>` / `cu config set <key> <value>` / `cu config path` | Manage CLI config |
|
|
66
|
+
| `cu completion <shell>` | Shell completions (bash/zsh/fish) |
|
|
67
|
+
|
|
68
|
+
## Quick Reference
|
|
69
|
+
|
|
70
|
+
| Topic | Detail |
|
|
71
|
+
| ------------------- | --------------------------------------------------------------------------------- |
|
|
72
|
+
| Task IDs | Stable alphanumeric strings (e.g. `abc123def`) |
|
|
73
|
+
| Initiatives | Detected via `custom_item_id !== 0` |
|
|
74
|
+
| `--list` on create | Optional when `--parent` is given (auto-detected) |
|
|
75
|
+
| `--status` | Fuzzy matching: exact > starts-with > contains. Prints match to stderr. |
|
|
76
|
+
| `--priority` | Names (`urgent`, `high`, `normal`, `low`) or numbers (1-4) |
|
|
77
|
+
| `--due-date` | `YYYY-MM-DD` format |
|
|
78
|
+
| `--assignee` | Numeric user ID (find via `cu task <id> --json`) |
|
|
79
|
+
| `--tags` | Comma-separated (e.g. `--tags "bug,frontend"`) |
|
|
80
|
+
| `--space` | Partial name match or exact ID |
|
|
81
|
+
| `--name` | Partial match, case-insensitive |
|
|
82
|
+
| `cu assign --to me` | Shorthand for your own user ID |
|
|
83
|
+
| `cu search` | Matches all query words against task name, case-insensitive |
|
|
84
|
+
| `cu sprint` | Auto-detects active sprint via view API and date range parsing |
|
|
85
|
+
| `cu summary` | Categories: completed (done/complete/closed within N hours), in progress, overdue |
|
|
86
|
+
| `cu overdue` | Excludes closed tasks, sorted most overdue first |
|
|
87
|
+
| `cu open` | Tries task ID first, falls back to name search |
|
|
88
|
+
| `cu task` | Shows custom fields in detail view |
|
|
89
|
+
| `cu lists` | Discovers list IDs needed for `--list` and `cu create -l` |
|
|
90
|
+
| Errors | stderr with exit code 1 |
|
|
91
|
+
| Parsing | Strict - excess/unknown arguments rejected |
|
|
92
|
+
|
|
93
|
+
## Agent Workflow Examples
|
|
94
|
+
|
|
95
|
+
### Investigate a task
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
cu task abc123def # markdown summary
|
|
99
|
+
cu subtasks abc123def # child tasks
|
|
100
|
+
cu comments abc123def # discussion
|
|
101
|
+
cu activity abc123def # task + comments combined
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Find tasks
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
cu tasks --status "in progress" # by status
|
|
108
|
+
cu tasks --name "login" # by partial name
|
|
109
|
+
cu search "payment flow" # multi-word search
|
|
110
|
+
cu search auth --status "prog" # fuzzy status match
|
|
111
|
+
cu sprint # current sprint
|
|
112
|
+
cu assigned # all my tasks by status
|
|
113
|
+
cu overdue # past due date
|
|
114
|
+
cu inbox --days 7 # recently updated
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Make changes
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
cu update abc123def -s "done"
|
|
121
|
+
cu update abc123def --priority high --due-date 2025-03-15
|
|
122
|
+
cu create -n "Fix the thing" -p abc123def
|
|
123
|
+
cu create -n "Fix bug" -l <listId> --priority urgent --tags "bug,frontend"
|
|
124
|
+
cu comment abc123def -m "Completed in PR #42"
|
|
125
|
+
cu assign abc123def --to me
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Discover workspace structure
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
cu spaces # all spaces
|
|
132
|
+
cu lists <spaceId> # lists in a space (needed for --list flag)
|
|
133
|
+
cu sprints # all sprints across folders
|
|
134
|
+
cu auth # verify token works
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Standup
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
cu summary # completed / in progress / overdue
|
|
141
|
+
cu summary --hours 48 # wider window
|
|
142
|
+
```
|