1 Commits
Author SHA1 Message Date
owen 7c84a54e6a Add self-update command
CI / Test (push) Waiting to run
CI / Build (push) Waiting to run
2026-06-25 07:50:05 -05:00
13 changed files with 1919 additions and 11 deletions
Generated
+128 -2
View File
@@ -2,6 +2,12 @@
# It is not intended for manual editing.
version = 4
[[package]]
name = "adler2"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
[[package]]
name = "aho-corasick"
version = "1.1.4"
@@ -91,6 +97,15 @@ dependencies = [
"num-traits",
]
[[package]]
name = "arbitrary"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1"
dependencies = [
"derive_arbitrary",
]
[[package]]
name = "assert-json-diff"
version = "2.0.2"
@@ -211,7 +226,7 @@ checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593"
[[package]]
name = "cassady"
version = "0.2.6"
version = "0.2.7"
dependencies = [
"anyhow",
"async-trait",
@@ -219,6 +234,7 @@ dependencies = [
"clap",
"crossterm",
"dirs",
"flate2",
"futures-util",
"ignore",
"include_dir",
@@ -227,13 +243,17 @@ dependencies = [
"ratatui",
"regex",
"reqwest",
"semver",
"serde",
"serde_json",
"sha2",
"tar",
"tempfile",
"thiserror 1.0.69",
"tokio",
"unicode-width 0.1.14",
"wiremock",
"zip",
]
[[package]]
@@ -365,6 +385,15 @@ dependencies = [
"libc",
]
[[package]]
name = "crc32fast"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
dependencies = [
"cfg-if",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.6"
@@ -501,6 +530,17 @@ version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
[[package]]
name = "derive_arbitrary"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.118",
]
[[package]]
name = "derive_more"
version = "2.1.1"
@@ -638,6 +678,16 @@ dependencies = [
"winapi",
]
[[package]]
name = "filetime"
version = "0.2.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759"
dependencies = [
"cfg-if",
"libc",
]
[[package]]
name = "find-msvc-tools"
version = "0.1.9"
@@ -656,6 +706,16 @@ version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flate2"
version = "1.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c"
dependencies = [
"crc32fast",
"miniz_oxide",
]
[[package]]
name = "fnv"
version = "1.0.7"
@@ -1376,6 +1436,16 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "miniz_oxide"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
dependencies = [
"adler2",
"simd-adler32",
]
[[package]]
name = "mio"
version = "1.2.1"
@@ -2231,6 +2301,12 @@ dependencies = [
"libc",
]
[[package]]
name = "simd-adler32"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214"
[[package]]
name = "siphasher"
version = "1.0.3"
@@ -2346,6 +2422,17 @@ dependencies = [
"syn 2.0.118",
]
[[package]]
name = "tar"
version = "0.4.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f6221d9a6003c78398e3b239969f352578258df48c8eb051caadae0015bc840"
dependencies = [
"filetime",
"libc",
"xattr",
]
[[package]]
name = "tempfile"
version = "3.27.0"
@@ -2976,7 +3063,7 @@ version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
"windows-sys 0.48.0",
"windows-sys 0.61.2",
]
[[package]]
@@ -3301,6 +3388,16 @@ version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4"
[[package]]
name = "xattr"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156"
dependencies = [
"libc",
"rustix",
]
[[package]]
name = "yoke"
version = "0.8.3"
@@ -3404,8 +3501,37 @@ dependencies = [
"syn 2.0.118",
]
[[package]]
name = "zip"
version = "2.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fabe6324e908f85a1c52063ce7aa26b68dcb7eb6dbc83a2d148403c9bc3eba50"
dependencies = [
"arbitrary",
"crc32fast",
"crossbeam-utils",
"displaydoc",
"flate2",
"indexmap",
"memchr",
"thiserror 2.0.18",
"zopfli",
]
[[package]]
name = "zmij"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
[[package]]
name = "zopfli"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249"
dependencies = [
"bumpalo",
"crc32fast",
"log",
"simd-adler32",
]
+6 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "cassady"
version = "0.2.6"
version = "0.2.7"
edition = "2021"
description = "Cassady/Cass minimal terminal coding agent"
license = "MIT"
@@ -33,11 +33,16 @@ ratatui = { version = "0.30", default-features = false, features = ["crossterm",
regex = "1"
pulldown-cmark = "0.12"
reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls", "stream"] }
semver = "1"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
sha2 = "0.10"
flate2 = "1"
tar = "0.4"
thiserror = "1"
tokio = { version = "1", features = ["macros", "rt-multi-thread", "sync", "time", "process", "io-util"] }
unicode-width = "0.1"
zip = { version = "2", default-features = false, features = ["deflate"] }
[dev-dependencies]
tempfile = "3"
+4 -2
View File
@@ -11,7 +11,7 @@ The project installs two equivalent commands, `cass` and `cassady`; examples use
- v0.2.6 adds an experimental Rust embedding API for headless sessions; it is useful for early integrations but not yet a stable long-term library contract.
- Config and conversation state live under `~/.cass`.
- Windows binaries are built for releases, but deeper Windows terminal, path, shell, and filesystem polish is planned for a later release.
- Cassady is not an installer, updater, or package manager.
- `cass update` can update release-archive installs from official GitHub releases; external package managers should still be updated through their own tools.
## Install from source
@@ -39,6 +39,7 @@ If Cassady cannot resolve a usable provider, model, or API key, it offers to run
```sh
cass setup
cass check
cass update --check
cass
```
@@ -66,9 +67,10 @@ cass --resume <chat-id>
cass --resume
cass check
cass setup
cass update
```
`cass --resume` without an id lists saved chats for the current directory. When Cassady exits a chat, it prints a resume command for that session.
`cass --resume` without an id lists saved chats for the current directory. `cass update` checks official GitHub releases and can update both `cass` and `cassady` in the current install directory. When Cassady exits a chat, it prints a resume command for that session.
Common in-chat commands:
+33
View File
@@ -1,5 +1,38 @@
# Cassady (Cass) Roadmap
## v0.2.7 — Self-Update Command
This release focuses on making Cassady easy to keep current after installation. The goal is to let users run one clean command, `cass update`, to check GitHub releases, choose the recommended prebuilt binary or a source-build fallback, verify what will be installed, and update both `cass` and `cassady` safely. See `plans/V0_2_7_SELF_UPDATE_COMMAND_PLAN.md`.
### Update Command Experience
- [x] **Add a polished `cass update` command.** Check the official Cassady GitHub releases, compare the running version to the selected release, and guide the user through an interactive update flow.
- Keep update independent of provider/model setup so it works even when config is missing or invalid.
- Support script-friendly checks with flags such as `--check`, `--dry-run`, and `--yes`.
- [x] **Select the right update path.** Prefer a matching prebuilt release archive when available, with explicit `--prebuilt` and `--source` modes for users who want to choose.
- Support the same macOS, Linux, and Windows targets used by Cassady releases.
- Offer source builds for unsupported targets or users who prefer building locally.
### Safe Installation
- [x] **Verify and stage prebuilt artifacts before replacing binaries.** Download archives and SHA-256 files from the release, verify checksums, extract safely, and validate staged `cass`/`cassady` binaries.
- Reject checksum mismatches, unsafe archive paths, missing binaries, and unexpected versions.
- Show clear progress and failure messages without dumping raw implementation details.
- [x] **Replace installed binaries cleanly.** Update the current binary and same-directory companion binary when possible, using backups and rollback on failure.
- Do not auto-run `sudo` or administrator prompts.
- Handle Windows executable replacement with a staged helper or documented manual fallback if necessary.
### Source Build Fallback and Documentation
- [x] **Build from release source when requested.** Download the selected release source, validate its version, check for Rust tooling, run a locked release build, and install the resulting local binaries.
- Keep source mode tied to release tags rather than arbitrary branches.
- Do not attempt cross-compilation or Rust toolchain installation in this release.
- [x] **Document and test update behavior.** Update README and bundled docs for `cass update`, platform notes, troubleshooting, and package-manager caveats.
- Add tests for release parsing, target mapping, asset selection, checksum validation, archive extraction safety, install planning, and mocked update flows.
## v0.2.6 — Rust Embedding API ✅ Completed
This release focuses on adding the first intentional Rust library surface for embedding Cassady in other Rust projects. The goal is to provide the bones for programmatic, headless agent sessions: configure a workspace, start or resume an agent session, send turns, stream typed events, and handle approvals without launching the TUI. See `plans/V0_2_6_RUST_EMBEDDING_API_PLAN.md`.
+2 -2
View File
@@ -6,12 +6,12 @@ Cassady tools may list, search, and read this directory. Mutating tools are bloc
## Contents
- [Commands](commands.md): CLI forms, global flags, in-chat commands, and keys.
- [Commands](commands.md): CLI forms, global flags, `cass update`, in-chat commands, and keys.
- [Configuration](configuration.md): `~/.cass` files, setup, precedence, schema examples, and validation.
- [Providers and models](providers.md): built-in OpenAI-compatible providers, custom endpoints, model discovery, and reasoning metadata.
- [Access modes and tool safety](access-modes.md): what tools can read, write, edit, and run in each mode.
- [Experimental Rust embedding API](embedding.md): import Cassady from Rust, start headless sessions, stream events, and handle approvals.
- [Workflows](workflows.md): common ways to inspect code, apply edits, run checks, switch models, and resume chats.
- [Troubleshooting](troubleshooting.md): symptoms, likely causes, fixes, and verification commands.
- [Platform notes](platforms.md): macOS, Linux, and Windows environment/path notes.
- [Platform notes](platforms.md): macOS, Linux, Windows, release artifact, and update notes.
- [Glossary](glossary.md): short definitions for Cassady terms.
+28 -1
View File
@@ -9,10 +9,11 @@ cass [OPTIONS]
cassady [OPTIONS]
cass check [OPTIONS]
cass setup [OPTIONS]
cass update [OPTIONS]
cass --resume [CHAT_ID]
```
Run `cass --help`, `cass check --help`, or `cass setup --help` for the help generated by the current binary.
Run `cass --help`, `cass check --help`, `cass setup --help`, or `cass update --help` for the help generated by the current binary.
## Startup behavior
@@ -61,6 +62,32 @@ Missing API keys for inactive providers are warnings. A missing active API key i
Runs the interactive setup wizard in a terminal. It configures OpenAI-compatible providers, API key environment-variable references, and first models. It updates `config.json`, `providers.json`, and `models.json` while preserving unrelated entries where possible.
### `cass update`
Checks official GitHub releases for Cassady, including Cassady prereleases, and updates the current install directory. The updater runs before provider/model config is loaded, so it can be used even if `~/.cass` is missing or invalid.
By default, Cassady selects the matching prebuilt archive for the current platform, downloads the archive and `.sha256` file, verifies SHA-256, stages both `cass` and `cassady`, and replaces same-directory binaries with rollback backups. If no prebuilt archive is available, it can build from the selected release source when you choose source mode.
Useful options:
- `--check`: check the selected release without installing.
- `--dry-run`: show the selected release, mode, asset, and install plan without downloading or installing.
- `--yes` / `-y`: accept default prompts for non-interactive use.
- `--prebuilt`: require a matching prebuilt archive.
- `--source`: build from release source even when a prebuilt archive exists.
- `--to TAG`: use a specific release tag such as `v0.2.7`.
Examples:
```sh
cass update --check
cass update --dry-run
cass update
cass update --source
```
The updater does not invoke `sudo` or administrator prompts. If the install directory is not writable, rerun the update from an install location you own or update through the same package manager or manual process you originally used.
## In-chat commands
Type `/` to open command autocomplete.
+15 -2
View File
@@ -52,9 +52,22 @@ That directory contains `config.json`, `providers.json`, `models.json`, `global.
## Non-interactive contexts
- `cass check` is suitable for scripts and CI because it prints text and exits non-zero on errors.
- `cass update --check` and `cass update --dry-run` are suitable for scripts that only need release status or an install plan.
- `cass update --yes` accepts default prompts for scripted updates, but still fails instead of escalating privileges when the install directory is not writable.
- `cass setup` requires an interactive terminal.
- `cass` chat is an interactive terminal UI.
## Release artifacts
## Release artifacts and updates
When using release archives, each archive contains both `cass` and `cassady`. Put the extracted binaries somewhere on your `PATH` or run them by explicit path. Cassady itself does not install, update, or manage PATH entries.
When using release archives, each archive contains both `cass` and `cassady`. Put the extracted binaries somewhere on your `PATH` or run them by explicit path.
`cass update` can update release-archive installs from official GitHub releases. It supports the same prebuilt targets as the release process:
- macOS Apple Silicon: `aarch64-apple-darwin`
- Linux x86_64: `x86_64-unknown-linux-gnu`
- Linux ARM64: `aarch64-unknown-linux-gnu`
- Windows x86_64: `x86_64-pc-windows-gnu`
On macOS and Linux, the updater replaces same-directory `cass` and `cassady` binaries with backups and rollback on failure. On Windows, replacing a running `.exe` is more constrained; if automatic replacement is unavailable, Cassady leaves staged files in place and reports manual copy guidance instead of partially modifying the install.
If Cassady is installed through a package manager in the future, prefer that package manager's update command instead of `cass update`.
+23
View File
@@ -143,6 +143,29 @@ Likely cause: Windows CRLF line endings or invisible whitespace differences.
Fix: re-read the exact target region and preserve the line endings in `old_text`, or use a smaller unique snippet.
## Update command problems
Symptom: `cass update` cannot complete.
Likely causes and fixes:
- Network or GitHub API failure: retry later or verify proxy/firewall settings.
- No matching prebuilt archive: use `cass update --source` if you have Rust installed, or download the release archive manually for a supported target.
- SHA-256 mismatch: do not install the archive. Retry the update; if it repeats, check the GitHub release page before proceeding.
- Missing Rust toolchain in source mode: install Rust/Cargo yourself, then rerun `cass update --source`. Cassady does not install Rust automatically.
- Non-writable install directory: update through the original install method, move Cassady to a directory you own, or manually replace the binaries. Cassady does not run `sudo` for you.
- PATH conflict: `cass update` updates the current executable directory. Run `which cass` / `which cassady` on macOS/Linux or `Get-Command cass` in PowerShell to confirm which binary your shell starts.
- Windows replacement limitation: if Cassady reports that automatic replacement is unavailable, use the staged file paths it prints and copy them after the running process exits.
Useful checks:
```sh
cass update --check
cass update --dry-run
cass --version
cassady --version
```
## Terminal rendering problems
Symptom: the UI appears garbled or keys do not behave as expected.
+390
View File
@@ -0,0 +1,390 @@
# 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 updated `cass` and `cassady` commands in the same install location.
## Scope
### In scope
- Add a `cass update` / `cassady update` subcommand.
- 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 `.sha256` files before installing.
- Offer a source-build path that downloads release source for the selected tag and builds local binaries.
- Update both shipped binaries, `cass` and `cassady`, 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 update` independent of model/provider setup so updates work even when `~/.cass` is 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, `sudo` automation, 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, `cass` and `cassady`.
- `src/cli.rs`: Clap command definitions currently include `check` and `setup`.
- `src/app.rs`: top-level command dispatch; update should run before setup/config loading.
- `src/main.rs` and `src/bin/cassady.rs`: both call `cassady::run()`.
- `README.md` and `docs/commands.md`: command documentation.
- `docs/platforms.md` and `docs/troubleshooting.md`: platform and recovery guidance.
- `AGENTS.md`: release artifacts use these names:
- `cassady-vX.Y.Z-aarch64-apple-darwin.tar.gz`
- `cassady-vX.Y.Z-x86_64-unknown-linux-gnu.tar.gz`
- `cassady-vX.Y.Z-aarch64-unknown-linux-gnu.tar.gz`
- `cassady-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
1. **Boring and recoverable.** Updating should be explicit, easy to understand, and safe to interrupt before installation starts.
2. **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.
3. **No surprise setup coupling.** Users should not need a configured provider, model, or API key to update the CLI.
4. **Respect install ownership.** Do not auto-escalate privileges or overwrite package-manager-owned paths without clear user confirmation.
5. **Both command names stay aligned.** If the user has both `cass` and `cassady` in the install directory, update them together.
6. **Interactive by default, scriptable when requested.** The normal path should be friendly; flags should support CI/check scripts.
7. **Fail closed on integrity.** Missing or mismatched checksums for prebuilt artifacts must stop installation.
## User Experience
### Default interactive flow
```text
$ 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:
```text
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:
```text
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:
```text
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:
```sh
cass update --source
```
Cassady should confirm prerequisites before building:
```text
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:
```sh
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 as `v0.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:
```rust
pub mod update;
```
Suggested internal types:
```rust
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:
```rust
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=30` and 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_name`
- `name`
- `draft`
- `prerelease`
- `assets[].name`
- `assets[].browser_download_url`
- `assets[].size`
- `tarball_url` or `zipball_url` for 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:
```text
cassady-vX.Y.Z-TARGET.tar.gz
cassady-vX.Y.Z-TARGET.tar.gz.sha256
```
or on Windows:
```text
cassady-vX.Y.Z-x86_64-pc-windows-gnu.zip
cassady-vX.Y.Z-x86_64-pc-windows-gnu.zip.sha256
```
Flow:
1. Download archive and checksum into a temporary staging directory.
2. Parse the `.sha256` file and verify that the checksum filename matches the downloaded archive name.
3. Compute SHA-256 of the archive and compare exactly.
4. Extract into staging using path traversal checks.
5. Require the expected binaries:
- Unix: `cass`, `cassady`
- Windows: `cass.exe`, `cassady.exe`
6. Run the staged `cass --version` or `cassady --version` when possible and confirm the expected version.
7. Build an install plan for the current executable directory.
8. Confirm the final plan with the user unless `--yes` was supplied.
9. Replace binaries with backups and rollback on failure.
10. 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:
1. Resolve the selected release tag.
2. Download release source from `tarball_url` or `zipball_url` into staging.
3. Extract with the same path traversal protections as prebuilt archives.
4. Verify `Cargo.toml` version matches the selected tag.
5. Run:
```sh
cargo build --release --locked --bins
```
from the extracted source tree.
6. Locate built binaries under `target/release/`.
7. Run staged `--version` checks.
8. 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 `cass` path
- sibling `cassady` path
- 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 `sudo` or 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:
1. Preferred: stage replacements and spawn a small PowerShell or `cmd` helper that waits for the current process to exit, moves files into place, and writes a log.
2. 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`:
- `semver` for version comparison.
- `sha2` for SHA-256 verification.
- `tar` and `flate2` for `.tar.gz` extraction.
- `zip` for Windows release archives and GitHub source zips if used.
Prefer small, well-maintained crates. Reuse existing `reqwest`, `tokio`, `serde`, and `serde_json`.
## Implementation Steps
1. Add CLI parsing for `cass update` and dispatch it before setup/config loading.
2. Add `src/update.rs` with release API types, version comparison, and target detection.
3. Implement GitHub release fetching with a testable client abstraction or injectable base URL for tests.
4. Implement asset selection for current platform and update mode.
5. Implement download, progress reporting, and checksum verification for prebuilt archives.
6. Implement safe archive extraction and staged binary validation.
7. Implement install planning from `current_exe()` and companion binary detection.
8. Implement Unix replacement with backups and rollback.
9. Implement Windows staged-helper replacement or a clearly documented manual fallback.
10. Implement source mode: source download, version validation, prerequisite checks, `cargo build --release --locked --bins`, and staged binary validation.
11. Polish interactive prompts and `--check`, `--dry-run`, `--yes`, `--prebuilt`, `--source`, and `--to` behavior.
12. Update docs and release notes template expectations if needed.
13. Add tests and run full verification.
## Tests
Add focused unit tests for:
- parsing `vX.Y.Z` tags 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 `.sha256` lines generated by the release process
- rejecting checksum filename mismatches and digest mismatches
- rejecting archive path traversal entries
- planning installation when only `cass`, only `cassady`, or both binaries exist
- refusing non-writable install targets in planning or dry-run mode
- source mode validating that `Cargo.toml` version 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:
```sh
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`: mention `cass update` in 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 --check` reports the current/latest release without reading provider config.
- `cass update --dry-run` shows the selected release, mode, asset/source, and install plan without modifying files.
- On supported release targets, `cass update` can download the matching official archive, verify SHA-256, stage both binaries, and update the current install directory.
- `cass update --source` can download release source, build with `cargo 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 `cass` and `cassady` sibling binaries remain version-aligned after a successful update.
- README and bundled docs explain the command accurately.
- `cargo fmt` and `cargo test --locked --all-targets` pass.
+4
View File
@@ -21,6 +21,10 @@ const TOOL_CANCELLED_MESSAGE: &str = "Tool execution cancelled by user.";
pub async fn run() -> Result<()> {
let mut cli = cli::parse();
if let Some(Command::Update(args)) = cli.command.clone() {
return crate::update::run(args).await;
}
if matches!(cli.command, Some(Command::Check)) {
let report = crate::check::run(&cli)?;
print!("{}", report.render());
+30 -1
View File
@@ -1,4 +1,4 @@
use clap::{Parser, Subcommand};
use clap::{Args, Parser, Subcommand};
use std::path::PathBuf;
#[derive(Debug, Parser, Clone)]
@@ -46,6 +46,35 @@ pub enum Command {
Check,
/// Configure an OpenAI-compatible provider and first model.
Setup,
/// Update Cassady from official GitHub releases.
Update(UpdateArgs),
}
#[derive(Debug, Args, Clone, PartialEq, Eq)]
pub struct UpdateArgs {
/// Check the latest release without installing.
#[arg(long)]
pub check: bool,
/// Show what would be updated without downloading or installing.
#[arg(long)]
pub dry_run: bool,
/// Accept default prompts for non-interactive use.
#[arg(long, short = 'y')]
pub yes: bool,
/// Require a matching prebuilt archive and do not fall back to source.
#[arg(long, conflicts_with = "source")]
pub prebuilt: bool,
/// Build from release source even when a prebuilt archive exists.
#[arg(long, conflicts_with = "prebuilt")]
pub source: bool,
/// Install a specific release tag, such as v0.2.7.
#[arg(long, value_name = "TAG")]
pub to: Option<String>,
}
pub fn parse() -> Cli {
+1
View File
@@ -16,6 +16,7 @@ pub mod security;
pub mod setup;
pub mod tools;
pub mod ui;
pub mod update;
pub async fn run() -> anyhow::Result<()> {
app::run().await
+1255
View File
File diff suppressed because it is too large Load Diff