A lightweight, portable CLI tool for managing personal project backlogs. Data is stored in human-readable YAML inside a .todo/ folder at the project root. Includes a keyboard-driven TUI kanban board.
- Requirements
- Installation
- Quick start
- Keyboard shortcuts
- Configuration
- Git integration
- Building & development
- Documentation
- Project structure
- Go 1.24+ (source installs only)
brew tap yamidaisuke/tasklin https://github.com/yamidaisuke/tasklin
brew install tasklingit clone https://github.com/frankcruz/tasklin
cd tasklin
make install # installs to $GOBIN / $GOPATH/binmake build # produces bin/tasklinRun tasklin init inside any directory (git repo or not):
cd /your/project
tasklin initYou will be prompted to:
- Keep or customise the default statuses (To Do / In Progress / Done)
- Optionally install git hooks that auto-transition tickets on commit/merge
This creates a .todo/ folder with config.yaml and empty tickets/ and deleted/ directories.
Use tasklin add to create tickets without opening the TUI:
tasklin add "Fix login bug"
tasklin add "Add dark mode" -l ui -l frontend
tasklin add "Deploy to staging" -s "In Progress"Prints the new ticket number and title on success:
#42 Fix login bug
Flags:
| Flag | Short | Description |
|---|---|---|
--label |
-l |
Label/tag to attach (repeatable) |
--status |
-s |
Initial status (defaults to first configured status) |
tasklin move 42 "In Progress"
tasklin move 42 done # case-insensitivePrints #<id> → <status> on success. Does nothing if the ticket is already in the target status.
tasklin delete 42Prints #<id> <title> deleted on success and archives the ticket to deleted.yaml so IDs are never reused.
tasklin update 42 --title "New title"
tasklin update 42 -l backend -l api # add labels
tasklin update 42 -r backend # remove a label
tasklin update 42 -t "New title" -l api -r oldPrints the ticket header followed by a line per change:
#42 New title
title: "Old title" → "New title"
labels: +api, -old
Flags:
| Flag | Short | Description |
|---|---|---|
--title |
-t |
New title |
--add-label |
-l |
Label to add (repeatable) |
--remove-label |
-r |
Label to remove (repeatable) |
tasklin show 42
tasklin show 42 --verbose # includes full transition historyExample output:
#42 Fix login bug
──────────────────────────────────────────────
Status ● In Progress
Labels [backend] [api]
Created 15 May 2026
──────────────────────────────────────────────
Transitions
14 May 2026 09:00 To Do → In Progress
Run tasklin with no arguments to open the kanban board:
tasklinIf .todo/ does not exist yet, the init flow runs automatically.
| Key | Action |
|---|---|
← / → or h / l |
Move focus between columns |
↑ / ↓ or k / j |
Move focus between tickets within a column |
Shift+← / Shift+→ |
Move the selected ticket one column left / right |
Enter |
View ticket detail (full transition history) |
n |
Create a new ticket in the focused column |
e |
Edit the selected ticket's title |
l |
Edit labels on the selected ticket |
/ |
Filter board by label |
m |
Open the move dialog to pick a target status |
d |
Delete the selected ticket (soft-deleted to deleted/) |
c |
Open the config screen |
? |
Show help overlay |
q / Ctrl+C |
Quit |
| Key | Action |
|---|---|
↑ / ↓ or k / j |
Select target status |
Enter |
Confirm move |
Esc / q |
Cancel |
| Key | Action |
|---|---|
↑ / ↓ or k / j |
Navigate fields |
Enter / Space |
Edit the focused field (or open status management) |
Esc / q |
Go back to the board |
| Key | Action |
|---|---|
↑ / ↓ or k / j |
Navigate statuses |
Shift+↑ / Shift+↓ |
Reorder the focused status |
n |
Add a new status |
e |
Edit name and colour of the focused status |
d |
Delete the focused status (minimum 2 required) |
Esc / q |
Go back to config |
All data lives in .todo/ at the project root and is plain YAML — safe to commit.
.todo/
├── config.yaml # statuses, title limit, default done status, auto-commit flag
├── tickets/ # one YAML file per active ticket (e.g. ab3f92c1.yaml)
├── deleted/ # one YAML file per soft-deleted ticket (never removed)
└── labels.yaml # index of all known labels (used for autocomplete)
| Field | Type | Default | Description |
|---|---|---|---|
title_limit |
int | 0 |
Max ticket title length (0 = unlimited) |
default_done_status |
string | "Done" |
Status name treated as "done" for hooks and auto-commit |
auto_commit_on_done |
bool | false |
Trigger interactive git commit when a ticket reaches done |
statuses |
list | To Do / In Progress / Done | Ordered list of status columns |
Tickets can have zero or more labels. Labels follow identifier rules: must start with a letter, followed by any combination of letters, digits, and underscores ([A-Za-z][A-Za-z0-9_]*).
- Press
lon any ticket to open the label editor - Type a label name;
Tab/Shift+Tabcycles through autocomplete suggestions drawn from previously used labels Enteradds the label;Backspaceat an empty input removes the last label on the ticket- Labels are displayed as
[chip]rows below the ticket title in the board (up to 2 rows) - Press
/to open the label filter — add one or more labels; only tickets matching all active filters are shown - Active filters are shown in the footer as
▼ label1 label2 Ctrl+Uinside the filter screen clears all active filters
All known labels are persisted in .todo/labels.yaml to power autocomplete across sessions.
When auto_commit_on_done is enabled, moving a ticket to the Done status triggers an interactive git commit flow:
- Any new (untracked) files are listed — confirm each with
y/N - Any deleted files are listed — confirm each with
y/N git add -pruns for interactive hunk selection on modified files- A commit is created with the message
[ab3f92c1] Title(8-char hex ticket ID) if anything was staged
Enable it from the in-app config screen (c) or by editing .todo/config.yaml directly.
When you run tasklin init inside a git repository you are offered the option to install hooks:
- commit-msg — if the commit message starts with
[ab3f92c1](8-char hex ID), transitions that ticket to the done status - post-merge — if the merged branch name contains
[ab3f92c1], transitions that ticket - pre-commit (optional) — automatically stages
.todo/in every commit
If you installed hooks before upgrading to per-file storage, run tasklin once to trigger automatic hook reinstall, or re-run tasklin init to reinstall manually.
All common operations are covered by the Makefile. Run make help to list every available target.
| Target | Description |
|---|---|
make / make build |
Compile for the current OS/ARCH into bin/tasklin |
make build-all |
Cross-compile for all platforms (see below) |
make run |
Build and launch tasklin in the current directory |
make run-sample |
Build and launch tasklin inside the generated sample project |
make sample |
Generate the sample/ test project (skips if already present) |
make sample CLEAN=1 |
Wipe and regenerate sample/ from scratch |
make test |
Run all unit tests with the race detector and coverage |
make test-ci |
Run tests in CI mode, writing coverage.out |
make install |
Install tasklin to $GOBIN / $GOPATH/bin |
make clean |
Remove bin/ and coverage.out |
make build-all produces one binary per platform under bin/:
| Platform | Output |
|---|---|
| Linux amd64 | bin/tasklin-linux-amd64 |
| Linux arm64 | bin/tasklin-linux-arm64 |
| macOS amd64 | bin/tasklin-darwin-amd64 |
| macOS arm64 | bin/tasklin-darwin-arm64 |
| Windows amd64 | bin/tasklin-windows-amd64.exe |
Version metadata (version, commit, buildDate) is injected at link time via -ldflags and can be overridden:
make build VERSION=1.2.0 COMMIT=abc1234A script in resources/gen-sample.sh generates a self-contained test environment under sample/ (gitignored):
make sample # generate (skips if sample/ already exists)
make sample CLEAN=1 # wipe and regenerate from scratch
make run-sample # build + generate + launch tasklin inside sample/Detailed developer documentation lives in docs/:
| Document | Description |
|---|---|
| Architecture | System design, component diagram, data flow |
| UI Reference | All TUI screens documented with ASCII art |
| Data Model | Data structures, YAML schemas, UML class diagram |
| Developer Guide | How to implement features, conventions, debugging |
| Requirements | Original requirements and acceptance criteria |
.
├── main.go
├── cmd/
│ ├── root.go # entry point, opens TUI
│ ├── init.go # `tasklin init` command
│ └── transition.go # internal `tasklin _transition` (used by git hooks)
├── internal/
│ ├── model/ # data types (Ticket, Status, Config, GlobalState)
│ ├── store/ # YAML persistence and branch-state helpers
│ ├── git/ # git root / branch detection
│ ├── hooks/ # git hook file generation
│ └── tui/ # Bubble Tea TUI (all screens in tui.go)
├── resources/
│ └── gen-sample.sh # generates sample/ with 1 000 tickets for testing
└── docs/
├── architecture.md
├── ui-reference.md
├── data-model.md
├── developer-guide.md
└── REQUIREMENTS.md