Ship coder-lite as the binary, answering both surfaces

dc06db01cfbf · AtlantisPleb · · parent db6beb978105

Ship coder-lite as the binary, answering both surfaces

`#105` made coder-lite the coder UI; the release still built
`openagents-cli`, so the shipped binary was the one nobody was meant to use.

Shipping coder-lite alone would have been a regression: it refused every
subcommand, so `issue`, `repo`, `auth`, `box` and the rest would have left the
release — including `update`, which means an installed build could not have
replaced itself. So the binary now answers both. Bare, it is the coder session.
Given a command `openagents-cli` defines, it dispatches to that command.

The subcommand set is read out of clap rather than kept by hand here, so a
command added to `openagents-cli` is reachable the moment it exists and one
removed stops being claimed. A hand-kept list is how the two surfaces would
drift, and drift between two copies of the same thing is what this pass has
been removing everywhere else.

`--version` reports `openagents_cli::VERSION` rather than `CARGO_PKG_VERSION`,
because `update` compares what the binary says against what the channel
resolves to. A binary published as 0.0.2 that reported 0.1.0 would make every
update either a no-op or a reinstall depending on which way the comparison
fell.

Deploy story

What this commit did to the running system — joined from the forge receipt chain, the part a commit page elsewhere cannot show.

Not deployed through the forge lane

No push, promotion, build, or deploy receipt references this commit (receipts are scanned over a bounded recent window). Changes shipped by full node replacement carry their proof in the release gate receipt instead.

Changed files

  • modified Cargo.lock
  • modified crates/coder-lite/Cargo.toml
  • modified crates/coder-lite/src/main.rs
  • modified ops/release-cli.sh

Diff

4 files changed, +48 -4

Cargo.lock modified +1

@@ -414,6 +414,7 @@ dependencies = [

414 414
 "anstyle",
415 415
 "anstyle-lossy",
416 416
 "anstyle-syntect",
417
 "clap",
417 418
 "crossterm",
418 419
 "futures",
419 420
 "html-escape",
crates/coder-lite/Cargo.toml modified +3

@@ -21,6 +21,9 @@ path = "src/main.rs"

21 21
# from it. What coder-lite owns is the front: the frame, the palette, the
22 22
# spinner, the system prompt, and the session loop in `interactive.rs`.
23 23
openagents-cli = { path = "../openagents-cli" }
24
# Only to read the CLI surface's own subcommand set and parse into it, so this
25
# binary can answer the commands `openagents-cli` defines without restating them.
26
clap = { version = "4", features = ["derive", "cargo"] }
24 27
tokio = { version = "1", features = ["full"] }
25 28
crossterm = { version = "0.28", features = ["event-stream"] }
26 29
ratatui = { version = "0.29", default-features = false, features = ["crossterm"] }
crates/coder-lite/src/main.rs modified +35 -1

@@ -62,9 +62,43 @@ and does not take that name.

62 62
Inside the session, `/help` lists the commands and the keys.
63 63
";
64 64
65
/// True when the first argument names a command the CLI surface answers.
66
///
67
/// The set is read out of clap rather than kept by hand here, so a subcommand
68
/// added to `openagents-cli` is reachable from this binary the moment it
69
/// exists, and one removed stops being claimed. A hand-kept list is how the
70
/// two surfaces would drift.
71
fn names_a_cli_command(arguments: &[String]) -> bool {
72
    use clap::CommandFactory;
73
    let Some(first) = arguments.first() else {
74
        return false;
75
    };
76
    if first.starts_with('-') {
77
        return false;
78
    }
79
    openagents_cli::cli::Cli::command()
80
        .get_subcommands()
81
        .any(|sub| sub.get_name() == first || sub.get_all_aliases().any(|alias| alias == first))
82
}
83
65 84
#[tokio::main]
66 85
async fn main() -> Result<(), Box<dyn std::error::Error>> {
67 86
    let arguments: Vec<String> = env::args().skip(1).collect();
87
88
    // One binary, two surfaces. Bare, it is the coder session; given a command
89
    // the CLI answers, it is that command. Without this, shipping this binary
90
    // would drop `issue`, `repo`, `auth` and the rest from the release --
91
    // including `update`, so an installed build could not replace itself.
92
    if names_a_cli_command(&arguments) {
93
        use clap::Parser;
94
        let cli = openagents_cli::cli::Cli::parse();
95
        if let Err(error) = openagents_cli::cli::run(cli).await {
96
            openagents_cli::errors::fail(&openagents_cli::errors::CliError::Internal(
97
                error.to_string(),
98
            ));
99
        }
100
        return Ok(());
101
    }
68 102
    let options = match parse(&arguments) {
69 103
        Ok(Parsed::Run(mut options, dev)) => {
70 104
            if dev {

@@ -126,7 +160,7 @@ fn parse(arguments: &[String]) -> Result<Parsed, String> {

126 160
                return Ok(Parsed::Said);
127 161
            }
128 162
            "-V" | "--version" => {
129
                println!("coder-lite {}", env!("CARGO_PKG_VERSION"));
163
                println!("coder-lite {}", openagents_cli::VERSION);
130 164
                return Ok(Parsed::Said);
131 165
            }
132 166
            "--dev" => dev = true,
ops/release-cli.sh modified +9 -3

@@ -234,9 +234,15 @@ for platform in $targets; do

234 234
  # Every artifact is rebuilt from source into its own target directory. Nothing
235 235
  # is copied forward from a previous run, so a build that fails cannot leave a
236 236
  # stale binary behind for the verification step to bless.
237
  # The shipped binary is `coder-lite`. It answers both surfaces: bare, it is
238
  # the coder session; given a command `openagents-cli` defines, it dispatches
239
  # to that command. Shipping the library crate's own binary instead would give
240
  # the subcommands without the session; shipping a session that refused them
241
  # would drop `issue`, `repo`, `auth` -- and `update`, so an installed build
242
  # could not replace itself.
237 243
  case "$triple" in
238
    *windows*) output="$repo_root/target/$triple/release/oa.exe" ;;
239
    *) output="$repo_root/target/$triple/release/oa" ;;
244
    *windows*) output="$repo_root/target/$triple/release/coder-lite.exe" ;;
245
    *) output="$repo_root/target/$triple/release/coder-lite" ;;
240 246
  esac
241 247
  rm -f "$output"
242 248

@@ -254,7 +260,7 @@ for platform in $targets; do

254 260
  # way the comparison fell. `build.rs` declares the dependency on this
255 261
  # variable, so changing it rebuilds.
256 262
  if ! (cd "$repo_root" && OPENAGENTS_CLI_RELEASE_VERSION="$version" \
257
    $build_command --release -p openagents-cli --target "$triple") \
263
    $build_command --release -p coder-lite --target "$triple") \
258 264
    >"$build_log" 2>&1; then
259 265
    echo "  SKIP: build failed (see $build_log)"
260 266
    tail -5 "$build_log" | sed 's/^/    /'

This page updates live while a promote is in flight · changelog