idea CLI Overview
August 17, 2026 · View on GitHub
The idea command manages a per-repo backlog stored in fab/backlog.md. It is a lightweight CRUD tool for capturing, triaging, and tracking ideas — small enough to live alongside hand-edited markdown, structured enough to be queryable from the command line.
Binary & Installation
Binary: src/cmd/idea/ (Go binary, distributed as idea).
Install via Homebrew tap:
brew install sahil87/tap/idea
Or install manually from a clean checkout:
./scripts/install.sh
The manual installer builds the binary via ./scripts/build.sh and copies it to ~/.local/bin/idea.
Worktree Behavior
By default, idea operates on the current worktree's fab/backlog.md (resolved via git rev-parse --show-toplevel). Pass --main to target the main worktree's backlog instead; internally, idea resolves the main worktree root by running git rev-parse --path-format=absolute --git-common-dir and taking its parent directory. In the main worktree, both behave identically. This ensures that users in a linked worktree get predictable local behavior unless they explicitly opt into the shared backlog.
The backlog file path can also be overridden globally by --file <path> or by setting the IDEAS_FILE environment variable.
System backlog and out-of-git operation
idea also works outside any git repository and offers a system-level backlog for cross-repo idea capture. The system backlog lives at $XDG_CONFIG_HOME/idea/backlog.md when XDG_CONFIG_HOME is set, and ~/.config/idea/backlog.md otherwise (resolved via Go's os.UserConfigDir). Its parent directory is created on demand on the first mutating write. The file format and all command semantics are identical to a repo backlog — only the path differs.
The backlog path is resolved by this precedence (first match wins). Note that --main and --file/IDEAS_FILE are not independent alternatives: --main selects which root is used, and --file/IDEAS_FILE (when set) are applied within that selected root.
--system— the system backlog, skipping git entirely (reachable from inside a repo too).--main— root = the main worktree root. Git-only: it still errors withnot in a git repositoryoutside a repo. A--file/IDEAS_FILEvalue, if set, is rooted here.- In a git repo (no
--main) — root = the current worktree root. A--file/IDEAS_FILEvalue, if set, is rooted here; otherwise{worktree-root}/fab/backlog.md(the default). - Outside a git repo (no
--main) — root = the system config dir ($XDG_CONFIG_HOME/idea/, else~/.config/idea/). A--file/IDEAS_FILEvalue, if set, is rooted here; otherwise the system backlog (the graceful fallback; commands no longer fail withnot in a git repository).
In all rooted cases an absolute --file/IDEAS_FILE value is used verbatim.
--system and --main are mutually exclusive — passing both is a user error and exits non-zero.
Commands
| Command | Description |
|---|---|
idea "text" | Add a new idea (shorthand for idea add) |
idea add "text" | Add a new idea to the backlog |
idea list | List open (uncompleted) ideas |
idea show <query> | Show a single idea matching the query |
idea done <query> | Mark an idea as done |
idea reopen <query> | Reopen a completed idea |
idea edit <query> | Edit an idea's text in your editor ($VISUAL, then $EDITOR, then vi) on the decoded text |
idea edit <query> "text" | Replace an idea's text inline |
idea rm <query> --yes | Delete an idea (requires --yes/-y or the equivalent --force to confirm; --dry-run previews the match without deleting) |
idea prune [--yes] | Bulk-remove all done ideas (dry run by default; --yes/-y or --force to delete) |
idea promote <query> | Move an idea from the current worktree's backlog to the main worktree's backlog |
idea fmt | Rewrite the backlog into canonical form, adopting bare checkbox lines (--check reports without writing) |
Promote semantics (idea promote <query>): the move preserves the idea's ID, date, and open/done status verbatim; the destination (main worktree) is written before the source (current worktree), so a crash mid-move duplicates the idea rather than losing it. An ID collision in the destination refuses the move with an operational error (exit 1) naming the ID — the ID is never re-minted — and leaves both files untouched. Run from the main worktree (or whenever source and destination resolve to the same file), promote is a no-op: nothing is written, a note: advisory goes to stderr, and the exit code is 0. --main and --system are usage errors with promote (exit 2) — promote defines its own source and destination; --file/IDEAS_FILE apply within each root. Outside a git repository promote fails operationally (not in a git repository, exit 1).
Editor form contract (idea edit <query>, no text argument): an unchanged buffer is a no-op — the backlog is untouched, a note: text unchanged — nothing to do advisory goes to stderr, and the exit code is 0. An emptied buffer is refused: no change, non-zero exit. A non-zero editor exit aborts: the backlog is untouched, non-zero exit. Passing --id/--date with the no-text form still opens the editor, applies the metadata at save, and suppresses the unchanged no-op — a metadata-only change lands without mutating the text.
Exit codes
idea follows the toolkit exit-code convention so a caller (or agent) can branch on why a command failed:
| Code | Class | Examples |
|---|---|---|
0 | Success | any command that completed its operation |
1 | Operational failure | no idea matched a query, ambiguous match, declined consent (rm/prune without --yes/--force), fmt --check on a non-canonical file, file I/O / editor / git-resolution errors |
2 | Usage error | unknown flag, wrong argument count, --system + --main together, shell-init with a missing/unsupported shell |
The distinction is between a malformed invocation (fix the command line → 2) and a well-formed invocation whose operation failed (a different remedy → 1). Error message wording is unchanged by this convention; only the exit code distinguishes the two failure classes.
ID Format & Query Semantics
Each idea gets a short 4-character lowercase alphanumeric ID (e.g., [a7k2]) and an ISO date (YYYY-MM-DD). IDs are unique within a single backlog file.
Queries (the <query> argument on show, done, reopen, edit, rm) match against either the ID or the description text. Matching is substring, case-insensitive. If a query matches more than one idea, the command refuses to act and lists the matches. An exact (case-insensitive) ID match takes precedence over substring matches: when the query is exactly an idea's ID, that idea is selected outright, so passing the canonical ID always resolves it — even when that ID string also appears as a substring inside another idea's text.
Parse & Format Behavior (lenient read, canonical write)
idea is liberal in what it accepts and strict in what it emits:
- Lenient on read. The
YYYY-MM-DD:date segment is optional on input, andideaalso accepts*/+bullets (in addition to-), arbitrary leading whitespace, and CRLF or LF line endings. A line is recognized as an idea by its[ ]/[x]checkbox plus 4-char[id]anchors. This means a hand-edited or externally-authored backlog of dateless- [ ] [id] textlines is read correctly rather than silently ignored. - Canonical on write. Every idea line
ideawrites uses one canonical form —-bullet, no indentation, LF endings, and a date that is always present (today's date is backfilled when the input had none). A mutating command (done/reopen/edit/rm, orprune --forcewhen it removes items) normalizes all recognized idea lines in the file at once; non-mutating commands (list/show) never rewrite the file. - Backfill notice. When a mutating save stamps today's date on one or more previously-dateless items,
ideaprints a brief advisory notice to stderr (note: stamped today's date on N previously-dateless item(s)), keeping stdout machine-parseable. The notice is suppressed when nothing was backfilled. - Explicit canonicalizer.
idea fmtrewrites the whole file into canonical form on demand — no semantic change required — and additionally adopts bare checkbox lines lacking the[id]anchor (fresh unique ID, today's date, checked state preserved), turning an existing markdown task list into a managed backlog in one command. It is idempotent (a second run is byte-stable and skips the write), reports to stderr while stdout stays empty, andidea fmt --checkwrites nothing, prints the would-be report, and exits non-zero when the file is not canonical.
For the full backlog line format — accepted input variants, the canonical output form, date backfill, Shape B pass-through, and the format-contract change note — see backlog-format.md.
External-Consumer Integration
The backlog file is plain Markdown with a stable line format. Any tool that reads fab/backlog.md can discover backlog IDs and descriptions without coupling to idea's internals — this is the contract.
One example consumer is fab-kit's /fab-new, which can accept a backlog ID and pull the description directly from the file when starting a new change. That integration is illustrative, not defining: the file format is the API, and idea is one (canonical) writer of that format.