@krodak/clickup-cli 1.1.0 → 1.3.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.1.0",
4
+ "version": "1.3.0",
5
5
  "author": {
6
6
  "name": "Krzysztof Rodak"
7
7
  },
package/README.md CHANGED
@@ -210,7 +210,7 @@ Status: :white_check_mark: implemented | :construction: planned | :no_entry_sign
210
210
  | Feature | Command | Status |
211
211
  | ----------------------- | -------------------- | ------------------ |
212
212
  | Add/remove tag on task | `cup tag <id>` | :white_check_mark: |
213
- | List space tags | `cup tags <spaceId>` | :construction: |
213
+ | List space tags | `cup tags <spaceId>` | :white_check_mark: |
214
214
  | Create/delete space tag | | :construction: |
215
215
 
216
216
  ### Time Tracking
@@ -222,8 +222,8 @@ Status: :white_check_mark: implemented | :construction: planned | :no_entry_sign
222
222
  | Timer status | `cup time status` | :white_check_mark: |
223
223
  | Log time entry | `cup time log <id> <duration>` | :white_check_mark: |
224
224
  | List entries | `cup time list` | :white_check_mark: |
225
- | Update entry | `cup time update <id>` | :construction: |
226
- | Delete entry | `cup time delete <id>` | :construction: |
225
+ | Update entry | `cup time update <id>` | :white_check_mark: |
226
+ | Delete entry | `cup time delete <id>` | :white_check_mark: |
227
227
 
228
228
  ### Workspace
229
229
 
@@ -232,7 +232,7 @@ Status: :white_check_mark: implemented | :construction: planned | :no_entry_sign
232
232
  | List spaces | `cup spaces` | :white_check_mark: |
233
233
  | List lists | `cup lists <spaceId>` | :white_check_mark: |
234
234
  | Check auth | `cup auth` | :white_check_mark: |
235
- | List folders | `cup folders <spaceId>` | :construction: |
235
+ | List folders | `cup folders <spaceId>` | :white_check_mark: |
236
236
  | List members | `cup members` | :construction: |
237
237
 
238
238
  ### Goals & Key Results
@@ -245,13 +245,14 @@ Status: :white_check_mark: implemented | :construction: planned | :no_entry_sign
245
245
 
246
246
  ### Docs
247
247
 
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
+ | Feature | Command | Status |
249
+ | ---------------- | ------------------------------------ | ------------------ |
250
+ | Search docs | `cup docs [query]` | :white_check_mark: |
251
+ | View doc / page | `cup doc <docId> [pageId]` | :white_check_mark: |
252
+ | All page content | `cup doc-pages <docId>` | :white_check_mark: |
253
+ | Create doc | `cup doc-create <title>` | :white_check_mark: |
254
+ | Create page | `cup doc-page-create <docId> <name>` | :white_check_mark: |
255
+ | Edit page | `cup doc-page-edit <docId> <pageId>` | :white_check_mark: |
255
256
 
256
257
  ### Attachments
257
258
 
package/dist/index.js CHANGED
@@ -371,6 +371,17 @@ var ClickUpClient = class {
371
371
  }
372
372
  return entries;
373
373
  }
374
+ async updateTimeEntry(teamId, timeEntryId, updates) {
375
+ const data = await this.request(
376
+ `/team/${teamId}/time_entries/${timeEntryId}`,
377
+ { method: "PUT", body: JSON.stringify(updates) }
378
+ );
379
+ return data.data;
380
+ }
381
+ async getSpaceTags(spaceId) {
382
+ const data = await this.request(`/space/${spaceId}/tag`);
383
+ return data.tags ?? [];
384
+ }
374
385
  async deleteTimeEntry(teamId, timeEntryId) {
375
386
  await this.request(`/team/${teamId}/time_entries/${timeEntryId}`, {
376
387
  method: "DELETE"
@@ -443,6 +454,21 @@ var ClickUpClient = class {
443
454
  body: JSON.stringify(updates)
444
455
  });
445
456
  }
457
+ async getDoc(workspaceId, docId) {
458
+ return this.requestV3(`/workspaces/${workspaceId}/docs/${docId}`);
459
+ }
460
+ async getDocPageListing(workspaceId, docId) {
461
+ const data = await this.requestV3(
462
+ `/workspaces/${workspaceId}/docs/${docId}/pagelisting`
463
+ );
464
+ return data.pages ?? [];
465
+ }
466
+ async getDocPages(workspaceId, docId) {
467
+ const data = await this.requestV3(
468
+ `/workspaces/${workspaceId}/docs/${docId}/pages?content_format=text/md`
469
+ );
470
+ return data.pages ?? [];
471
+ }
446
472
  };
