@krodak/clickup-cli 1.0.0 → 1.1.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.0.0",
4
+ "version": "1.1.0",
5
5
  "author": {
6
6
  "name": "Krzysztof Rodak"
7
7
  },
package/README.md CHANGED
@@ -29,6 +29,10 @@ Install the CLI, add the skill file to your agent, and it works with ClickUp. No
29
29
 
30
30
  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
31
 
32
+ ## Why a CLI and not MCP?
33
+
34
+ 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.
35
+
32
36
  ## Install
33
37
 
34
38
  You need Node 22+ and a ClickUp personal API token (`pk_...` from [ClickUp Settings > Apps](https://app.clickup.com/settings/apps)).
@@ -241,10 +245,13 @@ Status: :white_check_mark: implemented | :construction: planned | :no_entry_sign
241
245
 
242
246
  ### Docs
243
247
 
244
- | Feature | Command | Status |
245
- | ----------------- | ------------------ | -------------- |
246
- | Search docs | `cup docs <query>` | :construction: |
247
- | View page content | `cup doc <id>` | :construction: |
248
+ | Feature | Command | Status |
249
+ | ----------------- | ------------------------------------ | ------------------ |
250
+ | Search docs | `cup docs [query]` | :white_check_mark: |
251
+ | View page content | `cup doc <docId> <pageId>` | :white_check_mark: |
252
+ | Create doc | `cup doc-create <title>` | :white_check_mark: |
253
+ | Create page | `cup doc-page-create <docId> <name>` | :white_check_mark: |
254
+ | Edit page | `cup doc-page-edit <docId> <pageId>` | :white_check_mark: |
248
255
 
249
256
  ### Attachments
250
257
 
@@ -338,10 +345,6 @@ Custom ID resolution uses the `teamId` from your config, which is required (`cup
338
345
 
339
346
  **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.
340
347
 
341
- ## Why a CLI and not MCP?
342
-
343
- 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.
344
-
345
348
  ## Development
346
349
 
347
350
  ```bash
package/dist/index.js CHANGED
@@ -7,6 +7,7 @@ import { createRequire } from "module";
7
7
 
8
8
  // src/api.ts
9
9
  var BASE_URL = "https://api.clickup.com/api/v2";
10
+ var BASE_URL_V3 = "https://api.clickup.com/api/v3";
10
11
  var MAX_PAGES = 100;
11
12
  function isCustomTaskId(id) {
12
13
  return /^[A-Z]+-\d+$/i.test(id);
@@ -56,6 +57,29 @@ var ClickUpClient = class {
56
57
  }
57
58
  return data;
58
59
  }
60
+ async requestV3(path, options = {}) {
61
+ const res = await fetch(`${BASE_URL_V3}${path}`, {
62
+ ...options,
63
+ signal: AbortSignal.timeout(3e4),
64
+ headers: {
65
+ Authorization: this.apiToken,
66
+ ...options.body ? { "Content-Type": "application/json" } : {},
67
+ ...options.headers
68
+ }
69
+ });
70
+ let data;
71
+ try {
72
+ data = await res.json();
73
+ } catch {
74
+ throw new Error(`ClickUp API error ${res.status}: response was not valid JSON`);
75
+ }
76
+ if (!res.ok) {
77
+ const raw = data.err ?? data.error ?? data.ECODE ?? res.statusText;
78
+ const msg = typeof raw === "string" ? raw : JSON.stringify(raw);
79
+ throw new Error(`ClickUp API error ${res.status}: ${msg}`);
80
+ }
81
+ return data;
82
+ }
59
83
  async getMe() {
60
84
  if (this.meCache) return this.meCache;
61
85
  const data = await this.request("/user");
@@ -383,6 +407,42 @@ var ClickUpClient = class {
383
407
  }
384
408
  return data;
385
409
  }
410
+ async getDocs(workspaceId) {
411
+ const data = await this.requestV3(`/workspaces/${workspaceId}/docs`);
412
+ return data.docs ?? [];
413
+ }
414
+ async getDocPage(workspaceId, docId, pageId) {
415
+ return this.requestV3(
416
+ `/workspaces/${workspaceId}/docs/${docId}/pages/${pageId}?content_format=text/md`
417
+ );
418
+ }
419
+ async createDoc(workspaceId, title, content, parentId) {
420
+ const body = { title };
421
+ if (content) body.content = content;
422
+ if (parentId) {
423
+ body.parent_id = parentId;
424
+ body.parent_type = "doc";
425
+ }
426
+ return this.requestV3(`/workspaces/${workspaceId}/docs`, {
427
+ method: "POST",
428
+ body: JSON.stringify(body)
429
+ });
430
+ }
431
+ async createDocPage(workspaceId, docId, name, content, parentPageId) {
432
+ const body = { name, content_format: "text/md" };
433
+ if (content) body.content = content;
434
+ if (parentPageId) body.parent_page_id = parentPageId;
435
+ return this.requestV3(`/workspaces/${workspaceId}/docs/${docId}/pages`, {
436
+ method: "POST",
437
+ body: JSON.stringify(body)
438
+ });
439
+ }
440
+ async editDocPage(workspaceId, docId, pageId, updates) {
441
+ return this.requestV3(`/workspaces/${workspaceId}/docs/${docId}/pages/${pageId}`, {
442
+ method: "PUT",
443
+ body: JSON.stringify(updates)
444
+ });
445
+ }
386
446
  };
387
447
 
388
448
  // src/config.ts
@@ -2031,7 +2091,7 @@ function bashCompletion(name) {
2031
2091
  cword=$COMP_CWORD
2032
2092
  fi
2033
2093
 
2034
- local commands="init auth tasks task update create sprint sprints subtasks comment comment-edit comment-delete comments replies reply activity lists spaces inbox assigned open search summary overdue assign depend link attach move field delete tag checklist time config completion"
2094
+ local commands="init auth tasks task update create sprint sprints subtasks comment comment-edit comment-delete comments replies reply activity lists spaces inbox assigned open search summary overdue assign depend link attach move field delete tag checklist time docs doc doc-create doc-page-create doc-page-edit config completion"
2035
2095
 
2036
2096
  if [[ $cword -eq 1 ]]; then
2037
2097
  COMPREPLY=($(compgen -W "$commands --help --version" -- "$cur"))
@@ -2155,6 +2215,21 @@ function bashCompletion(name) {
2155
2215
  attach)
2156
2216
  COMPREPLY=($(compgen -f -- "$cur"))
2157
2217
  ;;
2218
+ docs)
2219
+ COMPREPLY=($(compgen -W "--json" -- "$cur"))
2220
+ ;;
2221
+ doc)
2222
+ COMPREPLY=($(compgen -W "--json" -- "$cur"))
2223
+ ;;
2224
+ doc-create)
2225
+ COMPREPLY=($(compgen -W "-c --content --json" -- "$cur"))
2226
+ ;;
2227
+ doc-page-create)
2228
+ COMPREPLY=($(compgen -W "-c --content --parent-page --json" -- "$cur"))
2229
+ ;;
2230
+ doc-page-edit)
2231
+ COMPREPLY=($(compgen -W "--name -c --content --json" -- "$cur"))
2232
+ ;;
2158
2233
  config)
