16 KiB
v0.2.7 Self-Update Command Implementation Plan
Goal
v0.2.7 adds a polished cass update command that can update Cassady from official GitHub releases without requiring users to manually download archives, verify checksums, unpack binaries, or rebuild from source.
Success statement:
A user can run
cass update, see the available release, choose the recommended prebuilt binary or a source build fallback, and finish with updatedcassandcassadycommands in the same install location.
Scope
In scope
- Add a
cass update/cassady updatesubcommand. - Query official Cassady GitHub releases from
owenqwenstarsky/cassady. - Compare the current binary version with the latest stable release.
- Download and install the matching prebuilt archive when available.
- Verify prebuilt archives with the shipped
.sha256files before installing. - Offer a source-build path that downloads release source for the selected tag and builds local binaries.
- Update both shipped binaries,
cassandcassady, when possible. - Use interactive prompts by default with clear summaries, confirmations, progress, success, and recovery messages.
- Provide non-interactive flags for check-only and yes-to-prompts usage.
- Keep
cass updateindependent of model/provider setup so updates work even when~/.cassis missing or broken. - Add tests for release parsing, target detection, asset selection, checksum validation, archive extraction safety, and install planning.
- Update README and bundled docs.
Out of scope
- Publishing through Homebrew, apt, winget, Scoop, npm, or other package managers.
- Automatic background updates or prompts during normal chat startup.
- Updating Cassady when it was installed by an external package manager that should own the install directory.
- Privilege escalation,
sudoautomation, or administrator prompts. - Code signing, notarization, or signature verification beyond existing SHA-256 files.
- Downgrading by default. Installing an older tag should require an explicit flag if supported.
- Cross-compiling in source mode. Source builds target the current host platform only.
Context and Current State
Relevant files:
Cargo.toml: package version and two binaries,cassandcassady.src/cli.rs: Clap command definitions currently includecheckandsetup.src/app.rs: top-level command dispatch; update should run before setup/config loading.src/main.rsandsrc/bin/cassady.rs: both callcassady::run().README.mdanddocs/commands.md: command documentation.docs/platforms.mdanddocs/troubleshooting.md: platform and recovery guidance.AGENTS.md: release artifacts use these names:cassady-vX.Y.Z-aarch64-apple-darwin.tar.gzcassady-vX.Y.Z-x86_64-unknown-linux-gnu.tar.gzcassady-vX.Y.Z-aarch64-unknown-linux-gnu.tar.gzcassady-vX.Y.Z-x86_64-pc-windows-gnu.zip
Current releases include both cass and cassady in each archive plus one .sha256 file per archive. The update command should reuse that release contract instead of inventing a new distribution format.
Design Principles
- Boring and recoverable. Updating should be explicit, easy to understand, and safe to interrupt before installation starts.
- Use official release artifacts first. Prefer prebuilt archives with SHA-256 verification; fall back to source builds when the user asks or no asset matches.
- No surprise setup coupling. Users should not need a configured provider, model, or API key to update the CLI.
- Respect install ownership. Do not auto-escalate privileges or overwrite package-manager-owned paths without clear user confirmation.
- Both command names stay aligned. If the user has both
cassandcassadyin the install directory, update them together. - Interactive by default, scriptable when requested. The normal path should be friendly; flags should support CI/check scripts.
- Fail closed on integrity. Missing or mismatched checksums for prebuilt artifacts must stop installation.
User Experience
Default interactive flow
$ cass update
Cassady update
Current version: v0.2.6
Latest release: v0.2.7
Install path: /usr/local/bin
Recommended: prebuilt aarch64-apple-darwin archive
Update Cassady to v0.2.7? [Y/n]
If the user accepts, Cassady should show concise phases:
Downloading cassady-v0.2.7-aarch64-apple-darwin.tar.gz ... 8.4 MB
Downloading cassady-v0.2.7-aarch64-apple-darwin.tar.gz.sha256 ... done
Verifying SHA-256 ... ok
Preparing cass and cassady ... ok
Installing to /usr/local/bin ... ok
Verifying installed version ... cass 0.2.7
Cassady is up to date.
If the current version is already latest:
Cassady is already up to date.
Current version: v0.2.7
Latest release: v0.2.7
Prebuilt or source selection
The default auto mode should choose the prebuilt release asset when a supported target is detected. If no matching prebuilt exists, prompt for source mode:
No prebuilt archive is available for this platform.
Build Cassady v0.2.7 from source instead? [Y/n]
If both paths are available and the user asks for source mode:
cass update --source
Cassady should confirm prerequisites before building:
Source build requires cargo, rustc, and a working C toolchain.
Build Cassady v0.2.7 from release source now? [Y/n]
Useful flags
Add a command shape like:
cass update [OPTIONS]
Suggested options:
--check: check GitHub for the latest release and print status without installing.--yes: accept default prompts for non-interactive use.--prebuilt: require a matching prebuilt archive; fail instead of falling back to source.--source: build from release source even when a prebuilt archive exists.--to TAG: install a specific release tag such asv0.2.7.--dry-run: resolve the release, target, assets, and install path without downloading or installing.
Optional later flags, only if implementation needs them:
--stable-only: ignore prerelease tags during latest-release selection if Cassady later publishes both stable and prerelease channels.--install-dir PATH: install into an explicit directory. This should be advanced and carefully documented because it can conflict with PATH order.
Avoid adding a public --repo override unless needed for testing; tests can inject a mock client instead.
Design
Module layout
Add a focused update module:
pub mod update;
Suggested internal types:
pub struct UpdateOptions { ... }
pub enum UpdateMode { Auto, Prebuilt, Source }
pub struct ReleaseInfo { ... }
pub struct ReleaseAsset { ... }
pub struct PlatformTarget { ... }
pub struct UpdatePlan { ... }
pub enum InstallAction { Replace, AddCompanion, SkipMissingCompanion }
src/cli.rs should add an Update subcommand with parsed flags. src/app.rs should dispatch it before setup/config loading:
if let Some(Command::Update(args)) = cli.command {
return crate::update::run(args).await;
}
This keeps update usable even when Config::load() would fail.
GitHub release discovery
Use the GitHub Releases API with an explicit user agent:
- Latest release:
GET https://api.github.com/repos/owenqwenstarsky/cassady/releases?per_page=30and choose the highest semver non-draft tag, including prereleases because Cassady's current release process marks releases as prereleases. - Specific tag:
GET https://api.github.com/repos/owenqwenstarsky/cassady/releases/tags/{tag}
Parse:
tag_namenamedraftprereleaseassets[].nameassets[].browser_download_urlassets[].sizetarball_urlorzipball_urlfor source mode
Use semver to compare env!("CARGO_PKG_VERSION") with release tags after stripping a leading v. Draft releases should never be selected. Prereleases should be eligible by default while Cassady's official releases are marked as prereleases.
Platform target mapping
Map the running platform to release asset targets:
| OS | Arch | Target | Archive |
|---|---|---|---|
| macOS | aarch64 |
aarch64-apple-darwin |
.tar.gz |
| Linux | x86_64 |
x86_64-unknown-linux-gnu |
.tar.gz |
| Linux | aarch64 |
aarch64-unknown-linux-gnu |
.tar.gz |
| Windows | x86_64 |
x86_64-pc-windows-gnu |
.zip |
Unsupported platforms should produce a clean message and offer source mode when possible.
Prebuilt update path
For tag vX.Y.Z and target TARGET, find:
cassady-vX.Y.Z-TARGET.tar.gz
cassady-vX.Y.Z-TARGET.tar.gz.sha256
or on Windows:
cassady-vX.Y.Z-x86_64-pc-windows-gnu.zip
cassady-vX.Y.Z-x86_64-pc-windows-gnu.zip.sha256
Flow:
- Download archive and checksum into a temporary staging directory.
- Parse the
.sha256file and verify that the checksum filename matches the downloaded archive name. - Compute SHA-256 of the archive and compare exactly.
- Extract into staging using path traversal checks.
- Require the expected binaries:
- Unix:
cass,cassady - Windows:
cass.exe,cassady.exe
- Unix:
- Run the staged
cass --versionorcassady --versionwhen possible and confirm the expected version. - Build an install plan for the current executable directory.
- Confirm the final plan with the user unless
--yeswas supplied. - Replace binaries with backups and rollback on failure.
- Verify installed version after replacement when possible.
Archive extraction must reject absolute paths, .. components, symlinks that escape staging, and unexpected top-level layouts.
Source-build update path
Source mode should still be tied to a GitHub release tag, not an arbitrary branch.
Flow:
-
Resolve the selected release tag.
-
Download release source from
tarball_urlorzipball_urlinto staging. -
Extract with the same path traversal protections as prebuilt archives.
-
Verify
Cargo.tomlversion matches the selected tag. -
Run:
cargo build --release --locked --binsfrom the extracted source tree.
-
Locate built binaries under
target/release/. -
Run staged
--versionchecks. -
Install using the same installer path as prebuilt updates.
Before source mode starts, check for cargo and rustc on PATH and show a clear error if they are missing. Do not attempt to install Rust automatically.
Install planning and replacement
Determine the current executable path with std::env::current_exe(), then derive the install directory. The install plan should include:
- current binary path
- sibling
casspath - sibling
cassadypath - which binaries currently exist
- which binaries are writable
- whether companion binaries will be updated, added, skipped, or blocked
Recommended behavior:
- Always update the currently running binary name.
- If the sibling binary exists in the same directory, update it too.
- If the sibling binary is missing and the directory is writable, ask whether to install it.
- If a target path is not writable, stop with an actionable message. Do not invoke
sudoor administrator prompts automatically. - Use backups such as
.cass-update-backup-v0.2.6-<timestamp>during replacement. - If any replacement fails, restore backups before returning an error.
Unix can generally replace a running executable via atomic rename. Windows cannot reliably overwrite the running .exe; implement one of these approaches during coding:
- Preferred: stage replacements and spawn a small PowerShell or
cmdhelper that waits for the current process to exit, moves files into place, and writes a log. - Fallback: stage replacements and print exact manual copy commands if helper launch is unavailable.
Document any Windows limitation honestly in docs/platforms.md and docs/troubleshooting.md.
Output and error style
Keep output concise and user-facing:
- Show current version, target version, install directory, selected mode, and asset/source name before changing files.
- Show clear phase lines for download, verify, build, install, and final verification.
- On failure, say whether anything was changed and where staging/backups are located.
- If update cannot proceed because the install path is not writable, tell the user which path failed and suggest reinstalling through the same method they originally used.
Avoid dumping raw GitHub JSON, backtraces, or Cargo logs unless the source build fails; in that case, preserve the final relevant Cargo output and staging path.
Dependencies
Likely additions to Cargo.toml:
semverfor version comparison.sha2for SHA-256 verification.tarandflate2for.tar.gzextraction.zipfor Windows release archives and GitHub source zips if used.
Prefer small, well-maintained crates. Reuse existing reqwest, tokio, serde, and serde_json.
Implementation Steps
- Add CLI parsing for
cass updateand dispatch it before setup/config loading. - Add
src/update.rswith release API types, version comparison, and target detection. - Implement GitHub release fetching with a testable client abstraction or injectable base URL for tests.
- Implement asset selection for current platform and update mode.
- Implement download, progress reporting, and checksum verification for prebuilt archives.
- Implement safe archive extraction and staged binary validation.
- Implement install planning from
current_exe()and companion binary detection. - Implement Unix replacement with backups and rollback.
- Implement Windows staged-helper replacement or a clearly documented manual fallback.
- Implement source mode: source download, version validation, prerequisite checks,
cargo build --release --locked --bins, and staged binary validation. - Polish interactive prompts and
--check,--dry-run,--yes,--prebuilt,--source, and--tobehavior. - Update docs and release notes template expectations if needed.
- Add tests and run full verification.
Tests
Add focused unit tests for:
- parsing
vX.Y.Ztags and comparing against the current version shape - ignoring drafts and prereleases where applicable
- mapping supported and unsupported platform targets
- matching asset and checksum filenames
- parsing
.sha256lines generated by the release process - rejecting checksum filename mismatches and digest mismatches
- rejecting archive path traversal entries
- planning installation when only
cass, onlycassady, or both binaries exist - refusing non-writable install targets in planning or dry-run mode
- source mode validating that
Cargo.tomlversion matches the selected tag
Add integration-style tests with a mock HTTP server for:
- already-up-to-date response
- latest prebuilt update plan
- missing prebuilt with source fallback prompt path, where build execution can be mocked
- download checksum mismatch failure
- successful staged install into a temporary directory using fake binaries
Manual checks:
cargo fmt
cargo test --locked --all-targets
cargo run -- update --check
cargo run -- update --dry-run --to v0.2.7
For a real release candidate, test from a temporary install directory before using cass update on the developer's normal binary.
Documentation
Update:
README.md: mentioncass updatein install/update and everyday command sections.docs/commands.md: full command reference, flags, interactivity, examples, and exit behavior.docs/platforms.md: platform-specific update support and Windows replacement notes.docs/troubleshooting.md: network failures, checksum mismatch, no matching prebuilt, missing Rust toolchain, non-writable install directory, PATH conflicts, and rollback recovery.docs/README.md: add any new update-related links or summaries.
Document that users should prefer the package manager's update mechanism if Cassady was installed through a package manager in the future.
Acceptance Criteria
cass update --checkreports the current/latest release without reading provider config.cass update --dry-runshows the selected release, mode, asset/source, and install plan without modifying files.- On supported release targets,
cass updatecan download the matching official archive, verify SHA-256, stage both binaries, and update the current install directory. cass update --sourcecan download release source, build withcargo build --release --locked --bins, and install the resulting local binaries.- Checksum mismatch, missing assets, unsupported platforms, missing Rust toolchain, and non-writable install paths fail with clear messages and no partial install.
- Existing
cassandcassadysibling binaries remain version-aligned after a successful update. - README and bundled docs explain the command accurately.
cargo fmtandcargo test --locked --all-targetspass.