447
473
 
448
474
  // src/config.ts
@@ -2091,7 +2117,7 @@ function bashCompletion(name) {
2091
2117
  cword=$COMP_CWORD
2092
2118
  fi
2093
2119
 
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"
2120
+ 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 tags checklist time docs doc doc-create doc-pages doc-page-create doc-page-edit folders config completion"
2095
2121
 
2096
2122
  if [[ $cword -eq 1 ]]; then
2097
2123
  COMPREPLY=($(compgen -W "$commands --help --version" -- "$cur"))
@@ -2221,6 +2247,15 @@ function bashCompletion(name) {
2221
2247
  doc)
2222
2248
  COMPREPLY=($(compgen -W "--json" -- "$cur"))
2223
2249
  ;;
2250
+ doc-pages)
2251
+ COMPREPLY=($(compgen -W "--json" -- "$cur"))
2252
+ ;;
2253
+ tags)
2254
+ COMPREPLY=($(compgen -W "--json" -- "$cur"))
2255
+ ;;
2256
+ folders)
2257
+ COMPREPLY=($(compgen -W "--name --json" -- "$cur"))
2258
+ ;;
2224
2259
  doc-create)
2225
2260
  COMPREPLY=($(compgen -W "-c --content --json" -- "$cur"))
2226
2261
  ;;
@@ -2291,10 +2326,13 @@ _${name}() {
2291
2326
  'link:Add or remove a link between two tasks'
2292
2327
  'attach:Upload a file attachment to a task'
2293
2328
  'docs:List workspace docs'
2294
- 'doc:View a doc page'
2329
+ 'doc:View a doc or doc page'
2295
2330
  'doc-create:Create a new doc'
2331
+ 'doc-pages:List all pages in a doc with content'
2296
2332
  'doc-page-create:Create a page in a doc'
2297
2333
  'doc-page-edit:Edit a doc page'
2334
+ 'tags:List tags in a space'
2335
+ 'folders:List folders in a space'
2298
2336
  'config:Manage CLI configuration'
2299
2337
  'completion:Output shell completion script'
2300
2338
  )
@@ -2628,6 +2666,22 @@ _${name}() {
2628
2666
  '2:page_id:' \\
2629
2667
  '--json[Force JSON output]'
2630
2668
  ;;
2669
+ doc-pages)
2670
+ _arguments \\
2671
+ '1:doc_id:' \\
2672
+ '--json[Force JSON output]'
2673
+ ;;
2674
+ tags)
2675
+ _arguments \\
2676
+ '1:space_id:' \\
2677
+ '--json[Force JSON output]'
2678
+ ;;
2679
+ folders)
2680
+ _arguments \\
2681
+ '1:space_id:' \\
2682
+ '--name[Filter by folder name]:text:' \\
2683
+ '--json[Force JSON output]'
2684
+ ;;
2631
2685
  doc-create)
2632
2686
  _arguments \\
2633
2687
  '1:title:' \\
@@ -2725,10 +2779,13 @@ complete -c ${name} -n __fish_use_subcommand -a reply -d 'Reply to a comment'
2725
2779
  complete -c ${name} -n __fish_use_subcommand -a link -d 'Add or remove a link between two tasks'
2726
2780
  complete -c ${name} -n __fish_use_subcommand -a attach -d 'Upload a file attachment to a task'
2727
2781
  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'
2782
+ complete -c ${name} -n __fish_use_subcommand -a doc -d 'View a doc or doc page'
2729
2783
  complete -c ${name} -n __fish_use_subcommand -a doc-create -d 'Create a new doc'
2784
+ complete -c ${name} -n __fish_use_subcommand -a doc-pages -d 'List all pages in a doc with content'
2730
2785
  complete -c ${name} -n __fish_use_subcommand -a doc-page-create -d 'Create a page in a doc'