2159
2234
  if [[ $cword -eq 2 ]]; then
2160
2235
  COMPREPLY=($(compgen -W "get set path" -- "$cur"))
@@ -2215,6 +2290,11 @@ _${name}() {
2215
2290
  'reply:Reply to a comment'
2216
2291
  'link:Add or remove a link between two tasks'
2217
2292
  'attach:Upload a file attachment to a task'
2293
+ 'docs:List workspace docs'
2294
+ 'doc:View a doc page'
2295
+ 'doc-create:Create a new doc'
2296
+ 'doc-page-create:Create a page in a doc'
2297
+ 'doc-page-edit:Edit a doc page'
2218
2298
  'config:Manage CLI configuration'
2219
2299
  'completion:Output shell completion script'
2220
2300
  )
@@ -2537,6 +2617,39 @@ _${name}() {
2537
2617
  '2:file_path:_files' \\
2538
2618
  '--json[Force JSON output]'
2539
2619
  ;;
2620
+ docs)
2621
+ _arguments \\
2622
+ '1:query:' \\
2623
+ '--json[Force JSON output]'
2624
+ ;;
2625
+ doc)
2626
+ _arguments \\
2627
+ '1:doc_id:' \\
2628
+ '2:page_id:' \\
2629
+ '--json[Force JSON output]'
2630
+ ;;
2631
+ doc-create)
2632
+ _arguments \\
2633
+ '1:title:' \\
2634
+ '(-c --content)'{-c,--content}'[Initial content]:text:' \\
2635
+ '--json[Force JSON output]'
2636
+ ;;
2637
+ doc-page-create)
2638
+ _arguments \\
2639
+ '1:doc_id:' \\
2640
+ '2:name:' \\
2641
+ '(-c --content)'{-c,--content}'[Page content]:text:' \\
2642
+ '--parent-page[Parent page ID]:page_id:' \\
2643
+ '--json[Force JSON output]'
2644
+ ;;
2645
+ doc-page-edit)
2646
+ _arguments \\
2647
+ '1:doc_id:' \\
2648
+ '2:page_id:' \\
2649
+ '--name[New page name]:text:' \\
2650
+ '(-c --content)'{-c,--content}'[New page content]:text:' \\
2651
+ '--json[Force JSON output]'
2652
+ ;;
2540
2653
  config)
