diff --git a/README.md b/README.md index c027f97..01e467a 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,10 @@ cu task create # View task details cu task view +# Archive a task (reversible; keeps history and URL) +cu task archive +cu task archive --undo + # Export tasks to CSV cu export tasks --format csv > tasks.csv ``` diff --git a/docs/site/commands/cu_task.md b/docs/site/commands/cu_task.md index a0a655b..4e4b42d 100644 --- a/docs/site/commands/cu_task.md +++ b/docs/site/commands/cu_task.md @@ -23,6 +23,7 @@ Create, view, update, and manage ClickUp tasks. ### SEE ALSO * [cu](cu.md) - A GitHub CLI-inspired command-line interface for ClickUp +* [cu task archive](cu_task_archive.md) - Archive a task * [cu task close](cu_task_close.md) - Close a task * [cu task create](cu_task_create.md) - Create a new task * [cu task interactive](cu_task_interactive.md) - Interactive task browser diff --git a/docs/site/commands/cu_task_archive.md b/docs/site/commands/cu_task_archive.md new file mode 100644 index 0000000..e572cca --- /dev/null +++ b/docs/site/commands/cu_task_archive.md @@ -0,0 +1,37 @@ +## cu task archive + +Archive a task + +### Synopsis + +Archive a task, removing it from active views while keeping its history +and URL intact. + +Archiving is reversible with --undo. Prefer it over closing a task whose work +has moved elsewhere: closing asserts a status the task never reached, and not +every list even has a status named "closed". + +``` +cu task archive [task-id] [flags] +``` + +### Options + +``` + -h, --help help for archive + --undo Unarchive instead of archiving +``` + +### Options inherited from parent commands + +``` + --config string config file (default is $HOME/.config/cu/config.yaml) + --debug enable debug mode + -o, --output string output format (table|json|yaml|csv) (default "table") +``` + +### SEE ALSO + +* [cu task](cu_task.md) - Manage tasks + +###### Auto generated by spf13/cobra on 28-Aug-2026 diff --git a/internal/api/archive.go b/internal/api/archive.go new file mode 100644 index 0000000..6e99198 --- /dev/null +++ b/internal/api/archive.go @@ -0,0 +1,41 @@ +package api + +import ( + "context" + "fmt" + + "github.com/raksul/go-clickup/clickup" +) + +// archiveRequest is sent directly rather than through clickup.TaskUpdateRequest, +// which has no archived field — the SDK cannot express this update. +type archiveRequest struct { + Archived bool `json:"archived"` +} + +// SetTaskArchived archives or unarchives a task. +// +// Archiving keeps the task, its history and its URL while removing it from +// active views. That makes it the right primitive for retiring a task whose +// work has moved elsewhere: closing it would assert a status the task never +// reached (and "closed" is not even a status name on every list), and deleting +// it destroys the record. +func (c *Client) SetTaskArchived(ctx context.Context, taskID string, archived bool) (*clickup.Task, error) { + if taskID == "" { + return nil, fmt.Errorf("a task id is required") + } + if err := c.rateLimiter.Wait(ctx); err != nil { + return nil, err + } + + req, err := c.client.NewRequest("PUT", fmt.Sprintf("task/%s", taskID), &archiveRequest{Archived: archived}) + if err != nil { + return nil, err + } + + task := new(clickup.Task) + if _, err := c.client.Do(ctx, req, task); err != nil { + return nil, c.handleError(err) + } + return task, nil +} diff --git a/internal/cmd/task.go b/internal/cmd/task.go index 4a0ea6f..c41feed 100644 --- a/internal/cmd/task.go +++ b/internal/cmd/task.go @@ -497,6 +497,47 @@ var taskReopenCmd = &cobra.Command{ }, } +var taskArchiveCmd = &cobra.Command{ + Use: "archive [task-id]", + Short: "Archive a task", + Long: `Archive a task, removing it from active views while keeping its history +and URL intact. + +Archiving is reversible with --undo. Prefer it over closing a task whose work +has moved elsewhere: closing asserts a status the task never reached, and not +every list even has a status named "closed".`, + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + taskID := args[0] + undo, _ := cmd.Flags().GetBool("undo") + + client, err := api.NewClient() + if err != nil { + return err + } + + task, err := client.SetTaskArchived(context.Background(), taskID, !undo) + if err != nil { + return err + } + + verb := "Archived" + if undo { + verb = "Unarchived" + } + + if outputFormat != "table" { + return output.Format(outputFormat, task) + } + + fmt.Printf("✓ %s task %s: %s\n", verb, task.ID, task.Name) + if task.URL != "" { + fmt.Printf(" View in ClickUp: %s\n", task.URL) + } + return nil + }, +} + var taskSearchCmd = &cobra.Command{ Use: "search [query]", Short: "Search for tasks", @@ -683,6 +724,7 @@ func init() { taskCmd.AddCommand(taskCloseCmd) taskCmd.AddCommand(taskReopenCmd) taskCmd.AddCommand(taskSearchCmd) + taskCmd.AddCommand(taskArchiveCmd) // List command flags taskListCmd.Flags().StringP("list", "l", "", "List ID or name") @@ -719,6 +761,7 @@ func init() { taskUpdateCmd.Flags().StringSlice("remove-assignee", []string{}, "Remove assignees (username or ID)") // Reopen command flags + taskArchiveCmd.Flags().Bool("undo", false, "Unarchive instead of archiving") taskCloseCmd.Flags().StringP("status", "s", "", "Status to set (default: the list's done status)") taskReopenCmd.Flags().StringP("status", "s", "", "Status to set (default: the list's first open status)") diff --git a/mkdocs.yml b/mkdocs.yml index 43b4b31..c486d28 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -50,6 +50,7 @@ nav: - cu task close: commands/cu_task_close.md - cu task reopen: commands/cu_task_reopen.md - cu task search: commands/cu_task_search.md + - cu task archive: commands/cu_task_archive.md - cu task interactive: commands/cu_task_interactive.md - Lists: - cu list: commands/cu_list.md