CLI reference
Every subcommand btw exposes is documented on this page. For the on-disk shape of task files, see File & storage format. For JSON output and error envelopes, see Agentic usage.
Global flags
Section titled “Global flags”--format=human|json (default human).
JSON output is intended for agentic consumers; the schema is documented as stable. Human output is the default and is what you see when running btw directly in a terminal.
--version (alias -v) prints the binary’s version on a single line and exits — see the version subcommand below for the underlying resolution rules.
Subcommands at a glance
Section titled “Subcommands at a glance”btw add <description>btw list [--all] [--closed] [--limit N] [--tag TAG]btw show <fragment>bytheway update <fragment> <new description>bytheway append <fragment> <text>btw close <fragment>bytheway reopen <fragment>btw edit <fragment>btw tag add <fragment> <tag>btw tag rm <fragment> <tag>btw tag ls [--all] [--closed]btw research [<fragment>]btw version<fragment> is anything that resolves to a single task. See Match resolution below.
There is no delete subcommand by design. Removing a task means rm against the data directory.
Creates a new task in open/, prints added <id>.
$ btw add buy milkadded abcdef12The slug is generated from the description and frozen on the file. update later replaces the description without renaming the file.
Lists open tasks by default.
Flags:
--allincludes closed tasks (truncated to the most recent 20).--closedshows closed tasks only.--limit Noverrides the truncation.--tag TAGfilters to tasks carrying the tag (single value, exact match after lowercase normalization). Composes as logical AND with--all/--closed. A non-matching tag returns an empty list with no error; an invalid tag value (whitespace, uppercase that doesn’t normalize cleanly, characters outside[a-z0-9-]) is rejected.
$ btw list$ btw list --all$ btw list --closed --limit 50$ btw list --tag bug$ btw list --tag infra --allIn --format=human, the rendering is TTY-aware:
- When stdout is a terminal, rows render as a borderless aligned table with a subdued
ID CREATED DESCRIPTIONheader, IDs in an accent colour, dates faint and date-only (YYYY-MM-DD), and descriptions hard-truncated with…to fit the terminal width.NO_COLORdisables colour while preserving layout. - When stdout is piped, redirected, or running in a non-TTY environment (CI, scripts), output is plain
id YYYY-MM-DD HH:MM descriptionrows with no header and no ANSI escapes — the contract callers rely on forgrep,awk, and other text tools.
In --format=json, output is a JSON array; the schema is documented in Agentic usage and is unchanged by TTY detection.
Prints a single task.
In --format=human, the rendering is TTY-aware:
- When stdout is a terminal, the body is rendered as styled Markdown (headings, code blocks, lists, tables) via glamour using its
autostyle, wrapped at the terminal width capped at 120 columns. A subdued one-line metadata strip —{id} · {YYYY-MM-DD created} · {open|closed}— is printed above the body, followed by a second faint line bearing the absolute task file path (no label, no quoting — bare path only). The path line lets a double-click select the whole path cleanly; terminal soft-wrap handles overflow on narrow terminals. Operational fields likelast_researchedandlast_research_logare intentionally omitted; agents can still get them via--format=json.NO_COLORdisables colour while preserving layout. - When stdout is piped, redirected, or running in a non-TTY environment (CI, scripts), the raw markdown file (frontmatter included) is written verbatim — unchanged and byte-identical to the pre-path-line behaviour — preserving any workflow that pipes
showoutput into an editor or other tooling.
In --format=json, it prints a JSON object with id, created, description, body, path, and (if closed) completed. When the task has been researched, the object also carries last_researched (RFC 3339 UTC) and last_research_log (absolute path to the latest run’s JSONL log); both are omitted when un-researched. path is the absolute, cleaned filesystem path of the task file; symlinks in the configured tasks directory are preserved verbatim. JSON output is unchanged by TTY detection.
$ btw show milk$ btw --format=json show abcdef12$ btw --format=json show abcdef12 | jq -r .path # the file on disk$ btw --format=json show abcdef12 | jq -r .last_research_log # latest research logOlder releases shipped a dedicated btw research-log <fragment> subcommand that printed only the latest research-log path; it has been removed in favour of the show --format=json | jq -r .last_research_log form above. Existing scripts that called btw research-log should switch to that pipeline.
update
Section titled “update”Replaces the first body line — the canonical description — without touching frontmatter, ID, slug, or filename.
$ bytheway update milk buy oat milk insteadappend
Section titled “append”Adds text to the body of a task, preserving everything above it.
$ bytheway append milk remember the brandMoves the file from open/ to closed/ via os.Rename and stamps completed: into the frontmatter.
$ btw close milkreopen
Section titled “reopen”The inverse of close: moves the file back to open/ and clears the completed: field.
$ bytheway reopen milktag add
Section titled “tag add”Adds a tag to an existing task. The tag is normalized (lower-cased, trimmed) and validated against [a-z0-9-] up to 40 characters. Re-running with the same tag is a no-op.
$ btw tag add milk urgenttagged abcdef12 with urgenttag rm
Section titled “tag rm”Removes a tag from an existing task. Re-running with an absent tag is a no-op. When the last tag is removed, the tags: frontmatter line is omitted entirely.
$ btw tag rm milk urgentremoved urgent from abcdef12tag ls
Section titled “tag ls”Reports each distinct tag in the corpus along with the count of tasks carrying it, sorted alphabetically by tag name. Tags with zero occurrences never appear — the listing is derived from task files; tags are not pre-registered.
Flags:
--allincludes closed tasks alongside open.--closedscans closed tasks only.
Default is open tasks only — same semantics as list.
In --format=human, output is a single space-padded line of name (count) entries, no header, no ANSI escapes. An empty corpus emits a blank line.
$ btw tag lsbug (3) infra (5) urgent (2)In --format=json, output is {"tags": [{"name": ..., "count": ...}, ...]}. The tags key is always present (empty array when none) so consumers can jq '.tags' without null-checking.
$ btw --format=json tag ls{ "tags": [ {"name": "bug", "count": 3}, {"name": "infra", "count": 5}, {"name": "urgent", "count": 2} ]}syscall.Execs $EDITOR (or the configured override, falling back to vi) on the resolved task path. See Configuration for editor resolution.
$ btw edit milkThis command is not usable from an agent shell because it replaces the calling process with the editor. The reference slash command instructs the user to run btw edit <id> from their own shell instead.
research
Section titled “research”Spawns a headless Claude Code agent to gather context for one task or sweep every open task. The agent runs unattended with --permission-mode=bypassPermissions; the security boundary is the --allowed-tools list passed to it.
$ btw research milk # research a single task$ btw research # batch-research every open taskFlags:
--concurrency N(default3): max concurrent in-flight research runs in batch mode.--all: force re-research of every open task, ignoringlast_researched.--stale DURATION: re-research tasks whoselast_researchedis older than this duration.--tag TAG(batch mode only): scope the sweep to open tasks carrying this tag (exact match, lowercased before filtering). Composes as an additional AND with--all/--stale— tag filter is applied first, then staleness logic runs over the surviving tasks. A non-matching tag yields a normal completion with zero tasks researched. Rejected with an error in single-task mode (when a<fragment>is supplied).--timeout DURATION(default10m): per-task hard timeout.--model ID: claude model id; overridesresearch_modelfromconfig.toml.
The shipped binary’s --allowed-tools baseline contains five built-in read-only tools and nothing else: Read, Glob, Grep, WebSearch, WebFetch. To extend the allowlist with MCP tools or pattern-restricted Bash(...) invocations on a per-machine basis, add a research_extra_tools block to your config.toml. There is no --extra-tool flag; tool availability is a per-machine property, not a per-invocation one.
Batch outcome reporting
Section titled “Batch outcome reporting”In a batch run (btw research or btw research --all), every task makes it into the final summary, even when prep- or post-run plumbing fails. Two cases that previously fell through the cracks now surface as failed outcomes:
- Writeback errors (the agent succeeded but the cmd could not append the Research section, set the
last_researchedfrontmatter, or append the worklog line — for example, the task file went missing mid-run, or the worklog directory is unwritable) emit atask_failedevent and afailedrow in the summary. The agent’s original summary text is preserved in the row’ssummaryfield; the writeback error message goes intoerror. Previously these were silently reported as if the writeback had succeeded. - Per-task
prompt.Renderfailures during prep (a malformed override template, or a task whose frontmatter does not satisfy a custom template’s field references) emit a synthetictask_failedevent for that task and afailedrow in the summary. Other tasks in the batch still run. Previously a single render failure aborted the whole sweep before any task ran.
Both cases count toward totals.failed in the summary; the summary’s totals.skipped continues to count only the staleness-based skip path.
JSON output is documented in Agentic usage.
version
Section titled “version”Prints the binary’s build identity. The same identity is what btw --version and btw -v report on a single line via cobra’s built-in version flag.
$ btw versionbytheway v1.2.3 (commit abcdef1, built 2026-05-03T10:00:00Z)
$ btw --format=json version{ "version": "v1.2.3", "commit": "abcdef1", "date": "2026-05-03T10:00:00Z", "source": "ldflags"}The JSON schema is {"version": string, "commit": string, "date": string, "source": string}. The source field records which resolution branch produced the values: "ldflags" for release builds with -ldflags="-X ...", "buildinfo" for binaries produced by go install module@version (where the version is read from the Go module proxy via runtime/debug.ReadBuildInfo), and "default" for unannotated developer builds (which report "version": "dev").
Match resolution
Section titled “Match resolution”For commands that take <fragment>, btw tries strategies in order and stops at the first one that yields any matches:
- ID exact
- ID prefix
- Description exact (case-insensitive)
- Description substring (case-insensitive)
If a strategy yields exactly one match, that is the result. If it yields more than one, the command exits non-zero with ErrAmbiguous and lists the candidates. If no strategy matches, it exits non-zero with ErrNoMatch and lists the current open tasks so the caller can pick.
In --format=json, both error cases produce a JSON envelope with a stable error discriminator ("ambiguous" or "no_match") so agents can branch without parsing prose. See Agentic usage for the envelope shapes.
Shell completion
Section titled “Shell completion”Cobra emits completion scripts for bash, zsh, fish, and powershell via the hidden completion <shell> subcommand. Generate once and write to your shell’s completions directory; the script delegates back to the binary at Tab time, so completions stay in sync as new subcommands ship without regenerating the script.
# fishbtw completion fish > ~/.config/fish/completions/btw.fish
# zsh (paths vary by setup)btw completion zsh > "${fpath[1]}/_btw"
# bashbtw completion bash > /etc/bash_completion.d/btwFor commands that take <fragment> — show, close, reopen, edit, update, append, research, tag add, tag rm — pressing Tab on the fragment position lists candidate tasks as <id>\t<first body line>, which fish and zsh render as the id alongside the task description. The candidate set is scoped per command:
close: open tasks onlyreopen: closed tasks onlyresearch: open tasks only- everything else: open and closed
Subsequent positional arguments (e.g. the <text> of update <fragment> <text>) return no candidates and suppress filename fallback, so a stray Tab on the second arg does not flood the menu with files from the current directory.