Projects and terminals
One window, a menu of codebases down the left, and every terminal nested under the project it belongs to. The arrangement is the product: a tab bar makes twenty shells look alike, and the whole point here is that they don't.
The menu
Projects sit in recency order, each wearing a status rollup dot — a project shows its neediest session's face, so an amber dot four projects down is a real claim on you without anything being unfolded. The project you're in trades that dot for a fold chevron and a quietly lifted card with its terminals inside, where each session's own dot says more than a rollup can.
Add a codebase with ⌘⇧O, or from any
terminal anywhere with Saggar . once
you've installed the command (Settings ▸ General). That path climbs
to the enclosing git root, so Saggar . from
src/ means the repository — and a worktree
checkout counts as a project in its own right. If kiln is on the Mac,
the menu seeds itself from kiln's recent projects on first launch;
if it isn't, the menu starts empty and says so.
⌘L filters the menu. A project's
header carries its branch, its dirty and ahead/behind counters, and the
buttons to open it in your editor or reveal it in Finder.
Terminals
⌘T opens a fresh terminal in the current
project, of the type you last picked from the + menu (
⌘N is always a plain shell). Underneath,
every one of them is a login shell — nothing is emulated and nothing is
wrapped: it is your
shell, your profile, your prompt, with three facts added to the
environment so tools can tell where they are running —
SAGGAR=1,
TERM_PROGRAM=Saggar, and a per-session
SAGGAR_SESSION id.
Terminals stay alive while you look elsewhere — that is the difference between a manager and a switcher, and it is what makes a background agent's prompt something Saggar can notice. Placement survives relaunch too: quit with eight terminals across three projects and they come back as fresh shells in the same working directories, ready to be picked up rather than reconstructed from memory.
Closing is ⌘W. A closed terminal is
remembered by name for a while, so the palette can reopen it with how
long ago it went — the common case after a close is wanting it back.
General terminals
A shell tied to no project at all is ⌘⇧T,
opened at home — the scratch shell you want before you know which
codebase the answer is in. The moment it
cds into a codebase already on the menu,
that project adopts it: the session reorganizes under the project in the
nav and from there it is an ordinary project terminal, branch chip,
history, scratchpad and all.
Smart collapsing
A menu that only grows is a menu you stop reading, so it tidies itself. Once every terminal in a project has gone fifteen minutes without output or input, the project folds its session list away and wears a status tally instead — a dot and a count per status, loudest first, so “one needs you, two working” reads off a folded row without unfolding it. The threshold is a picker in Settings ▸ Projects, and the whole behavior switches off there.
What it refuses to fold is as much of the rule as what it folds:
A project with a waiting prompt or a live command stays open however old its last byte is.
- The project you're currently in stays open.
A fold Saggar placed unfolds itself the moment that project wakes or starts needing you.
Unfold one by hand and it stays as you left it. The chevron is a decision, and the sweep never argues with it.
Reaching for a folded project opens it on the spot — a click, an open from the palette, a new terminal, a jump to one of its sessions — because asking for a project is asking to see it.
More than one at once
The menu answers watch many. Three other arrangements answer the cases it can't:
- Split view — ⌘D
A second live terminal beside the one you're in, for the drive a pair case: an agent and its dev-server logs, or a scratch shell for
git statuswhile a build churns.⌘Dsplits a fresh shell in the same directory; a menu row's Open beside current puts an existing terminal there. One pane owns the keyboard — drawn as a focus ring, moved with⌘]— and⌘⇧Wcollapses the split, returning the second terminal to the menu alive. Deliberately flat: one extra pane, one axis, no recursive pane tree.- Monitor dock — ⌘⇧M
The long-running watch you keep half an eye on:
npm run dev,test --watch,tail -f. It docks in the top-right corner, read-only, never taking focus, scoped to its project so it follows you across that project's terminals and hides when you leave. A running session can be moved there without a restart (Dock as monitor), and selecting a monitor promotes it to the main pane so you can interrupt or restart it. Pressing ↵ without typing docks a bare shell: the chord has already decided you want a monitor, and the field only says what it runs. Because the premise is that you're looking elsewhere, the dock lifts two things out of the scroll: the served URL, clickable, and the “ready in 340 ms” line that says it came up. Errors in the tail turn the frame amber with a count, and a dev server that dies rings the same cue any other failed session does.- A window of its own
Any terminal tears out into a separate window from the header's ellipsis. To compare two terminals rather than pair them, that is the move — spatial layout is yours to orchestrate, and the app doesn't try to own your screen.
Beside the terminal
⌘/ opens the project scratchpad beside the
session — a markdown file committed at
<project>/.saggar/scratchpad.md, so
it is legible to whoever, or whatever, opens the repo next.
Hovering a menu row raises a peek card on the row's trailing side: the session's name, project, and status, its transcript's last lines, its branch and agent. It is strictly for background terminals — one already on screen raises nothing, since a preview there would only cover the live shell with a staler copy of itself. The card takes no clicks, so the row underneath still opens on the first one.
Hovering a URL or a file path inside a terminal previews that instead: host and title for a link, with the page's Open Graph image and description filling in behind it; name, size, and first lines for a file; entries for a folder; an honest “not found” for a path that doesn't resolve. One click opens it in the browser, the default app, or Finder.
Git state
Each session, worktree, and project wears its branch alongside a state
chip: how far it sits ahead of or behind its upstream, and how many
files are dirty. It goes amber the moment there is uncommitted work and
stays quiet when clean and synced — which is also what makes
“finished, but with uncommitted work” a rank of its own in
triage. It is read from
git status off the main thread on a slow
beat, so the menu never blocks on a big repository.
Saggar also writes a machine-local file into each project
(.saggar/state.local.json, the mirror kiln
reads). When a repository's
.gitignore doesn't cover it, the
project landing offers one click to append the line — and if git already
tracks the file, it says so and names the
git rm --cached that a fresh ignore rule
won't do for you.