tix-cli 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tix_cli-0.1.0/.github/workflows/ci.yml +21 -0
- tix_cli-0.1.0/.gitignore +13 -0
- tix_cli-0.1.0/CHANGELOG.md +32 -0
- tix_cli-0.1.0/CLAUDE.md +68 -0
- tix_cli-0.1.0/LICENSE +21 -0
- tix_cli-0.1.0/PKG-INFO +191 -0
- tix_cli-0.1.0/README.md +146 -0
- tix_cli-0.1.0/docs/ticket-schema.md +124 -0
- tix_cli-0.1.0/pyproject.toml +49 -0
- tix_cli-0.1.0/src/tix/__init__.py +3 -0
- tix_cli-0.1.0/src/tix/__main__.py +29 -0
- tix_cli-0.1.0/src/tix/templates/_CHILD-TEMPLATE.md +20 -0
- tix_cli-0.1.0/src/tix/templates/_EPIC-TEMPLATE.md +42 -0
- tix_cli-0.1.0/src/tix/templates/_TEMPLATE.md +33 -0
- tix_cli-0.1.0/src/tix/tui.py +1183 -0
- tix_cli-0.1.0/tests/fixtures/tickets/README.md +3 -0
- tix_cli-0.1.0/tests/fixtures/tickets/integrations/alpha.md +12 -0
- tix_cli-0.1.0/tests/fixtures/tickets/spikes/beta.md +10 -0
- tix_cli-0.1.0/tests/test_smoke.py +50 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
name: ci
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
strategy:
|
|
12
|
+
matrix:
|
|
13
|
+
python-version: ["3.8", "3.11", "3.12"]
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
- uses: actions/setup-python@v5
|
|
17
|
+
with:
|
|
18
|
+
python-version: ${{ matrix.python-version }}
|
|
19
|
+
- run: pip install -e . pytest ruff
|
|
20
|
+
- run: ruff check src tests
|
|
21
|
+
- run: pytest -q
|
tix_cli-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.1.0] — 2026-05-18
|
|
9
|
+
|
|
10
|
+
Initial public release.
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- Curses TUI over a tree of markdown ticket briefs.
|
|
14
|
+
- Keyboard-driven navigation, filter (`/`), edit (`e`), pickup → `wt` (`p`).
|
|
15
|
+
- Sticky status pins: `i` (active), `d` (done), `x` (cancelled).
|
|
16
|
+
- Move ticket between areas (`m`), copy slug (`y`), open Linear URL (`o`).
|
|
17
|
+
- Split-pane preview with `glow` / `$PAGER` fallback.
|
|
18
|
+
- `tix <project>` form: `chdir` into `./<project>` and set
|
|
19
|
+
`TICKETS_DIR=<project>/.claude/tickets`.
|
|
20
|
+
- `TIX_PRELOAD_HOOK` env var — runs a user-supplied shell command before the
|
|
21
|
+
TUI launches so external tooling can derive `status:`. tix itself is a pure
|
|
22
|
+
reader.
|
|
23
|
+
- Bundled `_TEMPLATE.md`, `_EPIC-TEMPLATE.md`, `_CHILD-TEMPLATE.md` accessible
|
|
24
|
+
via `importlib.resources` at `tix/templates/`.
|
|
25
|
+
|
|
26
|
+
### Schema
|
|
27
|
+
- Filename is the slug.
|
|
28
|
+
- Epic = folder containing `_epic.md`.
|
|
29
|
+
- Status vocab pinned: `active`, `open`, `draft`, `done`, `cancelled`.
|
|
30
|
+
- Frontmatter parser is line-based (no PyYAML).
|
|
31
|
+
|
|
32
|
+
[0.1.0]: https://github.com/gitpancake/tix/releases/tag/v0.1.0
|
tix_cli-0.1.0/CLAUDE.md
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
## Project
|
|
4
|
+
|
|
5
|
+
`tix` is a keyboard-driven curses TUI over a tree of markdown ticket briefs. Stdlib-only Python. Single surface:
|
|
6
|
+
|
|
7
|
+
- `src/tix/tui.py` — curses reader. Renders, filters, navigates, pickups.
|
|
8
|
+
- `src/tix/__main__.py` — CLI router (default → TUI; `tix <project>` → cd-and-set-TICKETS_DIR).
|
|
9
|
+
|
|
10
|
+
**tix is a pure reader.** It does not write `status:` frontmatter. Users who want status auto-derived wire up their own script via `TIX_PRELOAD_HOOK`. That contract is load-bearing — do not bundle a reconciler.
|
|
11
|
+
|
|
12
|
+
## Invariants
|
|
13
|
+
|
|
14
|
+
1. **No status writes from tix.** `i`/`d`/`x` sticky pins are the only mutation path, and even those just edit the ticket file's frontmatter directly — they never derive state from external signals.
|
|
15
|
+
2. **Stdlib-only.** No PyYAML, no `rich`, no `prompt_toolkit`. Adds startup latency we don't accept. Frontmatter parser is intentionally line-based; preserve that contract.
|
|
16
|
+
3. **Filename = slug.** Never derive a slug from frontmatter `id:` or filename munging. `path.stem` is authoritative.
|
|
17
|
+
4. **Filesystem = DB.** No `.tix-cache`, no SQLite. `ACTIVE_LANES_FILE` is an *optional read-only* sidecar — tix consumes it if present (a preload hook might populate it) but never writes it itself.
|
|
18
|
+
|
|
19
|
+
## Status vocab (pinned)
|
|
20
|
+
|
|
21
|
+
`active`, `open`, `draft`, `done`, `cancelled`. Adding one requires changes in two places:
|
|
22
|
+
|
|
23
|
+
- `tui.py` → `STATUS_META`, `FILTER_ORDER`
|
|
24
|
+
- `docs/ticket-schema.md` → contract update
|
|
25
|
+
|
|
26
|
+
Pre-migration title-case variants (`In Progress`, `Todo`, etc.) are kept as read-only aliases — don't extend that set.
|
|
27
|
+
|
|
28
|
+
## TICKETS_DIR resolution
|
|
29
|
+
|
|
30
|
+
Order: `$TICKETS_DIR` (explicit) → `~/.claude/tickets` (fallback). No project-local autodiscovery. The `tix <project>` form is sugar that does `chdir` + sets `TICKETS_DIR` to `<project>/.claude/tickets` before the TUI imports.
|
|
31
|
+
|
|
32
|
+
## Preload hook
|
|
33
|
+
|
|
34
|
+
`run_preload_hook()` reads `TIX_PRELOAD_HOOK` and runs it as a shell command before curses takes over. Output is captured. Failures are swallowed. This is the only extension point tix exposes — keep it minimal. Do not add an "after" hook or per-action hooks; users with that level of need should fork.
|
|
35
|
+
|
|
36
|
+
## Editing rules
|
|
37
|
+
|
|
38
|
+
- `tui.py` runs inside `curses.wrapper`. Anything that prints or raises can wreck the terminal. Wrap subprocess calls in `try/except (OSError, subprocess.SubprocessError)`.
|
|
39
|
+
- Status bar rendering is hot. Don't read filesystem inside the render loop — `App.rebuild_rows()` is the bulk-read step.
|
|
40
|
+
- `subprocess.run` w/ `wt`/`git` returns are mostly best-effort. Keep them that way — surfacing transient failures into the TUI is worse than hiding them.
|
|
41
|
+
|
|
42
|
+
## Tests
|
|
43
|
+
|
|
44
|
+
Fixture-driven under `tests/fixtures/tickets/`. No network. Run:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pytest -q
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Distribution
|
|
51
|
+
|
|
52
|
+
- PyPI distribution name: `tix-cli`. Import name + executable: `tix`.
|
|
53
|
+
- One console script: `tix`.
|
|
54
|
+
- No native deps. `pipx install tix-cli` is the recommended install.
|
|
55
|
+
- Templates ship in `src/tix/templates/` and are accessed via `importlib.resources` (or `tix.__file__`-relative paths) — never hardcode an install prefix.
|
|
56
|
+
|
|
57
|
+
## Org-specific bits to keep configurable
|
|
58
|
+
|
|
59
|
+
- `AREAS` in `tui.py` is currently a fixed list. **Don't hardcode org-specific area names** when extending — make it configurable via env or a config file before any new area lands.
|
|
60
|
+
- `LINEAR_WORKSPACE` is the only external-tracker breadcrumb. No GitHub Issues / Jira integration creep — that belongs in a preload hook.
|
|
61
|
+
|
|
62
|
+
## Non-goals
|
|
63
|
+
|
|
64
|
+
- No remote sync.
|
|
65
|
+
- No mouse support.
|
|
66
|
+
- No realtime collab.
|
|
67
|
+
- No bundled markdown renderer — defer to `glow` / `$PAGER`.
|
|
68
|
+
- No bundled status reconciler — defer to `TIX_PRELOAD_HOOK`.
|
tix_cli-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Henry Pye
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
tix_cli-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tix-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Keyboard-driven terminal ticket explorer for a tree of markdown briefs.
|
|
5
|
+
Project-URL: Homepage, https://github.com/gitpancake/tix
|
|
6
|
+
Project-URL: Repository, https://github.com/gitpancake/tix
|
|
7
|
+
Project-URL: Issues, https://github.com/gitpancake/tix/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/gitpancake/tix/blob/main/CHANGELOG.md
|
|
9
|
+
Author: Henry Pye
|
|
10
|
+
License: MIT License
|
|
11
|
+
|
|
12
|
+
Copyright (c) 2026 Henry Pye
|
|
13
|
+
|
|
14
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
15
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
16
|
+
in the Software without restriction, including without limitation the rights
|
|
17
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
18
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
19
|
+
furnished to do so, subject to the following conditions:
|
|
20
|
+
|
|
21
|
+
The above copyright notice and this permission notice shall be included in all
|
|
22
|
+
copies or substantial portions of the Software.
|
|
23
|
+
|
|
24
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
25
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
26
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
27
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
28
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
29
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
30
|
+
SOFTWARE.
|
|
31
|
+
License-File: LICENSE
|
|
32
|
+
Keywords: curses,issue-tracker,markdown,tickets,tui
|
|
33
|
+
Classifier: Development Status :: 4 - Beta
|
|
34
|
+
Classifier: Environment :: Console :: Curses
|
|
35
|
+
Classifier: Intended Audience :: Developers
|
|
36
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
37
|
+
Classifier: Operating System :: MacOS
|
|
38
|
+
Classifier: Operating System :: POSIX
|
|
39
|
+
Classifier: Programming Language :: Python :: 3
|
|
40
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
41
|
+
Classifier: Topic :: Software Development
|
|
42
|
+
Classifier: Topic :: Terminals
|
|
43
|
+
Requires-Python: >=3.8
|
|
44
|
+
Description-Content-Type: text/markdown
|
|
45
|
+
|
|
46
|
+
# tix
|
|
47
|
+
|
|
48
|
+
Keyboard-driven terminal ticket explorer for a tree of markdown briefs. Linear-like TUI, zero deps beyond the Python stdlib + an optional markdown pager.
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
LANES STATE CTX
|
|
52
|
+
◐ P1 teams-error-mapping active 74K
|
|
53
|
+
○ P0 oauth-rotation-plan open —
|
|
54
|
+
● audit-logs-rollout done —
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Why
|
|
58
|
+
|
|
59
|
+
- **The filesystem is the database.** A ticket *is* a markdown file. `grep`, `git log`, and `ls` all keep working.
|
|
60
|
+
- **No SaaS, no auth, no network.** Runs entirely against a local tree.
|
|
61
|
+
- **Linear-like keys.** `j/k`, `/` to filter, `Enter` to open, `p` to pick up into a `wt` lane.
|
|
62
|
+
- **Pure reader.** tix never writes `status:` for you. If you want auto-status derivation, wire up your own preload hook (see below).
|
|
63
|
+
|
|
64
|
+
## Install
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
pipx install tix-cli
|
|
68
|
+
# or, in a venv:
|
|
69
|
+
pip install tix-cli
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Installs one console script: `tix`.
|
|
73
|
+
|
|
74
|
+
### From source
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
git clone https://github.com/gitpancake/tix
|
|
78
|
+
cd tix
|
|
79
|
+
pipx install --editable .
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Quickstart
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
mkdir -p ~/.claude/tickets/spikes
|
|
86
|
+
cp $(python -c 'import tix, pathlib; print(pathlib.Path(tix.__file__).parent / "templates" / "_TEMPLATE.md")') \
|
|
87
|
+
~/.claude/tickets/spikes/my-first-ticket.md
|
|
88
|
+
tix
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Or point at a project tree:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
cd ~/code
|
|
95
|
+
tix my-project # browses ~/code/my-project/.claude/tickets
|
|
96
|
+
TICKETS_DIR=./docs/tickets tix
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Keys
|
|
100
|
+
|
|
101
|
+
| Key | Action |
|
|
102
|
+
|---|---|
|
|
103
|
+
| `↑` `↓` / `j` `k` | Move |
|
|
104
|
+
| `Ctrl-U` `Ctrl-D` | Half page |
|
|
105
|
+
| `g` `G` | Top / bottom |
|
|
106
|
+
| `Enter` `→` `l` | Open in `glow` (or `$PAGER`) |
|
|
107
|
+
| `Esc` `←` `h` | Collapse / back |
|
|
108
|
+
| `/` | Filter |
|
|
109
|
+
| `e` | Edit in `$EDITOR` |
|
|
110
|
+
| `p` | Pickup → `wt <slug>` |
|
|
111
|
+
| `i` | Pin status `active` |
|
|
112
|
+
| `d` | Pin status `done` |
|
|
113
|
+
| `x` | Pin status `cancelled` |
|
|
114
|
+
| `m` | Move ticket to area |
|
|
115
|
+
| `y` | Copy slug to clipboard |
|
|
116
|
+
| `o` | Open Linear URL (if `linear:` set) |
|
|
117
|
+
| `r` | Reload (re-runs preload hook if set) |
|
|
118
|
+
| `?` | Help |
|
|
119
|
+
| `q` | Quit |
|
|
120
|
+
|
|
121
|
+
## Schema
|
|
122
|
+
|
|
123
|
+
A ticket is a markdown file with YAML-ish line-based frontmatter:
|
|
124
|
+
|
|
125
|
+
```markdown
|
|
126
|
+
---
|
|
127
|
+
status: open
|
|
128
|
+
priority: P1
|
|
129
|
+
area: integrations
|
|
130
|
+
linear: PROJ-123
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
# teams-error-mapping
|
|
134
|
+
|
|
135
|
+
## Context
|
|
136
|
+
…
|
|
137
|
+
|
|
138
|
+
## Acceptance criteria
|
|
139
|
+
- [ ] …
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Full contract: [`docs/ticket-schema.md`](docs/ticket-schema.md).
|
|
143
|
+
|
|
144
|
+
- **Filename is the slug.** `teams-error-mapping.md`, never `PROJ-123.md`.
|
|
145
|
+
- **Epic = folder.** A directory containing `_epic.md` is an epic; numbered children (`01-foo.md`, `02-bar.md`) are its stories.
|
|
146
|
+
- **Status vocab is pinned:** `active`, `open`, `draft`, `done`, `cancelled`.
|
|
147
|
+
|
|
148
|
+
## Configuration
|
|
149
|
+
|
|
150
|
+
| Env | Default | Purpose |
|
|
151
|
+
|---|---|---|
|
|
152
|
+
| `TICKETS_DIR` | `~/.claude/tickets` | Root of the ticket tree |
|
|
153
|
+
| `ACTIVE_LANES_FILE` | `~/.claude/active-lanes.json` | Optional sidecar map: slug → `{path, branch, repo, last_commit}`. Read by the TUI; tix never writes it. |
|
|
154
|
+
| `LINEAR_WORKSPACE` | *(unset)* | Slug used to derive `linear:` URLs (`o` key) |
|
|
155
|
+
| `TIX_PRELOAD_HOOK` | *(unset)* | Shell command run before launch. See below. |
|
|
156
|
+
| `EDITOR` | `vi` | Used by `e` |
|
|
157
|
+
| `PAGER` | `less` | Fallback when `glow` is absent |
|
|
158
|
+
|
|
159
|
+
`TICKETS_DIR` resolves in this order: explicit env var → `~/.claude/tickets`. There is no project-local autodiscovery; pass `tix <project>` or set `TICKETS_DIR` explicitly.
|
|
160
|
+
|
|
161
|
+
## Preload hook
|
|
162
|
+
|
|
163
|
+
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:
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
export TIX_PRELOAD_HOOK=~/bin/my-status-sync
|
|
167
|
+
tix
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
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.
|
|
171
|
+
|
|
172
|
+
The `r` key in the TUI re-runs the hook and reloads.
|
|
173
|
+
|
|
174
|
+
A reference implementation (filesystem + git + `gh`) lives in [gitpancake/.dotfiles](https://github.com/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.
|
|
175
|
+
|
|
176
|
+
## Optional integrations
|
|
177
|
+
|
|
178
|
+
- **`wt`** — if a `wt` command is on PATH, the `p` key suspends curses, runs `git fetch && git checkout main && git merge --ff-only && wt <slug>`, then resumes.
|
|
179
|
+
- **`glow`** — preferred markdown pager for ticket preview. Falls back to `$PAGER` (default `less`).
|
|
180
|
+
- **`gh`** — used by some preload hooks (not by tix itself).
|
|
181
|
+
|
|
182
|
+
## Non-goals
|
|
183
|
+
|
|
184
|
+
- No remote sync, no auth, no web UI.
|
|
185
|
+
- No mouse support.
|
|
186
|
+
- No notifications.
|
|
187
|
+
- No bundled status reconciler — wire your own via `TIX_PRELOAD_HOOK`.
|
|
188
|
+
|
|
189
|
+
## License
|
|
190
|
+
|
|
191
|
+
MIT.
|
tix_cli-0.1.0/README.md
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# tix
|
|
2
|
+
|
|
3
|
+
Keyboard-driven terminal ticket explorer for a tree of markdown briefs. Linear-like TUI, zero deps beyond the Python stdlib + an optional markdown pager.
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
LANES STATE CTX
|
|
7
|
+
◐ P1 teams-error-mapping active 74K
|
|
8
|
+
○ P0 oauth-rotation-plan open —
|
|
9
|
+
● audit-logs-rollout done —
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Why
|
|
13
|
+
|
|
14
|
+
- **The filesystem is the database.** A ticket *is* a markdown file. `grep`, `git log`, and `ls` all keep working.
|
|
15
|
+
- **No SaaS, no auth, no network.** Runs entirely against a local tree.
|
|
16
|
+
- **Linear-like keys.** `j/k`, `/` to filter, `Enter` to open, `p` to pick up into a `wt` lane.
|
|
17
|
+
- **Pure reader.** tix never writes `status:` for you. If you want auto-status derivation, wire up your own preload hook (see below).
|
|
18
|
+
|
|
19
|
+
## Install
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pipx install tix-cli
|
|
23
|
+
# or, in a venv:
|
|
24
|
+
pip install tix-cli
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Installs one console script: `tix`.
|
|
28
|
+
|
|
29
|
+
### From source
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
git clone https://github.com/gitpancake/tix
|
|
33
|
+
cd tix
|
|
34
|
+
pipx install --editable .
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Quickstart
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
mkdir -p ~/.claude/tickets/spikes
|
|
41
|
+
cp $(python -c 'import tix, pathlib; print(pathlib.Path(tix.__file__).parent / "templates" / "_TEMPLATE.md")') \
|
|
42
|
+
~/.claude/tickets/spikes/my-first-ticket.md
|
|
43
|
+
tix
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Or point at a project tree:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
cd ~/code
|
|
50
|
+
tix my-project # browses ~/code/my-project/.claude/tickets
|
|
51
|
+
TICKETS_DIR=./docs/tickets tix
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Keys
|
|
55
|
+
|
|
56
|
+
| Key | Action |
|
|
57
|
+
|---|---|
|
|
58
|
+
| `↑` `↓` / `j` `k` | Move |
|
|
59
|
+
| `Ctrl-U` `Ctrl-D` | Half page |
|
|
60
|
+
| `g` `G` | Top / bottom |
|
|
61
|
+
| `Enter` `→` `l` | Open in `glow` (or `$PAGER`) |
|
|
62
|
+
| `Esc` `←` `h` | Collapse / back |
|
|
63
|
+
| `/` | Filter |
|
|
64
|
+
| `e` | Edit in `$EDITOR` |
|
|
65
|
+
| `p` | Pickup → `wt <slug>` |
|
|
66
|
+
| `i` | Pin status `active` |
|
|
67
|
+
| `d` | Pin status `done` |
|
|
68
|
+
| `x` | Pin status `cancelled` |
|
|
69
|
+
| `m` | Move ticket to area |
|
|
70
|
+
| `y` | Copy slug to clipboard |
|
|
71
|
+
| `o` | Open Linear URL (if `linear:` set) |
|
|
72
|
+
| `r` | Reload (re-runs preload hook if set) |
|
|
73
|
+
| `?` | Help |
|
|
74
|
+
| `q` | Quit |
|
|
75
|
+
|
|
76
|
+
## Schema
|
|
77
|
+
|
|
78
|
+
A ticket is a markdown file with YAML-ish line-based frontmatter:
|
|
79
|
+
|
|
80
|
+
```markdown
|
|
81
|
+
---
|
|
82
|
+
status: open
|
|
83
|
+
priority: P1
|
|
84
|
+
area: integrations
|
|
85
|
+
linear: PROJ-123
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
# teams-error-mapping
|
|
89
|
+
|
|
90
|
+
## Context
|
|
91
|
+
…
|
|
92
|
+
|
|
93
|
+
## Acceptance criteria
|
|
94
|
+
- [ ] …
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Full contract: [`docs/ticket-schema.md`](docs/ticket-schema.md).
|
|
98
|
+
|
|
99
|
+
- **Filename is the slug.** `teams-error-mapping.md`, never `PROJ-123.md`.
|
|
100
|
+
- **Epic = folder.** A directory containing `_epic.md` is an epic; numbered children (`01-foo.md`, `02-bar.md`) are its stories.
|
|
101
|
+
- **Status vocab is pinned:** `active`, `open`, `draft`, `done`, `cancelled`.
|
|
102
|
+
|
|
103
|
+
## Configuration
|
|
104
|
+
|
|
105
|
+
| Env | Default | Purpose |
|
|
106
|
+
|---|---|---|
|
|
107
|
+
| `TICKETS_DIR` | `~/.claude/tickets` | Root of the ticket tree |
|
|
108
|
+
| `ACTIVE_LANES_FILE` | `~/.claude/active-lanes.json` | Optional sidecar map: slug → `{path, branch, repo, last_commit}`. Read by the TUI; tix never writes it. |
|
|
109
|
+
| `LINEAR_WORKSPACE` | *(unset)* | Slug used to derive `linear:` URLs (`o` key) |
|
|
110
|
+
| `TIX_PRELOAD_HOOK` | *(unset)* | Shell command run before launch. See below. |
|
|
111
|
+
| `EDITOR` | `vi` | Used by `e` |
|
|
112
|
+
| `PAGER` | `less` | Fallback when `glow` is absent |
|
|
113
|
+
|
|
114
|
+
`TICKETS_DIR` resolves in this order: explicit env var → `~/.claude/tickets`. There is no project-local autodiscovery; pass `tix <project>` or set `TICKETS_DIR` explicitly.
|
|
115
|
+
|
|
116
|
+
## Preload hook
|
|
117
|
+
|
|
118
|
+
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:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
export TIX_PRELOAD_HOOK=~/bin/my-status-sync
|
|
122
|
+
tix
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
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.
|
|
126
|
+
|
|
127
|
+
The `r` key in the TUI re-runs the hook and reloads.
|
|
128
|
+
|
|
129
|
+
A reference implementation (filesystem + git + `gh`) lives in [gitpancake/.dotfiles](https://github.com/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.
|
|
130
|
+
|
|
131
|
+
## Optional integrations
|
|
132
|
+
|
|
133
|
+
- **`wt`** — if a `wt` command is on PATH, the `p` key suspends curses, runs `git fetch && git checkout main && git merge --ff-only && wt <slug>`, then resumes.
|
|
134
|
+
- **`glow`** — preferred markdown pager for ticket preview. Falls back to `$PAGER` (default `less`).
|
|
135
|
+
- **`gh`** — used by some preload hooks (not by tix itself).
|
|
136
|
+
|
|
137
|
+
## Non-goals
|
|
138
|
+
|
|
139
|
+
- No remote sync, no auth, no web UI.
|
|
140
|
+
- No mouse support.
|
|
141
|
+
- No notifications.
|
|
142
|
+
- No bundled status reconciler — wire your own via `TIX_PRELOAD_HOOK`.
|
|
143
|
+
|
|
144
|
+
## License
|
|
145
|
+
|
|
146
|
+
MIT.
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# Ticket schema
|
|
2
|
+
|
|
3
|
+
The contract between tix and your ticket tree. Read this before hand-editing
|
|
4
|
+
anything under `$TICKETS_DIR` (default `~/.claude/tickets`).
|
|
5
|
+
|
|
6
|
+
## The one rule
|
|
7
|
+
|
|
8
|
+
**Filename is the slug.** A ticket file's stem *is* its identifier — never an
|
|
9
|
+
external tracker ID, never an arbitrary number.
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
good: oauth-rotation-plan.md
|
|
13
|
+
bad: PROJ-123.md
|
|
14
|
+
bad: DRAFT-7.md
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
`tix` and `ls` stay legible without opening any file. External IDs go in the
|
|
18
|
+
optional `linear:` frontmatter field as a historical breadcrumb; nothing ever
|
|
19
|
+
syncs.
|
|
20
|
+
|
|
21
|
+
## Two kinds of thing
|
|
22
|
+
|
|
23
|
+
| Kind | Lives at | Is |
|
|
24
|
+
|---|---|---|
|
|
25
|
+
| **Single ticket** | `<area>/<slug>.md` | One unit of change with acceptance criteria. |
|
|
26
|
+
| **Epic** | `<area>/<epic-slug>/` | A folder: `_epic.md` plus ordered `NN-<child>.md` children. |
|
|
27
|
+
|
|
28
|
+
A ticket lives in its area from creation — there is no staging folder. "Draft"
|
|
29
|
+
is a *status*, not a location: a freshly-scoped ticket is `status: draft` until
|
|
30
|
+
it's refined or picked up.
|
|
31
|
+
|
|
32
|
+
**Epic-ness is structural.** A directory containing `_epic.md` *is* an epic.
|
|
33
|
+
There is no separate registry — `find $TICKETS_DIR -name _epic.md` is the
|
|
34
|
+
epic index, and it is never stale.
|
|
35
|
+
|
|
36
|
+
## Areas
|
|
37
|
+
|
|
38
|
+
The root is a small, fixed set of area buckets. This is what keeps the tree
|
|
39
|
+
browsable instead of sprawling into one folder per epic.
|
|
40
|
+
|
|
41
|
+
The default areas baked into `tix` are:
|
|
42
|
+
|
|
43
|
+
- `integrations/`
|
|
44
|
+
- `ops/`
|
|
45
|
+
- `platform/`
|
|
46
|
+
- `spikes/`
|
|
47
|
+
- `tooling/`
|
|
48
|
+
|
|
49
|
+
These names are intentionally generic. Pick whatever set suits your project — a
|
|
50
|
+
future release will make `AREAS` configurable via env or a config file. For now
|
|
51
|
+
edit `src/tix/tui.py`'s `AREAS` list if you fork.
|
|
52
|
+
|
|
53
|
+
Add buckets deliberately. If you reach for a sixth, ask whether it's really an
|
|
54
|
+
area or just an epic that belongs inside an existing one.
|
|
55
|
+
|
|
56
|
+
## Frontmatter
|
|
57
|
+
|
|
58
|
+
```yaml
|
|
59
|
+
---
|
|
60
|
+
status: open # active | open | draft | done | cancelled
|
|
61
|
+
priority: P1 # P0 | P1 | P2 | P3 (blank = unprioritized)
|
|
62
|
+
area: integrations # one of the configured areas
|
|
63
|
+
linear: PROJ-123 # optional external-tracker breadcrumb
|
|
64
|
+
parent: <epic-slug> # only on epic children
|
|
65
|
+
---
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
The parser is intentionally line-based — no PyYAML, no nesting. Keep each value
|
|
69
|
+
on a single line.
|
|
70
|
+
|
|
71
|
+
### Status vocab
|
|
72
|
+
|
|
73
|
+
- `draft` — scoped, not yet refined. The cheapest possible "I might want this."
|
|
74
|
+
- `open` — refined and ready to be picked up. Default for new well-formed tickets.
|
|
75
|
+
- `active` — being worked. A reconciler can derive this from live worktrees or
|
|
76
|
+
branches; you can also pin it manually with `tix`'s `i` key.
|
|
77
|
+
- `done` — shipped. A reconciler can derive it from merged PRs containing the
|
|
78
|
+
slug; you can also pin it with `d`. Sticky: a manual mark survives the next
|
|
79
|
+
reconciliation pass so research/ops tickets without a PR signal can still
|
|
80
|
+
close out.
|
|
81
|
+
- `cancelled` — dropped. **Terminal** — trumps every derived signal until you
|
|
82
|
+
reopen it. Set/cleared with `x`.
|
|
83
|
+
|
|
84
|
+
`tix` itself is a **pure reader** — it never writes `status:` for you. If you
|
|
85
|
+
want statuses derived from external signals, wire your own reconciler via the
|
|
86
|
+
`TIX_PRELOAD_HOOK` env var; it runs once before each TUI launch. A reference
|
|
87
|
+
implementation (filesystem + git + `gh`) lives in
|
|
88
|
+
[gitpancake/.dotfiles](https://github.com/gitpancake/.dotfiles) as
|
|
89
|
+
`claude/scripts/ticket-status-sync.py` — derives `active` from worktrees and
|
|
90
|
+
`done` from merged PRs. Fork, replace, or skip entirely.
|
|
91
|
+
|
|
92
|
+
### Priority
|
|
93
|
+
|
|
94
|
+
Hand-driven, unlike status. Buckets are `P0` (drop everything) → `P3`
|
|
95
|
+
(eventually); blank sorts last. Within each group `tix` sorts by priority then
|
|
96
|
+
status, so P0/P1 work bubbles to the top. Edit the frontmatter directly or use
|
|
97
|
+
`+`/`−` from the TUI.
|
|
98
|
+
|
|
99
|
+
## Epic shape
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
<area>/<epic-slug>/
|
|
103
|
+
_epic.md # the durable, expansive brief
|
|
104
|
+
01-<child-slug>.md # ordered child stories
|
|
105
|
+
02-<child-slug>.md
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
- **`_epic.md`** carries context, goal, epic-level acceptance criteria,
|
|
109
|
+
constraints, and an ordered story list.
|
|
110
|
+
- **`NN-<child>.md` children** are the deep per-story detail. The `NN-` prefix
|
|
111
|
+
encodes execution order so `ls` and `tix` show the sequence.
|
|
112
|
+
|
|
113
|
+
## Slug conventions
|
|
114
|
+
|
|
115
|
+
Use a descriptive, kebab-cased phrase. Short enough to type, long enough to
|
|
116
|
+
read at a glance. Avoid encoding state in the name — that's what `status:` is
|
|
117
|
+
for.
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
good: oauth-rotation-plan
|
|
121
|
+
good: webhook-replay-on-failure
|
|
122
|
+
bad: fix-the-thing
|
|
123
|
+
bad: work-in-progress-stuff
|
|
124
|
+
```
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "tix-cli"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Keyboard-driven terminal ticket explorer for a tree of markdown briefs."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
license = { file = "LICENSE" }
|
|
12
|
+
authors = [{ name = "Henry Pye" }]
|
|
13
|
+
keywords = ["tui", "tickets", "curses", "markdown", "issue-tracker"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 4 - Beta",
|
|
16
|
+
"Environment :: Console :: Curses",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"License :: OSI Approved :: MIT License",
|
|
19
|
+
"Operating System :: POSIX",
|
|
20
|
+
"Operating System :: MacOS",
|
|
21
|
+
"Programming Language :: Python :: 3",
|
|
22
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
23
|
+
"Topic :: Software Development",
|
|
24
|
+
"Topic :: Terminals",
|
|
25
|
+
]
|
|
26
|
+
dependencies = []
|
|
27
|
+
|
|
28
|
+
[project.scripts]
|
|
29
|
+
tix = "tix.__main__:main"
|
|
30
|
+
|
|
31
|
+
[project.urls]
|
|
32
|
+
Homepage = "https://github.com/gitpancake/tix"
|
|
33
|
+
Repository = "https://github.com/gitpancake/tix"
|
|
34
|
+
Issues = "https://github.com/gitpancake/tix/issues"
|
|
35
|
+
Changelog = "https://github.com/gitpancake/tix/blob/main/CHANGELOG.md"
|
|
36
|
+
|
|
37
|
+
[tool.hatch.build.targets.wheel]
|
|
38
|
+
packages = ["src/tix"]
|
|
39
|
+
|
|
40
|
+
[tool.ruff]
|
|
41
|
+
line-length = 100
|
|
42
|
+
target-version = "py38"
|
|
43
|
+
|
|
44
|
+
[tool.ruff.lint]
|
|
45
|
+
select = ["E", "F", "W", "I", "B", "UP"]
|
|
46
|
+
ignore = ["E501"]
|
|
47
|
+
|
|
48
|
+
[tool.pytest.ini_options]
|
|
49
|
+
testpaths = ["tests"]
|