Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -635,7 +635,7 @@ jobs:
# asserts contents and version and says nothing about the tag.
container: off
args: --release --locked --out dist
- name: The wheel is the two binaries, at the version rust/Cargo.toml names
- name: The wheel is the binaries, at the version rust/Cargo.toml names
run: |
set -euo pipefail
version=$(sed -n 's/^version = "\(.*\)"/\1/p' rust/Cargo.toml | head -1)
Expand All @@ -645,6 +645,7 @@ jobs:
/tmp/wheel-check/bin/pip install --quiet "$wheel"
test "$(/tmp/wheel-check/bin/dl --version)" = "dl $version"
test "$(/tmp/wheel-check/bin/aid --version)" = "aid $version"
test "$(/tmp/wheel-check/bin/dla --version)" = "aid $version"
- uses: prefix-dev/setup-pixi@v0.10.2
with:
run-install: false
Expand Down
7 changes: 4 additions & 3 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,8 @@ jobs:
uses: PyO3/maturin-action@v1
with:
# `packaging/wheel/pyproject.toml` is the published project: maturin
# bin bindings over `rust/dl`, whose two bin targets are `dl` and (behind
# the `wheel` feature) `aid`.
# bin bindings over `rust/dl`, whose bin targets are `dl` and (behind
# the `wheel` feature) `aid` and its second name `dla`.
working-directory: packaging/wheel
# Built in a manylinux container rather than on the runner, because the
# tag is computed from the glibc the binaries actually got linked
Expand Down Expand Up @@ -147,7 +147,7 @@ jobs:
run: |
set -euo pipefail
version='${{ steps.decide.outputs.version }}'
# Exactly one wheel, and it is the two binaries at the version this
# Exactly one wheel, and it is the binaries at the version this
# release is being cut at. A mismatch means the version was read from one
# place and the binaries built from another, which is the failure mode
# single-sourcing exists to remove -- so it stops here, before PyPI.
Expand All @@ -164,6 +164,7 @@ jobs:
/tmp/wheel-check/bin/pip install --quiet "$wheel"
test "$(/tmp/wheel-check/bin/dl --version)" = "dl $version"
test "$(/tmp/wheel-check/bin/aid --version)" = "aid $version"
test "$(/tmp/wheel-check/bin/dla --version)" = "aid $version"

- name: Publish to PyPI
if: steps.decide.outputs.publish == 'true'
Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- **`dla` is a second name for `aid`.** It is easier to type: `dl` and one letter. It is the
same program, built from the same source, so it takes the same flags, prints the same
messages and answers `dla --version` as `aid <version>`. The wheel, the conda package and
`./dev.sh` (as `dla-next`) ship it, and bash completion treats it as `aid`. An existing
install gets `dla` completion after it runs `dl --install` again.

## [0.59.4] - 2026-10-05

### Fixed
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,9 @@ aid <user/repo>[@branch|@<pr-link>] [prompt...]
aid resume [<workspace>]
```

`dla` is the same program under a name that is easier to type: every `aid` line in this
section works with `dla` in its place.

`aid` is a shortcut, not a second launcher. It rewrites its command line into a `dl` one, so

```bash
Expand Down
11 changes: 7 additions & 4 deletions conda.recipe/recipe.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,14 @@ source:

build:
number: 0
# No longer `noarch: python`: as of 0.1.0 the package is two compiled binaries
# No longer `noarch: python`: as of 0.1.0 the package is compiled binaries
# for linux-64 and carries no Python at all.
script:
# Both binaries come out of the `dl` package -- its own `dl`, and an `aid`
# compiled from `rust/aid/src/main.rs` behind the `wheel` feature, which is
# Every binary comes out of the `dl` package -- its own `dl`, and an `aid`
# (and `dla`, its second name) compiled from `rust/aid/src/main.rs` behind the
# `wheel` feature, which is
# the arrangement `rust/dl/Cargo.toml` explains and the one the PyPI wheel
# ships from too. One `cargo install` for both, so the two channels cannot
# ships from too. One `cargo install` for all of them, so the two channels cannot
# ship different sets.
#
# `--locked` is the point of building from a tag: the dependency versions are
Expand Down Expand Up @@ -64,6 +65,8 @@ tests:
# package was built and named under.
- test "$(dl --version)" = "dl ${{ version }}"
- test "$(aid --version)" = "aid ${{ version }}"
# `dla` is aid under a second name, so it answers as aid.
- test "$(dla --version)" = "aid ${{ version }}"

