The world's fastest todo app.
A personal todo service backed by a plain Markdown file. Every action — toggle, edit, add, delete — reflects instantly in the UI with no perceived latency. Changes to the file from any text editor propagate to all open clients in ~200ms via WebSocket. No database, no sync delay, no cloud.
Comes with a REST API, a real-time web UI, and a terminal interface — all reading from and writing to a single .md file.
## Focus
- [ ] ship the thing [t:work] [d:2026-05-25]
- [ ] write tests
- [x] design review
## Inbox
- [ ] [!] call the doctor
go install github.com/zcag/odak@latestInstalls the full binary — CLI, TUI, and REST server with embedded web UI. Point the client at a running server via env vars or ~/.config/odak/client:
# env vars
export ODAK_ENDPOINT=http://your-server:8761
export ODAK_TOKEN=your-api-key
# or config file (~/.config/odak/client)
endpoint=http://your-server:8761
token=your-api-keygit clone https://github.com/zcag/odak
cd odak
make build # local binary → ~/.local/bin/odak
make deploy # cross-compile + push to server via rsync + systemd restartodak --server \
--file ~/todos.md \
--api-key secret \
--user admin \
--password secret \
--port 8761 \
--ui # serve the web UI at /Or via environment variables:
export ODAK_FILE=~/todos.md
export ODAK_API_KEY=secret
export ODAK_USER=admin
export ODAK_PASSWORD=secret
odak --server --uiA systemd user service template is included at deploy/odak.service.template.
Access at http://your-server:8761. Changes sync in real time via WebSocket — editing the Markdown file directly also reflects instantly in all open browsers.
Sections — items are grouped into named sections (Focus, Today, Next, Backlog, Someday, Recurring, Inbox). Sections collapse in the sidebar and in the main view.
Themes — 8 dark themes (Void, Carbon, Smoke, Midnight, Nord, Catppuccin, Gruvbox, Dracula) and 8 light themes. Hover the sun/moon icon to pick.
Keyboard shortcuts
| Key | Action |
|---|---|
j / ↓ |
focus next item |
k / ↑ |
focus previous item |
x |
toggle done |
e |
edit text |
d |
delete |
n / / |
focus add bar |
w |
toggle #work filter |
p |
toggle #personal filter |
r |
refresh |
? |
keyboard shortcut overlay |
Esc |
clear focus / close |
Drag and drop — reorder items within a section or drag to a different section header to move them.
Sub-items — child items are shown indented under their parent, collapsed by default. Click the ▶ indicator to expand.
odak # launch TUI (auto-detected when stdout is a TTY)
odak tui # explicitNavigate sections with arrow keys, toggle done with x, edit with e, hide completed with h.
odak list [section] # list todos
odak list --tag work # filter by tag
odak add "review PR" --section Next # add item
odak add "! urgent thing #work d:2026-05-25" # with flags inline
odak done <id> # toggle done
odak rm <id> # delete
odak move <id> Today # move to section
odak show <id> # show detailsStandard Markdown task lists, grouped by ## headings. Extra metadata lives in inline tags:
## Focus
- [ ] [!] urgent item
- [ ] [t:work] [t:personal] tagged item
- [ ] [d:2026-05-25] item with deadline
- [ ] [w:2026-06-01] item with trigger/wait date
- [ ] parent item
- [ ] child item (indented with 4 spaces or tab)
- [x] completed itemThe file is the source of truth — edit it directly and all clients update within ~200ms.
Use odak as a tool inside Claude Code or any MCP-compatible AI client.
The MCP server is served by odak --server itself over the Streamable HTTP
transport at /mcp, behind the same bearer API key as the REST API — there is
no separate process to launch.
Register it with Claude Code (user scope — available in every project):
claude mcp add --transport http odak http://<host>:<port>/mcp \
--header "Authorization: Bearer <api-key>"For other MCP clients, point them at the HTTP endpoint with the bearer header:
{
"mcpServers": {
"odak": {
"type": "http",
"url": "http://<host>:<port>/mcp",
"headers": { "Authorization": "Bearer <api-key>" }
}
}
}Available tools:
| Tool | Description |
|---|---|
list_todos |
List items, optionally filtered by section, tag, or parent_id |
get_todo |
Get a single item by id |
add_todo |
Add item with optional section, tags, urgent, deadline, trigger, parent_id |
edit_todo |
Update any field of an existing item |
toggle_done |
Toggle done state by id |
delete_todo |
Delete item by id |
move_todo |
Move item to a different section |
reorder_todos |
Reorder items within a section by providing ordered ids |
list_sections |
List sections with item counts |
get_raw |
Get the raw Markdown content of the file |
put_raw |
Overwrite the entire file with raw Markdown |
To connect odak from a hosted client that can't carry a static header — the
Claude.ai / ChatGPT "Connect" flow — point /mcp at a WorkOS AuthKit
authorization server. With it configured, /mcp accepts either the static
API key (CLI/TUI/local clients) or a valid AuthKit-issued JWT, and serves
the RFC 9728 Protected Resource Metadata at
/.well-known/oauth-protected-resource so hosts can discover the auth server.
Env-only, all unset ⇒ OAuth off (/mcp stays API-key-only):
export ODAK_OAUTH_ISSUER=https://your-app.authkit.app # AuthKit domain; JWKS at {issuer}/oauth2/jwks
export ODAK_MCP_RESOURCE=https://odak.example.com/mcp # this endpoint's public URL = the OAuth audience
export ODAK_OAUTH_ALLOWED_EMAIL=you@example.com # optional email-claim allowlist (single-user gate)
export ODAK_OAUTH_ALLOWED_SUB=user_123 # optional sub-claim allowlist (single-user gate)Tokens are validated against the JWKS, enforcing issuer, audience (== ODAK_MCP_RESOURCE,
RFC 8707 replay defense), signature, and the optional allowlist — a token passes if its
sub or email claim is listed (which claim an AuthKit access token carries varies,
so set whichever you can confirm; a rejected token's sub/email is logged so you can pin it).
All endpoints require Authorization: Bearer <api-key>.
GET /todos list items (?section=, ?tag=, ?parent_id=)
POST /todos create item
GET /todos/:id get item
PATCH /todos/:id update item
DELETE /todos/:id delete item
PATCH /todos/:id/done toggle done
POST /todos/:id/move move to section
POST /todos/reorder reorder within section
GET /sections list sections with counts
GET /raw raw markdown
PUT /raw overwrite markdown
GET /ws WebSocket (?token=)

