@krodak/clickup-cli 1.29.0 → 1.30.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.
@@ -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.29.0",
4
+ "version": "1.30.0",
5
5
  "author": {
6
6
  "name": "Krzysztof Rodak"
7
7
  },
package/README.md CHANGED
@@ -171,22 +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, search, subtasks, assign, 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 |
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 |
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
- | 🏢 **Workspace** | Spaces, folders, lists (full CRUD + rename + from template), members, task types, templates, plan |
189
- | 📎 **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, 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, task types, templates, plan, shared hierarchy |
190
+ | 📎 **Attachments** | Upload files to tasks, list task attachments, shown in detail views |
190
191
 
191
192
  [Full API coverage details](docs/api-coverage.md) | [Command reference](docs/commands.md)
192
193
 
package/dist/index.js CHANGED
@@ -1000,6 +1000,77 @@ var ClickUpClient = class {
1000
1000
  { method: "DELETE" }
1001
1001
  );
1002
1002
  }
1003
+ async mergeTasks(taskId, mergeWithTaskIds) {
1004
+ await this.request(this.taskPath(taskId, "/merge"), {
1005
+ method: "POST",
1006
+ body: JSON.stringify({ merge_with: mergeWithTaskIds })
1007
+ });
1008
+ }
1009
+ async updateTimeEstimatesByUser(taskId, estimates) {
1010
+ return this.requestV3(`/workspaces/${this.teamId}/tasks/${taskId}/time_estimates_by_user`, {
1011
+ method: "PATCH",
1012
+ body: JSON.stringify({ time_estimates_by_user: estimates })
1013
+ });
1014
+ }
1015
+ async replaceTimeEstimatesByUser(taskId, estimates) {
1016
+ return this.requestV3(`/workspaces/${this.teamId}/tasks/${taskId}/time_estimates_by_user`, {
1017
+ method: "PUT",
1018
+ body: JSON.stringify({ time_estimates_by_user: estimates })
1019
+ });
1020
+ }
1021
+ async getSharedHierarchy() {
1022
+ return this.request(`/team/${this.teamId}/shared`);
1023
+ }
1024
+ async getWebhooks() {
1025
+ const data = await this.request(`/team/${this.teamId}/webhook`);
1026
+ return readCollectionField(data, "webhooks", "webhooks");
1027
+ }
1028
+ async createWebhook(endpoint, events, opts) {
1029
+ const body = { endpoint, events };
1030
+ if (opts?.taskId) body.task_id = opts.taskId;
1031
+ if (opts?.listId) body.list_id = opts.listId;
1032
+ if (opts?.folderId) body.folder_id = opts.folderId;
1033
+ if (opts?.spaceId) body.space_id = opts.spaceId;
1034
+ const data = await this.request(`/team/${this.teamId}/webhook`, {
1035
+ method: "POST",
1036
+ body: JSON.stringify(body)
1037
+ });
1038
+ return expectRecordField(data, "webhook", "webhook");
1039
+ }
1040
+ async updateWebhook(webhookId, opts) {
1041
+ const data = await this.request(`/webhook/${webhookId}`, {
1042
+ method: "PUT",
1043
+ body: JSON.stringify(opts)
1044
+ });
1045
+ return expectRecordField(data, "webhook", "webhook");
1046
+ }
1047
+ async deleteWebhook(webhookId) {
1048
+ await this.request(`/webhook/${webhookId}`, { method: "DELETE" });
1049
+ }
1050
+ async getListComments(listId) {
1051
+ const data = await this.request(`/list/${listId}/comment`);
1052
+ return readCollectionField(data, "comments", "list comments");
1053
+ }
1054
+ async postListComment(listId, commentText, notifyAll) {
1055
+ const body = { comment_text: commentText };
1056
+ if (notifyAll) body.notify_all = true;
1057
+ return this.request(`/list/${listId}/comment`, {
1058
+ method: "POST",
1059
+ body: JSON.stringify(body)
1060
+ });
1061
+ }
1062
+ async getViewComments(viewId) {
1063
+ const data = await this.request(`/view/${viewId}/comment`);
1064
+ return readCollectionField(data, "comments", "view comments");
1065
+ }
1066
+ async postViewComment(viewId, commentText, notifyAll) {
1067
+ const body = { comment_text: commentText };
1068
+ if (notifyAll) body.notify_all = true;
1069
+ return this.request(`/view/${viewId}/comment`, {
1070
+ method: "POST",
1071
+ body: JSON.stringify(body)
1072
+ });
1073
+ }
1003
1074
  };
