UNPKG

task-pigeon

Version:

Small local task queue CLI backed by SQLite.

769 lines (559 loc) β€’ 24.5 kB
# 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. ```text CLI -> small command functions -> Drizzle -> SQLite ``` Pigeon 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: ```sh bunx --bun task-pigeon@latest status ``` Install it globally for repeated use: ```sh bun add --global task-pigeon pigeon status ``` Update a global install: ```sh bun update --global task-pigeon ``` Direct `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: ```sh cd ~/Code/my-project pigeon init pigeon config --by andrew pigeon doctor pigeon whoami pigeon status ``` Add work with enough context for the next person or agent: ```sh pigeon add "Fix failing publish" \ --url https://github.com/andrew-bierman/pigeon/issues/3 \ --note "trusted publishing failure" \ --priority high \ --labels release,npm ``` List and claim work: ```sh pigeon list pigeon claim --by codex pigeon claim 8 --by claude ``` Update task state: ```sh 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 8 ``` Watch the queue: ```sh 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 active ``` Monitor interactivity: ```text 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] quit ``` While 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: ```sh pigeon monitor --compact pigeon monitor --hide-status pigeon monitor --hide-tasks pigeon monitor --hide-messages ``` Use `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. ```sh pigeon wait --by codex pigeon wait --by codex --messages unread --timeout 300000 ``` Leave a message for other agents: ```sh 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 1 ``` ## Shared 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: ```sh pigeon status ``` To find stale queues in the current project or linked worktrees: ```sh pigeon db list ``` This 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: ```sh pigeon db merge --from .worktrees/pr-1116/.pigeon/pigeon.db pigeon db merge --from .worktrees/pr-1116/.pigeon/pigeon.db --apply ``` The 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: ```sh pigeon list --columns id,uid,status,title ``` To share a queue across directories or machines with a synced path, set `PIGEON_DB`: ```sh PIGEON_DB=~/tasks/my-project.pigeon.db pigeon status PIGEON_DB=~/tasks/my-project.pigeon.db pigeon list ``` Agents 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: ```sh pigeon snapshot export --file pigeon.snapshot.json pigeon snapshot import --file pigeon.snapshot.json ``` Snapshot 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_TOKEN` environment variables - project config (`.pigeon/config.json` in 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: ```sh pigeon whoami pigeon config --show env | rg '^PIGEON_' ``` To clear a stale remote configuration quickly: ```sh pigeon config --clear-remote-url --clear-remote-token ``` For a clean local session, isolate both DB and config: ```sh TMP_CONFIG=$(mktemp -d) PIGEON_DB=.tmp/pigeon.db XDG_CONFIG_HOME=$TMP_CONFIG pigeon status ``` ## Private 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: ```sh PIGEON_TOKEN=secret pigeon serve \ --host 100.x.y.z \ --port 8787 \ --db /data/pigeon/my-project.db ``` Client: ```sh pigeon config --remote-url http://100.x.y.z:8787 --remote-token secret pigeon status pigeon list pigeon claim --by andrew ``` Environment overrides work well for temporary sessions: ```sh PIGEON_URL=http://100.x.y.z:8787 PIGEON_TOKEN=secret pigeon monitor --by codex ``` Remote 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: ```sh pigeon config --clear-remote-url --clear-remote-token ``` ## Messages πŸ’¬ 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.” ```sh 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 --all ``` Mentions 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. ```sh 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: ```sh pigeon config --clear-by ``` Environment overrides are useful for one-off agent sessions: ```sh PIGEON_BY=claude pigeon monitor --mentions-only ``` ## Agent Workflow πŸ€– Recommended loop: ```sh 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 12 ``` Use `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: - `add` requires a non-empty title. - provided URLs must be valid URLs. - priority must be `low`, `normal`, `high`, or `urgent`. - labels are stored as trimmed, deduped CSV. - `block` requires a non-empty note. - blank optional `url`, `note`, or `labels` values 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: ```text id,status,title,url,claimedBy,updatedAt ``` Use `uid` as an optional stable task key. It is intentionally not in the default columns so normal output stays compact. Optional task metadata: ```sh 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 npm ``` For full readable links and notes, you can force normal list output: ```sh pigeon list --output normal pigeon watch --output normal ``` Choose columns for one run: ```sh 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,updatedAt ``` Force a compact grid: ```sh pigeon list --output table ``` Valid columns: ```text id,status,priority,title,url,claimedBy,blockedBy,labels,note,createdAt,updatedAt,claimedAt,blockedAt,doneAt ``` Valid output modes: ```text auto,table,normal ``` ## Configuration βš™οΈ Configuration precedence: ```text defaults < user config < project config < PIGEON_BY/PIGEON_URL/PIGEON_TOKEN/PIGEON_LOCAL_ONLY/PIGEON_WORKTREE_LOCAL/PIGEON_COLUMNS/PIGEON_OUTPUT/PIGEON_REQUIRE < CLI flags ``` `PIGEON_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: ```sh pigeon config --show ``` Set project defaults: ```sh pigeon config --by codex pigeon config --columns id,status,priority,title,url,labels,note,claimedBy,updatedAt --output normal ``` Set user defaults: ```sh pigeon config --user --by andrew pigeon config --user --columns id,status,priority,title,url,labels,note,claimedBy,updatedAt --output normal ``` Use environment overrides: ```sh 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 list ``` ### Required 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: ```sh pigeon requirements ``` Require owners on claims for the current project: ```sh pigeon config --require claim.by pigeon claim --by codex ``` Require richer task intake too: ```sh pigeon config --require claim.by,add.url,add.note ``` Clear requirements: ```sh pigeon config --clear-requirements ``` Use an environment override: ```sh PIGEON_REQUIRE=claim.by pigeon claim --by codex ``` ## Agent 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: ```sh npx @tanstack/intent@latest load task-pigeon#pigeon ``` Intent 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: ```sh 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 300000 ``` ## Roadmap πŸ—ΊοΈ 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`, and `blocked`. - βœ… 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 `--by` values. - βœ… 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`, and `monitor`. - βœ… Add a `CHANGELOG.md` and keep release notes current. - βœ… Add explicit local-only remote fallback guidance in docs: - `PIGEON_LOCAL_ONLY=1` - `whoami` + `doctor` checks - βœ… 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 `blockedBy` needs more than plain text. - `message reply` only 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 install` - `bun run ci` - `bun run smoke` - `bun run build` - `bun run build:binary` - `npm pack --dry-run` Fresh-machine verification: ```sh 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 status ``` Release flow (when you’re ready): ```sh bun run release:patch # or bun run release:minor git push git push --tags ``` Publishing 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 πŸ§ͺ ```sh bun install bun run ci ``` Useful scripts: ```sh bun test bun run test:coverage bun run check bun run typecheck bun run smoke bun run build bun run build:binary ``` `bun 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_DB` reads. - single `options` object params for command functions. For local CLI development: ```sh bun install bun run build bun link pigeon --help ``` For code work where you want to avoid touching your active queue/config (same machine, same user), use the dev runner: ```sh bun run dev bun run dev -- status bun run dev -- add "Try command in isolation" bun run dev -- monitor --once ``` `bun 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: ```sh pigeon doctor ``` It 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: ```sh PIGEON_LOCAL_ONLY=1 \ PIGEON_DB="$(mktemp)/pigeon-dev.db" \ XDG_CONFIG_HOME="$(mktemp -d)/xconfig" \ bunx --bun task-pigeon@latest status ``` ## Release πŸ“¦ See [CHANGELOG.md](./CHANGELOG.md) for release notes. Patch release: ```sh bun run release:patch git push git push --tags ``` Minor release: ```sh bun run release:minor git push git push --tags ``` Publishing runs from GitHub Actions on `v*` tags using npm Trusted Publishing. Use the GitHub Actions publish workflow rather than local npm publish commands.