Metadata-Version: 2.5
Name: jailbee
Version: 1.6.0
Summary: Manage isolated per-branch development environments using Incus system containers
Project-URL: Homepage, https://jailbee.gisgro.io
Project-URL: Documentation, https://jailbee.gisgro.io/docs/
Project-URL: Repository, https://github.com/VRTFinland/jailbee
Project-URL: Issues, https://github.com/VRTFinland/jailbee/issues
Project-URL: Changelog, https://github.com/VRTFinland/jailbee/blob/main/CHANGELOG.md
Author-email: Tuomas Airaksinen <tuomas.airaksinen@gisgro.com>
Maintainer-email: Tuomas Airaksinen <tuomas.airaksinen@gisgro.com>
License-Expression: GPL-3.0-or-later
License-File: LICENSE
Keywords: agent-sandbox,ai-agent,claude-code,cli,coding-agent,containers,dev-environment,developer-tools,git-branch,incus,isolation,lxc,sandbox
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: System Administrators
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development
Classifier: Topic :: System :: Systems Administration
Requires-Python: >=3.12
Requires-Dist: prompt-toolkit>=3.0
Requires-Dist: pydantic>=2.6
Requires-Dist: pyyaml>=6.0
Requires-Dist: questionary>=2.0
Requires-Dist: rich>=13.7
Requires-Dist: ruamel-yaml>=0.19.1
Requires-Dist: sqlmodel>=0.0.16
Requires-Dist: typer>=0.12
Provides-Extra: gui
Requires-Dist: pyside6>=6.7; extra == 'gui'
Provides-Extra: ssh
Requires-Dist: asyncssh>=2.24; extra == 'ssh'
Description-Content-Type: text/markdown

<p align="center">
  <img src="https://raw.githubusercontent.com/VRTFinland/jailbee/main/docs/images/jailbee-logo-dark.jpg"
       width="220" alt="JailBee">
</p>

<p align="center">
  <a href="https://github.com/VRTFinland/jailbee/actions/workflows/ci.yml"><img
    src="https://github.com/VRTFinland/jailbee/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
  <a href="https://github.com/VRTFinland/jailbee/blob/main/LICENSE"><img
    src="https://img.shields.io/badge/License-GPLv3-blue.svg" alt="License: GPL v3"></a>
</p>

