Initial commit: hqt — HQ Terminal TUI
TUI for orchestrating AI coding harness sessions (Claude Code, Codex, Kiro, etc.) via tmux. Click CLI bootstraps a Textual TUI over ProjectService/SessionService backed by SQLite, spawning harness sessions as tmux windows through TmuxManager. Includes recent fixes: - Visible Tab focus highlight on dialog OK/Cancel buttons - Auto-select first project on launch - Auto-select first session + per-project session-selection memory - tmux new-window targets an explicit free index, fixing "index N in use" failures (broken spawn/attach in attached sessions) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,247 @@
|
||||
# HQ Terminal — Implementation Plan
|
||||
|
||||
Reference spec: `.hq/2026-06-09-spec-hq-terminal.md`
|
||||
|
||||
## Phase 1: Project Scaffold
|
||||
|
||||
Set up the new workspace with tooling, dependencies, and basic structure.
|
||||
|
||||
### Steps
|
||||
|
||||
1. **Create workspace** at `~/dev/hqt/` with `uv init --lib --name hqt`
|
||||
2. **Configure pyproject.toml**:
|
||||
- Python ≥ 3.12
|
||||
- Dependencies: `textual>=2.0`, `sqlalchemy>=2.0`, `click>=8.0`
|
||||
- Dev dependencies: `pytest`, `pytest-asyncio`, `ruff`
|
||||
- Entry point: `[project.scripts] hqt = "hqt.cli:main"`
|
||||
3. **Create package structure** per spec: `src/hqt/` with all subdirectories
|
||||
4. **Create `.hq/PROJECT.md`** for the new project
|
||||
5. **Verify**: `uv run hqt --help` produces output
|
||||
|
||||
## Phase 2: Database Layer
|
||||
|
||||
SQLite models, engine, and schema management.
|
||||
|
||||
### Steps
|
||||
|
||||
1. **`src/hqt/config.py`** — Settings dataclass with paths:
|
||||
- `db_path`: `~/.local/share/hqt/hqt.db`
|
||||
- `config_dir`: `~/.config/hqt/`
|
||||
- `skills_dir`: `~/.config/hqt/skills/`
|
||||
- `tmux_path`: `tmux`
|
||||
- `tui_session_name`: `hqt-main`
|
||||
2. **`src/hqt/db/models.py`** — SQLAlchemy models: `Project`, `Harness`, `Session`, `McpServer`, `ProjectMcpServer`, `ProjectSkill`
|
||||
3. **`src/hqt/db/session.py`** — Engine factory, session factory, `ensure_db()` (creates dir + tables)
|
||||
4. **`src/hqt/db/migrate.py`** — Additive migration helper (add missing columns)
|
||||
5. **Tests**: model creation, ensure_db on fresh + existing databases
|
||||
6. **Verify**: `uv run pytest tests/test_db.py -q` passes
|
||||
|
||||
## Phase 3: TmuxRunner
|
||||
|
||||
The async tmux command abstraction. Largely ported from HQ.
|
||||
|
||||
### Steps
|
||||
|
||||
1. **`src/hqt/tmux/runner.py`** — Port `TmuxRunner` from HQ:
|
||||
- `new_session(name, cwd, command, width, height)`
|
||||
- `has_session(name) -> bool`
|
||||
- `kill_session(name)`
|
||||
- `send_text(name, text)`
|
||||
- `send_enter(name)`
|
||||
- `capture_pane(name) -> str`
|
||||
- `switch_client(target)` ← NEW (not in HQ)
|
||||
- `list_sessions() -> list[str]` ← NEW
|
||||
2. **`src/hqt/tmux/manager.py`** — Higher-level session lifecycle:
|
||||
- `spawn_session(session: Session, config: SpawnConfig) -> None`
|
||||
- `resume_session(session: Session, config: SpawnConfig) -> None`
|
||||
- `kill_session(session: Session) -> None`
|
||||
- `attach_session(session: Session) -> None` (switch-client)
|
||||
- `is_alive(session: Session) -> bool`
|
||||
- `poll_all_sessions(sessions: list[Session]) -> dict[int, bool]`
|
||||
3. **Tests**: mock `_exec` function, verify command construction
|
||||
4. **Verify**: `uv run pytest tests/test_tmux.py -q` passes
|
||||
|
||||
## Phase 4: Harness Abstraction Layer
|
||||
|
||||
The configurator protocol and concrete implementations.
|
||||
|
||||
### Steps
|
||||
|
||||
1. **`src/hqt/harnesses/base.py`** — `HarnessConfigurator` protocol, `SpawnConfig`, `HarnessIdentity` dataclasses
|
||||
2. **`src/hqt/harnesses/claude.py`** — Claude Code configurator:
|
||||
- UUID generation via `uuid5`
|
||||
- Spawn: `claude --session-id {uuid} --model {model}`
|
||||
- Resume: `claude --resume {uuid}`
|
||||
- MCP: write project `.mcp.json`
|
||||
- Skills: write/append `CLAUDE.md`
|
||||
- Ready/busy regexes from terminal profiles
|
||||
3. **`src/hqt/harnesses/kiro.py`** — Kiro CLI configurator (stub for now):
|
||||
- Basic spawn command
|
||||
- MCP: Kiro-managed (no injection in v1)
|
||||
4. **`src/hqt/harnesses/aider.py`** — Aider configurator:
|
||||
- Spawn: `aider --model {model} --restore-chat-history`
|
||||
- Skills: `--read {skill_file}` flags
|
||||
- No MCP support
|
||||
5. **`src/hqt/harnesses/generic.py`** — Fallback for unknown harnesses:
|
||||
- Env-var based config only
|
||||
6. **`src/hqt/harnesses/registry.py`** — Discover available harnesses (`shutil.which`), return configurator instances
|
||||
7. **Tests**: each configurator produces correct SpawnConfig for given inputs
|
||||
8. **Verify**: `uv run pytest tests/test_harnesses.py -q` passes
|
||||
|
||||
## Phase 5: Skills & MCP Services
|
||||
|
||||
Skill registry and MCP server management.
|
||||
|
||||
### Steps
|
||||
|
||||
1. **`src/hqt/skills/registry.py`** — Port from HQ: scan `skills_dir`, parse frontmatter, return `Skill` objects
|
||||
2. **`src/hqt/skills/injection.py`** — Port from HQ: `wrap_prompt_with_skills()`
|
||||
3. **`src/hqt/mcp/service.py`** — CRUD for MCP servers and project bindings (DB operations)
|
||||
4. **`src/hqt/projects/service.py`** — Project CRUD: add, list, archive, git status reading
|
||||
5. **Tests**: skill discovery with fixture directory, MCP CRUD, project CRUD
|
||||
6. **Verify**: `uv run pytest tests/test_skills.py tests/test_mcp.py tests/test_projects.py -q` passes
|
||||
|
||||
## Phase 6: Session Service (Orchestration Core)
|
||||
|
||||
The service that ties together DB, tmux, and harness configurators.
|
||||
|
||||
### Steps
|
||||
|
||||
1. **`src/hqt/sessions/service.py`** — Session lifecycle:
|
||||
- `create_session(project_id, harness_name, nickname, model, skill_names, mcp_ids) -> Session`
|
||||
- Generates harness UUID via configurator
|
||||
- Builds SpawnConfig
|
||||
- Calls tmux manager to spawn
|
||||
- Persists session row
|
||||
- `resume_session(session_id) -> None`
|
||||
- Loads session from DB
|
||||
- Builds resume SpawnConfig with stored UUID
|
||||
- Calls tmux manager to spawn
|
||||
- `stop_session(session_id) -> None`
|
||||
- Kills tmux session
|
||||
- Updates DB (last_activity_at)
|
||||
- `delete_session(session_id) -> None`
|
||||
- Kills tmux if alive
|
||||
- Removes DB row
|
||||
- `attach_session(session_id) -> None`
|
||||
- Calls tmux switch-client
|
||||
- `list_sessions(project_id) -> list[SessionInfo]`
|
||||
- Joins DB data with tmux liveness
|
||||
2. **Tests**: mock tmux manager, verify session lifecycle
|
||||
3. **Verify**: `uv run pytest tests/test_sessions.py -q` passes
|
||||
|
||||
## Phase 7: Textual TUI
|
||||
|
||||
The user interface.
|
||||
|
||||
### Steps
|
||||
|
||||
1. **`src/hqt/tui/app.py`** — Textual `App` subclass:
|
||||
- Bindings: q=quit, n=new session, a=add project, /=filter
|
||||
- Compose: Header, ProjectList, SessionList, Footer
|
||||
- Timer: poll tmux liveness every 3s
|
||||
2. **`src/hqt/tui/widgets/project_list.py`** — ListView of projects:
|
||||
- Shows name, path, branch
|
||||
- Selection updates session list
|
||||
- Keybindings: Enter=select, a=add, d=archive
|
||||
3. **`src/hqt/tui/widgets/session_list.py`** — ListView of sessions for selected project:
|
||||
- Shows harness icon, nickname, alive/dead status
|
||||
- Keybindings: Enter=attach, n=new, d=delete, r=rename, R=resume (if dead)
|
||||
4. **`src/hqt/tui/screens/new_session.py`** — Modal screen:
|
||||
- Harness selector (dropdown)
|
||||
- Nickname input
|
||||
- Model selector (populated from configurator.supported_models)
|
||||
- Skills multi-select
|
||||
- MCP servers multi-select
|
||||
- Create / Cancel buttons
|
||||
5. **`src/hqt/tui/screens/add_project.py`** — Modal:
|
||||
- Path input (with directory completion if feasible, or paste)
|
||||
- Name override (optional)
|
||||
6. **`src/hqt/tui/styles.tcss`** — Textual CSS for layout
|
||||
7. **Integration test**: App mounts, projects display, key bindings respond
|
||||
8. **Verify**: `uv run pytest tests/test_tui.py -q` passes (headless Textual testing)
|
||||
|
||||
## Phase 8: CLI Entry Point & tmux Bootstrap
|
||||
|
||||
Wire everything together.
|
||||
|
||||
### Steps
|
||||
|
||||
1. **`src/hqt/cli.py`** — Click CLI:
|
||||
- `hqt` (default) — bootstrap into tmux + launch TUI
|
||||
- `hqt list` — print projects/sessions table
|
||||
- `hqt attach <id>` — switch to session
|
||||
- `hqt new <project> <harness>` — create session non-interactively
|
||||
- `hqt doctor` — check tmux, harness availability
|
||||
2. **`src/hqt/__main__.py`** — `python -m hqt` support
|
||||
3. **tmux bootstrap logic**:
|
||||
```python
|
||||
def bootstrap():
|
||||
if not in_tmux():
|
||||
# Create hqt-main session with TUI as the command
|
||||
exec tmux new-session -s hqt-main "hqt --inside-tmux"
|
||||
else:
|
||||
# Already in tmux, just run the TUI
|
||||
run_tui()
|
||||
```
|
||||
4. **First-run setup**: suggest tmux keybinding, create config dir/skills dir
|
||||
5. **Verify**: `hqt doctor` works, `hqt` launches TUI inside tmux
|
||||
|
||||
## Phase 9: Integration Testing & Polish
|
||||
|
||||
### Steps
|
||||
|
||||
1. **End-to-end test**: create project → create session → verify tmux session exists → attach → kill → verify dead
|
||||
2. **Handle edge cases**:
|
||||
- tmux not installed → clear error
|
||||
- Harness not found → show in UI, disable create
|
||||
- DB locked → retry pattern
|
||||
- Session name collision → fail gracefully
|
||||
3. **README.md** with quick start
|
||||
4. **Verify**: full test suite passes, manual smoke test with claude-code
|
||||
|
||||
## Dependency Graph
|
||||
|
||||
```
|
||||
Phase 1 (scaffold)
|
||||
│
|
||||
├── Phase 2 (database)
|
||||
│ │
|
||||
│ ├── Phase 5 (skills + MCP)
|
||||
│ │ │
|
||||
│ │ └──────┐
|
||||
│ │ │
|
||||
│ └── Phase 6 (session service) ◄── Phase 4 (harness abstraction)
|
||||
│ │
|
||||
├── Phase 3 (tmux runner) ──────────────┘
|
||||
│ │
|
||||
│ ▼
|
||||
└── Phase 7 (TUI) ◄──────────── Phase 6
|
||||
│
|
||||
▼
|
||||
Phase 8 (CLI + bootstrap)
|
||||
│
|
||||
▼
|
||||
Phase 9 (integration + polish)
|
||||
```
|
||||
|
||||
## Estimated Effort
|
||||
|
||||
| Phase | Complexity | Approx. LOC |
|
||||
|-------|-----------|-------------|
|
||||
| 1. Scaffold | Low | ~50 (config) |
|
||||
| 2. Database | Low | ~150 |
|
||||
| 3. TmuxRunner | Low | ~120 (mostly port) |
|
||||
| 4. Harness abstraction | Medium | ~300 |
|
||||
| 5. Skills + MCP | Low | ~150 (mostly port) |
|
||||
| 6. Session service | Medium | ~200 |
|
||||
| 7. TUI | Medium-High | ~400 |
|
||||
| 8. CLI + bootstrap | Low | ~100 |
|
||||
| 9. Integration | Medium | ~150 |
|
||||
| **Total** | | **~1,620** |
|
||||
|
||||
## Implementation Order (sequential for a single agent)
|
||||
|
||||
1. Phase 1 → 2. Phase 2 → 3. Phase 3 → 4. Phase 4 → 5. Phase 5 → 6. Phase 6 → 7. Phase 7 → 8. Phase 8 → 9. Phase 9
|
||||
|
||||
Each phase produces a working, testable increment. The TUI (Phase 7) is the largest single piece but depends on everything below it being solid.
|
||||
Reference in New Issue
Block a user