about:
homepage: https://github.com/blooop/devlaunch
Expand Down
4 changes: 3 additions & 1 deletion dev.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#!/bin/bash
# Development installation script for DevLaunch
# Builds this working tree and installs it as `dl-next` and `aid-next`, beside the
# Builds this working tree and installs it as `dl-next` and `aid-next` (and
# `dla-next`, aid's second name), beside the
# released `dl` and `aid` rather than on top of them, so both are on PATH at once
# and running the wrong one is not possible by accident. The released build is what
# keeps working while this checkout is mid-change; it is installed by pixi global
Expand Down Expand Up @@ -159,6 +160,7 @@ echo " dl-next --version # prints <version>-dev; plain dl does not"
echo " dl-next owner/repo # clone + DevPod workspace (default branch)"
echo " dl-next owner/repo@branch # clone + DevPod workspace (specific branch)"
echo " aid-next owner/repo@branch # ...with a coding agent started in it"
echo " dla-next owner/repo@branch # the same as aid-next, shorter to type"
echo ""
echo "Against throwaway state, leaving the real workspace list alone:"
echo " XDG_CACHE_HOME=/tmp/dl-scratch/cache dl-next ..."
Expand Down
4 changes: 2 additions & 2 deletions docs/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ and `--test lock_wait` kill real process trees, and a SIGKILLed process writes n
the `rust` job like everything else, and `pixi run coverage-rust` includes them locally.

Inside this repository's devcontainer, `pixi run dl` and `pixi run aid` are `cargo run` over the
working tree; on a host, `./dev.sh` installs it as `dl-next`/`aid-next` beside the released pair. Both
working tree; on a host, `./dev.sh` installs it as `dl-next`/`aid-next`/`dla-next` beside the released builds. All
print a `-dev` version so a working-tree build is never mistaken for a released one. See
[AGENTS.md](../AGENTS.md).

Expand Down Expand Up @@ -232,7 +232,7 @@ The two published artifacts are built from the same cargo package, both taking t
`rust/Cargo.toml`, the only place it is written down:

```bash
cd packaging/wheel && maturin build --release --locked -o dist # the PyPI wheel: two binaries
cd packaging/wheel && maturin build --release --locked -o dist # the PyPI wheel: dl, aid and dla
rattler-build build --experimental --recipe conda.recipe/recipe.yaml # the conda package
```

Expand Down
4 changes: 2 additions & 2 deletions packaging/wheel/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# The PyPI artifact: a wheel whose payload is the two compiled binaries.
# The PyPI artifact: a wheel whose payload is the compiled binaries.
#
# Why this is a second pyproject.toml rather than the repository's own. The root
# `pyproject.toml` is the pixi workspace and the tool configuration; since #267 it
Expand Down Expand Up @@ -30,7 +30,7 @@ description = "DevLaunch - A streamlined CLI for devpod workspaces"
readme = "README.md"
license = "MIT"
authors = [{ name = "Austin Gregg-Smith", email = "blooop@gmail.com" }]
# No dependencies, and no `requires-python`. The wheel carries two statically
# No dependencies, and no `requires-python`. The wheel carries statically
# linked binaries; there is no Python in it to run and nothing to import. `dl`
# needs `devpod` and `git` on PATH, which no Python metadata can express -- the
# conda package is where those are declared (`conda.recipe/recipe.yaml`).
Expand Down
8 changes: 5 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
# table whose version was read out of `rust/Cargo.toml` -- served a Python `dl`
# that no longer exists.
#
# What ships is a linux-64 wheel of the two compiled binaries, built by maturin
# from `packaging/wheel/pyproject.toml`, and a conda package of the same two
# binaries from `conda.recipe/recipe.yaml`. Both read their version from
# What ships is a linux-64 wheel of the compiled binaries (`dl`, `aid`, `dla`),
# built by maturin from `packaging/wheel/pyproject.toml`, and a conda package of
# the same binaries from `conda.recipe/recipe.yaml`. Both read their version from
# `rust/Cargo.toml`, which is the one place it lives; a release is one edit.
# Neither is built from here, and the `rust` feature below is not what builds them
# either: it is for building the crates under `rust/` from inside a container.
Expand Down Expand Up @@ -148,6 +148,8 @@ default = { features = ["test", "rust"], solve-group = "default" }
# does on the host: a build of somebody's tree says so wherever it runs.
dl = "cargo run -q --locked --manifest-path rust/Cargo.toml -p dl --bin dl --features dl/dev-build --"
aid = "cargo run -q --locked --manifest-path rust/Cargo.toml -p aid --bin aid --features aid/dev-build --"
# `dla` is aid under a second name (rust/aid/Cargo.toml), so it is the same task.
dla = "cargo run -q --locked --manifest-path rust/Cargo.toml -p aid --bin dla --features aid/dev-build --"
dev-add-docker = "python scripts/devpod_provider.py docker"
dev = { cmd = "devpod up . --ide none && ssh pythontemplate.devpod", depends-on = ["dev-add-docker"] }
dev-vs = { cmd = "devpod up . --ide vscode", depends-on = ["dev-add-docker"] }
Expand Down
23 changes: 23 additions & 0 deletions rust/aid/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,29 @@ edition.workspace = true
license.workspace = true
repository.workspace = true

