A small TUI for managing short-lived multi-repo work environments. One choros per Jira-task-sized unit of work.
Choros is project-scoped to the directory it's launched in. Run it from the directory where you want your choros to live.
<cwd>/
├── .choros-config/
│ └── registry/
│ ├── service-api/ ← added with `choros add`
│ └── service-web/
├── PROJ-1234/ ← a choros, created by choros
│ ├── .choros-meta.toml
│ ├── service-api/ ← fresh clone of the registry repo
│ └── service-web/
└── PROJ-1235/
└── ...
A directory is recognized as a choros iff it contains .choros-meta.toml.
Populate the registry with choros add, passing an SSH remote:
choros add git@github.com:your-org/service-api.git
choros add git@github.com:your-org/service-web.gitThis clones each repo into .choros-config/registry/<name>, deriving <name>
from the URL. Only SSH remotes are supported — the registry copy is later
used as a --reference-if-able source for every workspace clone, which needs
a stable local git repo, not an HTTP(S) endpoint.
The TUI scans .choros-config/registry/ on launch and lets you pick repos from there.
./install.sh # ~/.local/bin/choros
INSTALL_DIR=/usr/local/bin sudo -E ./install.sh # system-wideOr with cargo directly:
cargo install --path .Run from inside the directory you want choros to live in.
choros # full TUI (manage existing + create new)
choros work # fast-create: just name + repos, then exit
choros work PROJ-1 # name pre-filled, jumps to repo selection
choros work PROJ-1 api web # fully non-interactive (no TUI)
choros archive # archive the workspace your shell is in
choros archive PROJ-1 # archive PROJ-1 from the project root
choros agent save settings # inside a workspace: TUI to promote claude
# settings entries up to the template
choros agent ls # list resumable claude / cursor sessions for
# the current directorychoros work is the quick path for "I want a fresh choros right now". It skips the main screen and drops you straight into the name + repo picker. With the shell integration below, your shell is cd'd into the new choros on success.
choros is an external binary, so it cannot change your shell's cwd on its own. Add this one line to your shell rc to enable cd-on-create:
# ~/.zshrc or ~/.bashrc
eval "$(choros shell-init)"After that, choros work … is silent on success and your shell ends up in the new choros dir.
Keys (main screen):
| key | action |
|---|---|
n |
new choros |
d |
delete the selected choros (with confirm) |
Enter |
show choros details |
j / k / arrows |
move selection |
r |
re-scan |
q |
quit |
In the new-choros modal: Tab switches between the name field and the repo list. Space toggles a repo. Enter creates. Esc cancels.
For each selected repo R:
- reads
git -C .choros-config/registry/R remote get-url origin - best-effort
git fetchin the registry copy git clone --reference-if-able .choros-config/registry/R <url> <choros>/Rgit -C <choros>/R checkout -b <choros-name>so each clone starts on a workspace-named branch
Then:
- writes
<choros>/.choros-meta.toml - drops a
choros-archiveskill at<choros>/.claude/skills/choros-archive/SKILL.md - copies any per-agent baselines from
.choros-config/templates/(currently.claude/settings.json; cursor.cursor/cli.jsonis opt-in — see Templates below) - detects Rust / JS toolchains in the cloned repos and wires up a shared
build cache under
.choros-config/store/— see Build cache below
choros init seeds .choros-config/templates/.claude/settings.json with a
minimal baseline. Edit this file once and every future workspace inherits it
(workspace creation copies it to <choros>/.claude/settings.json).
Claude writes interactive "always allow" grants to
<choros>/.claude/settings.local.json, not the template. To promote those new
grants up to the template so future workspaces inherit them:
cd <choros>
choros agent save settingsThis opens a TUI that diffs the workspace's claude settings against the
template and lets you check off entries to promote. Tab cycles the diff
source between settings.json, settings.local.json, and both (default).
choros agent ls enumerates Claude Code and Cursor CLI sessions whose
recorded cwd matches the directory you run it from (or any path beneath it).
Each line shows agent, modified time (UTC), session ID, the path (relative to
the workspace root) the session was invoked from, and a preview of the first
user message — feed the ID into claude --resume <id> or
cursor-agent --resume <id> to pick up where you left off.
When run from inside a choros workspace, the scope expands to the whole
workspace (not just cwd), and the PATH column is workspace-relative —
. for sessions invoked at the workspace root, <repo> or <repo>/<subdir>
for sessions invoked deeper. Outside a workspace (e.g. at the choros root)
the scope is the current directory and PATH is cwd-relative.
To make new workspaces cheap, Choros wires up a shared, per-project build
cache at <root>/.choros-config/store/ on workspace create:
| Toolchain | Detected by | Cache | Mechanism |
|---|---|---|---|
| Rust | Cargo.toml |
.choros-config/store/rust/sccache/ |
drops <choros>/.cargo/config.toml with rustc-wrapper = "sccache" and [env] SCCACHE_DIR = ... |
| JS | pnpm-lock.yaml / yarn.lock / package-lock.json |
.choros-config/store/js/pnpm-store/ |
runs pnpm install --store-dir=... (with pnpm import first for npm/yarn lockfiles); hardlinks files from the store into <choros>/<repo>/node_modules/ |
The cache is shared across every workspace under the same Choros root, so the
second workspace pays effectively nothing for the same deps. The cache is not
shared across different Choros roots — keeps Choros state self-contained and
makes rm -rf .choros-config/store/ a clean reset.
For npm/yarn repos, the pnpm-lock.yaml generated by pnpm import is added to
the clone's .git/info/exclude, so it never shows up as pending work (and the
archive flow can't accidentally commit it).
Requirements: sccache for the Rust path, pnpm for the JS path. If
either is missing from PATH, Choros logs a warning and skips that toolchain
without failing workspace creation.
Sccache server gotcha: sccache runs as a background daemon and inherits
the SCCACHE_DIR from whichever process first starts it. If you have a
sccache server already running from another context (e.g. you ran
sccache --show-stats outside a Choros workspace), it'll keep using its
original SCCACHE_DIR instead of the workspace's. Fix with
sccache --stop-server; the next cargo build inside a Choros workspace will
start a fresh server with the right env.
choros archive moves a workspace under .choros-config/archive/<name>/ so
it disappears from the active list but is still recoverable. Pass a name
explicitly to archive from the project root, or omit it to archive the
workspace your current directory is inside.
Each new workspace also ships with a Claude Code skill at
.claude/skills/choros-archive/SKILL.md. From inside the workspace, run
/choros-archive and the AI will commit any pending work, push every cloned
repo to its origin, then run choros archive to retire the workspace.
- Adding/removing registry repos from inside the TUI
gh-CLI integration to browse repos- "Open in editor" / shell drop-in
- Pulling latest main on existing choros clones
- Async parallel cloning
- Dirty-state checks on delete (delete is
rm -rf— choros are ephemeral by design)
cargo test --release
cargo build --release