Skip to content

Repository files navigation

tl cli - Task ledger for your repository

A Git-native task ledger for humans and AI coding agents.

CI Release Go Report Card License: MIT

Quick start — the shortest path to a shared human-and-agent task ledger:

brew install aholbreich/tap/tl && tl init && tl agents --write-files

Then tl ready to see what's open, tl claim <id> to take a task, and tl close <id> when it's done. Install options for every platform are below.

tl demo - init, create, ready, claim, note, close

Why tl?

AI coding agents are a regular part of software teams now, and the hardest problem in a repository is no longer writing code — it is keeping humans and agents coordinated. Chat threads disappear. TODO.md files drift. GitHub Issues live on a remote server and don't follow the code.

tl gives every repository a small, local task ledger that both humans and agents read and update — no daemon, no database, no remote service. Its differentiator from other Git-native trackers is agent-safe coordination: explicit claims with time-limited leases, dependency-aware ready lists, detectable stale work, and a recorded handoff trail — all in state you can read, diff, and reason about with any tool.

  • Agent-safe coordination: claims are explicit and lease-based, stale work is detectable, handoffs are recorded — agents don't silently step on each other
  • Dependency-aware: tl ready only lists work whose blockers are done
  • Git-native: state lives in .tl/ — commit it, diff it, branch it, review it in any PR
  • Human-readable: tasks are plain Markdown with YAML frontmatter — read or edit any task in your editor
  • Agent-readable: every read command supports --json, every write can be attributed with --actor
  • Boring by design: no daemon, no database, no git hooks, no automatic push — you decide when to sync

For an honest feature-by-feature comparison with the nearest tools — Beads, Backlog.md — and with GitHub Issues, see How tl cli compares.

Contents: Quickstart · Setup for agent collaboration · Installation Options · Commands · How tl cli compares · Development · Further reading


Quickstart

One tl init per repository creates the ledger and nothing else:

tl init                                                          # create .tl/ (once per repo)
tl completion --install                                          # TAB-complete task IDs (one-time)
tl create "Add login form validation"                            # add a task
tl create "Refactor auth errors" -t chore -p low --tag auth      # with type, priority, tag
tl list                                                          # see everything
tl show <id>                                                     # full task detail

Take a task from ready, work it, and close it:

tl ready                              # unclaimed, unblocked tasks
tl claim <id>                         # take a time-limited lease (re-run = heartbeat)
tl note <id> -m "Initial pass done."  # record progress for the next person
tl close <id>                         # done and verified

Setup for agent collaboration

Once the ledger exists, hand your agents the playbook in one step:

tl agents --write-files                # merge the tl workflow into AGENTS.md, CLAUDE.md, …

This injects a managed workflow block into the agent instruction files already present in your repo (AGENTS.md, CLAUDE.md, .cursorrules, and friends), so every agent that reads them also knows how to use tl. For constrained context windows:

tl agents --compact                    # print the short version
tl agents --write-files --compact      # write the short version

From then on, the agent loop is:

tl ready --json                          # what's claimable right now?
tl claim <id> --actor agent-a            # take a lease (and say who you are)
tl show <id>                             # read the task in full
tl note <id> -m "Blocked on the API key; handing back." --actor agent-a
tl release <id> --actor agent-a          # step away cleanly — or tl close when done

Identity resolves in order: --actor flag > TL_ACTOR > ACTOR_NAME > BEADS_ACTOR > agent auto-detection > hostname. Detection recognises Claude Code, aider, Windsurf and pi from the environment variables those harnesses export, and Codex from a .codex file. Cursor and GitHub Copilot are deliberately not detected: their variables are set by plain VS Code or are long-lived auth tokens, so a guess there would silently attribute one agent's claims to another. Setting TL_ACTOR once per session is the easiest way to stay attributed.


Installation Options

Latest releases are published to the GitHub Releases page as prebuilt archives for Linux and macOS (amd64 + arm64) and Windows (amd64 + arm64). Every release triggers an automatic update of the Homebrew tap, the RPM repository and the pacman repository.

Homebrew (macOS / Linux)

brew install aholbreich/tap/tl           # latest stable release
brew install --HEAD aholbreich/tap/tl    # or: build from current main

If you install multiple tools from the same tap, you can tap once:

brew tap aholbreich/tap
brew install tl

Arch Linux / Omarchy

Add the Holbreich pacman repository, which is updated automatically on every release:

echo '
[holbreich]
SigLevel = Optional TrustAll
Server = https://aholbreich.github.io/pacman-repo/$arch' | sudo tee -a /etc/pacman.conf
sudo pacman -Sy
sudo pacman -S tl-bin

On Omarchy:

omarchy pkg add tl-bin

