Skip to main content

Pick the next ClickUp ticket and start a Claude Code session on it. One command from terminal to PR.

Project description

clickup-work

A tiny CLI that picks the next ClickUp ticket assigned to you, cuts the right branch in the right repo, and launches a Claude Code session with the ticket pre-loaded. On exit, it opens a GitHub PR.

One command, from terminal to solving the problem.

Quick start

Four commands from zero to running:

# 1. Install
pipx install clickup-work

# 2. Save your ClickUp API token once (generate at ClickUp → Settings → Apps → API Token)
clickup-work login

# 3. Register the repo you want to work in
clickup-work add-repo ~/projects/my-app

# 4. Go
clickup-work --repo my-app

The token is stored in ~/.config/clickup-work/config.toml with mode 0600, so any new shell can use it without exporting an env var. Full details on each step are below.

What it does

$ clickup-work

⠧ fetching open tickets
✓ found 4 open ticket(s)

  urgent   in progress    Fix flaky checkout tests        [Product / Sprint 12]
  high     to do          Add dark-mode toggle            [Product / Sprint 12]
  normal   to do          Refactor notification queue     [Billing / Infra]
  normal   to do          iOS launch crash                [Mobile / Bugs]
pick a ticket >

Ticket:   iOS launch crash  (86c9abc)
Repo:     /home/you/projects/mobile-app  (nickname: mobile)
Base:     main  (resolved from config [repos.mobile].base_branch)
Branch:   fix/ios-launch-crash  →  PR into main

⠧ preparing branch fix/ios-launch-crash
✓ branch created: fix/ios-launch-crash

launching Claude Code… (exit the session to come back here)

(you work with Claude, commit, exit)

2 commit(s) ahead of main.
push branch and open PR? [Y/n] y
⠧ opening PR
✓ PR opened: https://github.com/you/mobile-app/pull/42

(status picker)
✓ ticket moved: to do → in review

Why

Because switching context between ClickUp, your terminal, your git branches, and your editor is the slowest part of shipping a ticket. This automates the mechanical parts and hands control to Claude Code for the actual work.

  • Picks the right ticket (interactive fzf picker, or --top to auto-pick)
  • Cuts a conventional branch (feat/<slug>, fix/<slug>, docs/<slug>, inferred from ClickUp task type — or override with --prefix)
  • Never guesses the base branch (explicit per-repo config, origin/HEAD fallback, verified against origin before any git operation)
  • Opens a PR automatically via gh when Claude's session ends with commits
  • Skips the PR cleanly if no commits were made (no noise, no force-push)

Requirements

  • Python 3.11+
  • claude — Claude Code CLI
  • git and gh, with gh auth login done
  • fzf (optional; falls back to a numbered picker)
  • A ClickUp API token (generate one under Settings → Apps → API Token)

Install

With pipx (recommended)

pipx install clickup-work

Don't have pipx? pip install --user clickup-work also works on most systems. On Arch/Debian-managed Pythons (PEP 668), use pipx or a venv.

Install the latest dev version directly from GitHub

pipx install git+https://github.com/Azhar-ud/clickup-work.git

From source

git clone https://github.com/Azhar-ud/clickup-work.git
cd clickup-work
python -m venv .venv && source .venv/bin/activate
pip install -e .

clickup-work is now on your PATH.

Configure

1. Token

The recommended one-time setup:

clickup-work login
# Paste your ClickUp API token (input hidden): ********
# ✓ token valid (ClickUp user id 12345)
# ✓ token saved to ~/.config/clickup-work/config.toml (mode 0600 — readable only by you).

The token lives in ~/.config/clickup-work/config.toml, so any new shell can use it — no export … line in ~/.zshrc, no missing-token errors when you open a new terminal.

The env var path still works for CI / scripts and overrides the saved token when both are present:

export CLICKUP_API_TOKEN=pk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

2. Config file

Copy the example and edit:

mkdir -p ~/.config/clickup-work
cp config.toml.example ~/.config/clickup-work/config.toml
$EDITOR ~/.config/clickup-work/config.toml

The quickest way to add a repo is the built-in helper — no TOML syntax to remember:

clickup-work add-repo ~/projects/my-app
# Found repo at /home/you/projects/my-app
# Detected default branch: main
# Nickname for this repo [my-app]: my-app
# Added [repos.my-app] to ~/.config/clickup-work/config.toml

Or hand-write a block:

[repos.my-app]
path          = "/home/you/projects/my-app"
base_branch   = "main"
# branch_prefix = "feat"   # optional override

See config.toml.example for all fields.

Usage

# Pick a ticket from the fzf picker, open it in the named repo
clickup-work --repo my-app

# Skip the picker; take the top-priority ticket
clickup-work --repo my-app --top

# One-off overrides
clickup-work --repo my-app --base staging          # target a different base
clickup-work --repo my-app --prefix fix            # force fix/… prefix
clickup-work --repo my-app --draft                 # open PR as a draft

