tix-cli 0.1.0__tar.gz → 0.2.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.
Files changed (98) hide show
  1. tix_cli-0.2.0/.claude/worktrees/02b-doc-driven-booking-audit/.claude/agent-state +1 -0
  2. tix_cli-0.2.0/.claude/worktrees/02b-doc-driven-booking-audit/.claude/ctx-cache +1 -0
  3. tix_cli-0.2.0/.claude/worktrees/02b-doc-driven-booking-audit/.claude/parent-cwd +1 -0
  4. tix_cli-0.2.0/.claude/worktrees/02b-doc-driven-booking-audit/.claude/sessions/55972 +1 -0
  5. tix_cli-0.2.0/.claude/worktrees/02b-doc-driven-booking-audit/.claude/settings.local.json +7 -0
  6. tix_cli-0.2.0/.claude/worktrees/02b-doc-driven-booking-audit/.claude/tmux-window +1 -0
  7. tix_cli-0.2.0/.claude/worktrees/02b-doc-driven-booking-audit/.env.local.port +1 -0
  8. tix_cli-0.2.0/.claude/worktrees/02b-doc-driven-booking-audit/.git +1 -0
  9. {tix_cli-0.1.0 → tix_cli-0.2.0/.claude/worktrees/02b-doc-driven-booking-audit}/CLAUDE.md +9 -2
  10. {tix_cli-0.1.0 → tix_cli-0.2.0/.claude/worktrees/02b-doc-driven-booking-audit}/README.md +25 -3
  11. tix_cli-0.2.0/.claude/worktrees/02b-doc-driven-booking-audit/src/tix/__main__.py +38 -0
  12. tix_cli-0.2.0/.claude/worktrees/03-easypost-client-mv/.claude/agent-state +1 -0
  13. tix_cli-0.2.0/.claude/worktrees/03-easypost-client-mv/.claude/ctx-cache +1 -0
  14. tix_cli-0.2.0/.claude/worktrees/03-easypost-client-mv/.claude/parent-cwd +1 -0
  15. tix_cli-0.2.0/.claude/worktrees/03-easypost-client-mv/.claude/sessions/45189 +1 -0
  16. tix_cli-0.2.0/.claude/worktrees/03-easypost-client-mv/.claude/settings.local.json +7 -0
  17. tix_cli-0.2.0/.claude/worktrees/03-easypost-client-mv/.claude/tmux-window +1 -0
  18. tix_cli-0.2.0/.claude/worktrees/03-easypost-client-mv/.env.local.port +1 -0
  19. tix_cli-0.2.0/.claude/worktrees/03-easypost-client-mv/.git +1 -0
  20. tix_cli-0.2.0/.claude/worktrees/03-easypost-client-mv/.github/workflows/ci.yml +21 -0
  21. tix_cli-0.2.0/.claude/worktrees/03-easypost-client-mv/.gitignore +13 -0
  22. tix_cli-0.2.0/.claude/worktrees/03-easypost-client-mv/CHANGELOG.md +32 -0
  23. tix_cli-0.2.0/.claude/worktrees/03-easypost-client-mv/CLAUDE.md +75 -0
  24. tix_cli-0.2.0/.claude/worktrees/03-easypost-client-mv/LICENSE +21 -0
  25. tix_cli-0.2.0/.claude/worktrees/03-easypost-client-mv/README.md +168 -0
  26. tix_cli-0.2.0/.claude/worktrees/03-easypost-client-mv/docs/ticket-schema.md +124 -0
  27. tix_cli-0.2.0/.claude/worktrees/03-easypost-client-mv/pyproject.toml +49 -0
  28. tix_cli-0.2.0/.claude/worktrees/03-easypost-client-mv/src/tix/__init__.py +3 -0
  29. tix_cli-0.2.0/.claude/worktrees/03-easypost-client-mv/src/tix/__main__.py +38 -0
  30. tix_cli-0.2.0/.claude/worktrees/03-easypost-client-mv/src/tix/templates/_CHILD-TEMPLATE.md +20 -0
  31. tix_cli-0.2.0/.claude/worktrees/03-easypost-client-mv/src/tix/templates/_EPIC-TEMPLATE.md +42 -0
  32. tix_cli-0.2.0/.claude/worktrees/03-easypost-client-mv/src/tix/templates/_TEMPLATE.md +33 -0
  33. tix_cli-0.2.0/.claude/worktrees/03-easypost-client-mv/src/tix/tui.py +1183 -0
  34. tix_cli-0.2.0/.claude/worktrees/03-easypost-client-mv/tests/fixtures/tickets/README.md +3 -0
  35. tix_cli-0.2.0/.claude/worktrees/03-easypost-client-mv/tests/fixtures/tickets/integrations/alpha.md +12 -0
  36. tix_cli-0.2.0/.claude/worktrees/03-easypost-client-mv/tests/fixtures/tickets/spikes/beta.md +10 -0
  37. tix_cli-0.2.0/.claude/worktrees/03-easypost-client-mv/tests/test_smoke.py +50 -0
  38. tix_cli-0.2.0/.claude/worktrees/08-ezeeship-client-mv/.claude/agent-state +1 -0
  39. tix_cli-0.2.0/.claude/worktrees/08-ezeeship-client-mv/.claude/ctx-cache +1 -0
  40. tix_cli-0.2.0/.claude/worktrees/08-ezeeship-client-mv/.claude/parent-cwd +1 -0
  41. tix_cli-0.2.0/.claude/worktrees/08-ezeeship-client-mv/.claude/sessions/63551 +1 -0
  42. tix_cli-0.2.0/.claude/worktrees/08-ezeeship-client-mv/.claude/settings.local.json +7 -0
  43. tix_cli-0.2.0/.claude/worktrees/08-ezeeship-client-mv/.claude/tmux-window +1 -0
  44. tix_cli-0.2.0/.claude/worktrees/08-ezeeship-client-mv/.env.local.port +1 -0
  45. tix_cli-0.2.0/.claude/worktrees/08-ezeeship-client-mv/.git +1 -0
  46. tix_cli-0.2.0/.claude/worktrees/08-ezeeship-client-mv/.github/workflows/ci.yml +21 -0
  47. tix_cli-0.2.0/.claude/worktrees/08-ezeeship-client-mv/.gitignore +13 -0
  48. tix_cli-0.2.0/.claude/worktrees/08-ezeeship-client-mv/CHANGELOG.md +32 -0
  49. tix_cli-0.2.0/.claude/worktrees/08-ezeeship-client-mv/CLAUDE.md +75 -0
  50. tix_cli-0.2.0/.claude/worktrees/08-ezeeship-client-mv/LICENSE +21 -0
  51. tix_cli-0.2.0/.claude/worktrees/08-ezeeship-client-mv/README.md +168 -0
  52. tix_cli-0.2.0/.claude/worktrees/08-ezeeship-client-mv/docs/ticket-schema.md +124 -0
  53. tix_cli-0.2.0/.claude/worktrees/08-ezeeship-client-mv/pyproject.toml +49 -0
  54. tix_cli-0.2.0/.claude/worktrees/08-ezeeship-client-mv/src/tix/__init__.py +3 -0
  55. tix_cli-0.2.0/.claude/worktrees/08-ezeeship-client-mv/src/tix/__main__.py +38 -0
  56. tix_cli-0.2.0/.claude/worktrees/08-ezeeship-client-mv/src/tix/templates/_CHILD-TEMPLATE.md +20 -0
  57. tix_cli-0.2.0/.claude/worktrees/08-ezeeship-client-mv/src/tix/templates/_EPIC-TEMPLATE.md +42 -0
  58. tix_cli-0.2.0/.claude/worktrees/08-ezeeship-client-mv/src/tix/templates/_TEMPLATE.md +33 -0
  59. tix_cli-0.2.0/.claude/worktrees/08-ezeeship-client-mv/src/tix/tui.py +1183 -0
  60. tix_cli-0.2.0/.claude/worktrees/08-ezeeship-client-mv/tests/fixtures/tickets/README.md +3 -0
  61. tix_cli-0.2.0/.claude/worktrees/08-ezeeship-client-mv/tests/fixtures/tickets/integrations/alpha.md +12 -0
  62. tix_cli-0.2.0/.claude/worktrees/08-ezeeship-client-mv/tests/fixtures/tickets/spikes/beta.md +10 -0
  63. tix_cli-0.2.0/.claude/worktrees/08-ezeeship-client-mv/tests/test_smoke.py +50 -0
  64. tix_cli-0.2.0/.github/workflows/ci.yml +21 -0
  65. tix_cli-0.2.0/.gitignore +13 -0
  66. tix_cli-0.2.0/CHANGELOG.md +49 -0
  67. tix_cli-0.2.0/CLAUDE.md +80 -0
  68. tix_cli-0.2.0/LICENSE +21 -0
  69. {tix_cli-0.1.0 → tix_cli-0.2.0}/PKG-INFO +29 -4
  70. tix_cli-0.2.0/README.md +171 -0
  71. tix_cli-0.2.0/docs/ticket-schema.md +124 -0
  72. tix_cli-0.2.0/pyproject.toml +49 -0
  73. tix_cli-0.2.0/src/tix/__init__.py +3 -0
  74. tix_cli-0.2.0/src/tix/__main__.py +73 -0
  75. tix_cli-0.2.0/src/tix/templates/_CHILD-TEMPLATE.md +20 -0
  76. tix_cli-0.2.0/src/tix/templates/_EPIC-TEMPLATE.md +42 -0
  77. tix_cli-0.2.0/src/tix/templates/_TEMPLATE.md +33 -0
  78. tix_cli-0.2.0/src/tix/tui.py +1197 -0
  79. tix_cli-0.2.0/tests/fixtures/tickets/README.md +3 -0
  80. tix_cli-0.2.0/tests/fixtures/tickets/integrations/alpha.md +12 -0
  81. tix_cli-0.2.0/tests/fixtures/tickets/spikes/beta.md +10 -0
  82. tix_cli-0.2.0/tests/test_smoke.py +80 -0
  83. tix_cli-0.1.0/src/tix/__main__.py +0 -29
  84. {tix_cli-0.1.0 → tix_cli-0.2.0/.claude/worktrees/02b-doc-driven-booking-audit}/.github/workflows/ci.yml +0 -0
  85. {tix_cli-0.1.0 → tix_cli-0.2.0/.claude/worktrees/02b-doc-driven-booking-audit}/.gitignore +0 -0
  86. {tix_cli-0.1.0 → tix_cli-0.2.0/.claude/worktrees/02b-doc-driven-booking-audit}/CHANGELOG.md +0 -0
  87. {tix_cli-0.1.0 → tix_cli-0.2.0/.claude/worktrees/02b-doc-driven-booking-audit}/LICENSE +0 -0
  88. {tix_cli-0.1.0 → tix_cli-0.2.0/.claude/worktrees/02b-doc-driven-booking-audit}/docs/ticket-schema.md +0 -0
  89. {tix_cli-0.1.0 → tix_cli-0.2.0/.claude/worktrees/02b-doc-driven-booking-audit}/pyproject.toml +0 -0
  90. {tix_cli-0.1.0 → tix_cli-0.2.0/.claude/worktrees/02b-doc-driven-booking-audit}/src/tix/__init__.py +0 -0
  91. {tix_cli-0.1.0 → tix_cli-0.2.0/.claude/worktrees/02b-doc-driven-booking-audit}/src/tix/templates/_CHILD-TEMPLATE.md +0 -0
  92. {tix_cli-0.1.0 → tix_cli-0.2.0/.claude/worktrees/02b-doc-driven-booking-audit}/src/tix/templates/_EPIC-TEMPLATE.md +0 -0
  93. {tix_cli-0.1.0 → tix_cli-0.2.0/.claude/worktrees/02b-doc-driven-booking-audit}/src/tix/templates/_TEMPLATE.md +0 -0
  94. {tix_cli-0.1.0 → tix_cli-0.2.0/.claude/worktrees/02b-doc-driven-booking-audit}/src/tix/tui.py +0 -0
  95. {tix_cli-0.1.0 → tix_cli-0.2.0/.claude/worktrees/02b-doc-driven-booking-audit}/tests/fixtures/tickets/README.md +0 -0
  96. {tix_cli-0.1.0 → tix_cli-0.2.0/.claude/worktrees/02b-doc-driven-booking-audit}/tests/fixtures/tickets/integrations/alpha.md +0 -0
  97. {tix_cli-0.1.0 → tix_cli-0.2.0/.claude/worktrees/02b-doc-driven-booking-audit}/tests/fixtures/tickets/spikes/beta.md +0 -0
  98. {tix_cli-0.1.0 → tix_cli-0.2.0/.claude/worktrees/02b-doc-driven-booking-audit}/tests/test_smoke.py +0 -0
