tix
Keyboard-driven terminal ticket explorer for a tree of markdown briefs. Linear-like TUI, zero deps beyond the Python stdlib + an optional markdown pager.
LANES STATE CTX
◐ P1 teams-error-mapping active 74K
○ P0 oauth-rotation-plan open —
● audit-logs-rollout done —
Why
- The filesystem is the database. A ticket is a markdown file.
grep,git log, andlsall keep working. - No SaaS, no auth, no network. Runs entirely against a local tree.
- Linear-like keys.
j/k,/to filter,Enterto open,pto pick up into awtlane. - Pure reader. tix never writes
status:for you. If you want auto-status derivation, wire up your own preload hook (see below).
Install
pipx install tix-cli
# or, in a venv:
pip install tix-cli
Installs one console script: tix.
From source
git clone https://github.com/gitpancake/tix
cd tix
pipx install --editable .
Quickstart
mkdir -p ~/.pi/agent/tickets/spikes
cp $(python -c 'import tix, pathlib; print(pathlib.Path(tix.__file__).parent / "templates" / "_TEMPLATE.md")') \
~/.pi/agent/tickets/spikes/my-first-ticket.md
tix
Or point at a project tree:
tix my-project # browses ~/.pi/agent/tickets/my-project plus
# ~/.claude/tickets/my-project when both exist, and
# chdirs into ~/Documents/code/my-project so pickup
# (p) runs against the right repo
TIX_CODE_DIR=~/src tix my-project # override the code-repo lookup root
TICKETS_DIR=./docs/tickets tix
For zsh users who want per-project TICKETS_DIR to follow cd, add this chpwd hook to ~/.zshrc:
autoload -U add-zsh-hook
_tix_tickets_dir() {
local root base
root=$(git rev-parse --show-toplevel 2>/dev/null) || { unset TICKETS_DIR; return; }
base=${root:t}
if [[ -d "$HOME/.pi/agent/tickets/$base" ]]; then
export TICKETS_DIR="$HOME/.pi/agent/tickets/$base"
else
unset TICKETS_DIR
fi
}
add-zsh-hook chpwd _tix_tickets_dir
_tix_tickets_dir
Mini reader
For narrow sidecars (think tmux pane next to an editor):
tix --mini # flat reverse-chrono list, ≥20 cols
tix my-project --mini # same project resolution as `tix <project>`
Mini surfaces a single signal — what was filed recently, can I pick one
up — and gets out of the way. Done/cancelled hidden; ↑/↓ move, Enter
opens in glow/$PAGER, p spawns wt, l sets/clears label:, q
quits. No filters, no groups, no rescope — use plain tix for that.
Keys
| Key | Action |
|---|---|
↑ ↓ / j k |
Move |
Ctrl-U Ctrl-D |
Half page |
g G |
Top / bottom |
Enter → |
Open in glow (or $PAGER) |
Esc ← h |
Collapse / back |
/ |
Filter |
e |
Edit in $EDITOR |
p |
Pickup → wt <slug> |
i |
Pin status active |
d |
Pin status done |
x |
Pin status cancelled |
l |
Set/clear label: |
m |
Move ticket to area |
y |
Copy slug to clipboard |
o |
Open Linear URL (if linear: set) |
r |
Reload (re-runs preload hook if set) |
? |
Help |
q |
Quit |
Schema
A ticket is a markdown file with YAML-ish line-based frontmatter:
---
status: open
priority: P1
label: backend
area: integrations
linear: PROJ-123
---
# teams-error-mapping
## Context
…
## Acceptance criteria
- [ ] …
Full contract: docs/ticket-schema.md.
- Filename is the slug.
teams-error-mapping.md, neverPROJ-123.md. - Epic = folder. A directory containing
_epic.mdis an epic; numbered children (01-foo.md,02-bar.md) are its stories. - Status vocab is pinned:
active,open,draft,done,cancelled.
Configuration
| Env | Default | Purpose |
|---|---|---|
TICKETS_DIR |
~/.pi/agent/tickets |
Primary ticket tree / write root |
TIX_EXTRA_TICKETS_DIRS |
(unset) | Extra read roots, separated by : on macOS/Linux |
TIX_PICKUP_AGENTS |
(unset) | Per-root agent launcher for p: <root>=<cmd> pairs, separated by :. When a picked-up ticket lives under <root>, wt runs with WT_AGENT_CMD=<cmd>. No match → wt's own default. Longest-ancestor root wins. An explicit WT_AGENT_CMD in the environment overrides the map. |
TIX_CODE_DIR |
~/Documents/code |
Lookup root for tix <project> — the repo it chdirs into so pickup works |
ACTIVE_LANES_FILE |
~/.claude/active-lanes.json |
Optional sidecar map: slug → {path, branch, repo, last_commit}. Read by the TUI; tix never writes it. |
LINEAR_WORKSPACE |
(unset) | Slug used to derive linear: URLs (o key) |
TIX_PRELOAD_HOOK |
(unset) | Shell command run before launch. See below. |
EDITOR |
vi |
Used by e |
PAGER |
less |
Fallback when glow is absent |
TICKETS_DIR resolves in this order: explicit env var → ~/.pi/agent/tickets. When TICKETS_DIR points at a project under either ~/.pi/agent/tickets/<project> or ~/.claude/tickets/<project>, tix also reads the matching other-side project tree if it exists, so Pi-created and Claude-created tickets both surface. tix <project> resolves the primary write root (centralized ~/.pi/agent/tickets/<project>/ first, legacy centralized ~/.claude/tickets/<project>/ second, then $TIX_CODE_DIR/<project>/.claude/tickets/, then ./<project>/.claude/tickets/ for legacy trees), adds any other existing project roots as extra read roots, and chdirs into the project's git repo under $TIX_CODE_DIR (default ~/Documents/code) so the pickup key (p) runs wt against the right repo. Or set TICKETS_DIR explicitly (e.g. via the chpwd hook above).
Preload hook
tix doesn't write status: — that's deliberate. If you want statuses derived from external signals (live worktrees, feature branches, merged PRs, calendar events, anything), put a script on disk and point at it:
export TIX_PRELOAD_HOOK=~/bin/my-status-sync
tix
The hook runs once before the TUI is drawn. Its stdout/stderr are discarded — curses is about to claim the screen, so the next render is the feedback, not the printed diff. The hook is best-effort: a missing or failing command never blocks launch.
The r key in the TUI re-runs the hook and reloads.
A reference implementation (filesystem + git + gh) lives in gitpancake/.dotfiles as claude/scripts/ticket-status-sync.py — it derives active from live worktrees and done from merged PRs. Copy it, fork it, replace it.
Optional integrations
wt— if awtcommand is on PATH, thepkey suspends curses, runsgit fetch && git checkout main && git merge --ff-only && wt <slug>, then resumes.glow— preferred markdown pager for ticket preview. tix reflows hard-wrapped briefs and pipes them to glow word-wrapped at the terminal width, so authored line width never causes ragged re-wrapping. Falls back to$PAGER(defaultless).gh— used by some preload hooks (not by tix itself).
Non-goals
- No remote sync, no auth, no web UI.
- No mouse support.
- No notifications.
- No bundled status reconciler — wire your own via
TIX_PRELOAD_HOOK.
License
MIT.
Metadata
Release files for tix-cli 0.3.19
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| tix_cli-0.3.19.tar.gz | 57.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| tix_cli-0.3.19-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 93.3 kB
Release files / tix_cli-0.3.19.tar.gz
| Download URL | tix_cli-0.3.19.tar.gz |
|---|---|
| Size | 57.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
dad8ecaaf26073716bceabd7f3050be9e8d3718938394cc1b99be28bab660b61
|
|
BLAKE2b-256 checksum How to use checksums |
b10fa23e8318bc49b9ee410f36fb0b46d2f6c845156e11132b053940a02ae2fa
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.11 {"installer":{"name":"uv","version":"0.11.11","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / tix_cli-0.3.19-py3-none-any.whl
| Download URL | tix_cli-0.3.19-py3-none-any.whl |
|---|---|
| Size | 35.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
4356bbd7857d74f20d81d538e625e7fc54ae149fdf67bfde3f47fbaecd0abdf8
|
|
BLAKE2b-256 checksum How to use checksums |
a22bf01efb64d294ec7b1dc59cfae72740369c37a909ffac36adac0bc28901fa
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.11 {"installer":{"name":"uv","version":"0.11.11","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|