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
40 changes: 28 additions & 12 deletions libs/cua-driver-rs/crates/cua-driver/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1084,9 +1084,10 @@ pub fn run_recording_cmd(subcommand: &str, args: &[String], socket: Option<&str>
/// `cua-driver update [--apply]` — check for a newer release and optionally apply it.
///
/// Shares the GitHub releases fetch with the startup banner via
/// [`crate::version_check::fetch_latest_version`] so both code paths
/// agree on tag filtering and HTTP semantics. Pass `--apply` to download
/// and install via the canonical install.sh.
/// [`crate::version_check::fetch_latest_version`] so both code paths agree on
/// tag filtering and HTTP semantics. `--apply` delegates to the canonical
/// installer script — see [`crate::updater`] for why we go through the script
/// instead of re-implementing the asset resolution + atomic swap + GC in Rust.
pub fn run_update_cmd(apply: bool) {
let current = env!("CARGO_PKG_VERSION");
println!("Current version: {current}");
Expand Down Expand Up @@ -1114,23 +1115,38 @@ pub fn run_update_cmd(apply: bool) {
println!(" cua-driver update --apply");
println!();
println!("Or reinstall directly:");
println!(" curl -fsSL https://raw.githubusercontent.com/trycua/cua/main/libs/cua-driver/scripts/install.sh | bash");
println!(" {}", crate::updater::manual_install_one_liner());
return;
}

println!("Downloading and installing cua-driver {v}…");
let status = std::process::Command::new("bash")
.arg("-c")
.arg("curl -fsSL https://raw.githubusercontent.com/trycua/cua/main/libs/cua-driver/scripts/install.sh | bash")
.status();
match status {
Ok(s) if s.success() => {}
let daemon_was_running = crate::updater::daemon_is_running();
match crate::updater::run_install_script(&v) {
Ok(s) if s.success() => {
println!("Installed cua-driver {v}.");
if daemon_was_running {
// The atomic swap (symlink retarget / junction flip)
// means the running daemon kept executing the old
// binary — restart picks up the new one.
println!();
println!("A daemon was running before the install. Restart it to pick up the new binary:");
println!(" cua-driver stop && cua-driver serve");
Comment on lines +1127 to +1133

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Does Windows PowerShell 5.1 support the && command chaining operator, or is it only available in PowerShell 7+?

💡 Result:

Windows PowerShell 5.1 does not support the && pipeline chaining operator. The && (and ||) pipeline chain operators were added beginning in PowerShell 7. [1] Microsoft’s documentation for about_Pipeline_Chain_Operators states: “Beginning in PowerShell 7, PowerShell implements the && and || operators to conditionally chain pipelines.” [1] Therefore, Windows PowerShell 5.1 (the “Windows PowerShell” line) lacks && support and you must use other constructs (for example, -and between expressions) or upgrade to PowerShell 7+ to use &&. [1] Practical implication: in PowerShell 5.1, command1 && command2 is not valid pipeline chaining syntax; in PowerShell 7+, it conditionally runs command2 only if the left-hand pipeline succeeded. [1]

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the relevant region around the reported line numbers
sed -n '1100,1160p' libs/cua-driver-rs/crates/cua-driver/src/cli.rs

# Also grab the function name + surrounding context for style consistency
rg -n "fn run_update_cmd|run_update_cmd" libs/cua-driver-rs/crates/cua-driver/src/cli.rs

Repository: trycua/cua

Length of output: 2889


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find Windows PowerShell invocation in updater.rs
fd -a "updater.rs" libs/cua-driver-rs/crates/cua-driver/src | head -n 20

# Search for powershell usage in the updater module(s)
rg -n "powershell\.exe|powershell" libs/cua-driver-rs/crates/cua-driver/src

# Also confirm where the restart hint string appears (single source of truth)
rg -n "A daemon was running before the install\. Restart it" -S libs/cua-driver-rs/crates/cua-driver/src/cli.rs

Repository: trycua/cua

Length of output: 1582


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show relevant parts of updater.rs around powershell invocation and install scripting
sed -n '1,140p' libs/cua-driver-rs/crates/cua-driver/src/updater.rs

# Find the function that runs the install script and print its body region
rg -n "fn run_install_script|run_install_script\\(" libs/cua-driver-rs/crates/cua-driver/src/updater.rs
sed -n '140,320p' libs/cua-driver-rs/crates/cua-driver/src/updater.rs

# Check if there are other user-facing command hints for Windows shell syntax
rg -n "A daemon was running|Restart it to pick up|manual_install_one_liner" libs/cua-driver-rs/crates/cua-driver/src/cli.rs
rg -n "manual_install_one_liner" libs/cua-driver-rs/crates/cua-driver/src/updater.rs libs/cua-driver-rs/crates/cua-driver/src/cli.rs

Repository: trycua/cua

Length of output: 5182


🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n "&&" libs/cua-driver-rs/crates/cua-driver/src/cli.rs
rg -n "cfg\\(windows\\)|cfg\\(not\\(windows\\)\\)" libs/cua-driver-rs/crates/cua-driver/src/cli.rs | head -n 50

Repository: trycua/cua

Length of output: 773


Make the restart hint Windows PowerShell 5.1-compatible.

cua-driver stop && cua-driver serve uses &&, which isn’t supported by Windows PowerShell 5.1 (it was added in PowerShell 7). Print two separate commands on Windows instead.

File: libs/cua-driver-rs/crates/cua-driver/src/cli.rs (around the restart message)

Suggested fix
                     if daemon_was_running {
                         println!();
                         println!("A daemon was running before the install. Restart it to pick up the new binary:");
-                        println!("  cua-driver stop && cua-driver serve");
+                        #[cfg(windows)]
+                        {
+                            println!("  cua-driver stop");
+                            println!("  cua-driver serve");
+                        }
+                        #[cfg(not(windows))]
+                        {
+                            println!("  cua-driver stop && cua-driver serve");
+                        }
                     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if daemon_was_running {
// The atomic swap (symlink retarget / junction flip)
// means the running daemon kept executing the old
// binary — restart picks up the new one.
println!();
println!("A daemon was running before the install. Restart it to pick up the new binary:");
println!(" cua-driver stop && cua-driver serve");
if daemon_was_running {
// The atomic swap (symlink retarget / junction flip)
// means the running daemon kept executing the old
// binary — restart picks up the new one.
println!();
println!("A daemon was running before the install. Restart it to pick up the new binary:");
#[cfg(windows)]
{
println!(" cua-driver stop");
println!(" cua-driver serve");
}
#[cfg(not(windows))]
{
println!(" cua-driver stop && cua-driver serve");
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@libs/cua-driver-rs/crates/cua-driver/src/cli.rs` around lines 1127 - 1133,
The restart hint currently prints a single line using shell && which isn't
supported in PowerShell 5.1; update the daemon_was_running branch in cli.rs (the
block that prints the restart instructions) to detect Windows (e.g.,
cfg!(windows) or cfg!(target_os = "windows")) and, on Windows, print two
separate lines ("  cua-driver stop" and "  cua-driver serve") instead of the
combined "cua-driver stop && cua-driver serve", leaving the original single-line
message for non-Windows platforms.

}
}
Ok(s) => {
println!("Installation failed (exit {}). Run the command above manually.", s.code().unwrap_or(1));
eprintln!(
"Installation failed (exit {}). Re-run install manually:",
s.code().unwrap_or(1)
);
eprintln!(" {}", crate::updater::manual_install_one_liner());
process::exit(s.code().unwrap_or(1));
}
Err(e) => {
eprintln!("Failed to run installer: {e}");
eprintln!("Failed to launch installer: {e}");
#[cfg(windows)]
eprintln!(" (is powershell.exe on PATH?)");
#[cfg(not(windows))]
eprintln!(" (is bash + curl on PATH?)");
process::exit(1);
}
}
Expand Down
1 change: 1 addition & 0 deletions libs/cua-driver-rs/crates/cua-driver/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ mod proxy;
mod serve;
mod skills;
mod telemetry;
mod updater;
mod version_check;

use std::sync::Arc;
Expand Down
99 changes: 99 additions & 0 deletions libs/cua-driver-rs/crates/cua-driver/src/updater.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
//! `cua-driver update --apply` implementation.
//!
//! Delegates the actual install work to the canonical installer scripts:
//! - Unix: `libs/cua-driver/scripts/install.sh` (delegates to
//! `_install-rust.sh` when `--backend=rust`)
//! - Windows: `libs/cua-driver/scripts/install.ps1`
//!
//! Why not reimplement the download / atomic-swap / GC in Rust? Those scripts
//! already solve the hard problems:
//! - target-triple → asset-name mapping (per-OS, per-arch)
//! - per-version dir layout (`packages/releases/<version>-<target>/`)
//! - atomic upgrade — symlink retarget on Unix, NTFS directory-junction
//! retarget on Windows. A running daemon survives the swap because the
//! kernel keeps the old inode alive (Unix) or the junction flip is a
//! reparse-point swap that doesn't touch the locked .exe (Windows).
//! - GC of stale per-version dirs (`CUA_DRIVER_RS_KEEP_VERSIONS`)
//! - PATH wiring
//!
//! Treating "update" as a pinned re-install with `CUA_DRIVER_RS_VERSION` set
//! keeps install + update reading from one source of truth. Improvements to
//! the on-disk layout ship in the scripts and benefit both code paths.

use std::process::{Command, ExitStatus};

/// Canonical install-script URLs. Match what the docs print as the one-liner;
/// users who run `cua-driver update --apply` and re-run the printed manual
/// command land at the exact same script. Per-OS gating keeps the unused
/// constant from triggering `dead_code` on the platform that doesn't use it.
#[cfg(not(windows))]
const CANONICAL_INSTALL_SH: &str =
"https://raw.githubusercontent.com/trycua/cua/main/libs/cua-driver/scripts/install.sh";
#[cfg(windows)]
const CANONICAL_INSTALL_PS1: &str =
"https://raw.githubusercontent.com/trycua/cua/main/libs/cua-driver/scripts/install.ps1";

/// The env var both scripts honour to pin the target release tag. Set to a
/// bare version like `"0.2.18"` (no `cua-driver-rs-v` prefix). See
/// `libs/cua-driver/scripts/_install-rust.sh` + `install.ps1`.
const VERSION_PIN_ENV: &str = "CUA_DRIVER_RS_VERSION";

/// Invoke the canonical installer pinned to `version`. Returns the
/// installer's exit status so the caller can produce the right
/// "succeeded / failed — re-run manually" message.
pub fn run_install_script(version: &str) -> std::io::Result<ExitStatus> {
#[cfg(windows)]
{
// Match the documented Windows one-liner: `irm <url> | iex`.
// -ExecutionPolicy Bypass lets the downloaded script run on
// machines with the default restricted policy without requiring
// the user to Set-ExecutionPolicy first. -NoProfile keeps any
// user profile script from racing the install.
let pwsh_cmd = format!("iwr -useb {CANONICAL_INSTALL_PS1} | iex");
Command::new("powershell.exe")
.env(VERSION_PIN_ENV, version)
.args([
"-NoProfile",
"-ExecutionPolicy",
"Bypass",
"-Command",
&pwsh_cmd,
])
.status()
}

#[cfg(not(windows))]
{
// Match the canonical curl-piped-to-bash invocation. `--backend=rust`
// is the explicit selector — without it the canonical install.sh
// auto-detects on macOS and would install the Swift driver instead.
let bash_cmd = format!(
"curl -fsSL {CANONICAL_INSTALL_SH} | bash -s -- install --backend=rust"
);
Command::new("bash")
.env(VERSION_PIN_ENV, version)
.args(["-c", &bash_cmd])
.status()
Comment on lines +70 to +76

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu

printf 'without pipefail:\n'
bash -c 'missing_cmd | bash -s -- install --backend=rust; printf "exit=%s\n" "$?"'

printf '\nwith pipefail:\n'
bash -o pipefail -c 'missing_cmd | bash -s -- install --backend=rust; printf "exit=%s\n" "$?"'

Repository: trycua/cua

Length of output: 212


🏁 Script executed:

#!/bin/bash
set -eu

printf 'without pipefail:\n'
bash -c 'missing_cmd | bash -s -- install --backend=rust; printf "exit=%s\n" "$?"'

printf '\nwith pipefail:\n'
bash -o pipefail -c 'missing_cmd | bash -s -- install --backend=rust; printf "exit=%s\n" "$?"'

Repository: trycua/cua

Length of output: 212


🏁 Script executed:

#!/bin/bash
set -eu

printf 'without pipefail:\n'
bash -c 'missing_cmd | bash -s -- install --backend=rust; printf "exit=%s\n" "$?"'

printf '\nwith pipefail:\n'
bash -o pipefail -c 'missing_cmd | bash -s -- install --backend=rust; printf "exit=%s\n" "$?"'

Repository: trycua/cua

Length of output: 212


Propagate curl failures out of the Unix install pipeline (add pipefail).

The Unix path uses curl -fsSL ... | bash -s -- ... and currently returns the RHS bash status; without pipefail, failures in curl can be masked. Shell repro: missing_cmd | bash -s -- ... => exit 0, while bash -o pipefail -c 'missing_cmd | ...' => exit 127.

Suggested fix
-        Command::new("bash")
-            .env(VERSION_PIN_ENV, version)
-            .args(["-c", &bash_cmd])
+        Command::new("bash")
+            .env(VERSION_PIN_ENV, version)
+            .args(["-o", "pipefail", "-c", &bash_cmd])
             .status()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@libs/cua-driver-rs/crates/cua-driver/src/updater.rs` around lines 70 - 76,
The pipeline using bash_cmd ("curl -fsSL {CANONICAL_INSTALL_SH} | bash -s --
install --backend=rust") can hide curl failures because pipefail is not set;
update the invocation in Command::new("bash") so the shell runs with pipefail
(either by passing bash the -o pipefail option before -c or by prefixing
bash_cmd with "set -o pipefail;") so that Command::new("bash").args([... "-c",
&bash_cmd]).status() propagates curl errors; change the args call that currently
uses ["-c", &bash_cmd] to include pipefail and keep VERSION_PIN_ENV usage the
same.

}
}

/// True if the local cua-driver daemon is currently accepting connections
/// on its default socket / named pipe. Used post-install to decide whether
/// to print the "restart the daemon to pick up the new binary" hint.
pub fn daemon_is_running() -> bool {
crate::serve::is_daemon_listening(&crate::serve::default_socket_path())
}

/// The platform-appropriate manual re-install command, used in both the
/// "available, run --apply" preview and the "apply failed, retry manually"
/// error message. Kept here so both messages stay in sync.
pub fn manual_install_one_liner() -> String {
#[cfg(windows)]
{
format!("irm {CANONICAL_INSTALL_PS1} | iex")
}
#[cfg(not(windows))]
{
format!("curl -fsSL {CANONICAL_INSTALL_SH} | bash -s -- install --backend=rust")
}
}
Loading