**JailBee** runs isolated, per-branch development environments in
[Incus](https://linuxcontainers.org/incus/) system containers. Spin up multiple
full stacks in parallel on one host — each with its own services, Docker daemon,
IDE, and browser — without port conflicts, Docker name clashes, or
shared-database collisions.

The CLI is `jailbee`, or `jb` for short. Release announcements are at
[jailbee.gisgro.io/news](https://jailbee.gisgro.io/news/).

**JailBee** is project-agnostic: every repo supplies its own `.jailbee/config.yaml`. The
golden image ships stack-neutral by default — language toolchains (JDK, Node,
Python venv/pip, Docker) are bundled but opt-in, enabled per repo via
`golden.stacks` / `golden.enable_snippets`. It was built at GISGRO, which is
its origin, not its scope.

## See it work

<p align="center">
  <a href="https://jailbee.gisgro.io/#demos"><img
    src="https://raw.githubusercontent.com/VRTFinland/jailbee/main/website/assets/media/a-teaser.gif"
    width="760"
    alt="A terminal recording: jb ls shows one container, jb new feat/health-endpoint clones a second from the golden image and provisions it — dependencies, database, dev server, agent — and jb ls shows it running."></a>
</p>

<p align="center">
  <a href="https://jailbee.gisgro.io/#demos"><strong>▶ Watch the whole flow</strong></a>
</p>

**What the full clip shows, end to end.** A branch gets a container of its own,
cloned from the golden image with the app's dependencies synced, its database
seeded and its dev server already listening on port 8080. A coding agent gets a
window inside it and is asked for a `/health` endpoint with a test; the human
detaches, checks the running service from outside, and comes back to find the
work committed. `jb git pull` brings that commit onto the host as a merge —
asking before it destroys anything — and `jb destroy` throws the container away
as its own deliberate step.

Every command and every line of output is real. Two stretches of the clip are
sped up and say so on screen; nothing else is edited.

## Key features

- **Per-branch isolation** — one full-stack container per git branch, running
  in parallel without port or Docker-name collisions.
- **Host↔container git bridge** — the container acts as a git remote; move
  commits with `jailbee git push`/`pull`/`checkout` instead of round-tripping
  through GitHub.
- **Submodules that travel** — sub-repos are initialised offline on
  `jailbee new` and their objects move with the superproject on every
  push/pull, so a repo with submodules needs no manual setup on either side.
- **Nested Docker** — `security.nesting=true` out of the box on Ubuntu 26.04.
- **GUI passthrough** — launch a JetBrains IDE (`jailbee ide`), Chrome or
  Firefox (`jailbee chrome`, `jailbee firefox`), or any other app registered
  under `apps:` (`jailbee apps run <name>`), from inside a container onto
  your Wayland session.
- **Host sockets, shared** — the Wayland display and (with `gpg.enabled`) the
  gpg-agent are attached to every container, PulseAudio and D-Bus on request
  (`gui.audio`, `gui.dbus`), so `git commit -S` and `ssh` work inside while
  the private key never leaves the host (a smartcard still asks for its
  touch). Mount any other host socket the same way and use it from inside.
- **Host services, forwarded in** — declare
  `host_ports: [{ name: adb, port: 5037 }]` and every container of the repo
  reaches that host service on its own localhost, so plain `adb devices` works
  inside with no `ADB_SERVER_SOCKET` juggling. `jailbee port` adds or removes a
  forward on one container without touching the config, in either direction —
  `to-host` for the rarer case where you do want a container's service on the
  host.
- **Network modes** — per-container egress allowlist with `strict` and
  `loose` policies (`jailbee net`), safe for unattended agent runs. Entries are
  hostnames and ports (`api.example.com:443`), not IP addresses: JailBee
  resolves them into the kernel ACL, keeps a cumulative pool as CDN
  addresses rotate, and pins the container's `/etc/hosts` to match. Any
  protocol, not just HTTP — `ssh`, `git+ssh` and a database client work
  under the same list. `jailbee net egress add` widens one container's copy
  of that list — or this machine's copy of the repo's — without editing the
  committed config, so a host only you need never lands in git; see
  [Egress overrides](https://jailbee.gisgro.io/docs/security/#egress-overrides).
  An optional work network (`jailbee net migrate`) gives containers a stable
  address and switches modes without replacing their NIC.
- **Personal settings stay out of git** — `~/.config/jailbee/global.yaml`
  holds what is yours on every repo, and a host-local
  `~/.config/jailbee/repos/<prefix>.yaml` what is yours on one (its GitHub
  token, credential group, extra egress), above the committed
  `.jailbee/config.yaml`; `jailbee config edit --local` edits it.
- **First-class Claude Code** — opt in with `agents.claude.enabled: true` and
  every container gets Claude Code installed, sharing one settings directory
  across the repo's containers and one login across every repo in its
  credential group (`jailbee account` manages the stored logins), while your
  host `~/.claude` is never read. Instructions written once in
  `~/.config/jailbee/AGENTS.md` reach every container's Claude. The Anthropic
  hosts are added to the strict-mode allowlist
  automatically, JailBee's own skills teach the in-container Claude to drive
  `jailbee`, and `jailbee pr` writes the PR title and body — to your repo's own
  standard, if you state one in `pr.prompt`. Start it
  automatically in a tmux window and the container is ready for an
  unattended run the moment it boots — with permission prompts turned off
  (`--dangerously-skip-permissions`), because the boundary is the container
  rather than the agent's own judgement. You size that boundary once in the
  repo's config; see
  [Running an agent without prompts](https://jailbee.gisgro.io/docs/security/#running-an-agent-without-prompts)
  for what it does and doesn't cover.
- **PR and issue outbox** — a container's `gh` is read-only, so an agent
  reviewing a PR or triaging issues inside it stages comments, replies,
  description rewrites and issue changes as JSON manifests instead of posting
  them straight to GitHub. A human reviews the exact text and publishes it
  with one confirmation: `jailbee review apply` and `jailbee issue apply`
  (also `ls`/`show`/`drop`), or `jailbee outbox` to browse both in one place;
  `jailbee ls` and both dashboards mark a container carrying pending
  manifests, and `jailbee pr` picks up a pending description in place of
  asking an agent to write one.
- **Claude Code on other models** — `claude-jb` runs Claude Code against a
  LiteLLM proxy that JailBee keeps in its own container, so the same agent
  can work on a ChatGPT subscription or on any provider you have an API key
  for, while plain `claude` stays native. One proxy per account, per-repo
  route overrides, live route reloads; see
  [Claude Code through LiteLLM](https://jailbee.gisgro.io/docs/litellm/).
- **Remote access over SSH** — an optional, key-only SSH service
  (`jailbee remote ssh enable`) opens the dashboard, a restricted console or
  policy-limited one-shot commands from another computer; host-management
  commands stay refused. With `remote.ssh.gui` on, GUI apps launched over SSH
  appear on a shared RDP display (`jailbee display`) you open in any RDP
  client; see
  [Remote GUI over SSH](https://jailbee.gisgro.io/docs/remote-gui/).
- **Generic agent support** — `agents: {codex: {enabled: true}}` wires any
  terminal coding agent into the same mount/egress/install/autostart
  pipeline Claude Code uses, via a shipped preset or one you write yourself.
  An agent that declares a skills directory gets JailBee's skills, and one
  with a headless command can write `jailbee pr`'s title and body
  (`pr.agent`). Five presets beyond Claude (`codex`, `gemini`, `aider`,
  `opencode`, `grok`) ship as untested starting points — see
  [Generic agent support](https://jailbee.gisgro.io/docs/agents/).
- **One shared state layer per repo** — package-manager caches, the JetBrains
  config, `~/.ssh` and Claude's login live in a shared dir outside the
  containers, set up once per repo instead of once per branch. Most of it is
  one mount every container shares live — pnpm's store, JetBrains, `~/.ssh`,
  Claude's login. Gradle, Maven and the Chrome profile instead give each
  container its own private slot seeded from the warmest one: a warm cache
  without the lock contention one shared `~/.gradle` used to cause. Either
  way the state outlives `jailbee destroy` / `jailbee new` — while nothing a
  container does reaches your host's own dotfiles.
- **Fast, cheap containers** — copy-on-write clones of one golden image; a live
  TUI dashboard (`jailbee dashboard`, alias `jailbee tui`) or Qt GUI dashboard
  (`jailbee gui`) spans
  every repo, shows what each container's agent is doing, and acts on what it
  shows: attach a shell or tmux, open the IDE, create or update the PR,
  update a container from its base, read its diff — without leaving the view
  that told you it was needed.

## Getting started

**JailBee** needs a Linux host running Incus. Install the CLI with
[`uv`](https://docs.astral.sh/uv/) or [`pipx`](https://pipx.pypa.io/) —
JailBee is an ordinary PyPI package and needs neither at runtime, but
Ubuntu 24.04+ refuses a bare `pip install` into its system Python:

```bash
uv tool install jailbee      # or: pipx install jailbee
```

For the optional Qt GUI dashboard (`jailbee gui`), add the `gui` extra:

```bash
uv tool install 'jailbee[gui]'      # or: pipx install 'jailbee[gui]'
```

Host setup — Incus, firewall, UID mapping, kernel keyring limits — is a
one-time job with a few moving parts. Follow **[Installation](https://jailbee.gisgro.io/docs/installation/)**
end-to-end first. Then, from the repo you want to manage:

```bash
jailbee config init          # write .jailbee/config.yaml
jailbee doctor               # sanity-check host + config
jailbee init                 # create Incus profiles, ACLs, bridge
jailbee base build           # build the golden image (one-time, ~10–15 min)
jailbee new feat/my-branch   # spin up an isolated env for a branch
```

See **[Getting started](https://jailbee.gisgro.io/docs/getting-started/)** for the full first-run
walkthrough.

### Trying it without a repo config

Skip `jailbee config init` and `jailbee init` entirely for a quick look —
`cd` into any git repo and just run `jailbee new work`. With no
`.jailbee/config.yaml` in the directory, JailBee synthesizes a config from
`~/.config/jailbee/global.yaml`'s `scratch:` block, creates that directory's
Incus profiles on the fly, and boots the container from a golden image
shared by every such directory on the host (alias `jailbee-scratch-base`).
The first time, it asks to build that image (a one-time, few-minutes cost);
every later scratch directory reuses it immediately. A directory that isn't
a git repo needs `jailbee new --mount work` instead, since there's no
upstream to clone from. See [`scratch`](https://jailbee.gisgro.io/docs/config/#scratch)
for the config block, and run `jailbee config init` once the work outlives
an afternoon.

## Post-install setup

Installing the package puts `jailbee` and `jb` on your `PATH` and nothing
else. One command installs the rest:

```bash
jailbee setup
```

It asks about three steps, each idempotent — re-run it after upgrading:

- **shell completions** for both `jailbee` and `jb` (bash, zsh or fish),
- the **`jailbee-net-refresh` user timer**, which keeps strict-mode egress
  allowlists current and expires `jailbee net loose --for` TTLs,
- JailBee's **agent skills** for the agents found on your host (opt-in via
  `install_host_skills` in `~/.config/jailbee/global.yaml`), so they know
  how to drive `jailbee`.

`jailbee doctor` reports any step that is missing. Host prerequisites — Incus,
the firewall, UID delegation — are separate; see
[Installation](https://jailbee.gisgro.io/docs/installation/).

### Shell completion

Restart the shell after `jailbee setup`, and TAB completes commands, options,
and:

- **container names** on every command that takes one (`jailbee shell`, `jailbee destroy`,
  `jailbee git push`, `jailbee ide`, …) — short names, from the containers that exist in
  the current repo
- **branch names** on `jailbee new` and `jailbee retarget`, from the host repo's local branches
- **snapshot tags** on `jailbee snapshot restore` and `jailbee snapshot delete`, from the
  container already named on the command line
- **fixed values** for `--format`, `--layer`, `--attach` and `--user`

Completion looks for `.jailbee/config.yaml` in the current directory, the same
default the commands themselves use; elsewhere it offers nothing. Unlike the
commands, it does not honor `--config`/`-c`, so e.g. `jailbee shell -c
/other/repo/.jailbee/config.yaml <TAB>` still completes against the *current
directory's* containers, not the repo the flag points at.

## Documentation

The full documentation lives at **[jailbee.gisgro.io/docs](https://jailbee.gisgro.io/docs/)** —
the same pages [this repository's `docs/`](https://github.com/VRTFinland/jailbee/tree/main/docs)
holds, indexed and searchable.

Start here:

| Doc | What's inside |
|---|---|
| [Installation](https://jailbee.gisgro.io/docs/installation/) | One-time host setup: Incus, UID delegation, installing the CLI (plus conditional firewall / kernel-keyring steps) |
| [Getting started](https://jailbee.gisgro.io/docs/getting-started/) | Concepts, configure a repo, build the image, and a "typical day" walkthrough |
| [Commands](https://jailbee.gisgro.io/docs/commands/) | Full command + flag reference table |
| [Configuration reference](https://jailbee.gisgro.io/docs/config/) | Every `.jailbee/config.yaml` and `global.yaml` key |
| [News](https://jailbee.gisgro.io/news/) | Release announcements (also as an [RSS feed](https://jailbee.gisgro.io/news/feed.xml)) |
| [FAQ](https://jailbee.gisgro.io/docs/faq/) | Short answers to the common questions, each linking to the page that covers it in full |

**Project internals** — maintainer procedure, kept in the repository:

| Doc | What's inside |
|---|---|
| [Manual testing](https://github.com/VRTFinland/jailbee/blob/main/docs/manual-testing.md) | End-to-end smoke-test recipes (require a real Incus daemon) |
| [Releasing](https://github.com/VRTFinland/jailbee/blob/main/docs/releasing.md) | Release process |
| [Contributing](https://github.com/VRTFinland/jailbee/blob/main/CONTRIBUTING.md) | Development setup and repo conventions |

## License

`jailbee` is free software, released under the GNU General Public License v3.0
or later (GPL-3.0-or-later). See [`LICENSE`](https://github.com/VRTFinland/jailbee/blob/main/LICENSE) for the full text.

Copyright © 2026 GISGRO Oy.
