@krodak/clickup-cli 0.18.0 → 0.19.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 -0
- package/dist/index.js +57 -24
- package/package.json +1 -1
- package/skills/clickup-cli/SKILL.md +3 -1
|
@@ -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 cu/cup command",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.19.0",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Krzysztof Rodak"
|
|
7
7
|
},
|
package/README.md
CHANGED
|
@@ -307,6 +307,23 @@ Environment variables override config file values:
|
|
|
307
307
|
|
|
308
308
|
When both are set, the config file is not required. Useful for CI/CD and containerized agents.
|
|
309
309
|
|
|
310
|
+
## Custom Task IDs
|
|
311
|
+
|
|
312
|
+
ClickUp workspaces can configure custom task IDs with a prefix per space (e.g., `PROJ-123`, `DEV-42`). The CLI detects these automatically - any ID matching the `PREFIX-DIGITS` format (uppercase letters, hyphen, digits) is treated as a custom task ID.
|
|
313
|
+
|
|
314
|
+
All commands that accept task IDs work with both native IDs and custom IDs:
|
|
315
|
+
|
|
316
|
+
```bash
|
|
317
|
+
cu task PROJ-123
|
|
318
|
+
cu update DEV-42 --status done
|
|
319
|
+
cu comment PROJ-456 -m "Fixed in latest commit"
|
|
320
|
+
cu subtasks DEV-100
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
Custom ID resolution uses the `teamId` from your config, which is required (`cu init` sets it up).
|
|
324
|
+
|
|
325
|
+
**Task links with custom IDs:** The `cu link` command passes both task IDs in a single API request. When both IDs are custom, this works correctly. However, mixing custom and native IDs in a single link command may not work as expected because the ClickUp API applies the `custom_task_ids` flag to all IDs in the request.
|
|
326
|
+
|
|
310
327
|
## Why a CLI and not MCP?
|
|
311
328
|
|
|
312
329
|
A CLI + skill file has fewer moving parts. No server process, no protocol layer. The agent already knows how to run shell commands - the skill file teaches it which ones exist. For tool-use with coding agents, CLI + instructions tends to work better than MCP in practice.
|
package/dist/index.js
CHANGED
|
@@ -8,11 +8,30 @@ import { createRequire } from "module";
|
|
|
8
8
|
// src/api.ts
|
|
9
9
|
var BASE_URL = "https://api.clickup.com/api/v2";
|
|
10
10
|
var MAX_PAGES = 100;
|
|
11
|
+
function isCustomTaskId(id) {
|
|
12
|
+
return /^[A-Z]+-\d+$/i.test(id);
|
|
13
|
+
}
|
|
11
14
|
var ClickUpClient = class {
|
|
12
15
|
apiToken;
|
|
16
|
+
teamId;
|
|
13
17
|
meCache = null;
|
|
14
18
|
constructor(config) {
|
|
15
19
|
this.apiToken = config.apiToken;
|
|
20
|
+
this.teamId = config.teamId;
|
|
21
|
+
}
|
|
22
|
+
taskPath(taskId, suffix = "") {
|
|
23
|
+
const base = `/task/${taskId}${suffix}`;
|
|
24
|
+
if (isCustomTaskId(taskId) && this.teamId) {
|
|
25
|
+
const sep = base.includes("?") ? "&" : "?";
|
|
26
|
+
return `${base}${sep}custom_task_ids=true&team_id=${this.teamId}`;
|
|
27
|
+
}
|
|
28
|
+
return base;
|
|
29
|
+
}
|
|
30
|
+
customIdQueryParams(taskId) {
|
|
31
|
+
if (isCustomTaskId(taskId) && this.teamId) {
|
|
32
|
+
return `?custom_task_ids=true&team_id=${this.teamId}`;
|
|
33
|
+
}
|
|
34
|
+
return "";
|
|
16
35
|
}
|
|
17
36
|
async request(path, options = {}) {
|
|
18
37
|
const res = await fetch(`${BASE_URL}${path}`, {
|
|
@@ -82,19 +101,19 @@ var ClickUpClient = class {
|
|
|
82
101
|
});
|
|
83
102
|
}
|
|
84
103
|
async updateTask(taskId, options) {
|
|
85
|
-
return this.request(
|
|
104
|
+
return this.request(this.taskPath(taskId), {
|
|
86
105
|
method: "PUT",
|
|
87
106
|
body: JSON.stringify(options)
|
|
88
107
|
});
|
|
89
108
|
}
|
|
90
109
|
async postComment(taskId, commentText) {
|
|
91
|
-
return this.request(
|
|
110
|
+
return this.request(this.taskPath(taskId, "/comment"), {
|
|
92
111
|
method: "POST",
|
|
93
112
|
body: JSON.stringify({ comment_text: commentText })
|
|
94
113
|
});
|
|
95
114
|
}
|
|
96
115
|
async getTaskComments(taskId) {
|
|
97
|
-
const data = await this.request(
|
|
116
|
+
const data = await this.request(this.taskPath(taskId, "/comment"));
|
|
98
117
|
return data.comments ?? [];
|
|
99
118
|
}
|
|
100
119
|
async getTasksFromList(listId, params = {}, options = {}) {
|
|
@@ -106,7 +125,7 @@ var ClickUpClient = class {
|
|
|
106
125
|
});
|
|
107
126
|
}
|
|
108
127
|
async getTask(taskId) {
|
|
109
|
-
return this.request(
|
|
128
|
+
return this.request(this.taskPath(taskId, "?include_markdown_description=true"));
|
|
110
129
|
}
|
|
111
130
|
async createTask(listId, options) {
|
|
112
131
|
return this.request(`/list/${listId}/task`, {
|
|
@@ -163,28 +182,32 @@ var ClickUpClient = class {
|
|
|
163
182
|
await this.request(`/list/${listId}/task/${taskId}`, { method: "DELETE" });
|
|
164
183
|
}
|
|
165
184
|
async setCustomFieldValue(taskId, fieldId, value) {
|
|
166
|
-
await this.request(`/
|
|
185
|
+
await this.request(this.taskPath(taskId, `/field/${fieldId}`), {
|
|
167
186
|
method: "POST",
|
|
168
187
|
body: JSON.stringify({ value })
|
|
169
188
|
});
|
|
170
189
|
}
|
|
171
190
|
async removeCustomFieldValue(taskId, fieldId) {
|
|
172
|
-
await this.request(`/
|
|
191
|
+
await this.request(this.taskPath(taskId, `/field/${fieldId}`), { method: "DELETE" });
|
|
173
192
|
}
|
|
174
193
|
async deleteTask(taskId) {
|
|
175
|
-
await this.request(
|
|
194
|
+
await this.request(this.taskPath(taskId), { method: "DELETE" });
|
|
176
195
|
}
|
|
177
196
|
async addTagToTask(taskId, tagName) {
|
|
178
|
-
await this.request(`/
|
|
197
|
+
await this.request(this.taskPath(taskId, `/tag/${encodeURIComponent(tagName)}`), {
|
|
198
|
+
method: "POST"
|
|
199
|
+
});
|
|
179
200
|
}
|
|
180
201
|
async removeTagFromTask(taskId, tagName) {
|
|
181
|
-
await this.request(`/
|
|
202
|
+
await this.request(this.taskPath(taskId, `/tag/${encodeURIComponent(tagName)}`), {
|
|
203
|
+
method: "DELETE"
|
|
204
|
+
});
|
|
182
205
|
}
|
|
183
206
|
async addDependency(taskId, opts) {
|
|
184
207
|
const body = {};
|
|
185
208
|
if (opts.dependsOn) body.depends_on = opts.dependsOn;
|
|
186
209
|
if (opts.dependencyOf) body.dependency_of = opts.dependencyOf;
|
|
187
|
-
await this.request(
|
|
210
|
+
await this.request(this.taskPath(taskId, "/dependency"), {
|
|
188
211
|
method: "POST",
|
|
189
212
|
body: JSON.stringify(body)
|
|
190
213
|
});
|
|
@@ -193,7 +216,7 @@ var ClickUpClient = class {
|
|
|
193
216
|
const params = new URLSearchParams();
|
|
194
217
|
if (opts.dependsOn) params.set("depends_on", opts.dependsOn);
|
|
195
218
|
if (opts.dependencyOf) params.set("dependency_of", opts.dependencyOf);
|
|
196
|
-
await this.request(`/
|
|
219
|
+
await this.request(this.taskPath(taskId, `/dependency?${params.toString()}`), {
|
|
197
220
|
method: "DELETE"
|
|
198
221
|
});
|
|
199
222
|
}
|
|
@@ -219,17 +242,21 @@ var ClickUpClient = class {
|
|
|
219
242
|
});
|
|
220
243
|
}
|
|
221
244
|
async addTaskLink(taskId, linksTo) {
|
|
222
|
-
await this.request(`/
|
|
245
|
+
await this.request(this.taskPath(taskId, `/link/${linksTo}`), {
|
|
246
|
+
method: "POST"
|
|
247
|
+
});
|
|
223
248
|
}
|
|
224
249
|
async deleteTaskLink(taskId, linksTo) {
|
|
225
|
-
await this.request(`/
|
|
250
|
+
await this.request(this.taskPath(taskId, `/link/${linksTo}`), {
|
|
251
|
+
method: "DELETE"
|
|
252
|
+
});
|
|
226
253
|
}
|
|
227
254
|
async getListCustomFields(listId) {
|
|
228
255
|
const data = await this.request(`/list/${listId}/field`);
|
|
229
256
|
return data.fields ?? [];
|
|
230
257
|
}
|
|
231
258
|
async createChecklist(taskId, name) {
|
|
232
|
-
const data = await this.request(
|
|
259
|
+
const data = await this.request(this.taskPath(taskId, "/checklist"), {
|
|
233
260
|
method: "POST",
|
|
234
261
|
body: JSON.stringify({ name })
|
|
235
262
|
});
|
|
@@ -265,10 +292,13 @@ var ClickUpClient = class {
|
|
|
265
292
|
duration: -1
|
|
266
293
|
};
|
|
267
294
|
if (description) body.description = description;
|
|
268
|
-
const data = await this.request(
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
295
|
+
const data = await this.request(
|
|
296
|
+
`/team/${teamId}/time_entries/start${this.customIdQueryParams(taskId)}`,
|
|
297
|
+
{
|
|
298
|
+
method: "POST",
|
|
299
|
+
body: JSON.stringify(body)
|
|
300
|
+
}
|
|
301
|
+
);
|
|
272
302
|
return data.data;
|
|
273
303
|
}
|
|
274
304
|
async stopTimeEntry(teamId) {
|
|
@@ -291,10 +321,13 @@ var ClickUpClient = class {
|
|
|
291
321
|
duration
|
|
292
322
|
};
|
|
293
323
|
if (opts?.description) body.description = opts.description;
|
|
294
|
-
const data = await this.request(
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
324
|
+
const data = await this.request(
|
|
325
|
+
`/team/${teamId}/time_entries${this.customIdQueryParams(taskId)}`,
|
|
326
|
+
{
|
|
327
|
+
method: "POST",
|
|
328
|
+
body: JSON.stringify(body)
|
|
329
|
+
}
|
|
330
|
+
);
|
|
298
331
|
return data.data;
|
|
299
332
|
}
|
|
300
333
|
async getTimeEntries(teamId, opts) {
|
|
@@ -322,7 +355,7 @@ var ClickUpClient = class {
|
|
|
322
355
|
const fileName = basename2(filePath);
|
|
323
356
|
const formData = new FormData();
|
|
324
357
|
formData.append("attachment", new Blob([fileBuffer]), fileName);
|
|
325
|
-
const res = await fetch(`${BASE_URL}
|
|
358
|
+
const res = await fetch(`${BASE_URL}${this.taskPath(taskId, "/attachment")}`, {
|
|
326
359
|
method: "POST",
|
|
327
360
|
headers: { Authorization: this.apiToken },
|
|
328
361
|
body: formData,
|
|
@@ -1598,7 +1631,7 @@ async function runAssignedCommand(config, opts) {
|
|
|
1598
1631
|
|
|
1599
1632
|
// src/commands/open.ts
|
|
1600
1633
|
function looksLikeTaskId(query) {
|
|
1601
|
-
return /^[a-z0-9]+$/i.test(query) && query.length <= 12;
|
|
1634
|
+
return /^[a-z0-9]+$/i.test(query) && query.length <= 12 || isCustomTaskId(query);
|
|
1602
1635
|
}
|
|
1603
1636
|
async function openTask(config, query, opts = {}) {
|
|
1604
1637
|
const client = new ClickUpClient(config);
|
package/package.json
CHANGED
|
@@ -94,7 +94,7 @@ All commands support `--help` for full flag details.
|
|
|
94
94
|
|
|
95
95
|
| Topic | Detail |
|
|
96
96
|
| ------------------------- | ------------------------------------------------------------------------------------------------------ |
|
|
97
|
-
| Task IDs |
|
|
97
|
+
| Task IDs | Native (`abc123def`) or custom (`PROJ-123`). Custom IDs auto-detected by `PREFIX-DIGITS` format |
|
|
98
98
|
| `--type` | Filter by task type: `task` (regular), or custom type name/ID (e.g. `initiative`, `Bug`) |
|
|
99
99
|
| `--list` on create | Optional when `--parent` is given (auto-detected) |
|
|
100
100
|
| `--status` | Fuzzy matching: exact > starts-with > contains. Prints match to stderr. |
|
|
@@ -124,6 +124,8 @@ All commands support `--help` for full flag details.
|
|
|
124
124
|
| `cu comment-edit` | Edit comment text and resolution status |
|
|
125
125
|
| `cu comment-delete` | Delete a comment |
|
|
126
126
|
| `cu replies` / `cu reply` | View and post threaded comment replies |
|
|
127
|
+
| Custom task IDs | Auto-detected by format (`PROJ-123`). Uses `teamId` from config. All commands support them |
|
|
128
|
+
| `cu link` + custom IDs | Both IDs must be the same type (both custom or both native). Mixing may not work |
|
|
127
129
|
| `cu link` | Link/unlink tasks (different from dependencies) |
|
|
128
130
|
| `cu attach` | Upload files to tasks. Attachments shown in `cu task` detail view |
|
|
129
131
|
| `cu task` | Shows custom fields, checklists, and attachments in detail view |
|