Commands¶
The terminal dashboard's container details panel (v) reads top to bottom. One
summary line says what the container is doing: its state and any background
job, IP, PR and issues, the agent, and busy processes. A running container with
a fixed limits.memory also shows its memory use as an htop-style bar
([mem||| 3.0G/8.0G]) at the right end of that line, coloured by the free
share like the system status row; it is left out when the line has no room for it.
A running container adds
up to two lines under it, labelled cpu and mem (the labels are dim), its top three processes by CPU
and by resident memory, grouped by process name with xN when several share a
name. When present, a separate
highlighted line below it lists exceptions: loose networking and its deadline,
attached optional mounts and their deadlines, and container-level egress overrides.
Then a git table lists the container's own tree and one row per
changed submodule, with commits ahead (↑) and behind (↓), the committed diff
against the base, the working-tree diff, and the merge prediction (plus the
diff against the host's checked-out HEAD when the note column has room). A dim
footer line holds what no column shows: a memory-limit override and, for a fork,
its source; it is left out when there is nothing to say (mode and credential group
are table columns).
Unchanged submodules are omitted; ? means the probe could not determine a
value. Nothing is cut: a line too long for the panel continues on the next
line, and a card taller than the room it gets shows a window of its content
with a scrollbar on its right edge. Scroll it with the wheel over the card,
PgUp/PgDn, or a click on the scrollbar; the position resets when the
selection moves. A repo card scrolls the same way through its notices.
The panel's height is yours to set: +/- make it a row taller or shorter and =
returns it to the built-in height; with the mouse on, drag the panel's top border.
The table always keeps five rows and its header, and the panel at least two rows.
The height is remembered with the rest of the view.
In-container outbox commands¶
Inside a non-child container, stage GitHub proposals with jailbee issue
create|edit|comment|labels|assign|state and jailbee pr
comment|reply|description|source. jailbee issue assign ISSUE|@REF takes --me
(the publishing account, @me), --unassign-me, and repeatable --add LOGIN /
--remove LOGIN; at least one is required. Use --body or --body-file; issue
commands accept --repo (default .), while PR source and new descriptions
accept --repo owner/name. jailbee pr source --ref REF --sha SHA also
accepts --head as an alias for --ref; new PRs need a description body.
An omitted PR selects exactly one actual container binding (primary, stacked,
imported-head or submodule), independent of the checkout branch or detached HEAD.
With multiple bindings, name the PR number. Host creation, start and PR bind/unbind
operations refresh a minimal read-only snapshot at
/opt/jailbee/metadata/bindings.json; existing containers acquire it on jb start
without rebuilding the golden image. An unavailable snapshot never falls back to
gh pr view on the current branch. A numbered command without a matching binding
uses the current repository for its explicit number.
jailbee pr description [N] updates the bound existing PR by default. It requires
an explicit replacement body via --body or --body-file, including when only
the title is intended to change. Title-only staging is refused: automatically
copying the current body could overwrite later edits when the proposal is
published. An explicitly empty body remains allowed. With no bindings it stages a new PR;
--new explicitly requests a new description only when the binding snapshot is
readable and empty. Any binding, including one for another repository, refuses
--new. New repository discovery uses read-only gh repo view without requiring
an existing PR; --repo owner/name overrides it. --new writes pr: null: it is
a proposal, not a host-side force-create switch. The host can resolve a null PR
to an existing binding, so --new must not bypass bindings.
Both outboxes cap pending manifests at 20; issue batches cap actions at 100.
Use --batch NAME to collect actions, jailbee outbox ls [--json] and
jailbee outbox show issue/NAME or pr/NAME to inspect staged proposals,
jailbee outbox submit NAME --kind issue|pr to submit a batch, and
jailbee outbox discard NAME --kind issue|pr to discard a draft. Drop a
staged proposal with jailbee outbox drop issue/NAME or pr/NAME. For
issue batches, create with --ref REF and address it later as @REF;
expected values come from that earlier create. Existing issue changes fetch
current state for host staleness checks.
Inline reviews use jailbee pr review add N --file PATH --line LINE --body
TEXT followed by jailbee pr review submit N; generic outbox submit refuses
review drafts. Review add and submit fetch the PR head and refuse if it differs
from the first comment's anchor; submit never replaces that anchor. Discard with
jailbee pr review discard N. None of these commands publish to GitHub; a
human inspects and applies on the host. Do not write outbox files or create
outbox directories by hand. Children cannot stage GitHub writes: send the
orchestrator exact content using jailbee inbox send message --text "...",
or --kind question when a result is needed.
Inside a container¶
Since 1.8.1 every container has a small jailbee (the host's code, mounted
read-only at /opt/jailbee/app). Only these commands work there; everything else
is host-only:
jailbee --versionjailbee config validate(configuration)jailbee service ...(Services)jailbee inbox ...(orchestration)jailbee issue ...,jailbee pr ...,jailbee outbox ...(outbox commands)
The terminal dashboard container menu offers Autostart → (u): Re-run… (r), Status (T), Cancel… (C), and Profile… (p) when applicable. The re-run picker puts failures first and asks for confirmation; failed or interrupted outcomes also mark the container row. Services… (V) on running containers lists services and offers start or stop/restart as appropriate, refresh and logs; an empty list is reported. Entries honor remote SSH policy; elevated service refresh may require the host. These new menus are terminal-only; Qt is unchanged.
Services¶
Inside a container use jailbee service [ls] [--json], jailbee service
start|stop|restart|refresh [SERVICE...], or jailbee service logs [SERVICE]
[-n N] [--follow]. Group option --repo PATH selects a repository path;
otherwise discovery starts at the current directory. jailbee service plan
[SERVICE...] --op start|restart|refresh --json exposes the execution plan.
On the host use jb service ls [CONTAINER] [--json], jb service
start|stop|restart|refresh [CONTAINER] [SERVICE...], or jb service logs
[CONTAINER] [SERVICE] [-n N] [--follow]. Omitted values use a terminal
picker, otherwise the command reports candidates. Definitions always come from
the container worktree, not the host checkout.
Automatic startup uses selected services (new --services, profile services,
or orchestration spawn/restart --services). The repo can position startup with
reserved autostart.on_start entries {stage: services}, optionally with
services: [names]; these do not broaden the selection. Slots after a detach
boundary run detached. See placement and fallback rules.
Start includes dependencies and waits for readiness; stop does not stop
dependents. Restart cascades only over restart: true edges. Refresh prepares
only named services before restarting them and their cascaded dependents;
start and restart never prepare. An elevated container refresh exits 3: ask the
orchestrator or run jb service refresh CONTAINER SERVICE... on the host.
Elevated refresh pauses an idle managed agent and resumes its session after
restoration, including after an ordinary prepare failure. --force skips only
the idle check; use it only when work has stopped. Host --yes consents to
foreign agents remaining active during elevation, not to interrupting work.
Restricted SSH cannot run service refresh, even with network and mount unlocks;
run it on the host. Logs use tmux scrollback, not durable or
byte-perfect capture: a line matching the exact tmux control-protocol snapshot
trailer can truncate the snapshot or end following.
Runtime arguments¶
jailbee service start and restart accept repeatable --env KEY=VALUE
and arguments after --, for exactly one explicitly named service. On the
host, put the container name first. Arguments require services.<name>.args:
true and are passed to run as positional parameters. For example, use
run: './scripts/dev/restart "$@" && exec tail -f /dev/null'.
Overrides are one-off by default. --persist saves supplied arguments and
merges supplied environment values into the persisted defaults. A plain
restart uses those defaults again; --reset-args clears persisted arguments
and environment before applying other options. Status displays environment
keys, not values; avoid putting secrets in positional arguments.
Status and log queries¶
A service may define a status shell command and status_timeout (default 5
seconds, minimum 1). service ls reports its result without changing lifecycle
state: JSON-object output is shown as status plus a short detail, other output
uses the first nonempty line, and failures/timeouts report status: error or
status: timed out. Status probes are cached briefly.
service logs reads tmux scrollback, not durable logs. -n N defaults to the
last 30 lines. Repeat --grep PATTERN or --filter NAME to select matching
lines (the union of matches); filters are defined in services.<name>.logs.filters.
Queries scan all retained history, then -n limits the last N matches before
context expansion. -B N, -A N, or -C N include surrounding lines;
overlapping or adjacent ranges merge, and gaps are separated by --.
--wait PATTERN waits for new complete output and accepts -B, -A, or -C
context; it cannot be combined with grep, filters, or follow. Its boundary is
successful control-client attachment to the tmux output stream, not command
invocation, and short setup output before attachment may be missed. For a
one-shot line, start the wait in the background before triggering the action;
starting a background process alone does not guarantee it has attached. If the
action already ran, query retained history instead, for example
jailbee service logs api --grep 'ready' -n 5 -A 40. Context before a wait match
may omit a short setup gap. Output stops after two seconds of silence.
--timeout is valid only with --wait, defaults to 300 seconds, and 0 means
unlimited. Exit codes are 0 for a match, 1 for timeout, 2 for invalid input,
and 3 if the pane becomes unusable before a buffered hit. These queries do not
change service state. Each services.<name>.logs.history_limit contributes to
the session setting; JailBee uses the maximum across configured services
(default 50000 lines, minimum 1000) for windows created later, including future
autostart windows. It does not resize existing panes or create durable logs.
Health and waiting¶
A live, ready service is unhealthy when its health command fails or times
out. If health is unset, ready is used; with neither there is no health
probe. health_timeout defaults to 5 seconds. Status reads cache probes for
five seconds; JailBee never automatically restarts an unhealthy service.
Use service ls and service logs to diagnose it, then restart it explicitly.
Inside a container, jailbee service wait api --until ready waits for a state;
on the host use jb service wait CONTAINER api --until ready. States are
stopped, starting, ready, failed, exited, and unhealthy. Waiting
for unhealthy also matches stopped, failed, or exited; with multiple services
the first matching service wins. --json prints the matching status.
--interval defaults to 10 seconds (minimum 1); --timeout defaults to 0
(no limit). Exit codes are 0 for a match, 1 for timeout, 2 for invalid input,
and 3 when waiting for ready encounters stopped, failed, or exited. Each tick
performs a synchronous fresh status read before checking the deadline, so an
in-flight scan can exceed the overall timeout. Lock acquisition and the probe
itself are each bounded separately by health_timeout, so a scan may include
both waits.
Global repository option¶
--repo PREFIX runs a command in that registered repository instead of the
current directory's repo. PREFIX is an exact registered prefix, not a path.
Without it, local commands keep their usual cwd / scratch behaviour.
It may precede the command (jb --repo x chrome feat), appear inside its
command path (jb net --repo x egress add pypi.org feat), or follow its
arguments (jb chrome feat --repo x). Only one global --repo is accepted;
arguments after -- are passed through unchanged.
Commands with their own --repo keep its existing meaning when written after
the command name: net egress add / rm, net refresh, net unregister
and console. Put the global selector before that name (or before the group),
for example jb --repo x net egress add pypi.org --repo.
The global selector cannot be combined with -c / --config.
Run these management commands on the host (or through the configured remote entry point), not inside a development container. Container arguments accept short container names, full prefixed names or aliases. Brackets in the syntax mark optional arguments: required values left out are resolved through a terminal prompt or, where safe, a single candidate; otherwise the command exits with a message. Listing commands generally treat an omitted target as "all" rather than asking.
| 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). Prompts for needed container and dockerd restarts; -y accepts them, while --no-restart defers them. Restarting dockerd can stop Docker workloads; stacks without a restart policy may need restarting manually. With LiteLLM running, also re-renders the proxy: routes, profiles and API keys reload live when possible. Cold settings changes or an unconfirmed reload can trigger an automatic instance restart, without a separate prompt, unless --no-restart is given. That flag still permits live reloads and warns about deferred restarts; an unconfirmed reload may already have applied new routes, so it is not a guarantee of retaining the old ones. A subsequent plain apply retries convergence. If the proxy needs jailbee litellm up instead, it says so. See LiteLLM |
jailbee upgrade [--force] [--dry-run] |
After upgrading jailbee: run base build and apply in every registered repo, restarting no container, dockerd or LiteLLM instance (apply still reports which need it). By default a repo runs only what the release's upgrade notes owe it; --force runs both everywhere (use it when a release may have forgotten its note); --dry-run lists what would run and why. jb dismiss does not exempt a repo. Repos sharing a base image (scratch) build it once; a repo whose build fails skips its apply, and the sweep goes on — exit 1 if anything failed. |
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 a container and run autostart. In the default clone mode, <name> is the Git branch and is slugified for the container name. --mount instead shares the host repo read-write, without cloning or creating a branch: <name> names only the container, and edits directly affect the host's working tree. <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. A <name> left out is asked for on a terminal (exit 2 off one). --current, --pr <N>, --mount, --background, --tmux, --shell, --attach shell\|tmux\|none, --no-clone, --no-autostart, --autostart-profile <name> (remember a named stage selection, else the configured default), --services <names> (comma-separated services plus dependencies; --services '' starts none; overrides profile services), --yes, --name <container>, --net strict\|loose, --memory <size>, --cpu <n>, --storage <pool> (Incus storage pool for this container, else defaults.storage_pool), --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), --prompt FILE\|- (start the primary agent with this initial prompt; - reads all of stdin, so pass -y for an existing branch; conflicts with --no-autostart, exit 2). --memory is an explicit per-container override; when omitted, the container inherits the repo's defaults.memory. See jailbee new --help for what each does |
jailbee ls [--all] [-o json] [--fields …] [--wide/-w] |
List managed containers + their git status (own repo by default; --all for every repo). On terminals, default tables drop low-priority columns to fit the width; --wide / -w, explicit --fields or configured ls.fields disables fitting. Piped output keeps every column. 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. Asked which job when <name> is omitted |
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; the Outbox box has a Sent tab (Tab switches; o opens the selected URL in the desktop browser, or shows a notice over remote SSH; v previews) and the repo-header menu a Sent… timeline (key s) of everything published for the repo, with † marking destroyed or earlier incarnations; 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 →, plus Extend loose… on a loose container (a new TTL counted from now, as re-running jailbee net loose does); 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. A change that succeeds — an egress add or remove, an account change — closes the panel and the menu it was opened from; a failed or refused one keeps the panel open for a retry. 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": type the branch inline — Tab completes from the host's other branches, ↑/↓ pick one, a name that is not a host branch is refused), git pull, git diff, job log — each shown only when it would do something. The menu leads with Attach tmux, Launch → and Outbox — Outbox (N pending) moves to the very top while the PR or issue outbox holds staged manifests, and the entry is left out while both are empty — then Git →, PR → (including "Create/update submodule PR…" when the container has submodule changes), Lifecycle → (restart, stop, destroy) and Network →; branch and PR creation end with an Autostart choice of none, all, configured profiles or Custom services…. The default create choice adds no profile argument. Restart with… opens the same choice for a running or stopped container before rebooting it; choosing any profile, including the default, stores that profile and resets explicit services so the profile's service selection applies. Custom services… preserves the stored stage profile on restart and uses default stages on create. The separate Profile… action in Autostart → switches the live profile additively: no reboot, no reset of explicit services and no stopping existing processes; on a stopped container it only stores the profile. Start, restart, stop and destroy run detached without leaving the dashboard (⟳ on the row while the command runs; start, restart and destroy go through their --background workers), and while a stop or a boot is in progress the state cell shows a yellow ■ or ▶ and the JOB column the phase; [f] Fork… (a running clone-mode container: asks for the new container's name inline and runs jailbee fork in the background) and [R] Rename… (running or stopped: edits the alias inline, starting from the current one; an empty answer clears it) sit at the same level. Every menu entry shows its own key in brackets ([g] Git →), scoped to the level that is open: pressing it is Enter on that entry, so g u updates from base and l D destroys. The same letters work in the table without Enter — g on a highlighted container opens Git → — so the table keeps only the keys no menu has. Every entry has a fixed letter, so a key never moves when another entry comes or goes; only Launch → apps take a free letter of their label. ! 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. The line and every question edit like a text field (←/→, Home/End, Ctrl-A/E/W/U/K). Keys typed ahead — faster than the screen redraws, as over a slow SSH link — act in order, each on what the key before it opened. 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. The container menu's Memory… action opens the same preset/custom-size picker; the Qt dashboard opens the command in a terminal. An asterisk marks an instance override in the displayed limit (for example, 24GiB*). Dashboard details and the Qt memory chip identify an instance override. An orchestrator (a container holding a grant) has ⎈ before its name. Fitted column widths never make the NAME column wider than 20 cells (tree indent and ⎈ included); a longer name ends in …, and a failed-autostart ⚠ stays visible after the cut. The details panel's title shows the whole label uncut (⎈, alias and name). A stopped container's row is drawn dim. Orchestration children are listed directly under their orchestrator, indented with └, whatever the sort; a child whose orchestrator is not listed is marked ⑂. Plain forks (jailbee fork) are not indented; the details panel names their source. Dashboard keys: F2/S settings overlay (Fields, Repos and Visibility tabs: columns, folding, which repos show; on Fields, Shift+↑/Shift+↓ moves an enabled column — never across name, which is always first and cannot be turned off; newly enabled columns are appended), </> sort the rows by the previous/next shown column (with a "default order" stop: newest first), I reverses the sort, and a click on a column header sorts by it (the first click uses the column's first direction — most numeric columns (created (AGE), cpu, memory, git diffs and counts) largest first, text columns, pr, ttl and loose_until ascending, and state, network, job and agent by rank rather than alphabetically — running first, loose first, live jobs first, the agent most in need of you first — a second click flips); the sort column's header carries ▲/▼, rows with no value go last in either direction, and rows sort inside each repo group (groups never move); a sort column that is not enabled, or not shown because every value in it is empty, sorts by the default order (the sort is kept and applies again when the column is shown); sort and column order are remembered per front-end. O optimize the column widths once from unfolded visible data (widths are also fitted on opening and once more on the first refresh; settings changes restore the default widths; session-only; retained on refresh and resize; press again to recompute — which selected columns show is recomputed on every refresh: a column with content appears, an all-empty one goes), ←/→ scroll the columns sideways when they do not fit the terminal: the first column stays put, a dim ‹/› marks columns out of view, and a column cut by the edge ends in … (session-only; reset by O and settings changes), F5/Ctrl-R force a full refresh, M turns mouse support (clicks, wheel, hover) off or on for the session (dashboard.mouse sets the default), with the mouse on, a click selects, Ctrl+click marks, a double- or right-click opens the menu, ▾/▸ and ‹/› are clickable, and Shift-drag still selects text in most terminals; 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, the base with the host's branches as suggestions, then runs jailbee new in the terminal). Space on a container toggles its mark without moving the cursor, on a repo header it folds/unfolds (in the settings overlay it toggles the setting); Shift+↑/↓ marks while moving, Ctrl+click marks a row (where the terminal passes Ctrl+click through; a Ctrl double-click is two toggles), Esc clears the marks. Marked rows show ● and the frame counts them; a folded repo shows how many of its containers are marked. While anything is marked, Enter on a container opens an N selected menu of what at least one of them can take — merge, git push/pull, start, restart, stop, network, clear name, destroy — and the N selected menu's own letters act on the marks (D destroy, u git push, …; they work in the table without Enter too). Start, stop, restart, network, clear name and destroy run in parallel without leaving the dashboard (⟳ while each runs, one notice at the end; what succeeded is unmarked, what failed or was skipped stays marked); destroy asks once ("No" first), the title naming every container and a risk line for each one that has something to lose. Marked orphan rows (terminal dashboard only) offer Destroy orphans… (O) instead, which runs jailbee orphan destroy --force per container after the same single question. Every other bulk verb over two or more containers asks once as well — "No" first, the title naming the containers, a line for each one it will skip; merge is the exception, whose CLI asks after the targets are picked, naming both ends. The terminal and Qt dashboards ask the same question, and the git push/pull runs they start carry --no-confirm; the merge runs carry --confirm. The TTL picked for a bulk net loose is passed as --for to every marked container, including those of repos configured not to revert. In the terminal dashboard the N selected menu also carries Mounts… (o): one entry per optional_mounts kind and direction any marked container takes, with counts — mount (N) (attached containers count under renew TTL, since jailbee mount on an attached kind only sets a new TTL) and unmount (N); the chosen one runs in parallel like start or stop, asks once over two or more containers, then asks the TTL once and passes it as --for to each. Git push and git pull run once per repo in the terminal through the CLI's own multi-target form, so its questions (push source and action) are asked once; merge picks its targets in the table (see Merge into… below). The Qt table view selects several rows (Ctrl/Shift+click) and offers the same N selected menu on right-click; it acts on the right-clicked row alone when that row is not part of the selection, whereas in the terminal dashboard, with marks present, Enter or right-click on any container row opens the N selected menu for the marks, even if the cursor row is unmarked. v shows/hides the details panel under the table — everything known about the highlighted container (or repo), with the action menu drawn to its right while open; the choice persists. +/- make the panel taller/shorter, = resets its height, and dragging its top border does the same with the mouse; the height persists too. The dashboard fills the terminal: the table takes the rows it needs, and the details panel grows from the bottom into what is left — for a busy agent, its last message in full and its recent tool calls and messages, newest first. Spare width lets the JOB column show text it would otherwise cut. A table taller than the screen scrolls with the cursor and shows a scrollbar; the wheel scrolls it without moving the selection. 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). While a repo owes jailbee base build or jailbee apply after an upgrade (the advice jailbee ls prints on stderr), its header shows ⚠ and its card lists the notice with clickable [run jb apply] / [run jb base build] and [dismiss] links, plus [run jb upgrade (all repos)] when two or more repos owe something; the repo menu mirrors them as [b] Run base build, [i] Dismiss notice… (same effect as jailbee dismiss) and [U] Upgrade all repos…, each shown only while it applies. Deprecation notices scoped to the repo's config (such as a retired key) appear on the same card with [dismiss] only, no run link. Over remote SSH the run links and menu entries follow the session's policy, and [dismiss] / Dismiss notice… are hidden. The container menu adds Outbox (the Outbox box: the container's staged PR and issue proposals as a tree beside a preview — Space marks a row and everything under it, d deletes the marked proposals, actions or comments after one "No"-first question, p publishes the marked proposals (or the one under the cursor) after one question showing each publication plan, c creates the PR a description awaits (pr or submodule pr in the terminal), X clears the outbox, v/m toggle the preview and the raw manifest (on a narrow terminal Enter on a row with nothing to expand shows the preview in the list's place, and Esc returns to the list), r refreshes, Esc goes back; see the git bridge), Snapshots… (create with a timestamp or a typed tag, restore or delete after a confirmation), Mounts… for the repo's optional_mounts (one panel listing every configured kind: choosing an unattached kind mounts it, an attached one unmounts it), Orchestration… (G; grant by profile or with defaults (the menu stays open after granting so the new grant can be edited in place), edit the limit, operations, mounts, loose and egress in place, edit the publish rights — a Publish issues… sub-menu of per-kind toggles, foreign issue rights typed as owner/name, …: kind, … (empty clears), PR and PR-review toggles and the hourly rate; over remote SSH a right is offered for adding only when the session could publish it by hand, while turning one off is always offered — re-apply a profile, save the grant as a named profile, adopt an existing container as a child or release one, or revoke after a confirmation — hidden for child containers and mount-mode containers, and over remote SSH when the session's policy forbids orchestrate, except on a container that already holds a grant while revoke is permitted, where permitted revoke and release actions remain available), and an Autostart → submenu (u) with available re-run, status, cancel and profile actions. Services… (V) lists managed services and their start, stop, restart, refresh and log actions. 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. A one-row system status bar at the top shows host memory, storage pool(s) and the root filesystem (/); free space below 15 % shows yellow, below 5 % red. Hide it with the Show system status row, the first row of the Visibility tab in the settings overlay (S, then Space); it is on by default and the choice is persisted. On a narrow terminal the bar shrinks and finally drops its graphical bars. |
jailbee console [--repo PREFIX] |
Interactive jb[<prefix>]> prompt for running JailBee commands with tab completion and history, on your own terminal and without the remote policy. See Console |
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. <name> comes first, so a command must follow the container name (jailbee exec smoke -- pnpm test); a container or command left out is asked for on a terminal (exit 2 off one). (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 an SSH session with remote.ssh.rdp on 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/restart [<name>] [--wait\|--no-wait] [--no-autostart] [--background\|--no-background] |
Start or restart a container and re-run autostart unless --no-autostart is given. Only restart also accepts --autostart-profile <name> (store a profile for this and later boots), --services <names> (explicit comma-separated services; '' selects none), and --reset-services (clear remembered explicit services so the profile applies). A profile flag alone preserves explicit services; combine it with --reset-services to use profile services. The none profile selects no configured stages or profile services, while retaining the generated agents stage; an explicit service selection still takes precedence. --services and --reset-services cannot be combined. --no-autostart skips the run entirely, including agents. Boot backgrounding follows boot.background; --background/-b and --no-background override it. Separately, --wait waits for every autostart stage in the foreground command or background boot worker instead of handing off at a stage boundary; --no-wait hands off after the first stage regardless of config. restart refuses while detached autostart is in flight; cancel it first with jailbee autostart cancel. Both commands refuse while a foreground autostart run --wait holds the rerun lock. |
jailbee stop [<name>] [--force] |
Stop a container. Refuses while detached autostart is in flight unless --force is given. --force also forces the container off instead of shutting it down cleanly, even without an autostart run; unsaved work can be lost. |
jailbee mem [<name>] [<size>] [--force] |
Set a container's memory limit as a per-container override, or pass default to clear the override and follow defaults.memory. With no size, opens the preset/custom-size picker. A fixed-size limit below current use warns and asks for confirmation on a terminal; off-terminal pass --force or the command exits 2. Percentage limits (1%–100%) are accepted but cannot be checked against byte-based current usage. |
jailbee fork [<source>] [<name>] [--branch <b>] [opts] |
Create a container from another container's committed state: the new clone starts at <source>'s HEAD commit (fetched to the host as refs/jailbee/<source>/<branch> and pinned under the new container's own name as refs/jailbee/<name>/HEAD, which jailbee destroy <name> removes — so the fork outlives <source>), on a branch named after <name> unless --branch names another — submodules follow the same branch. Only commits carry over by default; fork.warm_paths optionally seeds Git-ignored build outputs by btrfs reflink (HEAD forks only; other pools skip with a note). Repeatable --warm PATH replaces that list, and --no-warm disables it; the two are mutually exclusive. <source> must be running, in clone mode, and have no uncommitted changes (tracked or untracked); otherwise exit 2. The new container remembers <source> as its upstream (user.jailbee.fork_of): its BASE column keeps showing the base branch, the dashboards name <source> in the container's details (⑂ forked from <source>), and jailbee merge offers <source> first. Takes jailbee new's --net, --memory, --cpu, --storage, --credential-group, --yes, --background/--no-background, --attach/--no-attach/--tmux/--shell and --no-autostart, plus --prompt FILE\|- (initial prompt for the primary agent). --memory is explicit-only; without it the fork follows defaults.memory rather than inheriting the source container's override. A source or name left out is asked for on a terminal (exit 2 off one) |
jailbee rename [<name>] [<alias>] [--clear] |
Give a container an alias instantly, running or not. Listings and the dashboard show the alias (jailbee ls --format json as an alias key, null when unset, beside name, which stays the short name) and every command accepts it; the real name (and everything keyed on it) never changes. --clear, Ctrl-R in the interactive alias prompt (offered when an alias exists), or an alias equal to the container's own short name, removes it. An alias must be letters, digits and -; it is shown as typed but matched without regard to case, and cannot collide with another container's name or alias in any case |
jailbee autostart profile [<container>] [<profile>] [--no-run] |
Show current/default profiles and attempted on_create stages, or store a new profile. A running container starts missing setup and newly selected on_start stages immediately; a lighter profile stops nothing. Stopped containers and --no-run only store. Refuses during a live detached run. With both values omitted, pick the container first, then the profile. See Profiles. |
jailbee autostart run [<name>] [--stage NAME] [--step STAGE/STEP] [--failed] [--trigger on_create\|on_start] [--wait] |
Re-run selected ordinary stages or steps in a running container. Repeat selectors to combine them; without selectors choose interactively. --failed adds failed or interrupted steps. --list shows eligible stages; --list --json includes their steps and outcomes. See Re-running autostart. |
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\|--stopped] [--force] [--background] |
Destroy one container, --all for the whole repo, --stopped for only the repo's stopped containers (exit 0 with a message when there are none; mutually exclusive with a name and --all), or no-arg interactive checkbox (off a terminal: exit 2 listing the candidates). Before asking, JailBee assesses what would be lost (dirty tree, changed submodule, commits held nowhere else) and prints a line for each container whose work is at risk or cannot be assessed above the one confirmation, which defaults to No; --force skips the confirmation and the assessment |
jailbee orphan ls\|tmux\|shell\|start\|stop\|destroy [<name>] [--force] |
Handle containers whose repo can no longer be found (directory deleted or moved, or its config no longer loads) — the rows the dashboard files under (orphan). They need no repo, so the name is the full container name (omitted: picked on a terminal; destroy always asks, even for one). tmux/shell (--user dev\|root) work on a running orphan and take the repo directory from the container's user.jailbee.repo_dir; start/stop change its state. destroy does only the cleanup that needs no repo — stop, delete, the container's egress ACL, its background-job row — and leaves refs/jailbee/* transfer refs in the lost repo; it asks unless --force. A name that belongs to a repo that does load is refused (exit 1): use the ordinary command there. Host command over remote SSH |
jailbee git fetch [<name>] [-b <branch>] [--as <name>] [--force] [--tags\|--follow-tags\|--no-tags] |
Fetch into refs/jailbee/<short>/<branch>, transport submodule objects, then create or fast-forward the corresponding host and submodule branches. No branch switch occurs, but a target branch already checked out on the host is advanced with an ff-only merge, which can update its index and 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). If the target branch is not already checked out, switch to it 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. Several names are pulled in order, stopping at the first failure. 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) when merging in the checked-out target. A different target can advance without checkout only by a ref-level fast-forward, even with --no-ff; divergence requires --checkout, which leaves the host on that target (the last successful target in a multi-container run). Cleanup defaults to prompting on a terminal and skipping off one. --cleanup or an always cleanup policy can destroy the container and delete the fully merged host source branch without prompts or a work-loss assessment; uncommitted container work can be lost. --no-cleanup prevents both; a no-op pull skips cleanup. --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 terminal asks for it — type the name, Tab completes from the host's other local branches. Once the container is resolved, a missing base off a terminal exits 1; a missing container follows the usual resolver's exit-2 policy. Both dashboards offer it as "Change base branch". --merge merges the new base after changing the label; if that merge fails, the new base remains set and the command exits 1. It 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. Several names are pushed to in order with source and action resolved once; --pr and --force take exactly one (exit 2 otherwise). By default the host fetches and pushes origin/<source>, not the local branch (push.push_from / push.autofetch); this does not advance the host's local refs/heads/<source>. Inside the container, it also tries to create or fast-forward the same-named local branch, but skips it if checked out or diverged; merge/rebase/force actions handle the working branch separately, while --plain leaves it unchanged. --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 (off a terminal: exit 2 listing the candidates). Without --merge/--rebase/--plain the merge/rebase/plain choice follows push.default_action, which is ask: a prompt on a TTY, an exit-2 error off one. Both dashboards offer it as "Refresh from PR head" |
jailbee git merge [<source>…] [--into <target>]… [-b <branch>] [--plain] [--ff\|--no-ff] [--confirm] |
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". --ff/--no-ff pick the merge mode: by default a fast-forward when the target is on the source's branch and a merge commit otherwise, with a prompt when that fast-forward is impossible. --confirm asks once on a TTY before merging several sources or into several targets (Merge c1, c2 into c3?, default No) — the dashboards pass it after picking the targets; on the command line nothing is asked. Alias: jailbee merge |
jailbee pr bind [<name>] [<number>] / jailbee pr unbind [<name>] |
Bind an existing PR without pushing, or remove its binding. |
jailbee pr ls |
List PR bindings, including source branch map rows. |
jailbee pr unbind [<name>] --head <branch> |
Forget one source branch binding without removing the container's ordinary PR binding. |
jailbee pr push [<name>] --source-manifest <file> |
Publish the pinned source ref and SHA from a PR outbox manifest, independently of the container's own PR. Existing PRs use their actual head and base; conflicting branch overrides are refused. --force permits non-fast-forward updates. Pending sources show awaiting-push and create_scope; outbox apply exits 2 with a push hint, while c in the outbox starts PR push. A published source awaiting cleanup may remain applied; retry the same push. See the source manifest examples. |
jailbee pr open [<name>] |
Open the primary PR in a browser; --stacked opens the stacked PR instead. |
jailbee submodule pr bind [<name>] [<path>] [<number>] / jailbee submodule pr unbind [<name>] [<path>] |
Bind or unbind a submodule PR without pushing. |
jailbee submodule pr open [<name>] [<path>] |
Open a recorded submodule PR in a browser. |
jailbee pr push [<name>] [--ready\|--draft] [--description] [--title <t>] [--body <t>] [--base <b>] [-b <branch>] [--as <name>] [--pr N] [--stacked] [--retarget\|--no-retarget] [--force] [--web] [--ai\|--no-ai] [--no-outbox] [--no-submodules] [--yes] |
Create a draft PR or update an existing one. Before the superproject push, jailbee pr push offers submodules with commits ahead of their own base, a recorded submodule PR, or a pending outbox description for their repo. On a TTY, a checklist starts with all candidates ticked; --yes takes all. Off a TTY without --yes, they are skipped with a warning. --no-submodules opts out. --ai/--no-ai, --no-outbox and --ready/--draft apply to the submodule PRs too; other flags apply only to the superproject. A failed submodule does not stop the superproject PR, but the run exits 1. Submodule outbox partial failures also exit 1, keeping the published PR's recorded identity so a retry updates it rather than opening another PR. Both jailbee pr push and jailbee submodule pr push refresh cross-links: a <!-- jailbee:submodule-prs --> block in an authored superproject PR and a Part of … block in authored submodule PRs. Only the managed blocks change; an unchanged block causes no body edit. Foreign PR bodies are left untouched. Merge the submodule PRs first, so the superproject's gitlink points at merged commits; the merge order is advice, not a gate. 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. --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. 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 jailbee pr open --stacked opens it instead of 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. On the host, switching to a branch without a submodule removes its files but keeps its gitdir for a later return; ignored files are preserved and listed, and the switch is refused before changes if it has uncommitted or untracked changes. jailbee submodule checkout is a hidden deprecated alias |
jailbee submodule pr push [<name>] [<path>] [--title <t>] [--body <t>] [--base <b>] [--ready\|--draft] [--description] [--as <name>] [--pr N] [--force] [--web] [--ai\|--no-ai] [--no-outbox] [-b <branch>] [--yes] |
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 push (one PR per run; it can also run separately). 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. jailbee submodule pr open reads the recorded PR and opens it, requiring <path> when more than one is recorded. Both jailbee pr push and jailbee submodule pr push refresh cross-links: a <!-- jailbee:submodule-prs --> block in an authored superproject PR and a Part of … block in authored submodule PRs. Only the managed blocks change; an unchanged block causes no body edit. Foreign PR bodies are left untouched. Merge the submodule PRs first, so the superproject's gitlink points at merged commits; the merge order is advice, not a gate. Outbox partial failures exit 1 while keeping the published PR identity. --pr N targets the submodule's existing PR N, resolved against the submodule's own repo and remote |
jailbee outbox [<container>] / outbox browse [<container>] [--all-repos] |
The full-screen outbox manager: this repo's containers (or the named one; --all-repos, or a in the app, every registered repo's) with their PR and issue proposals as a tree beside a preview — Space mark a row and everything under it, d delete the marked, p publish the marked proposals (or the one under the cursor) after one question showing each plan, c create PR, X clear, v/m preview and raw manifest, r refresh, q/Esc quit. Off-TTY prints the overview table instead. Use outbox browse ls for a container named ls; a container and --all-repos together are exit 2. Listing is local; over SSH the manager is read-only (use explicit outbox drop / outbox apply). 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] [--color\|--no-color] |
Inspect pr/<manifest>.json or issue/<manifest>.json, full text, revision, receipts and zero-based action/comment indices. Markdown bodies are rendered on a terminal and verbatim in a pipe; --color renders them into a pipe too (for a pager), --no-color never. A container or proposal left out is asked for on a terminal |
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 clear [<container>...] [--dry-run] [-y\|--yes] |
Delete eligible unpublished PR and issue proposals locally in one or more containers, never publish them. Shows delete/keep reasons; --dry-run only previews, --yes skips only deletion confirmation. Omit names for a destructive single-container picker (even with one candidate; exit 2 off-terminal). Protected or changed proposals stay; exit 1 if anything is kept or fails, except cancelling the confirmation deletes nothing and exits 0. See clearing outboxes |
jailbee outbox apply [<container>] [<proposal>] [--dry-run] [--draft] [--force] [--foreign] [--revision TOKEN] [-o table\|json] [-y] |
Publish all pending actions in exactly one manifest through existing domain gates, never only a selected child. For a PR with review comments or replies, choose Publish, Publish as draft review, or Cancel; Cancel/Esc publishes nothing. --draft preselects draft mode and -y confirms it. Draft mode puts review comments and replies into your pending GitHub review, holds general comments in the outbox, and publishes a staged PR description normally; it never submits the review. GitHub submission happens later in GitHub's UI. A summary is supported on creation only; an existing review refuses a nonempty summary before adding that action's comments. Add the summary in GitHub or stage an empty summary. A general-comment-only manifest has no draft choice; --draft is invalid when nothing review-compatible is pending. On an interactive terminal, a transient Publishing n/total indicator names the action being attempted; the outbox TUI also shows the count. --force is PR-only; -y skips confirmation, not safety checks. -o json (with --dry-run only) prints the plan as one JSON object and never prompts. Foreign-target safeguards still apply (details) |
jailbee outbox sent ls [<container>] [--repo PREFIX] [-o table\|json] [--limit N] |
The Sent log: what JailBee published to GitHub from the outbox (reviews, replies, issue actions, PR descriptions, source PRs), newest first, with publisher (user or orchestrator), state (applied or uncertain) and GitHub URL. Records outlive the container; a record of a destroyed container is marked †. <container> shows only that container's current incarnation. Local, never calls GitHub |
jailbee outbox sent show <id> [-o table\|json] |
One published action in full, with its URL |
jailbee outbox sent clear [--repo PREFIX] [--before DATE] [-y] |
Delete Sent log records (GitHub is untouched); confirms unless -y. An omitted --repo is picked from a list (always asks); --before takes an ISO date or datetime (naive = UTC) |
jailbee review apply [<name>] [-y] [--dry-run] [--draft] [--force] [--foreign] |
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. For review comments/replies choose Publish, Publish as draft review, or Cancel; Cancel/Esc publishes nothing. --draft preselects draft mode; -y confirms it. Draft mode holds review comments and replies in your pending GitHub review, holds general comments in the outbox, and publishes descriptions normally. The pending review remains unsubmitted and visible only to your GitHub account until you submit it in GitHub. Summaries are supported on creation only. On an interactive terminal, a transient Publishing n/total indicator names the action being attempted; the outbox TUI also shows the count. Existing pending reviews accept empty-summary inline comments and replies; a nonempty summary is refused before adding that action's comments, including for a review created earlier in the same session. Add the summary in GitHub or stage an empty summary to avoid a public edit after concurrent submission. A pending review on another commit is refused. Partial append failures may have added comments already—check the pending review before retrying. A manifest whose PR head moved since it was written is held back unless --force; foreign-target safeguards still apply. --dry-run shows the plan only. See Reviewing a pull request |
jailbee review ls\|show\|drop, jailbee issue ls\|show\|drop |
Deprecated in favour of jailbee outbox ls\|show\|drop. Hidden from --help; they still work with unchanged behaviour and print a one-line hint on stderr (stdout, including -o json, is untouched) |
jailbee issue apply [<name>] [--manifest <name>] [--yes] [--dry-run] [--foreign] |
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. An issue action on a foreign repository (github:<owner>/<name>) is warned about in the plan and named in the one confirmation; with -y it needs --foreign. |
jailbee issue resolve [<name>] [<manifest>] [<action>] (--applied --url <url> [--issue <n>] \| --retry) [--yes] |
On a terminal, the container, manifest, action, mode and URL are asked for when omitted; off a terminal they must be given as arguments and flags (exit 2 otherwise). 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. add --for 30m schedules a container override to expire (max 24h); --for never / --no-revert keeps it permanently. Without these flags no TTL is added or prompted; repo scope does not accept them. rm also removes any expiry. 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] |
Snapshot the container's storage, not host bind mounts or separate shared volumes. Stop the container before restoring and preserve any newer work. restore and delete ask for the snapshot when [tag] is omitted — even with only one — and exit 2 off a terminal. With both container and tag supplied, they run without another confirmation |
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 (asked for when omitted) is always the container-side port; --host-port names the host side (default: PORT). Both addresses default to 127.0.0.1. This proxy deliberately makes the chosen host service reachable even in strict mode. |
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 (asked for when omitted) is always the container-side port; --host-port auto picks an available host port, accounting for other containers' claims (stopped ones included). Both addresses default to 127.0.0.1; using a wildcard --host-address can expose the service to other machines. |
jailbee port rm [HANDLE] [NAME] |
Remove one port forward (asked for when omitted, even with only one; exit 2 off a terminal). HANDLE is a device name, a host_ports name, or a container-side port |
jailbee orchestrate grant [NAME] [--profile P] [--children N] [--allow OP]... [--deny OP]... [--mount M]... [--clear-mounts] [--loose\|--no-loose] [--egress HOST]... [--clear-egress] |
Give a container the right to start and manage child containers. Fields not given come from --profile if named, else the container's current grant, else the orchestration.default_profile grant, else the built-in defaults: 4 running children, operations destroy, import, message, restart, rights, spawn, stop, and nothing grantable (no mounts, no loose, no egress). spawn and message are part of every grant: --deny spawn\|message is refused (use orchestrate revoke). OP is one of spawn, message, import, restart, stop, destroy, rights. Refused for mount-mode containers, for children, for unregistered repos (run jailbee apply) and when the repo disallows orchestration; a grant above the repo's caps is refused naming each excess. Over remote SSH it is refused unless remote.ssh.orchestration is ssh or orchestrator (see Security); revoke never needs it. Exit 1 on refusal. The grant is what the host's orchestration broker (jailbee-broker.service) checks when the container's agent asks to start or manage children; the agent does so by writing manifests (see Container orchestration). |
jailbee orchestrate revoke [NAME] |
Remove the grant (always asks which container, exit 2 off a terminal); says so when there was none |
jailbee orchestrate adopt [ORCHESTRATOR] [CHILD]... |
Adopt existing eligible containers into an orchestrator's topology, subject to its running-child cap; remote SSH requires remote.ssh.orchestration enabled. See adoption. |
jailbee orchestrate release [CHILD]... |
Release children and revoke orchestrator-owned rights, preserving user-owned rights; refused during a live autostart run. An omitted child always requires an interactive picker. Allowed over remote SSH as a narrowing action. |
jailbee orchestrate show [NAME] |
Show the grant, what the repo's caps leave of it (when different), and the number of running children |
jailbee orchestrate ls [--json] |
List the repo's containers holding a grant: limit, running children, operations, mounts, loose, egress; --json prints a list |
jailbee orchestrate profile save [CONTAINER] [PROFILE] [--force] |
Save a container's grant as a global host profile; --force replaces an existing global profile |
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. The current Claude adapter supports switching a running session on its next turn without a restart; the account shown in /status may lag until the session restarts. <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> selects that group's holder instead; repos and containers already using that holder all see the change, including this repo if it uses that group |
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 deletes the local stored credential; it does not contact the vendor or revoke independent logins elsewhere. Without another saved credential, log in again to add it back |
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 with an account-pool adapter |
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. Containers without their own group override follow; different overrides stay in place, and redundant ones are dropped. Refuses when a pooled agent is detected running unless --force; close agents first and restart them after changing groups. If both an ungrouped repo and the target group hold a login, credential reconciliation asks which to keep and discards the other |
jailbee account group use [<name>\|none] [<container>] [--force] / reset [<container>] [--force] |
One container's group, for its lifetime; reset returns it to the repo's group. Commands needing a group resolve an omitted name through the prompt policy (rm always asks on a terminal); ls, unset and reset need no group argument. Naming the repo's own group clears the override instead of writing one. A detected running pooled agent blocks use/reset unless --force; close it first and restart it afterward |
jailbee mount [<kind>] [<name>] [--for <dur>\|--no-revert] / jailbee unmount [<kind>] [<name>] |
Optional mounts; detached automatically after the effective TTL. Explicit --for/--no-revert overrides the default. Otherwise, an active TTL default prompts on a terminal and applies directly without one; a disabled policy or per-mount never mounts without a deadline or prompt. Re-running mount on an attached kind changes its TTL. --for accepts 30s, 15m, 2h, integer minutes, or never; --no-revert keeps the mount until manually detached. A kind or container left out is asked for on a terminal (exit 2 off one). Over remote SSH, mount is refused unless remote.ssh.mount_roots permits the resolved host directory or remote.ssh.mounts is on. A new mount authorized through mount_roots is read-only unless that root allows writing. Re-running mount on an attached device changes only its TTL: it does not downgrade an existing read-write mount to read-only. |
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> comes first and is asked for when omitted — 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). --move/--no-move: move the app here when it is open on another display (default: ask on a terminal) — see remote-gui |
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. --move/--no-move: move the app here when it is open on another display (default: ask on a terminal) — see remote-gui |
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). --move/--no-move: move the app here when it is open on another display (default: ask on a terminal) — see remote-gui |
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. --move/--no-move: move the app here when it is open on another display (default: ask on a terminal) — see remote-gui |
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. The build runs on defaults.storage_pool (else the default profile's root pool). On a btrfs pool the result is a stopped instance <alias>--golden that jailbee new copies with incus copy; on other drivers it is a published image under <alias>. Only one version is kept: each build replaces the previous one and removes any dated archives |
jailbee base prune [--all] [--days N] [--yes-to-all] |
Remove dated golden-image archives (<alias>-YYYY-MM-DD) left by jailbee before 1.7.5; builds now remove them themselves, so there is normally nothing to prune. 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 (image or golden instance) and any leftover 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 / attach [name] |
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. attach (host only) attaches the host's Wayland display to a running container, for GUI apps started from a shell; the GUI launchers do it themselves (see gui.wayland). On a Linux laptop or a Mac, waypipe ssh gives native windows instead; see Remote GUI |
jailbee net egress proxy up [--recreate] / down / status |
The shared Squid egress proxy behind wildcard entries and the work network (short alias: jailbee egress proxy …). jailbee apply and egress add start it when needed; up runs it by hand and --recreate deletes the container and builds it again, e.g. on another storage pool. down stops it and withdraws its ACL allowance, so containers that rely on it lose egress until the next up |
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] [--recreate] |
Create/repair the dedicated proxy, render its routes and start its service — except for an account whose routes require a missing ChatGPT login, which stays stopped until login and a second up. Missing xAI login does not prevent startup, but that account's xAI OAuth routes fail until login. Requires litellm.enabled: true in global config; --reinstall forces the package install again; --recreate deletes the container and builds it again (e.g. on another storage pool), keeping the state volume. See LiteLLM. |
jailbee litellm down [--purge] |
Stop the proxy and withdraw its dev-container ACL allowance; the container and its state volume (logins, settings) are kept. --purge deletes the container and the volume. 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 (asked for when omitted and there is more than one): ChatGPT by device code, xAI 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 / legacy-remote-ssh-shell (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 [--yes-to-all] |
Delete old stopped containers across all repos, asking about each unless --yes-to-all is given. Age is estimated from the Incus container directory's mtime (over 30 days), not a recorded stop time. Unlike destroy, this command does not assess dirty trees or unmerged commits; inspect and preserve work first. |
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 |
Before any PR agent run, JailBee asks whether to generate the configured PR title/
description and/or branch-name proposal with the named agent. One answer covers
both features; No is the default because generation costs time/money and sends
code to a model. --ai authorizes generation without asking; --no-ai skips both
features without asking (combining them is exit 2). Without a TTY, or with
JAILBEE_NONINTERACTIVE, generation is skipped with one stderr hint unless
--ai is given. --yes and --description do not authorize an agent run.
Disabled pr.ai_description / pr.ai_branch settings stay disabled even with
--ai. Existing outbox descriptions are used without this question. The policy
also applies to jailbee submodule pr push, the terminal dashboard and remote
SSH; Qt opens PR flows in a terminal for the same question.
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.
PR source selection¶
jailbee pr push <name> on a terminal offers the container's own branch first,
then pending source PR proposals from its outbox, labelled with title, source
head, base and manifest file. Picking one runs the same confirmation, validation
and receipt handling as --source-manifest <file>. Cancel publishes nothing.
An own-branch description is not a separate source; published proposals awaiting
cleanup are not offered again. Without pending sources there is no extra question.
The dashboards' Create/update PR offers the same choice: the terminal
frontend uses the CLI picker; Qt shows a native choice and opens a terminal for
source publication confirmation.
Off a terminal, the command keeps publishing the own branch and prints a pending
source count on stderr, with --source-manifest <file> and jailbee outbox <name>
hints. Explicit --source-manifest skips the picker. So does any own-branch option
(--title, --body, --base, --branch, --as, --pr, --stacked,
--retarget/--no-retarget, --ready/--draft/--no-draft, --description,
--web, --ai, --no-ai, --no-outbox, --no-submodules), but the count is still printed.
Only --config, --force and --yes preserve the choice; --force and --yes
carry over to the selected manifest. An unreadable or invalid outbox never blocks
the own-branch flow. jailbee submodule pr push is unchanged.
Console¶
jailbee console opens an interactive jb[<prefix>]> prompt on your own
terminal. It starts in the registered repository containing the current
directory, or asks which one (straight in when exactly one is registered);
--repo PREFIX picks one explicitly. At the prompt, type JailBee commands
without the jailbee prefix, with tab completion and history. repos lists
the registered repositories, use [PREFIX] switches repository (bare use
reopens the menu), dashboard opens the dashboard, help lists the commands
and exit (or Ctrl-D) leaves. A line's global --repo PREFIX runs only that
command in another repo; it does not change the prompt's selected repo (use
does). Locally it is unrestricted: it applies no
remote.ssh policy. It is an interactive entry point only, so it cannot be run
as a one-shot command over remote SSH (there the console is
ssh -t … console, restricted by policy; see below).
Bare jailbee (or jb) on a terminal opens whatever
default_command names — the dashboard by
default. Off a terminal it prints the help text and exits 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 takes the complete fingerprint printed by add or ls; left out, it is chosen from the stored keys on a terminal (even when only one is stored) and is exit 2 off one.
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] [--console/--no-console] [--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, so an invalid
combination is reported the same way a broken config file is. --console
with --commands disabled is valid: console-local navigation remains available,
but JailBee commands are refused. --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 --console --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 console [--repo PREFIX]
ssh -p 8022 jailbee@localhost help
ssh -p 8022 jailbee@localhost repos
ssh -p 8022 jailbee@localhost COMMAND [ARGS...] [--repo PREFIX]
ssh -p 8022 jailbee@localhost -- --repo PREFIX COMMAND [ARGS...]
The old shell spelling of the remote entry point (remote.ssh.shell,
default_entrypoint: shell, serve --shell) still works until 2.0.0;
jailbee config migrate --apply renames it. Only the exact ssh … shell
and ssh … shell --repo PREFIX forms mean the legacy console;
ssh -t … shell NAME --repo PREFIX runs jb shell NAME in a container.
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 console to open that entry point instead;
an explicit help lists enabled forms and allowed command paths. repos
lists the registered prefixes and roots visible to the session (it requires
exec: true). 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 console --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 without excluded repositories, help then runs
the real python -m jailbee --help in the current repo root. In allowlist
mode, or with excluded repositories, it instead renders a filtered second
panel, "Allowed Jailbee commands", listing allowed paths with their 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.
One-shot SSH execution accepts every JailBee command the remote console does,
under the same command policy and host restrictions. The global --repo PREFIX
follows the placement rules above; the old -- --repo PREFIX COMMAND form
still works. The registered root becomes the command's working directory.
Without a selector, one visible repo is used automatically; with several,
ssh -t shows a picker and a non-PTY call exits 2 naming the candidates.
Command help does not need a repo or open a picker. console --repo PREFIX
also takes an exact registered prefix; a console line's global selector is
temporary, just as it is locally. Excluded repos are unavailable to selectors,
pickers and repos.
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, adding an egress rule is a host command unless
remote.ssh.network is on, while removing one is always allowed
(container scope only; --repo is always refused). Each new SSH channel
gets the current validated effective policy; a console or dashboard already
running keeps its startup command-policy snapshot. Mount authorization is an
exception: each mount also checks the current global.yaml policy, so removing
a permitted root can block new mounts even in an older session. 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.
GPG keys over remote SSH¶
To decrypt or sign inside containers using keys on a Linux or macOS client,
opt in on the server in ~/.config/jailbee/global.yaml:
remote:
ssh:
gpg: true
Keep gpg.enabled: true in each participating repo. Run jb restart <container>
once for existing containers to install the new read-only switch mounts; no
profile apply or golden-image rebuild is needed. The local host agent still
works when the SSH service has never been started. Import the client's public
keys into the host's ~/.gnupg (gpg --import public-keys.asc): containers
read that keyring, not the client's keyring.
On the client, start the agent and ask GnuPG for its restricted extra socket:
gpgconf --launch gpg-agent
gpgconf --list-dirs agent-extra-socket
Use the command's actual output. Typical paths are
/run/user/<uid>/gnupg/S.gpg-agent.extra on Linux and
~/.gnupg/S.gpg-agent.extra on macOS with Homebrew GnuPG or GPGTools (expand
the home directory to its absolute path). Add one line to the SSH Host block
used to reach Jailbee:
Host jb
HostName your-host
User jailbee
Port 8022
RemoteForward /jailbee/gpg-agent /path/from/gpgconf/S.gpg-agent.extra
Open ssh -t jb dashboard or ssh -t jb console. While the forward is up,
every gpg.enabled container with the new mounts uses the client's agent,
not just the container you enter. The newest live forward wins; when it closes,
the previous live forward takes over, or the host agent resumes. Pinentry and
card touch prompts appear on the client. SSH_AUTH_SOCK and SSH authentication
inside containers keep using the host agent. No private key is transferred
through this forward. Cancelling the forward without closing SSH also restores
the previous agent. Only one SSH server process per host user owns the switch;
a concurrent diagnostic server on another port refuses GPG forwards rather than
interfering with the active service.
For a smartcard, run gpg --card-status on the client if its agent has not
learned the card yet. Software keys need no card-learning step. GnuPG 2.4 may
print problem with fast path key listing: Forbidden - ignored through the
restricted extra socket; this does not prevent decryption.
Treat this as host-wide authority to request operations. When GPG is a gate
for secrets, prefer a smartcard with a touch policy, for example
ykman openpgp keys set-touch dec on and ykman openpgp keys set-touch sig on
on the client. With a software key, or a card without a touch policy, the
client agent's passphrase cache (default-cache-ttl) can let any participating
container decrypt without a prompt while the cache is warm. Client-only private
keys stay on the client; any private keys already stored in the host's shared
~/.gnupg remain readable there. See Security.
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 --force '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, with the highlighted container as the source. The Qt
dashboard starts jailbee merge and leaves target selection to the CLI prompt.
The terminal dashboard picks the targets in the table itself, with every column
in view: the source rows show ◆, rows that cannot be a target (another repo,
stopped, mount mode) are dimmed and the cursor skips them. Entry keeps the
highlighted row if it is eligible; otherwise it moves to the next eligible row,
or the previous one if none follows. Space toggles a target's mark without
moving the cursor, Enter runs jailbee merge <sources> --into <target>…
over the marked targets — or the highlighted row when none is marked — and Esc
cancels. A line under the table names the sources and targets throughout. Bulk
Merge into… enters the same mode with the marked containers as sources
(they must be in one repo); Esc gives the marks back, and a successful merge
unmarks its sources. A source that disappears in a refresh ends the mode. The
equivalent !merge command follows the selected-container default described
above. Fork… and Rename… ask for the new container's name and the
alias: inline in the terminal dashboard ([f] and [R] in the container
menu), where Fork runs jailbee fork --background and Rename jailbee rename
(an empty alias clears it); the Qt dashboard opens jailbee fork <container>
or jailbee rename <container> in a terminal, where the CLI asks. Rename starts
with the current alias, or the container's short name when no alias is set,
so a suffix can be appended directly. Ctrl-R clears the name immediately in
the terminal dashboard's rename prompt and in the CLI prompt used by Qt
(the CLI offers the shortcut only when an alias exists). An empty Enter
response also clears the alias in the terminal dashboard, but not in the CLI.
The single-container menu has only Rename…; Clear name is available
in the multi-selection menu and skips containers without aliases. That menu
leads with Merge into…, then base updates and sending commits to the host,
followed by lifecycle, network and clear-name actions, with Destroy last.
The terminal dashboard alone offers Clear outbox… ([X]) in the
container menu and the N selected menu. It always asks for confirmation
with No first, even when general confirmations are disabled, then clears
eligible proposals in that container or the marked eligible containers.
Protected or changed proposals remain; Qt does not offer this action.
Change base branch asks for
the base itself: inline in the terminal dashboard, in a dialog in the Qt
dashboard, with the host's other local branches as suggestions. A branch the
host does not have is refused. The dashboard then runs
jailbee git retarget with both the container and the base.
The commands that exist for the text they print — pr, git push, git pull,
git diff, git retarget, 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.
--foreground (GUI-only) keeps it attached to the terminal. The TUI's
--interval/-i, --git-interval and --no-git are accepted by all three
commands but deprecated and ignored (see Refresh).
AI activity age¶
The dashboard AI age measures time since the latest real agent event across
that agent's live sessions, not time in its current status. Assistant text,
thinking and tool calls, and user tool results count; prompts, metadata and
file modification times do not. Missing, malformed or future event dates are
ignored; without a valid event date the age is unknown (no status-age fallback).
Status symbols and urgency still describe the most urgent live session.
The details panel shows the selected activity session's status duration
separately, for example active 14s ago (busy for 23m); an idle agent whose last
event is as old as its idle time reads just active 25m ago. Recent-idle highlighting
uses event age too. Both ls agent columns retain status duration; the JSON
since field still records status changes.
Refresh¶
Every dashboard — jailbee dashboard, jailbee tui, jailbee gui — shows what
one shared background service (jailbee _state-service) gathers. It starts on
demand and exits 30 seconds after the last dashboard closes. Its pace is set by
dashboard.refresh
in the global config; its log is ~/.local/state/jailbee/state-service.log.
--interval/-i, --git-interval and --no-git are deprecated: they print a
warning and have no effect. The Qt Refresh menu has only Refresh now.
A minimised window, or a dashboard whose terminal is running jailbee tmux or
jailbee shell, does not make the service gather.
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 offer the same single-container actions. Table also supports multi-selection and a bulk action menu; Cards selects one container at a time, and right-click selects that card before opening its menu. 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) and card style (Compact or Grid),
- enabled columns, their order, and the table's column widths,
- folded and hidden repositories, and whether empty repositories are shown,
- sort field and direction.
View preferences are stored separately for the terminal and Qt frontends.
Not persisted: window size and position — that's left to the window manager.
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.