2731
2786
  complete -c ${name} -n __fish_use_subcommand -a doc-page-edit -d 'Edit a doc page'
2787
+ complete -c ${name} -n __fish_use_subcommand -a tags -d 'List tags in a space'
2788
+ complete -c ${name} -n __fish_use_subcommand -a folders -d 'List folders in a space'
2732
2789
  complete -c ${name} -n __fish_use_subcommand -a config -d 'Manage CLI configuration'
2733
2790
  complete -c ${name} -n __fish_use_subcommand -a completion -d 'Output shell completion script'
2734
2791
 
@@ -2886,6 +2943,13 @@ complete -c ${name} -n '__fish_seen_subcommand_from docs' -l json -d 'Force JSON
2886
2943
 
2887
2944
  complete -c ${name} -n '__fish_seen_subcommand_from doc' -l json -d 'Force JSON output'
2888
2945
 
2946
+ complete -c ${name} -n '__fish_seen_subcommand_from doc-pages' -l json -d 'Force JSON output'
2947
+
2948
+ complete -c ${name} -n '__fish_seen_subcommand_from tags' -l json -d 'Force JSON output'
2949
+
2950
+ complete -c ${name} -n '__fish_seen_subcommand_from folders' -l name -d 'Filter by folder name'
2951
+ complete -c ${name} -n '__fish_seen_subcommand_from folders' -l json -d 'Force JSON output'
2952
+
2889
2953
  complete -c ${name} -n '__fish_seen_subcommand_from doc-create' -s c -l content -d 'Initial content'
2890
2954
  complete -c ${name} -n '__fish_seen_subcommand_from doc-create' -l json -d 'Force JSON output'
2891
2955
 
@@ -3296,10 +3360,76 @@ function formatDocsMarkdown(docs) {
3296
3360
  }
3297
3361
 
3298
3362
  // src/commands/doc.ts
3363
+ import chalk8 from "chalk";
3364
+ async function getDocInfo(config, docId) {
3365
+ const client = new ClickUpClient(config);
3366
+ const [doc, pages] = await Promise.all([
3367
+ client.getDoc(config.teamId, docId),
3368
+ client.getDocPageListing(config.teamId, docId)
3369
+ ]);
3370
+ return { doc, pages };
3371
+ }
3372
+ function formatDocInfo(doc, pages, indent = 0) {
3373
+ const lines = [];
3374
+ if (indent === 0) {
3375
+ lines.push(`${chalk8.bold(doc.name)} ${chalk8.dim(doc.id)}`);
3376
+ if (pages.length === 0) {
3377
+ lines.push(" (no pages)");
3378
+ }
3379
+ }
3380
+ for (const page of pages) {
3381
+ const prefix = " ".repeat(indent + 1);
3382
+ lines.push(`${prefix}${page.name} ${chalk8.dim(page.id)}`);
3383
+ if (page.pages && page.pages.length > 0) {
3384
+ lines.push(formatDocInfo(doc, page.pages, indent + 1));
3385
+ }
3386
+ }
3387
+ return lines.join("\n");
3388
+ }
3389
+ function formatDocInfoMarkdown(doc, pages, indent = 0) {
3390
+ const lines = [];
3391
+ if (indent === 0) {
3392
+ lines.push(`# ${doc.name}`);
3393
+ lines.push(`ID: ${doc.id}`);
3394
+ lines.push("");
3395
+ if (pages.length === 0) {
3396
+ lines.push("No pages.");
3397
+ return lines.join("\n");
3398
+ }
3399
+ lines.push("## Pages");
3400
+ }
3401
+ for (const page of pages) {
3402
+ const prefix = " ".repeat(indent);
3403
+ lines.push(`${prefix}- **${page.name}** (${page.id})`);
3404
+ if (page.pages && page.pages.length > 0) {
3405
+ lines.push(formatDocInfoMarkdown(doc, page.pages, indent + 1));
3406
+ }
3407
+ }
3408
+ return lines.join("\n");
3409
+ }
3299
3410
  async function getDocPage(config, docId, pageId) {
3300
3411
  const client = new ClickUpClient(config);
3301
3412
  return client.getDocPage(config.teamId, docId, pageId);
3302
3413
  }