tl-bin installs /usr/bin/tl, so the command stays tl. It is named -bin because it packages the prebuilt release binary rather than compiling from source. Thereafter pacman -Syu — or omarchy update — keeps it current like any other package.

SigLevel = Optional TrustAll is required because these packages are not GPG signed. Pacman will still verify the checksums in the repository database and fetch over HTTPS, but it cannot verify who built a package. Add the repository only if you are willing to trust its owner.

Without adding a repository

The PKGBUILD is in this repository, so it can be built directly. This needs no third-party repository and no AUR account, but nothing will upgrade the result — pacman has no source to compare it against.

curl -O https://raw.githubusercontent.com/aholbreich/tl/main/packaging/aur/PKGBUILD
makepkg -si

AUR: tl-bin is prepared for the AUR but not yet published — account registration is unresolved. The pacman repository above is the supported path in the meantime. See pacman-repo.

Install script (macOS / Linux)

curl -fsSL https://raw.githubusercontent.com/aholbreich/tl/main/install.sh | sh

Install a specific version or target directory:

curl -fsSL https://raw.githubusercontent.com/aholbreich/tl/main/install.sh | sh -s -- --version 0.9.0
curl -fsSL https://raw.githubusercontent.com/aholbreich/tl/main/install.sh | sh -s -- --bin-dir "$HOME/.local/bin"

Windows

Download the latest tl-windows-<arch>.zip from the Releases page and unpack tl.exe into a directory on your PATH.

From source

git clone https://github.com/aholbreich/tl
cd tl
make install                # installs `tl` to $HOME/bin

Cross-platform release archives:

make dists                  # tl-linux-amd64.tar.gz, tl-darwin-arm64.tar.gz, …
RPM (Fedora / Red Hat) — repo-based install

Add the Holbreich RPM repository:

# Documentation: https://aholbreich.github.io/rpm-repo/#installation-fedora-centos-redhat
echo '[Holbreich]
name=Holbreich Repository
baseurl=https://aholbreich.github.io/rpm-repo/
enabled=1
gpgcheck=0' | sudo tee /etc/yum.repos.d/holbreich.repo

Install tl:

sudo dnf install tl
tl --version

If you run into issues with the RPM repository, see the rpm-repo project.


Commands

The whole surface at a glance:

# Set up
tl init                            # create the .tl/ ledger (once per repo)
tl completion --install            # enable TAB completion for task IDs

# Define work
tl create "<title>" [-t type -p prio --tag x --ref r -d "..."]  # add a task
tl refine <id> [-p prio -t title --edit] [--actor name] # edit an existing task
tl refine <id> [--add-ref r --remove-ref r]             # attach/detach references
tl dep add <id> --on <id>                               # declare a dependency
tl dep remove <id> --on <id>                            # drop one

# Do the work
tl ready [--tag x] [--json]        # unclaimed, unblocked tasks
tl claim <id>                      # take a time-limited lease (re-run = heartbeat)
tl note <id> -m "..."              # record progress / handoff context
tl close <id>                      # done and verified

# When it doesn't just finish
tl block <id> -m "..."             # external blocker; releases the claim
tl unblock <id>                    # blocker cleared; back to open
tl pending <id> --question "..."   # need a human decision; releases the claim
tl resolve <id> --answer "..." [--actor name] # human answers; task reopens
tl cancel <id> -m "..."            # won't be done
tl remove <id> -m "..." [--force]  # delete a mistaken task file from the active ledger
tl release <id>                    # step away cleanly (leave a note first)

# Inspect
tl list [--all --status s --tag t --mine] [--type t --priority p] [--json]
tl list --dashboard [--tag t] > tasks.md  # regeneratable Markdown overview
tl tree [<id>] [--all] [--json]    # dependency graph as a forest
tl show <id> [--json]              # full task detail
tl history [<id>] [--json]         # event-by-event audit trail
tl stale                           # claims whose lease has expired
tl doctor [--json] [--fix] [--force] # scan ledger for integrity issues (optionally repair)

# Agents
tl agents [--compact] [--write-files [--dry-run] [--file path]] # print or install agent workflow guide

Markdown dashboard

tl list --dashboard writes a plain Markdown snapshot to stdout, grouped by status in list order (pending human, blocked, in progress, open; then done and cancelled with --all). Each task includes its ID, title, status, priority, type, claimant, a one-line description capped at 240 characters, and references. Missing types display and filter as task. References are displayed as text, not interpreted as links. Notes are omitted to keep the overview compact.

Metadata occupies one line in status · priority · type · claimant order:

in_progress · high · task · 👤 aho

The 👤 icon identifies the claimant; - means unclaimed.

All list filters compose: --status, --claimed-by, --mine, --tag, --type/-t, and --priority/-p (including l, m, h aliases). --status done or --status cancelled includes that status without --all. --json takes precedence over --dashboard and keeps the existing list JSON format. Markdown never includes terminal colors or generation timestamps, and listing does not modify the ledger.