1004
1075
 
1005
1076
  // src/config.ts
@@ -4045,7 +4116,12 @@ var commandMetadata = [
4045
4116
  description: "List my recent time entries (--all for team)"
4046
4117
  },
4047
4118
  { section: "write", usage: "time update <timeEntryId>", description: "Update a time entry" },
4048
- { section: "write", usage: "time delete <timeEntryId>", description: "Delete a time entry" }
4119
+ { section: "write", usage: "time delete <timeEntryId>", description: "Delete a time entry" },
4120
+ {
4121
+ section: "write",
4122
+ usage: "time estimate-by-user <taskId> <userId> <duration>",
4123
+ description: "Set per-user time estimate"
4124
+ }
4049
4125
  ]
4050
4126
  },
4051
4127
  {
@@ -4501,6 +4577,92 @@ var commandMetadata = [
4501
4577
  { section: "read", usage: "favorite list", description: "List saved favorites" }
4502
4578
  ]
4503
4579
  },
4580
+ {
4581
+ name: "list-comments",
4582
+ description: "List comments on a list",
4583
+ flags: ["--json"],
4584
+ quickReference: [
4585
+ {
4586
+ section: "read",
4587
+ usage: "list-comments <listId>",
4588
+ description: "List comments on a list"
4589
+ }
4590
+ ]
4591
+ },
4592
+ {
4593
+ name: "list-comment",
4594
+ description: "Post a comment on a list",
4595
+ flags: ["-m", "--message", "--notify-all", "--json"],
4596
+ quickReference: [
4597
+ {
4598
+ section: "write",
4599
+ usage: "list-comment <listId>",
4600
+ description: "Post a comment on a list"
4601
+ }
4602
+ ]
4603
+ },
4604
+ {
4605
+ name: "view-comments",
4606
+ description: "List comments on a view",
4607
+ flags: ["--json"],
4608
+ quickReference: [
4609
+ {
4610
+ section: "read",
4611
+ usage: "view-comments <viewId>",
4612
+ description: "List comments on a view"
4613
+ }
4614
+ ]
4615
+ },
4616
+ {
4617
+ name: "view-comment",
4618
+ description: "Post a comment on a view",
4619
+ flags: ["-m", "--message", "--notify-all", "--json"],
4620
+ quickReference: [
4621
+ {
4622
+ section: "write",
4623
+ usage: "view-comment <viewId>",
4624
+ description: "Post a comment on a view"
4625
+ }
4626
+ ]
4627
+ },
4628
+ {
4629
+ name: "webhook",
4630
+ description: "Manage webhooks",
4631
+ quickReference: [
4632
+ { section: "read", usage: "webhook list", description: "List webhooks" },
4633
+ {
4634
+ section: "write",
4635
+ usage: "webhook create",
4636
+ description: "Create a webhook"
4637
+ },
4638
+ { section: "write", usage: "webhook update <webhookId>", description: "Update a webhook" },
4639
+ { section: "write", usage: "webhook delete <webhookId>", description: "Delete a webhook" }
4640
+ ]
4641
+ },
4642
+ {
4643
+ name: "merge",
4644
+ description: "Merge a task into another (source becomes subtask of target)",
4645
+ flags: ["--confirm", "--json"],
4646
+ quickReference: [
4647
+ {
4648
+ section: "write",
4649
+ usage: "merge <sourceTaskId> <intoTaskId>",
4650
+ description: "Merge a task into another"
4651
+ }
4652
+ ]
4653
+ },
4654
+ {
4655
+ name: "shared",
4656
+ description: "Show shared spaces, folders, and lists",
4657
+ flags: ["--json"],
4658
+ quickReference: [
4659
+ {
4660
+ section: "read",
4661
+ usage: "shared",
4662
+ description: "Show shared spaces, folders, and lists"
4663
+ }
4664
+ ]
4665
+ },
4504
4666
  {
4505
4667
  name: "chat",
4506
4668
  description: "Chat channels and messaging",
@@ -7776,6 +7938,222 @@ function formatFavoritesMarkdown(favorites) {
7776
7938
  return lines.join("\n");
7777
7939
  }
7778
7940
 
7941
+ // src/commands/merge.ts
7942
+ async function mergeCommand(config, sourceTaskId, intoTaskId, opts) {
7943
+ const client = new ClickUpClient(config);
7944
+ if (!opts.confirm) {
7945
+ if (!isTTY()) {
7946
+ throw new Error("Destructive operation requires --confirm flag in non-interactive mode");
7947
+ }
7948
+ const { confirm: confirm3 } = await import("@inquirer/prompts");
7949
+ const confirmed = await confirm3({
7950
+ message: `Merge task ${sourceTaskId} into ${intoTaskId}? The source task becomes a subtask of the target.`,
7951
+ default: false
7952
+ });
7953
+ if (!confirmed) {
7954
+ throw new Error("Cancelled");
7955
+ }
7956
+ }
7957
+ await client.mergeTasks(intoTaskId, [sourceTaskId]);
7958
+ return { sourceTaskId, intoTaskId, merged: true };
7959
+ }
7960
+
7961
+ // src/commands/webhook.ts
7962
+ import chalk27 from "chalk";
7963
+ var WEBHOOK_COLUMNS = [
7964
+ { key: "id", label: "ID" },
7965
+ { key: "endpoint", label: "Endpoint", maxWidth: 45 },
7966
+ { key: "events", label: "Events", maxWidth: 30 },
7967
+ {
7968
+ key: "status",
7969
+ label: "Status",
7970
+ maxWidth: 10,
7971
+ format: (v) => v === "active" ? chalk27.green(v) : chalk27.dim(v)
7972
+ },
7973
+ { key: "scope", label: "Scope", maxWidth: 15 }
7974
+ ];
7975
+ function webhookScope(w) {
7976
+ if (w.task_id) return `task:${w.task_id}`;
7977
+ if (w.list_id) return `list:${w.list_id}`;
7978
+ if (w.folder_id) return `folder:${w.folder_id}`;
7979
+ if (w.space_id) return `space:${w.space_id}`;
7980
+ return "workspace";
7981
+ }
7982
+ async function fetchWebhooks(config) {
7983
+ const client = new ClickUpClient(config);
7984
+ return client.getWebhooks();
7985
+ }
7986
+ async function createWebhookCommand(config, opts) {
7987
+ const client = new ClickUpClient(config);
7988
+ const events = opts.events.split(",").map((e) => e.trim());
7989
+ const scopeOpts = {};
7990
+ if (opts.space) scopeOpts.spaceId = opts.space;
7991
+ if (opts.folder) scopeOpts.folderId = opts.folder;
7992
+ if (opts.list) scopeOpts.listId = opts.list;
7993
+ if (opts.task) scopeOpts.taskId = opts.task;
7994
+ return client.createWebhook(opts.url, events, scopeOpts);
7995
+ }
7996
+ async function updateWebhookCommand(config, webhookId, opts) {
7997
+ const client = new ClickUpClient(config);
7998
+ const payload = {};
7999
+ if (opts.url) payload.endpoint = opts.url;
8000
+ if (opts.events) payload.events = opts.events.split(",").map((e) => e.trim());
8001
+ if (opts.status) payload.status = opts.status;
8002
+ return client.updateWebhook(webhookId, payload);
8003
+ }
8004
+ async function deleteWebhookCommand(config, webhookId, opts) {
8005
+ const client = new ClickUpClient(config);
8006
+ if (!opts.confirm) {
8007
+ if (!isTTY()) {
8008
+ throw new Error("Destructive operation requires --confirm flag in non-interactive mode");
8009
+ }
8010
+ const { confirm: confirm3 } = await import("@inquirer/prompts");
8011
+ const confirmed = await confirm3({
8012
+ message: `Delete webhook ${webhookId}? This cannot be undone.`,
8013
+ default: false
8014
+ });
8015
+ if (!confirmed) {
8016
+ throw new Error("Cancelled");
8017
+ }
8018
+ }
8019
+ await client.deleteWebhook(webhookId);
8020
+ return { webhookId, deleted: true };
8021
+ }
8022
+ function formatWebhooks(webhooks) {
8023
+ if (webhooks.length === 0) return "No webhooks found.";
8024
+ const rows = webhooks.map((w) => ({
8025
+ id: w.id,
8026
+ endpoint: w.endpoint,
8027
+ events: w.events.join(", "),
8028
+ status: w.status,
8029
+ scope: webhookScope(w)
8030
+ }));
8031
+ return formatTable(rows, WEBHOOK_COLUMNS);
8032
+ }
8033
+ function formatWebhooksMarkdown(webhooks) {
8034
+ if (webhooks.length === 0) return "No webhooks found.";
8035
+ const lines = [];
8036
+ for (const w of webhooks) {
8037
+ lines.push(`## ${w.id}`);
8038
+ lines.push("");
8039
+ lines.push(`- **Endpoint:** ${w.endpoint}`);
8040
+ lines.push(`- **Events:** ${w.events.join(", ")}`);
8041
+ lines.push(`- **Status:** ${w.status}`);
8042
+ lines.push(`- **Scope:** ${webhookScope(w)}`);
8043
+ lines.push("");
8044
+ }
8045
+ return lines.join("\n");
8046
+ }
8047
+
8048
+ // src/commands/time-estimate-by-user.ts
8049
+ async function timeEstimateByUserCommand(config, taskId, userId, duration, opts) {
8050
+ const client = new ClickUpClient(config);
8051
+ const timeMs = parseTimeEstimate(duration);
8052
+ const estimates = [{ assignee: userId, time: timeMs }];
8053
+ if (opts.replace) {
8054
+ return client.replaceTimeEstimatesByUser(taskId, estimates);
8055
+ }
8056
+ return client.updateTimeEstimatesByUser(taskId, estimates);
8057
+ }
8058
+
8059
+ // src/commands/shared.ts
8060
+ import chalk28 from "chalk";
8061
+ async function fetchSharedHierarchy(config) {
8062
+ const client = new ClickUpClient(config);
8063
+ return client.getSharedHierarchy();
8064
+ }
8065
+ function formatSharedHierarchy(hierarchy) {
8066
+ const { spaces, folders, lists } = hierarchy.shared;
8067
+ if (spaces.length === 0 && folders.length === 0 && lists.length === 0) {
8068
+ return "No shared items found.";
8069
+ }
8070
+ const lines = [];
8071
+ if (spaces.length > 0) {
8072
+ lines.push(chalk28.bold("Spaces"));
8073
+ for (const s of spaces) {
8074
+ lines.push(` ${s.name} ${chalk28.dim(s.id)}`);
8075
+ }
8076
+ }
8077
+ if (folders.length > 0) {
8078
+ if (lines.length > 0) lines.push("");
8079
+ lines.push(chalk28.bold("Folders"));
8080
+ for (const f of folders) {
8081
+ lines.push(` ${f.name} ${chalk28.dim(f.id)}`);
8082
+ }
8083
+ }
8084
+ if (lists.length > 0) {
8085
+ if (lines.length > 0) lines.push("");
8086
+ lines.push(chalk28.bold("Lists"));
8087
+ for (const l of lists) {
8088
+ lines.push(` ${l.name} ${chalk28.dim(l.id)}`);
8089
+ }
8090
+ }
8091
+ return lines.join("\n");
8092
+ }
8093
+ function formatSharedHierarchyMarkdown(hierarchy) {
8094
+ const { spaces, folders, lists } = hierarchy.shared;
8095
+ if (spaces.length === 0 && folders.length === 0 && lists.length === 0) {
8096
+ return "No shared items found.";
8097
+ }
8098
+ const lines = ["# Shared Hierarchy", ""];
8099
+ if (spaces.length > 0) {
8100
+ lines.push("## Spaces", "");
8101
+ for (const s of spaces) {
8102
+ lines.push(`- **${s.name}** (${s.id})`);
8103
+ }
8104
+ lines.push("");
8105
+ }
8106
+ if (folders.length > 0) {
8107
+ lines.push("## Folders", "");
8108
+ for (const f of folders) {
8109
+ lines.push(`- **${f.name}** (${f.id})`);
8110
+ }
8111
+ lines.push("");
8112
+ }
8113
+ if (lists.length > 0) {
8114
+ lines.push("## Lists", "");
8115
+ for (const l of lists) {
8116
+ lines.push(`- **${l.name}** (${l.id})`);
8117
+ }
8118
+ lines.push("");
8119
+ }
8120
+ return lines.join("\n");
8121
+ }
8122
+
8123
+ // src/commands/list-comments.ts
8124
+ async function fetchListComments(config, listId) {
8125
+ const client = new ClickUpClient(config);
8126
+ const comments = await client.getListComments(listId);
8127
+ return comments.map((c) => ({
8128
+ id: c.id,
8129
+ user: c.user.username,
8130
+ date: c.date,
8131
+ text: c.comment_text
8132
+ }));
8133
+ }
8134
+ async function postListCommentCommand(config, listId, text, notifyAll) {
8135
+ if (!text.trim()) throw new Error("Comment text cannot be empty");
8136
+ const client = new ClickUpClient(config);
8137
+ return client.postListComment(listId, text, notifyAll);
8138
+ }
8139
+
8140
+ // src/commands/view-comments.ts
8141
+ async function fetchViewComments(config, viewId) {
8142
+ const client = new ClickUpClient(config);
8143
+ const comments = await client.getViewComments(viewId);
8144
+ return comments.map((c) => ({
8145
+ id: c.id,
8146
+ user: c.user.username,
8147
+ date: c.date,
8148
+ text: c.comment_text
8149
+ }));
8150
+ }
8151
+ async function postViewCommentCommand(config, viewId, text, notifyAll) {
8152
+ if (!text.trim()) throw new Error("Comment text cannot be empty");
8153
+ const client = new ClickUpClient(config);
8154
+ return client.postViewComment(viewId, text, notifyAll);
8155
+ }
8156
+
7779
8157
  // src/index.ts
7780
8158
  var require2 = createRequire(import.meta.url);
7781
8159
  var { version } = require2("../package.json");
@@ -9861,6 +10239,140 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
9861
10239
  }