3414
+ async function getAllDocPages(config, docId) {
3415
+ const client = new ClickUpClient(config);
3416
+ return client.getDocPages(config.teamId, docId);
3417
+ }
3418
+ function formatDocPages(pages) {
3419
+ if (pages.length === 0) return "No pages found";
3420
+ return pages.map((p) => {
3421
+ const header = `# ${p.name}
3422
+ `;
3423
+ return header + (p.content ?? "");
3424
+ }).join("\n\n---\n\n");
3425
+ }
3426
+ function formatDocPagesMarkdown(pages) {
3427
+ if (pages.length === 0) return "No pages found";
3428
+ return pages.map((p) => {
3429
+ const header = `# ${p.name}`;
3430
+ return header + "\n\n" + (p.content ?? "");
3431
+ }).join("\n\n---\n\n");
3432
+ }
3303
3433
  async function createDoc(config, title, content) {
3304
3434
  if (!title.trim()) throw new Error("Doc title cannot be empty");
3305
3435
  const client = new ClickUpClient(config);
@@ -3319,8 +3449,44 @@ async function editDocPage(config, docId, pageId, updates) {
3319
3449
  return client.editDocPage(config.teamId, docId, pageId, updates);
3320
3450
  }
3321
3451
 
3452
+ // src/commands/folders.ts
3453
+ import chalk9 from "chalk";
3454
+ async function listFolders(config, spaceId, nameFilter) {
3455
+ const client = new ClickUpClient(config);
3456
+ const folders = await client.getFolders(spaceId);
3457
+ let filtered = folders;
3458
+ if (nameFilter) {
3459
+ const lower = nameFilter.toLowerCase();
3460
+ filtered = folders.filter((f) => f.name.toLowerCase().includes(lower));
3461
+ }
3462
+ const results = [];
3463
+ for (const folder of filtered) {
3464
+ const lists = await client.getFolderLists(folder.id);
3465
+ results.push({ id: folder.id, name: folder.name, lists });
3466
+ }
3467
+ return results;
3468
+ }
3469
+ function formatFolders(folders) {
3470
+ if (folders.length === 0) return "No folders found";
3471
+ return folders.map((f) => {
3472
+ const header = `${chalk9.bold(f.name)} ${chalk9.dim(f.id)}`;
3473
+ if (f.lists.length === 0) return header;
3474
+ const listLines = f.lists.map((l) => ` ${l.name} ${chalk9.dim(l.id)}`);
3475
+ return [header, ...listLines].join("\n");
3476
+ }).join("\n\n");
3477
+ }
3478
+ function formatFoldersMarkdown(folders) {
3479
+ if (folders.length === 0) return "No folders found";
3480
+ return folders.map((f) => {
3481
+ const header = `- **${f.name}** (${f.id})`;
3482
+ if (f.lists.length === 0) return header;
3483
+ const listLines = f.lists.map((l) => ` - ${l.name} (${l.id})`);
3484
+ return [header, ...listLines].join("\n");
3485
+ }).join("\n");
3486
+ }
3487
+
3322
3488
  // src/commands/time.ts
3323
- import chalk8 from "chalk";
3489
+ import chalk10 from "chalk";
3324
3490
  async function startTimer(config, taskId, description) {
3325
3491
  const client = new ClickUpClient(config);
3326
3492
  return client.startTimeEntry(config.teamId, taskId, description);
@@ -3349,6 +3515,20 @@ async function listTimeEntries(config, opts) {
3349
3515
  taskId: opts?.taskId
3350
3516
  });
3351
3517
  }
