Commands¶
| Command | Description |
|---|---|
jailbee init |
First-time setup: create Incus profiles, ACLs, shared directories |
jailbee apply [-y] [--no-restart] |
Re-apply current config (profiles, ACL, /etc/hosts, dockerd proxy); prompt to restart containers if profiles changed, and dockerd if the proxy config changed. With LiteLLM running it also re-renders the proxy; route and profile changes (global or any repo override) are reloaded into the running instances without a restart, and an instance restarts only for a secret, extra outside its model_list, settings or an unconfirmed reload, saying which; --no-restart still applies reloads and defers only restarts, leaving those instances on the previous routes with a warning until a plain apply. If the proxy needs jailbee litellm up instead, it says so. See LiteLLM |
jb remote ssh enable / disable / restart / status / serve |
Manage the optional per-user SSH service. enable installs, enables and starts it; disable stops and disables it without deleting keys; restart applies listener changes; status inspects service/config/key state; serve runs it in the foreground for diagnostics. |
jb remote ssh key add [PATH\|-] / ls / rm SHA256:FINGERPRINT |
Add one plain OpenSSH public key — from PATH, from stdin with -, or pasted at a prompt when no argument and no piped input is given — list every key, or remove one by its full SHA256 fingerprint. All authorized keys have the same access. |
jailbee new <name> [<base>] [opts] |
Create container, clone repo, run autostart. <name> names the environment (used as the branch inside the container, slugified into the container name). <base> sets the container's base branch: it is branched off when <name> is new, and used purely as the comparison anchor when <name> already exists (that branch is cloned as-is for review, after a confirmation). Without <base> the base is the repo's default branch. --current, --pr <N>, --mount, --background, --tmux, --shell, --attach shell|tmux|none, --no-clone, --no-autostart, --yes, --name <container>, --net strict|loose, --memory <size>, --cpu <n>, --credential-group <g>, --from-base <image> (create from an image other than the golden one), --no-attach (same as --attach none), --no-background (foreground, overriding new.background), --no-fetch (with --pr: use the PR head already on the host), --wait/--no-wait (override a detach: true autostart stage for this run — see below). See jailbee new --help for what each does |
jailbee ls [--all] [-o json] [--fields …] |
List managed containers + their git status (own repo by default; --all for every repo). DIFF ± is the direct committed-tree diff against the host's live target branch; ↑/↓ count commits unique to the container/target respectively. The target is the host's local branch, falling back to its last-fetched upstream tracking ref (marked on BASE); no fetch occurs. LOCAL ±/L↑ compare with the host's currently checked-out HEAD and remain opt-in. See Configuration |
jailbee job ls [--all-repos] [-o json] [--fields …] |
List in-flight and failed background jobs with phase, pid, age, error and log path |
jailbee job log <name> [--follow] |
Print (or follow) the worker log of a background job — including a detached autostart run's supervisor; there is no separate jailbee autostart log |
jailbee job clear [<name>] [--all] |
Acknowledge a dead background job — clears the failed/stale record without touching the container. Refuses a job whose worker is still alive. Leftover boot records need no acknowledging: a jailbee start/jailbee restart that completes clears its own |
jailbee dashboard (alias: jailbee tui) |
Live, auto-refreshing TUI of containers across all repos; Enter opens a container menu or a repo-header menu with New container…, New from PR…, Credential group…, Accounts…, a Network → submenu (Egress), Apply config…, Diagnostics →, Prune stale containers… and Fold/Unfold (orphan repos only offer Fold/Unfold; details below). Container menus group network mode switches and Egress… under Network →; Egress is viewable for stopped containers too. In the Egress panel, a adds an override and r removes the selected removable override; config-sourced grants and inherited repo grants in a container panel are read-only. Repo-level removal deletes the stored entry from both host-local and legacy sources when duplicated. Every change goes through the scoped jailbee net egress CLI, so its DNS/ACL validation and repo jailbee apply advice remain authoritative. The container action menu carries workflow commands too — merge, pr, git push, git push --pr ("refresh from PR head", review containers only), git retarget ("change base branch"; the CLI asks for the branch), git pull, git diff, job log — each shown only when it would do something. Pending outbox applies (Apply N PR action(s), Apply N issue action(s)) lead the menu; Git → comes before PR →. ! opens an inline JailBee command line: Tab completes command paths, options and containers in the selected repository; Enter runs it and Esc/Ctrl-C cancels it. A selected container supplies an omitted, unambiguous container positional (including !merge's source); a selected repo header supplies the repository only, so !merge lets the CLI ask for both source and target. Explicit arguments always win. Quick keys: t/s tmux/shell, i/c IDE/Chrome, p open the PR, P create/update it, u update from base, d show the diff, D destroy the container (asks to confirm), F2/S settings overlay (columns + folding), h/? help, e/E edit the selected row's repo config (E: the global one), n create a container in the selected row's repo (asks for a branch and a base branch, then runs jailbee new in the terminal). Space folds/unfolds the selected repo (in the settings overlay it toggles the setting). A, or Accounts… in the repo menu, opens the credential-group overlay: pick a group or stored login and use, park or delete it, n creates a group; the repo and container menus carry Credential group… to change which group a repo or a single container follows. Changes run the real jailbee account … commands; a refusal (for example a running agent) is shown as a notice — use ! with --force to override. The repo menu also carries Apply config… (runs jailbee apply in the terminal, optionally with --no-restart; its restart question is asked there), Diagnostics → (doctor, paged locally and printed with a pause over remote SSH; disk-usage) and Prune stale containers… (jailbee prune, which asks about each container). The container menu adds Snapshots… (create with a timestamp or a typed tag, restore or delete after a confirmation), Mount…/Unmount… for the repo's optional_mounts (only kinds not attached / attached), and, while the container has an autostart run, Autostart status and Cancel autostart…. Over remote SSH an entry appears only when the session's policy permits its command; apply and mount manage the host, so restrict_host: true keeps them hidden. Press h/? for the full key reference. |
jailbee shell <name> |
Interactive shell (lands in the in-container clone) |
jailbee tmux <name> |
Attach to the autostart tmux session inside the container |
jailbee exec <name> [--cwd repo\|home\|<path>] [--detach\|-d [--gui]] -- <cmd> |
Run a command in the container as the dev user (e.g. jailbee exec smoke -- pnpm test). --detach/-d runs it in the background — it survives jailbee returning and its output goes to a log file inside the container (/tmp/jailbee-exec-<timestamp>-<uuid>.log); needed for a GUI app (jailbee exec smoke -d -- firefox), useful for anything long-running. --gui (with -d only; otherwise exit 2) marks the command as a GUI app: in a GUI-enabled SSH session it draws on the shared RDP display, which is started and awaited first (see Remote GUI); a plain -d never touches that display |
jailbee start/stop/restart <name> [--wait\|--no-wait] |
Lifecycle: start/restart re-run autostart, and take --wait (run every stage in the foreground, ignoring detach: true) / --no-wait (hand off after the first stage regardless of config), mutually exclusive. stop refuses while a detached autostart run is in flight (--force skips the check and cuts it off); restart refuses too, with no --force — cancel the run first (jailbee autostart cancel) |
jailbee autostart status <name> |
Show how far a container's detached autostart stages have got: one row per step, grouped by stage, plus notes (an interrupted step, or a leftover record to jailbee job clear) |
jailbee autostart cancel <name> |
Stop the worker running a container's detached autostart stages (SIGTERM): it unwinds the stage in flight — interrupts the running step (best-effort, doesn't wait), detaches the stage's mounts, restores the network (compare-and-swap, so a jailbee net run since then stands), and marks the job row failed. Refuses when the worker is already gone, pointing at jailbee job clear |
jailbee destroy [<name>] [--all] [--force] [--background] |
Destroy one container, --all for the whole repo, or no-arg interactive checkbox. Before the usual confirmation, JailBee assesses what would be lost (dirty tree, changed submodule, commits held nowhere else) and, if anything is at risk, shows a summary and a second confirmation defaulting to No; --force skips both prompts and the assessment |
jailbee git fetch <name> [-b <branch>] [--as <name>] [--force] [--tags\|--follow-tags\|--no-tags] |
Fetch into refs/jailbee/<short>/<branch>, transport submodule objects, then point the host branch and every submodule's branch of the same name at the container's state — without switching the working tree. -b picks which branch to read from the container; --as names the host branch written. A diverged host branch is left alone with a warning; --force overwrites it, except when it is the branch currently checked out. --tags/--follow-tags/--no-tags override pull.tags for this run (see Tags). Switch the tree with jailbee branch <branch> afterwards |
jailbee git checkout <name> [-b <branch>] [--as <name>] [--confirm\|--no-confirm] [--tags\|--follow-tags\|--no-tags] |
Fetch + check out the container's branch on the host (ff-only). -b picks which branch to read from the container, --as names the branch written on the host (default: the container branch, or its PR head when set). With one eligible container and no name, confirms first (confirm.auto_target). --tags/--follow-tags/--no-tags override pull.tags for this run. |
jailbee git pull [<name>] [--into <b>\|--current] [--ff\|--no-ff] [--checkout] [--cleanup\|--no-cleanup] [--confirm\|--no-confirm] [--tags\|--follow-tags\|--no-tags] |
Fetch + merge the container's branch into its base branch (alias: jailbee pull). --current merges into the host's checked-out branch instead (mutually exclusive with --into). No name → multi-select picker. With one eligible container and no name, confirms first (confirm.auto_target). --ff/--no-ff override pull.ff (default auto: fast-forward when possible, merge commit otherwise) for this run; --tags/--follow-tags/--no-tags override pull.tags. See Fast-forward policy and Tags. |
jailbee git retarget <name> [<base>] [--merge] |
Re-point a container's base branch (stacked-PR maintenance). Without <base> a TTY gets a picker of the host's other local branches (off a TTY it is an error); both dashboards offer it as "Change base branch". --merge does not honour push.tags — it never sends tags, and there is no flag to change that |
jailbee git push [<name>] [--merge\|--rebase\|--plain\|--force] [--ff\|--no-ff] [--from <b>\|--current] [--from-origin\|--from-local] [--fetch\|--no-fetch] [--confirm\|--no-confirm] [--tags\|--follow-tags\|--no-tags] |
Send a host branch into a container (alias: jailbee push). No name → multi-select picker. By default the host fetches and pushes origin/<source>, not the local branch — a local refs/heads/<base> only advances on git pull (push.push_from / push.autofetch). --from-local sends the host's local branch instead; --current always resolves locally, and --pr pushes refs/jailbee/pr/<N>/head verbatim (so --from-local/--from-origin are rejected with it). With one eligible container and no name, confirms first (confirm.auto_target). --ff/--no-ff pick how --merge merges: by default a fast-forward when the container is already on the pushed branch and a merge commit otherwise, with a prompt when that fast-forward is impossible. --tags/--follow-tags/--no-tags override push.tags (default none), including with --merge/--rebase/--force. |
jailbee git push [<name>] --pr |
For jailbee new --pr containers, re-fetch the PR head from GitHub and push it in (pull in commits the author pushed since you started). With no name on a TTY, choose one running clone-mode PR container, or use the only eligible one automatically; scripts must name the container. Without --merge/--rebase/--plain the merge/rebase/plain choice follows push.default_action, which is ask: a prompt on a TTY, an error off one. Both dashboards offer it as "Refresh from PR head" |
jailbee git merge <source…> --into <target> [--into <target>…] [-b <branch>] [--plain] |
Merge one container's branch into another without a host checkout — objects travel source → host → target, no host branch, index or superproject working tree is touched (a host sub-repo can still be created, for a submodule born in the source container). Both ends take several containers: every source is merged into every target (repeat --into). --into is never inferred, but either end may be omitted on a TTY and is then asked for — the sources first (a checkbox that merges in listed order), the targets second (a checkbox too); off a TTY both must be given. A container may not be named at both ends: each prompt hides the rows the other end holds, and a typed collision is exit 2 before anything is merged. Several sources are merged into each target one at a time, in order given, stopping at that target's first conflict — the targets after it are still attempted; the summary always names what landed, what stopped it, what remains, and the resume command, and a run with several targets closes with a roll-up of every target's state. Conflicts are resolved inside the target with jailbee shell <target>. Each source that lands prints its own ── Submodules block, read inside the target container. -b is not submodule-safe — the transport enumerates the source's checked-out state, not the branch being read. --plain transports the refs only and reports "transported", not "merged". Alias: jailbee merge |
jailbee pr <name> [--ready\|--draft] [--description] [--title <t>] [--body <t>] [--base <b>] [-b <branch>] [--as <name>] [--pr N] [--stacked] [--retarget\|--no-retarget] [--force] [--web] [--open] [--no-ai] [--no-outbox] [--yes] |
Create a draft PR or update an existing one. Opens a new PR when the container has none yet; when one exists, pushes new commits and optionally regenerates the description (--description) / toggles draft state. The new PR's head branch name and description are written by the repo's agent (pr.agent, by default the one it autostarts — see pr), convention-aware and confirmed on a TTY; a description staged in the container's outbox wins over generation unless --no-outbox. --base overrides the PR base (default: the container's base branch), -b the branch detection, and --open just opens the container's PR in the browser. --as <name> sets the head name explicitly on a new PR (exit 2 once the container has one — the PR's head is fixed); --force force-pushes a rebased/amended branch with --force-with-lease (requires explicit container name). --no-ai opts out of AI generation entirely (top-level command; hidden alias: jailbee git pr). On a jailbee new --pr container JailBee asks once — an arrow-key menu — what to publish: the container's commits into that PR's head branch, or a new PR based on it (a stacked PR). The answer is remembered; --yes picks the first non-interactively and --stacked the second, and off a TTY one of them is required. After adopting that PR — one JailBee did not create — --force also asks before overwriting the head and the description is never regenerated unless you pass --description/--title/--body. Fork PRs are refused with a manual-push recipe. --stacked opens a PR whose base is the reviewed PR's head: it needs a head branch of its own (--as, or the agent's proposal — publishing under the reviewed head is exit 2), records itself under user.jailbee.stacked_pr* so user.jailbee.pr keeps naming the reviewed PR (and with it jailbee ls's PR column and jailbee git push --pr), and offers to retarget the container's base branch onto that head so the host-target commit count measures its own commits when that target ref exists (--retarget/--no-retarget; the default asks on a TTY, otherwise prints the command). Refused on a fork PR (its head is not a branch in your origin, so it cannot be a base), on a container that already publishes to a PR's head, and on one never created from a PR. Later runs update the stacked PR without asking, and --open prefers it over the reviewed PR. --stacked with --pr N is exit 2. When a container has no PR but its branch already has an open one, JailBee offers to push to that PR instead of opening a duplicate (same recorded labels and hands-off rules; closed/merged and fork PRs fall through to a new PR, and --as skips the check). --pr N names that PR outright, for the common case where the container's branch is not named like the PR's head branch: same confirmation and same labels, but a closed/merged or fork PR is refused rather than falling through to a new PR, and retargeting from another number asks first. --pr with --as is exit 2. |
jailbee git diff <name> [--wt\|--all\|--incoming\|--stat] [--color\|--no-color] |
By default, show the direct committed-tree diff against the host's live target branch. --incoming selects the contribution-only three-dot diff; --wt is working-tree-only and --all combines working-tree changes with the default committed diff. A missing target or unreadable target object is an error. Alias: jailbee diff. Colour follows stdout; --color forces it on for a consumer that pipes the output into a pager (as jailbee dashboard does), --no-color forces it off |
jailbee branch [<branch>] [--container <name>] [--submodules-only] |
Put the tree on one branch locally — host repo by default, or a named container; no host↔container transport. On the host, a BRANCH argument checks it out in the superproject too, then aligns the submodules to it; --submodules-only leaves the superproject alone (detached HEAD, deliberate mismatch). A container's branch is never switched — there BRANCH is pure submodule placement, and --submodules-only is rejected. jailbee submodule checkout is a hidden deprecated alias |
jailbee submodule pr [<name>] [<path>] [--title <t>] [--body <t>] [--base <b>] [--ready\|--draft] [--description] [--as <name>] [--pr N] [--force] [--web] [--no-ai] [-b <branch>] [--yes] [--open] |
Create or update a GitHub PR in a submodule's own repository from commits made inside it — a separate repo from the superproject, so a separate PR from jailbee pr (one PR per run, independent of it). Off a TTY, without <path>, the submodule ahead of its own base is targeted automatically; several ahead are listed and <path> is required; none ahead is reported as a fact and exits 0. On a TTY, without <path>, a container picker runs first (even with only one container), then a submodule picker offers every submodule — not only the ones ahead of base — instead of erroring on ambiguity; either picker is skipped when its target is unambiguous from the arguments given. A plan block (container/submodule/source/base/remote/action) is then shown and must be confirmed before anything is transported or published; --yes skips that confirmation but not the pickers. The signal is the submodule's own refs/jailbee/base/<super-base> anchor, not the superproject's gitlink diff — commits can show here before the gitlink bump is even committed (reported as information, not an error). Base branch: --base > submodule.<name>.branch declared in .gitmodules (found by descending from repo root, unless .) > the sub-repo's <remote>/HEAD > main. Head branch: --as > the agent's proposal > the branch the commits came from; the chosen head is remembered so a re-run updates the same PR. --branch/-b here selects which branch is read from the submodule, not the superproject branch. --open reads the recorded PR and opens it, requiring <path> when more than one is recorded. On success, if the container also has a superproject PR, JailBee notes the merge order (submodule PR first) as information only. --pr N targets the submodule's existing PR N, resolved against the submodule's own repo and remote |
jailbee outbox [<container>] / outbox browse [<container>] |
Unified terminal proposal browser; off-TTY prints an overview. Use outbox browse ls for a container named ls. Inspection is local and read-only over SSH. See Unified proposal management |
jailbee outbox ls [<container>] [--all-repos] [-o table\|json] |
List both stores with explicit empty/unavailable states; JSON includes availability and errors, not just proposals |
jailbee outbox show <container> <proposal> [-o table\|json] |
Inspect pr/<manifest>.json or issue/<manifest>.json, full text, revision, receipts and zero-based action/comment indices |
jailbee outbox drop <container> <proposal> [--action N] [--comment N] [--with-dependents] [--archive-journal] [--revision TOKEN] [-y] |
Confirm exact local deletion scope; comment requires action. Selective edits require no recorded publication progress; deleting an issue create with dependents needs explicit cascade consent |
jailbee outbox apply <container> <proposal> [--dry-run] [--force] [--revision TOKEN] [-y] |
Publish all pending actions in exactly one manifest through existing domain gates, never only a selected child. --force is PR-only; -y skips confirmation, not safety checks |
jailbee review apply [<name>] [-y] [--dry-run] [--force] |
Publish the PR review comments, replies and description rewrites a container's agent staged in ~/.jailbee/pr-outbox/, with the host's own gh, after showing each one. A manifest whose PR head moved since it was written is held back unless --force; --dry-run shows the plan only. Published manifests are deleted and logged in applied.log, so a re-run never double-posts. See Reviewing a pull request |
jailbee review ls [--all-repos] [-o table\|json] [--fields …] |
List pending PR outbox manifests across this repo's containers (--all-repos: every registered repo) |
jailbee review show [<name>] [<manifest>] |
Print pending manifests in full |
jailbee review drop [<name>] [<manifest>] [-y] |
Delete pending manifests without publishing them |
jailbee issue ls [<name>] [-o table\|json] [--fields …] |
List GitHub issue actions a container's outbox has staged (~/.jailbee/issue-outbox/), never touching GitHub itself. Omitting <name> does not open a picker — it lists every container of this repo at once. A stopped container's outbox cannot be read, so it's named in a note rather than shown as empty. See Issue management |
jailbee issue show <name> [<manifest>] |
Print a pending manifest's proposed text and host journal state in full (never truncated) — on a terminal, Markdown bodies are laid out and coloured, with HTML and image URLs shown as text rather than hidden, and piped output stays raw — omit <manifest> for every pending one in that container |
jailbee issue apply [<name>] [--manifest <name>] [--yes] [--dry-run] |
Show one combined plan for every selected manifest and repository it touches, ask once, then apply it — a two-stage expected-field staleness check guards every mutating action, and a run stops at the first action it cannot durably resolve, resumable by re-running. Omitting <name> auto-picks the one running container with anything pending; --dry-run prints the plan and exits without asking or mutating; --yes skips only the confirmation |
jailbee issue drop <name> [<manifest>] [--archive-journal] [--yes] |
Delete pending issue actions without publishing them — no GitHub call. Refuses a manifest with any recorded journal progress by default; --archive-journal allows dropping settled (applied/pending) progress after archiving a record of it, but still refuses while any action is uncertain |
jailbee issue resolve <name> <manifest> <action> (--applied --url <url> [--issue <n>] \| --retry) [--yes] |
Reconcile one action an earlier apply left uncertain (its GitHub outcome unknown): --applied durably records a human's own confirmation of what landed (--issue required, and only valid, for a create action); --retry forgets the uncertain record so the action is attempted again |
jailbee net strict <name> |
Switch to the egress allowlist; clears any loose TTL |
jailbee net loose <name> [--for <dur>\|--no-revert] |
Switch to full NAT. --for sets the auto-revert TTL for this switch only (30s, 45m, 4h; max 24h; never = no auto-revert, same as --no-revert). With neither flag on a TTY, JailBee asks; otherwise loose_auto_revert.after applies. See Configuration |
jb net migrate [--undo] [--yes] |
Explicitly activate the host-wide work-network generation for future containers, or undo that default. Existing instances are never moved; undo preserves resources still in use. Firewall reachability cannot be verified until a work container is running. See Installation |
jailbee net refresh/status/unregister/install |
Egress-pool refresh timer + allowlist management. status also lists every egress override on the host |
jailbee net egress ls/add/rm/export [<entry>] [<name>] [--container <name>] [--repo] |
Widen a strict allowlist without editing committed config (short alias: jailbee egress …). add/rm default to container scope — the entry lives in that container's own label and dies with it; --repo scopes to every container of the repo on this host, stored in ~/.config/jailbee/repos/<prefix>.yaml (not in git) and pushed by the next jailbee apply, while a container-scoped add/rm takes effect at once. Omit ENTRY on a TTY to enter an add destination or select a stored override to remove; it uses the usual one-container resolver/picker unless --container <name> explicitly names the target. The option names the container; it does not choose repo scope. Existing ENTRY [NAME] invocations remain valid. Either way the entry materialises against the container's current network mode: adding to a loose container stores it but changes no ACL until the container returns to strict. Overrides are additive only — rm refuses an entry that exists only in config.yaml and points at the file, but does remove one that is also stored as an override. ls shows every applicable entry with its source; export prints a complete replacement for the config's egress_allow: key — paste it over the whole key, since a second egress_allow: would silently win. See Egress overrides |
jailbee snapshot create/restore/ls/delete <name> [tag] |
Snapshots |
jailbee port ls [NAME] |
List port forwards; with no NAME, every container of the repo (includes forwards added with incus directly, shown as source other) |
jailbee port to-container PORT [NAME] [--host-port N] [--proto tcp\|udp] [--host-address IP] [--container-address IP] |
Make a host service reachable inside the container. PORT is always the container-side port; --host-port names the host side (default: PORT) |
jailbee port to-host PORT [NAME] [--host-port N\|auto] [--proto tcp\|udp] [--host-address IP] [--container-address IP] |
Make a container service reachable on the host. PORT is always the container-side port; --host-port auto picks a free host port not already claimed by another container (stopped ones included) |
jailbee port rm HANDLE [NAME] |
Remove one port forward. HANDLE is a device name, a host_ports name, or a container-side port |
jailbee account ls [-o json] [--fields …] [-a <agent>] [-g <group>] |
Every stored login on this host and which holder each is live in: one row per credential group with the account it holds, per repo keeping its own login, and per parked login. AGENT names the pool; -a narrows the table to one agent (default: every enabled pooled agent). USED BY names the repos and containers reading each holder — container names when no repo resolves to it, which is how a group used only by an account group use override is discoverable. -g filters to one group (none for the ungrouped holders); the parked store stays listed |
jailbee account use [<email\|slot>] [-a <agent>] [-g <group>] |
Switch this repo's holder to a stored login. Omit the account to pick from a menu of the stored logins (the live one is not offered — it is already active); without a TTY the error names the candidates instead. Holder-wide: every repo sharing the credential group moves with it. A running agent session adopts the new credential on its next turn — no restart. <email> is enough unless two stored accounts share it, in which case pass the full slot name (<email>[#<org8>][~<disambiguator>] — the ~ part appears when one account has two independent logins stored). -a picks the agent's pool when several are pooled and the reference is ambiguous. -g <group> activates it into that group's holder instead — a group this repo is not a member of, so nothing about this repo's own login changes |
jailbee account park [-a <agent>] [-g <group>] |
Store the login in use and leave the holder empty, so the next agent run in a container of this holder prompts /login. This is how a new account enters the pool — there is no add, because only a browser login creates a credential. With several agents live at once, -a (or a picker on a TTY) names which to park; -g parks that group's login instead of this repo's |
jailbee account rm [<email\|slot>] [-a <agent>] [--yes] |
Delete a stored login permanently. Omit the account to pick from a menu. Refuses the live one (park it first). JailBee never contacts the vendor, so a deleted login only comes back through a fresh /login |
jailbee account group ls [-o json] [--fields …] |
The credential groups on this host and what each holds — the same rows as account ls, narrowed to the groups themselves (no parked logins, no repo keeping its own) |
jailbee account group create <name> |
Create an empty credential group before anything is assigned to it (set, use and account use -g create one on demand too). One 0700 directory per enabled agent |
jailbee account group rm <name> [--yes] |
Remove a credential group nothing uses. Refuses while a repo resolves to it, while it is the host default, or while a container has been moved into it; a login it still holds is parked into the host-wide store first, never deleted |
jailbee account group set <name>\|none [--force] / unset [--force] |
This repo's permanent group, written to its host-local file (~/.config/jailbee/repos/<prefix>.yaml); unset removes the local choice so the global default applies. Every container follows, and an override the change makes redundant is dropped |
jailbee account group use <name>\|none [<container>] [--force] / reset [<container>] [--force] |
One container's group, for its lifetime. Naming the repo's own group clears the override instead of writing one |
jailbee mount <kind> <name> / jailbee unmount <kind> <name> |
Optional mounts |
jailbee apps ls [<name>] [-o json] [--fields …] [--force] |
List the GUI apps this repo's containers can launch — builtins (browsers, JetBrains IDE) plus apps: entries, in registry order. Without a container this is config only; name one to add a STATUS column that actually probes each app (present/missing) |
jailbee apps run <app> [<args>…] [--container <name>] [--force] |
Launch a GUI app by name. <app> is required and comes first — see jailbee apps ls; the container is named with --container, not a second positional (three positionals with an optional middle one can't be told apart) |
jailbee ide <name> [--app idea\|webstorm] |
Launch JetBrains IDE. Needs jetbrains.enabled |
jailbee chrome <name> [URL] |
Launch Chrome. Needs browsers.chrome.enabled (or the deprecated top-level chrome.enabled); URL falls back to browsers.chrome.url |
jailbee firefox <name> [URL] |
Launch Firefox. Needs browsers.firefox.enabled; URL falls back to browsers.firefox.url. Firefox defaults to source: image (the host's Firefox is a snap on Ubuntu, so there's nothing useful to mount) |
jailbee browser [<name>] [URL] |
Launch the default browser: browsers.default when set, or the single enabled browser when exactly one is. Otherwise it errors and names what to set |
jailbee pool ls/prune [NAME] |
Inspect or clean per-container cache pool slots (Gradle, Maven, Chrome and Firefox by default — see pooled_caches); omit NAME for every pool. jailbee chrome-pool ls/prune still works as a deprecated alias for jailbee pool ls/prune chrome-profile; Firefox has no such alias — use jailbee pool ls/prune firefox-profile |
jailbee base build |
Build the golden image |
jailbee base prune [--all] [--days N] [--yes-to-all] |
Remove superseded dated golden-image archives (<alias>-YYYY-MM-DD). Lists all candidates and confirms once (a single batch confirmation, not per-archive); the live base image is always kept; in-use archives are skipped (batch continues). --all prunes archives for every registered repo, not just the current one; --days N only removes archives older than N days (default: all dated archives are candidates); --yes-to-all skips the confirmation prompt |
jailbee base usage [--all] |
Show disk usage of golden base images: each live base and dated archive with its size, per-repo subtotals, a prunable (archives-only) figure, and a grand total. --all includes every registered repo, not just the current one |
jailbee display up [--recreate] / down / status |
Shared RDP display for GUI apps launched over SSH. up starts it and prints how to connect; down stops it and revokes SSH forwarding to it. See Remote GUI |
jailbee registry up [--recreate] / down / status / verify [--purge] |
Docker registry mirror control. up repairs a half-provisioned mirror in place (reinstalls the proxy when its Quadlet unit is missing, and once more if the service never starts); --recreate deletes the container and rebuilds it from the image, preserving the host-side cache and CA. verify hashes every cache entry stored under a digest against that digest — a corrupt one makes every pull that needs it fail with unexpected commit digest — lists mismatches and offers to remove them (--purge: without asking); the next pull fetches a removed entry from upstream again |
jailbee litellm up [--reinstall] |
Create/repair the dedicated proxy, render its routes and start its service — except for an account with no ChatGPT login yet, which stays stopped until login and a second up. Requires litellm.enabled: true in global config; --reinstall forces the package install again. See LiteLLM. |
jailbee litellm down [--purge] |
Delete the proxy container and its dev-container ACL allowance; keep its state volume (logins, settings) unless --purge. Run jailbee apply per repo afterward. |
jailbee litellm status |
Show container state, proxy address/version, and per account the instance health and login presence; exits nonzero if absent or unhealthy. |
jailbee litellm ls |
List every profile as claude-jb uses it: default profile, autostart, model aliases, and per tier the route, model, effort and context window. Shows the global block, then one block per repo with a LiteLLM override (skipping broken ones with a warning). Read-only; allowed over remote SSH in the default commands mode, but refused when remote.ssh.excluded_repos is set (it lists every repo). |
jailbee litellm login [ACCOUNT] [--provider chatgpt\|xai] |
Interactive login for an account in litellm.accounts (optional when there is only one): ChatGPT by device code, xAI (experimental) in the host's browser. --provider is required only when the account's profiles use both. |
jailbee litellm logout [ACCOUNT] [--provider chatgpt\|xai] |
Delete that account's token for the provider in the proxy's state volume (the proxy must be running). |
jailbee litellm logs [ACCOUNT] [-f] |
Show the instance's last 200 journal lines; -f follows. |
jailbee setup [--yes] [--status] [--only STEP] [--shell SHELL] |
Post-install steps for this machine: shell completions (jailbee and jb), the jailbee-net-refresh user timer, and the bundled agent skills for the agents found on the host (opt-in — install_host_skills in the global config). Interactive by default and idempotent — re-run after upgrading. --status reports what is in place without installing anything. Needs no repo config |
jailbee doctor |
Diagnostics |
jailbee dismiss [KEYS…] [--all] [--clear] |
Mark repeating advisory warnings read, so they stop appearing on jailbee ls / new / shell. Keys are base-build and apply (an owed action — see Troubleshooting), update (a newer release on PyPI — see update_check) and legacy-config-dir / legacy-chrome-block / legacy-credentials-block / legacy-per-repo-map / legacy-pr-keys (deprecated config spellings). With no arguments, lists what applies here and what has been dismissed. An owed action returns when a later release adds a new reason for it — upgrading alone does not bring it back; a dismissed update returns when a release newer than the dismissed one appears; a deprecation stays dismissed until the config changes. jailbee doctor reports them either way, marked with when they were dismissed, so nothing is ever hidden from it. KEY@scope targets one of several files raising the same notice; --clear undoes a dismissal |
jailbee disk-usage |
Disk usage breakdown |
jailbee prune |
Interactive cleanup of stale containers |
jailbee config show/validate/init |
Configuration. show accepts --layer global\|repo\|local\|effective (default effective); the effective layer includes an agents: section with every configured agent fully resolved (preset fields included) — see Generic agent support |
jailbee config migrate [--apply] |
Preview migrations of deprecated config spellings and storage (default dry run); --apply writes changes, leaves .bak backups, and leaves conflicts in place for manual resolution. See Host-local overrides |
jailbee config edit [--global\|--local] [--write patch\|regenerate] |
Interactive editor for the repo, global, or host-local layer, with each field's own help text, its origin and a validated, backed-up save. --global edits ~/.config/jailbee/global.yaml; --local edits ~/.config/jailbee/repos/<prefix>.yaml; --write overrides config_edit.write_policy for one run. Structured lists (host_mounts, agents, autostart steps, …) open a drill-down screen — n new, x delete, J/K reorder, Enter to open an entry. github.token is masked and read-only in the editor; edit the local YAML directly to change it. |
jailbee version / jailbee --version |
Print the JailBee version |
Compatibility aliases.
jailbee claude ls|use|park|rm|group …still works as a hidden alias for the matchingjailbee accountcommand, andjailbee new --claude-groupfor--credential-group; both warn once per invocation. A container labelleduser.jailbee.claude_group(the pre-rename spelling) is read but never written, and is not warned about — nobody is being asked to retype it. All are removed in 2.0.0.
Remote SSH service¶
The optional SSH service exposes JailBee, not a host shell. Installation and
startup are separate opt-ins; see Installation
and remote.ssh configuration.
Service and key commands:
jb remote ssh enable
jb remote ssh disable
jb remote ssh restart
jb remote ssh status
jb remote ssh serve
jb remote ssh key add PATH
jb remote ssh key add -
jb remote ssh key add
jb remote ssh key ls
jb remote ssh key rm SHA256:FINGERPRINT
key add reads exactly one plain OpenSSH public key — from PATH, from
stdin with - (e.g. cat k.pub | jb remote ssh key add -), or, with no
argument, pasted at a prompt on a terminal (one line, no Ctrl-D needed) or
read from piped stdin when there is no terminal. It rejects options,
certificates and duplicates, preserves the key's comment, and prints
<fingerprint> <algorithm> <comment>. key ls uses the same one-line
format. key rm requires the complete fingerprint printed by add or ls.
New connections reload the key file, so adding or removing a client key needs
no service restart; an already-authenticated connection is not revoked.
status prints whether the unit is installed, enabled and active, the
configured address and port, enabled entry points, authorized-key count and
each detected problem. A deliberately absent, disabled or inactive service
and an empty key list are reported without making status fail; invalid config
or an unsafe, missing or uninspectable host key produces a nonzero exit.
serve runs the same listener in the foreground and is intended for
diagnostics. On startup it prints the listening address, the host key's
SHA256 fingerprint and a matching connect example. disable preserves global
configuration, client keys and the server host key.
serve also accepts one-off overrides of remote.ssh for experimentation,
without touching global.yaml:
jb remote ssh serve [--listen ADDR] [--port N] \
[--dashboard/--no-dashboard] [--shell/--no-shell] [--exec/--no-exec] \
[--commands disabled|allowlist|full] [--allow CMD]... \
[--restrict-host/--no-restrict-host] [--files/--no-files]
--files turns on sftp/scp into a container's repo directory for this run
(see Security → File transfer).
Every flag defaults to unset, in which case global.yaml decides as usual;
only a flag actually given overrides its field, for this run alone. The
merged result is validated by the same rules as global.yaml (for example
--shell still requires an enabled commands.mode), so an invalid
combination is reported the same way a broken config file is. --allow is
repeatable, and given at least once, REPLACES the configured
commands.allow list rather than appending to it. The systemd service's
ExecStart never passes any of these flags. For example:
jb remote ssh serve --port 18022 --shell --commands allowlist --allow ls --allow new
The SSH username is always jailbee. With the default listener port, the
accepted client forms are:
ssh -t -p 8022 jailbee@localhost dashboard
ssh -t -p 8022 jailbee@localhost shell [--repo PREFIX]
ssh -p 8022 jailbee@localhost help
ssh -p 8022 jailbee@localhost -- --repo PREFIX COMMAND [ARGS...]
The -- is for the client: OpenSSH keeps parsing its own options after the
destination while the next word starts with -, so a bare --repo fails with
unknown option -- -.
A commandless ssh -p 8022 jailbee@localhost prints the enabled entry points
and exits successfully by default. Set remote.ssh.default_entrypoint in the
host's global.yaml to dashboard or shell to open that entry point instead;
an explicit help always prints the list. All three entry points are enabled
by default, while commands.mode independently governs console and one-shot
commands across the console and dashboard; exec: false disables one-shot
execution only. commands.mode: disabled blocks dashboard actions that run
JailBee commands, but not dashboard navigation or the console's local
navigation. dashboard
and the remote console require -t; one-shot commands do not inherently require a PTY, though
an interactive JailBee command may. Server-written text (this help, and any
rejection) uses CRLF line endings whenever the client negotiated a PTY (which
OpenSSH does even for a commandless, -t-less login) and plain LF otherwise;
child JailBee command output is unaffected either way.
The console starts by asking which registered repository to use — an
arrow-key menu when more than one is registered, or straight in if there is
exactly one — unless started with shell --repo PREFIX. It offers repos,
use [PREFIX] (bare use reopens the menu), dashboard (listed only when
enabled), help, and exit, rendered as a Rich panel styled like jb
--help's own. In full mode, help then runs the real python -m jailbee
--help in the current repo root, so the console shows exactly what a local
jb --help would. In allowlist mode it instead renders a second panel,
"Allowed Jailbee commands", listing each allowed path with its own one-line
help. In disabled mode it prints a one-line note instead. Esc, Ctrl-C and
Ctrl-D all cancel the menu (exiting the console at startup, returning to the
prompt from use). Every other line is an allowed JailBee command,
tab-completed word by word (e.g. git p<Tab> offers pull/push).
A hidden alias (merge, pull, push, ...) is policy-checked against the
public command it aliases, so allow: [git merge] also permits typing
merge. A public group's own help — git, or git --help/-h — is
permitted on its own whenever some command under it is allowed (or in full
mode); the bare top-level --help/-h works the same way. Unknown commands
are rejected before a JailBee child starts. The console performs no shell
expansion, pipes, redirection or executable lookup.
Every one-shot command needs --repo PREFIX. PREFIX is an exact registered
repository prefix, never a filesystem path; the registered root becomes the
command's working directory. The same rule applies to shell --repo PREFIX.
See Security and limitations before granting access.
For SSH dashboard sessions, the effective remote.ssh policy is checked before
any action that runs a JailBee command, including !, merge, container actions,
quick keys, and creation. These actions are blocked by commands.mode: disabled;
exec controls only one-shot SSH execution. Dashboard navigation remains
available, and console-local navigation remains available while JailBee
commands are disabled. The same host-protection rule applies even when command
policy is otherwise permissive, unless restrict_host: false was explicitly
configured. For the dashboard's Egress panel, net egress ls controls read
access and net egress add/rm are checked independently; with the default
restrict_host: true, Egress is read-only even when the command policy is
full. Each new SSH channel
gets the current validated effective policy; a console or dashboard already
running keeps its startup snapshot. An upgrade adopting the enabled entry-point
defaults can expose the console and command routes on an already-enabled service
unless global.yaml disables them; review that policy and authorized keys
before upgrading.
Top-level app promotion¶
An apps: entry with top_level: true also runs as a bare jailbee <name>
[args…] — equivalent to jailbee apps run <name> [args…], just without
the apps run. Everything after the name is passed through unchanged, so
another container is named exactly the way apps run names one:
jailbee <name> --container <container> [args…], and an argument that
starts with a dash needs a -- separator
(jailbee <name> -- --some-flag). A bare jailbee <name>
<container> does not work — apps run takes the container as an
option, so the name is appended to the app's own arguments and the app
starts in the default container instead. A built-in command of the same
name always wins, and jailbee config validate reports the collision as a
config error (it is checked in validate_runtime, so only that command
reports it — jailbee doctor and ordinary commands do not) rather than
the app silently never being reachable that way. See
apps.
jailbee gui / jailbee dashboard --gui¶
The graphical (Qt) counterpart to the terminal dashboard — the same live, cross-repo container view with per-container actions via right-click / popup menus. Requires the optional PySide6 extra:
uv tool install 'jailbee[gui]' # or: pipx install 'jailbee[gui]'
(from a checkout of the JailBee repo: make install, or
uv tool install -e '.[gui]')
By default the GUI detaches to the background: the command prints a
"Launched jailbee dashboard GUI in the background" message and returns
immediately, with the window's stdout/stderr logged to /tmp/jailbee-gui.log
(useful if the window fails to appear — e.g. missing PySide6 platform
plugins). Pass --foreground to run it attached to the current terminal
instead (blocks until the window closes; errors surface directly).
Interactive actions (shell, tmux) open in a host terminal emulator. Set
$JAILBEE_TERMINAL to force a specific emulator; otherwise JailBee auto-detects one
(x-terminal-emulator, ptyxis, gnome-terminal, konsole, foot, alacritty, kitty, xterm). IDE,
Chrome and Firefox launches reuse the same jailbee ide / jailbee chrome /
jailbee firefox behaviour; the action menu also lists every other
apps: entry as a "Launch <name>" item — dispatched as
jailbee apps run <name> --container <container> regardless of
top_level, so it launches in the highlighted row's own container whether
or not the entry is promoted to a top-level command.
The shared per-container action menu also offers Merge into… when the git
bridge is available. It starts jailbee merge with the highlighted container
as its source and leaves target selection to the CLI prompt; no target is
guessed. In the terminal dashboard, the equivalent !merge command follows
the selected-container default described above. Change base branch works the
same way: it starts jailbee git retarget with the container and the CLI's
branch picker chooses the new base (terminal window in the Qt dashboard).
The commands that exist for the text they print — pr, git push, git pull,
git diff, job log — get no terminal emulator: they run inside the GUI and
stream their output into a JailBee window with Stop and Copy buttons and
the exit code on its status line. The window is non-modal, so the dashboard
keeps refreshing behind it and several commands can be watched at once. Stop
matters for job log on a live job, which follows the worker's log until it is
stopped.
Because that child process has no stdin, whatever the CLI would prompt for is
collected in a dialog first and passed as a flag — and only where the CLI would
ask: git push's merge/rebase/plain choice when the repo's
push.default_action is ask, its source when push.default_source is ask
(see Configuration), pr's draft/ready, description and
existing-PR-head choices, and a confirmation for git pull, which merges into
the host's own branch. Cancelling a dialog dispatches nothing.
Options mirror the TUI: --interval, --git-interval, --no-git, plus
--foreground (GUI-only).
Layouts: Table vs Cards¶
A View menu switches the container list between two layouts:
- Table — the original wide, sortable columns view.
- Cards — a width-adaptive card grid; cards re-wrap to fill the window (one column when narrow, several side by side when wide).
Both layouts share the same right-click action menu and selection behaviour. Fresh installs (no prior GUI session) open in Cards.
Persisted state¶
Between sessions the GUI remembers, per-machine, in the same SQLite state DB
used for other JailBee state (state.sqlite):
- the selected layout (Table or Cards),
- the table layout's column widths and order,
- the refresh cadence and whether auto-refresh is paused (set via the Refresh menu).
Not persisted: window size and position — that's left to the window manager.
--interval precedence when the GUI starts: an explicit --interval/-i
flag wins, otherwise the persisted refresh cadence from the last session is
used, otherwise the default of 3s. --git-interval is never persisted — pass
it each time it should differ from the default.
For full flag-level detail without
jailbee <cmd> --help, the jailbee-usage skill (docs/skills/jailbee-usage/) and itsreferences/commands.mddocument every command and option.