A dense, keyboard-driven local task list backed by your ClickUp data.
Pulls every task assigned to you (across all teams, not closed) into a local SQLite mirror, lets you triage from a fast Vue UI, and pushes title/description/status changes back to ClickUp on the next sync. Single self-contained Go binary — the SPA is embedded.
- Single binary, no runtime deps. SPA + DB migrations embedded.
- Sync: on startup, every 10 minutes (configurable), and on demand via
Sync noworPOST /api/sync. - Scope: tasks assigned to the authenticated user, across all teams (workspaces) the token can see. Closed tasks are not fetched.
- Push back: only
title,description,status(per design). Priority and tags from ClickUp are read-only locally; priority can't be pushed back. - Local-only tags: add tags that don't exist in ClickUp — they persist locally and never leak upstream.
- Verbatim ClickUp statuses: status pills, filter chips, and the
1–9keyboard shortcuts are driven by your list's actual status names and colors. - Open in ClickUp:
oon a focused row (or click theopen ↗link) opens the task atapp.clickup.com/t/{id}in a new tab.
# 1. Drop your token in a .env file
echo 'CLICKUP_API_TOKEN=pk_xxx_...' > .env
# 2. Build and run
make build
./dist/clickdown
# → http://127.0.0.1:7878Get a personal token at https://app.clickup.com/settings/apps.
| Target | What it does |
|---|---|
make build |
Build the SPA + binary for the host platform |
make build-mac |
Build darwin/arm64 and darwin/amd64 binaries |
make build-windows |
Build windows/amd64 binary |
make build-all |
All of the above |
make run |
Build and run |
make dev |
Run Go API on :7878 + Vite dev server on :5173 together |
make dev-api |
Just the Go API |
make dev-web |
Just the Vite dev server (proxies /api to :7878) |
Resolved in order: flags → env → .env file → defaults.
.env is auto-loaded from the working directory and from ~/.clickdown/.env (existing process env always wins).
| Flag | Env | Default | Purpose |
|---|---|---|---|
-token |
CLICKUP_API_TOKEN |
(required for sync) | ClickUp personal API token |
-db |
CLICKDOWN_DB |
~/.clickdown/clickdown.db |
SQLite file path |
-addr |
CLICKDOWN_ADDR |
127.0.0.1:7878 |
HTTP listen address |
-sync-interval |
CLICKDOWN_SYNC_INTERVAL |
10m |
Background sync interval |
The interval is also editable at runtime via the Tweaks panel (gear icon, bottom-right).
| Key | Action |
|---|---|
j / k / ↑ / ↓ |
Move focus |
↵ |
Expand/collapse the focused row |
e |
Edit title of the focused row |
1–9 |
Set status of the focused row (by orderindex) |
o |
Open the focused task in ClickUp (new tab) |
/ |
Focus search |
x |
Toggle selection |
esc |
Close expanded row / clear search |
- Go 1.26+,
modernc.org/sqlite(pure Go),pressly/goose/v3for migrations,sqlcfor query codegen. - Vue 3.5 + Vite 6, vanilla CSS (Geist + Geist Mono from Google Fonts).
- SPA built into
web/dist/, embedded into the binary via//go:embed all:web/dist.
.
├── main.go # wires config → db → sync → http
├── Makefile
├── sqlc.yaml
├── internal/
│ ├── config/ # env + flags + .env loader
│ ├── clickup/ # REST client
│ ├── db/
│ │ ├── migrations/ # goose SQL migrations (embedded)
│ │ ├── queries/ # sqlc input
│ │ └── gen/ # sqlc output (committed)
│ ├── server/ # HTTP API + SPA serving
│ └── sync/ # pull + push reconciliation worker
└── web/
├── index.html
├── vite.config.ts
└── src/
├── App.vue
├── api.ts
├── components/ # Row, StatusPill, TagList, EditableTitle, PriorityBadge, TweaksPanel
└── assets/styles.css
- The local DB is a mirror; tasks
clickup_idis canonical. Every fetched task is upserted; tasks that disappear from the remote response are soft-deleted (deleted_at). - Conflict resolution is per-field: if the local field is dirty (in
task_dirty), the pull skips that field; otherwise the remote wins whenremote.date_updated > local.clickup_updated_at. - Local mutations push on the next sync via the
task_dirtyqueue, which survives restarts. - Statuses are stored verbatim (no fixed 5-state model); the
statusestable is refreshed every sync from each task's embedded status object. - "Completed" statuses are hidden by default — both ClickUp's
type='closed'and common name patterns (published,archived,done,completed,cancel(l)ed). Your filter tweaks persist inlocalStorage. CLICKUP_TOKENis also accepted (legacy) butCLICKUP_API_TOKENis preferred.