3518
+ async function updateTimeEntry(config, timeEntryId, opts) {
3519
+ if (!opts.description && !opts.duration) {
3520
+ throw new Error("Provide --description or --duration to update");
3521
+ }
3522
+ const client = new ClickUpClient(config);
3523
+ const updates = {};
3524
+ if (opts.description) updates.description = opts.description;
3525
+ if (opts.duration) updates.duration = parseTimeEstimate(opts.duration);
3526
+ return client.updateTimeEntry(config.teamId, timeEntryId, updates);
3527
+ }
3528
+ async function deleteTimeEntry(config, timeEntryId) {
3529
+ const client = new ClickUpClient(config);
3530
+ await client.deleteTimeEntry(config.teamId, timeEntryId);
3531
+ }
3352
3532
  function formatTimeEntry(entry) {
3353
3533
  const lines = [];
3354
3534
  const taskName = entry.task?.name ?? "No task";
@@ -3356,8 +3536,8 @@ function formatTimeEntry(entry) {
3356
3536
  const isRunning = entry.duration < 0;
3357
3537
  const elapsed = isRunning ? Date.now() - Number(entry.start) : entry.duration;
3358
3538
  const durationStr = formatDuration(elapsed);
3359
- const status = isRunning ? chalk8.green("RUNNING") : "";
3360
- lines.push(`${chalk8.bold(taskName)} ${chalk8.dim(taskId)} ${status}`);
3539
+ const status = isRunning ? chalk10.green("RUNNING") : "";
3540
+ lines.push(`${chalk10.bold(taskName)} ${chalk10.dim(taskId)} ${status}`);
3361
3541
  lines.push(
3362
3542
  ` ${durationStr} - ${formatTimestamp(entry.start)}${entry.description ? ` - ${entry.description}` : ""}`
3363
3543
  );
@@ -3381,6 +3561,21 @@ function formatTimeEntriesMarkdown(entries) {
3381
3561
  return entries.map(formatTimeEntryMarkdown).join("\n");
3382
3562
  }
3383
3563
 
3564
+ // src/commands/tags.ts
3565
+ import chalk11 from "chalk";
3566
+ async function listSpaceTags(config, spaceId) {
3567
+ const client = new ClickUpClient(config);
3568
+ return client.getSpaceTags(spaceId);
3569
+ }
3570
+ function formatTags(tags) {
3571
+ if (tags.length === 0) return "No tags found";
3572
+ return tags.map((t) => chalk11.bold(t.name)).join(", ");
3573
+ }
3574
+ function formatTagsMarkdown(tags) {
3575
+ if (tags.length === 0) return "No tags found";
3576
+ return tags.map((t) => `- ${t.name}`).join("\n");
3577
+ }
3578
+
3384
3579
  // src/index.ts
3385
3580
  var require2 = createRequire(import.meta.url);
3386
3581
  var { version } = require2("../package.json");
@@ -3923,6 +4118,48 @@ timeCmd.command("list").description("List recent time entries (default: last 7 d
3923
4118
  }
3924
4119
  })
3925
4120
  );
4121
+ timeCmd.command("update <timeEntryId>").description("Update a time entry").option("-d, --description <text>", "New description").option("--duration <duration>", 'New duration (e.g. "2h", "30m")').option("--json", "Force JSON output even in terminal").action(
4122
+ wrapAction(
4123
+ async (timeEntryId, opts) => {
4124
+ const config = loadConfig();
4125
+ const entry = await updateTimeEntry(config, timeEntryId, {
4126
+ description: opts.description,
4127
+ duration: opts.duration
4128
+ });
4129
+ if (shouldOutputJson(opts.json ?? false)) {
4130
+ console.log(JSON.stringify(entry, null, 2));
4131
+ } else if (isTTY()) {
4132
+ console.log(formatTimeEntry(entry));
4133
+ } else {
4134
+ console.log(formatTimeEntryMarkdown(entry));
4135
+ }
4136
+ }
4137
+ )
4138
+ );
4139
+ timeCmd.command("delete <timeEntryId>").description("Delete a time entry").option("--json", "Force JSON output even in terminal").action(
4140
+ wrapAction(async (timeEntryId, opts) => {
4141
+ const config = loadConfig();
4142
+ await deleteTimeEntry(config, timeEntryId);
4143
+ if (shouldOutputJson(opts.json ?? false)) {
4144
+ console.log(JSON.stringify({ deleted: timeEntryId }));
4145
+ } else {
4146
+ console.log(`Deleted time entry ${timeEntryId}`);
4147
+ }
4148
+ })
4149
+ );
4150
+ program.command("tags <spaceId>").description("List tags in a space").option("--json", "Force JSON output even in terminal").action(
4151
+ wrapAction(async (spaceId, opts) => {
4152
+ const config = loadConfig();
4153
+ const tags = await listSpaceTags(config, spaceId);
4154
+ if (shouldOutputJson(opts.json ?? false)) {
4155
+ console.log(JSON.stringify(tags, null, 2));
4156
+ } else if (isTTY()) {
4157
+ console.log(formatTags(tags));
4158
+ } else {
4159
+ console.log(formatTagsMarkdown(tags));
4160
+ }
4161
+ })
4162
+ );
3926
4163
  program.command("docs [query]").description("List workspace docs (optionally filter by name)").option("--json", "Force JSON output even in terminal").action(
3927
4164
  wrapAction(async (query, opts) => {
3928
4165
  const config = loadConfig();
@@ -3936,16 +4173,53 @@ program.command("docs [query]").description("List workspace docs (optionally fil
3936
4173
  }
3937
4174
  })
3938
4175
  );