# Preview only — no git, no Claude
clickup-work --repo my-app --dry-run

# See every API call and git command
clickup-work --repo my-app --verbose

# Register a new repo in config
clickup-work add-repo ~/projects/new-repo [--name nickname] [--base-branch main]

# Personal workload report (this week + next week)
clickup-work workload                              # show your load
clickup-work workload set-capacity 4               # save 4h/day to config

Full flag list

Flag Purpose
--repo NAME_OR_PATH Repo nickname from config, or an absolute/~ path
--base BRANCH Override base branch for this run
--prefix NAME Override branch prefix (feat, fix, chore, docs, …)
--top, -t Auto-pick top-priority ticket (skip picker)
--draft Open the resulting PR as a draft
--no-status Skip the "move ticket to which status?" prompt after the PR opens
--no-time Skip the "track time spent / update estimate?" prompts after the PR opens
--no-assign Skip the "reassign to which member?" prompt after the PR opens
--yes, -y Skip the "push branch and open PR?" confirmation prompt
--dry-run Preview the ticket + plan, touch nothing
--verbose, -v Print every HTTP request and shell command

How it picks the repo (multi-project workflow)

When you're assigned tickets across several ClickUp folders that map to different repos, the tool can route each ticket to the right repo automatically. You don't set anything up upfront — it learns as you go.

First time you pick a ticket from a folder it hasn't seen:

Ticket "iOS launch crash" is in folder "Mobile",
which isn't linked to any repo yet.

Which repo should this folder route to?
  1. marketing  (/home/you/marketing-site)
  2. billing    (/home/you/billing-service)
  3. mobile     (/home/you/mobile-app)
  (or q to cancel and pass --repo manually)
> 3

✓ folder "Mobile" now routes to 'mobile' (saved to ~/.config/clickup-work/config.toml)

The mapping is saved as folder_ids = ["<id>"] inside the repo's config block. Future tickets from that folder skip the prompt and go straight to the right repo.

Resolution order when you run clickup-work:

  1. --repo <name> → always wins; scopes the picker to that repo's folders
  2. default_repo in config → backward-compatible single-repo fallback
  3. Exactly one repo registered → that one
  4. Otherwise → fetch all assigned tickets, pick one, route by folder

Scoping the picker to one project (focus mode):

clickup-work --repo mobile     # only shows tickets from folders linked to 'mobile'

Hand-editing config still works if you prefer:

[repos.mobile]
path        = "/home/you/mobile-app"
base_branch = "main"
folder_ids  = ["901234567", "901234890"]   # optional; tool fills these in

How it picks the base branch

Resolution order for the suggested default, first non-empty wins:

  1. --base <branch> flag
  2. [repos.<name>].base_branch in config
  3. git symbolic-ref refs/remotes/origin/HEAD (auto-detect)
  4. Error out — the tool refuses to guess

Unless --base was passed, the tool then prompts to confirm or override:

base branch [main]: _

Press Enter to accept the suggestion or type a different branch name — useful when a repo has a dev / staging / release line you sometimes branch off instead of main. Passing --base <branch> skips the prompt.

Before any branching, it verifies the chosen base exists on origin:

git ls-remote --exit-code --heads origin <base>

If it doesn't, the tool aborts with a clear message — no stray branches, no PRs targeting a dead base, no silent typos.

How it picks the branch prefix

Condition Prefix
--prefix <name> Whatever you pass
[repos.<name>].branch_prefix in config That value
ClickUp task type contains "bug" / is incident/hotfix fix
ClickUp task type contains "doc" docs
Anything else feat

Post-session behavior

When you exit Claude:

ahead = git rev-list --count origin/<base>..HEAD
  • ahead == 0 → print "no new commits on the branch — skipping PR", exit 0
  • ahead ≥ 1 → ask push branch and open PR? [Y/n]; on yes, git push -u origin <branch> then gh pr create --base <base> --head <branch>. Pass --yes / -y to skip the prompt.
  • --draft passed → PR is opened as a draft

If you answer n at the confirmation, the feature branch stays local — you can push it by hand whenever you're ready. No force-push, no reset.

Once the PR is open, the tool prompts you to move the ClickUp ticket to a new status — pulled live from the ticket's list, so whatever your workspace is configured to use (in review, qa, blocked, …) is what you'll see. Pick one to update, or hit q / Esc to leave it where it is. Pass --no-status to skip the prompt entirely.

After the status prompt, two short follow-ups offer to log time spent and update the ticket's time estimate. Both accept formats like 1h 30m, 90m, or 1.5h (a bare number is treated as minutes). Hit Enter on either to skip just that one, or pass --no-time to skip both.

A final prompt offers to reassign the ticket to another workspace member — useful for "I'm done, please review" handoffs. With fzf installed, you get fuzzy search across name and email (start typing huzaifa and the list narrows as you type). After picking, a follow-up asks whether to remove yourself from the ticket too: No keeps you as a co-assignee, Yes is a clean handoff. Esc or empty pick skips entirely; --no-assign turns the prompt off.

