Skip to content

Commit 34ec6d1

Browse files
isaacrowntreeclaude
andcommitted
fix: review findings and update docs for v0.28-v0.31 features
Fixes from code review: - Add float64 case to getIntValue (custom field dates/emojis from JSON) - Fix semaphore drain deadlock in searchTaskComments on context cancel (replaced cap-fill pattern with sync.WaitGroup) - Normalize task API paths: remove trailing slash between ID and query - Use strconv.FormatInt for 32-bit safety in date serialization - Remove dead dedup function and its tests Update SKILL.md and README with all new features: folder/list commands, chat send, --list-name, --assignee search, --include-closed, bulk delete, recursive view, checklist item edit. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 256977f commit 34ec6d1

8 files changed

Lines changed: 101 additions & 49 deletions

File tree

README.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,17 @@ See the [getting started guide](https://triptechtravel.github.io/clickup-cli/get
3434
3535
## What it does
3636
37-
- **Task management** -- view, create, edit, search, and bulk-edit tasks with custom fields, tags, points, and time estimates
37+
- **Task management** -- view, create, edit, search, bulk-edit, and bulk-delete tasks with custom fields, tags, points, and time estimates
38+
- **Folder & list management** -- browse folders and lists, select defaults per directory, create tasks by list name
3839
- **Docs** -- list, view, create ClickUp Docs and manage their pages (list, view, create, edit) via the v3 API
3940
- **Git integration** -- auto-detects task IDs from branch names and links PRs, branches, and commits to ClickUp
4041
- **Sprint dashboard** -- `sprint current` shows tasks grouped by status; `task create --current` creates tasks in the active sprint
4142
- **Time tracking** -- log time, view per-task entries, or query workspace-wide timesheets by date range
4243
- **Comments & inbox** -- add comments with @mentions, view your recent mentions across the workspace
44+
- **Chat** -- send messages to ClickUp chat channels
45+
- **Search** -- server-side search with parallel space traversal, `--assignee` filtering (name, username, ID, or `me`)
4346
- **Fuzzy status matching** -- set statuses with partial input (`"review"` matches `"code review"`)
44-
- **AI-friendly** -- `--json` output and explicit flags make it easy for AI agents to read and update tasks
47+
- **AI-friendly** -- `--json` output, recursive task view, and explicit flags make it easy for AI agents to read and update tasks
4548
- **CI/CD ready** -- `--with-token`, exit codes, and JSON output for automation; includes GitHub Actions examples
4649
4750
## Commands
@@ -50,13 +53,16 @@ Full command list with flags and examples: **[Command reference](https://triptec
5053
5154
| Area | Key commands |
5255
|------|-------------|
53-
| **Tasks** | `task view`, `task create`, `task edit`, `task search`, `task recent` |
56+
| **Tasks** | `task view`, `task create`, `task edit`, `task search`, `task delete`, `task recent` |
57+
| **Folders** | `folder list`, `folder select` |
58+
| **Lists** | `list list`, `list select` |
5459
| **Docs** | `doc list`, `doc view`, `doc create`, `doc page list`, `doc page view`, `doc page create`, `doc page edit` |
5560
| **Time** | `task time log`, `task time list` |
5661
| **Status** | `status set`, `status list`, `status add` |
5762
| **Git** | `link pr`, `link sync`, `link branch`, `link commit` |
5863
| **Sprints** | `sprint current`, `sprint list` |
5964
| **Comments** | `comment add`, `comment list` |
65+
| **Chat** | `chat send` |
6066
| **Attachments** | `attachment list`, `attachment add` |
6167
| **Workspace** | `inbox`, `member list`, `space select`, `tag list`, `field list` |
6268

internal/apiv2/local.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ func DependencyQuery(dependsOn, dependencyOf string, customTaskIDs bool, teamID
7171
// GetTaskLocal fetches a single task. qs is a query string (e.g. from TaskQuery).
7272
func GetTaskLocal(ctx context.Context, client *api.Client, taskID, qs string) (*clickup.Task, error) {
7373
var task clickup.Task
74-
path := fmt.Sprintf("task/%s/%s", taskID, qs)
74+
path := fmt.Sprintf("task/%s%s", taskID, qs)
7575
if err := do(ctx, client, "GET", path, nil, &task); err != nil {
7676
return nil, err
7777
}
@@ -104,7 +104,7 @@ func CreateTaskLocal(ctx context.Context, client *api.Client, listID string, req
104104
// TaskAssigneeUpdateRequest are used.
105105
func UpdateTaskLocal(ctx context.Context, client *api.Client, taskID string, req any, qs string) (*clickup.Task, error) {
106106
var task clickup.Task
107-
path := fmt.Sprintf("task/%s/%s", taskID, qs)
107+
path := fmt.Sprintf("task/%s%s", taskID, qs)
108108
if err := do(ctx, client, "PUT", path, req, &task); err != nil {
109109
return nil, err
110110
}
@@ -113,7 +113,7 @@ func UpdateTaskLocal(ctx context.Context, client *api.Client, taskID string, req
113113

114114
// DeleteTaskLocal deletes a task.
115115
func DeleteTaskLocal(ctx context.Context, client *api.Client, taskID, qs string) error {
116-
path := fmt.Sprintf("task/%s/%s", taskID, qs)
116+
path := fmt.Sprintf("task/%s%s", taskID, qs)
117117
return do(ctx, client, "DELETE", path, nil, nil)
118118
}
119119

internal/apiv2/local_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ func TestGetTaskLocal(t *testing.T) {
182182

183183
require.NoError(t, err)
184184
assert.Equal(t, "GET", capturedMethod)
185-
assert.Contains(t, capturedPath, "/task/task123/")
185+
assert.Contains(t, capturedPath, "/task/task123")
186186

187187
// Verify decoded fields
188188
assert.Equal(t, "task123", task.ID)
@@ -263,7 +263,7 @@ func TestUpdateTaskLocal(t *testing.T) {
263263

264264
require.NoError(t, err)
265265
assert.Equal(t, "PUT", capturedMethod)
266-
assert.Contains(t, capturedPath, "/task/task123/")
266+
assert.Contains(t, capturedPath, "/task/task123")
267267
assert.Equal(t, "Updated Name", capturedBody["name"])
268268
assert.EqualValues(t, 2, capturedBody["priority"])
269269
assert.Equal(t, "task123", task.ID)
@@ -285,7 +285,7 @@ func TestDeleteTaskLocal(t *testing.T) {
285285

286286
require.NoError(t, err)
287287
assert.Equal(t, "DELETE", capturedMethod)
288-
assert.Contains(t, capturedPath, "/task/task123/")
288+
assert.Contains(t, capturedPath, "/task/task123")
289289
}
290290

291291
func TestGetFilteredTeamTasksLocal(t *testing.T) {

internal/clickup/custom_fields.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,8 @@ func getIntValue(v interface{}) (int64, bool) {
316316
switch v := v.(type) {
317317
case int:
318318
return int64(v), true
319+
case float64:
320+
return int64(v), true
319321
case string:
320322
num, err := strconv.ParseInt(v, 10, 64)
321323
if err != nil {

internal/clickup/date.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ func (d Date) MarshalJSON() ([]byte, error) {
9191
}
9292

9393
func int64ToJsonNumber(n int64) json.Number {
94-
b := []byte(strconv.Itoa(int(n)))
94+
b := []byte(strconv.FormatInt(n, 10))
9595

9696
var v json.Number
9797
if err := json.Unmarshal(b, &v); err != nil {

pkg/cmd/task/search.go

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -693,23 +693,24 @@ func searchTaskComments(ctx context.Context, client *api.Client, query string, t
693693
// Fan out with bounded concurrency.
694694
sem := make(chan struct{}, maxWorkers)
695695
resultCh := make(chan result, len(tasks))
696+
var wg sync.WaitGroup
696697

697698
for _, t := range tasks {
698699
if ctx.Err() != nil {
699700
break
700701
}
701702
sem <- struct{}{} // acquire
703+
wg.Add(1)
702704
go func(task searchTask) {
705+
defer wg.Done()
703706
defer func() { <-sem }() // release
704707
match := taskMatchesComment(ctx, client, query, task.ID)
705708
resultCh <- result{task: task, match: match}
706709
}(t)
707710
}
708711

709712
// Wait for all goroutines to finish.
710-
for i := 0; i < cap(sem); i++ {
711-
sem <- struct{}{}
712-
}
713+
wg.Wait()
713714
close(resultCh)
714715

715716
var scored []scoredTask
@@ -779,18 +780,6 @@ func dedupScored(tasks []scoredTask) []scoredTask {
779780
return result
780781
}
781782

782-
func dedup(tasks []searchTask) []searchTask {
783-
seen := make(map[string]bool)
784-
var unique []searchTask
785-
for _, t := range tasks {
786-
if !seen[t.ID] {
787-
seen[t.ID] = true
788-
unique = append(unique, t)
789-
}
790-
}
791-
return unique
792-
}
793-
794783
func noResultsPrompt(ios *iostreams.IOStreams, opts *searchOptions) error {
795784
p := prompter.New(ios)
796785
idx, err := p.Select("What would you like to do?", []string{

pkg/cmd/task/search_test.go

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -11,28 +11,6 @@ import (
1111
"github.com/triptechtravel/clickup-cli/internal/testutil"
1212
)
1313

14-
func TestDedup(t *testing.T) {
15-
tasks := []searchTask{
16-
{ID: "1", Name: "Task A"},
17-
{ID: "2", Name: "Task B"},
18-
{ID: "1", Name: "Task A duplicate"},
19-
{ID: "3", Name: "Task C"},
20-
{ID: "2", Name: "Task B duplicate"},
21-
}
22-
23-
result := dedup(tasks)
24-
assert.Len(t, result, 3)
25-
assert.Equal(t, "1", result[0].ID)
26-
assert.Equal(t, "Task A", result[0].Name) // keeps first occurrence
27-
assert.Equal(t, "2", result[1].ID)
28-
assert.Equal(t, "3", result[2].ID)
29-
}
30-
31-
func TestDedup_Empty(t *testing.T) {
32-
result := dedup(nil)
33-
assert.Nil(t, result)
34-
}
35-
3614
func TestNewCmdSearch_Flags(t *testing.T) {
3715
cmd := NewCmdSearch(nil)
3816

skills/clickup-cli/SKILL.md

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ clickup auth login # Authenticate with API token
2222
clickup auth status # Check auth status
2323
```
2424

25-
Configuration is stored in `~/.config/clickup/config.yml`. Supports per-directory defaults for space, team, and folder.
25+
Configuration is stored in `~/.config/clickup/config.yml`. Supports per-directory defaults for space, team, folder, and list.
2626

2727
## Task Management
2828

@@ -45,9 +45,11 @@ clickup task view 86abc1 86abc2 86abc3 --json
4545
clickup task view 86abc1 86abc2 86abc3 --jq '[.[] | {id: .id, tags: [.tags[].name]}]'
4646

4747
# Search tasks by name and description (supports fuzzy matching)
48-
# Uses progressive drill-down: sprint → your tasks → space → workspace
48+
# Uses server-side search (Level 0) with parallel space traversal
4949
clickup task search "login bug"
5050
clickup task search "login bug" --exact # Exact matches only
51+
clickup task search "login bug" --assignee me # Only your tasks
52+
clickup task search "login bug" --assignee "alice" # By name/username/ID
5153

5254
# Recent tasks (excludes archived folders)
5355
clickup task recent
@@ -56,6 +58,19 @@ clickup task recent --sprint # Only current sprint tasks
5658
# List tasks in a specific list
5759
clickup task list --list-id 12345
5860

61+
# List tasks using configured default list (via `list select`)
62+
clickup task list
63+
64+
# Include closed tasks in the list
65+
clickup task list --include-closed
66+
clickup task list -c --list-id 12345
67+
68+
# Recursive view — fetch subtasks and their children in a single tree
69+
clickup task view 86abc123 --recursive --json
70+
71+
# Bulk delete tasks (requires confirmation or -y to skip)
72+
clickup task delete 86abc1 86abc2 86abc3 -y
73+
5974
# Task activity/comment history
6075
clickup task activity CU-abc123
6176
```
@@ -92,6 +107,7 @@ clickup task activity CU-abc123
92107

93108
- `--current`**preferred**: auto-resolves the active sprint list (no need to know the list ID)
94109
- `--list-id` — explicit list ID (use when creating outside the current sprint)
110+
- `--list-name` — resolve a list name to its ID (e.g., `--list-name "Sprint 89"`)
95111
- `--name` — task name (required — follow naming conventions above)
96112
- `--description` or `--markdown-description` — clear description of the work
97113
- `--status` — initial status (e.g., "open", "in progress")
@@ -115,6 +131,13 @@ After creating a task, consider adding checklists for acceptance criteria or sub
115131
clickup task checklist add <task-id> "Acceptance Criteria"
116132
clickup task checklist item add <checklist-id> "Unit tests pass"
117133
clickup task checklist item add <checklist-id> "Code reviewed"
134+
clickup task checklist item add <checklist-id> "Assigned item" --assignee 12345678
135+
```
136+
137+
Example — create a task by list name (resolves name to ID):
138+
139+
```bash
140+
clickup task create --list-name "Sprint 89" --name "Fix bug" --status "open"
118141
```
119142

120143
Example of a well-populated task creation:
@@ -262,6 +285,32 @@ clickup sprint current
262285
clickup sprint list
263286
```
264287

288+
## Folders
289+
290+
```bash
291+
# List folders in a space
292+
clickup folder list
293+
clickup folder list --space 12345
294+
clickup folder list --archived --json
295+
296+
# Select a default folder (interactive)
297+
clickup folder select
298+
clickup folder select --local # per-directory
299+
```
300+
301+
## Lists
302+
303+
```bash
304+
# List lists in a folder
305+
clickup list list --folder 12345
306+
clickup list list --space 12345 # folderless lists
307+
clickup list list --json
308+
309+
# Select a default list (interactive)
310+
clickup list select
311+
clickup list select --local # per-directory
312+
```
313+
265314
## Comments
266315
267316
```bash
@@ -477,6 +526,29 @@ clickup doc page edit <doc-id> <page-id> \
477526
clickup doc create --name "Sprint Retro" --json | jq -r '.id'
478527
```
479528
529+
## Chat
530+
531+
```bash
532+
# Send a message to a chat channel
533+
clickup chat send <channel-id> "Hello team!"
534+
clickup chat send <channel-id> "Deploy complete" --json
535+
```
536+
537+
## Checklists
538+
539+
```bash
540+
# Add a checklist to a task
541+
clickup task checklist add <task-id> "Acceptance Criteria"
542+
543+
# Add items to a checklist
544+
clickup task checklist item add <checklist-id> "Unit tests pass"
545+
clickup task checklist item add <checklist-id> "Assigned item" --assignee 12345678
546+
547+
# Edit checklist items (supports bulk — multiple item IDs)
548+
clickup task checklist item edit <checklist-id> <item-id> --assignee 12345678
549+
clickup task checklist item edit <checklist-id> <item-id1> <item-id2> --assignee 12345678
550+
```
551+
480552
## Common Flags
481553
482554
| Flag | Description |
@@ -499,3 +571,8 @@ clickup doc create --name "Sprint Retro" --json | jq -r '.id'
499571
- **Multi-list**: `task list-add`/`task list-remove` manage secondary list memberships — useful for cross-team sprint planning
500572
- **Naming conventions**: Task names follow `[Work Type] Context — Action (Platform)` format for sprint-board scannability. Check existing tasks in the list for the prevailing convention before creating
501573
- **Tag reuse**: Always check available tags with `clickup tag list` before creating tasks. Use existing tags for consistency; don't invent new ones without user confirmation
574+
- **Per-directory config**: `folder select --local` and `list select --local` store defaults in the current directory, useful for monorepos with different ClickUp contexts
575+
- **Server-side search**: `task search` uses ClickUp's server-side search with parallel space traversal for faster results
576+
- **Assignee shortcut**: `task search --assignee me` filters results to the authenticated user; also accepts names, usernames, or IDs
577+
- **Contextual task list**: `task list` falls back to the configured default list (via `list select`) when no `--list-id` is given
578+
- **Bulk delete**: `task delete ID1 ID2 ID3 -y` deletes multiple tasks in one command

0 commit comments

Comments
 (0)