2541
2654
  local -a config_cmds
2542
2655
  config_cmds=(
@@ -2611,6 +2724,11 @@ complete -c ${name} -n __fish_use_subcommand -a replies -d 'List threaded replie
2611
2724
  complete -c ${name} -n __fish_use_subcommand -a reply -d 'Reply to a comment'
2612
2725
  complete -c ${name} -n __fish_use_subcommand -a link -d 'Add or remove a link between two tasks'
2613
2726
  complete -c ${name} -n __fish_use_subcommand -a attach -d 'Upload a file attachment to a task'
2727
+ complete -c ${name} -n __fish_use_subcommand -a docs -d 'List workspace docs'
2728
+ complete -c ${name} -n __fish_use_subcommand -a doc -d 'View a doc page'
2729
+ complete -c ${name} -n __fish_use_subcommand -a doc-create -d 'Create a new doc'
2730
+ complete -c ${name} -n __fish_use_subcommand -a doc-page-create -d 'Create a page in a doc'
2731
+ complete -c ${name} -n __fish_use_subcommand -a doc-page-edit -d 'Edit a doc page'
2614
2732
  complete -c ${name} -n __fish_use_subcommand -a config -d 'Manage CLI configuration'
2615
2733
  complete -c ${name} -n __fish_use_subcommand -a completion -d 'Output shell completion script'
2616
2734
 
@@ -2764,6 +2882,21 @@ complete -c ${name} -n '__fish_seen_subcommand_from comment-edit' -l resolved -d
2764
2882
  complete -c ${name} -n '__fish_seen_subcommand_from comment-edit' -l unresolved -d 'Mark comment as unresolved'
2765
2883
  complete -c ${name} -n '__fish_seen_subcommand_from comment-edit' -l json -d 'Force JSON output'
2766
2884
 
2885
+ complete -c ${name} -n '__fish_seen_subcommand_from docs' -l json -d 'Force JSON output'
2886
+
2887
+ complete -c ${name} -n '__fish_seen_subcommand_from doc' -l json -d 'Force JSON output'
2888
+
2889
+ complete -c ${name} -n '__fish_seen_subcommand_from doc-create' -s c -l content -d 'Initial content'
2890
+ complete -c ${name} -n '__fish_seen_subcommand_from doc-create' -l json -d 'Force JSON output'
2891
+
2892
+ complete -c ${name} -n '__fish_seen_subcommand_from doc-page-create' -s c -l content -d 'Page content'
2893
+ complete -c ${name} -n '__fish_seen_subcommand_from doc-page-create' -l parent-page -d 'Parent page ID'
2894
+ complete -c ${name} -n '__fish_seen_subcommand_from doc-page-create' -l json -d 'Force JSON output'
2895
+
2896
+ complete -c ${name} -n '__fish_seen_subcommand_from doc-page-edit' -l name -d 'New page name'
2897
+ complete -c ${name} -n '__fish_seen_subcommand_from doc-page-edit' -s c -l content -d 'New page content'
2898
+ complete -c ${name} -n '__fish_seen_subcommand_from doc-page-edit' -l json -d 'Force JSON output'
2899
+
2767
2900
  complete -c ${name} -n '__fish_seen_subcommand_from config; and not __fish_seen_subcommand_from get set path' -a get -d 'Print a config value'
2768
2901
  complete -c ${name} -n '__fish_seen_subcommand_from config; and not __fish_seen_subcommand_from get set path' -a set -d 'Set a config value'
2769
2902
  complete -c ${name} -n '__fish_seen_subcommand_from config; and not __fish_seen_subcommand_from get set path' -a path -d 'Print config file path'
@@ -3142,8 +3275,52 @@ async function attachFile(config, taskId, filePath) {
3142
3275
  return client.createTaskAttachment(taskId, filePath);
3143
3276
  }
3144
3277
 
3145
- // src/commands/time.ts
3278
+ // src/commands/docs.ts
3146
3279
  import chalk7 from "chalk";
3280
+ async function listDocs(config, query) {
3281
+ const client = new ClickUpClient(config);
3282
+ const docs = await client.getDocs(config.teamId);
3283
+ if (query) {
3284
+ const lower = query.toLowerCase();
3285
+ return docs.filter((d) => d.name.toLowerCase().includes(lower));
3286
+ }
3287
+ return docs;
3288
+ }
3289
+ function formatDocs(docs) {
3290
+ if (docs.length === 0) return "No docs found";
3291
+ return docs.map((d) => `${chalk7.bold(d.name)} ${chalk7.dim(d.id)}`).join("\n");
3292
+ }
3293
+ function formatDocsMarkdown(docs) {
3294
+ if (docs.length === 0) return "No docs found";
3295
+ return docs.map((d) => `- **${d.name}** (${d.id})`).join("\n");
3296
+ }
3297
+
3298
+ // src/commands/doc.ts
3299
+ async function getDocPage(config, docId, pageId) {
3300
+ const client = new ClickUpClient(config);
3301
+ return client.getDocPage(config.teamId, docId, pageId);
3302
+ }
3303
+ async function createDoc(config, title, content) {
3304
+ if (!title.trim()) throw new Error("Doc title cannot be empty");
3305
+ const client = new ClickUpClient(config);
3306
+ const doc = await client.createDoc(config.teamId, title, content);
3307
+ return { id: doc.id, title: doc.name ?? title };
3308
+ }
3309
+ async function createDocPage(config, docId, name, content, parentPageId) {
3310
+ if (!name.trim()) throw new Error("Page name cannot be empty");
3311
+ const client = new ClickUpClient(config);
3312
+ return client.createDocPage(config.teamId, docId, name, content, parentPageId);
3313
+ }
3314
+ async function editDocPage(config, docId, pageId, updates) {
3315
+ if (!updates.name && !updates.content) {
3316
+ throw new Error("Provide --name or --content to update");
3317
+ }
3318
+ const client = new ClickUpClient(config);
3319
+ return client.editDocPage(config.teamId, docId, pageId, updates);
3320
+ }
3321
+
3322
+ // src/commands/time.ts
3323
+ import chalk8 from "chalk";
3147
3324
  async function startTimer(config, taskId, description) {
3148
3325
  const client = new ClickUpClient(config);
3149
3326
  return client.startTimeEntry(config.teamId, taskId, description);
@@ -3179,8 +3356,8 @@ function formatTimeEntry(entry) {
3179
3356
  const isRunning = entry.duration < 0;
3180
3357
  const elapsed = isRunning ? Date.now() - Number(entry.start) : entry.duration;
3181
3358
  const durationStr = formatDuration(elapsed);
3182
- const status = isRunning ? chalk7.green("RUNNING") : "";
3183
- lines.push(`${chalk7.bold(taskName)} ${chalk7.dim(taskId)} ${status}`);
3359
+ const status = isRunning ? chalk8.green("RUNNING") : "";
3360
+ lines.push(`${chalk8.bold(taskName)} ${chalk8.dim(taskId)} ${status}`);
3184
3361
  lines.push(
3185
3362
  ` ${durationStr} - ${formatTimestamp(entry.start)}${entry.description ? ` - ${entry.description}` : ""}`
3186
3363
  );
@@ -3746,6 +3923,72 @@ timeCmd.command("list").description("List recent time entries (default: last 7 d
3746
3923
  }
3747
3924
  })
3748
3925
  );
