@krodak/clickup-cli 1.31.1 → 1.32.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/dist/index.js +60 -26
- package/package.json +2 -2
- package/skills/clickup-cli/SKILL.md +3 -2
|
@@ -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.32.0",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Krzysztof Rodak"
|
|
7
7
|
},
|
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
|
});
|
|
@@ -1792,6 +1803,12 @@ function stringifyFieldValue(value) {
|
|
|
1792
1803
|
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
1793
1804
|
return JSON.stringify(value);
|
|
1794
1805
|
}
|
|
1806
|
+
function optionDisplayName(option) {
|
|
1807
|
+
return option.label ?? option.name;
|
|
1808
|
+
}
|
|
1809
|
+
function dropdownOptionName(option) {
|
|
1810
|
+
return option.name ?? option.label;
|
|
1811
|
+
}
|
|
1795
1812
|
function formatCustomFieldValue(field) {
|
|
1796
1813
|
if (field.value === null || field.value === void 0) return null;
|
|
1797
1814
|
const options = field.type_config?.options;
|
|
@@ -1799,11 +1816,14 @@ function formatCustomFieldValue(field) {
|
|
|
1799
1816
|
case "drop_down": {
|
|
1800
1817
|
if (!options) return stringifyFieldValue(field.value);
|
|
1801
1818
|
const match = options.find((o) => o.orderindex === Number(field.value));
|
|
1802
|
-
return match
|
|
1819
|
+
return match ? dropdownOptionName(match) ?? stringifyFieldValue(field.value) : stringifyFieldValue(field.value);
|
|
1803
1820
|
}
|
|
1804
1821
|
case "labels": {
|
|
1805
1822
|
if (!Array.isArray(field.value) || !options) return stringifyFieldValue(field.value);
|
|
1806
|
-
const names = field.value.map((id) =>
|
|
1823
|
+
const names = field.value.map((id) => {
|
|
1824
|
+
const match = options.find((o) => o.id === id);
|
|
1825
|
+
return match ? optionDisplayName(match) : void 0;
|
|
1826
|
+
}).filter((n) => n !== void 0);
|
|
1807
1827
|
return names.length > 0 ? names.join(", ") : null;
|
|
1808
1828
|
}
|
|
1809
1829
|
case "date": {
|
|
@@ -6330,6 +6350,15 @@ var SUPPORTED_TYPES = /* @__PURE__ */ new Set([
|
|
|
6330
6350
|
"tasks",
|
|
6331
6351
|
"users"
|
|
6332
6352
|
]);
|
|
6353
|
+
function labelOptionName(option) {
|
|
6354
|
+
return option.label ?? option.name;
|
|
6355
|
+
}
|
|
6356
|
+
function dropdownOptionName2(option) {
|
|
6357
|
+
return option.name ?? option.label;
|
|
6358
|
+
}
|
|
6359
|
+
function availableOptionNames(options, getOptionName) {
|
|
6360
|
+
return options.map((option) => getOptionName(option) ?? option.id).join(", ");
|
|
6361
|
+
}
|
|
6333
6362
|
function findFieldByName(fields, name) {
|
|
6334
6363
|
const lower = name.toLowerCase();
|
|
6335
6364
|
const match = fields.find((f) => f.name.toLowerCase() === lower);
|
|
@@ -6360,13 +6389,15 @@ function parseFieldValue(field, rawValue) {
|
|
|
6360
6389
|
const options = field.type_config?.options;
|
|
6361
6390
|
if (!options?.length) throw new Error("Dropdown field has no configured options");
|
|
6362
6391
|
const lower = rawValue.toLowerCase();
|
|
6363
|
-
const option = options.find((o) => o
|
|
6392
|
+
const option = options.find((o) => dropdownOptionName2(o)?.toLowerCase() === lower);
|
|
6364
6393
|
if (!option) {
|
|
6365
|
-
const available = options
|
|
6394
|
+
const available = availableOptionNames(options, dropdownOptionName2);
|
|
6366
6395
|
throw new Error(`Option "${rawValue}" not found. Available options: ${available}`);
|
|
6367
6396
|
}
|
|
6368
6397
|
if (option.orderindex === void 0) {
|
|
6369
|
-
throw new Error(
|
|
6398
|
+
throw new Error(
|
|
6399
|
+
`Dropdown option "${dropdownOptionName2(option) ?? option.id}" has no orderindex`
|
|
6400
|
+
);
|
|
6370
6401
|
}
|
|
6371
6402
|
return option.orderindex;
|
|
6372
6403
|
}
|
|
@@ -6378,9 +6409,9 @@ function parseFieldValue(field, rawValue) {
|
|
|
6378
6409
|
const ids = [];
|
|
6379
6410
|
for (const name of names) {
|
|
6380
6411
|
const lower = name.toLowerCase();
|
|
6381
|
-
const option = options.find((o) => o
|
|
6412
|
+
const option = options.find((o) => labelOptionName(o)?.toLowerCase() === lower);
|
|
6382
6413
|
if (!option) {
|
|
6383
|
-
const available = options
|
|
6414
|
+
const available = availableOptionNames(options, labelOptionName);
|
|
6384
6415
|
throw new Error(`Label "${name}" not found. Available: ${available}`);
|
|
6385
6416
|
}
|
|
6386
6417
|
ids.push(option.id);
|
|
@@ -7223,6 +7254,9 @@ var FIELD_COLUMNS = [
|
|
|
7223
7254
|
},
|
|
7224
7255
|
{ key: "options", label: "Options", maxWidth: 40 }
|
|
7225
7256
|
];
|
|
7257
|
+
function optionDisplayName2(option) {
|
|
7258
|
+
return option.label ?? option.name ?? option.id;
|
|
7259
|
+
}
|
|
7226
7260
|
async function listFields(config, listId) {
|
|
7227
7261
|
const client = new ClickUpClient(config);
|
|
7228
7262
|
return client.getListCustomFields(listId);
|
|
@@ -7234,14 +7268,14 @@ function formatFields(fields) {
|
|
|
7234
7268
|
name: f.name,
|
|
7235
7269
|
type: f.type,
|
|
7236
7270
|
required: f.required ? "yes" : "no",
|
|
7237
|
-
options: f.type_config?.options?.map(
|
|
7271
|
+
options: f.type_config?.options?.map(optionDisplayName2).join(", ") ?? ""
|
|
7238
7272
|
}));
|
|
7239
7273
|
return formatTable(rows, FIELD_COLUMNS);
|
|
7240
7274
|
}
|
|
7241
7275
|
function formatFieldsMarkdown(fields) {
|
|
7242
7276
|
if (fields.length === 0) return "No custom fields";
|
|
7243
7277
|
return fields.map((f) => {
|
|
7244
|
-
const options = f.type_config?.options?.map(
|
|
7278
|
+
const options = f.type_config?.options?.map(optionDisplayName2).join(", ");
|
|
7245
7279
|
const optStr = options ? ` [${options}]` : "";
|
|
7246
7280
|
return `- **${f.name}** \`${f.id}\` (${f.type})${f.required ? " - required" : ""}${optStr}`;
|
|
7247
7281
|
}).join("\n");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@krodak/clickup-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.32.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.32.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.32.0, update with `npm install -g @krodak/clickup-cli` and refresh this skill with `cup skill`.
|
|
11
11
|
|
|
12
12
|
## Install & Configure
|
|
13
13
|
|
|
@@ -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 |
|