@krodak/clickup-cli 1.6.0 → 1.7.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 +36 -57
- package/dist/index.js +135 -8
- package/package.json +1 -1
- package/skills/clickup-cli/SKILL.md +7 -0
|
@@ -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.7.0",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Krzysztof Rodak"
|
|
7
7
|
},
|
package/README.md
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
>
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="banner.png" alt="cup - ClickUp CLI for AI agents (and humans)" width="100%">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<p align="center">
|
|
6
|
+
<a href="https://www.npmjs.com/package/@krodak/clickup-cli"><img src="https://img.shields.io/npm/v/@krodak/clickup-cli" alt="npm"></a>
|
|
7
|
+
<a href="https://nodejs.org"><img src="https://img.shields.io/node/v/@krodak/clickup-cli" alt="node"></a>
|
|
8
|
+
<a href="./LICENSE"><img src="https://img.shields.io/npm/l/@krodak/clickup-cli" alt="license"></a>
|
|
9
|
+
<a href="https://github.com/krodak/clickup-cli/actions/workflows/ci.yml"><img src="https://github.com/krodak/clickup-cli/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
|
|
10
|
+
<a href="https://github.com/krodak/homebrew-tap"><img src="https://img.shields.io/badge/homebrew-tap-FBB040?logo=homebrew&logoColor=black" alt="homebrew"></a>
|
|
11
|
+
</p>
|
|
9
12
|
|
|
10
13
|
```bash
|
|
11
14
|
npm install -g @krodak/clickup-cli && cup init
|
|
12
15
|
```
|
|
13
16
|
|
|
14
|
-
`cup` is the binary name. The previous `cu` name was retired in v0.21.0 to avoid conflict with the Unix [cu(1)](<https://en.wikipedia.org/wiki/Cu_(Unix_utility)>) utility.
|
|
15
|
-
|
|
16
17
|
## Talk to your agent
|
|
17
18
|
|
|
18
19
|
Install the CLI, add the skill file to your agent, and it works with ClickUp. No API knowledge needed.
|
|
@@ -29,6 +30,18 @@ Install the CLI, add the skill file to your agent, and it works with ClickUp. No
|
|
|
29
30
|
|
|
30
31
|
The agent reads the skill file, picks the right `cup` commands, and handles everything. You don't need to learn the CLI - the agent does.
|
|
31
32
|
|
|
33
|
+
### Agent mode
|
|
34
|
+
|
|
35
|
+
When piped (no TTY), output is Markdown optimized for AI context windows. Pass `--json` for structured data.
|
|
36
|
+
|
|
37
|
+

|
|
38
|
+
|
|
39
|
+
### Terminal mode
|
|
40
|
+
|
|
41
|
+
In a terminal, you get interactive tables with colors. Most commands scope to your assigned tasks by default.
|
|
42
|
+
|
|
43
|
+

|
|
44
|
+
|
|
32
45
|
## Why a CLI and not MCP?
|
|
33
46
|
|
|
34
47
|
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.
|
|
@@ -130,39 +143,22 @@ The skill file follows the [Agent Skills](https://agentskills.io) open standard.
|
|
|
130
143
|
|
|
131
144
|
Full CRUD for the core ClickUp workflow:
|
|
132
145
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
**
|
|
136
|
-
|
|
137
|
-
**Docs**
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
**
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
**
|
|
146
|
-
|
|
147
|
-
**Goals & OKRs** - goals CRUD, key results CRUD
|
|
148
|
-
|
|
149
|
-
**Sprints** - auto-detect active sprint, flexible date parsing, config override
|
|
150
|
-
|
|
151
|
-
**Workspace** - spaces, folders, lists, members, task types, templates
|
|
152
|
-
|
|
153
|
-
**Attachments** - upload files to tasks, shown in detail views
|
|
146
|
+
| Area | Capabilities |
|
|
147
|
+
| -------------------- | ----------------------------------------------------------------------------------------------------------------------- |
|
|
148
|
+
| ✅ **Tasks** | Create, read, update, delete, duplicate, search, subtasks, assign, dependencies, links, multi-list, bulk status updates |
|
|
149
|
+
| 💬 **Comments** | Post, edit, delete, threaded replies, notify all |
|
|
150
|
+
| 📄 **Docs** | List, read, create, edit, delete (v3 API) |
|
|
151
|
+
| ⏱️ **Time Tracking** | Start/stop timer, log entries, list/update/delete history |
|
|
152
|
+
| ☑️ **Checklists** | View, create, delete, add/edit/delete items |
|
|
153
|
+
| 🔧 **Custom Fields** | List, set, remove values (dropdown, date, checkbox, text, etc.) |
|
|
154
|
+
| 🏷️ **Tags** | Add/remove on tasks, space-level create/update/delete |
|
|
155
|
+
| 🎯 **Goals & OKRs** | Goals CRUD, key results CRUD |
|
|
156
|
+
| 🏃 **Sprints** | Auto-detect active sprint, flexible date parsing, config override |
|
|
157
|
+
| 🏢 **Workspace** | Spaces, folders, lists (read + create), members, task types, templates |
|
|
158
|
+
| 📎 **Attachments** | Upload files to tasks, shown in detail views |
|
|
154
159
|
|
|
155
160
|
[Full API coverage details](docs/api-coverage.md) | [Command reference](docs/commands.md)
|
|
156
161
|
|
|
157
|
-
## Output Modes
|
|
158
|
-
|
|
159
|
-
| Context | Default | Override |
|
|
160
|
-
| -------------- | --------------------------- | ---------------------------- |
|
|
161
|
-
| Terminal (TTY) | Interactive tables + picker | `--json` |
|
|
162
|
-
| Piped (no TTY) | Markdown (optimized for AI) | `--json` or `CU_OUTPUT=json` |
|
|
163
|
-
|
|
164
|
-
Most commands scope to your assigned tasks by default - keeping output small and relevant for agent context windows.
|
|
165
|
-
|
|
166
162
|
## Configuration
|
|
167
163
|
|
|
168
164
|
### Profiles
|
|
@@ -213,23 +209,6 @@ Environment variables override config file values:
|
|
|
213
209
|
|
|
214
210
|
When both `CU_API_TOKEN` and `CU_TEAM_ID` are set, the config file is not required. Useful for CI/CD and containerized agents.
|
|
215
211
|
|
|
216
|
-
## Custom Task IDs
|
|
217
|
-
|
|
218
|
-
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.
|
|
219
|
-
|
|
220
|
-
All commands that accept task IDs work with both native IDs and custom IDs:
|
|
221
|
-
|
|
222
|
-
```bash
|
|
223
|
-
cup task PROJ-123
|
|
224
|
-
cup update DEV-42 --status done
|
|
225
|
-
cup comment PROJ-456 -m "Fixed in latest commit"
|
|
226
|
-
cup subtasks DEV-100
|
|
227
|
-
```
|
|
228
|
-
|
|
229
|
-
Custom ID resolution uses the `teamId` from your config, which is required (`cup init` sets it up).
|
|
230
|
-
|
|
231
|
-
**Task links with custom IDs:** The `cup 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.
|
|
232
|
-
|
|
233
212
|
## Development
|
|
234
213
|
|
|
235
214
|
```bash
|
package/dist/index.js
CHANGED
|
@@ -100,6 +100,12 @@ var ClickUpClient = class {
|
|
|
100
100
|
...options.headers
|
|
101
101
|
}
|
|
102
102
|
});
|
|
103
|
+
if (res.status === 204 || res.headers.get("content-length") === "0") {
|
|
104
|
+
if (!res.ok) {
|
|
105
|
+
throw new Error(`ClickUp API error ${res.status}: ${res.statusText}`);
|
|
106
|
+
}
|
|
107
|
+
return {};
|
|
108
|
+
}
|
|
103
109
|
let parsed;
|
|
104
110
|
try {
|
|
105
111
|
parsed = await res.json();
|
|
@@ -218,6 +224,12 @@ var ClickUpClient = class {
|
|
|
218
224
|
async getListWithStatuses(listId) {
|
|
219
225
|
return this.request(`/list/${listId}`);
|
|
220
226
|
}
|
|
227
|
+
async createSpace(teamId, name) {
|
|
228
|
+
return this.request(`/team/${teamId}/space`, {
|
|
229
|
+
method: "POST",
|
|
230
|
+
body: JSON.stringify({ name, multiple_assignees: true })
|
|
231
|
+
});
|
|
232
|
+
}
|
|
221
233
|
async getSpaces(teamId) {
|
|
222
234
|
const data = await this.request(`/team/${teamId}/space?archived=false`);
|
|
223
235
|
return readCollectionField(data, "spaces", "spaces");
|
|
@@ -232,6 +244,24 @@ var ClickUpClient = class {
|
|
|
232
244
|
"custom task types"
|
|
233
245
|
);
|
|
234
246
|
}
|
|
247
|
+
async createList(spaceId, name) {
|
|
248
|
+
return this.request(`/space/${spaceId}/list`, {
|
|
249
|
+
method: "POST",
|
|
250
|
+
body: JSON.stringify({ name })
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
async createFolderList(folderId, name) {
|
|
254
|
+
return this.request(`/folder/${folderId}/list`, {
|
|
255
|
+
method: "POST",
|
|
256
|
+
body: JSON.stringify({ name })
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
async createFolder(spaceId, name) {
|
|
260
|
+
return this.request(`/space/${spaceId}/folder`, {
|
|
261
|
+
method: "POST",
|
|
262
|
+
body: JSON.stringify({ name })
|
|
263
|
+
});
|
|
264
|
+
}
|
|
235
265
|
async getLists(spaceId) {
|
|
236
266
|
const data = await this.request(`/space/${spaceId}/list?archived=false`);
|
|
237
267
|
return readCollectionField(data, "lists", "space lists");
|
|
@@ -1567,7 +1597,10 @@ function parseTimeEstimate(value) {
|
|
|
1567
1597
|
}
|
|
1568
1598
|
function buildUpdatePayload(opts) {
|
|
1569
1599
|
const payload = {};
|
|
1570
|
-
if (opts.name !== void 0)
|
|
1600
|
+
if (opts.name !== void 0) {
|
|
1601
|
+
if (!opts.name.trim()) throw new Error("Task name cannot be empty");
|
|
1602
|
+
payload.name = opts.name;
|
|
1603
|
+
}
|
|
1571
1604
|
if (opts.description !== void 0) payload.markdown_content = opts.description;
|
|
1572
1605
|
if (opts.status !== void 0) payload.status = opts.status;
|
|
1573
1606
|
if (opts.priority !== void 0) payload.priority = parsePriority(opts.priority);
|
|
@@ -1616,6 +1649,7 @@ async function updateTask(config, taskId, options) {
|
|
|
1616
1649
|
|
|
1617
1650
|
// src/commands/create.ts
|
|
1618
1651
|
async function createTask(config, options) {
|
|
1652
|
+
if (!options.name.trim()) throw new Error("Task name cannot be empty");
|
|
1619
1653
|
const client = new ClickUpClient(config);
|
|
1620
1654
|
let listId = options.list;
|
|
1621
1655
|
if (!listId && options.parent) {
|
|
@@ -3014,6 +3048,38 @@ var commandMetadata = [
|
|
|
3014
3048
|
{ section: "read", usage: "folders <spaceId>", description: "List folders in a space" }
|
|
3015
3049
|
]
|
|
3016
3050
|
},
|
|
3051
|
+
{
|
|
3052
|
+
name: "space-create",
|
|
3053
|
+
description: "Create a new space in your workspace",
|
|
3054
|
+
flags: ["--json"],
|
|
3055
|
+
quickReference: [
|
|
3056
|
+
{ section: "write", usage: "space-create <name>", description: "Create a space" }
|
|
3057
|
+
]
|
|
3058
|
+
},
|
|
3059
|
+
{
|
|
3060
|
+
name: "list-create",
|
|
3061
|
+
description: "Create a new list in a space",
|
|
3062
|
+
flags: ["--folder", "--json"],
|
|
3063
|
+
quickReference: [
|
|
3064
|
+
{
|
|
3065
|
+
section: "write",
|
|
3066
|
+
usage: "list-create <spaceId> <name>",
|
|
3067
|
+
description: "Create a list in a space"
|
|
3068
|
+
}
|
|
3069
|
+
]
|
|
3070
|
+
},
|
|
3071
|
+
{
|
|
3072
|
+
name: "folder-create",
|
|
3073
|
+
description: "Create a new folder in a space",
|
|
3074
|
+
flags: ["--json"],
|
|
3075
|
+
quickReference: [
|
|
3076
|
+
{
|
|
3077
|
+
section: "write",
|
|
3078
|
+
usage: "folder-create <spaceId> <name>",
|
|
3079
|
+
description: "Create a folder in a space"
|
|
3080
|
+
}
|
|
3081
|
+
]
|
|
3082
|
+
},
|
|
3017
3083
|
{
|
|
3018
3084
|
name: "members",
|
|
3019
3085
|
description: "List workspace members",
|
|
@@ -3824,6 +3890,24 @@ ${renderZshTopLevelCommands(name)}
|
|
|
3824
3890
|
'--name[Filter by folder name]:text:' \\
|
|
3825
3891
|
'--json[Force JSON output]'
|
|
3826
3892
|
;;
|
|
3893
|
+
space-create)
|
|
3894
|
+
_arguments \\
|
|
3895
|
+
'1:name:' \\
|
|
3896
|
+
'--json[Force JSON output]'
|
|
3897
|
+
;;
|
|
3898
|
+
list-create)
|
|
3899
|
+
_arguments \\
|
|
3900
|
+
'1:space_id:' \\
|
|
3901
|
+
'2:name:' \\
|
|
3902
|
+
'--folder[Create inside a folder]:folder_id:' \\
|
|
3903
|
+
'--json[Force JSON output]'
|
|
3904
|
+
;;
|
|
3905
|
+
folder-create)
|
|
3906
|
+
_arguments \\
|
|
3907
|
+
'1:space_id:' \\
|
|
3908
|
+
'2:name:' \\
|
|
3909
|
+
'--json[Force JSON output]'
|
|
3910
|
+
;;
|
|
3827
3911
|
doc-create)
|
|
3828
3912
|
_arguments \\
|
|
3829
3913
|
'1:title:' \\
|
|
@@ -4772,7 +4856,7 @@ function wrapAction(fn) {
|
|
|
4772
4856
|
return async (...args) => {
|
|
4773
4857
|
await fn(...args).catch((err) => {
|
|
4774
4858
|
console.error(err instanceof Error ? err.message : String(err));
|
|
4775
|
-
process.
|
|
4859
|
+
process.exitCode = 1;
|
|
4776
4860
|
});
|
|
4777
4861
|
};
|
|
4778
4862
|
}
|
|
@@ -5622,6 +5706,47 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
|
|
|
5622
5706
|
}
|
|
5623
5707
|
})
|
|
5624
5708
|
);
|
|
5709
|
+
program.command("space-create <name>").description("Create a new space in your workspace").option("--json", "Force JSON output even in terminal").action(
|
|
5710
|
+
wrapAction(async (name, opts) => {
|
|
5711
|
+
if (!name.trim()) throw new Error("Space name cannot be empty");
|
|
5712
|
+
const config = loadConfig(getProfileName());
|
|
5713
|
+
const client = new ClickUpClient(config);
|
|
5714
|
+
const space = await client.createSpace(config.teamId, name);
|
|
5715
|
+
if (shouldOutputJson(opts.json ?? false)) {
|
|
5716
|
+
console.log(JSON.stringify(space, null, 2));
|
|
5717
|
+
} else {
|
|
5718
|
+
console.log(`Created space "${space.name}" (${space.id})`);
|
|
5719
|
+
}
|
|
5720
|
+
})
|
|
5721
|
+
);
|
|
5722
|
+
program.command("list-create <spaceId> <name>").description("Create a new list in a space").option("--folder <folderId>", "Create the list inside a folder").option("--json", "Force JSON output even in terminal").action(
|
|
5723
|
+
wrapAction(
|
|
5724
|
+
async (spaceId, name, opts) => {
|
|
5725
|
+
if (!name.trim()) throw new Error("List name cannot be empty");
|
|
5726
|
+
const config = loadConfig(getProfileName());
|
|
5727
|
+
const client = new ClickUpClient(config);
|
|
5728
|
+
const list = opts.folder ? await client.createFolderList(opts.folder, name) : await client.createList(spaceId, name);
|
|
5729
|
+
if (shouldOutputJson(opts.json ?? false)) {
|
|
5730
|
+
console.log(JSON.stringify(list, null, 2));
|
|
5731
|
+
} else {
|
|
5732
|
+
console.log(`Created list "${list.name}" (${list.id})`);
|
|
5733
|
+
}
|
|
5734
|
+
}
|
|
5735
|
+
)
|
|
5736
|
+
);
|
|
5737
|
+
program.command("folder-create <spaceId> <name>").description("Create a new folder in a space").option("--json", "Force JSON output even in terminal").action(
|
|
5738
|
+
wrapAction(async (spaceId, name, opts) => {
|
|
5739
|
+
if (!name.trim()) throw new Error("Folder name cannot be empty");
|
|
5740
|
+
const config = loadConfig(getProfileName());
|
|
5741
|
+
const client = new ClickUpClient(config);
|
|
5742
|
+
const folder = await client.createFolder(spaceId, name);
|
|
5743
|
+
if (shouldOutputJson(opts.json ?? false)) {
|
|
5744
|
+
console.log(JSON.stringify(folder, null, 2));
|
|
5745
|
+
} else {
|
|
5746
|
+
console.log(`Created folder "${folder.name}" (${folder.id})`);
|
|
5747
|
+
}
|
|
5748
|
+
})
|
|
5749
|
+
);
|
|
5625
5750
|
program.command("doc-create <title>").description("Create a new doc").option("-c, --content <text>", "Initial content (markdown)").option("--json", "Force JSON output even in terminal").action(
|
|
5626
5751
|
wrapAction(async (title, opts) => {
|
|
5627
5752
|
const config = loadConfig(getProfileName());
|
|
@@ -5822,14 +5947,16 @@ async function run(argv = process.argv) {
|
|
|
5822
5947
|
}
|
|
5823
5948
|
process.on("SIGINT", () => {
|
|
5824
5949
|
process.stderr.write("\nInterrupted\n");
|
|
5825
|
-
process.
|
|
5950
|
+
process.exitCode = 130;
|
|
5826
5951
|
});
|
|
5827
|
-
|
|
5828
|
-
try {
|
|
5829
|
-
|
|
5830
|
-
} catch {
|
|
5831
|
-
|
|
5952
|
+
function checkDirectExecution() {
|
|
5953
|
+
try {
|
|
5954
|
+
return process.argv[1] !== void 0 && fileURLToPath(import.meta.url) === realpathSync(resolve(process.argv[1]));
|
|
5955
|
+
} catch {
|
|
5956
|
+
return false;
|
|
5957
|
+
}
|
|
5832
5958
|
}
|
|
5959
|
+
var isDirectExecution = checkDirectExecution();
|
|
5833
5960
|
if (isDirectExecution) {
|
|
5834
5961
|
await run();
|
|
5835
5962
|
}
|
package/package.json
CHANGED
|
@@ -108,6 +108,9 @@ All commands support `--help` for full flag details. All commands support `--jso
|
|
|
108
108
|
| `cup doc-page-edit <docId> <pageId> [--name text] [-c content]` | Edit doc page |
|
|
109
109
|
| `cup doc-delete <docId>` | Delete a doc |
|
|
110
110
|
| `cup doc-page-delete <docId> <pageId>` | Delete doc page |
|
|
111
|
+
| `cup space-create <name>` | Create a space |
|
|
112
|
+
| `cup list-create <spaceId> <name> [--folder folderId]` | Create a list in a space or folder |
|
|
113
|
+
| `cup folder-create <spaceId> <name>` | Create a folder in a space |
|
|
111
114
|
| `cup tag-create <spaceId> <name> [--fg color] [--bg color]` | Create space tag |
|
|
112
115
|
| `cup tag-update <spaceId> <tagName> --name <newName> [--fg c] [--bg c]` | Update space tag |
|
|
113
116
|
| `cup tag-delete <spaceId> <name>` | Delete space tag |
|
|
@@ -215,6 +218,10 @@ cup doc-page-edit <docId> <pageId> -c "# Updated"
|
|
|
215
218
|
cup spaces --name "Engineering" # find space ID
|
|
216
219
|
cup folders <spaceId> # folders with their lists
|
|
217
220
|
cup lists <spaceId> # lists in a space
|
|
221
|
+
cup space-create "New Space" # create a space
|
|
222
|
+
cup folder-create <spaceId> "Q2" # create a folder
|
|
223
|
+
cup list-create <spaceId> "Backlog" # create a list
|
|
224
|
+
cup list-create <spaceId> "Sprint" --folder <folderId>
|
|
218
225
|
cup members # workspace members
|
|
219
226
|
cup fields <listId> # custom fields on a list
|
|
220
227
|
cup task-types # custom task types
|