tl list --dashboard --type feature --priority high > roadmap.md
tl list --dashboard --tag docs > docs-tasks.md

There is no dedicated area or due-date field; use tags to scope areas of work. Watch mode and dependency-tree rendering are not part of the dashboard.

Dependency tree

tl tree renders the graph the ledger already stores. A task's children are the tasks it depends on, so a parent sits above the slices it waits for, and a root is a task nothing else depends on.

$ tl tree
task-tvp        Test AUR build and installation end-to-end  open         medium
└─ task-o9g     Publish tl-bin initial import to AUR        open         high
   └─ task-8xx  Register AUR account and set up SSH access  in_progress  high
task-ahk        Report feature-level spec tags              open         medium
└─ task-hwb     Document the @implemented tag convention    open         low

Priority is coloured exactly as in tl list — red, yellow, blue for high, medium and low — and closed rows are dimmed when --all reveals them. tl tree carries the same spec column that list and ready do, so a blocked slice and its specification are visible together.

With a task id, only that subtree is drawn. Closed tasks are hidden by default and revealed by --all, matching tl list; naming a task explicitly always renders it, whatever its status.

A shared dependency is legitimate, so it is drawn under every parent that waits for it rather than being assigned to one. A dependency cycle is drawn once and marked (cycle) instead of recursing, so the command always terminates — diagnosing cycles is tl doctor's job, not this one's. Nothing visible is ever dropped: a task no root reaches, which is what happens inside a cycle, is rendered as its own root.

--json nests children so consumers do not re-derive the graph. Its nodes carry id, title, status and priority rather than whole tasks — a shared dependency appears at several positions, and repeating full bodies at each would bloat the document rather than help.

tl tree --json | jq -r '.. | objects | select(.children == []) | .id'   # leaves

Spec status

A reference whose path ends in .feature is treated as a spec reference. That is the whole rule — no flag on tl create, no frontmatter field, no configuration. tl show marks such a reference (spec).

tl list, tl ready and tl tree add a column showing each referenced spec, whether the file is there, and how many scenarios it holds. A Scenario Outline counts once, however many Examples rows it has.

ID        Status  Title                     Spec
task-7fi  open    Add tl tree               features/tree.feature (missing)
task-cys  open    Add tl agents --remove    features/agents.feature (14)
task-wke  open    Add --type field          -

The column is drawn only when some listed task actually carries a spec reference, so a project that writes no Gherkin sees exactly the output it saw before this existed and reads no files. Resolving every spec in a 500-task ledger costs about 3 ms. A missing or unreadable spec renders as (missing) or (unknown) rather than failing the listing — tl doctor is what complains about dead references.

In --json, spec is always an array, never null, so consumers need no presence check.

tl list --json | jq -r '.[] | select(.spec[]?.state == "missing") | .id'

What this does not tell you. The link is to a file. A feature file usually describes a capability while a task is a slice of one, so the column says "this task points at a spec that exists", not "this task's behaviour is specified" — and never that the work is done. Delivery state stays in the task's own status, beside it. See .decisions/0002-reading-referenced-files.md.

Exit codes: 0 success · 1 generic · 2 invalid args · 3 task not found · 4 task not ready · 5 already claimed · 7 lock failed

  • Walkthrough: docs/usage.md — tl by example, flow by flow
  • Behavioral spec: features/ (one .feature file per command)
  • Per-command flags: tl <cmd> --help

How tl cli compares

tl shares a category with Beads and Backlog.md: Git-native task trackers for humans and AI coding agents. The short version — tl is the files-only, no-database option, and its one differentiator is agent-safe coordination with readable, Git-native state: explicit claims, detectable stale work, computable dependencies, recorded handoffs, everything inspectable by hand.

Feature-by-feature, including the honest "why tl and not Beads / Backlog.md / GitHub Issues": docs/comparison.md.


Development

make build                  # version-stamped local binary
make test                   # all Go tests
make bdd                    # godog suite only
make dists                  # local cross-platform archives for manual testing
make release VERSION=x.y.z  # validate, tag, and push; GitHub Actions publishes
make clean

CI runs gofmt, go vet, make build, make test on every PR and push to main (see .github/workflows/ci.yaml). make release VERSION=x.y.z only verifies that HEAD is clean, on main, and already pushed to origin/main, then pushes the tag. The tag-triggered release workflow builds all platform archives and publishes the GitHub Release.


Further reading

About

Git-native task ledger that lets humans and AI coding agents coordinate work in one repo - claims, leases, handoff notes, JSON output. No daemon, no database.

Topics

Resources

Contributing

Stars

9 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages