@krodak/clickup-cli 0.8.0 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -9
- package/dist/index.js +221 -44
- package/package.json +1 -1
- package/skills/clickup-cli/SKILL.md +91 -95
package/README.md
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
# cu - ClickUp CLI
|
|
2
2
|
|
|
3
|
-
A ClickUp CLI built for AI agents that also works well for humans. Outputs
|
|
3
|
+
> A ClickUp CLI built for AI agents that also works well for humans. Outputs Markdown when piped (optimized for AI context windows), interactive tables when run in a terminal.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
[](https://www.npmjs.com/package/@krodak/clickup-cli)
|
|
6
|
+
[](https://nodejs.org)
|
|
7
|
+
[](./LICENSE)
|
|
8
|
+
[](https://github.com/krodak/clickup-cli/actions/workflows/ci.yml)
|
|
6
9
|
|
|
7
10
|
```bash
|
|
8
11
|
npm install -g @krodak/clickup-cli # or: brew tap krodak/tap && brew install clickup-cli
|
|
9
12
|
cu init # walks you through API token + workspace setup
|
|
10
13
|
```
|
|
11
14
|
|
|
12
|
-
You need
|
|
15
|
+
You need a ClickUp personal API token (`pk_...` from https://app.clickup.com/settings/apps).
|
|
13
16
|
|
|
14
17
|
## Using with AI agents
|
|
15
18
|
|
|
@@ -89,9 +92,9 @@ cu update abc123 -s "done" # update status
|
|
|
89
92
|
cu assign abc123 --to me # assign yourself
|
|
90
93
|
```
|
|
91
94
|
|
|
92
|
-
Pass `--json` to any command to
|
|
95
|
+
Pass `--json` to any read command to force JSON output instead of the default format.
|
|
93
96
|
|
|
94
|
-
When output is piped (no TTY), all commands
|
|
97
|
+
When output is piped (no TTY), all commands output **Markdown** by default - optimized for AI agent context windows. Pass `--json` to any command for JSON output. Set `CU_OUTPUT=json` environment variable to always get JSON when piped.
|
|
95
98
|
|
|
96
99
|
## Commands
|
|
97
100
|
|
|
@@ -179,12 +182,15 @@ cu task abc123
|
|
|
179
182
|
cu task abc123 --json
|
|
180
183
|
```
|
|
181
184
|
|
|
185
|
+
**Note:** When piped, `cu task` outputs a structured Markdown summary of the task. For the full raw API response with all fields (custom fields, checklists, etc.), use `--json`.
|
|
186
|
+
|
|
182
187
|
### `cu subtasks <id>`
|
|
183
188
|
|
|
184
189
|
List subtasks of a task or initiative.
|
|
185
190
|
|
|
186
191
|
```bash
|
|
187
192
|
cu subtasks abc123
|
|
193
|
+
cu subtasks abc123 --include-closed
|
|
188
194
|
cu subtasks abc123 --json
|
|
189
195
|
```
|
|
190
196
|
|
|
@@ -410,10 +416,11 @@ cu completion fish > ~/.config/fish/completions/cu.fish # Fish
|
|
|
410
416
|
|
|
411
417
|
Environment variables override config file values:
|
|
412
418
|
|
|
413
|
-
| Variable | Description
|
|
414
|
-
| -------------- |
|
|
415
|
-
| `CU_API_TOKEN` | ClickUp personal API token (`pk_`)
|
|
416
|
-
| `CU_TEAM_ID` | Workspace (team) ID
|
|
419
|
+
| Variable | Description |
|
|
420
|
+
| -------------- | ----------------------------------------------------------------- |
|
|
421
|
+
| `CU_API_TOKEN` | ClickUp personal API token (`pk_`) |
|
|
422
|
+
| `CU_TEAM_ID` | Workspace (team) ID |
|
|
423
|
+
| `CU_OUTPUT` | Set to `json` to force JSON output when piped (default: markdown) |
|
|
417
424
|
|
|
418
425
|
When both are set, the config file is not required. Useful for CI/CD and containerized agents.
|
|
419
426
|
|
package/dist/index.js
CHANGED
|
@@ -135,6 +135,7 @@ var ClickUpClient = class {
|
|
|
135
135
|
const baseParams = new URLSearchParams({
|
|
136
136
|
subtasks: String(filters.subtasks ?? true)
|
|
137
137
|
});
|
|
138
|
+
if (filters.includeClosed) baseParams.set("include_closed", "true");
|
|
138
139
|
baseParams.append("assignees[]", String(me.id));
|
|
139
140
|
for (const s of filters.statuses ?? []) baseParams.append("statuses[]", s);
|
|
140
141
|
for (const id of filters.listIds ?? []) baseParams.append("list_ids[]", id);
|
|
@@ -161,9 +162,11 @@ var ClickUpClient = class {
|
|
|
161
162
|
const data = await this.request(`/task/${taskId}/comment`);
|
|
162
163
|
return data.comments ?? [];
|
|
163
164
|
}
|
|
164
|
-
async getTasksFromList(listId, params = {}) {
|
|
165
|
+
async getTasksFromList(listId, params = {}, options = {}) {
|
|
165
166
|
return this.paginate((page) => {
|
|
166
|
-
const
|
|
167
|
+
const base = { subtasks: "true", page: String(page), ...params };
|
|
168
|
+
if (options.includeClosed) base["include_closed"] = "true";
|
|
169
|
+
const qs = new URLSearchParams(base).toString();
|
|
167
170
|
return `/list/${listId}/task?${qs}`;
|
|
168
171
|
});
|
|
169
172
|
}
|
|
@@ -227,6 +230,11 @@ import chalk from "chalk";
|
|
|
227
230
|
function isTTY() {
|
|
228
231
|
return Boolean(process.stdout.isTTY);
|
|
229
232
|
}
|
|
233
|
+
function shouldOutputJson(forceJson) {
|
|
234
|
+
if (forceJson) return true;
|
|
235
|
+
if (process.env["CU_OUTPUT"] === "json") return true;
|
|
236
|
+
return false;
|
|
237
|
+
}
|
|
230
238
|
function cell(value, width) {
|
|
231
239
|
if (value.length > width) return value.slice(0, width - 1) + "\u2026";
|
|
232
240
|
return value.padEnd(width);
|
|
@@ -259,6 +267,128 @@ var TASK_COLUMNS = [
|
|
|
259
267
|
{ key: "list", label: "LIST" }
|
|
260
268
|
];
|
|
261
269
|
|
|
270
|
+
// src/markdown.ts
|
|
271
|
+
function escapeCell(value) {
|
|
272
|
+
return value.replace(/\|/g, "\\|");
|
|
273
|
+
}
|
|
274
|
+
function formatMarkdownTable(rows, columns) {
|
|
275
|
+
const header = "| " + columns.map((c) => c.label).join(" | ") + " |";
|
|
276
|
+
const divider = "| " + columns.map(() => "---").join(" | ") + " |";
|
|
277
|
+
const lines = [header, divider];
|
|
278
|
+
for (const row of rows) {
|
|
279
|
+
const cells = columns.map((c) => escapeCell(String(row[c.key] ?? "")));
|
|
280
|
+
lines.push("| " + cells.join(" | ") + " |");
|
|
281
|
+
}
|
|
282
|
+
return lines.join("\n");
|
|
283
|
+
}
|
|
284
|
+
var TASK_MD_COLUMNS = [
|
|
285
|
+
{ key: "id", label: "ID" },
|
|
286
|
+
{ key: "name", label: "Name" },
|
|
287
|
+
{ key: "status", label: "Status" },
|
|
288
|
+
{ key: "list", label: "List" }
|
|
289
|
+
];
|
|
290
|
+
function formatTasksMarkdown(tasks) {
|
|
291
|
+
if (tasks.length === 0) return "No tasks found.";
|
|
292
|
+
return formatMarkdownTable(tasks, TASK_MD_COLUMNS);
|
|
293
|
+
}
|
|
294
|
+
function formatCommentsMarkdown(comments) {
|
|
295
|
+
if (comments.length === 0) return "No comments found.";
|
|
296
|
+
return comments.map((c) => `**${c.user}** (${c.date})
|
|
297
|
+
|
|
298
|
+
${c.text}`).join("\n\n---\n\n");
|
|
299
|
+
}
|
|
300
|
+
var LIST_MD_COLUMNS = [
|
|
301
|
+
{ key: "id", label: "ID" },
|
|
302
|
+
{ key: "name", label: "Name" },
|
|
303
|
+
{ key: "folder", label: "Folder" }
|
|
304
|
+
];
|
|
305
|
+
function formatListsMarkdown(lists) {
|
|
306
|
+
if (lists.length === 0) return "No lists found.";
|
|
307
|
+
return formatMarkdownTable(lists, LIST_MD_COLUMNS);
|
|
308
|
+
}
|
|
309
|
+
var SPACE_MD_COLUMNS = [
|
|
310
|
+
{ key: "id", label: "ID" },
|
|
311
|
+
{ key: "name", label: "Name" }
|
|
312
|
+
];
|
|
313
|
+
function formatSpacesMarkdown(spaces) {
|
|
314
|
+
if (spaces.length === 0) return "No spaces found.";
|
|
315
|
+
return formatMarkdownTable(spaces, SPACE_MD_COLUMNS);
|
|
316
|
+
}
|
|
317
|
+
function formatGroupedTasksMarkdown(groups) {
|
|
318
|
+
const sections = groups.filter((g) => g.tasks.length > 0).map((g) => `## ${g.label}
|
|
319
|
+
|
|
320
|
+
${formatMarkdownTable(g.tasks, TASK_MD_COLUMNS)}`);
|
|
321
|
+
if (sections.length === 0) return "No tasks found.";
|
|
322
|
+
return sections.join("\n\n");
|
|
323
|
+
}
|
|
324
|
+
function formatDate(ms) {
|
|
325
|
+
const d = new Date(Number(ms));
|
|
326
|
+
const year = d.getUTCFullYear();
|
|
327
|
+
const month = String(d.getUTCMonth() + 1).padStart(2, "0");
|
|
328
|
+
const day = String(d.getUTCDate()).padStart(2, "0");
|
|
329
|
+
return `${year}-${month}-${day}`;
|
|
330
|
+
}
|
|
331
|
+
function formatDuration(ms) {
|
|
332
|
+
const totalMinutes = Math.floor(ms / 6e4);
|
|
333
|
+
const hours = Math.floor(totalMinutes / 60);
|
|
334
|
+
const minutes = totalMinutes % 60;
|
|
335
|
+
return `${hours}h ${minutes}m`;
|
|
336
|
+
}
|
|
337
|
+
function formatTaskDetailMarkdown(task) {
|
|
338
|
+
const lines = [`# ${task.name}`, ""];
|
|
339
|
+
const isInitiative2 = (task.custom_item_id ?? 0) !== 0;
|
|
340
|
+
const fields = [
|
|
341
|
+
["ID", task.id],
|
|
342
|
+
["Status", task.status.status],
|
|
343
|
+
["Type", isInitiative2 ? "initiative" : "task"],
|
|
344
|
+
["List", task.list.name],
|
|
345
|
+
["URL", task.url],
|
|
346
|
+
[
|
|
347
|
+
"Assignees",
|
|
348
|
+
task.assignees.length > 0 ? task.assignees.map((a) => a.username).join(", ") : void 0
|
|
349
|
+
],
|
|
350
|
+
["Priority", task.priority?.priority],
|
|
351
|
+
["Parent", task.parent ?? void 0],
|
|
352
|
+
["Start Date", task.start_date ? formatDate(task.start_date) : void 0],
|
|
353
|
+
["Due Date", task.due_date ? formatDate(task.due_date) : void 0],
|
|
354
|
+
[
|
|
355
|
+
"Time Estimate",
|
|
356
|
+
task.time_estimate != null && task.time_estimate > 0 ? formatDuration(task.time_estimate) : void 0
|
|
357
|
+
],
|
|
358
|
+
[
|
|
359
|
+
"Time Spent",
|
|
360
|
+
task.time_spent != null && task.time_spent > 0 ? formatDuration(task.time_spent) : void 0
|
|
361
|
+
],
|
|
362
|
+
["Tags", task.tags && task.tags.length > 0 ? task.tags.map((t) => t.name).join(", ") : void 0],
|
|
363
|
+
["Created", task.date_created ? formatDate(task.date_created) : void 0],
|
|
364
|
+
["Updated", task.date_updated ? formatDate(task.date_updated) : void 0]
|
|
365
|
+
];
|
|
366
|
+
for (const [label, value] of fields) {
|
|
367
|
+
if (value != null && value !== "") {
|
|
368
|
+
lines.push(`**${label}:** ${value}`);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
if (task.description) {
|
|
372
|
+
lines.push("", "## Description", "", task.description);
|
|
373
|
+
}
|
|
374
|
+
return lines.join("\n");
|
|
375
|
+
}
|
|
376
|
+
function formatUpdateConfirmation(id, name) {
|
|
377
|
+
return `Updated task ${id}: "${name}"`;
|
|
378
|
+
}
|
|
379
|
+
function formatCreateConfirmation(id, name, url) {
|
|
380
|
+
return `Created task ${id}: "${name}" - ${url}`;
|
|
381
|
+
}
|
|
382
|
+
function formatCommentConfirmation(id) {
|
|
383
|
+
return `Comment posted (id: ${id})`;
|
|
384
|
+
}
|
|
385
|
+
function formatAssignConfirmation(taskId, opts) {
|
|
386
|
+
const parts = [];
|
|
387
|
+
if (opts.to) parts.push(`Assigned ${opts.to} to ${taskId}`);
|
|
388
|
+
if (opts.remove) parts.push(`Removed ${opts.remove} from ${taskId}`);
|
|
389
|
+
return parts.join("; ");
|
|
390
|
+
}
|
|
391
|
+
|
|
262
392
|
// src/interactive.ts
|
|
263
393
|
import { execFileSync } from "child_process";
|
|
264
394
|
import { checkbox, confirm, Separator } from "@inquirer/prompts";
|
|
@@ -490,10 +620,14 @@ async function fetchMyTasks(config, opts = {}) {
|
|
|
490
620
|
return filtered.map(summarize);
|
|
491
621
|
}
|
|
492
622
|
async function printTasks(tasks, forceJson, config) {
|
|
493
|
-
if (forceJson
|
|
623
|
+
if (shouldOutputJson(forceJson)) {
|
|
494
624
|
console.log(JSON.stringify(tasks, null, 2));
|
|
495
625
|
return;
|
|
496
626
|
}
|
|
627
|
+
if (!isTTY()) {
|
|
628
|
+
console.log(formatTasksMarkdown(tasks));
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
497
631
|
if (tasks.length === 0) {
|
|
498
632
|
console.log("No tasks found.");
|
|
499
633
|
return;
|
|
@@ -771,7 +905,7 @@ var SPRINT_COLUMNS = [
|
|
|
771
905
|
{ key: "sprint", label: "SPRINT", maxWidth: 60 },
|
|
772
906
|
{ key: "dates", label: "DATES" }
|
|
773
907
|
];
|
|
774
|
-
function
|
|
908
|
+
function formatDate2(d) {
|
|
775
909
|
return `${d.getMonth() + 1}/${d.getDate()}`;
|
|
776
910
|
}
|
|
777
911
|
function buildSprintInfos(lists, folderName, today) {
|
|
@@ -832,7 +966,7 @@ async function listSprints(config, opts = {}) {
|
|
|
832
966
|
}
|
|
833
967
|
const rows = allSprints.map((s) => {
|
|
834
968
|
const dates = parseSprintDates(s.name);
|
|
835
|
-
const dateStr = dates ? `${
|
|
969
|
+
const dateStr = dates ? `${formatDate2(dates.start)} - ${formatDate2(dates.end)}` : "";
|
|
836
970
|
return {
|
|
837
971
|
id: s.id,
|
|
838
972
|
sprint: s.active ? `* ${s.name}` : s.name,
|
|
@@ -843,10 +977,14 @@ async function listSprints(config, opts = {}) {
|
|
|
843
977
|
}
|
|
844
978
|
|
|
845
979
|
// src/commands/subtasks.ts
|
|
846
|
-
async function fetchSubtasks(config, taskId) {
|
|
980
|
+
async function fetchSubtasks(config, taskId, options = {}) {
|
|
847
981
|
const client = new ClickUpClient(config);
|
|
848
982
|
const parent = await client.getTask(taskId);
|
|
849
|
-
const tasks = await client.getTasksFromList(
|
|
983
|
+
const tasks = await client.getTasksFromList(
|
|
984
|
+
parent.list.id,
|
|
985
|
+
{ parent: taskId, subtasks: "false" },
|
|
986
|
+
{ includeClosed: options.includeClosed }
|
|
987
|
+
);
|
|
850
988
|
return tasks.map(summarize);
|
|
851
989
|
}
|
|
852
990
|
|
|
@@ -859,7 +997,7 @@ async function postComment(config, taskId, text) {
|
|
|
859
997
|
|
|
860
998
|
// src/commands/comments.ts
|
|
861
999
|
import chalk3 from "chalk";
|
|
862
|
-
function
|
|
1000
|
+
function formatDate3(timestamp) {
|
|
863
1001
|
return new Date(Number(timestamp)).toLocaleString("en-US", {
|
|
864
1002
|
month: "short",
|
|
865
1003
|
day: "numeric",
|
|
@@ -879,10 +1017,14 @@ async function fetchComments(config, taskId) {
|
|
|
879
1017
|
}));
|
|
880
1018
|
}
|
|
881
1019
|
function printComments(comments, forceJson) {
|
|
882
|
-
if (forceJson
|
|
1020
|
+
if (shouldOutputJson(forceJson)) {
|
|
883
1021
|
console.log(JSON.stringify(comments, null, 2));
|
|
884
1022
|
return;
|
|
885
1023
|
}
|
|
1024
|
+
if (!isTTY()) {
|
|
1025
|
+
console.log(formatCommentsMarkdown(comments));
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
886
1028
|
if (comments.length === 0) {
|
|
887
1029
|
console.log("No comments found.");
|
|
888
1030
|
return;
|
|
@@ -891,7 +1033,7 @@ function printComments(comments, forceJson) {
|
|
|
891
1033
|
for (let i = 0; i < comments.length; i++) {
|
|
892
1034
|
const c = comments[i];
|
|
893
1035
|
if (i > 0) console.log(separator);
|
|
894
|
-
console.log(`${chalk3.bold(c.user)} ${chalk3.dim(
|
|
1036
|
+
console.log(`${chalk3.bold(c.user)} ${chalk3.dim(formatDate3(c.date))}`);
|
|
895
1037
|
console.log(c.text);
|
|
896
1038
|
if (i < comments.length - 1) console.log("");
|
|
897
1039
|
}
|
|
@@ -920,10 +1062,14 @@ async function fetchLists(config, spaceId, opts = {}) {
|
|
|
920
1062
|
return results;
|
|
921
1063
|
}
|
|
922
1064
|
function printLists(lists, forceJson) {
|
|
923
|
-
if (forceJson
|
|
1065
|
+
if (shouldOutputJson(forceJson)) {
|
|
924
1066
|
console.log(JSON.stringify(lists, null, 2));
|
|
925
1067
|
return;
|
|
926
1068
|
}
|
|
1069
|
+
if (!isTTY()) {
|
|
1070
|
+
console.log(formatListsMarkdown(lists));
|
|
1071
|
+
return;
|
|
1072
|
+
}
|
|
927
1073
|
if (lists.length === 0) {
|
|
928
1074
|
console.log("No lists found.");
|
|
929
1075
|
return;
|
|
@@ -991,7 +1137,7 @@ async function fetchInbox(config, days = 30) {
|
|
|
991
1137
|
async function printInbox(tasks, forceJson, config) {
|
|
992
1138
|
const now = Date.now();
|
|
993
1139
|
const groups = groupTasks(tasks, now);
|
|
994
|
-
if (forceJson
|
|
1140
|
+
if (shouldOutputJson(forceJson)) {
|
|
995
1141
|
const jsonGroups = {};
|
|
996
1142
|
for (const { key } of TIME_PERIODS) {
|
|
997
1143
|
if (groups[key].length > 0) {
|
|
@@ -1001,6 +1147,14 @@ async function printInbox(tasks, forceJson, config) {
|
|
|
1001
1147
|
console.log(JSON.stringify(jsonGroups, null, 2));
|
|
1002
1148
|
return;
|
|
1003
1149
|
}
|
|
1150
|
+
if (!isTTY()) {
|
|
1151
|
+
const mdGroups = TIME_PERIODS.filter((p) => groups[p.key].length > 0).map((p) => ({
|
|
1152
|
+
label: p.label,
|
|
1153
|
+
tasks: groups[p.key]
|
|
1154
|
+
}));
|
|
1155
|
+
console.log(formatGroupedTasksMarkdown(mdGroups));
|
|
1156
|
+
return;
|
|
1157
|
+
}
|
|
1004
1158
|
if (tasks.length === 0) {
|
|
1005
1159
|
console.log("No recently updated tasks.");
|
|
1006
1160
|
return;
|
|
@@ -1032,7 +1186,11 @@ async function listSpaces(config, opts) {
|
|
|
1032
1186
|
);
|
|
1033
1187
|
spaces = spaces.filter((s) => mySpaceIds.has(s.id));
|
|
1034
1188
|
}
|
|
1035
|
-
if (
|
|
1189
|
+
if (shouldOutputJson(opts.json ?? false)) {
|
|
1190
|
+
console.log(JSON.stringify(spaces, null, 2));
|
|
1191
|
+
} else if (!isTTY()) {
|
|
1192
|
+
console.log(formatSpacesMarkdown(spaces.map((s) => ({ id: s.id, name: s.name }))));
|
|
1193
|
+
} else {
|
|
1036
1194
|
const table = formatTable(
|
|
1037
1195
|
spaces.map((s) => ({ id: s.id, name: s.name })),
|
|
1038
1196
|
[
|
|
@@ -1041,8 +1199,6 @@ async function listSpaces(config, opts) {
|
|
|
1041
1199
|
]
|
|
1042
1200
|
);
|
|
1043
1201
|
console.log(table);
|
|
1044
|
-
} else {
|
|
1045
|
-
console.log(JSON.stringify(spaces, null, 2));
|
|
1046
1202
|
}
|
|
1047
1203
|
}
|
|
1048
1204
|
|
|
@@ -1095,9 +1251,11 @@ function groupByStatus(tasks, includeClosed) {
|
|
|
1095
1251
|
}
|
|
1096
1252
|
async function runAssignedCommand(config, opts) {
|
|
1097
1253
|
const client = new ClickUpClient(config);
|
|
1098
|
-
const allTasks = await client.getMyTasks(config.teamId
|
|
1254
|
+
const allTasks = await client.getMyTasks(config.teamId, {
|
|
1255
|
+
includeClosed: opts.includeClosed
|
|
1256
|
+
});
|
|
1099
1257
|
const groups = groupByStatus(allTasks, opts.includeClosed ?? false);
|
|
1100
|
-
if (opts.json
|
|
1258
|
+
if (shouldOutputJson(opts.json ?? false)) {
|
|
1101
1259
|
const result = {};
|
|
1102
1260
|
for (const group of groups) {
|
|
1103
1261
|
result[group.status.toLowerCase()] = group.tasks.map((t) => toJsonTask(t, summarize(t)));
|
|
@@ -1105,6 +1263,14 @@ async function runAssignedCommand(config, opts) {
|
|
|
1105
1263
|
console.log(JSON.stringify(result, null, 2));
|
|
1106
1264
|
return;
|
|
1107
1265
|
}
|
|
1266
|
+
if (!isTTY()) {
|
|
1267
|
+
const mdGroups = groups.map((g) => ({
|
|
1268
|
+
label: g.status,
|
|
1269
|
+
tasks: g.tasks.map((t) => summarize(t))
|
|
1270
|
+
}));
|
|
1271
|
+
console.log(formatGroupedTasksMarkdown(mdGroups));
|
|
1272
|
+
return;
|
|
1273
|
+
}
|
|
1108
1274
|
if (groups.length === 0) {
|
|
1109
1275
|
console.log("No tasks found.");
|
|
1110
1276
|
return;
|
|
@@ -1130,13 +1296,13 @@ async function openTask(config, query, opts = {}) {
|
|
|
1130
1296
|
} catch {
|
|
1131
1297
|
}
|
|
1132
1298
|
if (task) {
|
|
1133
|
-
if (opts.json) {
|
|
1299
|
+
if (shouldOutputJson(opts.json ?? false)) {
|
|
1134
1300
|
console.log(JSON.stringify(task, null, 2));
|
|
1301
|
+
} else if (!isTTY()) {
|
|
1302
|
+
console.log(formatTaskDetailMarkdown(task));
|
|
1135
1303
|
} else {
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
console.log(task.url);
|
|
1139
|
-
}
|
|
1304
|
+
console.log(task.name);
|
|
1305
|
+
console.log(task.url);
|
|
1140
1306
|
openUrl(task.url);
|
|
1141
1307
|
}
|
|
1142
1308
|
return task;
|
|
@@ -1154,15 +1320,18 @@ async function openTask(config, query, opts = {}) {
|
|
|
1154
1320
|
}
|
|
1155
1321
|
console.log("Opening first match...");
|
|
1156
1322
|
}
|
|
1157
|
-
if (opts.json) {
|
|
1323
|
+
if (shouldOutputJson(opts.json ?? false)) {
|
|
1158
1324
|
const fullTask = await client.getTask(first.id);
|
|
1159
1325
|
console.log(JSON.stringify(fullTask, null, 2));
|
|
1160
1326
|
return fullTask;
|
|
1161
1327
|
}
|
|
1162
|
-
if (isTTY()) {
|
|
1163
|
-
|
|
1164
|
-
console.log(
|
|
1328
|
+
if (!isTTY()) {
|
|
1329
|
+
const fullTask = await client.getTask(first.id);
|
|
1330
|
+
console.log(formatTaskDetailMarkdown(fullTask));
|
|
1331
|
+
return fullTask;
|
|
1165
1332
|
}
|
|
1333
|
+
console.log(first.name);
|
|
1334
|
+
console.log(first.url);
|
|
1166
1335
|
openUrl(first.url);
|
|
1167
1336
|
return {
|
|
1168
1337
|
id: first.id,
|
|
@@ -1224,10 +1393,19 @@ async function runSummaryCommand(config, opts) {
|
|
|
1224
1393
|
const client = new ClickUpClient(config);
|
|
1225
1394
|
const allTasks = await client.getMyTasks(config.teamId);
|
|
1226
1395
|
const result = categorizeTasks(allTasks, opts.hours);
|
|
1227
|
-
if (opts.json
|
|
1396
|
+
if (shouldOutputJson(opts.json)) {
|
|
1228
1397
|
console.log(JSON.stringify(result, null, 2));
|
|
1229
1398
|
return;
|
|
1230
1399
|
}
|
|
1400
|
+
if (!isTTY()) {
|
|
1401
|
+
const mdGroups = [
|
|
1402
|
+
{ label: "Completed Recently", tasks: result.completed },
|
|
1403
|
+
{ label: "In Progress", tasks: result.inProgress },
|
|
1404
|
+
{ label: "Overdue", tasks: result.overdue }
|
|
1405
|
+
];
|
|
1406
|
+
console.log(formatGroupedTasksMarkdown(mdGroups));
|
|
1407
|
+
return;
|
|
1408
|
+
}
|
|
1231
1409
|
printSection("Completed Recently", result.completed);
|
|
1232
1410
|
printSection("In Progress", result.inProgress);
|
|
1233
1411
|
printSection("Overdue", result.overdue);
|
|
@@ -1309,7 +1487,7 @@ async function assignTask(config, taskId, opts) {
|
|
|
1309
1487
|
|
|
1310
1488
|
// src/commands/activity.ts
|
|
1311
1489
|
import chalk4 from "chalk";
|
|
1312
|
-
function
|
|
1490
|
+
function formatDate4(timestamp) {
|
|
1313
1491
|
return new Date(Number(timestamp)).toLocaleString("en-US", {
|
|
1314
1492
|
month: "short",
|
|
1315
1493
|
day: "numeric",
|
|
@@ -1351,7 +1529,7 @@ function printActivity(result, forceJson) {
|
|
|
1351
1529
|
console.log("");
|
|
1352
1530
|
console.log(chalk4.dim("-".repeat(60)));
|
|
1353
1531
|
}
|
|
1354
|
-
console.log(`${chalk4.bold(c.user)} ${chalk4.dim(
|
|
1532
|
+
console.log(`${chalk4.bold(c.user)} ${chalk4.dim(formatDate4(c.date))}`);
|
|
1355
1533
|
console.log(c.text);
|
|
1356
1534
|
}
|
|
1357
1535
|
}
|
|
@@ -1863,8 +2041,10 @@ program.command("task <taskId>").description("Get task details").option("--json"
|
|
|
1863
2041
|
wrapAction(async (taskId, opts) => {
|
|
1864
2042
|
const config = loadConfig();
|
|
1865
2043
|
const result = await getTask(config, taskId);
|
|
1866
|
-
if (opts.json
|
|
2044
|
+
if (shouldOutputJson(opts.json ?? false)) {
|
|
1867
2045
|
console.log(JSON.stringify(result, null, 2));
|
|
2046
|
+
} else if (!isTTY()) {
|
|
2047
|
+
console.log(formatTaskDetailMarkdown(result));
|
|
1868
2048
|
} else {
|
|
1869
2049
|
console.log(formatTaskDetail(result));
|
|
1870
2050
|
}
|
|
@@ -1875,10 +2055,10 @@ program.command("update <taskId>").description("Update a task").option("-n, --na
|
|
|
1875
2055
|
const config = loadConfig();
|
|
1876
2056
|
const payload = buildUpdatePayload(opts);
|
|
1877
2057
|
const result = await updateTask(config, taskId, payload);
|
|
1878
|
-
if (
|
|
2058
|
+
if (shouldOutputJson(false)) {
|
|
1879
2059
|
console.log(JSON.stringify(result, null, 2));
|
|
1880
2060
|
} else {
|
|
1881
|
-
console.log(
|
|
2061
|
+
console.log(formatUpdateConfirmation(result.id, result.name));
|
|
1882
2062
|
}
|
|
1883
2063
|
})
|
|
1884
2064
|
);
|
|
@@ -1886,10 +2066,10 @@ program.command("create").description("Create a new task").option("-l, --list <l
|
|
|
1886
2066
|
wrapAction(async (opts) => {
|
|
1887
2067
|
const config = loadConfig();
|
|
1888
2068
|
const result = await createTask(config, opts);
|
|
1889
|
-
if (
|
|
2069
|
+
if (shouldOutputJson(false)) {
|
|
1890
2070
|
console.log(JSON.stringify(result, null, 2));
|
|
1891
2071
|
} else {
|
|
1892
|
-
console.log(
|
|
2072
|
+
console.log(formatCreateConfirmation(result.id, result.name, result.url));
|
|
1893
2073
|
}
|
|
1894
2074
|
})
|
|
1895
2075
|
);
|
|
@@ -1905,10 +2085,10 @@ program.command("sprints").description("List all sprints in sprint folders").opt
|
|
|
1905
2085
|
await listSprints(config, opts);
|
|
1906
2086
|
})
|
|
1907
2087
|
);
|
|
1908
|
-
program.command("subtasks <taskId>").description("List subtasks of a task or initiative").option("--json", "Force JSON output even in terminal").action(
|
|
2088
|
+
program.command("subtasks <taskId>").description("List subtasks of a task or initiative").option("--include-closed", "Include closed/done subtasks").option("--json", "Force JSON output even in terminal").action(
|
|
1909
2089
|
wrapAction(async (taskId, opts) => {
|
|
1910
2090
|
const config = loadConfig();
|
|
1911
|
-
const tasks = await fetchSubtasks(config, taskId);
|
|
2091
|
+
const tasks = await fetchSubtasks(config, taskId, { includeClosed: opts.includeClosed });
|
|
1912
2092
|
await printTasks(tasks, opts.json ?? false, config);
|
|
1913
2093
|
})
|
|
1914
2094
|
);
|
|
@@ -1916,10 +2096,10 @@ program.command("comment <taskId>").description("Post a comment on a task").requ
|
|
|
1916
2096
|
wrapAction(async (taskId, opts) => {
|
|
1917
2097
|
const config = loadConfig();
|
|
1918
2098
|
const result = await postComment(config, taskId, opts.message);
|
|
1919
|
-
if (
|
|
1920
|
-
console.log(`Comment posted (id: ${result.id})`);
|
|
1921
|
-
} else {
|
|
2099
|
+
if (shouldOutputJson(false)) {
|
|
1922
2100
|
console.log(JSON.stringify(result, null, 2));
|
|
2101
|
+
} else {
|
|
2102
|
+
console.log(formatCommentConfirmation(result.id));
|
|
1923
2103
|
}
|
|
1924
2104
|
})
|
|
1925
2105
|
);
|
|
@@ -2003,13 +2183,10 @@ program.command("assign <taskId>").description("Assign or unassign users from a
|
|
|
2003
2183
|
wrapAction(async (taskId, opts) => {
|
|
2004
2184
|
const config = loadConfig();
|
|
2005
2185
|
const result = await assignTask(config, taskId, opts);
|
|
2006
|
-
if (opts.json
|
|
2186
|
+
if (shouldOutputJson(opts.json ?? false)) {
|
|
2007
2187
|
console.log(JSON.stringify(result, null, 2));
|
|
2008
2188
|
} else {
|
|
2009
|
-
|
|
2010
|
-
if (opts.to) parts.push(`Assigned ${opts.to} to task ${taskId}`);
|
|
2011
|
-
if (opts.remove) parts.push(`Removed ${opts.remove} from task ${taskId}`);
|
|
2012
|
-
console.log(parts.join("; "));
|
|
2189
|
+
console.log(formatAssignConfirmation(taskId, { to: opts.to, remove: opts.remove }));
|
|
2013
2190
|
}
|
|
2014
2191
|
})
|
|
2015
2192
|
);
|
package/package.json
CHANGED
|
@@ -1,19 +1,32 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: clickup
|
|
3
|
-
description: 'Use when managing ClickUp tasks, initiatives, sprints, or comments via the `cu` CLI tool.
|
|
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, assigning tasks, listing spaces and lists, opening tasks in browser, checking auth or config.'
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# ClickUp CLI (`cu`)
|
|
7
7
|
|
|
8
|
-
Reference for AI agents using the `cu`
|
|
8
|
+
Reference for AI agents using the `cu` CLI tool. Covers task management, sprint tracking, initiatives, comments, and project workflows.
|
|
9
9
|
|
|
10
|
-
Keywords: ClickUp, task management, sprint, initiative, project management, agile, backlog, subtasks
|
|
10
|
+
Keywords: ClickUp, task management, sprint, initiative, project management, agile, backlog, subtasks, standup, overdue, search
|
|
11
11
|
|
|
12
12
|
## Setup
|
|
13
13
|
|
|
14
|
-
Config at `~/.config/cu/config.json`
|
|
14
|
+
Config at `~/.config/cu/config.json` with `apiToken` and `teamId`. Run `cu init` to set up interactively.
|
|
15
15
|
|
|
16
|
-
|
|
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`
|
|
17
30
|
|
|
18
31
|
## Commands
|
|
19
32
|
|
|
@@ -27,19 +40,19 @@ All commands support `--help` for full flag details.
|
|
|
27
40
|
| `cu initiatives [--status s] [--name q] [--list id] [--space id] [--json]` | My initiatives |
|
|
28
41
|
| `cu assigned [--include-closed] [--json]` | All my tasks grouped by status |
|
|
29
42
|
| `cu sprint [--status s] [--space nameOrId] [--json]` | Tasks in active sprint (auto-detected) |
|
|
30
|
-
| `cu
|
|
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) |
|
|
31
45
|
| `cu task <id> [--json]` | Single task details |
|
|
32
|
-
| `cu subtasks <id> [--json]`
|
|
33
|
-
| `cu comments <id> [--json]` |
|
|
34
|
-
| `cu
|
|
35
|
-
| `cu
|
|
36
|
-
| `cu open <query> [--json]` | Open task in browser by ID or name |
|
|
46
|
+
| `cu subtasks <id> [--include-closed] [--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) |
|
|
37
50
|
| `cu summary [--hours n] [--json]` | Standup helper: completed, in-progress, overdue |
|
|
38
51
|
| `cu overdue [--json]` | Tasks past their due date |
|
|
39
|
-
| `cu
|
|
40
|
-
| `cu
|
|
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 |
|
|
41
55
|
| `cu auth [--json]` | Check authentication status |
|
|
42
|
-
| `cu sprints [--space nameOrId] [--json]` | List all sprints (marks active with \*) |
|
|
43
56
|
|
|
44
57
|
### Write
|
|
45
58
|
|
|
@@ -48,102 +61,85 @@ All commands support `--help` for full flag details.
|
|
|
48
61
|
| `cu update <id> [-n name] [-d desc] [-s status] [--priority p] [--due-date d] [--assignee id] [--json]` | Update task fields |
|
|
49
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) |
|
|
50
63
|
| `cu comment <id> -m text` | Post comment on task |
|
|
51
|
-
| `cu assign <id> [--to userId\|me] [--remove userId\|me] [--json]` | Assign/unassign users
|
|
64
|
+
| `cu assign <id> [--to userId\|me] [--remove userId\|me] [--json]` | Assign/unassign users |
|
|
52
65
|
| `cu config get <key>` / `cu config set <key> <value>` / `cu config path` | Manage CLI config |
|
|
53
|
-
| `cu completion <shell>` |
|
|
54
|
-
|
|
55
|
-
##
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
## Agent workflow example
|
|
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
|
+
| `--include-closed` | Include closed/done tasks (on `subtasks` and `assigned`) |
|
|
83
|
+
| `cu assign --to me` | Shorthand for your own user ID |
|
|
84
|
+
| `cu search` | Matches all query words against task name, case-insensitive |
|
|
85
|
+
| `cu sprint` | Auto-detects active sprint via view API and date range parsing |
|
|
86
|
+
| `cu summary` | Categories: completed (done/complete/closed within N hours), in progress, overdue |
|
|
87
|
+
| `cu overdue` | Excludes closed tasks, sorted most overdue first |
|
|
88
|
+
| `cu open` | Tries task ID first, falls back to name search |
|
|
89
|
+
| `cu task` | Shows custom fields in detail view |
|
|
90
|
+
| `cu lists` | Discovers list IDs needed for `--list` and `cu create -l` |
|
|
91
|
+
| Errors | stderr with exit code 1 |
|
|
92
|
+
| Parsing | Strict - excess/unknown arguments rejected |
|
|
93
|
+
|
|
94
|
+
## Agent Workflow Examples
|
|
95
|
+
|
|
96
|
+
### Investigate a task
|
|
86
97
|
|
|
87
98
|
```bash
|
|
88
|
-
|
|
89
|
-
cu
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
cu
|
|
93
|
-
|
|
94
|
-
# Check current sprint
|
|
95
|
-
cu sprint --json | jq '.[].name'
|
|
99
|
+
cu task abc123def # markdown summary
|
|
100
|
+
cu subtasks abc123def # child tasks (open only)
|
|
101
|
+
cu subtasks abc123def --include-closed # all child tasks
|
|
102
|
+
cu comments abc123def # discussion
|
|
103
|
+
cu activity abc123def # task + comments combined
|
|
104
|
+
```
|
|
96
105
|
|
|
97
|
-
|
|
98
|
-
cu task abc123def --json
|
|
106
|
+
### Find tasks
|
|
99
107
|
|
|
100
|
-
|
|
101
|
-
cu
|
|
108
|
+
```bash
|
|
109
|
+
cu tasks --status "in progress" # by status
|
|
110
|
+
cu tasks --name "login" # by partial name
|
|
111
|
+
cu search "payment flow" # multi-word search
|
|
112
|
+
cu search auth --status "prog" # fuzzy status match
|
|
113
|
+
cu sprint # current sprint
|
|
114
|
+
cu assigned # all my tasks by status
|
|
115
|
+
cu overdue # past due date
|
|
116
|
+
cu inbox --days 7 # recently updated
|
|
117
|
+
```
|
|
102
118
|
|
|
103
|
-
|
|
104
|
-
cu comments abc123def --json
|
|
119
|
+
### Make changes
|
|
105
120
|
|
|
106
|
-
|
|
121
|
+
```bash
|
|
107
122
|
cu update abc123def -s "done"
|
|
108
|
-
|
|
109
|
-
# Update priority and due date
|
|
110
123
|
cu update abc123def --priority high --due-date 2025-03-15
|
|
111
|
-
|
|
112
|
-
# Create subtask under initiative (no --list needed)
|
|
113
124
|
cu create -n "Fix the thing" -p abc123def
|
|
114
|
-
|
|
115
|
-
# Create task with priority and tags
|
|
116
125
|
cu create -n "Fix bug" -l <listId> --priority urgent --tags "bug,frontend"
|
|
117
|
-
|
|
118
|
-
# Post a comment
|
|
119
126
|
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
127
|
cu assign abc123def --to me
|
|
128
|
+
```
|
|
135
129
|
|
|
136
|
-
|
|
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
|
|
130
|
+
### Discover workspace structure
|
|
143
131
|
|
|
144
|
-
|
|
132
|
+
```bash
|
|
133
|
+
cu spaces # all spaces
|
|
134
|
+
cu spaces --name "Engineering" # find space ID by name
|
|
135
|
+
cu lists <spaceId> # lists in a space (needs ID from cu spaces)
|
|
136
|
+
cu sprints # all sprints across folders
|
|
137
|
+
cu auth # verify token works
|
|
138
|
+
```
|
|
145
139
|
|
|
146
|
-
|
|
140
|
+
### Standup
|
|
147
141
|
|
|
148
|
-
|
|
142
|
+
```bash
|
|
143
|
+
cu summary # completed / in progress / overdue
|
|
144
|
+
cu summary --hours 48 # wider window
|
|
149
145
|
```
|