3926
+ program.command("docs [query]").description("List workspace docs (optionally filter by name)").option("--json", "Force JSON output even in terminal").action(
3927
+ wrapAction(async (query, opts) => {
3928
+ const config = loadConfig();
3929
+ const docs = await listDocs(config, query);
3930
+ if (shouldOutputJson(opts.json ?? false)) {
3931
+ console.log(JSON.stringify(docs, null, 2));
3932
+ } else if (isTTY()) {
3933
+ console.log(formatDocs(docs));
3934
+ } else {
3935
+ console.log(formatDocsMarkdown(docs));
3936
+ }
3937
+ })
3938
+ );
3939
+ program.command("doc <docId> <pageId>").description("View a doc page").option("--json", "Force JSON output even in terminal").action(
3940
+ wrapAction(async (docId, pageId, opts) => {
3941
+ const config = loadConfig();
3942
+ const page = await getDocPage(config, docId, pageId);
3943
+ if (shouldOutputJson(opts.json ?? false)) {
3944
+ console.log(JSON.stringify(page, null, 2));
3945
+ } else {
3946
+ if (page.name) console.log(`# ${page.name}
3947
+ `);
3948
+ console.log(page.content ?? "");
3949
+ }
3950
+ })
3951
+ );
3952
+ 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(
3953
+ wrapAction(async (title, opts) => {
3954
+ const config = loadConfig();
3955
+ const result = await createDoc(config, title, opts.content);
3956
+ if (shouldOutputJson(opts.json ?? false)) {
3957
+ console.log(JSON.stringify(result, null, 2));
3958
+ } else {
3959
+ console.log(`Created doc "${result.title}" (${result.id})`);
3960
+ }
3961
+ })
3962
+ );
3963
+ program.command("doc-page-create <docId> <name>").description("Create a page in a doc").option("-c, --content <text>", "Page content (markdown)").option("--parent-page <pageId>", "Parent page ID for nesting").option("--json", "Force JSON output even in terminal").action(
3964
+ wrapAction(
3965
+ async (docId, name, opts) => {
3966
+ const config = loadConfig();
3967
+ const page = await createDocPage(config, docId, name, opts.content, opts.parentPage);
3968
+ if (shouldOutputJson(opts.json ?? false)) {
3969
+ console.log(JSON.stringify(page, null, 2));
3970
+ } else {
3971
+ console.log(`Created page "${page.name}" (${page.id}) in doc ${docId}`);
3972
+ }
3973
+ }
3974
+ )
3975
+ );
3976
+ program.command("doc-page-edit <docId> <pageId>").description("Edit a doc page").option("--name <text>", "New page name").option("-c, --content <text>", "New page content (markdown)").option("--json", "Force JSON output even in terminal").action(
3977
+ wrapAction(
3978
+ async (docId, pageId, opts) => {
3979
+ const config = loadConfig();
3980
+ const page = await editDocPage(config, docId, pageId, {
3981
+ name: opts.name,
3982
+ content: opts.content
3983
+ });
3984
+ if (shouldOutputJson(opts.json ?? false)) {
3985
+ console.log(JSON.stringify(page, null, 2));
3986
+ } else {
3987
+ console.log(`Updated page "${page.name}" (${page.id})`);
3988
+ }
3989
+ }
3990
+ )
3991
+ );
3749
3992
  var configCmd = program.command("config").description("Manage CLI configuration");
