ctx
The way I write code has changed. With agents, I context-switch more, and the
time it takes to spin up or tear down a context became increasingly noticeable.
I didn't enjoy the git worktree UX, so I ended up maintaining multiple
long-lived clones of the same repository nestled in different tmux sessions to
work in parallel. This worked well for me, but it wasn't exactly elegant. So
here we are.
ctx lets you manage repo-scoped work contexts: each context is a fresh full
checkout on its own local branch, wrapped in a terminal multiplexer session
with your panes already laid out.
Disclaimer: this is fully vibe-coded.
Install
Or grab a prebuilt binary from the latest release.
Setup
ctx runs fine with zero config; setup is optional but will likely improve
your experience. The fastest way through it is to not do it yourself: tell your
agent "set up ctx following its README" and let it work through this section.
Instructions for the agent: ask the user each question, then apply their answer using the concrete snippets under Configuration.
- Which multiplexer do you use, tmux or zellij? Set
multiplexerin the config file. - Which panes should every context session open with (agent, editor,
git client, ...), and which gets focus? Write the
layouttable. - Which additional status columns do you want? (github, agent). Make sure
their pre-requisites are met, then add them to the
[[status]]table. - Which key should summon the picker? Bind it in the multiplexer's config as shown under Instant picker.
- Should the agent be able to use ctx itself? Install the docs as shown under Agent docs.
- Verify: register a repo (
ctx repo add), create a context (ctx new), and check thatctx listshows the configured columns. Then clean up (ctx rm). - Instruct the user on how to open and use the TUI.
Usage
Run ctx to manage contexts and repos interactively in the TUI (? lists the
keybindings). The TUI is meant to be summoned from inside a multiplexer
session: hit the picker key (see Instant picker) and it opens as a floating
pane over the session you're in.
You can also use ctx as a CLI:
# ...work, commit, push...
More detail:
# List contexts with their repo, branch, and status:
# Contexts branch off the up-to-date default branch. To base one on another branch:
# Leave the name out to get a generated one (e.g. holy-tiger); the TUI's new-context
# prompt pre-fills the same, so enter accepts it and typing replaces it:
# Re-attach to a context, unarchiving it and recreating its session if needed:
# Show, set, or clear the default repo for new contexts:
# List registered repos, or remove some (their contexts are left alone):
# Set contexts aside without deleting them (kills their sessions), and bring one back.
# Archived contexts keep their names: names are unique across live and archived.
# List archived contexts, or empty the whole archive:
Configuration
Configure ctx via $XDG_CONFIG_HOME/ctx/config.toml. All fields shown with
their defaults.
= "~/.local/share/ctx/contexts" # where checkouts live
= "~/.local/share/ctx/repos" # internal storage for registered repos
= "~/.local/share/ctx/archive" # where archived contexts go
= "" # work branch prefix, e.g. "jane/"
= "tmux" # or "zellij" (requires zellij >= 0.44)
= true # false swaps builtin glyphs for plain unicode
Multiplexer layout
ctx supports tmux and zellij multiplexers. Customise the layout of a
ctx session via the layout table in the config:
# The pane layout: a tree of panes and "row"/"column" splits ("row" = side
# by side, "column" = stacked). A pane runs `command` or a `builtin`
# (default: a shell) in the checkout; at most one pane may set `focus`.
[]
= "row"
[[]]
= "column"
[[]]
= "lazygit"
[[]]
= "nvim"
[[]]
= "claude"
= true
Pane builtins
A pane can use a builtin instead of a command. Where a command pane
always runs the same string, a builtin names something ctx knows how to
run, so it can compose the invocation itself. Extra flags go in args.
The claude builtin runs Claude Code:
[[]]
= "claude"
= "--model opus" # optional extra flags
ctx new --set prompt="..." hands it an initial prompt, so an agent (or
you) can spin off an exploration into its own context:
Recreating the session later (ctx open after a kill or an unarchive)
resumes the checkout's conversation via claude --continue.
Instant picker
The best way to summon the TUI is in a floating overlay in your current multiplexer session.
zellij
Add the following to your config.kdl to open ctx with Ctrl-o g:
keybinds {
session {
bind "g" {
Run "ctx" "tui" "--exit" {
floating true
close_on_exit true
}
SwitchToMode "Normal"
}
}
}
tmux
Add the following to your .tmux.conf to open ctx with Ctrl-b g:
bind g display-popup -E "ctx tui --exit"
Theme
The TUI renders with your terminal's ANSI palette. A [theme] table overrides
individual colours with hex values, e.g. to match a truecolor lazygit theme:
[]
= "#c8d3f5"
= "#2d3f76" # the focused panel's cursor row
= "#ff966c"
= "#589ed7"
Status columns
The STATUS column shows the git state: * for uncommitted changes, ↑n for n
unpushed commits.
You can add further columns via [[status]]. ctx comes with some builtin
status integrations. The TUI re-polls every few seconds and colours known
states.
GitHub builtin
[[]]
= "pr"
= "github" # the branch's latest PR, collapsed into one cell
Requires an authenticated gh in the checkout (without one the cells stay
blank), and a nerd font: the states render as
nerd-font glyphs (set nerd_font = false to fall back to plain Unicode).
Agent builtin
[[]]
= "agent"
= "agent" # .git/agent-status, hook-written by your agent
You'll also need to configure your agent to write its status, e.g., for Claude
Code, add the following to ~/.claude/settings.json:
Custom
You can also add your own status column via a command that runs in the checkout:
[[]]
= "anything"
= "my-status" # first line of any command, run in the checkout
Agent docs
To make your agent ctx-aware, install the output of ctx agent-docs as
a skill. E.g., for Claude Code:
&&
Environment variables
Fresh checkouts don't carry untracked files like .env. All contexts of a
repo share the parent directory <contexts_dir>/<repo>/; you could, for
example, use a tool like direnv to export env vars in
all of a repo's contexts from a single .envrc there:
Contributing
See CONTRIBUTING.md.