@krodak/clickup-cli 1.31.2 → 1.33.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 +1 -1
- package/README.md +17 -17
- package/dist/index.js +165 -47
- package/package.json +2 -2
- package/skills/clickup-cli/SKILL.md +10 -7
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clickup-cli",
|
|
3
3
|
"description": "ClickUp CLI skills for managing tasks, sprints, comments, checklists, custom fields, tags, and time tracking via the cup command",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.33.0",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Krzysztof Rodak"
|
|
7
7
|
},
|
package/README.md
CHANGED
|
@@ -171,23 +171,23 @@ npx skills add https://github.com/krodak/clickup-cli
|
|
|
171
171
|
|
|
172
172
|
Full CRUD for the core ClickUp workflow:
|
|
173
173
|
|
|
174
|
-
| Area | Capabilities
|
|
175
|
-
| -------------------- |
|
|
176
|
-
| ✅ **Tasks** | Create, read, update, delete, duplicate, merge, search, subtasks, assign (users and groups), dependencies, links, multi-list, bulk operations (status, assign, due-date, tag, priority, field, move)
|
|
177
|
-
| 💬 **Comments** | Post, edit, delete by ID or by task scope for your own comments, threaded replies, notify all, list and view comments
|
|
178
|
-
| 🗨️ **Chat** | List channels, send messages, replies, reactions, channel management
|
|
179
|
-
| 📄 **Docs** | List, read, create, edit, delete (v3 API)
|
|
180
|
-
| ⏱️ **Time Tracking** | Start/stop timer, log entries, list/update/delete history, per-user time estimates
|
|
181
|
-
| ☑️ **Checklists** | View, create, delete, add/edit/delete items
|
|
182
|
-
| 🔧 **Custom Fields** | List, create, set, remove values (text, number, dropdown, labels, date, checkbox, url, email, rating, progress, relationship, people)
|
|
183
|
-
| 🏷️ **Tags** | Add/remove on tasks, space-level create/update/delete
|
|
184
|
-
| 🎯 **Goals & OKRs** | Goals CRUD, key results CRUD
|
|
185
|
-
| 🏃 **Sprints** | Auto-detect active sprint, `sprint:current` pseudo-ID for move/create, flexible date parsing, config override, favorite sprint folders
|
|
186
|
-
| ⭐ **Favorites** | Local favorites for quick access to sprint folders, spaces, lists, folders, views, tasks
|
|
187
|
-
| 👁️ **Views** | List, get, create, update, delete views on lists
|
|
188
|
-
| 🔗 **Webhooks** | List, create, update, delete webhooks; scope to space, folder, list, or task
|
|
189
|
-
| 🏢 **Workspace** | Spaces, folders, lists (full CRUD + rename + from template), members, user groups, task types, templates, plan, shared hierarchy
|
|
190
|
-
| 📎 **Attachments** | Upload files to tasks, list task attachments, shown in detail views
|
|
174
|
+
| Area | Capabilities |
|
|
175
|
+
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
176
|
+
| ✅ **Tasks** | Create, read, update, delete, duplicate, merge, search, subtasks, assign (users and groups), dependencies, links, multi-list, bulk operations (status, assign, due-date, tag, priority, field, move) |
|
|
177
|
+
| 💬 **Comments** | Post, edit, delete by ID or by task scope for your own comments, threaded replies, notify all, real @mentions (flag or inline `<@id>` token, notifies the user), clickable rich links, list and view comments |
|
|
178
|
+
| 🗨️ **Chat** | List channels, send messages, replies, reactions, channel management |
|
|
179
|
+
| 📄 **Docs** | List, read, create, edit, delete (v3 API) |
|
|
180
|
+
| ⏱️ **Time Tracking** | Start/stop timer, log entries, list/update/delete history, per-user time estimates |
|
|
181
|
+
| ☑️ **Checklists** | View, create, delete, add/edit/delete items |
|
|
182
|
+
| 🔧 **Custom Fields** | List, create, set, remove values (text, number, dropdown, labels, date, checkbox, url, email, rating, progress, relationship, people) |
|
|
183
|
+
| 🏷️ **Tags** | Add/remove on tasks, space-level create/update/delete |
|
|
184
|
+
| 🎯 **Goals & OKRs** | Goals CRUD, key results CRUD |
|
|
185
|
+
| 🏃 **Sprints** | Auto-detect active sprint, `sprint:current` pseudo-ID for move/create, flexible date parsing, config override, favorite sprint folders |
|
|
186
|
+
| ⭐ **Favorites** | Local favorites for quick access to sprint folders, spaces, lists, folders, views, tasks |
|
|
187
|
+
| 👁️ **Views** | List, get, create, update, delete views on lists |
|
|
188
|
+
| 🔗 **Webhooks** | List, create, update, delete webhooks; scope to space, folder, list, or task |
|
|
189
|
+
| 🏢 **Workspace** | Spaces, folders, lists (full CRUD + rename + from template), members, user groups, task types, templates, plan, shared hierarchy |
|
|
190
|
+
| 📎 **Attachments** | Upload files to tasks, list task attachments, shown in detail views |
|
|
191
191
|
|
|
192
192
|
[Full API coverage details](docs/api-coverage.md) | [Command reference](docs/commands.md)
|
|
193
193
|
|
package/dist/index.js
CHANGED
|
@@ -70,6 +70,10 @@ function expectPaginatedCollectionField(data, key, context) {
|
|
|
70
70
|
function isCustomTaskId(id) {
|
|
71
71
|
return /^[A-Z]+-\d+$/i.test(id);
|
|
72
72
|
}
|
|
73
|
+
function normalizeTaskId(input) {
|
|
74
|
+
const match = /^https?:\/\/app\.clickup\.com\/t\/(?:[^/?#]+\/)?([^/?#]+)/.exec(input.trim());
|
|
75
|
+
return match ? match[1] : input;
|
|
76
|
+
}
|
|
73
77
|
var ClickUpClient = class {
|
|
74
78
|
apiToken;
|
|
75
79
|
teamId;
|
|
@@ -79,15 +83,16 @@ var ClickUpClient = class {
|
|
|
79
83
|
this.teamId = config.teamId;
|
|
80
84
|
}
|
|
81
85
|
taskPath(taskId, suffix = "") {
|
|
82
|
-
const
|
|
83
|
-
|
|
86
|
+
const normalized = normalizeTaskId(taskId);
|
|
87
|
+
const base = `/task/${normalized}${suffix}`;
|
|
88
|
+
if (isCustomTaskId(normalized) && this.teamId) {
|
|
84
89
|
const sep = base.includes("?") ? "&" : "?";
|
|
85
90
|
return `${base}${sep}custom_task_ids=true&team_id=${this.teamId}`;
|
|
86
91
|
}
|
|
87
92
|
return base;
|
|
88
93
|
}
|
|
89
94
|
customIdQueryParams(taskId) {
|
|
90
|
-
if (isCustomTaskId(taskId) && this.teamId) {
|
|
95
|
+
if (isCustomTaskId(normalizeTaskId(taskId)) && this.teamId) {
|
|
91
96
|
return `?custom_task_ids=true&team_id=${this.teamId}`;
|
|
92
97
|
}
|
|
93
98
|
return "";
|
|
@@ -402,17 +407,20 @@ var ClickUpClient = class {
|
|
|
402
407
|
);
|
|
403
408
|
}
|
|
404
409
|
async addTaskToList(taskId, listId) {
|
|
405
|
-
|
|
410
|
+
const normalized = normalizeTaskId(taskId);
|
|
411
|
+
await this.request(`/list/${listId}/task/${normalized}`, { method: "POST" });
|
|
406
412
|
}
|
|
407
413
|
async removeTaskFromList(taskId, listId) {
|
|
408
|
-
|
|
414
|
+
const normalized = normalizeTaskId(taskId);
|
|
415
|
+
await this.request(`/list/${listId}/task/${normalized}`, { method: "DELETE" });
|
|
409
416
|
}
|
|
410
417
|
async moveTaskToList(taskId, listId) {
|
|
411
418
|
if (!this.teamId) {
|
|
412
419
|
throw new Error("teamId is required to move a task to a new home list");
|
|
413
420
|
}
|
|
421
|
+
const normalized = normalizeTaskId(taskId);
|
|
414
422
|
const [task, destList] = await Promise.all([
|
|
415
|
-
this.getTask(
|
|
423
|
+
this.getTask(normalized),
|
|
416
424
|
this.getListWithStatuses(listId)
|
|
417
425
|
]);
|
|
418
426
|
const taskStatus = task.status.status.toLowerCase();
|
|
@@ -428,7 +436,7 @@ var ClickUpClient = class {
|
|
|
428
436
|
destination_status: destStatus.status
|
|
429
437
|
});
|
|
430
438
|
}
|
|
431
|
-
await this.requestV3(`/workspaces/${this.teamId}/tasks/${
|
|
439
|
+
await this.requestV3(`/workspaces/${this.teamId}/tasks/${normalized}/home_list/${listId}`, {
|
|
432
440
|
method: "PUT",
|
|
433
441
|
body: JSON.stringify({ status_mappings: statusMappings })
|
|
434
442
|
});
|
|
@@ -462,8 +470,9 @@ var ClickUpClient = class {
|
|
|
462
470
|
return this.request(`/team/${this.teamId}/plan`);
|
|
463
471
|
}
|
|
464
472
|
async getTaskAttachments(taskId) {
|
|
473
|
+
const normalized = normalizeTaskId(taskId);
|
|
465
474
|
const data = await this.requestV3(
|
|
466
|
-
`/workspaces/${this.teamId}/tasks/${
|
|
475
|
+
`/workspaces/${this.teamId}/tasks/${normalized}/attachments`
|
|
467
476
|
);
|
|
468
477
|
return expectArrayField(data, "data", "task attachments");
|
|
469
478
|
}
|
|
@@ -518,14 +527,16 @@ var ClickUpClient = class {
|
|
|
518
527
|
});
|
|
519
528
|
}
|
|
520
529
|
async addTaskLink(taskId, linksTo) {
|
|
521
|
-
await this.request(
|
|
522
|
-
|
|
523
|
-
|
|
530
|
+
await this.request(
|
|
531
|
+
this.taskPath(taskId, `/link/${normalizeTaskId(linksTo)}`),
|
|
532
|
+
{ method: "POST" }
|
|
533
|
+
);
|
|
524
534
|
}
|
|
525
535
|
async deleteTaskLink(taskId, linksTo) {
|
|
526
|
-
await this.request(
|
|
527
|
-
|
|
528
|
-
|
|
536
|
+
await this.request(
|
|
537
|
+
this.taskPath(taskId, `/link/${normalizeTaskId(linksTo)}`),
|
|
538
|
+
{ method: "DELETE" }
|
|
539
|
+
);
|
|
529
540
|
}
|
|
530
541
|
async getListCustomFields(listId) {
|
|
531
542
|
const data = await this.request(`/list/${listId}/field`);
|
|
@@ -1007,17 +1018,17 @@ var ClickUpClient = class {
|
|
|
1007
1018
|
async mergeTasks(taskId, mergeWithTaskIds) {
|
|
1008
1019
|
await this.request(this.taskPath(taskId, "/merge"), {
|
|
1009
1020
|
method: "POST",
|
|
1010
|
-
body: JSON.stringify({ merge_with: mergeWithTaskIds })
|
|
1021
|
+
body: JSON.stringify({ merge_with: mergeWithTaskIds.map(normalizeTaskId) })
|
|
1011
1022
|
});
|
|
1012
1023
|
}
|
|
1013
1024
|
async updateTimeEstimatesByUser(taskId, estimates) {
|
|
1014
|
-
return this.requestV3(`/workspaces/${this.teamId}/tasks/${taskId}/time_estimates_by_user`, {
|
|
1025
|
+
return this.requestV3(`/workspaces/${this.teamId}/tasks/${normalizeTaskId(taskId)}/time_estimates_by_user`, {
|
|
1015
1026
|
method: "PATCH",
|
|
1016
1027
|
body: JSON.stringify({ time_estimates_by_user: estimates })
|
|
1017
1028
|
});
|
|
1018
1029
|
}
|
|
1019
1030
|
async replaceTimeEstimatesByUser(taskId, estimates) {
|
|
1020
|
-
return this.requestV3(`/workspaces/${this.teamId}/tasks/${taskId}/time_estimates_by_user`, {
|
|
1031
|
+
return this.requestV3(`/workspaces/${this.teamId}/tasks/${normalizeTaskId(taskId)}/time_estimates_by_user`, {
|
|
1021
1032
|
method: "PUT",
|
|
1022
1033
|
body: JSON.stringify({ time_estimates_by_user: estimates })
|
|
1023
1034
|
});
|
|
@@ -1055,8 +1066,8 @@ var ClickUpClient = class {
|
|
|
1055
1066
|
const data = await this.request(`/list/${listId}/comment`);
|
|
1056
1067
|
return readCollectionField(data, "comments", "list comments");
|
|
1057
1068
|
}
|
|
1058
|
-
async postListComment(listId, commentText, notifyAll) {
|
|
1059
|
-
const body = { comment_text: commentText };
|
|
1069
|
+
async postListComment(listId, commentText, notifyAll, richBlocks) {
|
|
1070
|
+
const body = richBlocks ? { comment: richBlocks } : { comment_text: commentText };
|
|
1060
1071
|
if (notifyAll) body.notify_all = true;
|
|
1061
1072
|
return this.request(`/list/${listId}/comment`, {
|
|
1062
1073
|
method: "POST",
|
|
@@ -1067,8 +1078,8 @@ var ClickUpClient = class {
|
|
|
1067
1078
|
const data = await this.request(`/view/${viewId}/comment`);
|
|
1068
1079
|
return readCollectionField(data, "comments", "view comments");
|
|
1069
1080
|
}
|
|
1070
|
-
async postViewComment(viewId, commentText, notifyAll) {
|
|
1071
|
-
const body = { comment_text: commentText };
|
|
1081
|
+
async postViewComment(viewId, commentText, notifyAll, richBlocks) {
|
|
1082
|
+
const body = richBlocks ? { comment: richBlocks } : { comment_text: commentText };
|
|
1072
1083
|
if (notifyAll) body.notify_all = true;
|
|
1073
1084
|
return this.request(`/view/${viewId}/comment`, {
|
|
1074
1085
|
method: "POST",
|
|
@@ -2854,7 +2865,9 @@ function processInlineFormatting(text, lineAttrs) {
|
|
|
2854
2865
|
{ type: "bold", re: /\*{2}([^*]+)\*{2}/ },
|
|
2855
2866
|
{ type: "italic", re: /(?<!\*)\*(?!\*)([^*]+)(?<!\*)\*(?!\*)/ },
|
|
2856
2867
|
{ type: "strike", re: /~~([^~]+)~~/ },
|
|
2857
|
-
{ type: "
|
|
2868
|
+
{ type: "mention", re: /<@(\d+)>/ },
|
|
2869
|
+
{ type: "link", re: /\[([^\]]+)\]\(([^)]+)\)/ },
|
|
2870
|
+
{ type: "autolink", re: /(?<![([])(https?:\/\/[^\s<>]+)/ }
|
|
2858
2871
|
];
|
|
2859
2872
|
for (const { type, re } of patterns) {
|
|
2860
2873
|
const m = re.exec(remaining);
|
|
@@ -2902,6 +2915,15 @@ function processInlineFormatting(text, lineAttrs) {
|
|
|
2902
2915
|
attributes: { ...lineAttrs, link: matchResult[2] }
|
|
2903
2916
|
});
|
|
2904
2917
|
break;
|
|
2918
|
+
case "autolink":
|
|
2919
|
+
blocks.push({
|
|
2920
|
+
text: innerText,
|
|
2921
|
+
attributes: { ...lineAttrs, link: innerText }
|
|
2922
|
+
});
|
|
2923
|
+
break;
|
|
2924
|
+
case "mention":
|
|
2925
|
+
blocks.push({ type: "tag", user: { id: Number(innerText) } });
|
|
2926
|
+
break;
|
|
2905
2927
|
}
|
|
2906
2928
|
remaining = remaining.slice(earliestIndex + matchResult[0].length);
|
|
2907
2929
|
}
|
|
@@ -3004,10 +3026,42 @@ function markdownToCommentBlocks(markdown) {
|
|
|
3004
3026
|
}
|
|
3005
3027
|
|
|
3006
3028
|
// src/commands/comment.ts
|
|
3007
|
-
async function
|
|
3029
|
+
async function resolveMemberId(client, teamId, value) {
|
|
3030
|
+
if (/^\d+$/.test(value)) return Number(value);
|
|
3031
|
+
if (value === "me") return (await client.getMe()).id;
|
|
3032
|
+
const members = await client.getWorkspaceMembers(teamId);
|
|
3033
|
+
const match = members.find(
|
|
3034
|
+
(m) => m.email?.toLowerCase() === value.toLowerCase() || m.username?.toLowerCase() === value.toLowerCase()
|
|
3035
|
+
);
|
|
3036
|
+
if (match) return match.id;
|
|
3037
|
+
const available = members.map((m) => `${m.username} (${m.email})`).join(", ");
|
|
3038
|
+
throw new Error(`Member "${value}" not found. Available: ${available}`);
|
|
3039
|
+
}
|
|
3040
|
+
function createCachedMemberResolver(client, teamId) {
|
|
3041
|
+
let membersCache = null;
|
|
3042
|
+
const cachingClient = {
|
|
3043
|
+
getMe: () => client.getMe(),
|
|
3044
|
+
getWorkspaceMembers: async () => {
|
|
3045
|
+
if (membersCache === null) membersCache = await client.getWorkspaceMembers(teamId);
|
|
3046
|
+
return membersCache;
|
|
3047
|
+
}
|
|
3048
|
+
};
|
|
3049
|
+
return (value) => resolveMemberId(cachingClient, teamId, value);
|
|
3050
|
+
}
|
|
3051
|
+
function buildCommentBlocks(message, mentionIds) {
|
|
3052
|
+
const messageBlocks = markdownToCommentBlocks(message);
|
|
3053
|
+
if (mentionIds.length === 0) return messageBlocks;
|
|
3054
|
+
const mentionBlocks = [];
|
|
3055
|
+
for (const id of mentionIds) {
|
|
3056
|
+
mentionBlocks.push({ type: "tag", user: { id } });
|
|
3057
|
+
mentionBlocks.push({ text: " " });
|
|
3058
|
+
}
|
|
3059
|
+
return [...mentionBlocks, ...messageBlocks];
|
|
3060
|
+
}
|
|
3061
|
+
async function postComment(config, taskId, text, notifyAll, mentionIds) {
|
|
3008
3062
|
if (!text.trim()) throw new Error("Comment text cannot be empty");
|
|
3009
3063
|
const client = new ClickUpClient(config);
|
|
3010
|
-
const blocks =
|
|
3064
|
+
const blocks = buildCommentBlocks(text, mentionIds ?? []);
|
|
3011
3065
|
return client.postComment(taskId, text, notifyAll, blocks);
|
|
3012
3066
|
}
|
|
3013
3067
|
|
|
@@ -3821,7 +3875,7 @@ var commandMetadata = [
|
|
|
3821
3875
|
{
|
|
3822
3876
|
name: "comment",
|
|
3823
3877
|
description: "Post a comment on a task",
|
|
3824
|
-
flags: ["-m", "--message", "--notify-all", "--json"],
|
|
3878
|
+
flags: ["-m", "--message", "--notify-all", "--mention", "--json"],
|
|
3825
3879
|
quickReference: [
|
|
3826
3880
|
{ section: "write", usage: "comment <taskId>", description: "Post a comment on a task" }
|
|
3827
3881
|
]
|
|
@@ -3829,7 +3883,7 @@ var commandMetadata = [
|
|
|
3829
3883
|
{
|
|
3830
3884
|
name: "comment-edit",
|
|
3831
3885
|
description: "Edit an existing comment",
|
|
3832
|
-
flags: ["-m", "--message", "--resolved", "--unresolved", "--json"],
|
|
3886
|
+
flags: ["-m", "--message", "--resolved", "--unresolved", "--mention", "--json"],
|
|
3833
3887
|
quickReference: [
|
|
3834
3888
|
{
|
|
3835
3889
|
section: "write",
|
|
@@ -3873,7 +3927,7 @@ var commandMetadata = [
|
|
|
3873
3927
|
{
|
|
3874
3928
|
name: "reply",
|
|
3875
3929
|
description: "Reply to a comment",
|
|
3876
|
-
flags: ["-m", "--message", "--notify-all", "--json"],
|
|
3930
|
+
flags: ["-m", "--message", "--notify-all", "--mention", "--json"],
|
|
3877
3931
|
quickReference: [
|
|
3878
3932
|
{ section: "write", usage: "reply <commentId>", description: "Reply to a comment" }
|
|
3879
3933
|
]
|
|
@@ -4678,7 +4732,7 @@ var commandMetadata = [
|
|
|
4678
4732
|
{
|
|
4679
4733
|
name: "list-comment",
|
|
4680
4734
|
description: "Post a comment on a list",
|
|
4681
|
-
flags: ["-m", "--message", "--notify-all", "--json"],
|
|
4735
|
+
flags: ["-m", "--message", "--notify-all", "--mention", "--json"],
|
|
4682
4736
|
quickReference: [
|
|
4683
4737
|
{
|
|
4684
4738
|
section: "write",
|
|
@@ -4702,7 +4756,7 @@ var commandMetadata = [
|
|
|
4702
4756
|
{
|
|
4703
4757
|
name: "view-comment",
|
|
4704
4758
|
description: "Post a comment on a view",
|
|
4705
|
-
flags: ["-m", "--message", "--notify-all", "--json"],
|
|
4759
|
+
flags: ["-m", "--message", "--notify-all", "--mention", "--json"],
|
|
4706
4760
|
quickReference: [
|
|
4707
4761
|
{
|
|
4708
4762
|
section: "write",
|
|
@@ -5145,6 +5199,7 @@ ${renderZshTopLevelCommands(name)}
|
|
|
5145
5199
|
'1:task_id:' \\
|
|
5146
5200
|
'(-m --message)'{-m,--message}'[Comment text]:text:' \\
|
|
5147
5201
|
'--notify-all[Notify all assignees]' \\
|
|
5202
|
+
'--mention[Mention a user (ID, email, username, or me)]:user:' \\
|
|
5148
5203
|
'--json[Force JSON output]'
|
|
5149
5204
|
;;
|
|
5150
5205
|
comments)
|
|
@@ -5379,6 +5434,7 @@ ${renderZshTopLevelCommands(name)}
|
|
|
5379
5434
|
'(-m --message)'{-m,--message}'[New comment text]:text:' \\
|
|
5380
5435
|
'--resolved[Mark comment as resolved]' \\
|
|
5381
5436
|
'--unresolved[Mark comment as unresolved]' \\
|
|
5437
|
+
'--mention[Mention a user (ID, email, username, or me)]:user:' \\
|
|
5382
5438
|
'--json[Force JSON output]'
|
|
5383
5439
|
;;
|
|
5384
5440
|
comment-delete)
|
|
@@ -5399,6 +5455,7 @@ ${renderZshTopLevelCommands(name)}
|
|
|
5399
5455
|
'1:comment_id:' \\
|
|
5400
5456
|
'(-m --message)'{-m,--message}'[Reply text]:text:' \\
|
|
5401
5457
|
'--notify-all[Notify all assignees]' \\
|
|
5458
|
+
'--mention[Mention a user (ID, email, username, or me)]:user:' \\
|
|
5402
5459
|
'--json[Force JSON output]'
|
|
5403
5460
|
;;
|
|
5404
5461
|
link)
|
|
@@ -6769,13 +6826,13 @@ function formatChecklistsMarkdown(checklists) {
|
|
|
6769
6826
|
}
|
|
6770
6827
|
|
|
6771
6828
|
// src/commands/comment-edit.ts
|
|
6772
|
-
async function editComment(config, commentId, text, resolved) {
|
|
6829
|
+
async function editComment(config, commentId, text, resolved, mentionIds) {
|
|
6773
6830
|
if (text === void 0 && resolved === void 0) {
|
|
6774
6831
|
throw new Error("Provide at least one of: --message, --resolved, --unresolved");
|
|
6775
6832
|
}
|
|
6776
6833
|
if (text !== void 0 && !text.trim()) throw new Error("Comment text cannot be empty");
|
|
6777
6834
|
const client = new ClickUpClient(config);
|
|
6778
|
-
const blocks = text !== void 0 ?
|
|
6835
|
+
const blocks = text !== void 0 ? buildCommentBlocks(text, mentionIds ?? []) : void 0;
|
|
6779
6836
|
await client.updateComment(commentId, text ?? "", resolved, blocks);
|
|
6780
6837
|
}
|
|
6781
6838
|
|
|
@@ -6816,10 +6873,10 @@ async function getReplies(config, commentId) {
|
|
|
6816
6873
|
const client = new ClickUpClient(config);
|
|
6817
6874
|
return client.getThreadedComments(commentId);
|
|
6818
6875
|
}
|
|
6819
|
-
async function createReply(config, commentId, text, notifyAll) {
|
|
6876
|
+
async function createReply(config, commentId, text, notifyAll, mentionIds) {
|
|
6820
6877
|
if (!text.trim()) throw new Error("Reply text cannot be empty");
|
|
6821
6878
|
const client = new ClickUpClient(config);
|
|
6822
|
-
const blocks =
|
|
6879
|
+
const blocks = buildCommentBlocks(text, mentionIds ?? []);
|
|
6823
6880
|
await client.createThreadedComment(commentId, text, notifyAll, blocks);
|
|
6824
6881
|
}
|
|
6825
6882
|
function formatReplies(replies) {
|
|
@@ -8261,10 +8318,11 @@ async function fetchListComments(config, listId) {
|
|
|
8261
8318
|
text: c.comment_text
|
|
8262
8319
|
}));
|
|
8263
8320
|
}
|
|
8264
|
-
async function postListCommentCommand(config, listId, text, notifyAll) {
|
|
8321
|
+
async function postListCommentCommand(config, listId, text, notifyAll, mentionIds) {
|
|
8265
8322
|
if (!text.trim()) throw new Error("Comment text cannot be empty");
|
|
8266
8323
|
const client = new ClickUpClient(config);
|
|
8267
|
-
|
|
8324
|
+
const blocks = buildCommentBlocks(text, mentionIds ?? []);
|
|
8325
|
+
return client.postListComment(listId, text, notifyAll, blocks);
|
|
8268
8326
|
}
|
|
8269
8327
|
|
|
8270
8328
|
// src/commands/view-comments.ts
|
|
@@ -8278,10 +8336,11 @@ async function fetchViewComments(config, viewId) {
|
|
|
8278
8336
|
text: c.comment_text
|
|
8279
8337
|
}));
|
|
8280
8338
|
}
|
|
8281
|
-
async function postViewCommentCommand(config, viewId, text, notifyAll) {
|
|
8339
|
+
async function postViewCommentCommand(config, viewId, text, notifyAll, mentionIds) {
|
|
8282
8340
|
if (!text.trim()) throw new Error("Comment text cannot be empty");
|
|
8283
8341
|
const client = new ClickUpClient(config);
|
|
8284
|
-
|
|
8342
|
+
const blocks = buildCommentBlocks(text, mentionIds ?? []);
|
|
8343
|
+
return client.postViewComment(viewId, text, notifyAll, blocks);
|
|
8285
8344
|
}
|
|
8286
8345
|
|
|
8287
8346
|
// src/index.ts
|
|
@@ -8305,6 +8364,18 @@ function parseOptionalNumberOption(value, optionName) {
|
|
|
8305
8364
|
function splitCommaList(value) {
|
|
8306
8365
|
return value.split(",").map((v) => v.trim()).filter((v) => v.length > 0);
|
|
8307
8366
|
}
|
|
8367
|
+
function collect(value, previous) {
|
|
8368
|
+
return [...previous, value];
|
|
8369
|
+
}
|
|
8370
|
+
async function resolveMentions(client, teamId, mentions) {
|
|
8371
|
+
if (mentions.length === 0) return [];
|
|
8372
|
+
const resolve2 = createCachedMemberResolver(client, teamId);
|
|
8373
|
+
const ids = [];
|
|
8374
|
+
for (const mention of mentions) {
|
|
8375
|
+
ids.push(await resolve2(mention));
|
|
8376
|
+
}
|
|
8377
|
+
return ids;
|
|
8378
|
+
}
|
|
8308
8379
|
function createCachedGroupResolver(client) {
|
|
8309
8380
|
let cache = null;
|
|
8310
8381
|
const cachingClient = {
|
|
@@ -8595,11 +8666,18 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
|
|
|
8595
8666
|
}
|
|
8596
8667
|
)
|
|
8597
8668
|
);
|
|
8598
|
-
program.command("comment <taskId>").description("Post a comment on a task").requiredOption("-m, --message <text>", "Comment text").option("--notify-all", "Notify all assignees").option(
|
|
8669
|
+
program.command("comment <taskId>").description("Post a comment on a task").requiredOption("-m, --message <text>", "Comment text").option("--notify-all", "Notify all assignees").option(
|
|
8670
|
+
"--mention <user>",
|
|
8671
|
+
'Mention a user (ID, email, username, or "me"). Repeatable.',
|
|
8672
|
+
collect,
|
|
8673
|
+
[]
|
|
8674
|
+
).option("--json", "Force JSON output even in terminal").action(
|
|
8599
8675
|
wrapAction(
|
|
8600
8676
|
async (taskId, opts) => {
|
|
8601
8677
|
const config = loadConfig(getProfileName());
|
|
8602
|
-
const
|
|
8678
|
+
const client = new ClickUpClient(config);
|
|
8679
|
+
const mentionIds = await resolveMentions(client, config.teamId, opts.mention);
|
|
8680
|
+
const result = await postComment(config, taskId, opts.message, opts.notifyAll, mentionIds);
|
|
8603
8681
|
if (shouldOutputJson(opts.json ?? false)) {
|
|
8604
8682
|
console.log(JSON.stringify(result, null, 2));
|
|
8605
8683
|
} else {
|
|
@@ -8615,14 +8693,21 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
|
|
|
8615
8693
|
printComments(comments, opts.json ?? false);
|
|
8616
8694
|
})
|
|
8617
8695
|
);
|
|
8618
|
-
program.command("comment-edit <commentId>").description("Edit an existing comment").option("-m, --message <text>", "New comment text").option("--resolved", "Mark comment as resolved").option("--unresolved", "Mark comment as unresolved").option(
|
|
8696
|
+
program.command("comment-edit <commentId>").description("Edit an existing comment").option("-m, --message <text>", "New comment text").option("--resolved", "Mark comment as resolved").option("--unresolved", "Mark comment as unresolved").option(
|
|
8697
|
+
"--mention <user>",
|
|
8698
|
+
'Mention a user (ID, email, username, or "me"). Repeatable.',
|
|
8699
|
+
collect,
|
|
8700
|
+
[]
|
|
8701
|
+
).option("--json", "Force JSON output even in terminal").action(
|
|
8619
8702
|
wrapAction(
|
|
8620
8703
|
async (commentId, opts) => {
|
|
8621
8704
|
const config = loadConfig(getProfileName());
|
|
8622
8705
|
let resolved;
|
|
8623
8706
|
if (opts.resolved) resolved = true;
|
|
8624
8707
|
if (opts.unresolved) resolved = false;
|
|
8625
|
-
|
|
8708
|
+
const client = new ClickUpClient(config);
|
|
8709
|
+
const mentionIds = await resolveMentions(client, config.teamId, opts.mention);
|
|
8710
|
+
await editComment(config, commentId, opts.message, resolved, mentionIds);
|
|
8626
8711
|
if (shouldOutputJson(opts.json ?? false)) {
|
|
8627
8712
|
console.log(JSON.stringify({ success: true, commentId }, null, 2));
|
|
8628
8713
|
} else {
|
|
@@ -8681,11 +8766,18 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
|
|
|
8681
8766
|
}
|
|
8682
8767
|
})
|
|
8683
8768
|
);
|
|
8684
|
-
program.command("reply <commentId>").description("Reply to a comment").requiredOption("-m, --message <text>", "Reply text").option("--notify-all", "Notify all assignees").option(
|
|
8769
|
+
program.command("reply <commentId>").description("Reply to a comment").requiredOption("-m, --message <text>", "Reply text").option("--notify-all", "Notify all assignees").option(
|
|
8770
|
+
"--mention <user>",
|
|
8771
|
+
'Mention a user (ID, email, username, or "me"). Repeatable.',
|
|
8772
|
+
collect,
|
|
8773
|
+
[]
|
|
8774
|
+
).option("--json", "Force JSON output even in terminal").action(
|
|
8685
8775
|
wrapAction(
|
|
8686
8776
|
async (commentId, opts) => {
|
|
8687
8777
|
const config = loadConfig(getProfileName());
|
|
8688
|
-
|
|
8778
|
+
const client = new ClickUpClient(config);
|
|
8779
|
+
const mentionIds = await resolveMentions(client, config.teamId, opts.mention);
|
|
8780
|
+
await createReply(config, commentId, opts.message, opts.notifyAll, mentionIds);
|
|
8689
8781
|
if (shouldOutputJson(opts.json ?? false)) {
|
|
8690
8782
|
console.log(JSON.stringify({ success: true, commentId }, null, 2));
|
|
8691
8783
|
} else {
|
|
@@ -10486,11 +10578,24 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
|
|
|
10486
10578
|
printComments(comments, opts.json ?? false);
|
|
10487
10579
|
})
|
|
10488
10580
|
);
|
|
10489
|
-
program.command("list-comment <listId>").description("Post a comment on a list").requiredOption("-m, --message <text>", "Comment text").option("--notify-all", "Notify all assignees").option(
|
|
10581
|
+
program.command("list-comment <listId>").description("Post a comment on a list").requiredOption("-m, --message <text>", "Comment text").option("--notify-all", "Notify all assignees").option(
|
|
10582
|
+
"--mention <user>",
|
|
10583
|
+
'Mention a user (ID, email, username, or "me"). Repeatable.',
|
|
10584
|
+
collect,
|
|
10585
|
+
[]
|
|
10586
|
+
).option("--json", "Force JSON output even in terminal").action(
|
|
10490
10587
|
wrapAction(
|
|
10491
10588
|
async (listId, opts) => {
|
|
10492
10589
|
const config = loadConfig(getProfileName());
|
|
10493
|
-
const
|
|
10590
|
+
const client = new ClickUpClient(config);
|
|
10591
|
+
const mentionIds = await resolveMentions(client, config.teamId, opts.mention);
|
|
10592
|
+
const result = await postListCommentCommand(
|
|
10593
|
+
config,
|
|
10594
|
+
listId,
|
|
10595
|
+
opts.message,
|
|
10596
|
+
opts.notifyAll,
|
|
10597
|
+
mentionIds
|
|
10598
|
+
);
|
|
10494
10599
|
if (shouldOutputJson(opts.json ?? false)) {
|
|
10495
10600
|
console.log(JSON.stringify(result, null, 2));
|
|
10496
10601
|
} else {
|
|
@@ -10506,11 +10611,24 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
|
|
|
10506
10611
|
printComments(comments, opts.json ?? false);
|
|
10507
10612
|
})
|
|
10508
10613
|
);
|
|
10509
|
-
program.command("view-comment <viewId>").description("Post a comment on a view").requiredOption("-m, --message <text>", "Comment text").option("--notify-all", "Notify all assignees").option(
|
|
10614
|
+
program.command("view-comment <viewId>").description("Post a comment on a view").requiredOption("-m, --message <text>", "Comment text").option("--notify-all", "Notify all assignees").option(
|
|
10615
|
+
"--mention <user>",
|
|
10616
|
+
'Mention a user (ID, email, username, or "me"). Repeatable.',
|
|
10617
|
+
collect,
|
|
10618
|
+
[]
|
|
10619
|
+
).option("--json", "Force JSON output even in terminal").action(
|
|
10510
10620
|
wrapAction(
|
|
10511
10621
|
async (viewId, opts) => {
|
|
10512
10622
|
const config = loadConfig(getProfileName());
|
|
10513
|
-
const
|
|
10623
|
+
const client = new ClickUpClient(config);
|
|
10624
|
+
const mentionIds = await resolveMentions(client, config.teamId, opts.mention);
|
|
10625
|
+
const result = await postViewCommentCommand(
|
|
10626
|
+
config,
|
|
10627
|
+
viewId,
|
|
10628
|
+
opts.message,
|
|
10629
|
+
opts.notifyAll,
|
|
10630
|
+
mentionIds
|
|
10631
|
+
);
|
|
10514
10632
|
if (shouldOutputJson(opts.json ?? false)) {
|
|
10515
10633
|
console.log(JSON.stringify(result, null, 2));
|
|
10516
10634
|
} else {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@krodak/clickup-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.33.0",
|
|
4
4
|
"description": "ClickUp CLI for AI agents and humans",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"@inquirer/prompts": "^8.3.0",
|
|
48
48
|
"chalk": "^5.6.2",
|
|
49
|
-
"commander": "^
|
|
49
|
+
"commander": "^15.0.0"
|
|
50
50
|
},
|
|
51
51
|
"engines": {
|
|
52
52
|
"node": ">=22.0.0"
|
|
@@ -3,11 +3,11 @@ name: clickup
|
|
|
3
3
|
description: 'Use when managing ClickUp tasks, sprints, or comments via the `cup` CLI tool. Triggers: task queries, status updates, sprint tracking, creating subtasks, posting comments, threaded replies, standup summaries, searching tasks, checking overdue items, assigning tasks, listing spaces and lists, opening tasks in browser, checking auth or config, setting custom fields, deleting tasks, managing tags, managing checklists, editing comments, task links, time tracking, attachments, file uploads, listing members, listing fields, duplicating tasks, bulk operations, goals, key results, saved filters, favorites.'
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# ClickUp CLI (`cup`) - skill version 1.
|
|
6
|
+
# ClickUp CLI (`cup`) - skill version 1.33.0
|
|
7
7
|
|
|
8
8
|
Reference for AI agents using the `cup` CLI tool. Covers task management, sprint tracking, comments, time tracking, custom fields, goals, docs, and project workflows.
|
|
9
9
|
|
|
10
|
-
> **Version check:** Run `cup --version`. If your installed version is older than 1.
|
|
10
|
+
> **Version check:** Run `cup --version`. If your installed version is older than 1.33.0, update with `npm install -g @krodak/clickup-cli` and refresh this skill with `cup skill`.
|
|
11
11
|
|
|
12
12
|
## Install & Configure
|
|
13
13
|
|
|
@@ -163,11 +163,11 @@ All commands support `--help` for full flag details. All commands support `--jso
|
|
|
163
163
|
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
164
164
|
| `cup create -n name [-l listId\|sprint:current] [-p parentId] [-d desc] [-s status] [--priority p] [--due-date d] [--start-date d] [--time-estimate t] [--assignee id\|me] [--group-assignee uuid\|@handle,...] [--tags t] [--custom-item-id n] [--template id] [--field "Name" val]` | Create task (`--list` accepts `sprint:current`, `--field` sets custom fields inline) |
|
|
165
165
|
| `cup update <id> [-n name] [-d desc] [-s status] [--priority p] [--due-date d\|none] [--start-date d] [--time-estimate t] [--assignee id\|me] [--remove-assignee id\|me] [--group-assignee uuid\|@handle] [--remove-group-assignee uuid\|@handle] [--parent id] [--detach] [--archive] [--unarchive] [--type type] [--field "Name" val]` | Update task fields (including custom fields and group assignees) |
|
|
166
|
-
| `cup comment <id> -m text [--notify-all]`
|
|
167
|
-
| `cup comment-edit <commentId> -m text [--resolved] [--unresolved]`
|
|
166
|
+
| `cup comment <id> -m text [--notify-all] [--mention user]` | Post comment (markdown auto-converted to rich text; `--mention` for real @mentions, repeatable) |
|
|
167
|
+
| `cup comment-edit <commentId> -m text [--resolved] [--unresolved] [--mention user]` | Edit a comment (markdown auto-converted to rich text; `--mention` for real @mentions) |
|
|
168
168
|
| `cup comment-delete <commentId>` or `cup comment-delete --task <taskId> --mine [--match text]` | Delete a comment by ID or delete one of your task comments |
|
|
169
169
|
| `cup replies <commentId>` | List threaded replies |
|
|
170
|
-
| `cup reply <commentId> -m text [--notify-all]`
|
|
170
|
+
| `cup reply <commentId> -m text [--notify-all] [--mention user]` | Reply to a comment (markdown auto-converted to rich text; `--mention` for real @mentions) |
|
|
171
171
|
| `cup assign <id> [--to ids\|me] [--remove ids\|me] [--group uuid\|@handle,...] [--remove-group uuid\|@handle,...]` | Assign/unassign users and groups (all flags accept comma-separated values) |
|
|
172
172
|
| `cup depend <id> [--on taskId] [--blocks taskId] [--remove]` | Add/remove dependencies |
|
|
173
173
|
| `cup move <id> [--to listId\|sprint:current] [--remove listId]` | Add/remove task from lists. **`--to` + `--remove` together changes the task's _home_ list** (uses v3 `home_list` endpoint with auto status mapping). `--to` alone adds multi-list membership. `--to` accepts `sprint:current`. |
|
|
@@ -225,8 +225,8 @@ All commands support `--help` for full flag details. All commands support `--jso
|
|
|
225
225
|
| `cup view-create <listId> <name> -t <type> [--group-by field]` | Create a view on a list |
|
|
226
226
|
| `cup view-update <viewId> [-n name] [--group-by field]` | Update a view |
|
|
227
227
|
| `cup view-delete <viewId> [--confirm]` | Delete a view (DESTRUCTIVE) |
|
|
228
|
-
| `cup list-comment <listId> -m text [--notify-all]`
|
|
229
|
-
| `cup view-comment <viewId> -m text [--notify-all]`
|
|
228
|
+
| `cup list-comment <listId> -m text [--notify-all] [--mention user]` | Post comment on a list (`--mention` for real @mentions) |
|
|
229
|
+
| `cup view-comment <viewId> -m text [--notify-all] [--mention user]` | Post comment on a view (`--mention` for real @mentions) |
|
|
230
230
|
| `cup webhook create --url <url> --events <events> [--space\|--folder\|--list\|--task <id>]` | Create a webhook |
|
|
231
231
|
| `cup webhook update <webhookId> [--url u] [--events e] [--status s]` | Update a webhook |
|
|
232
232
|
| `cup webhook delete <webhookId> [--confirm]` | Delete a webhook (DESTRUCTIVE) |
|
|
@@ -264,6 +264,7 @@ All commands support `--help` for full flag details. All commands support `--jso
|
|
|
264
264
|
| Topic | Detail |
|
|
265
265
|
| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
266
266
|
| Task IDs | Native (`abc123def`) or custom (`PROJ-123`). Custom IDs auto-detected by `PREFIX-DIGITS` format |
|
|
267
|
+
| Task URLs | All commands that accept a task ID also accept full ClickUp URLs (`https://app.clickup.com/t/<id>` or `https://app.clickup.com/t/<workspace>/<id>`). The ID is auto-extracted |
|
|
267
268
|
| `--status` | Fuzzy matching: exact > starts-with > contains. Prints match to stderr |
|
|
268
269
|
| `--priority` | Names (`urgent`, `high`, `normal`, `low`) or numbers (1-4) |
|
|
269
270
|
| `--due-date` | `YYYY-MM-DD` (date only), `YYYY-MM-DDTHH:MM` (with time), or full ISO 8601 with offset. Time-of-day formats set `due_date_time: true` in ClickUp |
|
|
@@ -288,6 +289,8 @@ All commands support `--help` for full flag details. All commands support `--jso
|
|
|
288
289
|
| `cup delete` | DESTRUCTIVE. Requires `--confirm` in non-interactive mode. Cannot be undone |
|
|
289
290
|
| Errors | stderr with exit code 1. Strict parsing - excess/unknown arguments rejected |
|
|
290
291
|
| Description quoting | Use `$'...'` quoting for `-d` / `-m` values containing backticks or newlines: `-d $'Use \`init()\` first.\n\n- Step 1'`. Heredocs and double quotes strip backticks |
|
|
292
|
+
| `--mention <user>` | Real ClickUp @mention (notifies the user). Accepts ID, email, username, or `me`. Repeatable. Also: `<@userId>` inline token in `-m` for mid-sentence mentions. Bare `@Name` is NOT parsed (ambiguous). On `comment`, `reply`, `comment-edit`, `list-comment`, `view-comment` |
|
|
293
|
+
| Comment links | In comment messages, bare URLs (`https://...`) and markdown links (`[text](url)`) both render as clickable links. Unfurled preview cards are not supported (web-UI only) |
|
|
291
294
|
|
|
292
295
|
> **Note:** `cup search`, `cup tasks`, `cup overdue`, and `cup assigned` default to tasks assigned to the current user. Use `--all` to include tasks assigned to others. This matters when searching for parent initiatives or team-wide items.
|
|
293
296
|
|