3750
3993
  configCmd.command("get <key>").description("Print a config value").action(
3751
3994
  wrapAction(async (key) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@krodak/clickup-cli",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "ClickUp CLI for AI agents and humans",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -52,6 +52,8 @@ All commands support `--help` for full flag details.
52
52
  | `cup lists <spaceId> [--name partial] [--json]` | Lists in a space (including folder lists) |
53
53
  | `cup open <query> [--json]` | Open task in browser by ID or name |
54
54
  | `cup auth [--json]` | Check authentication status |
55
+ | `cup docs [query] [--json]` | List workspace docs (optionally filter by name) |
56
+ | `cup doc <docId> <pageId> [--json]` | View a doc page (markdown content) |
55
57
 
56
58
  ### Write
57
59
 
@@ -83,6 +85,9 @@ All commands support `--help` for full flag details.
83
85
  | `cup time status [--json]` | Show currently running timer |
84
86
  | `cup time log <taskId> <duration> [-d desc] [--json]` | Log manual time entry (e.g. "2h", "30m") |
85
87
  | `cup time list [--days n] [--task id] [--json]` | List recent time entries |
88
+ | `cup doc-create <title> [-c content] [--json]` | Create a new doc |
89
+ | `cup doc-page-create <docId> <name> [-c content] [--parent-page pageId] [--json]` | Create a page in a doc |
90
+ | `cup doc-page-edit <docId> <pageId> [--name text] [-c content] [--json]` | Edit a doc page |
86
91
  | `cup config get <key>` / `cup config set <key> <value>` / `cup config path` | Manage CLI config (keys: apiToken, teamId, sprintFolderId) |
87
92
  | `cup completion <shell>` | Shell completions (bash/zsh/fish) |
88
93
 
@@ -124,6 +129,11 @@ All commands support `--help` for full flag details.
124
129
  | `cup link` + custom IDs | Both IDs must be the same type (both custom or both native). Mixing may not work |
125
130
  | `cup link` | Link/unlink tasks (different from dependencies) |
126
131
  | `cup attach` | Upload files to tasks. Attachments shown in `cup task` detail view |
132
+ | `cup docs` | List and search workspace docs by name |
133
+ | `cup doc` | View a doc page content (markdown) |
134
+ | `cup doc-create` | Create a new doc with optional initial content |
135
+ | `cup doc-page-create` | Create a page in a doc, optionally nested under a parent page |
136
+ | `cup doc-page-edit` | Edit a doc page name or content |
127
137
  | `cup task` | Shows custom fields, checklists, attachments, dependencies, and linked tasks in detail view |
128
138
  | `cup lists` | Discovers list IDs needed for `--list` and `cup create -l` |
129
139
  | Errors | stderr with exit code 1 |
@@ -195,6 +205,20 @@ cup time list --days 7 # recent entries
195
205
  cup delete abc123def --confirm # irreversible!
196
206
  ```
197
207
 
208
+ ### Work with docs
209
+
210
+ ```bash
211
+ cup docs # list all docs
212
+ cup docs "design" # search docs by name
213
+ cup doc <docId> <pageId> # view page content
214
+ cup doc-create "Architecture Notes" # create a doc
215
+ cup doc-create "Notes" -c "# Draft" # create with content
216
+ cup doc-page-create <docId> "New Section" # add page to doc
217
+ cup doc-page-create <docId> "Sub" --parent-page <pageId> # nested page
218
+ cup doc-page-edit <docId> <pageId> --name "Renamed" # rename page
219
+ cup doc-page-edit <docId> <pageId> -c "# Updated content" # edit content
220
+ ```
221
+
198
222
  ### Discover workspace structure
199
223
 
200
224
  ```bash