Metadata-Version: 2.5
Name: maestro-tui
Version: 0.2.5
Summary: A terminal-native multi-agent command center
Project-URL: Homepage, https://github.com/jmbmunda/maestro
Project-URL: Repository, https://github.com/jmbmunda/maestro
Project-URL: Issues, https://github.com/jmbmunda/maestro/issues
Author-email: Jay Mark Munda <jbmunda@multisyscorp.com>
License-Expression: MIT
License-File: LICENSE
Keywords: agents,claude,orchestration,terminal,textual,tui
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Operating System :: POSIX
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development
Classifier: Topic :: Terminals
Requires-Python: >=3.11
Requires-Dist: ptyprocess>=0.7.0
Requires-Dist: pyte>=0.8.2
Requires-Dist: textual>=0.86.0
Requires-Dist: tomli-w>=1.0.0
Provides-Extra: test
Requires-Dist: pytest-asyncio>=0.23; extra == 'test'
Requires-Dist: pytest>=8.0; extra == 'test'
Description-Content-Type: text/markdown

# maestro

A terminal-native multi-agent command center. Spawns AI/CLI agents in real PTYs
and shows them in switchable panes with a session timeline — all inside one TUI.

## Install

Maestro is a Python CLI (needs **Python 3.11+** and a `claude` on your PATH).
Install it on any device with [`pipx`](https://pipx.pypa.io) so it lands in its
own isolated venv with the `maestro` command on your PATH:

```bash
pipx install "git+https://github.com/jmbmunda/maestro.git"
```

Or run it once without installing, using [`uv`](https://docs.astral.sh/uv/):

```bash
uvx --from git+https://github.com/jmbmunda/maestro.git maestro
```

Update later with `pipx upgrade maestro`. Pin a release with `...@v0.1.0`.

### From source (development)

```bash
git clone https://github.com/jmbmunda/maestro.git && cd maestro
python3 -m venv .venv && source .venv/bin/activate
pip install -e .
```

## Run

```bash
maestro
```

## Keys

Each pane is a **real terminal** (full ANSI: colors, cursor, alt-screen), so
`claude`, `vim`, `htop` etc. render correctly. Maestro has two modes:

**OUTSIDE** (default — manage panes):

- `Ctrl+S` — spawn a `claude` agent in a new pane
- `Ctrl+Shift+S` — spawn a `$SHELL` agent (terminal-dependent; see note)
- `Ctrl+W` — dismiss the focused pane (confirms first if it's still running)
- `r` — rename the focused pane (blank resets to the default `pane-N · claude`)
- `b` — broadcast one prompt to multiple panes at once (pick targets, type, Enter)
- `a` — quick agents: launch a pre-seeded role, or create/edit/delete templates
- `Ctrl+J` / `Ctrl+K` — focus next / previous pane
- `Ctrl+L` — cycle layout: split → grid → focused
- `Ctrl+B` — toggle the sessions status panel
- `Ctrl+A` — toggle the agents panel (off by default)
- `Ctrl+T` — toggle the timeline
- `s` — toggle the **skills board** (every skill you have; hover for what it does,
  click to run it)
- `/` — open the **skills launcher** (pick a skill → injects `/name` into the focused pane)
- `c` (or `Ctrl+C`) — copy the highlighted text (drag over a pane to highlight)
- `Enter` — step **INSIDE** the focused pane to interact with it
- `q` — quit (also `Ctrl+Q`, where the terminal doesn't grab it — the VSCode
  integrated terminal does)

**INSIDE** (drive one agent): every keystroke goes straight to that pane's
terminal — arrows, `Esc`, `Tab`, `Shift+Tab` (cycle Claude modes), `Option+Enter`
(newline), `Option+←/→` (word motion), `Ctrl-C`, everything. The one reserved key
is **`Ctrl+]`**, which steps back OUTSIDE.

> **Heads-up:** `Ctrl+]` is reliable everywhere (terminals send it as a single
> byte, `0x1d`). `Ctrl+Esc` also works as a fallback, but only in terminals that
> transmit it distinctly (kitty keyboard protocol / `modifyOtherKeys=2`). The
> footer reflects the mode — while INSIDE it shows just `Ctrl+] Exit pane`;
> OUTSIDE it shows the command list.

**Scroll back through a pane's history** — every pane has a real scrollbar: drag
the thumb, click the trough to page, or mouse-wheel over the pane (works INSIDE or
OUTSIDE, including finished panes). While OUTSIDE, `PageUp`/`PageDown` page the
focused pane. The border title shows `⇡N (scrolled)` while you're above the live
view; the pane keeps following new output while parked at the bottom and stays put
once you scroll up — typing INSIDE snaps you back to the live bottom. Up to ~2000
lines are kept per pane.

**Highlight text and copy it** — drag over a pane to highlight (double-click takes a
word, path or URL; triple-click the whole line), then press **`c`** — or `Ctrl+C` —
while OUTSIDE to copy it. Highlighting never copies on its own, and the highlight
survives stepping out of a pane, so to copy something you selected while INSIDE:
`Ctrl+]` then `c`. (Copy has to be an OUTSIDE verb — INSIDE, every keystroke belongs
to the agent, `Ctrl+C` included, which must keep interrupting it.) `Esc` or a plain
click clears the highlight; highlighting never focuses or enters a pane. Copies go
out through your OS clipboard (`pbcopy` / `wl-copy` / `xclip` / `xsel`) *and* OSC 52,
so it also works over SSH where the host terminal allows it. Selection is bounded to
what you can see: drag past a pane's edge and it won't run away through the
scrollback — scroll first (wheel / `PageUp`), then drag.

> **Right-click won't do it.** The menu a right-click opens belongs to your terminal
> (VS Code, iTerm2), and its **Copy** copies the *terminal's* own selection — which
> maestro's highlight isn't, so it comes back empty. Use `c` / `Ctrl+C`. (The
> right-click itself is harmless: maestro ignores every button but the left one, so it
> no longer leaves the highlight stuck to your pointer.)

**Click a pane to focus + interact in one step** — clicking auto-enters that pane,
no separate `Enter` needed. Clicking a different pane while INSIDE another stays
INSIDE the new one. Clicking a finished pane drops you back OUTSIDE. In **focused**
layout, click a row in the `panes` strip to switch.

(Keyboard nav `Ctrl+J`/`Ctrl+K` still moves focus *without* auto-entering, so you
can chain commands like spawn → navigate → spawn → … without stepping in.) The background is transparent, so maestro inherits your terminal's
wallpaper/theme.

### Quick agents

Press **`a`** (OUTSIDE) for one-click specialized agents. A quick agent is a template
— a name + a starting prompt + an optional skill — that spawns a `claude` pane, names
it, injects the skill and prompt, and drops you inside. Ships with built-ins (Reviewer,
Tester, Security, Docs, Refactor) and is fully CRUD-able in-app: **Enter** launches the
highlighted one, **n** new, **e** edit, **d** delete. Templates live in a hand-editable
`~/.config/maestro/quick-agents.toml` (seeded on first run).

### Broadcast

Press **`b`** (OUTSIDE) to fan one prompt out to several sessions at once: a modal
lists the running panes (all checked by default — Space toggles), you type a prompt,
and Enter sends it to every checked pane. Great for "run the same task across N
agents" or kicking off a fleet. Uncheck any pane (e.g. a `$SHELL`) you don't want.

### Skills

Press **`/`** (OUTSIDE) to open the skills launcher — a filterable list of the skills
in `~/.claude/skills` (and a project's `.claude/skills`). Pick one and maestro types
`/name` into the focused pane and steps you inside, exactly as if you'd typed it in
Claude. (Typing `/name` yourself while INSIDE still works too.)

Press **`s`** for the **skills board** — the timeline's sibling, docked at the bottom
of the workspace, listing every skill you have in `ls`-style columns (read *down* a
column, then across; each column is only as wide as its own longest name, so the
roster packs into as few rows as the width allows).

- **Hover** a name and its description appears on the board's bottom border — the
  answer to "what does `qpr` actually do?" without opening anything.
- **Click** it to run it: maestro types `/name` into the focused pane and steps you
  inside, exactly like picking it from the `/` launcher. Only the names are click
  targets, so a stray click in the gaps can't type into a working agent.
- **Live state** at a glance: a skill whose run is in flight **spins in green**
  (`◐`, the same spinner the working pane's title shows), while `●` marks the ones
  the focused pane has already run. Project skills are bold.

```
╭─skills · 25──────────────────────────────────────────────────────────────╮
│   brainstorming             find-skills               ◓ qa-tester         │
│ ● bug-fix                   frontend-design             qpr               │
│   code-review               full-output-enforcement     quick-save        │
╰────────────────────────────────────────────────── ◓ qa-tester · pane-1 ──╯
```

A run counts as in flight from the moment the skill launches until the turn it
drives settles (Claude prints no "skill finished" marker, so the turn boundary is
the honest signal). It's a **fleet view** — a skill running in a pane you're not
looking at still spins, and the bottom border says which pane picked it up.

**Skills that aren't yours still show up.** Claude runs plenty that have no `SKILL.md`
under `~/.claude/skills` — its own bundled ones (`artifact-design`, `dataviz`, …) and
plugin skills (`superpowers:brainstorming`). Watch one run and it joins the roster for
as long as the pane that ran it is open, so the board never sits still while the pane
beside it is clearly working one. Those rows have no description to hover (the border
says so), but they run on click like any other. Only skills Claude actually *announces*
are added — `/clear` and the rest of its built-in commands look identical from the
outside, so they stay off the board.

The board is **off by default** and remembered in `.maestro/state.json`. It's only as
tall as the roster needs (capped, then it scrolls), and the roster is re-read each
time you open it, so a skill you just added shows up without a restart.

Maestro also watches each pane's output for Claude's skill markers and shows the
active skill as a `⚡<skill>` badge on the pane's border (best-effort — it keys off
Claude's output text).

### Status board

Maestro shows, at a glance, which sessions need you. Each pane gets a best-effort
status from what it's doing — one glyph, one colour, the same everywhere:

One family of circles, where how full the ring is says where the session is — so the
column still reads with the colour taken away:

- `◐` **working** (green) — Claude is on a turn right now; the circle turns
  (`◐ ◓ ◑ ◒`) in the rail and the pane's own title, and the pane's border glows
- `●` **waiting** (amber) — solid, sitting there: alive but quiet → *needs you*
  (a question, an approval, or just done with its turn)
- `○` **done** — spent, exited cleanly · `⊗` **failed** (red) — exited non-zero

The **`panes`** rail on the left lists every pane with its status; your terminal's
own title bar summarises them (e.g. `maestro   ◐2 ●1 ○1`), and each pane border
reflects it too. (Heuristic: maestro can't tell "waiting for approval" from "waiting
for your next message" — both show as `● waiting`.)

The **focused pane's row is a filled bar** across the rail, so the session you're
driving is never something you have to hunt for; every other row keeps a plain name
and lets the glyph column carry the colour. The pane itself matches, with a
**coloured border** (green while you're INSIDE it) and full-brightness text while
non-focused panes are dimmed.

The `panes` rail is **on by default** (toggle with `Ctrl+A`); the `timeline` event
log is **off by default** (toggle with `Ctrl+T`), as is the `skills` board (toggle
with `s`).

### Theme

Maestro defaults to the **`ansi-dark`** theme, which draws with your terminal's own
16-colour palette — so it adapts to whatever colour scheme your terminal uses. Press
`Ctrl+P` → "Change theme" to pick another (`ansi-light` for light terminals, or any
of Textual's built-ins like `gruvbox` / `dracula`). Your choice is remembered in
`.maestro/state.json`.

> **Note:** `Ctrl+Shift+S` relies on the terminal forwarding shift+ctrl combos; some
> terminals fold it into `Ctrl+S`. `claude` is the default spawn either way.
>
> `Ctrl+]` is the only key maestro reserves while INSIDE, so apps that use it
> (e.g. vim's tag-jump) won't see it.

## Layouts

- **split** — vertical stack; auto-promotes to grid once a 4th pane spawns
- **grid** — auto-shaped tiles (2×2, 2×3, 3×3, then a scrollable 3×N)
- **focused** — one large pane plus a left strip of status tiles for the rest

When an agent **exits cleanly** (`exit=0`) the pane is auto-dismissed — no
frozen-screen confusion. **Failed exits** (non-zero) stay sticky so you can read
what went wrong; press `Ctrl+W` to dismiss those.

## Layout

```
┌─timeline──────────────────────────────────────────┐
│ 16:18:30 spawn  pane-1 claude                      │
│ 16:18:42 spawn  pane-2 /bin/zsh -l                 │
└────────────────────────────────────────────────────┘
┌─panes──────────┐ ┌─◓ pane-1 · claude · INSIDE — Ctrl+] to exit┐
│ ◓ Crash Log  ⚡│ │                                             │
│ ● pane-2 · sh  │ │   (live terminal — claude renders here)     │
│ ○ Roadmap      │ │                                             │
└────────────────┘ └─────────────────────────────────────────────┘
```

(In the app the first rail row — the focused pane — is painted as a filled
highlight bar; `⚡` marks a pane running a skill. The `panes` rail shows in every
layout — `Ctrl+A` hides it. A pane's border turns heavy/green while you're INSIDE
it.)

## Where things live

- `maestro/app.py` — Textual app, key bindings, focus-toggle, spawn/dismiss/focus
- `maestro/panes.py` — `Pane` model + `PaneManager` state machine (Textual-agnostic)
- `maestro/layouts.py` — split / grid / focused renderers + the focus tiles strip
- `maestro/terminal.py` — pyte-backed `Emulator`, `TerminalView`, query responder, key encoder
- `maestro/agent.py` — PTY-backed subprocess wrapper (`ptyprocess`); raw bytes + resize
- `maestro/events.py` — append-only JSONL event log (`.maestro/session.jsonl`)

`.maestro/state.json` persists your layout + chrome-visibility choices (timeline,
agents rail, skills board) across restarts. Agent sessions themselves are not resumed.

## Tests

```bash
pip install -e '.[test]'
pytest
```

## Next steps

- Provider adapters beyond `claude` / `$SHELL` (`codex`, `gemini`, `gh copilot`)
- Claude Code skill passthrough (toggle `~/.claude/skills` per pane)
- Prompt library + per-agent system prompts
- Inter-agent message bus for "review on modify" workflows
- Terminal niceties: mouse reporting, bracketed paste, configurable INSIDE-exit key