# Two names for one program. `dla` is `aid` under a name that is easier to type
# (`dl` plus one letter), built from the same `main.rs`, so there is nothing for
# the two to disagree about: same flags, same messages, and `dla --version` still
# answers `aid <version>`. A copy rather than a symlink because a bin-wheel and a
# conda package ship files, and neither can carry a link. Naming the bins at all
# turns off cargo's discovery of `src/main.rs`, which is why `aid` is listed too.
#
# Cargo warns that `main.rs` is "present in multiple build targets". That warning
# is the arrangement, not a mistake in it. The ways around it cost more: a
# `dla.rs` that loads `main.rs` as a module breaks the `crate::` paths in the
# submodules, and `include!` refuses `main.rs`'s `//!` docs. `test = false`
# keeps a plain `cargo test` from running `main.rs`'s unit tests twice; an
# explicit `--bins`, which CI passes, selects `dla` anyway, and the second run
# costs a link and well under a second.
[[bin]]
name = "aid"
path = "src/main.rs"

[[bin]]
name = "dla"
path = "src/main.rs"
test = false

[dependencies]
# The only devlaunch crate named here, and deliberately: aid is a command-line
# rewrite that hands the result to dl, so what it can reach is whatever dl makes
Expand Down
31 changes: 31 additions & 0 deletions rust/aid/tests/rewrite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,37 @@ impl Run {
}
}

// ===========================================================================
// dla: aid under a second name
// ===========================================================================

#[test]
fn dla_answers_exactly_as_aid_does() {
// `dla` is a second bin target over the same `main.rs` (`aid/Cargo.toml`), so
// it is aid in every respect, its own name in `--version` included. The two
// lines here are the ones aid answers without a workspace, which is enough to
// prove the binary is aid's and not some other program wearing the name.
for args in [&["--version"][..], &["--help"][..]] {
let aid = Command::new(env!("CARGO_BIN_EXE_aid"))
.args(args)
.keeping_coverage()
.output()
.expect("the aid binary runs");
let dla = Command::new(env!("CARGO_BIN_EXE_dla"))
.args(args)
.keeping_coverage()
.output()
.expect("the dla binary runs");
assert!(aid.status.success(), "aid {args:?}: {aid:?}");
assert_eq!(dla.status.code(), aid.status.code(), "dla {args:?}");
assert_eq!(
String::from_utf8_lossy(&dla.stdout),
String::from_utf8_lossy(&aid.stdout),
"dla {args:?}"
);
}
}

