task-pigeon
v0.2.63
Published
Small local task queue CLI backed by SQLite.
Downloads
8,038
Maintainers
Readme
Pigeon 🐦
Pigeon is a small local task queue CLI for coordinating work across people and coding agents.
It stores tasks in a project-local SQLite database, so everyone running Pigeon from the same repo can share a queue without a server, account, daemon, or hosted project tracker.
CLI -> small command functions -> Drizzle -> SQLitePigeon is intentionally boring: tasks, claims, notes, blockers, done.
Why Pigeon? ✨
Agent coordination usually fails in small, annoying ways: two agents claim the same work, blocked tasks disappear from view, context lives in chat scrollback, or each checkout quietly creates a separate queue.
Pigeon keeps the coordination surface tiny:
- 🗂️ project-local SQLite by default.
- 🙋 explicit task claiming with optional owner requirements.
- 💬 tiny message board for cross-agent coordination.
- 🔗 URLs and notes built into the happy path.
- 🚧 blocked work stays visible in the default queue.
- 🧾 readable responsive output for terminals of different sizes.
- 🛠️ config when you need policy, defaults when you do not.
Install 🚀
Pigeon requires Bun >=1.3.14.
Run it without installing:
bunx --bun task-pigeon@latest statusInstall it globally for repeated use:
bun add --global task-pigeon
pigeon statusUpdate a global install:
bun update --global task-pigeonDirect bunx installs from Git URLs are not supported. Use the npm package.
Quick Start ⚡
Run Pigeon from the project directory that should own the queue:
cd ~/Code/my-project
pigeon init
pigeon config --by andrew
pigeon doctor
pigeon whoami
pigeon statusAdd work with enough context for the next person or agent:
pigeon add "Fix failing publish" \
--url https://github.com/andrew-bierman/pigeon/issues/3 \
--note "trusted publishing failure" \
--priority high \
--labels release,npmList and claim work:
pigeon list
pigeon claim --by codex
pigeon claim 8 --by claudeUpdate task state:
pigeon update 8 --note "OIDC configured; waiting on npm" --priority urgent
pigeon block 8 --note "waiting on npm account setting" --blocked-by npm
pigeon release 8
pigeon done 8Watch the queue:
pigeon watch
pigeon monitor --by codex
pigeon monitor --by codex --once # one-shot output for scripts
pigeon monitor --by codex --mentions-only
pigeon monitor --by codex --messages unread
pigeon monitor --by codex --messages all --all-messages
pigeon monitor --by codex --hide-status --hide-tasks
pigeon monitor --by codex --compact
pigeon monitor --by codex --output table
pigeon wait --by codex
pigeon wait --by codex --mentions-only
pigeon wait --by codex --timeout 300000
pigeon watch --all
pigeon watch --status pending --interval 500
pigeon list --status pending,blocked
pigeon list --status activeMonitor interactivity:
Monitor runs as a tiny interactive dashboard in TTY terminals.
In non-interactive sessions it stays in plain text and auto-refreshes like before.
[↑/k] move [tab] switch pane
[enter] detail [esc/backspace/left] back from detail
[c] claim [d] done [r] release
[a] ack message [z] resolve message
[e] reply message
[n] new message
[g] jump to top [h] toggle help overlay
[t] task section [m] message section [s] status section
[p] pause refresh [f] force refresh [q] quitWhile the monitor is open, press n to write and send a coordination message inline. Messages accept normal @mentions, so you can keep the conversation on the same screen without dropping back to a separate command.
Press e on a selected message to reply directly to the original author.
The monitor keeps a visible control bar near the top so it is obvious whether you are browsing, in detail, or composing. Detail mode backs out with Esc, Backspace, or left arrow, and the expanded help overlay stays available with h.
By default, list and watch show active tasks: pending, claimed, and blocked. Use --all to include done, --status done to inspect completed work, or CSV filters like --status pending,blocked. --status active is shorthand for pending,claimed,blocked.
monitor hides resolved messages by default. Add --all-messages when you explicitly want resolved message history on the screen.
Use monitor visibility flags when you want a focused screen:
pigeon monitor --compact
pigeon monitor --hide-status
pigeon monitor --hide-tasks
pigeon monitor --hide-messagesUse wait when an agent is idle, blocked, or waiting for another counterpart. It exits when visible tasks or watched messages change, then the agent should re-run status, list, and unread messages.
pigeon wait --by codex
pigeon wait --by codex --messages unread --timeout 300000Leave a message for other agents:
pigeon message add "@claude Codex is checking release automation" --by codex
pigeon message add "Please review the release notes" --by codex --to claude
pigeon message list --mentioned claude
pigeon message list --by claude --unread
pigeon message ack 1 --by claude
pigeon message resolve 1Shared Queues 🤝
By default, Pigeon stores data at .pigeon/pigeon.db under the shared project root for the current checkout. Nested worktrees join that same queue unless you explicitly opt into worktree-local mode.
Pigeon discovers the project root from Git metadata, so this works from any Git repository and from any nested directory. Linked worktrees resolve their shared Git root automatically; no package.json or Pigeon-specific project setup is required.
Always check the active queue before claiming:
pigeon statusTo find stale queues in the current project or linked worktrees:
pigeon db listThis reports each discovered database with task and message counts. It does not scan unrelated directories or the whole disk.
To combine an old worktree queue into the active queue, preview first and then apply:
pigeon db merge --from .worktrees/pr-1116/.pigeon/pigeon.db
pigeon db merge --from .worktrees/pr-1116/.pigeon/pigeon.db --applyThe source database is never modified. New tasks and messages carry a short stable key, so merges match the same record even when mutable fields change. Older databases and snapshots fall back to exact-content matching, while conflicting local IDs are remapped and message read state is preserved.
Numeric IDs remain local SQLite keys for simple commands. Show the stable task keys when you need to compare or document records across databases:
pigeon list --columns id,uid,status,titleTo share a queue across directories or machines with a synced path, set PIGEON_DB:
PIGEON_DB=~/tasks/my-project.pigeon.db pigeon status
PIGEON_DB=~/tasks/my-project.pigeon.db pigeon listAgents should use the same project directory or the same PIGEON_DB; otherwise they will create separate queues.
Snapshots 📦
Need a quick backup or a way to move a queue to another machine? Use a JSON snapshot of the current local database:
pigeon snapshot export --file pigeon.snapshot.json
pigeon snapshot import --file pigeon.snapshot.jsonSnapshot commands act on the local SQLite queue. If you have remote mode configured in your shell, set PIGEON_LOCAL_ONLY=1 first so the command stays on the local database.
Why is remote mode unexpectedly enabled?
pigeon uses this precedence for remote settings:
PIGEON_URL/PIGEON_TOKENenvironment variables- project config (
.pigeon/config.jsonin the current directory) - user config (
$XDG_CONFIG_HOME/pigeon/config.json) - package defaults
If a command unexpectedly calls a remote server, check the resolved value:
pigeon whoami
pigeon config --show
env | rg '^PIGEON_'To clear a stale remote configuration quickly:
pigeon config --clear-remote-url --clear-remote-tokenFor a clean local session, isolate both DB and config:
TMP_CONFIG=$(mktemp -d)
PIGEON_DB=.tmp/pigeon.db XDG_CONFIG_HOME=$TMP_CONFIG pigeon statusPrivate Remote 🌐
When another computer needs the same queue, the boring option is to run one Pigeon server next to the SQLite file and connect over a private network such as Tailscale.
Server:
PIGEON_TOKEN=secret pigeon serve \
--host 100.x.y.z \
--port 8787 \
--db /data/pigeon/my-project.dbClient:
pigeon config --remote-url http://100.x.y.z:8787 --remote-token secret
pigeon status
pigeon list
pigeon claim --by andrewEnvironment overrides work well for temporary sessions:
PIGEON_URL=http://100.x.y.z:8787 PIGEON_TOKEN=secret pigeon monitor --by codexRemote mode is command-level HTTP, not SQL-over-HTTP. The client sends fixed Pigeon operations such as listTasks or claimTask; the server owns Drizzle, SQLite, migrations, and locking. The private API is implemented with Elysia and Eden Treaty for a typed client/server contract. That keeps the system as one SQLite writer without introducing sync conflict rules.
Use pigeon remote for examples. Use pigeon whoami to see whether the current shell is pointed at a remote server.
Clear remote config:
pigeon config --clear-remote-url --clear-remote-tokenMessages 💬
Messages are for coordination context that should not become a task: “I am editing the README,” “Claude is handling issue #12,” “do not publish until CI is green.”
pigeon message add "@codex Claude is reviewing the table output" --by claude
pigeon message add "Please check the package metadata" --by claude --to codex
pigeon message reply 3 "I checked it" --by codex
pigeon message list
pigeon message list --mentioned codex
pigeon message list --by codex --unread
pigeon message ack 3 --by codex
pigeon message resolve 3
pigeon message list --allMentions can come from @name in the message body or from --to name. Use comma-separated names for multiple counterparts: --to codex,claude.
Read state is per agent name. If Codex acknowledges a message, it is still unread for Claude until Claude acknowledges it too.
The sender should usually clean up the message. If you post a message for Claude, check Claude's view with pigeon message list --by claude; once Claude has acknowledged or acted on it, resolve the message so it leaves everyone's monitor.
Resolved messages are hidden from normal message lists and monitors for everyone. Use ack for “I read this”; use resolve for “this no longer belongs on the live board.” Use pigeon message list --all to inspect resolved message history.
pigeon status --by codex includes Codex's unread message count.
Identity 🪪
Pigeon identity is casual local config, not real sign-in. It is just the default value for commands that accept --by, which keeps repeated agent commands shorter and makes monitors easier to read.
pigeon config --by andrew
pigeon config --user --by codex
pigeon whoami
pigeon status
pigeon claim
pigeon message add "@claude I am taking the npm publish task"Clear it when you want commands to require explicit names again:
pigeon config --clear-byEnvironment overrides are useful for one-off agent sessions:
PIGEON_BY=claude pigeon monitor --mentions-onlyAgent Workflow 🤖
Recommended loop:
pigeon status
pigeon list --output normal
pigeon claim --by codex
pigeon update 12 --note "Investigating failing publish workflow"
pigeon block 12 --note "waiting on npm trusted publishing"
pigeon wait --by codex
pigeon done 12Use release when you claimed the wrong task or need to put work back in pending.
Input validation is deliberately strict where it prevents coordination mess:
addrequires a non-empty title.- provided URLs must be valid URLs.
- priority must be
low,normal,high, orurgent. - labels are stored as trimmed, deduped CSV.
blockrequires a non-empty note.- blank optional
url,note, orlabelsvalues clear that field.
Output 📋
list and watch use responsive auto output by default. When URLs or notes are visible, auto uses a readable list so links stay intact while titles and notes wrap with the terminal.
Default columns:
id,status,title,url,claimedBy,updatedAtUse uid as an optional stable task key. It is intentionally not in the default columns so normal output stays compact.
Optional task metadata:
pigeon add "Polish release docs" --priority high --labels docs,release
pigeon update 12 --priority urgent --labels ci,npm
pigeon block 12 --note "waiting on package access" --blocked-by npmFor full readable links and notes, you can force normal list output:
pigeon list --output normal
pigeon watch --output normalChoose columns for one run:
pigeon columns
pigeon list --columns id,status,priority,title,url,labels,blockedBy,note,claimedBy,updatedAt
pigeon watch --columns id,status,priority,title,url,labels,blockedBy,note,claimedBy,updatedAtForce a compact grid:
pigeon list --output tableValid columns:
id,status,priority,title,url,claimedBy,blockedBy,labels,note,createdAt,updatedAt,claimedAt,blockedAt,doneAtValid output modes:
auto,table,normalConfiguration ⚙️
Configuration precedence:
defaults < user config < project config < PIGEON_BY/PIGEON_URL/PIGEON_TOKEN/PIGEON_LOCAL_ONLY/PIGEON_WORKTREE_LOCAL/PIGEON_COLUMNS/PIGEON_OUTPUT/PIGEON_REQUIRE < CLI flagsPIGEON_LOCAL_ONLY=1 disables remote mode for that shell and forces local DB/queue behavior, even if user/project config already has a remote URL.
PIGEON_WORKTREE_LOCAL=1 opts into per-worktree queue isolation when you intentionally want a separate board in each checkout.
Project config lives next to the active database at .pigeon/config.json.
User config lives at $XDG_CONFIG_HOME/pigeon/config.json, or ~/.config/pigeon/config.json when XDG_CONFIG_HOME is not set.
Config files are JSON with a top-level "version": 1. Older unversioned config files are treated as v1, and Pigeon writes the version on the next config save. Pigeon intentionally does not use executable TypeScript config.
Show effective config, paths, and sources:
pigeon config --showSet project defaults:
pigeon config --by codex
pigeon config --columns id,status,priority,title,url,labels,note,claimedBy,updatedAt --output normalSet user defaults:
pigeon config --user --by andrew
pigeon config --user --columns id,status,priority,title,url,labels,note,claimedBy,updatedAt --output normalUse environment overrides:
PIGEON_BY=claude pigeon status
PIGEON_URL=http://100.x.y.z:8787 PIGEON_TOKEN=secret pigeon status
PIGEON_COLUMNS=id,status,title,url PIGEON_OUTPUT=normal pigeon listRequired Fields ✅
Pigeon can require selected command fields from config. Defaults are intentionally loose; project teams can tighten only the fields they care about.
Show the available required-field keys:
pigeon requirementsRequire owners on claims for the current project:
pigeon config --require claim.by
pigeon claim --by codexRequire richer task intake too:
pigeon config --require claim.by,add.url,add.noteClear requirements:
pigeon config --clear-requirementsUse an environment override:
PIGEON_REQUIRE=claim.by pigeon claim --by codexAgent Skill 🧠
Pigeon ships a TanStack Intent skill in the npm package.
Agents that understand Intent can load package guidance for project-local queues, claiming, releasing, blocking, readable output, and config:
npx @tanstack/intent@latest load task-pigeon#pigeonIntent discovers package skills from installed project dependencies. Install task-pigeon in the project first when using intent load from a project workspace.
Command Tour 🧭
| Command | Purpose |
| --- | --- |
| pigeon init | Create or verify the active SQLite queue. |
| pigeon status | Show the active database path and status counts. |
| pigeon add | Add pending work with optional URL, note, priority, and labels. |
| pigeon list | Show active work by default. |
| pigeon message | Add, list, ack, and resolve coordination messages. |
| pigeon claim | Claim the oldest pending task, or a specific ID. |
| pigeon release | Move a claimed task back to pending. |
| pigeon update | Edit title, URL, note, priority, or labels. |
| pigeon block | Mark work blocked with a required note and optional dependency. |
| pigeon done | Mark work complete. |
| pigeon watch | Re-render the queue as it changes. |
| pigeon monitor | Watch status, tasks, and messages together. |
| pigeon wait | Block until visible tasks or messages change. |
| pigeon serve | Serve one SQLite queue over private HTTP. |
| pigeon config | Show or update project/user defaults. |
| pigeon columns | Show output column names and examples. |
| pigeon requirements | Show configurable required-field keys. |
| pigeon identity | Show local actor examples. |
| pigeon remote | Show private remote server examples. |
| pigeon whoami | Show the active actor, source, and queue path. |
Common commands:
pigeon init
pigeon status
pigeon config --show
pigeon whoami
pigeon identity
pigeon remote
pigeon columns
pigeon requirements
pigeon message add "@claude Heads up for other agents" --by codex
pigeon message list --mentioned claude
pigeon message list --by claude --unread
pigeon message ack 1 --by claude
pigeon message resolve 1
pigeon message list --all
pigeon add "Task title" --url https://example.com --note "context"
pigeon list
pigeon list --all
pigeon list --status claimed
pigeon list --status pending,blocked
pigeon list --status active
pigeon claim --by codex
pigeon claim 8 --by codex
pigeon release 8
pigeon update 8 --title "New title" --url https://example.com --note "context"
pigeon block 8 --note "why work is blocked"
pigeon done 8
pigeon watch
pigeon monitor --by codex
pigeon monitor --by codex --mentions-only
pigeon wait --by codex
pigeon wait --by codex --mentions-only
pigeon wait --by codex --timeout 300000Roadmap 🗺️
Pigeon’s goal is still: modern tooling, minimal architecture, useful coordination. Everything should stay boring in practice: clear, local-first, and easy to reason about.
What Works Today
- ✅ Project-local SQLite queues with no server required.
- ✅ Typed Drizzle schema and migrations.
- ✅ Strict validation for task titles, URLs, blockers, and configurable required fields.
- ✅ Atomic claiming so agents do not grab the same pending task.
- ✅ Active-by-default lists:
pending,claimed, andblocked. - ✅ Task metadata for priority, labels, and blocking dependency.
- ✅ CSV status filters and
--status active. - ✅ Responsive readable output that preserves links.
- ✅ User/project config for columns, output, and required fields.
- ✅ Casual local identity config for default
--byvalues. - ✅ Private HTTP remote mode for sharing one SQLite queue across machines.
- ✅ Cross-agent messages with per-agent read state and global resolve.
- ✅ Direct message replies that auto-target the original author.
- ✅ Message mentions plus mention-focused monitor/wait filters.
- ✅ Interactive monitor dashboard with detail panes, inline compose, and quick actions.
- ✅ Blocking wait command for agents that need to wake on board changes.
- ✅ TanStack Intent skill packaged with npm.
- ✅ Trusted npm publishing through GitHub Actions.
- ✅ Lightweight JSON snapshots for backup/recovery.
Launch Gates (Before public)
- ✅ Finalize release/upgrade docs (bunx, global install, version bump flow).
- ✅ Publish a short “fresh machine” validation flow for
whoami,config,status, andmonitor. - ✅ Add a
CHANGELOG.mdand keep release notes current. - ✅ Add explicit local-only remote fallback guidance in docs:
PIGEON_LOCAL_ONLY=1whoami+doctorchecks
- ✅ Make
.pigeon+ DB path behavior explicit for avoiding queue forks across machines.
Near-Term (Post-Launch, if still boring)
- Better message ergonomics:
message list --resolved,message ack --all --by <name>. - Monitor polish for longer sessions (sectioned defaults and clearer visibility hints).
- Optional shell completion if it stays clean with CitTY and doesn’t add complexity.
- More flexible required fields only if usage proves the defaults are insufficient.
Maybe Later
These are interesting, but only if they stay boring:
- Due dates if they are just metadata.
- Dependency polish if
blockedByneeds more than plain text. message replyonly if flat messages become hard to scan.- A compact TUI only if the plain CLI remains first-class.
- Optional GitHub issue helpers for URLs, without becoming a GitHub client.
- Direct Cloudflare D1 support if the implementation can stay small and local SQLite remains the default.
- Hosted SQLite experiments, likely Turso/libSQL first, only if private remote is not enough.
Production Checklist 📦
Before publish from a clean clone:
bun installbun run cibun run smokebun run buildbun run build:binarynpm pack --dry-run
Fresh-machine verification:
bunx --bun task-pigeon@latest status
bunx --bun task-pigeon@latest whoami
bunx --bun task-pigeon@latest config --show
PIGEON_LOCAL_ONLY=1 bunx --bun task-pigeon@latest statusRelease flow (when you’re ready):
bun run release:patch # or bun run release:minor
git push
git push --tagsPublishing is executed by GitHub Actions using npm trusted publishing.
Do not run npm publish locally for normal releases. If you need to trigger a publish manually, use the Publish GitHub Actions workflow dispatch or the GitHub UI.
Non-Goals
These are how Pigeon avoids becoming the thing it was built to avoid:
- No hosted service.
- No daemon required.
- No plugin system.
- No workflow engine, event bus, or automation framework.
- No repository/service/adapter layers just for architecture theater.
- No multi-user permissions model beyond private network access plus optional bearer token.
- No attempt to replace Linear, GitHub Issues, Slack, or project management tools.
The bar for new features: can two agents use it immediately, does it make coordination clearer, and can the code still be explained as CLI -> small command functions -> Drizzle -> SQLite?
Development 🧪
bun install
bun run ciUseful scripts:
bun test
bun run test:coverage
bun run check
bun run typecheck
bun run smoke
bun run build
bun run build:binarybun run check runs Biome, the project policy checker, and TanStack Intent validation.
The policy checker enforces the intentionally flat architecture:
- CitTY for CLI parsing.
- Drizzle-centered database access.
- centralized
PIGEON_DBreads. - single
optionsobject params for command functions.
For local CLI development:
bun install
bun run build
bun link
pigeon --helpFor code work where you want to avoid touching your active queue/config (same machine, same user), use the dev runner:
bun run dev
bun run dev -- status
bun run dev -- add "Try command in isolation"
bun run dev -- monitor --oncebun run dev always uses:
- a temporary database path
- a temporary user config directory
PIGEON_LOCAL_ONLY=1(no remote calls)
This lets you iterate from any project without changing what other agent sessions are using.
If you are shipping this for others, pigeon doctor is the quality default you can suggest to every support request:
pigeon doctorIt prints the resolved DB path, config sources, and remote state so people can self-diagnose in 10 seconds.
For the installed package (or bunx) while developing:
PIGEON_LOCAL_ONLY=1 \
PIGEON_DB="$(mktemp)/pigeon-dev.db" \
XDG_CONFIG_HOME="$(mktemp -d)/xconfig" \
bunx --bun task-pigeon@latest statusRelease 📦
See CHANGELOG.md for release notes.
Patch release:
bun run release:patch
git push
git push --tagsMinor release:
bun run release:minor
git push
git push --tagsPublishing runs from GitHub Actions on v* tags using npm Trusted Publishing. Use the GitHub Actions publish workflow rather than local npm publish commands.
