@krodak/clickup-cli 1.34.0 → 1.35.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.34.0",
4
+ "version": "1.35.0",
5
5
  "author": {
6
6
  "name": "Krzysztof Rodak"
7
7
  },
package/dist/index.js CHANGED
@@ -4172,6 +4172,18 @@ var commandMetadata = [
4172
4172
  }
4173
4173
  ]
4174
4174
  },
4175
+ {
4176
+ name: "attach-get",
4177
+ description: "Download task attachment(s) by ID or title",
4178
+ flags: ["-o", "--output", "--dir", "--all", "--force", "--json"],
4179
+ quickReference: [
4180
+ {
4181
+ section: "read",
4182
+ usage: "attach-get <taskId> [selector]",
4183
+ description: "Download task attachment(s)"
4184
+ }
4185
+ ]
4186
+ },
4175
4187
  {
4176
4188
  name: "task-members",
4177
4189
  description: "List members with access to a task",
@@ -6949,6 +6961,83 @@ async function attachFile(config, taskId, filePath) {
6949
6961
  return client.createTaskAttachment(taskId, filePath);
6950
6962
  }
6951
6963
 
6964
+ // src/commands/attach-get.ts
6965
+ function sanitizeFilename(name) {
6966
+ const base = name.replace(/[/\\]/g, "_").replace(/^\.+/, "");
6967
+ return base.length > 0 ? base : "attachment";
6968
+ }
6969
+ function selectAttachments(attachments, selector, all) {
6970
+ if (all) return attachments;
6971
+ if (attachments.length === 0) {
6972
+ throw new Error("No attachments found on this task");
6973
+ }
6974
+ if (!selector) {
6975
+ if (attachments.length === 1) return [attachments[0]];
6976
+ const list = attachments.map((a) => ` ${a.id} ${a.title}`).join("\n");
6977
+ throw new Error(
6978
+ `Task has ${attachments.length} attachments. Specify one by ID or title, or use --all:
6979
+ ${list}`
6980
+ );
6981
+ }
6982
+ const lower = selector.toLowerCase();
6983
+ const byId = attachments.find((a) => a.id === selector);
6984
+ if (byId) return [byId];
6985
+ const byExactTitle = attachments.find((a) => a.title.toLowerCase() === lower);
6986
+ if (byExactTitle) return [byExactTitle];
6987
+ const byPartial = attachments.filter((a) => a.title.toLowerCase().includes(lower));
6988
+ if (byPartial.length === 1) return [byPartial[0]];
6989
+ if (byPartial.length > 1) {
6990
+ const list = byPartial.map((a) => ` ${a.id} ${a.title}`).join("\n");
6991
+ throw new Error(`Multiple attachments match "${selector}":
6992
+ ${list}`);
6993
+ }
6994
+ const available = attachments.map((a) => ` ${a.id} ${a.title}`).join("\n");
6995
+ throw new Error(`No attachment matching "${selector}". Available:
6996
+ ${available}`);
6997
+ }
6998
+ async function fileExists(path) {
6999
+ const { access } = await import("fs/promises");
7000
+ try {
7001
+ await access(path);
7002
+ return true;
7003
+ } catch {
7004
+ return false;
7005
+ }
7006
+ }
7007
+ async function downloadAttachment(attachment, targetPath, force) {
7008
+ const { writeFile } = await import("fs/promises");
7009
+ if (!force && await fileExists(targetPath)) {
7010
+ throw new Error(`File already exists: ${targetPath} (use --force to overwrite)`);
7011
+ }
7012
+ const res = await fetch(attachment.url, { signal: AbortSignal.timeout(6e4) });
7013
+ if (!res.ok) {
7014
+ throw new Error(
7015
+ `Failed to download "${attachment.title}": HTTP ${res.status} ${res.statusText}`
7016
+ );
7017
+ }
7018
+ const buffer = Buffer.from(await res.arrayBuffer());
7019
+ await writeFile(targetPath, buffer);
7020
+ return { title: attachment.title, path: targetPath, size: buffer.length };
7021
+ }
7022
+ async function attachGet(config, taskId, selector, opts) {
7023
+ const { resolve: resolve2 } = await import("path");
7024
+ const client = new ClickUpClient(config);
7025
+ const attachments = await client.getTaskAttachments(taskId);
7026
+ const selected = selectAttachments(attachments, selector, opts.all ?? false);
7027
+ const results = [];
7028
+ for (const att of selected) {
7029
+ let targetPath;
7030
+ if (opts.output && !opts.all) {
7031
+ targetPath = resolve2(opts.output);
7032
+ } else {
7033
+ const dir = opts.dir ?? ".";
7034
+ targetPath = resolve2(dir, sanitizeFilename(att.title));
7035
+ }
7036
+ results.push(await downloadAttachment(att, targetPath, opts.force ?? false));
7037
+ }
7038
+ return results;
7039
+ }
7040
+
6952
7041
  // src/commands/docs.ts
6953
7042
  var DOC_COLUMNS = [
6954
7043
  { key: "id", label: "ID", maxWidth: 15 },
@@ -9128,6 +9217,21 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
9128
9217
  }
9129
9218
  })
