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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.59.1] - 2026-10-01

### Fixed

- **`rm` no longer counts a commit and the commit that reverts it** (#664). Neither has a
Expand All @@ -19,6 +21,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
and so does a second commit that grew from it, such as a branch or the stash. A merge,
a root commit, an empty commit and a revert of a commit that another rule cleared
still count, and so does an earlier draft of a commit that was edited later.
- **`aid <workspace>` goes straight to the prompt editor again** (#666). 0.59.0 asked for the
agent, the model and the effort on every promptless launch on a terminal. Only a line
with no workspace, such as a bare `aid` or `aid --codex`, is asked now. A line that
names its workspace starts the agent on its defaults and the line's flags, as 0.58.0 did.

## [0.59.0] - 2026-09-30

Expand Down
13 changes: 7 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ one argument instead of a clone, a config file and a build command.
[![GitHub pull-requests merged](https://badgen.net/github/merged-prs/blooop/devlaunch)](https://github.com/blooop/devlaunch/pulls?q=is%3Amerged)
[![GitHub release](https://img.shields.io/github/release/blooop/devlaunch.svg)](https://GitHub.com/blooop/devlaunch/releases/)
[![PyPI](https://img.shields.io/pypi/v/devlaunch)](https://pypi.org/project/devlaunch/)
[![Conda](https://img.shields.io/badge/conda-v0.59.0-brightgreen?logo=anaconda)](https://prefix.dev/channels/blooop/packages/devlaunch)
[![Conda](https://img.shields.io/badge/conda-v0.59.1-brightgreen?logo=anaconda)](https://prefix.dev/channels/blooop/packages/devlaunch)
[![License](https://img.shields.io/github/license/blooop/devlaunch)](https://opensource.org/license/mit/)
[![Platform](https://img.shields.io/badge/platform-linux--64-blue)](https://github.com/blooop/devlaunch/releases)
[![Pixi Badge](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/prefix-dev/pixi/main/assets/badge/v0.json)](https://pixi.sh)
Expand Down Expand Up @@ -341,7 +341,7 @@ clone, and [docs/cleanup.md](docs/cleanup.md) says what it carries one past and

```bash
$ dl --version
dl 0.59.0
dl 0.59.1
```

`--devcontainer <variant|path>` picks a non-default `devcontainer.json`. A bare name means
Expand Down Expand Up @@ -411,10 +411,11 @@ With no workspace on the line, `aid` on a terminal lets you pick one of your wor
does. With no prompt on the line, `aid` starts the container booting and asks for the prompt while it
does. Type it free of shell quoting, with no escaping and no history expansion eating a `!`. A
paste keeps its line breaks and can be any length, Alt-Enter or Ctrl-J adds a line, and an empty
Enter starts the agent's plain session. Before the prompt it asks for the agent (one row per Claude
login, then `codex` and `gemini`), the model and the effort, each in a picker that lists your
recent choices first, so one Enter repeats the last launch. A flag on the line skips its picker,
and Esc stops the boot. See [docs/cli.md](docs/cli.md#the-pickers-ahead-of-the-prompt). Piping
Enter starts the agent's plain session. A bare `aid`, which picks its workspace, also asks before
the prompt for the agent (one row per Claude login, then `codex` and `gemini`), the model and the
effort, each in a picker that lists your recent choices first, so one Enter repeats the last
launch. A flag on the line skips its picker, and Esc stops the boot. `aid <workspace>` asks for
none of them and goes straight to the prompt. See [docs/cli.md](docs/cli.md#the-pickers-ahead-of-the-prompt). Piping
stdin or setting `DEVLAUNCH_NO_TTY=1` skips the question and launches one-shot, so scripts behave
as they always have.

Expand Down
12 changes: 9 additions & 3 deletions docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -759,8 +759,14 @@ Remote Control. A value that is missing, empty or starts with `-` is refused too

## The pickers ahead of the prompt

`aid <workspace>` with no prompt on a terminal asks for up to three settings before
it opens the prompt editor. The workspace boots in the background the whole time.
`aid` with no workspace and no prompt on a terminal asks for up to three settings
after the workspace picker and before it opens the prompt editor. The workspace
boots in the background the whole time. A line of flags with no workspace, such as
`aid --codex`, is asked the same way.

`aid <workspace>` asks for none of them. A line that names its workspace said what
it wanted, so it goes straight to the prompt editor, and every setting its flags
left open stays at the agent's default.

1. **The agent**, with one row per Claude login and one row for each other agent:

Expand Down Expand Up @@ -797,7 +803,7 @@ Where the name is part of a listed row, such as `gpt-5.5` beside `gpt-5.5-codex`
Alt-Enter uses the text as typed instead of the row.

A setting that a flag on the line already gave is not asked for, so
`aid --model opus --effort max <ws>` goes straight to the prompt. A line with a
`aid --model opus --effort max` asks for the workspace and the agent only. A line with a
prompt on it asks for nothing, as before.

**Esc cancels the launch.** In a picker Esc and Ctrl-C are keys, not signals, so
Expand Down
10 changes: 5 additions & 5 deletions rust/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion rust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ members = [
# The single source of the version (docs/rust-rewrite-plan.md: cutover ships
# 0.1.0, version read from Cargo.toml).
[workspace.package]
version = "0.59.0"
version = "0.59.1"
edition = "2024"
license = "MIT"
repository = "https://github.com/blooop/devlaunch"
Expand Down
25 changes: 22 additions & 3 deletions rust/aid/src/interactive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,17 @@
}
}

/// Whether the agent, model and effort pickers run before the editor.
///
/// Only a bare `aid`, or a line of flags with no workspace, asks. A line that
/// names its workspace already said what it wanted, so it gets the editor alone
/// and every setting it left open stays the agent's default.
#[derive(Clone, Copy)]
pub(crate) enum Pickers {
Ask,
Skip,
}

/// How the interactive flow ended.
pub(crate) enum Collected {
/// Launch this line, once the boot beside it, if any, has finished.
Expand All @@ -187,10 +198,12 @@
///
/// `argv` and `environment` are what `parsed` was parsed from: the agent picker
/// builds its rows by parsing the line again with each row's flags in front.
/// `pickers` says whether those pickers run at all.
pub(crate) fn collect_prompt(
parsed: AidArgs,
argv: &[String],
environment: Environment<'_>,
pickers: Pickers,
) -> Collected {
let promptless_agent = matches!(
&parsed.task,
Expand All @@ -216,9 +229,15 @@
// The pickers come after the boot has started, so the minute they take is
// also spent booting. Nothing they choose reaches the boot: `up` takes no
// model, and `--claude-profile` is read when the session starts, not at `up`.
let Some(parsed) = settle(parsed, argv, environment) else {
boot.cancel();
return Collected::Cancelled;
let parsed = match pickers {
Pickers::Skip => parsed,

Check warning on line 233 in rust/aid/src/interactive.rs

View check run for this annotation

Codecov / codecov/patch

rust/aid/src/interactive.rs#L232-L233

Added lines #L232 - L233 were not covered by tests
Pickers::Ask => {
let Some(parsed) = settle(parsed, argv, environment) else {
boot.cancel();
return Collected::Cancelled;

Check warning on line 237 in rust/aid/src/interactive.rs

View check run for this annotation

Codecov / codecov/patch

rust/aid/src/interactive.rs#L235-L237

Added lines #L235 - L237 were not covered by tests
};
parsed

Check warning on line 239 in rust/aid/src/interactive.rs

View check run for this annotation

Codecov / codecov/patch

rust/aid/src/interactive.rs#L239

Added line #L239 was not covered by tests
}
};
banner(&parsed);
match dl::read_prompt() {
Expand Down
33 changes: 22 additions & 11 deletions rust/aid/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,14 +150,22 @@
// id is added to the end of the line, after the leading flags, which is where a
// typed spec goes, and from here on the line is that longer one, because the
// agent picker parses it again.
//
// Only that last pick, a workspace for a line that named none, earns the agent,
// model and effort pickers: a line that names its workspace goes straight to
// the editor.
let with_pick: Vec<String>;
let (argv, parsed) = match rewrite::parse_aid_args(argv, environment) {
Ok(rewrite::Line::Ready(parsed)) => (argv, parsed),
let (argv, parsed, pickers) = match rewrite::parse_aid_args(argv, environment) {
Ok(rewrite::Line::Ready(parsed)) => (argv, parsed, interactive::Pickers::Skip),
// `aid resume` with no workspace. The pick comes before everything below,
// which is all about one named workspace, so from here on this line is an
// `aid resume <id>` like any other.
Ok(rewrite::Line::Unpicked(unpicked)) => match dl::pick_workspace() {
Ok(workspace_id) => (argv, unpicked.picked(workspace_id)),
Ok(workspace_id) => (
argv,
unpicked.picked(workspace_id),
interactive::Pickers::Skip,
),

Check warning on line 168 in rust/aid/src/main.rs

View check run for this annotation

Codecov / codecov/patch

rust/aid/src/main.rs#L164-L168

Added lines #L164 - L168 were not covered by tests
Err(code) => return code,
},
Err(UsageError::NoWorkspace)
Expand All @@ -169,7 +177,9 @@
};
with_pick = argv.iter().cloned().chain([spec]).collect();
match rewrite::parse_aid_args(&with_pick, environment) {
Ok(rewrite::Line::Ready(parsed)) => (with_pick.as_slice(), parsed),
Ok(rewrite::Line::Ready(parsed)) => {
(with_pick.as_slice(), parsed, interactive::Pickers::Ask)

Check warning on line 181 in rust/aid/src/main.rs

View check run for this annotation

Codecov / codecov/patch

rust/aid/src/main.rs#L180-L181

Added lines #L180 - L181 were not covered by tests
}
// A line that names its workspace is never unpicked.
Ok(rewrite::Line::Unpicked(_)) => {
eprintln!("{}", refusal(&UsageError::NoWorkspace));
Expand Down Expand Up @@ -199,7 +209,7 @@
Ok(spec) => parsed.with_spec(spec),
Err(code) => return code,
};
let (parsed, boot) = match interactive::collect_prompt(parsed, argv, environment) {
let (parsed, boot) = match interactive::collect_prompt(parsed, argv, environment, pickers) {
interactive::Collected::Launch(parsed, boot) => (*parsed, boot),
// 130, the code a Ctrl-C at the prompt editor ends with, because a cancel
// in a picker is the same request made with a different key.
Expand Down Expand Up @@ -408,14 +418,15 @@
reopens its latest session.

With no workspace on a terminal, aid lets you pick one of your workspaces, as
dl does.
dl does. Then it asks for each setting the line left open: the agent (one row
per Claude login, then each other agent), the model and the effort. Each
picker lists your recent choices first, so one Enter repeats the last launch.
Type a name that is not listed to use it. Alt-Enter uses the text as typed
where it is part of a listed name. A line that names its workspace asks for
none of these, and the agent starts on its defaults and the line's flags.

With no prompt on a terminal, aid boots the workspace in the background and
asks for the prompt while it does. First it asks for each setting the line
left open: the agent (one row per Claude login, then each other agent), the
model and the effort. Each picker lists your recent choices first, so one
Enter repeats the last launch. Type a name that is not listed to use it.
Alt-Enter uses the text as typed where it is part of a listed name.
asks for the prompt while it does.

Then type the prompt free of shell quoting and press Enter to launch. A paste
keeps its line breaks, and Alt-Enter or Ctrl-J adds a line. An empty Enter
Expand Down
Loading
Loading