A lightweight, Go-based CLI TODO manager designed for local AI workflows — with a background daemon that sends push notifications (via ntfy) and webhooks when tasks are due. All data is stored locally in SQLite.
- ✅ Full CRUD — Add, list, show, edit, mark done, and delete TODOs
- 🔔 Multi-channel Notifications — ntfy push notifications + generic webhook postback
- 📦 SQLite Storage — No external database needed, everything stored in
~/.local/share/todo/todo.db - 🎨 Pretty Output — Color-coded priorities and statuses with table formatting
- 🤖 AI-Agent Ready — Structured CLI output, simple commands, and a pre-defined agent skill (
@skills/todo/SKILL.md) make it a perfect tool for AI-driven workflows - 🐳 Docker Support — Run the daemon as a container with shared SQLite volume
- 🚀 Single Binary — Pure Go, no CGO, compiles to a single binary
todo is designed to work seamlessly as a tool in AI agent pipelines. Whether you're using pi, Claude Code, Codex, or your own agent harness:
- Deterministic CLI — Every command has predictable output, easy for agents to parse
- Simple CRUD interface —
add,list,show,edit,done,delete— no ambiguity - Filterable output — Agents can query by status (
-s pending) or priority (-p high) to focus on what matters - Notification daemon — Set it and forget it; the daemon handles alerting humans while agents manage the task lifecycle
- Included Agent Skill — The
@skills/todo/SKILL.mdteaches any compatible agent how to usetodoexpertly, including natural date parsing, priority inference, and multi-step workflows - Webhook integration — The postback channel lets agents create tasks that notify external systems (Slack bots, CI pipelines, dashboards) when due
Example AI workflow:
User: "Remind me to review the PR before Friday's standup"
Agent: todo add "Review PR" -p high -D "2026-03-06 09:00"
→ Daemon sends ntfy push to your phone at 9 AM Friday
→ Postback hits your Slack bot with the reminder
Drop @skills/todo/ into your agent's skills directory and it just works.
Install via vercel-labs/skills:
# Install the todo skill into your agent's skills directory
npx @vercel-labs/skills add https://github.com/vividvilla/todo.git/skills# Clone the repository
git clone https://github.com/vividvilla/todo.git
cd todo
# Build
go build -o todo .
# (Optional) Install to your PATH
cp todo ~/.local/bin/
# Or use just
just install- Go 1.24+ (for building)
# Add your first TODO
todo add "Buy groceries" -p high -D "2026-03-04T10:00:00Z"
# Add more TODOs
todo add "Write documentation" -d "README and API docs" -p medium
todo add "Fix login bug" -p high -D "2026-03-04"
# List all active TODOs
todo list
# Show details
todo show 1
# Mark as done
todo done 1
# Set up notifications (one or both)
todo config set ntfy_url "https://ntfy.sh/your_topic"
todo config set postback_url "https://your-server.com/webhook"
# Start the notification daemon
todo daemonAdd a new TODO.
todo add "Deploy v2.0"
todo add "Review PR" -p high -D "2026-03-05T09:00:00Z"
todo add "Write tests" -d "Unit tests for auth module" -p mediumFlags:
| Flag | Short | Default | Description |
|---|---|---|---|
--description |
-d |
Description text | |
--priority |
-p |
medium |
Priority: low, medium, high |
--due |
-D |
Due date/time (see date formats) |
List TODOs. By default, hides completed tasks.
todo list # Active TODOs (pending + in-progress)
todo list -a # All TODOs including done
todo list -p high # Only high priority
todo list -s pending # Only pending
todo ls # Alias for listFlags:
| Flag | Short | Description |
|---|---|---|
--status |
-s |
Filter: pending, in-progress, done |
--priority |
-p |
Filter: low, medium, high |
--all |
-a |
Show all including done |
Output Example:
╭────┬────────────────────┬──────────┬─────────────┬──────────────────────╮
│ ID │ TITLE │ PRIORITY │ STATUS │ DUE │
├────┼────────────────────┼──────────┼─────────────┼──────────────────────┤
│ 3 │ Fix login bug │ high │ pending │ Mar 4, 2026 12:00 AM │
│ 1 │ Buy groceries │ high │ pending │ Mar 4, 2026 3:30 PM │
│ 2 │ Write docs │ medium │ in-progress │ - │
╰────┴────────────────────┴──────────┴─────────────┴──────────────────────╯
Show full details of a TODO.
todo show 1Output:
TODO #1
─────────────────────────────────
Title: Buy groceries
Description: -
Priority: high
Status: pending
Due: Wed Mar 4, 2026 3:30 PM
Notified: false
Created: Wed Mar 4, 2026 12:38 AM
Updated: Wed Mar 4, 2026 12:38 AM
Edit an existing TODO. Only specified flags are updated.
todo edit 1 -t "Buy organic groceries" # Change title
todo edit 1 -p low # Change priority
todo edit 1 -s in-progress # Change status
todo edit 1 -D "2026-03-10" # Change due date
todo edit 1 -D "" # Clear due dateFlags:
| Flag | Short | Description |
|---|---|---|
--title |
-t |
New title |
--description |
-d |
New description |
--priority |
-p |
New priority: low, medium, high |
--status |
-s |
New status: pending, in-progress, done |
--due |
-D |
New due date (empty string to clear) |
Mark a TODO as done.
todo done 1
# ✓ TODO #1 marked as doneDelete a TODO permanently.
todo delete 3
todo rm 3 # AliasSet a configuration value.
todo config set ntfy_url "https://ntfy.sh/your_topic"
todo config set postback_url "https://your-server.com/hooks/todo"Supported config keys:
| Key | Description |
|---|---|
postback_url |
Generic webhook URL (JSON POST) |
ntfy_url |
ntfy topic URL (e.g. https://ntfy.sh/my_topic) |
Get a configuration value.
todo config get ntfy_url
# ntfy_url: https://ntfy.sh/your_topicStart the notification daemon. It periodically checks for overdue TODOs and sends notifications via all configured channels (postback, ntfy).
todo daemon # Check every 60 seconds (default)
todo daemon -i 30 # Check every 30 secondsFlags:
| Flag | Short | Default | Description |
|---|---|---|---|
--interval |
-i |
60 |
Check interval (seconds) |
The --due / -D flag accepts the following date formats:
| Format | Example |
|---|---|
| RFC3339 | 2026-03-04T15:00:00Z |
| RFC3339 with offset | 2026-03-04T20:30:00+05:30 |
| Date only (midnight local) | 2026-03-04 |
| Datetime without TZ (local) | 2026-03-04T15:00:00 |
| Date + time (local) | 2026-03-04 15:00 |
- The daemon runs a ticker loop (default: every 60 seconds)
- On each tick, it queries the database for TODOs where:
due_atis in the pastnotifiedisfalsestatusis notdone
- For each overdue TODO, it sends notifications via all configured channels
- If at least one channel delivers successfully (HTTP 2xx), the TODO is marked as
notified - On failure, each channel retries once after 2 seconds, then skips until the next cycle
| Channel | Config Key | Description |
|---|---|---|
| Postback | postback_url |
Generic JSON webhook (POST) |
| ntfy | ntfy_url |
Push notifications via ntfy.sh |
You can configure one or both. They fire independently.
ntfy is a simple pub-sub notification service. Install the ntfy app on your phone, subscribe to your topic, and get push notifications when TODOs are overdue.
todo config set ntfy_url "https://ntfy.sh/your_topic"The ntfy notification includes:
- Title:
⏰ TODO #1 Overdue - Markdown body: Task title, description, priority, status, due date
- Priority mapping:
high→ urgent (5),medium→ default (3),low→ low (2) - Emoji tags: 🚨 for high,
⚠️ for medium, ℹ️ for low
Sends a JSON POST to any webhook URL:
todo config set postback_url "https://your-server.com/webhook"Payload format:
{
"event": "todo_due",
"todo": {
"id": 3,
"title": "Fix login bug",
"description": "Authentication failing on mobile",
"priority": "high",
"status": "pending",
"due_at": "2026-03-04T00:00:00Z",
"notified": false,
"created_at": "2026-03-03T19:08:00Z",
"updated_at": "2026-03-03T19:08:00Z"
},
"timestamp": "2026-03-04T00:01:00Z"
}# 1. Configure one or more notification channels
todo config set ntfy_url "https://ntfy.sh/your_topic"
todo config set postback_url "https://your-server.com/webhook"
# 2. Start the daemon
todo daemon
# 3. (Optional) Run as a systemd service — see belowCreate ~/.config/systemd/user/todo.service:
[Unit]
Description=todo notification daemon
After=network.target
[Service]
ExecStart=%h/.local/bin/todo daemon
Restart=on-failure
RestartSec=10
[Install]
WantedBy=default.targetThen:
systemctl --user daemon-reload
systemctl --user enable todo
systemctl --user start todo
systemctl --user status todo
# View logs
journalctl --user -u todo -fThe daemon can run as a container while you use the CLI on the host. They share the same SQLite database via a volume mount.
# 1. Configure notification channels (from host CLI)
todo config set ntfy_url "https://ntfy.sh/your_topic"
# and/or
todo config set postback_url "https://your-server.com/webhook"
# 2. Start the daemon container
docker compose up -d
# 3. View logs
docker compose logs -f
# 4. Stop the daemon
docker compose down| Variable | Default | Description |
|---|---|---|
TODO_DATA |
~/.local/share/todo |
Path to the data directory (SQLite DB) |
TZ |
Asia/Kolkata |
Timezone for the container |
TODO_DATA=/path/to/data docker compose up -ddocker build -t todo .
docker run --rm -v ~/.local/share/todo:/home/todo/.local/share/todo todo daemonAll data is stored in ~/.local/share/todo/:
~/.local/share/todo/
├── todo.db # SQLite database
└── todo.pid # PID file (when daemon is running)
The notification system uses a Notifier interface, making it easy to add new channels:
type Notifier interface {
Name() string
Send(todo model.Todo) error
}Built-in channels: Postback (webhook), ntfy
Planned / easy to add:
- Email — Add an SMTP notifier in
notify/ - Slack — Add a Slack webhook notifier
- Desktop — Add
notify-send/ OS notification support - Telegram — Add a Telegram bot notifier
To add a new channel: implement Notifier, then register it in buildNotifiers() in cmd/daemon.go.
MIT