@@ -0,0 +1 @@
1
+ /Users/henrypye/Documents/code/tix
@@ -0,0 +1,7 @@
1
+ {
2
+ "enabledPlugins": {
3
+ "vercel@claude-plugins-official": false,
4
+ "claude-md-management@claude-plugins-official": false,
5
+ "skill-creator@claude-plugins-official": false
6
+ }
7
+ }
@@ -0,0 +1 @@
1
+ 02b-doc-driven-booking-audit
@@ -0,0 +1 @@
1
+ gitdir: /Users/henrypye/Documents/code/tix/.git/worktrees/02b-doc-driven-booking-audit
@@ -5,7 +5,7 @@
5
5
  `tix` is a keyboard-driven curses TUI over a tree of markdown ticket briefs. Stdlib-only Python. Single surface:
6
6
 
7
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).
8
+ - `src/tix/__main__.py` — CLI router (default → TUI; `tix <project>` → resolve `~/.claude/tickets/<project>/`, fallback `./<project>/.claude/tickets/`; sets `TICKETS_DIR` before the TUI imports).
9
9
 
10
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
11
 
@@ -27,7 +27,14 @@ Pre-migration title-case variants (`In Progress`, `Todo`, etc.) are kept as read
27
27
 
28
28
  ## TICKETS_DIR resolution
