Metadata-Version: 2.4
Name: droneship
Version: 1.6.0
Summary: Run coding agents like Claude Code in isolated, policy-enforced cloud sandboxes
Project-URL: Homepage, https://github.com/highflame-ai/highflame-forge
Project-URL: Documentation, https://github.com/highflame-ai/highflame-forge#readme
Project-URL: Repository, https://github.com/highflame-ai/highflame-forge
Project-URL: Issues, https://github.com/highflame-ai/highflame-forge/issues
Author-email: Highflame Team <team@highflame.ai>
License: MIT
License-File: LICENSE
Keywords: agent,ai-agent,claude-code,coding-agent,developer-tools,gvisor,isolation,sandbox,security
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: MacOS
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Security
Classifier: Topic :: Software Development :: Build Tools
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.12
Requires-Dist: httpx>=0.27.0
Requires-Dist: packaging>=23.0
Requires-Dist: pathspec>=0.12
Requires-Dist: pydantic>=2.5.0
Requires-Dist: pyyaml>=6.0
Requires-Dist: rich>=13.7.0
Requires-Dist: typer>=0.9.0
Requires-Dist: websockets>=13.0
Provides-Extra: all
Requires-Dist: highflame-forge[gcp,modal,runpod,service]; extra == 'all'
Provides-Extra: dev
Requires-Dist: mypy>=1.8.0; extra == 'dev'
Requires-Dist: opentelemetry-sdk>=1.27.0; extra == 'dev'
Requires-Dist: pre-commit>=3.6.0; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
Requires-Dist: pytest-cov>=4.1.0; extra == 'dev'
Requires-Dist: pytest-timeout>=2.3.0; extra == 'dev'
Requires-Dist: pytest>=7.4.0; extra == 'dev'
Requires-Dist: ruff>=0.1.6; extra == 'dev'
Provides-Extra: gcp
Requires-Dist: google-cloud-aiplatform>=1.38.0; extra == 'gcp'
Requires-Dist: google-cloud-compute>=1.14.0; extra == 'gcp'
Provides-Extra: modal
Requires-Dist: modal>=1.6.1; extra == 'modal'
Provides-Extra: runpod
Requires-Dist: asyncssh>=2.23.1; extra == 'runpod'
Requires-Dist: runpod>=1.6.0; extra == 'runpod'
Provides-Extra: seccomp
Requires-Dist: pyseccomp>=0.1.2; (sys_platform == 'linux') and extra == 'seccomp'
Provides-Extra: service
Requires-Dist: cryptography>=49.0.0; extra == 'service'
Requires-Dist: fastapi>=0.137.0; extra == 'service'
Requires-Dist: highflame-policy>=2.2.28; extra == 'service'
Requires-Dist: httpx>=0.27.0; extra == 'service'
Requires-Dist: idna>=3.7; extra == 'service'
Requires-Dist: opentelemetry-api>=1.27.0; extra == 'service'
Requires-Dist: opentelemetry-exporter-otlp>=1.27.0; extra == 'service'
Requires-Dist: opentelemetry-sdk>=1.27.0; extra == 'service'
Requires-Dist: publicsuffixlist>=1.0.2; extra == 'service'
Requires-Dist: pydantic-settings>=2.14.2; extra == 'service'
Requires-Dist: pyjwt[crypto]>=2.14.0; extra == 'service'
Requires-Dist: starlette>=1.3.1; extra == 'service'
Requires-Dist: uvicorn[standard]>=0.26.0; extra == 'service'
Description-Content-Type: text/markdown

# droneship