3939
- program.command("doc <docId> <pageId>").description("View a doc page").option("--json", "Force JSON output even in terminal").action(
4176
+ program.command("doc <docId> [pageId]").description("View a doc (metadata + page tree) or a specific page").option("--json", "Force JSON output even in terminal").action(
3940
4177
  wrapAction(async (docId, pageId, opts) => {
3941
4178
  const config = loadConfig();
3942
- const page = await getDocPage(config, docId, pageId);
4179
+ if (pageId) {
4180
+ const page = await getDocPage(config, docId, pageId);
4181
+ if (shouldOutputJson(opts.json ?? false)) {
4182
+ console.log(JSON.stringify(page, null, 2));
4183
+ } else {
4184
+ if (page.name) console.log(`# ${page.name}
4185
+ `);
4186
+ console.log(page.content ?? "");
4187
+ }
4188
+ } else {
4189
+ const { doc, pages } = await getDocInfo(config, docId);
4190
+ if (shouldOutputJson(opts.json ?? false)) {
4191
+ console.log(JSON.stringify({ ...doc, pages }, null, 2));
4192
+ } else if (isTTY()) {
4193
+ console.log(formatDocInfo(doc, pages));
4194
+ } else {
4195
+ console.log(formatDocInfoMarkdown(doc, pages));
4196
+ }
4197
+ }
4198
+ })
4199
+ );
4200
+ program.command("doc-pages <docId>").description("List all pages in a doc with content").option("--json", "Force JSON output even in terminal").action(
4201
+ wrapAction(async (docId, opts) => {
4202
+ const config = loadConfig();
4203
+ const pages = await getAllDocPages(config, docId);
3943
4204
  if (shouldOutputJson(opts.json ?? false)) {
3944
- console.log(JSON.stringify(page, null, 2));
4205
+ console.log(JSON.stringify(pages, null, 2));
4206
+ } else if (isTTY()) {
4207
+ console.log(formatDocPages(pages));
3945
4208
  } else {
3946
- if (page.name) console.log(`# ${page.name}
3947
- `);
3948
- console.log(page.content ?? "");
4209
+ console.log(formatDocPagesMarkdown(pages));
4210
+ }
4211
+ })
4212
+ );
4213
+ program.command("folders <spaceId>").description("List folders in a space (with their lists)").option("--name <partial>", "Filter folders by partial name match").option("--json", "Force JSON output even in terminal").action(
4214
+ wrapAction(async (spaceId, opts) => {
4215
+ const config = loadConfig();
4216
+ const folders = await listFolders(config, spaceId, opts.name);
4217
+ if (shouldOutputJson(opts.json ?? false)) {
4218
+ console.log(JSON.stringify(folders, null, 2));
4219
+ } else if (isTTY()) {
4220
+ console.log(formatFolders(folders));
4221
+ } else {
4222
+ console.log(formatFoldersMarkdown(folders));
3949
4223
  }
3950
4224
  })
3951
4225
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@krodak/clickup-cli",
3
- "version": "1.1.0",
3
+ "version": "1.3.0",
4
4
  "description": "ClickUp CLI for AI agents and humans",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -52,8 +52,11 @@ 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 folders <spaceId> [--name partial] [--json]` | Folders in a space (with their lists) |
56
+ | `cup tags <spaceId> [--json]` | List tags available in a space |
55
57
  | `cup docs [query] [--json]` | List workspace docs (optionally filter by name) |
56
- | `cup doc <docId> <pageId> [--json]` | View a doc page (markdown content) |
58
+ | `cup doc <docId> [pageId] [--json]` | View doc metadata + page tree, or a specific page |
59
+ | `cup doc-pages <docId> [--json]` | All pages in a doc with content |
57
60
 
58
61
  ### Write
59
62
 
@@ -85,6 +88,8 @@ All commands support `--help` for full flag details.
85
88
  | `cup time status [--json]` | Show currently running timer |
86
89
  | `cup time log <taskId> <duration> [-d desc] [--json]` | Log manual time entry (e.g. "2h", "30m") |
87
90
  | `cup time list [--days n] [--task id] [--json]` | List recent time entries |
91
+ | `cup time update <timeEntryId> [-d desc] [--duration dur] [--json]` | Update a time entry |
92
+ | `cup time delete <timeEntryId> [--json]` | Delete a time entry |
88
93
  | `cup doc-create <title> [-c content] [--json]` | Create a new doc |
89
94
  | `cup doc-page-create <docId> <name> [-c content] [--parent-page pageId] [--json]` | Create a page in a doc |
90
95
  | `cup doc-page-edit <docId> <pageId> [--name text] [-c content] [--json]` | Edit a doc page |
@@ -121,7 +126,10 @@ All commands support `--help` for full flag details.
121
126
  | `cup overdue` | Excludes closed tasks, sorted most overdue first |
122
127
  | `cup open` | Tries task ID first, falls back to name search |
123
128
  | `cup checklist` | Full CRUD for task checklists: view, create, delete, add-item, edit-item, delete-item |
124
- | `cup time` | Track time: start/stop timer, log entries, list history. Duration format: "2h", "30m", "1h30m" |
129
+ | `cup tags` | List available tags in a space. Useful for discovering valid tag names before using `cup tag` |
130
+ | `cup time` | Track time: start/stop timer, log entries, list history, update/delete entries. Duration format: "2h", "30m", "1h30m" |
131
+ | `cup time update` | Update a time entry description or duration |
132
+ | `cup time delete` | Delete a time entry |
125
133
  | `cup comment-edit` | Edit comment text and resolution status |
126
134
  | `cup comment-delete` | Delete a comment |
127
135
  | `cup replies` / `cup reply` | View and post threaded comment replies |
@@ -129,8 +137,10 @@ All commands support `--help` for full flag details.
129
137
  | `cup link` + custom IDs | Both IDs must be the same type (both custom or both native). Mixing may not work |
130
138
  | `cup link` | Link/unlink tasks (different from dependencies) |
131
139
  | `cup attach` | Upload files to tasks. Attachments shown in `cup task` detail view |
140
+ | `cup folders` | List folders in a space with their contained lists |
132
141
  | `cup docs` | List and search workspace docs by name |
133
- | `cup doc` | View a doc page content (markdown) |
142
+ | `cup doc` | View doc metadata + page tree (no pageId), or a specific page (with pageId) |
143
+ | `cup doc-pages` | Dump all pages in a doc with full content |
134
144
  | `cup doc-create` | Create a new doc with optional initial content |
135
145
  | `cup doc-page-create` | Create a page in a doc, optionally nested under a parent page |
136
146
  | `cup doc-page-edit` | Edit a doc page name or content |
@@ -210,7 +220,9 @@ cup delete abc123def --confirm # irreversible!
210
220
  ```bash
211
221
  cup docs # list all docs
212
222
  cup docs "design" # search docs by name
223
+ cup doc <docId> # doc metadata + page tree
213
224
  cup doc <docId> <pageId> # view page content
225
+ cup doc-pages <docId> # all pages with content
214
226
  cup doc-create "Architecture Notes" # create a doc
215
227
  cup doc-create "Notes" -c "# Draft" # create with content
216
228
  cup doc-page-create <docId> "New Section" # add page to doc
@@ -224,6 +236,8 @@ cup doc-page-edit <docId> <pageId> -c "# Updated content" # edit content
224
236
  ```bash
225
237
  cup spaces # all spaces
226
238
  cup spaces --name "Engineering" # find space ID by name
239
+ cup folders <spaceId> # folders with their lists
240
+ cup folders <spaceId> --name "sprint" # filter folders by name
227
241
  cup lists <spaceId> # lists in a space (needs ID from cup spaces)
228
242
  cup sprints # all sprints across folders
229
243
  cup auth # verify token works