29
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.
30
+ Order: `$TICKETS_DIR` (explicit) → `~/.claude/tickets` (fallback). No in-binary project autodiscovery from cwd.
31
+
32
+ The `tix <project>` form sets `TICKETS_DIR` via:
33
+ 1. `~/.claude/tickets/<project>/` if that dir exists (centralized layout — preferred)
34
+ 2. else `./<project>/.claude/tickets/` if that dir exists (legacy per-repo layout — `chdir` into `./<project>` before launch)
35
+ 3. else error
36
+
37
+ Per-project autoswitch on `cd` lives in the user's shell, not tix. The README documents a zsh `chpwd` hook recipe for the centralized layout.
31
38
 
32
39
  ## Preload hook
33
40
 
@@ -1,5 +1,9 @@
1
1
  # tix
2
2
 
3
+ [![PyPI](https://img.shields.io/pypi/v/tix-cli.svg)](https://pypi.org/project/tix-cli/)
4
+ [![Python versions](https://img.shields.io/pypi/pyversions/tix-cli.svg)](https://pypi.org/project/tix-cli/)
5
+ [![License](https://img.shields.io/pypi/l/tix-cli.svg)](https://github.com/gitpancake/tix/blob/main/LICENSE)
6
+
3
7
  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
8
 
5
9
  ```
@@ -46,11 +50,29 @@ tix
46
50
  Or point at a project tree:
47
51
 
48
52
  ```bash
49
- cd ~/code
50
- tix my-project # browses ~/code/my-project/.claude/tickets
53
+ tix my-project # browses ~/.claude/tickets/my-project (centralized)
54
+ # falls back to ./my-project/.claude/tickets (legacy)
51
55
  TICKETS_DIR=./docs/tickets tix
52
56
  ```
53
57
 
58
+ For zsh users who want per-project `TICKETS_DIR` to follow `cd`, add this `chpwd` hook to `~/.zshrc`:
59
+
60
+ ```zsh
61
+ autoload -U add-zsh-hook
62
+ _tix_tickets_dir() {
63
+ local root base
64
+ root=$(git rev-parse --show-toplevel 2>/dev/null) || { unset TICKETS_DIR; return; }
65
+ base=${root:t}
66
+ if [[ -d "$HOME/.claude/tickets/$base" ]]; then
67
+ export TICKETS_DIR="$HOME/.claude/tickets/$base"
68
+ else
69
+ unset TICKETS_DIR
70
+ fi
71
+ }
72
+ add-zsh-hook chpwd _tix_tickets_dir
73
+ _tix_tickets_dir
74
+ ```
75
+
54
76
  ## Keys
55
77
 
56
78
  | Key | Action |
@@ -111,7 +133,7 @@ Full contract: [`docs/ticket-schema.md`](docs/ticket-schema.md).
111
133
  | `EDITOR` | `vi` | Used by `e` |
112
134
  | `PAGER` | `less` | Fallback when `glow` is absent |
113
135
 
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.
136
+ `TICKETS_DIR` resolves in this order: explicit env var → `~/.claude/tickets`. There is no in-binary project-local autodiscovery; pass `tix <project>` (resolves `~/.claude/tickets/<project>/` first, then `./<project>/.claude/tickets/` for legacy trees) or set `TICKETS_DIR` explicitly (e.g. via the `chpwd` hook above).
115
137
 
116
138
  ## Preload hook
117
139
 
@@ -0,0 +1,38 @@
1
+ """tix CLI entry point.
2
+
3
+ tix browse $TICKETS_DIR (default ~/.claude/tickets)
4
+ tix <project> browse ~/.claude/tickets/<project>/ (centralized layout).
5
+ Legacy fallback: ./<project>/.claude/tickets/
6
+ """
7
+ import os
8
+ import sys
9
+ from pathlib import Path
10
+
11
+
12
+ def main(argv=None):
13
+ argv = list(sys.argv[1:] if argv is None else argv)
14
+
15
+ if argv and not argv[0].startswith("-"):
16
+ proj = argv.pop(0)
17
+ home_proj = Path.home() / ".claude" / "tickets" / proj
18
+ if home_proj.is_dir():
19
+ os.environ["TICKETS_DIR"] = str(home_proj)
20
+ else:
21
+ legacy = Path.cwd() / proj / ".claude" / "tickets"
22
+ if legacy.is_dir():
23
+ os.chdir(Path.cwd() / proj)
24
+ os.environ["TICKETS_DIR"] = str(legacy)
25
+ else:
26
+ print(
27
+ f"tix: no ticket directory at {home_proj} (or {legacy})",
28
+ file=sys.stderr,
29
+ )
30
+ return 1
31
+
32
+ from . import tui
33
+ sys.argv = ["tix", *argv]
34
+ return tui.main()
35
+
36
+
37
+ if __name__ == "__main__":
38
+ sys.exit(main())
@@ -0,0 +1 @@
1
+ 1779238819:51627:26403
@@ -0,0 +1 @@
1
+ /Users/henrypye/Documents/code/tix
@@ -0,0 +1,7 @@
1
+ {
2
+ "enabledPlugins": {
3
+ "vercel@claude-plugins-official": false,
4
+ "claude-md-management@claude-plugins-official": false,
5
+ "skill-creator@claude-plugins-official": false
6
+ }
7
+ }
@@ -0,0 +1 @@
1
+ 03-easypost-client-mv
@@ -0,0 +1 @@
1
+ gitdir: /Users/henrypye/Documents/code/tix/.git/worktrees/03-easypost-client-mv
@@ -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
@@ -0,0 +1,13 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ build/
5
+ dist/
6
+ .venv/
7
+ .eggs/
8
+ .pytest_cache/
9
+ .ruff_cache/
10
+ .mypy_cache/
11
+ .coverage
12
+ htmlcov/
13
+ .DS_Store
@@ -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
@@ -0,0 +1,75 @@
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>` → resolve `~/.claude/tickets/<project>/`, fallback `./<project>/.claude/tickets/`; sets `TICKETS_DIR` before the TUI imports).
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 in-binary project autodiscovery from cwd.
31
+
32
+ The `tix <project>` form sets `TICKETS_DIR` via:
33
+ 1. `~/.claude/tickets/<project>/` if that dir exists (centralized layout — preferred)
34
+ 2. else `./<project>/.claude/tickets/` if that dir exists (legacy per-repo layout — `chdir` into `./<project>` before launch)
35
+ 3. else error
36
+
37
+ Per-project autoswitch on `cd` lives in the user's shell, not tix. The README documents a zsh `chpwd` hook recipe for the centralized layout.
38
+
39
+ ## Preload hook
40
+
41
+ `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.
42
+
43
+ ## Editing rules
44
+
45
+ - `tui.py` runs inside `curses.wrapper`. Anything that prints or raises can wreck the terminal. Wrap subprocess calls in `try/except (OSError, subprocess.SubprocessError)`.
46
+ - Status bar rendering is hot. Don't read filesystem inside the render loop — `App.rebuild_rows()` is the bulk-read step.
47
+ - `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.
48
+
49
+ ## Tests
50
+
51
+ Fixture-driven under `tests/fixtures/tickets/`. No network. Run:
52
+
53
+ ```bash
54
+ pytest -q
55
+ ```
56
+
57
+ ## Distribution
58
+
59
+ - PyPI distribution name: `tix-cli`. Import name + executable: `tix`.
60
+ - One console script: `tix`.
61
+ - No native deps. `pipx install tix-cli` is the recommended install.
62
+ - Templates ship in `src/tix/templates/` and are accessed via `importlib.resources` (or `tix.__file__`-relative paths) — never hardcode an install prefix.
63
+
64
+ ## Org-specific bits to keep configurable
65
+
66
+ - `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.
67
+ - `LINEAR_WORKSPACE` is the only external-tracker breadcrumb. No GitHub Issues / Jira integration creep — that belongs in a preload hook.
68
+
69
+ ## Non-goals
70
+
71
+ - No remote sync.
72
+ - No mouse support.
73
+ - No realtime collab.
74
+ - No bundled markdown renderer — defer to `glow` / `$PAGER`.
75
+ - No bundled status reconciler — defer to `TIX_PRELOAD_HOOK`.
@@ -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.
@@ -0,0 +1,168 @@
1
+ # tix
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/tix-cli.svg)](https://pypi.org/project/tix-cli/)
4
+ [![Python versions](https://img.shields.io/pypi/pyversions/tix-cli.svg)](https://pypi.org/project/tix-cli/)
5
+ [![License](https://img.shields.io/pypi/l/tix-cli.svg)](https://github.com/gitpancake/tix/blob/main/LICENSE)
6
+
7
+ Keyboard-driven terminal ticket explorer for a tree of markdown briefs. Linear-like TUI, zero deps beyond the Python stdlib + an optional markdown pager.
8
+
9
+ ```
10
+ LANES STATE CTX
11
+ ◐ P1 teams-error-mapping active 74K
12
+ ○ P0 oauth-rotation-plan open —
13
+ ● audit-logs-rollout done —
14
+ ```
15
+
16
+ ## Why
17
+
18
+ - **The filesystem is the database.** A ticket *is* a markdown file. `grep`, `git log`, and `ls` all keep working.
19
+ - **No SaaS, no auth, no network.** Runs entirely against a local tree.
20
+ - **Linear-like keys.** `j/k`, `/` to filter, `Enter` to open, `p` to pick up into a `wt` lane.
21
+ - **Pure reader.** tix never writes `status:` for you. If you want auto-status derivation, wire up your own preload hook (see below).
22
+
23
+ ## Install
24
+
25
+ ```bash
26
+ pipx install tix-cli
27
+ # or, in a venv:
28
+ pip install tix-cli
29
+ ```
30
+
31
+ Installs one console script: `tix`.
32
+
33
+ ### From source
34
+
35
+ ```bash
36
+ git clone https://github.com/gitpancake/tix
37
+ cd tix
38
+ pipx install --editable .
39
+ ```
40
+
41
+ ## Quickstart
42
+
43
+ ```bash
44
+ mkdir -p ~/.claude/tickets/spikes
45
+ cp $(python -c 'import tix, pathlib; print(pathlib.Path(tix.__file__).parent / "templates" / "_TEMPLATE.md")') \
46
+ ~/.claude/tickets/spikes/my-first-ticket.md
47
+ tix
48
+ ```
49
+
50
+ Or point at a project tree:
51
+
52
+ ```bash
53
+ tix my-project # browses ~/.claude/tickets/my-project (centralized)
54
+ # falls back to ./my-project/.claude/tickets (legacy)
55
+ TICKETS_DIR=./docs/tickets tix
56
+ ```
57
+
58
+ For zsh users who want per-project `TICKETS_DIR` to follow `cd`, add this `chpwd` hook to `~/.zshrc`:
59
+
60
+ ```zsh
61
+ autoload -U add-zsh-hook
62
+ _tix_tickets_dir() {
63
+ local root base
64
+ root=$(git rev-parse --show-toplevel 2>/dev/null) || { unset TICKETS_DIR; return; }
65
+ base=${root:t}
66
+ if [[ -d "$HOME/.claude/tickets/$base" ]]; then
67
+ export TICKETS_DIR="$HOME/.claude/tickets/$base"
68
+ else
69
+ unset TICKETS_DIR
70
+ fi
71
+ }
72
+ add-zsh-hook chpwd _tix_tickets_dir
73
+ _tix_tickets_dir
74
+ ```
75
+
76
+ ## Keys
77
+
78
+ | Key | Action |
79
+ |---|---|
80
+ | `↑` `↓` / `j` `k` | Move |
81
+ | `Ctrl-U` `Ctrl-D` | Half page |
82
+ | `g` `G` | Top / bottom |
83
+ | `Enter` `→` `l` | Open in `glow` (or `$PAGER`) |
84
+ | `Esc` `←` `h` | Collapse / back |
85
+ | `/` | Filter |
86
+ | `e` | Edit in `$EDITOR` |
87
+ | `p` | Pickup → `wt <slug>` |
88
+ | `i` | Pin status `active` |
89
+ | `d` | Pin status `done` |
90
+ | `x` | Pin status `cancelled` |
91
+ | `m` | Move ticket to area |
92
+ | `y` | Copy slug to clipboard |
93
+ | `o` | Open Linear URL (if `linear:` set) |
94
+ | `r` | Reload (re-runs preload hook if set) |
95
+ | `?` | Help |
96
+ | `q` | Quit |
97
+
98
+ ## Schema
99
+
100
+ A ticket is a markdown file with YAML-ish line-based frontmatter:
101
+
102
+ ```markdown
103
+ ---
104
+ status: open
105
+ priority: P1
106
+ area: integrations
107
+ linear: PROJ-123
108
+ ---
109
+
110
+ # teams-error-mapping
111
+
112
+ ## Context
113
+
114
+
115
+ ## Acceptance criteria
116
+ - [ ] …
117
+ ```
118
+
119
+ Full contract: [`docs/ticket-schema.md`](docs/ticket-schema.md).
120
+
121
+ - **Filename is the slug.** `teams-error-mapping.md`, never `PROJ-123.md`.
122
+ - **Epic = folder.** A directory containing `_epic.md` is an epic; numbered children (`01-foo.md`, `02-bar.md`) are its stories.
123
+ - **Status vocab is pinned:** `active`, `open`, `draft`, `done`, `cancelled`.
124
+
125
+ ## Configuration
126
+
127
+ | Env | Default | Purpose |
128
+ |---|---|---|
129
+ | `TICKETS_DIR` | `~/.claude/tickets` | Root of the ticket tree |
130
+ | `ACTIVE_LANES_FILE` | `~/.claude/active-lanes.json` | Optional sidecar map: slug → `{path, branch, repo, last_commit}`. Read by the TUI; tix never writes it. |
131
+ | `LINEAR_WORKSPACE` | *(unset)* | Slug used to derive `linear:` URLs (`o` key) |
132
+ | `TIX_PRELOAD_HOOK` | *(unset)* | Shell command run before launch. See below. |
133
+ | `EDITOR` | `vi` | Used by `e` |
134
+ | `PAGER` | `less` | Fallback when `glow` is absent |
135
+
136
+ `TICKETS_DIR` resolves in this order: explicit env var → `~/.claude/tickets`. There is no in-binary project-local autodiscovery; pass `tix <project>` (resolves `~/.claude/tickets/<project>/` first, then `./<project>/.claude/tickets/` for legacy trees) or set `TICKETS_DIR` explicitly (e.g. via the `chpwd` hook above).
137
+
138
+ ## Preload hook
139
+
140
+ 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:
141
+
142
+ ```bash
143
+ export TIX_PRELOAD_HOOK=~/bin/my-status-sync
144
+ tix
145
+ ```
146
+
147
+ 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.
148
+
149
+ The `r` key in the TUI re-runs the hook and reloads.
150
+
151
+ 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.
152
+
153
+ ## Optional integrations
154
+
155
+ - **`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.
156
+ - **`glow`** — preferred markdown pager for ticket preview. Falls back to `$PAGER` (default `less`).
157
+ - **`gh`** — used by some preload hooks (not by tix itself).
158
+
159
+ ## Non-goals
160
+
161
+ - No remote sync, no auth, no web UI.
162
+ - No mouse support.
163
+ - No notifications.
164
+ - No bundled status reconciler — wire your own via `TIX_PRELOAD_HOOK`.
165
+
166
+ ## License
167
+
168
+ 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
+ ```