The PR body itself is generated from the commits on the branch — a ## Summary section bullets each commit subject, a ## Test plan checklist is left for you to fill in, and the original ClickUp ticket description is tucked into a collapsed <details> block for reviewer context.

Workload (personal load report)

If your week is part-time, scattered, or being asked about by a PM, the workload subcommand answers "what's on your plate?" in one shot — without opening the ClickUp UI:

$ clickup-work workload

Capacity: 4h/day · 20h/week

This week (May 4 – May 10):
  ████████████████░░░░  16.5h / 20h  ✓ under
  • 86c9abc     Fix auth bug                                 4h  due Wed
  • 86c9def     Refactor cache layer                         8h  due Fri
  • 86c9ghi     Review PR #88                              2.5h  due Thu
  • 86c9jkl     Old overdue ticket                           2h  OVERDUE (2026-05-01)

Next week (May 11 – May 17):
  ████████████████████  26h / 20h  ⚠ OVER by 6h
  • 86c9mno     Migrate session store                       16h  due 2026-05-12
  • 86c9pqr     Spike: new search backend                   10h  due 2026-05-15

⚠ 2 assigned ticket(s) have no time estimate — Workload can't see them:
  • 86c9stu     Investigate flaky test
  • 86c9vwx     Update docs

Tickets without a due date (not bucketed): 1
  • 86c9yza     Onboarding doc                                       4h

Setting your capacity

clickup-work workload set-capacity 4    # 4h/day, weekly capacity = 20h
clickup-work workload set-capacity 4h   # same — `h` suffix accepted
clickup-work workload set-capacity 4.5  # decimals fine

This writes a [workload] block to your config:

[workload]
hours_per_day = 4

The default if absent is 8 (full-time). Weekly capacity is always hours_per_day × 5 weekdays.

Flags

Flag Purpose
--hours-per-day HOURS Override capacity for this run only — does not write config
--no-unestimated Hide the "tickets without a time estimate" section

How tickets land in each section

Ticket has… Lands in…
due_date in this week (or overdue) and a positive time_estimate This week's bar + ticket list
due_date in next week and a positive time_estimate Next week's bar + ticket list
due_date in either week but no estimate Unestimated section (Workload view is blind to these)
No due_date Undated section
due_date more than two weeks out Out of horizon — not shown

The bar is hours_used / weekly_capacity; it fills up as estimates pile on. The header underneath tells you whether you're under, at, or over — and by how many hours.

Why a separate [workload] block

ClickUp's Workload view configures capacity per-user inside the ClickUp UI, but that capacity setting is not exposed through the public REST API. There's no endpoint to read it. So clickup-work keeps its own copy in your local config — set-capacity writes it, the report reads it. If you want the same number in ClickUp's Workload view too, set it there once via the Workload settings panel.

Safety

  • Plaintext of your token never leaves CLICKUP_API_TOKEN (env) or your shell rc. Nothing is written back to disk by this tool.
  • The tool never does git reset --hard, force-pushes, or amends.
  • If a feature branch already exists, it's reused (not reset) — good for resuming partial work.
  • --dry-run runs everything up to "touch disk", including the branch-exists-on-origin check, and stops there.

License

MIT. See LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

clickup_work-0.13.0.tar.gz (42.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

clickup_work-0.13.0-py3-none-any.whl (39.7 kB view details)

Uploaded Python 3

File details

Details for the file clickup_work-0.13.0.tar.gz.

File metadata

  • Download URL: clickup_work-0.13.0.tar.gz
  • Upload date:
  • Size: 42.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for clickup_work-0.13.0.tar.gz
Algorithm Hash digest
SHA256 6cc885cc27c092404ebab9ab0149afbea2ec04495b17051940bbc7f070c7ec2c
MD5 8373baf7f833b10de5441e3c8dd9a304
BLAKE2b-256 4ba19a1adfcf38ac9055d9dd15c7218690e9d6a35749de7d9c3a8e5dcf0562c7

See more details on using hashes here.

Provenance

The following attestation bundles were made for clickup_work-0.13.0.tar.gz:

Publisher: publish.yml on Azhar-ud/clickup-work

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file clickup_work-0.13.0-py3-none-any.whl.

File metadata

  • Download URL: clickup_work-0.13.0-py3-none-any.whl
  • Upload date:
  • Size: 39.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for clickup_work-0.13.0-py3-none-any.whl
Algorithm Hash digest
SHA256 d47bc4a7aa1c0ae436ea825b0ea320a58936b36c59d9d121445de8faf2595db8
MD5 54a658e032dd175b29a8e9ed10920268
BLAKE2b-256 a736d147cb5f44011e032dff1679cf2eb08763cb97fd178ca6a5b6f291e9ff2b

See more details on using hashes here.

Provenance

The following attestation bundles were made for clickup_work-0.13.0-py3-none-any.whl:

Publisher: publish.yml on Azhar-ud/clickup-work

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page