// ===========================================================================
// the three answers aid gives on its own
// ===========================================================================
Expand Down
9 changes: 7 additions & 2 deletions rust/devlaunch-core/completions/dl.bash
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
#
# The same function serves `aid`, whose first argument is a dl workspace spec
# too. Only the flag list and what follows the spec differ, so the two places
# that care branch on $cmd rather than the script being copied for aid.
# that care branch on $cmd rather than the script being copied for aid. `dla` is
# aid under a second name and completes as aid.
_dl_completion() {
local cur prev opts
COMPREPLY=()
Expand Down Expand Up @@ -48,11 +49,14 @@ _dl_completion() {
fi
fi

# The command being completed: dl or aid.
# The command being completed: dl or aid. `dla` is aid's second name.
local cmd=""
if (( ${#words[@]} > 0 )); then
cmd="${words[0]##*/}"
fi
if [[ "$cmd" == dla ]]; then
cmd=aid
fi

# The options offered where a workspace spec goes.
#
Expand Down Expand Up @@ -375,4 +379,5 @@ _dl_completion() {
# Use -o default for better completion behavior
complete -o default -F _dl_completion dl
complete -o default -F _dl_completion aid
complete -o default -F _dl_completion dla
# end dl completion
12 changes: 10 additions & 2 deletions rust/dl/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,11 @@ serde_json = { workspace = true }
skim = { workspace = true }
term = { workspace = true }

# The released package ships two binaries, and a maturin bin-wheel ships the bin
# The released package ships three binaries (`dl`, `aid`, and `dla`, which is aid
# under a second name), and a maturin bin-wheel ships the bin
# targets of exactly *one* cargo package (maturin 1.14 asks cargo metadata for
# `root_package()` and takes its targets; there is no cross-package collection).
# So the package the wheel is built from has to own both bin targets, and this is
# So the package the wheel is built from has to own every bin target, and this is
# that package -- see `packaging/wheel/pyproject.toml`.
#
# Nothing forks: the bin below compiles the very `aid/src/main.rs` the `aid`
Expand Down Expand Up @@ -58,6 +59,13 @@ name = "aid"
path = "../aid/src/main.rs"
required-features = ["wheel"]

# `aid`'s second name, for the same reason it has one in `aid/Cargo.toml`.
[[bin]]
name = "dla"
path = "../aid/src/main.rs"
required-features = ["wheel"]
test = false

[dev-dependencies]
devlaunch-test-support = { workspace = true }
tempfile = { workspace = true }
Expand Down
13 changes: 7 additions & 6 deletions scripts/check_wheel.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
#!/usr/bin/env python3
"""Check a built devlaunch wheel before anything installs or publishes it.

The wheel is not a Python package any more: since 0.1.0 it is a container for two
compiled binaries, built by maturin from ``packaging/wheel/pyproject.toml``. That
The wheel is not a Python package any more: since 0.1.0 it is a container for
compiled binaries (`dl`, `aid`, and `dla`, which is aid under a second name), built by maturin from ``packaging/wheel/pyproject.toml``. That
makes two things worth asserting mechanically, both of which are silent failures
otherwise:

* **Both binaries are in there.** They come from *one* cargo package (`rust/dl`,
whose second bin target sits behind the ``wheel`` feature), so a lost feature
* **Every binary is in there.** They come from *one* cargo package (`rust/dl`,
whose `aid` and `dla` bin targets sit behind the ``wheel`` feature), so a lost feature
flag or a renamed target ships a wheel with `dl` and no `aid` -- a wheel that
installs cleanly and leaves half the tool missing.
* **The version in the wheel is the version being released.** It is read from
Expand All @@ -30,9 +30,10 @@
import zipfile
from pathlib import Path

# What the wheel must contain, and all it may contain beyond `.dist-info`: the two
# What the wheel must contain, and all it may contain beyond `.dist-info`: the
# binaries, as `.data/scripts/` entries that pip drops into the environment's bin/.
BINARIES = ("dl", "aid")
# `dla` is `aid` under a second name, built from the same source.
BINARIES = ("dl", "aid", "dla")


def check(wheel: Path, version: str) -> list[str]:
Expand Down
5 changes: 5 additions & 0 deletions test/test_bash_completion.py
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,11 @@ def test_aid_completes_the_same_workspaces_as_dl(self):
"""aid's first argument is a dl workspace spec, so it completes alike."""
assert self.run_completion("aid my-") == self.run_completion("dl my-")

def test_dla_completes_exactly_as_aid(self):
"""`dla` is aid's second name, so every line completes as aid's does."""
for line in ("dla my-", "dla --", "dla my-workspace ", "dla --codex my-"):
assert self.run_completion(line) == self.run_completion("aid" + line[3:]), line

def test_aid_completes_agent_flags(self):
"""Test completion of aid's own flags."""
completions = self.run_completion("aid --")
Expand Down
Loading