9862
10240
  })
9863
10241
  );
10242
+ program.command("merge <sourceTaskId> <intoTaskId>").description("Merge a task into another (source becomes subtask of target)").option("--confirm", "Skip confirmation prompt (required in non-interactive mode)").option("--json", "Force JSON output even in terminal").action(
10243
+ wrapAction(
10244
+ async (sourceTaskId, intoTaskId, opts) => {
10245
+ const config = loadConfig(getProfileName());
10246
+ const result = await mergeCommand(config, sourceTaskId, intoTaskId, opts);
10247
+ if (shouldOutputJson(opts.json ?? false)) {
10248
+ console.log(JSON.stringify(result, null, 2));
10249
+ } else {
10250
+ console.log(`Merged task ${result.sourceTaskId} into ${result.intoTaskId}`);
10251
+ }
10252
+ }
10253
+ )
10254
+ );
10255
+ timeCmd.command("estimate-by-user <taskId> <userId> <duration>").description(
10256
+ 'Set per-user time estimate (e.g. "2h", "30m"). Use --replace to overwrite all estimates.'
10257
+ ).option("--replace", "Replace all estimates (PUT) instead of updating (PATCH)").option("--json", "Force JSON output even in terminal").action(
10258
+ wrapAction(
10259
+ async (taskId, userId, duration, opts) => {
10260
+ const config = loadConfig(getProfileName());
10261
+ const result = await timeEstimateByUserCommand(config, taskId, userId, duration, opts);
10262
+ if (shouldOutputJson(opts.json ?? false)) {
10263
+ console.log(JSON.stringify(result, null, 2));
10264
+ } else {
10265
+ console.log(
10266
+ `${opts.replace ? "Replaced" : "Updated"} time estimate for user ${userId} on task ${taskId}`
10267
+ );
10268
+ }
10269
+ }
10270
+ )
10271
+ );
10272
+ program.command("shared").description("Show shared spaces, folders, and lists").option("--json", "Force JSON output even in terminal").action(
10273
+ wrapAction(async (opts) => {
10274
+ const config = loadConfig(getProfileName());
10275
+ const hierarchy = await fetchSharedHierarchy(config);
10276
+ if (shouldOutputJson(opts.json ?? false)) {
10277
+ console.log(JSON.stringify(hierarchy, null, 2));
10278
+ } else if (isTTY()) {
10279
+ console.log(formatSharedHierarchy(hierarchy));
10280
+ } else {
10281
+ console.log(formatSharedHierarchyMarkdown(hierarchy));
10282
+ }
10283
+ })
10284
+ );
10285
+ program.command("list-comments <listId>").description("List comments on a list").option("--json", "Force JSON output even in terminal").action(
10286
+ wrapAction(async (listId, opts) => {
10287
+ const config = loadConfig(getProfileName());
10288
+ const comments = await fetchListComments(config, listId);
10289
+ printComments(comments, opts.json ?? false);
10290
+ })
10291
+ );
10292
+ 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("--json", "Force JSON output even in terminal").action(
10293
+ wrapAction(
10294
+ async (listId, opts) => {
10295
+ const config = loadConfig(getProfileName());
10296
+ const result = await postListCommentCommand(config, listId, opts.message, opts.notifyAll);
10297
+ if (shouldOutputJson(opts.json ?? false)) {
10298
+ console.log(JSON.stringify(result, null, 2));
10299
+ } else {
10300
+ console.log(formatCommentConfirmation(result.id));
10301
+ }
10302
+ }
10303
+ )
10304
+ );
10305
+ program.command("view-comments <viewId>").description("List comments on a view").option("--json", "Force JSON output even in terminal").action(
10306
+ wrapAction(async (viewId, opts) => {
10307
+ const config = loadConfig(getProfileName());
10308
+ const comments = await fetchViewComments(config, viewId);
10309
+ printComments(comments, opts.json ?? false);
10310
+ })
10311
+ );
10312
+ 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("--json", "Force JSON output even in terminal").action(
10313
+ wrapAction(
10314
+ async (viewId, opts) => {
10315
+ const config = loadConfig(getProfileName());
10316
+ const result = await postViewCommentCommand(config, viewId, opts.message, opts.notifyAll);
10317
+ if (shouldOutputJson(opts.json ?? false)) {
10318
+ console.log(JSON.stringify(result, null, 2));
10319
+ } else {
10320
+ console.log(formatCommentConfirmation(result.id));
10321
+ }
10322
+ }
10323
+ )
10324
+ );
10325
+ const webhookCmd = program.command("webhook").description("Manage webhooks");
10326
+ webhookCmd.command("list").description("List webhooks in your workspace").option("--json", "Force JSON output even in terminal").action(
10327
+ wrapAction(async (opts) => {
10328
+ const config = loadConfig(getProfileName());
10329
+ const webhooks = await fetchWebhooks(config);
10330
+ if (shouldOutputJson(opts.json ?? false)) {
10331
+ console.log(JSON.stringify(webhooks, null, 2));
10332
+ } else if (isTTY()) {
10333
+ console.log(formatWebhooks(webhooks));
10334
+ } else {
10335
+ console.log(formatWebhooksMarkdown(webhooks));
10336
+ }
10337
+ })
10338
+ );
10339
+ webhookCmd.command("create").description("Create a webhook").requiredOption("--url <url>", "Webhook endpoint URL").requiredOption("--events <events>", "Comma-separated event names").option("--space <id>", "Scope to a space").option("--folder <id>", "Scope to a folder").option("--list <id>", "Scope to a list").option("--task <id>", "Scope to a task").option("--json", "Force JSON output even in terminal").action(
10340
+ wrapAction(
10341
+ async (opts) => {
10342
+ const config = loadConfig(getProfileName());
10343
+ const result = await createWebhookCommand(config, opts);
10344
+ if (shouldOutputJson(opts.json ?? false)) {
10345
+ console.log(JSON.stringify(result, null, 2));
10346
+ } else {
10347
+ console.log(`Created webhook ${result.id} \u2192 ${result.endpoint}`);
10348
+ }
10349
+ }
10350
+ )
10351
+ );
10352
+ webhookCmd.command("update <webhookId>").description("Update a webhook").option("--url <url>", "New endpoint URL").option("--events <events>", "New comma-separated event names").option("--status <status>", "New status (active or inactive)").option("--json", "Force JSON output even in terminal").action(
10353
+ wrapAction(
10354
+ async (webhookId, opts) => {
10355
+ const config = loadConfig(getProfileName());
10356
+ const result = await updateWebhookCommand(config, webhookId, opts);
10357
+ if (shouldOutputJson(opts.json ?? false)) {
10358
+ console.log(JSON.stringify(result, null, 2));
10359
+ } else {
10360
+ console.log(`Updated webhook ${result.id}`);
10361
+ }
10362
+ }
10363
+ )
10364
+ );
10365
+ webhookCmd.command("delete <webhookId>").description("Delete a webhook (requires confirmation)").option("--confirm", "Skip confirmation prompt (required in non-interactive mode)").option("--json", "Force JSON output even in terminal").action(
10366
+ wrapAction(async (webhookId, opts) => {
10367
+ const config = loadConfig(getProfileName());
10368
+ const result = await deleteWebhookCommand(config, webhookId, opts);
10369
+ if (shouldOutputJson(opts.json ?? false)) {
10370
+ console.log(JSON.stringify(result, null, 2));
10371
+ } else {
10372
+ console.log(`Deleted webhook ${result.webhookId}`);
10373
+ }
10374
+ })
10375
+ );
9864
10376
  program.command("completion <shell>").description("Output shell completion script (bash, zsh, fish)").action(
9865
10377
  wrapAction(async (shell) => {
9866
10378
  const script = generateCompletion(shell, programName);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@krodak/clickup-cli",
3
- "version": "1.29.0",
3
+ "version": "1.30.0",
4
4
  "description": "ClickUp CLI for AI agents and humans",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -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.29.0
6
+ # ClickUp CLI (`cup`) - skill version 1.30.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.29.0, update with `npm install -g @krodak/clickup-cli` and refresh this skill with `cup skill`.
10
+ > **Version check:** Run `cup --version`. If your installed version is older than 1.30.0, update with `npm install -g @krodak/clickup-cli` and refresh this skill with `cup skill`.
11
11
 
12
12
  ## Install & Configure
13
13
 
@@ -144,6 +144,10 @@ All commands support `--help` for full flag details. All commands support `--jso
144
144
  | `cup view <viewId>` | Get view details |
145
145
  | `cup open <query>` | Open task in browser by ID or name |
146
146
  | `cup auth` | Check authentication status |
147
+ | `cup list-comments <listId>` | Comments on a list |
148
+ | `cup view-comments <viewId>` | Comments on a view |
149
+ | `cup webhook list` | List webhooks in workspace |
150
+ | `cup shared` | Shared spaces, folders, and lists |
147
151
  | `cup chat channels [--all] [--type type]` | List chat channels |
148
152
  | `cup chat channel <id>` | Show channel details |
149
153
  | `cup chat messages <channelId> [--limit n]` | List channel messages |
@@ -220,6 +224,13 @@ All commands support `--help` for full flag details. All commands support `--jso
220
224
  | `cup view-create <listId> <name> -t <type> [--group-by field]` | Create a view on a list |
221
225
  | `cup view-update <viewId> [-n name] [--group-by field]` | Update a view |
222
226
  | `cup view-delete <viewId> [--confirm]` | Delete a view (DESTRUCTIVE) |
227
+ | `cup list-comment <listId> -m text [--notify-all]` | Post comment on a list |
228
+ | `cup view-comment <viewId> -m text [--notify-all]` | Post comment on a view |
229
+ | `cup webhook create --url <url> --events <events> [--space\|--folder\|--list\|--task <id>]` | Create a webhook |
230
+ | `cup webhook update <webhookId> [--url u] [--events e] [--status s]` | Update a webhook |
231
+ | `cup webhook delete <webhookId> [--confirm]` | Delete a webhook (DESTRUCTIVE) |
232
+ | `cup merge <sourceTaskId> <intoTaskId> [--confirm]` | Merge task into another (DESTRUCTIVE, source deleted) |
233
+ | `cup time estimate-by-user <taskId> <userId> <duration> [--replace]` | Set per-user time estimate |
223
234
  | `cup chat send <channelId> -m <text> [--post --title t]` | Send message to channel |
224
235
  | `cup chat reply <messageId> -m <text>` | Reply to a message |
225
236
  | `cup chat react <messageId> --emoji <name>` | Add reaction |
@@ -288,7 +299,7 @@ When running in a terminal (not piped), task-listing commands (`cup tasks`, `cup
288
299
  - **Enter** to confirm and view details of selected tasks
289
300
  - After viewing details, prompted to open tasks in browser
290
301
 
291
- Other interactive prompts: sprint selection (when multiple match), workspace selection (`cup init`), agent selection (`cup skill`), destructive action confirmations (`cup delete`, `cup list-delete`, `cup folder-delete`, `cup space-delete`, `cup archive`, `cup view-delete`).
302
+ Other interactive prompts: sprint selection (when multiple match), workspace selection (`cup init`), agent selection (`cup skill`), destructive action confirmations (`cup delete`, `cup list-delete`, `cup folder-delete`, `cup space-delete`, `cup archive`, `cup view-delete`, `cup merge`, `cup webhook delete`).
292
303
 
293
304
  When piped or called with `--json`, all commands output non-interactive markdown or JSON. Agents should always use `--json` for structured data or pipe for markdown.
294
305
 
@@ -426,4 +437,4 @@ cup summary --hours 48 # wider window
426
437
 
427
438
  ## DELETE SAFETY
428
439
 
429
- IMPORTANT: Always confirm with the user before running `cup delete`, `cup list-delete`, `cup folder-delete`, or `cup space-delete`. These are destructive, irreversible operations. Even when using `--confirm` flag, verify the ID is correct with the user first.
440
+ IMPORTANT: Always confirm with the user before running `cup delete`, `cup list-delete`, `cup folder-delete`, `cup space-delete`, `cup merge`, or `cup webhook delete`. These are destructive, irreversible operations. Even when using `--confirm` flag, verify the ID is correct with the user first.