Skip to main content

devlaunch

A streamlined CLI for devpod with intuitive autocomplete and fzf fuzzy selection.

Continuous Integration Status

Ci Codecov GitHub issues GitHub pull-requests merged GitHub release PyPI Conda License Python Pixi Badge

Installation

Pixi (Recommended)

pixi global install --channel conda-forge --channel https://prefix.dev/blooop devlaunch

This installs devlaunch along with devpod and all dependencies automatically.

Pip

pip install devlaunch

Note: When using pip, you must install devpod separately. If devpod is not on PATH, every command that needs it prints a single install hint on stderr and exits 127 (the shell's "command not found" code). dl --help and dl --version keep working without it.

Shell Completions

After installation, set up shell completions for dl and aid:

dl --install
source ~/.bashrc  # or restart your terminal

Usage

dl                               # Interactive workspace selector (fzf)
dl <user/repo>                   # Start workspace and attach shell
dl <user/repo> <cmd>             # Run workspace command (stop, code, etc.)
dl <user/repo> -- <command>      # Run shell command in workspace

Commands that need a terminal

dl <ws> -- <command> gives the command a terminal whenever dl itself has one, so interactive programs — a coding agent, htop, git rebase -i, a REPL — start and stay up instead of exiting immediately. Redirect the output and the terminal goes away again, so dl <ws> -- ls > files.txt stays free of escape sequences.

This needs the ssh host alias devpod up writes to ~/.ssh/config. If a workspace has none, dl says so and falls back to the plain devpod ssh transport, which has no terminal; dl <ws> restart republishes the alias. Set DEVLAUNCH_NO_TTY=1 to force the fallback everywhere.

aid: start a coding agent in a workspace

aid is dl with a coding agent started for you:

aid <user/repo>[@branch] [prompt...]   # Open the workspace, start the agent

It is a shortcut, not a second launcher. aid rewrites its command line into a dl one and hands it to dl itself, so

aid blooop/devlaunch@fix/42 fix the flaky test

is exactly

dl blooop/devlaunch@fix/42 -- claude 'fix the flaky test'

That means an aid workspace is the dl workspace: same clone, same workspace id, same container — started if stopped, attached to if already running, and never rebuilt just because aid asked for it. Anything dl learns, aid gets.

Option Description
--claude, --codex, --gemini Pick the agent (default: claude)
--devcontainer <variant|path> Passed through to dl
DEVLAUNCH_AID_AGENT=<agent> Change the default agent

Everything after the workspace is the prompt, flags and all, so it never needs quoting to survive aid's own parsing. Managing workspaces — listing, stopping, deleting, VS Code — stays with dl.

The agent's CLI has to be installed in the container; aid runs it there, it does not install it.

Workspace Sources

dl myproject                     # Existing workspace by name
dl user/repo                     # Create from GitHub repo
dl user/repo@branch              # Create from specific branch
dl ./path                        # Create from local path

Workspace IDs

dl user/repo@branch derives one id that names both the devpod workspace (what you see in dl --ls) and the clone directory under ~/.cache/devlaunch/repos/:

<repo-slug>-<branch-slug>-<syllables>      at most 38 characters

blooop/devlaunch@main                             -> devlaunch-main-zovomobo
blooop/devlaunch@feature/auth                     -> devlaunch-feature-auth-poliseno
blooop/devlaunch@feature-auth                     -> devlaunch-feature-auth-nesatabe
blooop/test_renv@nb4                              -> test-renv-nb4-polenita
kinisi-robotics/kinisi_ros@ags-devcontainer-tooling-support
                                                  -> kinisi-ros-ags-devcontainer-t-lenevere
blooop/devlaunch@dependabot/github_actions/codecov/codecov-action-6
                                                  -> devlaunch-dependabot-codecov-sifivasa

The eight-character syllable suffix is a hash of the full (owner, repo, branch) triple. It is what makes the id unique: the readable part is shortened to fit the length limit, and shortening it does not affect whether two branches share an id. Long branch names drop whole /-separated middle segments before losing characters, so the part that identifies the branch survives. Note the third and fourth lines above: feature/auth and feature-auth read the same once slugged but are different branches, and they get different ids.

Owner and repo are matched case-insensitively, the way GitHub treats them, so dl NVIDIA/cuda-samples@main and dl nvidia/cuda-samples@main are the same workspace. Branch names are case-sensitive, because git refs are.

URL specs (dl github.com/owner/repo) get an id in the same shape, with the suffix hashed over the URL.

The id is also the container hostname, so it stays well inside the 38-character budget to leave room for tools that add their own prefixes.

Branch names must be safe as both git refs and directory names — a name with a space or a leading dash is rejected rather than quietly rewritten.

Upgrading from an older devlaunch

This id format is new, and the directories and containers on your machine were named by the previous scheme. The first dl user/repo… command after upgrading migrates the cache once and prints what it did. dl --help, dl --version, dl --ls and opening an existing workspace by name do not trigger it.

Your clone directories are renamed. What was ~/.cache/devlaunch/repos/blooop/devlaunch/main becomes ~/.cache/devlaunch/repos/blooop/devlaunch/devlaunch-main-zovomobo. A workspace is a git clone whose origin points at the .bare cache next to it, and .bare does not move, so this is a plain rename: branches, history and uncommitted changes all survive — only the folder name changes. metadata.json is updated in the same pass, so nothing is left pointing at the old name.

Your existing devpod containers keep their old ids and are orphaned. The next dl user/repo@branch builds a fresh container under the new id. dl does not delete containers for you — deleting by id is how a running sidecar got destroyed the last time something tried (kinisi_ros#9766) — so it prints a one-line notice with the count and writes the old ids to ~/.cache/devlaunch/orphaned-workspaces.txt. Remove them when you are ready:

xargs -r -n1 devpod delete < ~/.cache/devlaunch/orphaned-workspaces.txt

A clone directory with no metadata record is left alone. Nothing records which branch it was cloned for, and the old directory name cannot be turned back into one — feature/auth and feature-auth both became feature-auth — so a guessed name would be worse than no rename. Those directories stay exactly where they are and are listed in ~/.cache/devlaunch/unmigrated-clones.txt.

Running dl again changes nothing: the migration is keyed on the version field in metadata.json, not on directory names, so a branch that happens to look like a new-scheme id is never mistaken for one. If a migration is interrupted, the next run finishes it — the version is written last, in the same atomic save as the new paths, so it never claims more than the filesystem has actually done.

Workspace Commands

Command Description
dl <user/repo> stop Stop the workspace
dl <user/repo> rm, prune Delete the workspace
dl <user/repo> code Open in VS Code
dl <user/repo> restart Stop and start (no rebuild)
dl <user/repo> recreate Recreate container
dl <user/repo> reset Clean slate (remove all, recreate)
dl <user/repo> -- <command> Run shell command in workspace (with a terminal, when dl has one)

Options

Option Description
--devcontainer <variant|path> Use a non-default devcontainer.json. A bare name means .devcontainer/<name>/devcontainer.json. Stored with the workspace, so pass it once.
DEVLAUNCH_NO_TTY=1 Never give a workspace command a terminal; always use the plain devpod ssh transport.

Projects with demanding devcontainers — several variants, compose sidecars, or a host-side initializeCommand that has to tell branch workspaces apart — are covered in docs/devcontainer-projects.md.

GitHub Authentication

Every workspace dl opens inherits the host's GitHub login, so gh is already authenticated inside the container and the devcontainer.json does not have to arrange anything for it. devpod forwards the ssh agent and git credentials on its own, but nothing else carries gh.

devlaunch takes the token from GH_TOKEN, GITHUB_TOKEN, or gh auth token, whichever answers first, and hands it to the container as GH_TOKEN. That reaches any image and any container user, unlike a bind-mount of ~/.config/gh, and it works whether the host keeps its token in hosts.yml or in a keyring. The token is passed to devpod through a private file and through devpod's own environment, never on a command line, so it does not appear in ps. dl installs gh itself (see Tools in every workspace), so the login has something to be spent on whatever the image ships. Check a workspace with:

dl <workspace> -- gh auth status

Who gets the token

Everything running in the container does — including a postCreateCommand from a repo you did not write. dl someone/repo builds and runs that project's devcontainer with your GitHub token in its environment, and a gh auth login token usually carries repo, workflow, gist and read:org scopes. devpod already forwards the ssh agent to every workspace, so this is not a new trust boundary, but it is a wider one. Skip it for a repo you have not read:

DEVLAUNCH_NO_GH_TOKEN=1 dl someone/repo
Variable Description
DEVLAUNCH_NO_GH_TOKEN=1 Do not forward the host's GitHub login into workspaces

When the token changes

dl refreshes the token on every start, so rotating it on the host is enough for any workspace that gets started or restarted afterwards. Attaching to a workspace that is already running skips that step, and the token it was given at startup stays in place — including one it was given before you set DEVLAUNCH_NO_GH_TOKEN. Run dl <workspace> restart to replace it.

Tools in every workspace

gh and claude are available in every workspace dl opens, in every kind of session — an interactive dl <workspace>, a one-shot dl <workspace> -- <command>, and aid. The repo's devcontainer.json does not have to provide them, and most do not: dl launches arbitrary repos, so a guarantee that depended on the image would not be a guarantee.

They are installed with pixi global on devpod up, and put on the PATH of a login shell through whichever of ~/.bash_profile, ~/.bash_login or ~/.profile bash actually reads — it sources only the first of those that exists, so an image shipping a ~/.bash_profile never reads ~/.profile. A workspace that already has both is left alone — the check runs first, so the cost after the first launch is one round-trip and no network. If pixi is missing from the image, dl installs that too.

An install that fails costs the workspace its tools, not its launch: dl logs a warning and hands you the session anyway.

DEVLAUNCH_NO_TOOLS=1 dl someone/repo
Variable Description
DEVLAUNCH_NO_TOOLS=1 Do not install gh or claude into workspaces

Attaching to a workspace that is already running skips devpod up, and so skips this too. A workspace started by something other than dl — or created before this existed — picks the tools up on its next dl <workspace> restart.

Global Commands

Command Description
dl --ls List all workspaces
dl --install Install shell completions
dl --purge [-y] Remove all devlaunch data
dl --prune-worktrees [days] Remove unused worktrees (default: 30 days)
dl --refresh Refresh completion cache
dl --help, -h Show this help
dl --version Show version (an editable install also names the tree it runs from)

A released install prints the version and nothing else. An install made in editable mode says so and names the checkout it resolves to, so two builds of the same version are told apart at a glance:

$ dl --version
dl 0.0.9

$ dl-next --version          # editable install of a working tree
dl 0.0.9 (dev, editable from /path/to/your/devlaunch)

aid --version reports the same thing under its own name. The provenance comes from the installed package's own PEP 610 metadata; an install that records none just prints the bare version.

Examples

dl                               # Select workspace with fzf
dl devpod                        # Open existing workspace
dl loft-sh/devpod                # Create from GitHub
dl blooop/devlaunch@main         # Create from specific branch
dl ./my-project                  # Create from local folder
dl blooop/devlaunch code         # Open in VS Code
dl blooop/devlaunch -- make test # Run command in workspace
dl blooop/devlaunch stop         # Stop workspace

Features

  • Fuzzy Selection: When called without arguments, uses fzf for interactive workspace selection
  • Smart Completion: Tab completion for workspaces, GitHub repos (owner/repo format), and paths
  • GitHub Shorthand: Use owner/repo instead of full URLs - automatically expands to github.com/owner/repo
  • Branch Support: Specify branches with owner/repo@branch syntax
  • Fast Autocomplete: Completion cache for ~3ms response time (vs ~700ms without cache)
  • One Round-Trip Per Question: every devpod call costs ~0.45s, far more than dl itself, so a command reads the workspace list at most once — and dl <ws> -- <cmd> skips the extra round-trip that names an interactive prompt, since a one-shot command has none

Worktree Backend

For git repositories, devlaunch uses an efficient worktree backend by default:

  • Efficient Storage: Repos are cloned once to ~/.cache/devlaunch/repos/owner/repo/, then git worktrees are created for each branch
  • Shared Git Objects: All branches share git objects, saving disk space
  • Lazy Fetch: Remote updates are only fetched if the configured interval has elapsed (default: 1 hour)

Container Sharing Mode

Use --shared to share a single container across multiple branches of the same repo:

dl --shared owner/repo@branch1  # Creates container "owner-repo"
dl --shared owner/repo@branch2  # Reuses "owner-repo" container

Pre-warming

Use --warm to prepare a workspace without attaching a shell:

dl --warm owner/repo@branch  # Creates container in background

Shell Completion

After running dl --install, you get intelligent tab completion:

  • Workspace names from your devpod list
  • Known GitHub owners and repositories from your workspaces
  • File/directory paths when starting with ./, /, or ~
  • All global flags (--ls, --install, etc.) and workspace commands

How the completion cache stays current

The data behind completions lives in ~/.cache/devlaunch/completions.json, and building it means a git ls-remote per known repo — seconds of work. So it is rebuilt in the background at most once an hour (the same interval the worktree backend uses for lazy fetches), and at most once per dl invocation. Commands that change your workspaces (starting, stopping or deleting one) rebuild it as soon as they finish, regardless of when it was last built. Commands with no use for it — dl --help, dl --version — do not touch it at all.

A branch created on a remote in the last hour may therefore not be offered yet. dl --refresh rebuilds the cache immediately and ignores the interval.

Development

This project uses pixi for environment management.

# Run tests
pixi run test

# Run full CI suite
pixi run ci

# Format and lint
pixi run style

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

devlaunch-0.0.14.tar.gz (68.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

devlaunch-0.0.14-py2.py3-none-any.whl (76.4 kB view details)

Uploaded Python 2Python 3

File details

Details for the file devlaunch-0.0.14.tar.gz.

File metadata

  • Download URL: devlaunch-0.0.14.tar.gz
  • Upload date:
  • Size: 68.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.12.13

File hashes

Hashes for devlaunch-0.0.14.tar.gz
Algorithm Hash digest
SHA256 070f8497ccb766b1eba81ffd02791104a70899e0d23be61afd9c0df3f0ecb0bd
MD5 2b7fe346657b54a502e459253adb8eb7
BLAKE2b-256 c648527275a7abac3d85dfd4a7de19de3cf6a9ecda9b4eef94ea4aa5726deae2

See more details on using hashes here.

File details

Details for the file devlaunch-0.0.14-py2.py3-none-any.whl.

File metadata

  • Download URL: devlaunch-0.0.14-py2.py3-none-any.whl
  • Upload date:
  • Size: 76.4 kB
  • Tags: Python 2, Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.12.13

File hashes

Hashes for devlaunch-0.0.14-py2.py3-none-any.whl
Algorithm Hash digest
SHA256 9d04f7be6cf59e163a9cb816c6036c71dd3633096509d55c2e10a90d305921a5
MD5 08eeda6d58b582de4d61f5c2d6ba8772
BLAKE2b-256 65330a2c4caffcf7c88db0fefc14a7451bc464d8b55f7ba4779dd172872ae319

See more details on using hashes here.

Release history Release notifications | RSS feed

0.25.0

1 file

0.24.0

1 file

0.23.0

1 file

0.22.0

1 file

0.21.0

1 file

0.20.0

1 file

0.19.1

1 file

0.19.0

1 file

0.18.0

1 file

0.17.0

1 file

0.16.0

1 file

0.15.0

1 file

0.14.0

1 file

0.13.0

1 file

0.12.0

1 file

0.11.0

1 file

0.10.0

1 file

0.9.0

1 file

0.8.0

1 file

0.7.3

1 file

0.7.2

1 file

0.7.1

1 file

0.7.0

1 file

0.6.1

1 file

0.6.0

1 file

0.5.0

1 file

0.4.1

1 file

0.4.0

1 file

0.3.3

1 file

0.3.2

1 file

0.3.1

1 file

0.3.0

1 file

0.2.2

1 file

0.2.1

1 file

0.2.0

1 file

0.1.2

1 file

0.1.1

1 file

0.1.0

1 file

0.0.29

2 files

0.0.28

2 files

0.0.27

2 files

0.0.26

2 files

0.0.25

2 files

0.0.24

2 files

0.0.23

2 files

0.0.22

2 files

0.0.21

2 files

0.0.20

2 files

0.0.19

2 files

0.0.18

2 files

0.0.17

2 files

0.0.16

2 files

0.0.15

2 files

This release

0.0.14 This release

2 files

0.0.13

2 files

0.0.12

2 files

0.0.11

2 files

0.0.10

2 files

0.0.9

2 files

0.0.8

2 files

0.0.7

2 files

0.0.6

2 files

0.0.5

2 files

0.0.4

2 files

0.0.3

2 files

0.0.2

2 files

0.0.1

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page