9130
9219
  );
9220
+ program.command("attach-get <taskId> [selector]").description("Download task attachment(s) by ID or title").option("-o, --output <path>", "Output file path (single attachment only)").option("--dir <dir>", "Directory to save into (default: current dir)").option("--all", "Download all attachments").option("--force", "Overwrite existing files").option("--json", "Force JSON output even in terminal").action(
9221
+ wrapAction(
9222
+ async (taskId, selector, opts) => {
9223
+ const config = loadConfig(getProfileName());
9224
+ const results = await attachGet(config, taskId, selector, opts);
9225
+ if (shouldOutputJson(opts.json ?? false)) {
9226
+ console.log(JSON.stringify(results, null, 2));
9227
+ } else {
9228
+ for (const r of results) {
9229
+ console.log(`Downloaded "${r.title}" -> ${r.path} (${r.size} bytes)`);
9230
+ }
9231
+ }
9232
+ }
9233
+ )
9234
+ );
9131
9235
  program.command("task-members <taskId>").description("List members with access to a task").option("--json", "Force JSON output even in terminal").action(
9132
9236
  wrapAction(async (taskId, opts) => {
9133
9237
  const config = loadConfig(getProfileName());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@krodak/clickup-cli",
3
- "version": "1.34.0",
3
+ "version": "1.35.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.34.0
6
+ # ClickUp CLI (`cup`) - skill version 1.35.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.34.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.35.0, update with `npm install -g @krodak/clickup-cli` and refresh this skill with `cup skill`.
11
11
 
12
12
  ## Install & Configure
13
13
 
@@ -107,55 +107,56 @@ All commands support `--help` for full flag details. All commands support `--jso
107
107
 
108
108
  ### Read
109
109
 
110
- | Command | What it returns |
111
- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
112
- | `cup tasks [--status s] [--name q] [--type t] [--list id] [--space id] [--all] [--include-closed] [--assignee id\|me] [--tag t] [--due-before d] [--due-after d] [--created-after d] [--created-before d] [--field "Name" val]` | Tasks assigned to you (filter by status, name, type, list, space, assignee, tag, dates, custom fields). `--all` for all assignees |
113
- | `cup assigned [--status s] [--include-closed]` | All my tasks grouped by status |
114
- | `cup sprint [--status s] [--space nameOrId] [--folder id] [--include-closed]` | Tasks in active sprint (auto-detected) |
115
- | `cup sprints [--space nameOrId]` | List all sprints (marks active with \*) |
116
- | `cup search <query> [--status s] [--list id] [--space id] [--all] [--include-closed] [--assignee id\|me] [--tag t] [--due-before d] [--due-after d] [--created-after d] [--created-before d] [--field "Name" val]` | Search your tasks by name. `--all` for all assignees |
117
- | `cup task <id>` | Single task details (custom fields, checklists, attachments, deps, links) |
118
- | `cup subtasks <id> [--status s] [--name q] [--include-closed]` | Subtasks of a task |
119
- | `cup comments <id>` | Comments on a task |
120
- | `cup activity <id>` | Task details + comment history combined |
121
- | `cup inbox [--days n] [--include-closed]` | Tasks updated in last n days (default 30) |
122
- | `cup summary [--hours n]` | Standup: completed, in-progress, overdue |
123
- | `cup overdue [--all] [--include-closed]` | Tasks past due date (most overdue first) |
124
- | `cup spaces [--name partial] [--my] [--archived]` | List/filter workspace spaces |
125
- | `cup lists <spaceId> [--name partial] [--archived]` | Lists in a space (including folder lists) |
126
- | `cup folders <spaceId> [--name partial] [--archived]` | Folders in a space (with their lists) |
127
- | `cup time-in-status <id>` | Show how long a task has been in each status |
128
- | `cup members` | Workspace members (username, ID, email) |
129
- | `cup groups` | User groups/teams (handle, name, UUID, member count) for `--group-assignee` flags |
130
- | `cup fields <listId>` | Custom fields on a list (type, required, options) |
131
- | `cup attachments <taskId>` | List attachments on a task (name, size, URL) |
132
- | `cup task-members <taskId>` | List members with access to a task |
133
- | `cup plan` | Show workspace plan and usage |
134
- | `cup tags <spaceId>` | Tags available in a space |
135
- | `cup goals` | Workspace goals with progress |
136
- | `cup key-results <goalId>` | Key results for a goal |
137
- | `cup docs [query]` | Workspace docs (optionally filter by name) |
138
- | `cup doc <docId> [pageId]` | Doc metadata + page tree, or a specific page |
139
- | `cup doc-pages <docId>` | All pages in a doc with content |
140
- | `cup task-types` | Custom task types (for `--custom-item-id`) |
141
- | `cup templates` | Task templates (for `--template`) |
142
- | `cup list-templates` | List templates (for `list-from-template`) |
143
- | `cup folder-templates` | Folder templates |
144
- | `cup views <listId>` | List views on a list |
145
- | `cup view <viewId>` | Get view details |
146
- | `cup open <query>` | Open task in browser by ID or name |
147
- | `cup auth` | Check authentication status |
148
- | `cup list-comments <listId>` | Comments on a list |
149
- | `cup view-comments <viewId>` | Comments on a view |
150
- | `cup webhook list` | List webhooks in workspace |
151
- | `cup shared` | Shared spaces, folders, and lists |
152
- | `cup chat channels [--all] [--type type]` | List chat channels |
153
- | `cup chat channel <id>` | Show channel details |
154
- | `cup chat messages <channelId> [--limit n]` | List channel messages |
155
- | `cup chat members <channelId>` | List channel members |
156
- | `cup chat followers <channelId>` | List channel followers |
157
- | `cup chat replies <messageId>` | List message replies |
158
- | `cup chat reactions <messageId>` | List reactions on a message |
110
+ | Command | What it returns |
111
+ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
112
+ | `cup tasks [--status s] [--name q] [--type t] [--list id] [--space id] [--all] [--include-closed] [--assignee id\|me] [--tag t] [--due-before d] [--due-after d] [--created-after d] [--created-before d] [--field "Name" val]` | Tasks assigned to you (filter by status, name, type, list, space, assignee, tag, dates, custom fields). `--all` for all assignees |
113
+ | `cup assigned [--status s] [--include-closed]` | All my tasks grouped by status |
114
+ | `cup sprint [--status s] [--space nameOrId] [--folder id] [--include-closed]` | Tasks in active sprint (auto-detected) |
115
+ | `cup sprints [--space nameOrId]` | List all sprints (marks active with \*) |
116
+ | `cup search <query> [--status s] [--list id] [--space id] [--all] [--include-closed] [--assignee id\|me] [--tag t] [--due-before d] [--due-after d] [--created-after d] [--created-before d] [--field "Name" val]` | Search your tasks by name. `--all` for all assignees |
117
+ | `cup task <id>` | Single task details (custom fields, checklists, attachments, deps, links) |
118
+ | `cup subtasks <id> [--status s] [--name q] [--include-closed]` | Subtasks of a task |
119
+ | `cup comments <id>` | Comments on a task |
120
+ | `cup activity <id>` | Task details + comment history combined |
121
+ | `cup inbox [--days n] [--include-closed]` | Tasks updated in last n days (default 30) |
122
+ | `cup summary [--hours n]` | Standup: completed, in-progress, overdue |
123
+ | `cup overdue [--all] [--include-closed]` | Tasks past due date (most overdue first) |
124
+ | `cup spaces [--name partial] [--my] [--archived]` | List/filter workspace spaces |
125
+ | `cup lists <spaceId> [--name partial] [--archived]` | Lists in a space (including folder lists) |
126
+ | `cup folders <spaceId> [--name partial] [--archived]` | Folders in a space (with their lists) |
127
+ | `cup time-in-status <id>` | Show how long a task has been in each status |
128
+ | `cup members` | Workspace members (username, ID, email) |
129
+ | `cup groups` | User groups/teams (handle, name, UUID, member count) for `--group-assignee` flags |
130
+ | `cup fields <listId>` | Custom fields on a list (type, required, options) |
131
+ | `cup attachments <taskId>` | List attachments on a task (name, size, URL) |
132
+ | `cup attach-get <taskId> [idOrTitle] [-o path] [--all] [--dir d] [--force]` | Download task attachment(s). No selector + 1 attachment downloads it; multiple requires a selector or `--all`. Saves to the attachment's filename by default |
133
+ | `cup task-members <taskId>` | List members with access to a task |
134
+ | `cup plan` | Show workspace plan and usage |
135
+ | `cup tags <spaceId>` | Tags available in a space |
136
+ | `cup goals` | Workspace goals with progress |
137
+ | `cup key-results <goalId>` | Key results for a goal |
138
+ | `cup docs [query]` | Workspace docs (optionally filter by name) |
139
+ | `cup doc <docId> [pageId]` | Doc metadata + page tree, or a specific page |
140
+ | `cup doc-pages <docId>` | All pages in a doc with content |
141
+ | `cup task-types` | Custom task types (for `--custom-item-id`) |
142
+ | `cup templates` | Task templates (for `--template`) |
143
+ | `cup list-templates` | List templates (for `list-from-template`) |
144
+ | `cup folder-templates` | Folder templates |
145
+ | `cup views <listId>` | List views on a list |
146
+ | `cup view <viewId>` | Get view details |
147
+ | `cup open <query>` | Open task in browser by ID or name |
148
+ | `cup auth` | Check authentication status |
149
+ | `cup list-comments <listId>` | Comments on a list |
150
+ | `cup view-comments <viewId>` | Comments on a view |
151
+ | `cup webhook list` | List webhooks in workspace |
152
+ | `cup shared` | Shared spaces, folders, and lists |
153
+ | `cup chat channels [--all] [--type type]` | List chat channels |
154
+ | `cup chat channel <id>` | Show channel details |
155
+ | `cup chat messages <channelId> [--limit n]` | List channel messages |
156
+ | `cup chat members <channelId>` | List channel members |
157
+ | `cup chat followers <channelId>` | List channel followers |
158
+ | `cup chat replies <messageId>` | List message replies |
159
+ | `cup chat reactions <messageId>` | List reactions on a message |
159
160
 
160
161
  ### Write
161
162