Launch Claude Code or Codex agents on GitHub repos with isolated workspaces and optional Docker dev environments
Project description
luv
A CLI that launches Claude Code or Codex agents on GitHub repos with isolated workspaces and optional Docker dev environments.
luv clones a repo, creates a branch, and drops you into a Claude session ready to work. When the repo ships a .luv/settings.json, it spins up Docker Compose automatically so every command runs in the right environment. Point it at a remote machine and the whole thing runs there in a tmux session you can detach from and reattach to later.
Install
# With uv (recommended)
uv tool install luv-cli
# With pip
pip install luv-cli
Requirements: GitHub CLI (gh) plus either Claude Code or Codex must be installed and authenticated.
Quick start
# One-time setup: default GitHub org, and optionally a remote machine
luv config
# Create a new workspace and launch Claude
luv my-repo "add user authentication"
# Launch Codex in YOLO mode instead
luv --codex my-repo "add user authentication"
# Select Claude explicitly (Claude remains the default)
luv --claude my-repo "add user authentication"
# Base a new workspace off a non-default branch
luv my-repo -b develop "add user authentication"
# Use a different org inline
luv other-org/my-repo "fix the bug"
# Reopen workspace #42
luv my-repo 42
# Open any GitHub PR by URL
luv -l https://github.com/org/repo/pull/123
# Open a shell instead of Claude
luv -n my-repo 42
# Resume last Claude session
luv -r my-repo 42
# Clean up fully-merged workspaces
luv --clean
# See what's running on your remote machine, and reattach
luv ls
luv continue
How it works
- Clones the repo into
~/prs/{repo}-{number}/ - Creates a new branch
luv-{number} - Configures the selected agent with the workspace's PR conventions
- Launches Claude with Opus 5 at max effort, or Codex in YOLO mode
All workspaces live under ~/prs/. The number comes from the repo's GitHub issue counter to avoid collisions.
With a remote host configured, steps 1–4 happen on that machine inside a tmux session instead — see Remote sessions.
Commands
| Command | Description |
|---|---|
luv config |
Interactive setup: remote host, SSH key, default org |
luv config set|get|unset <key> [value] |
Read or write a single setting |
luv config list |
Show all settings |
luv --init |
Configure default GitHub org only |
luv ls [--host H] [--prune] |
List live sessions across hosts |
luv continue [<repo> [number]] |
Attach to a live session |
luv [org/]<repo> [prompt...] |
Create a new workspace and launch Claude (default) |
luv --codex [org/]<repo> [prompt...] |
Create a workspace and launch Codex in YOLO mode |
luv [org/]<repo> -b <branch> [prompt...] |
Create a workspace based off <branch> instead of the default |
luv [org/]<repo> <number> [prompt] |
Reopen an existing workspace |
luv -l <PR URL> [prompt] |
Open any GitHub PR by URL |
luv [org/]<repo> -pr <number> [prompt] |
Open a PR by repo + number |
luv --clean |
Delete workspaces where the branch is fully pushed/merged |
luv --clean -f |
Force delete all workspaces |
luv --clean --safe -f |
Force delete only workspaces older than 24h |
Flags
| Flag | Description |
|---|---|
--claude |
Launch Claude Code (default) |
--codex |
Launch Codex with approvals and sandboxing bypassed (YOLO mode) |
-n |
Navigate: open a shell instead of Claude |
-r |
Resume: resume the selected agent's last session |
-p |
Launch Claude in plan permission mode (default: bypassPermissions) |
-nit |
Non-interactive: run the selected agent and exit (no REPL); Claude streams stream-json events to stdout |
-m MODEL |
Model to use; Claude defaults to claude-opus-5, while Codex uses its configured CLI default |
-b BRANCH |
Base a new workspace off BRANCH (clone + branch from it); recorded in git config luv.base so the PR can target it |
-e |
Env: pass LUV_* environment variables (with prefix stripped) into the session |
-s HOST |
Run on HOST over SSH, overriding the configured remote host |
-i PATH |
SSH identity file to use for this invocation |
--local |
Force local execution even when a remote host is configured |
-f, --force |
Skip safety checks (with --clean) |
--safe |
With --clean -f, only delete workspaces older than 24h (mtime) |
Docker dev environments
If a repo contains .luv/settings.json with a compose_file key, luv automatically starts a Docker Compose environment and runs Claude inside the dev-environment container.
Setup
1. Create .luv/settings.json in your repo:
{
"compose_file": ".luv/docker-compose.yml"
}
The compose_file path is relative to the repo root.
2. Create the Docker Compose file:
services:
dev-environment:
image: your-org/dev-env:latest
volumes:
- .:/workspace
working_dir: /workspace
stdin_open: true
tty: true
depends_on:
- postgres
postgres:
image: postgres:16
environment:
POSTGRES_PASSWORD: dev
The dev-environment service must have the selected agent CLI (claude or codex) installed in its image.
How Docker mode works
- Detects
.luv/settings.jsonwithcompose_filekey - Tears down any stale environment from a previous run
- Starts
docker compose up -d --buildwith a unique project name (luv-{repo}-{number}) for network/volume isolation - Verifies the
dev-environmentservice is running - Runs the selected agent inside the container via
docker compose exec - The repo is volume-mounted, so all file changes and git commits are visible on the host
- On exit (including Ctrl-C), tears down the environment with
docker compose down -v
Docker mode works with all flags: -n opens a bash shell in the container, -r resumes a Claude session in the container.
Remote sessions
Point luv at another machine and every workspace command runs there, inside a tmux session that survives disconnects.
luv config # set the remote host, SSH key, and org
luv myrepo "fix the flaky test" # clones and launches Claude on the remote
# ...close the laptop, go home...
luv ls # see what's still running
luv continue # reattach exactly where you left off
luv re-invokes itself on the remote over SSH, so every flag works there unchanged. The remote needs luv, tmux, gh, and git on its PATH.
Use --local for a one-off local run, -s HOST to target a different machine, and -i PATH for a different SSH key.
Full guide, including how to prepare a fresh Ubuntu box and set up SSH keys: docs/remote-sessions.md.
Workspace cleanup
luv --clean scans ~/prs/ and safely removes workspaces that are fully pushed. It checks:
- Working tree is clean (no uncommitted changes)
- No unpushed commits
- If the remote branch is gone, verifies the PR was merged and local HEAD matches
Use luv --clean -f to skip all safety checks and delete everything. Add --safe (i.e. luv --clean --safe -f) to restrict force-delete to workspaces whose folder mtime is older than 24 hours, leaving recently-touched workspaces alone.
Workspaces with a live tmux session are skipped unless you pass -f.
Configuration
Run luv config for interactive setup, or luv --init to set just your default GitHub org. Both save to ~/.luv/config.json:
{
"org": "exosphere",
"prs_dir": "~/prs",
"remote": {
"host": "box",
"identity_file": "~/.ssh/id_ed25519",
"dir": "~/prs"
}
}
You can also pass org/repo inline to override the default for any command (e.g., luv other-org/my-repo).
Every key, with defaults and resolution order: docs/configuration.md. The session registry that backs luv ls: docs/sessions.md.
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file luv_cli-0.1.0.tar.gz.
File metadata
- Download URL: luv_cli-0.1.0.tar.gz
- Upload date:
- Size: 35.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a1caeb55f8c3c8c2aa529aad4a6b3bb5d564ade0e869eaaea17a8adab8cc73e2
|
|
| MD5 |
cec8825fc8579b938867b6f0a0a3d401
|
|
| BLAKE2b-256 |
e05e695cef9fbd814f1479b0980ae0dbaca12c05c35155aab4aacc7bd64435d7
|
Provenance
The following attestation bundles were made for luv_cli-0.1.0.tar.gz:
Publisher:
publish.yml on FailproofAI/luv
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
luv_cli-0.1.0.tar.gz -
Subject digest:
a1caeb55f8c3c8c2aa529aad4a6b3bb5d564ade0e869eaaea17a8adab8cc73e2 - Sigstore transparency entry: 2251252444
- Sigstore integration time:
-
Permalink:
FailproofAI/luv@0255afcf5a1fe86023915ca206632c620a72acbe -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/FailproofAI
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@0255afcf5a1fe86023915ca206632c620a72acbe -
Trigger Event:
push
-
Statement type:
File details
Details for the file luv_cli-0.1.0-py3-none-any.whl.
File metadata
- Download URL: luv_cli-0.1.0-py3-none-any.whl
- Upload date:
- Size: 23.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
11bccb61d6d2dc2855da89860e61bcb70d9854cac2c3ac3e22775752f1840215
|
|
| MD5 |
e0690115f6ef4064497e2f69b6254c1e
|
|
| BLAKE2b-256 |
c7209ccda5d05f8bcf4ecb932e06b93535a3aab6f6df6b0bdbe1af57b3f4051e
|
Provenance
The following attestation bundles were made for luv_cli-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on FailproofAI/luv
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
luv_cli-0.1.0-py3-none-any.whl -
Subject digest:
11bccb61d6d2dc2855da89860e61bcb70d9854cac2c3ac3e22775752f1840215 - Sigstore transparency entry: 2251253088
- Sigstore integration time:
-
Permalink:
FailproofAI/luv@0255afcf5a1fe86023915ca206632c620a72acbe -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/FailproofAI
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@0255afcf5a1fe86023915ca206632c620a72acbe -
Trigger Event:
push
-
Statement type: