proj · the agent-aware session picker
proj is a Bubble Tea picker for the sessions you actually work in. It doesn't just list tmux sessions — it reads what's happening in each: which agent is live, whether it's running or idle, and whether muster is holding mail for it. The highlighted row previews on the right, so you choose with context instead of guessing from a name.
$ proj SESSIONS ──────────────────┬─ PREVIEW ───────────────── › web claude·run ✉ │ ~/src/web · 3 windows api codex·idle │ ● claude working — infra — │ "GET /users missing createdAt" dotfiles — │ muster: 1 reply · 2m ago ↑↓ move · ⏎ attach · / filter · q quit
what it shows
Open the picker with a bare proj. Every live tmux session across all
per-project servers is listed as <project>/<work>, and each row carries
the context you'd otherwise have to attach to discover.
Which agent is attached — pi, claude, cursor, or
blank — and its state (claude·run, codex·idle), resolved live from
the pane, not a stale cache.
A session waiting on muster mail glows with a ✉ flag, so the picker doubles as a glance at which work sessions need you.
The highlighted row previews to the right — working directory, windows, the agent's current activity — so you attach to the right session the first time.
proj runs one tmux server per project (the proj-<name> socket
convention muster uses), so a crash in one project never
takes down another. Scope the picker to a project or span them all.
Beside the agent pane, a sidebar of your choosing — scratch, a file
explorer, a shell — built on new work and arranged the way your day runs.
Non-interactive verbs an agent can run without hijacking your terminal:
proj list --json, proj current --json, and
proj new <project>/<work> --agent <name> to spawn a detached
session.
agent + model
Selecting + new work… in a project opens an inline prompt to name the
session. While you type, Tab cycles the agent that will launch into it, and
Shift-Tab opens a filterable list of that agent's models — the footer shows
both. Change the agent and the model re-seeds to that agent's default.
Model menus are scoped to the agent, because agents don't share models. Claude Code
only accepts Anthropic models, so its menu is a built-in list. pi can reach many
providers, so its menu is discovered from pi --list-models, filtered to the
providers you name in config, and offered as provider/id — launched as
pi --model <provider/id>. Agents that take no model (cursor,
none) simply don't show the list.
select model — type to filter claude-bridge/claude-opus-4-8 › openai-codex/gpt-5.6-sol openai-codex/gpt-6-astra ↑↓ move · ⏎ select · esc keep current
config
proj reads a roots file (which directories are projects) and an optional config.toml (defaults, the sidebar, model menus, per-project overrides). Unset keys fall back to built-in defaults.
One project root per line; blank lines and # comments ignored.
# work ~/GitHub/myapp ~/GitHub/infra # personal ~/GitHub/dotfiles
default_agent = "pi" # what new work launches; "none" for a plain shell sidebar = true # auto-build the sidebar column on new work [sidebar_layout] panes = ["scratch", "yazi", "shell"] # top → bottom sizes = { scratch = 12, shell = 10 } # rows; the middle pane fills [model_providers] # pi's model menu = these providers' models pi = ["claude-bridge", "openai-codex"] [project."bettor-help"] # optional per-project overrides default_agent = "claude" default_model = "claude-sonnet-4-6" # pin this project's model sidebar = false
pi's list is discovered from pi --list-models and cached ~24h;
it refreshes on its own, or when you run pi update.
keys
| Key | In the list |
|---|---|
↑ ↓ | move the cursor |
⏎ | attach a session · drill into a project · start new work |
type / ⌫ | filter the list |
Tab | toggle folders ↔ live sessions |
^x | reap the highlighted session (confirm with a second ^x) |
esc / q | back out · quit |
| Key | Naming new work |
|---|---|
Tab | cycle the agent |
Shift-Tab | open the model list (type to filter, ⏎ pick, esc keep) |
^s | toggle the sidebar for this session |
⏎ / esc | create · cancel |
shell
proj needs a small zsh shim so the shell can eval the one thing the
binary can't do inside its own process: attach a bare shell to a session on another server.
The shim ships in the repo at shell/proj.zsh — source it from your rc.
$ source /path/to/tackle/shell/proj.zsh
The shim also installs auto-join: fired once when the shell starts, if you're
interactive, not already inside tmux, and your $PWD is inside a known project root,
it opens that project's picker automatically — so a new terminal in a project lands straight on
proj. Opt out with NO_AUTO_TMUX=1 (per shell) or
~/.no-auto-tmux (global); set AUTO_CLAUDE=1 to auto-join with claude
preselected.
install
One command downloads proj, verifies its checksum, and puts it on your PATH
(~/.local/bin). macOS and Linux, Apple silicon and x86-64.
$ curl -fsSL https://tackle.tools/install.sh | sh -s proj # then $ proj
Run it again to upgrade; check your version with proj version.
Building from source instead? go install github.com/schuettc/tackle/cmd/proj@latest.