project-manager-tui
A terminal task board for people running coding agents in git worktrees.
Tasks live in plain Markdown that you and the agents both edit. pm-tui renders
them as one list ordered by what needs you: agent requests waiting on an answer
first, then your open tasks, with everything an agent is busy with hidden until
you ask to see it. One keypress opens a task's worktree, starts its agent, or
releases its branch — the TUI shells out to a script you supply for all of it,
so it knows nothing about tmux, ssh or your remote host.
Install
pip install project-manager-tui
pm-tui
Requires Python 3.9+.
Configure
~/.project-manager-tui.toml:
# Required.
agent_worktree_script = "/path/to/agent-worktree.sh"
projects_dir = "/efs/me/projects"
# Optional, with their defaults.
projects_filename = "Projects.md"
git_repo_dir = "." # the cwd `go` is run in
sync_interval = 300 # seconds between `sync-master` runs
editor = "$EDITOR" # falls back to vim
approvals_dir = "" # default: {projects_dir}/../approvals
drive_idle_seconds = 30 # before a driven task's idleness is reported
drive_lease_hours = 12 # how long a drive grant lives
drive_command = "" # default: pm-drive (see Driving a task)
# Optional. Which agent a NEW worktree starts on, cycled in the UI with `m`.
# Short name -> "<cli>-<model>", split at the FIRST dash: everything before it
# is the CLI to run, everything after is the model handed to that CLI. The
# first entry is the default, and the table's order is the cycle order.
#
# Keep the tables LAST in the file: a TOML table swallows every bare key that
# follows it, so a `models` section in the middle silently steals the settings
# under it.
[models]
fable = "claude-fable" # claude --model fable
astra = "codex-gpt-6-astra" # codex -m gpt-6-astra
pro = "gemini-gemini-3-pro" # gemini -m gemini-3-pro
# Optional. The same for project agents (below), so the two levels can run
# different CLIs -- gemini on tasks and claude on projects, or the reverse.
# Without it project agents use [models].
[project_models]
fable = "claude-fable"
Without [models] the picker is empty, m does nothing, and every worktree
starts the way it did before: claude, on whatever the container defaults to.
m cycles the picker for what is selected: the project one in project view or
on a project agent row, the task one otherwise.
agent_worktree_script is the only real dependency, and writing one is the bulk
of the setup — see the contract below.
Layout
projects_dir/
Projects.md ## Active / ## Future / ## Archived, one slug per line
my-project/
Project.md the task list
tasks/3/Task.md one task's brief, created on demand
A task is a numbered line in Project.md:
1. [ ] rewrite the fill parser
2. [ ] backfill september >1 blocked until task 1 closes
3. [ ] chase the vendor >2026-09-20 pinned to a date
4. [x] delete the old path
The state character drives everything:
| state | meaning |
|---|---|
[ ] |
todo |
[i] |
an agent is running /ready on it |
[t] |
tested |
[r] |
released — branch pushed to master |
[d] |
delivered — deployed |
[v] |
verified — you have seen it work |
[x] |
complete |
[s] [z] |
snoozed / snoozed forever |
[x] and [v] are what unblock a dependent task. [i] [t] [r] [d] mean
underway, so the task is not offered as new work.
Keys
Task view:
j/k navigate z snooze: 1H/2H/4H/8H/1D/2D…
t vim Task.md u unset: clear state + time snooze
p vim Project.md c complete
v view worktree r release (mark [r])
g go: start its agent d delete task
s switch to worktree V/Z/B/W show verified/snoozed/blocked/working
D drive (see below) i read what the driver has sent
m cycle the model P project view, ? help, q quit
Approval rows (!) — requests an agent has filed and cannot execute itself:
Enter review + run it i inspect the request JSON
x reject, with a reason t/p vim the filing task's Task.md/Project.md
v view the agent z snooze, u clear a hold nothing is paying
Project agents
A project agent is a session on a whole project rather than on one task: ask it what the project is about, have it check a task's results, or have it write the next task. It does no coding -- that is what tasks are for -- and it has no branch or worktree; it runs in the same sandbox as the task agents, with the project directory as its working directory.
Project view: v open the project's agent (creates it the first time)
c close it
Task view: an open agent is a row, `<slug>-pm project agent`, that
behaves like a worktree row for status: hidden while it works,
listed (and counted by the light) while it waits for you.
v/g open it, t/p vim Project.md, c close it.
Closing keeps the session: the next open resumes it. Which agent a NEW project
agent starts on is [project_models] (or [models] without it), cycled with
m in project view.
Driving a task
D on a task row hands that task to the project agent: it is told what it now
has, told again whenever the task goes idle, and its answers are typed into the
task's session. The row is orange for as long as the grant holds, with
⇄10h·3 beside it -- ten hours of lease left, three messages sent -- so a
drive is never something happening quietly. D again hands it back, i reads
everything the driver has said, and D on the project agent's own row stops
every drive at once.
Telling the project agent to drive something works as well as the key does: it
writes the same grant, and the row turns orange within a poll either way. What
it cannot do is undo your revoke -- that leaves a tombstone only D clears.
A grant lasts drive_lease_hours (12) and has no message cap; the count on the
row is what makes a runaway obvious. Nothing about driving hides a row: a
driven task that is working is hidden like any other, and a driven task waiting
on someone is on screen, in orange, whether or not its driver is awake.
The project agent's half is pm-drive, installed with this package -- so
unlike agent-worktree.sh it is not something you write. Install it wherever
your agents run and set drive_command if it is not simply on their PATH. The
project directory has to be reachable from both sides, which is the one thing
that fails quietly: the row goes orange and nothing is ever delivered. The
paths need not match, only the directory.
The driver may talk, and may have a stopped task agent started to hear it. It
may not set state letters, release or complete anything. It is also told not to
answer a task sitting in a question widget -- it cannot know what a keystroke
would select there -- which is why task agents are asked to put questions in
their Task.md in prose instead. A gemini task agent has no widget at all: a
pending one is invisible in its transcript, so the launcher denies the tool
rather than leave the rule to chance.
The agent's standing instructions live in {projects_dir}/CLAUDE.md, an
ancestor of every project directory, so one file serves all of them; the
package ships a starter (project-agent-role.md, printed the same way as the
contract below) that awt-remote.sh also hands to Codex and Gemini, which read
no CLAUDE.md. Copy it there once; without it a project agent starts with only
the per-project prompt the launcher builds.
Status light
pm-tui --web # TUI plus the status page
pm-tui --web-only # just the page, no TUI
Serves a full-window light on port 8899: green when the default view is
empty, amber with a count when something wants you, grey when the count
is too old to trust. --web-port, --web-bind and --web-interval move it;
/status.json is the same thing as JSON. Park it on a second screen and stop
checking.
The agent-worktree.sh contract
The TUI never touches git, tmux or ssh itself. It invokes your script with
list, add, rm, switch, view, go, tell, close, release,
approve and sync-master, and the shipped contract document specifies each
one — the exact
list output format, which status tokens the working filter and the status
light read, what may prompt and what may not, and the approval queue's on-disk
format, statuses and leases.
It ships inside the package:
python -c "import importlib.resources as r, sys; \
sys.stdout.write((r.files('project_manager_tui') / 'agent-worktree-contract.md').read_text())"
Read it before writing a script. Several of the TUI's behaviours — hiding busy
worktrees, the green light, a go that does not steal your terminal — are
conventions your script has to hold up its end of, and a script that ignores
them still runs, just quieter than it should.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file project_manager_tui-0.1.22.tar.gz.
File metadata
- Download URL: project_manager_tui-0.1.22.tar.gz
- Upload date:
- Size: 90.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.10.8
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
48d1839d9cf0c227671e7dcae6b77049cc8b21c35c2535ae77103dc1fd3393a9
|
|
| MD5 |
f955b310d9383710435d793121bc1bc8
|
|
| BLAKE2b-256 |
1a981b1d1b515293ec99bc9efd3e2d5e6fa421c3873882a23f31cd939088a8ce
|
File details
Details for the file project_manager_tui-0.1.22-py3-none-any.whl.
File metadata
- Download URL: project_manager_tui-0.1.22-py3-none-any.whl
- Upload date:
- Size: 92.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.10.8
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3e107bf919282e082db21289787b55bddd907d41ee20729452cb46b26bc9339b
|
|
| MD5 |
e8a14e781f28f62f2a35c9015cfbc016
|
|
| BLAKE2b-256 |
4b4da4548fc0b1f9f1493eca0ab2255c73c7c4e118f86e7222d97f3c381baeff
|