[![Python 3.12+](https://img.shields.io/badge/python-3.12+-blue.svg)](https://www.python.org/downloads/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

**Run coding agents like Claude Code in isolated, policy-enforced cloud sandboxes.**

`droneship` is a CLI for the hosted Highflame control plane. You sign in, launch
an agent in an isolated sandbox, and never put cloud-provider credentials on
your laptop. Runs on macOS and Linux.

## Install

```bash
curl -fsSL https://droneship.run/install | sh
```

The installer uses [uv](https://docs.astral.sh/uv/) (and installs it if it is
missing) to put the `droneship` binary on your PATH, usually `~/.local/bin`.
If the next prompt says `command not found`, add the line the installer
printed, or:

```bash
export PATH="$HOME/.local/bin:$PATH"
```

### Updates

`droneship` checks droneship.run for a newer release at most once an hour. When
there is one, it installs it and runs your command on the new version. Set
`DRONESHIP_NO_AUTO_UPDATE=1` to turn this off, and run `droneship update` to
update right away.

## First run

```bash
droneship login
droneship connect
droneship run claude
```

1. `droneship login` opens a browser and stores credentials for this machine.
   Account and project are fixed at sign-in; switch project by signing in
   again with `--project NAME`.
2. `droneship connect` authorizes GitHub so private repos clone. Skip it for
   public repos — `droneship run claude` will also prompt the first time it meets a
   private repo, and again if the connection lapses. `--force` replaces a
   stored connection.
3. `droneship run claude` starts Claude Code in a sandbox with a fresh clone of
   this repo's `origin` at your current branch (unless you pass `--repo` /
   `--ref`), or reattaches to the one already running for this repo (another
   repo's sandbox is never reattached; `--id` names one). Push first: uncommitted
   work and unpushed commits are not copied, and the CLI says so when you have
   either. `--new` starts another alongside it. A saved workspace resumes on
   your branch when its tree is clean; with uncommitted work it stays on the
   branch it was left on, and the CLI says which.

```bash
cd ~/src/my-repo
droneship run claude
droneship resume claude      # pick up a previous conversation
```

Arguments after `droneship run claude` are passed through to `claude`. Put `--` first
for any that droneship would otherwise read as its own.

In a sandbox Claude doesn't ask permission for each command; the sandbox is the boundary. `--ask` restores the prompts.

Claude runs inside the sandbox's tmux session, so it survives a dropped connection: detach on purpose with `~.` (ssh's escape, at the start of a line) and `droneship run claude` reattaches to the same agent. `run claude` exits with tmux's status, not Claude's. tmux has mouse mode on, so a drag selects inside tmux; hold Shift while dragging to select with your terminal instead.

On a machine with no browser (typically SSH into a dev box):

```bash
droneship login --headless
```

### Credentials stay out of the sandbox

When Claude Code or Codex runs on Modal through the Highflame gateway (not
`--direct`), the container it runs in holds no gateway key, your own API key,
MCP token or GitHub token. A
separate sidecar injects them on the way out, and the GitHub token can only be
used for this sandbox's repo. Private repos get the same KERNEL isolation as
public ones, except with subscription sign-in.

### MCP servers

To give the agent MCP servers, commit a `.mcp.json` to the repo, as you would
for Claude Code on your laptop. The sandbox clones it with the code.

- In an interactive session, Claude Code asks you to approve the repo's servers
  once.
- Prompt runs (`-p`) use them without asking: the sandbox trusts the checkout,
  so a prompt run starts every server the repo's `.mcp.json` names, including
  commands, before anyone reviews them. Run `-p` only on repos and branches you
  trust, not on fork PRs or third-party repos.
- Each server's host must be on the sandbox's egress allowlist, or the server
  fails to connect.
- Never put keys or tokens in `.mcp.json`: it is committed and cloned into
  every sandbox. There is no safe way yet to give a server its own API key; a
  key you paste into the sandbox stays in it, so use a short-lived, narrowly
  scoped key if you must.

`claude mcp add` inside a sandbox works for that sandbox only. Your laptop's MCP
settings are not copied in.

## Commands

`droneship --help` lists every command.

### Account

```bash
droneship login                 # sign in through your browser
droneship login --headless      # print a code to approve from another device
droneship login --project NAME  # sign in to this project (name, slug, or id)
droneship logout                # discard this profile's stored credentials
droneship whoami                # identity, project, and which control plane
droneship connect               # GitHub, so private repos work
```

### Claude Code routines

Run [Claude Code routines](https://code.claude.com/docs/en/routines) in Droneship
sandboxes. Needs a Claude Team or Enterprise organization with self-hosted
environments turned on, and a Droneship plan other than Free or Trial. Create one in claude.ai → Admin settings → Cloud
environments, copy its key, then:

```bash
droneship routines enable      # paste the environment key (or pipe it in)
droneship routines             # is it connected?
droneship routines disable
```

Routines, claude.ai/code sessions and `claude --cloud` sessions sent to that
environment then run here, one sandbox per session, and show up in
`droneship list`. A sandbox ends soon after its session goes idle; a reply
resumes the session in a new one. Git goes through Anthropic, so no git
credentials are in the sandbox, and inference goes straight to Anthropic on the
routine owner's plan. `--policy-bundle` sets what the sandboxes may reach
(default `baseline-secure`); run `enable` again and press Enter at the key prompt
to change only that.

### Sandboxes

```bash
droneship run claude                   # Claude Code; reattaches if one is running
droneship run claude --new             # another sandbox, alongside the first
droneship run claude --id ID           # attach to a specific sandbox
droneship run claude --fresh           # discard this repo's saved workspace first
droneship run claude --ephemeral       # save nothing when the sandbox goes
droneship run claude -L 3000           # forward a sandbox port while you work
droneship run claude -p "<prompt>" -d  # run one prompt in the background
droneship resume claude                # pick up a previous conversation
droneship resume claude ID             # continue a stopped prompt run
droneship shell                        # interactive shell (your only sandbox)
droneship shell --id ID pwd            # one-off remote command
droneship shell -L 3000                # forward a sandbox port to this machine
droneship list                         # running sandboxes, with repo@branch (-v: every column)
droneship list -a                      # include stopped ones you can resume
droneship logs [-f] ID                 # its output, live or saved
droneship kill ID                      # shut one down; its saved workspace stays
droneship diff [--stat]                # what the agent changed: commits + uncommitted work
droneship pull                         # fetch it into a local branch, droneship/<branch>
droneship pr                           # push the sandbox's branch and open a PR
droneship workspace list [-v]          # saved workspaces (outlive the sandbox); -v adds Name
droneship workspace delete [NAME]      # permanently delete a saved workspace (this repo's when omitted)
```

`list` fits its columns to the terminal; scripts should read `droneship list --json`.

The agent's work comes back through git. Run these in your checkout of the
repo. `droneship diff` shows everything that isn't on GitHub yet: the sandbox's
commits since `origin/<branch>` (or `--base REF`), plus its uncommitted and
untracked files. On a terminal it opens in your pager, and
`droneship diff > x.patch` gives a plain patch. `droneship pull` fetches the
same work into the local branch `droneship/<branch>`, with any uncommitted
changes as one extra commit. It never touches your working tree or current
branch. Review with `git log`, then `git merge --ff-only droneship/<branch>`.
If you have committed on `droneship/<branch>` yourself, a later pull refuses
to replace it unless you pass `--force`.
`droneship pr` pushes the sandbox's branch from inside the sandbox. It refuses
if there are uncommitted changes or the branch is the default one. It then
opens the PR with your local `gh` if that is signed in, or prints GitHub's
compare page and opens it in your browser.

Prompt runs (`-p`) are one-off Claude Code tasks in a new sandbox that shuts down when done. Without `-d`, the CLI streams output and exits with the agent's exit code; with `-d`, it detaches (Ctrl-C also detaches without stopping the run). Stopped runs stay in `droneship list -a` for 7 days, as long as the snapshot exists, and can be resumed via `droneship resume claude ID`. Each prompt run gets a fresh clone of the current repo at the current branch (uncommitted local changes are not included) and never uses your saved workspace, so it can run while you work in your own sandbox on the same repo; to keep its changes, resume it and push. Prompt runs use the MCP servers in the repo's own `.mcp.json` (see [MCP servers](#mcp-servers)). Modal backend only. Billed with your own key through the AI gateway: with `ANTHROPIC_API_KEY` set in your shell, the run uses it (no `--byok` needed); with no key, the CLI stops before it starts a sandbox. Not with `--subscription` or `--direct`. A resumed run keeps the run's egress policy, isolation and time limit, and uses your key again (keys are never stored), or your plan with `--sub`. The model credential lives in a separate sidecar container, so the agent and its snapshot never hold it. A private repo's GitHub token is the exception: the agent needs it to push, so it stays in the sandbox. `droneship kill ID` on a stopped run deletes it and its snapshot; if the snapshot can't be deleted, it expires within 7 days.

Other agents wired today:

```bash
droneship run codex
droneship run python --entrypoint agent.py
droneship run langgraph
```

`python` and `langgraph` run **your** agent. The repo comes from your
checkout, its dependencies are installed at boot, and inference is governed
the same way as every other harness — with no Highflame-specific code in it.

See `droneship run --help` for the full agent list, egress bundles
(`--policy-bundle`), isolation floor (`--isolation`), and billing flags
(`--subscription`, `--byok`, `--no-byok`, `--direct`). With no billing flag,
a `-p` run of claude or codex uses your exported provider key (`ANTHROPIC_API_KEY`
or `OPENAI_API_KEY`). An interactive launch in a terminal signs in to your plan
inside the sandbox; add `--byok` to use your key there, as you must with no terminal. Names that are not
wired yet fail rather than provision a broken sandbox.

## Configuration

The CLI talks HTTPS to the control plane. It does not take cloud-provider
credentials. Optional overrides:

```bash
# Which stored login to use (also: droneship --profile NAME …)
DRONESHIP_PROFILE=default

# Set at login if you are not on the default deployment (api.highflame.ai)
DRONESHIP_API_URL=https://api-dev.highflame.dev
DRONESHIP_AUTH_URL=https://auth-dev.highflame.dev
```

`--api-url` and `--auth-url` live on `droneship login` only. A token minted
against one environment is never sent to another. Keep one profile per
deployment (`droneship --profile dev login --api-url ...`): `login` refuses to
move a signed-in profile to a different deployment unless you pass `--force`.

`DRONESHIP_AUTH_URL` / `--auth-url` is the AuthN issuer (`https://auth.highflame.ai`
by default). A profile, variable, or flag that still names a Studio origin
(`studio.highflame.ai`, `studio-dev.highflame.dev`, `localhost:3000`) is mapped
to its AuthN issuer automatically, so existing logins keep working.

## Development

```bash
git clone https://github.com/highflame-ai/highflame-forge.git
cd highflame-forge
uv sync --extra dev

uv run pytest tests/
uv run mypy src/highflame_forge
uv run ruff check src/
```

## License

MIT License — see [LICENSE](LICENSE) for details.
