@krodak/clickup-cli 0.18.0 → 0.20.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 +127 -52
- package/package.json +1 -1
- package/skills/clickup-cli/SKILL.md +6 -4
|
@@ -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.20.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,21 @@ 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
|
-
async postComment(taskId, commentText) {
|
|
91
|
-
|
|
109
|
+
async postComment(taskId, commentText, notifyAll) {
|
|
110
|
+
const body = { comment_text: commentText };
|
|
111
|
+
if (notifyAll) body.notify_all = true;
|
|
112
|
+
return this.request(this.taskPath(taskId, "/comment"), {
|
|
92
113
|
method: "POST",
|
|
93
|
-
body: JSON.stringify(
|
|
114
|
+
body: JSON.stringify(body)
|
|
94
115
|
});
|
|
95
116
|
}
|
|
96
117
|
async getTaskComments(taskId) {
|
|
97
|
-
const data = await this.request(
|
|
118
|
+
const data = await this.request(this.taskPath(taskId, "/comment"));
|
|
98
119
|
return data.comments ?? [];
|
|
99
120
|
}
|
|
100
121
|
async getTasksFromList(listId, params = {}, options = {}) {
|
|
@@ -106,7 +127,7 @@ var ClickUpClient = class {
|
|
|
106
127
|
});
|
|
107
128
|
}
|
|
108
129
|
async getTask(taskId) {
|
|
109
|
-
return this.request(
|
|
130
|
+
return this.request(this.taskPath(taskId, "?include_markdown_description=true"));
|
|
110
131
|
}
|
|
111
132
|
async createTask(listId, options) {
|
|
112
133
|
return this.request(`/list/${listId}/task`, {
|
|
@@ -163,28 +184,32 @@ var ClickUpClient = class {
|
|
|
163
184
|
await this.request(`/list/${listId}/task/${taskId}`, { method: "DELETE" });
|
|
164
185
|
}
|
|
165
186
|
async setCustomFieldValue(taskId, fieldId, value) {
|
|
166
|
-
await this.request(`/
|
|
187
|
+
await this.request(this.taskPath(taskId, `/field/${fieldId}`), {
|
|
167
188
|
method: "POST",
|
|
168
189
|
body: JSON.stringify({ value })
|
|
169
190
|
});
|
|
170
191
|
}
|
|
171
192
|
async removeCustomFieldValue(taskId, fieldId) {
|
|
172
|
-
await this.request(`/
|
|
193
|
+
await this.request(this.taskPath(taskId, `/field/${fieldId}`), { method: "DELETE" });
|
|
173
194
|
}
|
|
174
195
|
async deleteTask(taskId) {
|
|
175
|
-
await this.request(
|
|
196
|
+
await this.request(this.taskPath(taskId), { method: "DELETE" });
|
|
176
197
|
}
|
|
177
198
|
async addTagToTask(taskId, tagName) {
|
|
178
|
-
await this.request(`/
|
|
199
|
+
await this.request(this.taskPath(taskId, `/tag/${encodeURIComponent(tagName)}`), {
|
|
200
|
+
method: "POST"
|
|
201
|
+
});
|
|
179
202
|
}
|
|
180
203
|
async removeTagFromTask(taskId, tagName) {
|
|
181
|
-
await this.request(`/
|
|
204
|
+
await this.request(this.taskPath(taskId, `/tag/${encodeURIComponent(tagName)}`), {
|
|
205
|
+
method: "DELETE"
|
|
206
|
+
});
|
|
182
207
|
}
|
|
183
208
|
async addDependency(taskId, opts) {
|
|
184
209
|
const body = {};
|
|
185
210
|
if (opts.dependsOn) body.depends_on = opts.dependsOn;
|
|
186
211
|
if (opts.dependencyOf) body.dependency_of = opts.dependencyOf;
|
|
187
|
-
await this.request(
|
|
212
|
+
await this.request(this.taskPath(taskId, "/dependency"), {
|
|
188
213
|
method: "POST",
|
|
189
214
|
body: JSON.stringify(body)
|
|
190
215
|
});
|
|
@@ -193,7 +218,7 @@ var ClickUpClient = class {
|
|
|
193
218
|
const params = new URLSearchParams();
|
|
194
219
|
if (opts.dependsOn) params.set("depends_on", opts.dependsOn);
|
|
195
220
|
if (opts.dependencyOf) params.set("dependency_of", opts.dependencyOf);
|
|
196
|
-
await this.request(`/
|
|
221
|
+
await this.request(this.taskPath(taskId, `/dependency?${params.toString()}`), {
|
|
197
222
|
method: "DELETE"
|
|
198
223
|
});
|
|
199
224
|
}
|
|
@@ -212,24 +237,30 @@ var ClickUpClient = class {
|
|
|
212
237
|
const data = await this.request(`/comment/${commentId}/reply`);
|
|
213
238
|
return data.comments ?? [];
|
|
214
239
|
}
|
|
215
|
-
async createThreadedComment(commentId, text) {
|
|
240
|
+
async createThreadedComment(commentId, text, notifyAll) {
|
|
241
|
+
const body = { comment_text: text };
|
|
242
|
+
if (notifyAll) body.notify_all = true;
|
|
216
243
|
await this.request(`/comment/${commentId}/reply`, {
|
|
217
244
|
method: "POST",
|
|
218
|
-
body: JSON.stringify(
|
|
245
|
+
body: JSON.stringify(body)
|
|
219
246
|
});
|
|
220
247
|
}
|
|
221
248
|
async addTaskLink(taskId, linksTo) {
|
|
222
|
-
await this.request(`/
|
|
249
|
+
await this.request(this.taskPath(taskId, `/link/${linksTo}`), {
|
|
250
|
+
method: "POST"
|
|
251
|
+
});
|
|
223
252
|
}
|
|
224
253
|
async deleteTaskLink(taskId, linksTo) {
|
|
225
|
-
await this.request(`/
|
|
254
|
+
await this.request(this.taskPath(taskId, `/link/${linksTo}`), {
|
|
255
|
+
method: "DELETE"
|
|
256
|
+
});
|
|
226
257
|
}
|
|
227
258
|
async getListCustomFields(listId) {
|
|
228
259
|
const data = await this.request(`/list/${listId}/field`);
|
|
229
260
|
return data.fields ?? [];
|
|
230
261
|
}
|
|
231
262
|
async createChecklist(taskId, name) {
|
|
232
|
-
const data = await this.request(
|
|
263
|
+
const data = await this.request(this.taskPath(taskId, "/checklist"), {
|
|
233
264
|
method: "POST",
|
|
234
265
|
body: JSON.stringify({ name })
|
|
235
266
|
});
|
|
@@ -265,10 +296,13 @@ var ClickUpClient = class {
|
|
|
265
296
|
duration: -1
|
|
266
297
|
};
|
|
267
298
|
if (description) body.description = description;
|
|
268
|
-
const data = await this.request(
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
299
|
+
const data = await this.request(
|
|
300
|
+
`/team/${teamId}/time_entries/start${this.customIdQueryParams(taskId)}`,
|
|
301
|
+
{
|
|
302
|
+
method: "POST",
|
|
303
|
+
body: JSON.stringify(body)
|
|
304
|
+
}
|
|
305
|
+
);
|
|
272
306
|
return data.data;
|
|
273
307
|
}
|
|
274
308
|
async stopTimeEntry(teamId) {
|
|
@@ -291,10 +325,13 @@ var ClickUpClient = class {
|
|
|
291
325
|
duration
|
|
292
326
|
};
|
|
293
327
|
if (opts?.description) body.description = opts.description;
|
|
294
|
-
const data = await this.request(
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
328
|
+
const data = await this.request(
|
|
329
|
+
`/team/${teamId}/time_entries${this.customIdQueryParams(taskId)}`,
|
|
330
|
+
{
|
|
331
|
+
method: "POST",
|
|
332
|
+
body: JSON.stringify(body)
|
|
333
|
+
}
|
|
334
|
+
);
|
|
298
335
|
return data.data;
|
|
299
336
|
}
|
|
300
337
|
async getTimeEntries(teamId, opts) {
|
|
@@ -322,7 +359,7 @@ var ClickUpClient = class {
|
|
|
322
359
|
const fileName = basename2(filePath);
|
|
323
360
|
const formData = new FormData();
|
|
324
361
|
formData.append("attachment", new Blob([fileBuffer]), fileName);
|
|
325
|
-
const res = await fetch(`${BASE_URL}
|
|
362
|
+
const res = await fetch(`${BASE_URL}${this.taskPath(taskId, "/attachment")}`, {
|
|
326
363
|
method: "POST",
|
|
327
364
|
headers: { Authorization: this.apiToken },
|
|
328
365
|
body: formData,
|
|
@@ -584,6 +621,20 @@ function formatTaskDetailMarkdown(task) {
|
|
|
584
621
|
lines.push(`- [${att.title}](${att.url})`);
|
|
585
622
|
}
|
|
586
623
|
}
|
|
624
|
+
if (task.dependencies?.length) {
|
|
625
|
+
lines.push("", "## Dependencies", "");
|
|
626
|
+
for (const dep of task.dependencies) {
|
|
627
|
+
const direction = dep.depends_on === task.id ? "blocks" : "depends on";
|
|
628
|
+
const otherId = dep.depends_on === task.id ? dep.task_id : dep.depends_on;
|
|
629
|
+
lines.push(`- ${direction} ${otherId}`);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
if (task.linked_tasks?.length) {
|
|
633
|
+
lines.push("", "## Linked Tasks", "");
|
|
634
|
+
for (const lt of task.linked_tasks) {
|
|
635
|
+
lines.push(`- ${lt.task_id}`);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
587
638
|
return lines.join("\n");
|
|
588
639
|
}
|
|
589
640
|
function formatUpdateConfirmation(id, name) {
|
|
@@ -737,6 +788,22 @@ function formatTaskDetail(task) {
|
|
|
737
788
|
lines.push(` ${att.title} ${chalk2.dim(att.url)}`);
|
|
738
789
|
}
|
|
739
790
|
}
|
|
791
|
+
if (task.dependencies?.length) {
|
|
792
|
+
lines.push("");
|
|
793
|
+
lines.push(chalk2.bold("Dependencies"));
|
|
794
|
+
for (const dep of task.dependencies) {
|
|
795
|
+
const direction = dep.depends_on === task.id ? "blocks" : "depends on";
|
|
796
|
+
const otherId = dep.depends_on === task.id ? dep.task_id : dep.depends_on;
|
|
797
|
+
lines.push(` ${direction} ${chalk2.dim(otherId)}`);
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
if (task.linked_tasks?.length) {
|
|
801
|
+
lines.push("");
|
|
802
|
+
lines.push(chalk2.bold("Linked Tasks"));
|
|
803
|
+
for (const lt of task.linked_tasks) {
|
|
804
|
+
lines.push(` ${chalk2.dim(lt.task_id)}`);
|
|
805
|
+
}
|
|
806
|
+
}
|
|
740
807
|
if (task.text_content?.trim()) {
|
|
741
808
|
lines.push("");
|
|
742
809
|
lines.push(descriptionPreview(task.text_content));
|
|
@@ -1304,10 +1371,10 @@ async function fetchSubtasks(config, taskId, options = {}) {
|
|
|
1304
1371
|
}
|
|
1305
1372
|
|
|
1306
1373
|
// src/commands/comment.ts
|
|
1307
|
-
async function postComment(config, taskId, text) {
|
|
1374
|
+
async function postComment(config, taskId, text, notifyAll) {
|
|
1308
1375
|
if (!text.trim()) throw new Error("Comment text cannot be empty");
|
|
1309
1376
|
const client = new ClickUpClient(config);
|
|
1310
|
-
return client.postComment(taskId, text);
|
|
1377
|
+
return client.postComment(taskId, text, notifyAll);
|
|
1311
1378
|
}
|
|
1312
1379
|
|
|
1313
1380
|
// src/commands/comments.ts
|
|
@@ -1598,7 +1665,7 @@ async function runAssignedCommand(config, opts) {
|
|
|
1598
1665
|
|
|
1599
1666
|
// src/commands/open.ts
|
|
1600
1667
|
function looksLikeTaskId(query) {
|
|
1601
|
-
return /^[a-z0-9]+$/i.test(query) && query.length <= 12;
|
|
1668
|
+
return /^[a-z0-9]+$/i.test(query) && query.length <= 12 || isCustomTaskId(query);
|
|
1602
1669
|
}
|
|
1603
1670
|
async function openTask(config, query, opts = {}) {
|
|
1604
1671
|
const client = new ClickUpClient(config);
|
|
@@ -1915,7 +1982,7 @@ function bashCompletion(name) {
|
|
|
1915
1982
|
COMPREPLY=($(compgen -W "--status --name --include-closed --json" -- "$cur"))
|
|
1916
1983
|
;;
|
|
1917
1984
|
comment)
|
|
1918
|
-
COMPREPLY=($(compgen -W "-m --message --json" -- "$cur"))
|
|
1985
|
+
COMPREPLY=($(compgen -W "-m --message --notify-all --json" -- "$cur"))
|
|
1919
1986
|
;;
|
|
1920
1987
|
comments)
|
|
1921
1988
|
COMPREPLY=($(compgen -W "--json" -- "$cur"))
|
|
@@ -1988,7 +2055,7 @@ function bashCompletion(name) {
|
|
|
1988
2055
|
COMPREPLY=($(compgen -W "--json" -- "$cur"))
|
|
1989
2056
|
;;
|
|
1990
2057
|
reply)
|
|
1991
|
-
COMPREPLY=($(compgen -W "-m --message --json" -- "$cur"))
|
|
2058
|
+
COMPREPLY=($(compgen -W "-m --message --notify-all --json" -- "$cur"))
|
|
1992
2059
|
;;
|
|
1993
2060
|
link)
|
|
1994
2061
|
COMPREPLY=($(compgen -W "--remove --json" -- "$cur"))
|
|
@@ -2139,6 +2206,7 @@ _${name}() {
|
|
|
2139
2206
|
_arguments \\
|
|
2140
2207
|
'1:task_id:' \\
|
|
2141
2208
|
'(-m --message)'{-m,--message}'[Comment text]:text:' \\
|
|
2209
|
+
'--notify-all[Notify all assignees]' \\
|
|
2142
2210
|
'--json[Force JSON output]'
|
|
2143
2211
|
;;
|
|
2144
2212
|
comments)
|
|
@@ -2360,6 +2428,7 @@ _${name}() {
|
|
|
2360
2428
|
_arguments \\
|
|
2361
2429
|
'1:comment_id:' \\
|
|
2362
2430
|
'(-m --message)'{-m,--message}'[Reply text]:text:' \\
|
|
2431
|
+
'--notify-all[Notify all assignees]' \\
|
|
2363
2432
|
'--json[Force JSON output]'
|
|
2364
2433
|
;;
|
|
2365
2434
|
link)
|
|
@@ -2501,6 +2570,7 @@ complete -c ${name} -n '__fish_seen_subcommand_from subtasks' -l include-closed
|
|
|
2501
2570
|
complete -c ${name} -n '__fish_seen_subcommand_from subtasks' -l json -d 'Force JSON output'
|
|
2502
2571
|
|
|
2503
2572
|
complete -c ${name} -n '__fish_seen_subcommand_from comment' -s m -l message -d 'Comment text'
|
|
2573
|
+
complete -c ${name} -n '__fish_seen_subcommand_from comment' -l notify-all -d 'Notify all assignees'
|
|
2504
2574
|
complete -c ${name} -n '__fish_seen_subcommand_from comment' -l json -d 'Force JSON output'
|
|
2505
2575
|
|
|
2506
2576
|
complete -c ${name} -n '__fish_seen_subcommand_from comments' -l json -d 'Force JSON output'
|
|
@@ -2586,6 +2656,7 @@ complete -c ${name} -n '__fish_seen_subcommand_from comment-delete' -l json -d '
|
|
|
2586
2656
|
complete -c ${name} -n '__fish_seen_subcommand_from replies' -l json -d 'Force JSON output'
|
|
2587
2657
|
|
|
2588
2658
|
complete -c ${name} -n '__fish_seen_subcommand_from reply' -s m -l message -d 'Reply text'
|
|
2659
|
+
complete -c ${name} -n '__fish_seen_subcommand_from reply' -l notify-all -d 'Notify all assignees'
|
|
2589
2660
|
complete -c ${name} -n '__fish_seen_subcommand_from reply' -l json -d 'Force JSON output'
|
|
2590
2661
|
|
|
2591
2662
|
complete -c ${name} -n '__fish_seen_subcommand_from link' -l remove -d 'Remove the link'
|
|
@@ -2918,10 +2989,10 @@ async function getReplies(config, commentId) {
|
|
|
2918
2989
|
const client = new ClickUpClient(config);
|
|
2919
2990
|
return client.getThreadedComments(commentId);
|
|
2920
2991
|
}
|
|
2921
|
-
async function createReply(config, commentId, text) {
|
|
2992
|
+
async function createReply(config, commentId, text, notifyAll) {
|
|
2922
2993
|
if (!text.trim()) throw new Error("Reply text cannot be empty");
|
|
2923
2994
|
const client = new ClickUpClient(config);
|
|
2924
|
-
await client.createThreadedComment(commentId, text);
|
|
2995
|
+
await client.createThreadedComment(commentId, text, notifyAll);
|
|
2925
2996
|
}
|
|
2926
2997
|
function formatReplies(replies) {
|
|
2927
2998
|
if (replies.length === 0) return "No replies";
|
|
@@ -3145,16 +3216,18 @@ program.command("subtasks <taskId>").description("List subtasks of a task or ini
|
|
|
3145
3216
|
}
|
|
3146
3217
|
)
|
|
3147
3218
|
);
|
|
3148
|
-
program.command("comment <taskId>").description("Post a comment on a task").requiredOption("-m, --message <text>", "Comment text").option("--json", "Force JSON output even in terminal").action(
|
|
3149
|
-
wrapAction(
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3219
|
+
program.command("comment <taskId>").description("Post a comment on a task").requiredOption("-m, --message <text>", "Comment text").option("--notify-all", "Notify all assignees").option("--json", "Force JSON output even in terminal").action(
|
|
3220
|
+
wrapAction(
|
|
3221
|
+
async (taskId, opts) => {
|
|
3222
|
+
const config = loadConfig();
|
|
3223
|
+
const result = await postComment(config, taskId, opts.message, opts.notifyAll);
|
|
3224
|
+
if (shouldOutputJson(opts.json ?? false)) {
|
|
3225
|
+
console.log(JSON.stringify(result, null, 2));
|
|
3226
|
+
} else {
|
|
3227
|
+
console.log(formatCommentConfirmation(result.id));
|
|
3228
|
+
}
|
|
3156
3229
|
}
|
|
3157
|
-
|
|
3230
|
+
)
|
|
3158
3231
|
);
|
|
3159
3232
|
program.command("comments <taskId>").description("List comments on a task").option("--json", "Force JSON output even in terminal").action(
|
|
3160
3233
|
wrapAction(async (taskId, opts) => {
|
|
@@ -3201,16 +3274,18 @@ program.command("replies <commentId>").description("List threaded replies on a c
|
|
|
3201
3274
|
}
|
|
3202
3275
|
})
|
|
3203
3276
|
);
|
|
3204
|
-
program.command("reply <commentId>").description("Reply to a comment").requiredOption("-m, --message <text>", "Reply text").option("--json", "Force JSON output even in terminal").action(
|
|
3205
|
-
wrapAction(
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3277
|
+
program.command("reply <commentId>").description("Reply to a comment").requiredOption("-m, --message <text>", "Reply text").option("--notify-all", "Notify all assignees").option("--json", "Force JSON output even in terminal").action(
|
|
3278
|
+
wrapAction(
|
|
3279
|
+
async (commentId, opts) => {
|
|
3280
|
+
const config = loadConfig();
|
|
3281
|
+
await createReply(config, commentId, opts.message, opts.notifyAll);
|
|
3282
|
+
if (shouldOutputJson(opts.json ?? false)) {
|
|
3283
|
+
console.log(JSON.stringify({ success: true, commentId }, null, 2));
|
|
3284
|
+
} else {
|
|
3285
|
+
console.log(`Replied to comment ${commentId}`);
|
|
3286
|
+
}
|
|
3212
3287
|
}
|
|
3213
|
-
|
|
3288
|
+
)
|
|
3214
3289
|
);
|
|
3215
3290
|
program.command("activity <taskId>").description("Show task details and comments combined").option("--json", "Force JSON output even in terminal").action(
|
|
3216
3291
|
wrapAction(async (taskId, opts) => {
|
package/package.json
CHANGED
|
@@ -63,7 +63,7 @@ All commands support `--help` for full flag details.
|
|
|
63
63
|
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- |
|
|
64
64
|
| `cu update <id> [-n name] [-d desc] [-s status] [--priority p] [--due-date d] [--time-estimate t] [--assignee id\|me] [--parent id] [--json]` | Update task fields (desc supports markdown) |
|
|
65
65
|
| `cu create -n name [-l listId] [-p parentId] [-d desc] [-s status] [--priority p] [--due-date d] [--time-estimate t] [--assignee id\|me] [--tags t] [--custom-item-id n] [--json]` | Create task (desc supports markdown) |
|
|
66
|
-
| `cu comment <id> -m text [--json]`
|
|
66
|
+
| `cu comment <id> -m text [--notify-all] [--json]` | Post comment on task |
|
|
67
67
|
| `cu comment-edit <commentId> -m text [--resolved] [--unresolved] [--json]` | Edit an existing comment |
|
|
68
68
|
| `cu assign <id> [--to userId\|me] [--remove userId\|me] [--json]` | Assign/unassign users |
|
|
69
69
|
| `cu depend <id> [--on taskId] [--blocks taskId] [--remove] [--json]` | Add/remove task dependencies |
|
|
@@ -79,7 +79,7 @@ All commands support `--help` for full flag details.
|
|
|
79
79
|
| `cu checklist delete-item <checklistId> <itemId> [--json]` | Delete a checklist item |
|
|
80
80
|
| `cu comment-delete <commentId> [--json]` | Delete a comment |
|
|
81
81
|
| `cu replies <commentId> [--json]` | List threaded replies on a comment |
|
|
82
|
-
| `cu reply <commentId> -m text [--json]`
|
|
82
|
+
| `cu reply <commentId> -m text [--notify-all] [--json]` | Reply to a comment |
|
|
83
83
|
| `cu link <taskId> <linksTo> [--remove] [--json]` | Add or remove link between tasks |
|
|
84
84
|
| `cu attach <taskId> <filePath> [--json]` | Upload file attachment to a task |
|
|
85
85
|
| `cu time start <taskId> [-d desc] [--json]` | Start tracking time on a task |
|
|
@@ -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,9 +124,11 @@ 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
|
-
| `cu task` | Shows custom fields, checklists, and
|
|
131
|
+
| `cu task` | Shows custom fields, checklists, attachments, dependencies, and linked tasks in detail view |
|
|
130
132
|
| `cu lists` | Discovers list IDs needed for `--list` and `cu create -l` |
|
|
131
133
|
| Errors | stderr with exit code 1 |
|
|
132
134
|
| Parsing | Strict - excess/unknown arguments rejected |
|