Automate desktop release binaries
This commit is contained in:
@@ -5,12 +5,16 @@ on:
|
||||
paths:
|
||||
- 'src/**'
|
||||
- 'tests/**'
|
||||
- 'cassady-desktop/**'
|
||||
- 'docs/**'
|
||||
- 'plans/**'
|
||||
- '*.md'
|
||||
- 'Cargo.toml'
|
||||
- 'Cargo.lock'
|
||||
- 'build.rs'
|
||||
- 'package.json'
|
||||
- 'npm/**'
|
||||
- '.github/workflows/**'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -26,6 +30,18 @@ jobs:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Linux desktop build dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y --no-install-recommends \
|
||||
pkg-config \
|
||||
libwebkit2gtk-4.1-dev \
|
||||
libgtk-3-dev \
|
||||
libayatana-appindicator3-dev \
|
||||
librsvg2-dev \
|
||||
libxdo-dev \
|
||||
libssl-dev
|
||||
|
||||
- name: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
@@ -42,6 +58,18 @@ jobs:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Linux desktop build dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y --no-install-recommends \
|
||||
pkg-config \
|
||||
libwebkit2gtk-4.1-dev \
|
||||
libgtk-3-dev \
|
||||
libayatana-appindicator3-dev \
|
||||
librsvg2-dev \
|
||||
libxdo-dev \
|
||||
libssl-dev
|
||||
|
||||
- name: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
|
||||
@@ -0,0 +1,426 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Release tag to build, for example v0.4.0'
|
||||
required: true
|
||||
type: string
|
||||
replace_assets:
|
||||
description: 'Delete existing release assets before uploading rebuilt assets'
|
||||
required: true
|
||||
default: true
|
||||
type: boolean
|
||||
publish_github_release:
|
||||
description: 'Publish the GitHub release after uploading assets'
|
||||
required: true
|
||||
default: false
|
||||
type: boolean
|
||||
publish_npm:
|
||||
description: 'Publish npm packages after release assets are built. Requires the NPM_TOKEN repository secret.'
|
||||
required: true
|
||||
default: false
|
||||
type: boolean
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
concurrency:
|
||||
group: release-${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref_name }}
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
RELEASE_TAG: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref_name }}
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Test release commit
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Check out release ref
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ env.RELEASE_TAG }}
|
||||
|
||||
- name: Install Linux desktop build dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y --no-install-recommends \
|
||||
pkg-config \
|
||||
libwebkit2gtk-4.1-dev \
|
||||
libgtk-3-dev \
|
||||
libayatana-appindicator3-dev \
|
||||
librsvg2-dev \
|
||||
libxdo-dev \
|
||||
libssl-dev
|
||||
|
||||
- name: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Cache Cargo artifacts
|
||||
uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: Confirm version matches tag
|
||||
run: |
|
||||
version=$(awk -F\" '/^version = / { print $2; exit }' Cargo.toml)
|
||||
test "${RELEASE_TAG}" = "v${version}"
|
||||
|
||||
- name: Run tests
|
||||
run: cargo test --locked --all-targets
|
||||
|
||||
build-macos:
|
||||
name: Build macOS Apple Silicon
|
||||
needs: test
|
||||
runs-on: macos-14
|
||||
steps:
|
||||
- name: Check out release ref
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ env.RELEASE_TAG }}
|
||||
|
||||
- name: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: aarch64-apple-darwin
|
||||
|
||||
- name: Cache Cargo artifacts
|
||||
uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: npm
|
||||
cache-dependency-path: cassady-desktop/package-lock.json
|
||||
|
||||
- name: Install desktop frontend dependencies
|
||||
working-directory: cassady-desktop
|
||||
run: npm ci
|
||||
|
||||
- name: Install Tauri CLI
|
||||
run: cargo install tauri-cli --version 2.11.3 --locked
|
||||
|
||||
- name: Build CLI binaries
|
||||
run: cargo build -p cassady --release --locked --target aarch64-apple-darwin --bin cass --bin cassady
|
||||
|
||||
- name: Build desktop binary
|
||||
working-directory: cassady-desktop
|
||||
run: cargo tauri build --target aarch64-apple-darwin --no-bundle -- --locked
|
||||
|
||||
- name: Package archive
|
||||
env:
|
||||
TRIPLE: aarch64-apple-darwin
|
||||
run: |
|
||||
set -euo pipefail
|
||||
name="cassady-${RELEASE_TAG}-${TRIPLE}"
|
||||
mkdir -p "dist/${name}"
|
||||
cp "target/${TRIPLE}/release/cass" "dist/${name}/cass"
|
||||
cp "target/${TRIPLE}/release/cassady" "dist/${name}/cassady"
|
||||
cp "target/${TRIPLE}/release/cassady-desktop" "dist/${name}/cassady-desktop"
|
||||
cp README.md "dist/${name}/README.md"
|
||||
tar -C dist -czf "dist/${name}.tar.gz" "${name}"
|
||||
(cd dist && shasum -a 256 "${name}.tar.gz" > "${name}.tar.gz.sha256")
|
||||
|
||||
- name: Upload release artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: release-aarch64-apple-darwin
|
||||
path: |
|
||||
dist/cassady-${{ env.RELEASE_TAG }}-aarch64-apple-darwin.tar.gz
|
||||
dist/cassady-${{ env.RELEASE_TAG }}-aarch64-apple-darwin.tar.gz.sha256
|
||||
if-no-files-found: error
|
||||
|
||||
build-linux:
|
||||
name: Build Linux ${{ matrix.label }}
|
||||
needs: test
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- label: x86_64
|
||||
runner: ubuntu-24.04
|
||||
triple: x86_64-unknown-linux-gnu
|
||||
- label: ARM64
|
||||
runner: ubuntu-24.04-arm
|
||||
triple: aarch64-unknown-linux-gnu
|
||||
runs-on: ${{ matrix.runner }}
|
||||
steps:
|
||||
- name: Check out release ref
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ env.RELEASE_TAG }}
|
||||
|
||||
- name: Install Linux desktop build dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y --no-install-recommends \
|
||||
pkg-config \
|
||||
libwebkit2gtk-4.1-dev \
|
||||
libgtk-3-dev \
|
||||
libayatana-appindicator3-dev \
|
||||
librsvg2-dev \
|
||||
libxdo-dev \
|
||||
libssl-dev \
|
||||
patchelf
|
||||
|
||||
- name: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: ${{ matrix.triple }}
|
||||
|
||||
- name: Cache Cargo artifacts
|
||||
uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: npm
|
||||
cache-dependency-path: cassady-desktop/package-lock.json
|
||||
|
||||
- name: Install desktop frontend dependencies
|
||||
working-directory: cassady-desktop
|
||||
run: npm ci
|
||||
|
||||
- name: Install Tauri CLI
|
||||
run: cargo install tauri-cli --version 2.11.3 --locked
|
||||
|
||||
- name: Build CLI binaries
|
||||
run: cargo build -p cassady --release --locked --target "${{ matrix.triple }}" --bin cass --bin cassady
|
||||
|
||||
- name: Build desktop binary
|
||||
working-directory: cassady-desktop
|
||||
run: cargo tauri build --target "${{ matrix.triple }}" --no-bundle -- --locked
|
||||
|
||||
- name: Package archive
|
||||
env:
|
||||
TRIPLE: ${{ matrix.triple }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
name="cassady-${RELEASE_TAG}-${TRIPLE}"
|
||||
mkdir -p "dist/${name}"
|
||||
cp "target/${TRIPLE}/release/cass" "dist/${name}/cass"
|
||||
cp "target/${TRIPLE}/release/cassady" "dist/${name}/cassady"
|
||||
cp "target/${TRIPLE}/release/cassady-desktop" "dist/${name}/cassady-desktop"
|
||||
cp README.md "dist/${name}/README.md"
|
||||
tar -C dist -czf "dist/${name}.tar.gz" "${name}"
|
||||
(cd dist && shasum -a 256 "${name}.tar.gz" > "${name}.tar.gz.sha256")
|
||||
|
||||
- name: Upload release artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: release-${{ matrix.triple }}
|
||||
path: |
|
||||
dist/cassady-${{ env.RELEASE_TAG }}-${{ matrix.triple }}.tar.gz
|
||||
dist/cassady-${{ env.RELEASE_TAG }}-${{ matrix.triple }}.tar.gz.sha256
|
||||
if-no-files-found: error
|
||||
|
||||
build-windows-cli:
|
||||
name: Build Windows x86_64 CLI
|
||||
needs: test
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Check out release ref
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ env.RELEASE_TAG }}
|
||||
|
||||
- name: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: x86_64-pc-windows-gnu
|
||||
|
||||
- name: Cache Cargo artifacts
|
||||
uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: Set up Zig
|
||||
uses: goto-bus-stop/setup-zig@v2
|
||||
with:
|
||||
version: 0.14.1
|
||||
|
||||
- name: Install cargo-zigbuild
|
||||
run: cargo install cargo-zigbuild --locked
|
||||
|
||||
- name: Build Windows CLI binaries
|
||||
run: cargo zigbuild -p cassady --release --locked --target x86_64-pc-windows-gnu --bin cass --bin cassady
|
||||
|
||||
- name: Package archive
|
||||
env:
|
||||
TRIPLE: x86_64-pc-windows-gnu
|
||||
run: |
|
||||
set -euo pipefail
|
||||
name="cassady-${RELEASE_TAG}-${TRIPLE}"
|
||||
mkdir -p "dist/${name}"
|
||||
cp "target/${TRIPLE}/release/cass.exe" "dist/${name}/cass.exe"
|
||||
cp "target/${TRIPLE}/release/cassady.exe" "dist/${name}/cassady.exe"
|
||||
cp README.md "dist/${name}/README.md"
|
||||
(cd dist && zip -qr "${name}.zip" "${name}")
|
||||
(cd dist && shasum -a 256 "${name}.zip" > "${name}.zip.sha256")
|
||||
|
||||
- name: Upload release artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: release-x86_64-pc-windows-gnu
|
||||
path: |
|
||||
dist/cassady-${{ env.RELEASE_TAG }}-x86_64-pc-windows-gnu.zip
|
||||
dist/cassady-${{ env.RELEASE_TAG }}-x86_64-pc-windows-gnu.zip.sha256
|
||||
if-no-files-found: error
|
||||
|
||||
github-release:
|
||||
name: Upload GitHub release assets
|
||||
needs:
|
||||
- build-macos
|
||||
- build-linux
|
||||
- build-windows-cli
|
||||
runs-on: ubuntu-24.04
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
REPLACE_ASSETS: ${{ github.event_name != 'workflow_dispatch' || inputs.replace_assets }}
|
||||
PUBLISH_GITHUB_RELEASE: ${{ github.event_name == 'workflow_dispatch' && inputs.publish_github_release }}
|
||||
steps:
|
||||
- name: Check out release ref
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ env.RELEASE_TAG }}
|
||||
|
||||
- name: Download release artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: release-*
|
||||
path: dist
|
||||
merge-multiple: true
|
||||
|
||||
- name: Verify checksums
|
||||
run: |
|
||||
set -euo pipefail
|
||||
ls -lh dist/cassady-${RELEASE_TAG}-*
|
||||
for sum in dist/cassady-${RELEASE_TAG}-*.sha256; do
|
||||
(cd dist && shasum -a 256 -c "$(basename "$sum")")
|
||||
done
|
||||
|
||||
- name: Choose release notes
|
||||
id: notes
|
||||
run: |
|
||||
set -euo pipefail
|
||||
tag="${RELEASE_TAG}"
|
||||
if [ -f "docs/releases/${tag}.md" ]; then
|
||||
echo "file=docs/releases/${tag}.md" >> "$GITHUB_OUTPUT"
|
||||
elif [ -f "dist/RELEASE_NOTES_${tag}.md" ]; then
|
||||
echo "file=dist/RELEASE_NOTES_${tag}.md" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
printf '## Cassady %s\n\nRelease assets for Cassady %s.\n' "$tag" "$tag" > "dist/RELEASE_NOTES_${tag}.md"
|
||||
echo "file=dist/RELEASE_NOTES_${tag}.md" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Create or update release assets
|
||||
run: |
|
||||
set -euo pipefail
|
||||
tag="${RELEASE_TAG}"
|
||||
notes="${{ steps.notes.outputs.file }}"
|
||||
if ! gh release view "$tag" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
|
||||
gh release create "$tag" \
|
||||
--repo "$GITHUB_REPOSITORY" \
|
||||
--title "Cassady ${tag}" \
|
||||
--notes-file "$notes" \
|
||||
--draft \
|
||||
--verify-tag
|
||||
else
|
||||
gh release edit "$tag" \
|
||||
--repo "$GITHUB_REPOSITORY" \
|
||||
--title "Cassady ${tag}" \
|
||||
--notes-file "$notes"
|
||||
fi
|
||||
|
||||
if [ "$REPLACE_ASSETS" = "true" ]; then
|
||||
gh release view "$tag" --repo "$GITHUB_REPOSITORY" --json assets --jq '.assets[].name' | while IFS= read -r asset; do
|
||||
[ -z "$asset" ] && continue
|
||||
gh release delete-asset "$tag" "$asset" --repo "$GITHUB_REPOSITORY" -y
|
||||
done
|
||||
fi
|
||||
|
||||
gh release upload "$tag" \
|
||||
dist/cassady-${tag}-aarch64-apple-darwin.tar.gz \
|
||||
dist/cassady-${tag}-aarch64-apple-darwin.tar.gz.sha256 \
|
||||
dist/cassady-${tag}-x86_64-unknown-linux-gnu.tar.gz \
|
||||
dist/cassady-${tag}-x86_64-unknown-linux-gnu.tar.gz.sha256 \
|
||||
dist/cassady-${tag}-aarch64-unknown-linux-gnu.tar.gz \
|
||||
dist/cassady-${tag}-aarch64-unknown-linux-gnu.tar.gz.sha256 \
|
||||
dist/cassady-${tag}-x86_64-pc-windows-gnu.zip \
|
||||
dist/cassady-${tag}-x86_64-pc-windows-gnu.zip.sha256 \
|
||||
--repo "$GITHUB_REPOSITORY" \
|
||||
--clobber
|
||||
|
||||
if [ "$PUBLISH_GITHUB_RELEASE" = "true" ]; then
|
||||
release_id=$(gh release view "$tag" --repo "$GITHUB_REPOSITORY" --json databaseId --jq .databaseId)
|
||||
gh api \
|
||||
--method PATCH \
|
||||
"repos/${GITHUB_REPOSITORY}/releases/${release_id}" \
|
||||
-F draft=false \
|
||||
-F prerelease=false \
|
||||
-f make_latest=true >/dev/null
|
||||
fi
|
||||
|
||||
npm-publish:
|
||||
name: Publish npm packages
|
||||
needs:
|
||||
- github-release
|
||||
if: ${{ github.event_name == 'workflow_dispatch' && inputs.publish_npm }}
|
||||
runs-on: ubuntu-24.04
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
steps:
|
||||
- name: Check out release ref
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ env.RELEASE_TAG }}
|
||||
|
||||
- name: Download release artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: release-*
|
||||
path: dist
|
||||
merge-multiple: true
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
registry-url: https://registry.npmjs.org/
|
||||
|
||||
- name: Rehydrate target binaries from release archives
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [ -z "${NODE_AUTH_TOKEN:-}" ]; then
|
||||
echo "::error::Set the NPM_TOKEN repository secret before publishing npm packages."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
for archive in dist/cassady-${RELEASE_TAG}-*.tar.gz; do
|
||||
tar -xzf "$archive" -C dist
|
||||
done
|
||||
unzip -q -o "dist/cassady-${RELEASE_TAG}-x86_64-pc-windows-gnu.zip" -d dist
|
||||
|
||||
for triple in aarch64-apple-darwin x86_64-unknown-linux-gnu aarch64-unknown-linux-gnu; do
|
||||
src="dist/cassady-${RELEASE_TAG}-${triple}"
|
||||
dst="target/${triple}/release"
|
||||
mkdir -p "$dst"
|
||||
cp "$src/cass" "$dst/cass"
|
||||
cp "$src/cassady" "$dst/cassady"
|
||||
cp "$src/cassady-desktop" "$dst/cassady-desktop"
|
||||
chmod 755 "$dst/cass" "$dst/cassady" "$dst/cassady-desktop"
|
||||
done
|
||||
|
||||
triple=x86_64-pc-windows-gnu
|
||||
src="dist/cassady-${RELEASE_TAG}-${triple}"
|
||||
dst="target/${triple}/release"
|
||||
mkdir -p "$dst"
|
||||
cp "$src/cass.exe" "$dst/cass.exe"
|
||||
cp "$src/cassady.exe" "$dst/cassady.exe"
|
||||
|
||||
- name: Publish to npm
|
||||
run: npm run npm:publish
|
||||
@@ -2,7 +2,26 @@
|
||||
|
||||
## Release process: build artifacts and create a draft GitHub release
|
||||
|
||||
Use this process when preparing a Cassady (`cass`) release. Always create the GitHub release as a **draft** first; do not publish the final release unless the user explicitly asks.
|
||||
Use this process when preparing a Cassady (`cass`) release. Prefer the GitHub Actions release workflow over local cross-builds so macOS/Linux/Windows assets are built on runners instead of by hand. Always create the GitHub release as a **draft** first; do not publish the final release unless the user explicitly asks.
|
||||
|
||||
### 0. Preferred automated release workflow
|
||||
|
||||
The release workflow is `.github/workflows/release.yml`. On a pushed `v*` tag it builds and uploads draft GitHub release assets automatically. For an existing tag or to publish the release/npm packages, run it manually:
|
||||
|
||||
```sh
|
||||
gh workflow run release.yml \
|
||||
--repo owenqwenstarsky/cassady \
|
||||
-f tag="$TAG" \
|
||||
-f replace_assets=true \
|
||||
-f publish_github_release=false \
|
||||
-f publish_npm=false
|
||||
```
|
||||
|
||||
Set `publish_github_release=true` only when the user explicitly asks to publish the GitHub release. Set `publish_npm=true` only when the user explicitly asks to publish npm packages, and make sure the `NPM_TOKEN` repository secret exists first. The workflow deletes/replaces old release assets, uploads the four current archives/checksums, and publishes npm packages from those runner-built archives.
|
||||
|
||||
Keep release notes in `docs/releases/${TAG}.md` when the workflow should apply curated notes. If that file is missing, the workflow falls back to generated placeholder notes.
|
||||
|
||||
Use the manual steps below only for local debugging or when the workflow cannot be used.
|
||||
|
||||
### 1. Review prior releases for consistency
|
||||
|
||||
@@ -17,7 +36,7 @@ Keep release notes consistent with the existing format:
|
||||
- Title: `## Cassady vX.Y.Z`
|
||||
- One short summary paragraph.
|
||||
- `### Downloads` with four bullets in this order: macOS Apple Silicon, Linux x86_64, Linux ARM64, Windows x86_64.
|
||||
- Note that each archive contains both `cass` and `cassady`; mention SHA-256 files.
|
||||
- Note that macOS/Linux archives contain `cass`, `cassady`, and `cassady-desktop`; the Windows archive contains `cass` and `cassady`; mention SHA-256 files.
|
||||
- `### Highlights` with concise user-facing bullets.
|
||||
- Optional `### Upgrade notes` only when compatibility, config, or migration details matter.
|
||||
- `### Install from source` with a `cargo install --git ... --tag vX.Y.Z` command.
|
||||
@@ -41,22 +60,30 @@ If the version is wrong, update `Cargo.toml` and `Cargo.lock` first, then commit
|
||||
cargo +stable test --locked --all-targets
|
||||
```
|
||||
|
||||
### 4. Build all release binaries
|
||||
### 4. Build all release binaries manually only when needed
|
||||
|
||||
Prerequisites for cross-builds: stable Rust, `cargo-zigbuild`, Zig, and the needed Rust targets.
|
||||
The runner workflow should normally do this. Manual prerequisites for cross-builds are stable Rust, `cargo-zigbuild`, Tauri CLI, Zig, Node/npm, and the needed Rust targets.
|
||||
|
||||
```sh
|
||||
rustup target add aarch64-apple-darwin x86_64-unknown-linux-gnu aarch64-unknown-linux-gnu x86_64-pc-windows-gnu
|
||||
cargo install cargo-zigbuild --locked
|
||||
cargo install cargo-zigbuild tauri-cli --locked
|
||||
```
|
||||
|
||||
Build the same four targets used by previous releases:
|
||||
Build the same four CLI targets used by previous releases, then build desktop binaries for macOS/Linux. Do not build or upload DMG/installer artifacts unless the user explicitly asks.
|
||||
|
||||
```sh
|
||||
cargo +stable build --release --locked --target aarch64-apple-darwin
|
||||
cargo +stable zigbuild --release --locked --target x86_64-unknown-linux-gnu
|
||||
cargo +stable zigbuild --release --locked --target aarch64-unknown-linux-gnu
|
||||
cargo +stable zigbuild --release --locked --target x86_64-pc-windows-gnu
|
||||
|
||||
(cd cassady-desktop && cargo tauri build --target aarch64-apple-darwin)
|
||||
# Build Linux desktop binaries with `cargo tauri build` on Linux/Tauri builders. The packaging step accepts either:
|
||||
# target/x86_64-unknown-linux-gnu/release/cassady-desktop
|
||||
# target/aarch64-unknown-linux-gnu/release/cassady-desktop
|
||||
# or the Tauri builder output paths:
|
||||
# target/tauri-linux-x86_64/release/cassady-desktop
|
||||
# target/tauri-linux-aarch64/release/cassady-desktop
|
||||
```
|
||||
|
||||
### 5. Rebuild the `dist/` artifacts
|
||||
@@ -75,11 +102,40 @@ rm -rf \
|
||||
|
||||
mkdir -p dist
|
||||
|
||||
desktop_binary_for() {
|
||||
case "$1" in
|
||||
aarch64-apple-darwin)
|
||||
if [ -f "target/aarch64-apple-darwin/release/cassady-desktop" ]; then
|
||||
printf '%s\n' "target/aarch64-apple-darwin/release/cassady-desktop"
|
||||
else
|
||||
printf '%s\n' "target/release/cassady-desktop"
|
||||
fi
|
||||
;;
|
||||
x86_64-unknown-linux-gnu)
|
||||
if [ -f "target/x86_64-unknown-linux-gnu/release/cassady-desktop" ]; then
|
||||
printf '%s\n' "target/x86_64-unknown-linux-gnu/release/cassady-desktop"
|
||||
else
|
||||
printf '%s\n' "target/tauri-linux-x86_64/release/cassady-desktop"
|
||||
fi
|
||||
;;
|
||||
aarch64-unknown-linux-gnu)
|
||||
if [ -f "target/aarch64-unknown-linux-gnu/release/cassady-desktop" ]; then
|
||||
printf '%s\n' "target/aarch64-unknown-linux-gnu/release/cassady-desktop"
|
||||
else
|
||||
printf '%s\n' "target/tauri-linux-aarch64/release/cassady-desktop"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
for target in aarch64-apple-darwin x86_64-unknown-linux-gnu aarch64-unknown-linux-gnu; do
|
||||
name="cassady-${TAG}-${target}"
|
||||
desktop_bin=$(desktop_binary_for "$target")
|
||||
test -f "$desktop_bin"
|
||||
mkdir -p "dist/${name}"
|
||||
cp "target/${target}/release/cass" "dist/${name}/cass"
|
||||
cp "target/${target}/release/cassady" "dist/${name}/cassady"
|
||||
cp "$desktop_bin" "dist/${name}/cassady-desktop"
|
||||
cp README.md "dist/${name}/README.md"
|
||||
tar -C dist -czf "dist/${name}.tar.gz" "${name}"
|
||||
done
|
||||
@@ -103,6 +159,7 @@ Sanity-check the generated artifacts:
|
||||
ls -lh dist/cassady-${TAG}-*.tar.gz dist/cassady-${TAG}-*.zip dist/cassady-${TAG}-*.sha256
|
||||
for sum in dist/cassady-${TAG}-*.sha256; do (cd dist && shasum -a 256 -c "$(basename "$sum")"); done
|
||||
tar -tzf "dist/cassady-${TAG}-aarch64-apple-darwin.tar.gz" | head
|
||||
tar -tzf "dist/cassady-${TAG}-x86_64-unknown-linux-gnu.tar.gz" | grep cassady-desktop
|
||||
unzip -l "dist/cassady-${TAG}-x86_64-pc-windows-gnu.zip" | head
|
||||
```
|
||||
|
||||
@@ -122,7 +179,7 @@ Cassady vX.Y.Z ...
|
||||
- Linux ARM64: `cassady-vX.Y.Z-aarch64-unknown-linux-gnu.tar.gz`
|
||||
- Windows x86_64: `cassady-vX.Y.Z-x86_64-pc-windows-gnu.zip`
|
||||
|
||||
Each archive contains both `cass` and `cassady`. SHA-256 checksum files are included for every archive.
|
||||
The macOS and Linux archives contain `cass`, `cassady`, and `cassady-desktop`; launch the desktop app with `cass desktop`. The Windows archive contains `cass` and `cassady`. SHA-256 checksum files are included for every archive.
|
||||
|
||||
### Highlights
|
||||
|
||||
@@ -148,6 +205,8 @@ cargo +stable build --release --locked --target aarch64-apple-darwin
|
||||
cargo +stable zigbuild --release --locked --target x86_64-unknown-linux-gnu
|
||||
cargo +stable zigbuild --release --locked --target aarch64-unknown-linux-gnu
|
||||
cargo +stable zigbuild --release --locked --target x86_64-pc-windows-gnu
|
||||
(cd cassady-desktop && cargo tauri build --target aarch64-apple-darwin)
|
||||
# Linux desktop binaries built with `cargo tauri build` on Linux/Tauri builders and copied into the Linux archives.
|
||||
```
|
||||
````
|
||||
|
||||
@@ -199,7 +258,7 @@ gh release view "$TAG" --repo owenqwenstarsky/cassady --json tagName,name,isDraf
|
||||
|
||||
Do **not** publish the draft or mark it as the final/latest release unless the user explicitly asks.
|
||||
|
||||
## NPM package process: prepare and publish the CLI packages
|
||||
## NPM package process: prepare and publish the binary packages
|
||||
|
||||
Use this process when preparing Cassady for npm. The npm distribution is a tiny wrapper package plus platform-specific binary packages. Do not publish to npm unless the user explicitly asks; when they ask for a publish-ready setup, make sure the packages can be published with one command.
|
||||
|
||||
@@ -209,6 +268,7 @@ The committed npm tooling generates packages under `dist/npm/` from the current
|
||||
|
||||
- Wrapper package: `cassady`
|
||||
- Exposes both npm binaries: `cass` and `cassady`.
|
||||
- `cass desktop` launches the bundled desktop binary when the installed platform package includes one.
|
||||
- Depends on the platform packages through `optionalDependencies` at the exact same version.
|
||||
- Platform packages:
|
||||
- `@cassady/cli-darwin-arm64` for `aarch64-apple-darwin`
|
||||
@@ -220,7 +280,7 @@ Before the first publish, make sure the npm account owns or has publish access t
|
||||
|
||||
### 1. Build the release binaries first
|
||||
|
||||
The npm packages copy binaries from `target/<target>/release/`, so run the same verification and target builds used for the GitHub release first:
|
||||
Prefer the `.github/workflows/release.yml` workflow with `publish_npm=true`; it rebuilds release assets on runners, rehydrates the npm package inputs from those archives, and publishes with `NPM_TOKEN`. For manual publishing, the npm packages copy binaries from the release build outputs, so run the same verification and target builds used for the GitHub release first. macOS/Linux packages include `cassady-desktop` so `cass desktop` works from npm installs; Windows remains CLI-only for now.
|
||||
|
||||
```sh
|
||||
cargo +stable test --locked --all-targets
|
||||
@@ -228,6 +288,8 @@ cargo +stable build --release --locked --target aarch64-apple-darwin
|
||||
cargo +stable zigbuild --release --locked --target x86_64-unknown-linux-gnu
|
||||
cargo +stable zigbuild --release --locked --target aarch64-unknown-linux-gnu
|
||||
cargo +stable zigbuild --release --locked --target x86_64-pc-windows-gnu
|
||||
(cd cassady-desktop && cargo tauri build --target aarch64-apple-darwin)
|
||||
# Linux desktop binaries built with `cargo tauri build` on Linux/Tauri builders, as in the GitHub release process.
|
||||
```
|
||||
|
||||
### 2. Generate and verify the npm package directories
|
||||
|
||||
Generated
+1
@@ -415,6 +415,7 @@ dependencies = [
|
||||
"futures-util",
|
||||
"ignore",
|
||||
"include_dir",
|
||||
"libc",
|
||||
"nanoid",
|
||||
"pulldown-cmark",
|
||||
"ratatui",
|
||||
|
||||
@@ -28,6 +28,7 @@ dirs = "5"
|
||||
futures-util = "0.3"
|
||||
ignore = "0.4"
|
||||
include_dir = "0.7"
|
||||
libc = "0.2"
|
||||
nanoid = "0.4"
|
||||
ratatui = { version = "0.30", default-features = false, features = ["crossterm", "underline-color"] }
|
||||
regex = "1"
|
||||
|
||||
@@ -9,11 +9,11 @@ The project installs two equivalent commands, `cass` and `cassady`; examples use
|
||||
## Current scope and limitations
|
||||
|
||||
- Provider support includes OpenAI-compatible chat/completions APIs plus the `ChatGPT Codex` preset for users already signed in to Codex.
|
||||
- The primary interface is an interactive terminal UI.
|
||||
- The primary interface is an interactive terminal UI; v0.4.0 also includes a desktop app preview launched with `cass desktop` on macOS/Linux binary installs.
|
||||
- An experimental Rust embedding API for headless sessions is available (added in v0.2.6); 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.
|
||||
- `cass update` can update release-archive installs from official GitHub releases; external package managers should still be updated through their own tools.
|
||||
- `cass update` can update release-archive installs from official GitHub releases, including the companion desktop binary when present in a prebuilt archive; external package managers should still be updated through their own tools.
|
||||
|
||||
## Install with npm
|
||||
|
||||
@@ -30,7 +30,7 @@ cass --version
|
||||
cassady --version
|
||||
```
|
||||
|
||||
The npm package installs a small launcher plus the matching Rust-built binary package for your platform. Supported npm platforms are macOS Apple Silicon, Linux x86_64, Linux ARM64, and Windows x86_64.
|
||||
The npm package installs a small launcher plus the matching Rust-built binary package for your platform. macOS and Linux packages include the `cassady-desktop` companion used by `cass desktop`; Windows remains CLI-only for now. Supported npm platforms are macOS Apple Silicon, Linux x86_64, Linux ARM64, and Windows x86_64.
|
||||
|
||||
## Install from source
|
||||
|
||||
@@ -78,6 +78,7 @@ Run `cass check` any time to validate JSON config, provider/model references, ac
|
||||
cass [--model MODEL] [--cwd PATH]
|
||||
cass --resume <chat-id>
|
||||
cass --resume
|
||||
cass desktop
|
||||
cass check
|
||||
cass login
|
||||
cass logout
|
||||
@@ -85,7 +86,7 @@ cass setup
|
||||
cass update
|
||||
```
|
||||
|
||||
`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.
|
||||
`cass --resume` without an id lists saved chats for the current directory. `cass desktop` launches the desktop app in the background when the `cassady-desktop` binary is installed next to `cass` or on `PATH`; after it starts, you can close the terminal. `cass update` checks official GitHub releases and can update `cass`, `cassady`, and the companion desktop binary when present in the selected prebuilt archive. When Cassady exits a chat, it prints a resume command for that session.
|
||||
|
||||
Common in-chat commands:
|
||||
|
||||
@@ -160,7 +161,7 @@ let session = SessionBuilder::new()
|
||||
.await?;
|
||||
```
|
||||
|
||||
See [Experimental Rust embedding API](docs/embedding.md) for session creation, streamed events, approval handling, cancellation, and current limitations.
|
||||
See [Experimental Rust embedding API](docs/embedding.md) for session creation, streamed events, approval handling, cancellation, and current limitations. The desktop app is an in-repo reference embedding built on that API.
|
||||
|
||||
## More documentation
|
||||
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
# Cassady Desktop
|
||||
|
||||
Cassady Desktop is the v0.4.0 desktop preview. It uses the same Cassady config, providers, tools, approval policy, and `~/.cass/conversations/*.jsonl` sessions as the terminal CLI.
|
||||
|
||||
## Launch from a release archive
|
||||
|
||||
macOS and Linux release archives include three binaries:
|
||||
|
||||
- `cass`
|
||||
- `cassady`
|
||||
- `cassady-desktop`
|
||||
|
||||
Keep them in the same directory or put all three on `PATH`, then launch the desktop app with:
|
||||
|
||||
```sh
|
||||
cass desktop
|
||||
```
|
||||
|
||||
`cass desktop` starts `cassady-desktop` as a detached background process, so you can close the terminal after the window opens. For debugging, keep it attached with:
|
||||
|
||||
```sh
|
||||
cass desktop --foreground
|
||||
```
|
||||
|
||||
If the desktop binary is somewhere else, set:
|
||||
|
||||
```sh
|
||||
CASSADY_DESKTOP_BIN=/path/to/cassady-desktop cass desktop
|
||||
```
|
||||
|
||||
Run `cass setup` first if Cassady has no configured provider.
|
||||
|
||||
## Development
|
||||
|
||||
From the repository root:
|
||||
|
||||
```sh
|
||||
cd cassady-desktop
|
||||
npm install
|
||||
cargo tauri build --target aarch64-apple-darwin
|
||||
```
|
||||
|
||||
`cargo tauri build` runs the frontend build and embeds the generated assets into the standalone binary. A plain `cargo build -p cassady-desktop` is useful for Rust checks, but do not package that output because it may run in Tauri dev-asset mode and open to a blank window when copied elsewhere.
|
||||
|
||||
For a GUI dev session, run the Vite dev server and the Tauri app using your local Tauri workflow. Release packaging uses the raw `cassady-desktop` binary, not DMG, app bundle, AppImage, MSI, or other installer artifacts.
|
||||
@@ -5,9 +5,21 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="theme-color" content="#0b0d10" />
|
||||
<title>Cassady</title>
|
||||
<style>
|
||||
html,
|
||||
body,
|
||||
#root {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
background: #0b0d10;
|
||||
color: #8b929d;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<div id="root">loading Cassady…</div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,3 +1,15 @@
|
||||
fn main() {
|
||||
// Tauri embeds the built frontend into release binaries. `cargo tauri build`
|
||||
// runs the frontend build before Cargo, so make Cargo rerun this build script
|
||||
// whenever the generated frontend changes; otherwise a standalone desktop
|
||||
// binary can accidentally contain stale or missing assets and open to a
|
||||
// blank WebView.
|
||||
println!("cargo:rerun-if-changed=../dist/index.html");
|
||||
println!("cargo:rerun-if-changed=../dist/assets");
|
||||
println!("cargo:rerun-if-changed=../index.html");
|
||||
println!("cargo:rerun-if-changed=../src");
|
||||
println!("cargo:rerun-if-changed=../vite.config.ts");
|
||||
println!("cargo:rerun-if-changed=../package.json");
|
||||
println!("cargo:rerun-if-changed=../package-lock.json");
|
||||
tauri_build::build()
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -29,7 +29,7 @@
|
||||
"withGlobalTauri": false
|
||||
},
|
||||
"bundle": {
|
||||
"active": true,
|
||||
"active": false,
|
||||
"targets": "all",
|
||||
"icon": [
|
||||
"icons/32x32.png",
|
||||
|
||||
@@ -18,7 +18,7 @@ export default function App() {
|
||||
|
||||
if (!cwd) {
|
||||
return (
|
||||
<div className="vignette scanlines grain flex h-screen items-center justify-center bg-[var(--color-bg)] font-mono text-xs text-[var(--color-fg-dim)]">
|
||||
<div className="vignette scanlines grain flex h-screen w-screen items-center justify-center bg-[var(--color-bg)] font-mono text-xs text-[var(--color-fg-dim)]">
|
||||
loading…
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -139,7 +139,7 @@ export function ChatShell({ cwd }: { cwd: string }) {
|
||||
}, [configError, chat, turn.blocks, turn.pendingApproval, cwd, handleNew]);
|
||||
|
||||
return (
|
||||
<div className="vignette scanlines grain relative flex h-screen flex-col bg-[var(--color-bg)]">
|
||||
<div className="vignette scanlines grain relative flex h-screen w-screen min-w-0 flex-col overflow-hidden bg-[var(--color-bg)]">
|
||||
<TopBar
|
||||
chat={chat}
|
||||
onNewChat={() => void handleNew()}
|
||||
|
||||
@@ -6,6 +6,7 @@ import path from "path";
|
||||
const host = process.env.TAURI_DEV_HOST;
|
||||
|
||||
export default defineConfig({
|
||||
base: "./",
|
||||
plugins: [react(), tailwindcss()],
|
||||
resolve: {
|
||||
alias: {
|
||||
|
||||
+14
-3
@@ -1,6 +1,6 @@
|
||||
# Commands
|
||||
|
||||
Cassady installs two equivalent binaries: `cass` and `cassady`. This page uses `cass`, but the same options and subcommands apply to `cassady`.
|
||||
Cassady installs two equivalent CLI binaries: `cass` and `cassady`. This page uses `cass`, but the same options and subcommands apply to `cassady`. macOS/Linux release archives also include a `cassady-desktop` companion binary launched by `cass desktop`.
|
||||
|
||||
## Top-level forms
|
||||
|
||||
@@ -11,11 +11,12 @@ cass check [OPTIONS]
|
||||
cass login [OPTIONS]
|
||||
cass logout [OPTIONS]
|
||||
cass setup [OPTIONS]
|
||||
cass desktop [OPTIONS]
|
||||
cass update [OPTIONS]
|
||||
cass --resume [CHAT_ID]
|
||||
```
|
||||
|
||||
Run `cass --help`, `cass check --help`, `cass login --help`, `cass logout --help`, `cass setup --help`, or `cass update --help` for the help generated by the current binary.
|
||||
Run `cass --help`, `cass check --help`, `cass login --help`, `cass logout --help`, `cass setup --help`, `cass desktop --help`, or `cass update --help` for the help generated by the current binary.
|
||||
|
||||
## Startup behavior
|
||||
|
||||
@@ -76,11 +77,21 @@ Opens an interactive menu for removing saved providers from Cassady config. Remo
|
||||
|
||||
Runs the interactive setup wizard in a terminal. It configures providers, authentication sources, and first models. OpenAI-compatible providers use API key environment-variable references; `ChatGPT Codex` uses local Codex auth. It updates `config.json`, `providers.json`, and `models.json` while preserving unrelated entries where possible.
|
||||
|
||||
### `cass desktop`
|
||||
|
||||
Launches the Cassady desktop app and immediately detaches it from the terminal, so the app window can keep running after you close the terminal. The desktop binary must be installed as `cassady-desktop` next to `cass` or somewhere on `PATH`. You can also set `CASSADY_DESKTOP_BIN=/path/to/cassady-desktop`.
|
||||
|
||||
Useful option:
|
||||
|
||||
- `--foreground`: keep the desktop process attached to the terminal for debugging.
|
||||
|
||||
Use `cass --cwd PATH desktop` to choose the launch cwd/workspace root for the desktop app.
|
||||
|
||||
### `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.
|
||||
By default, Cassady selects the matching prebuilt archive for the current platform, downloads the archive and `.sha256` file, verifies SHA-256, stages `cass` and `cassady`, stages `cassady-desktop` when the archive contains it, 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:
|
||||
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
|
||||
Cassady v0.2.6 includes an experimental Rust API for running headless agent sessions from another Rust program. The API is intended for early integrations and may change before Cassady declares a stable library contract.
|
||||
|
||||
The embedding API uses the same provider configuration, global instructions, prompts, access modes, tools, and JSONL conversation storage as the `cass` terminal UI. By default it reads and writes under `~/.cass`, so run `cass setup` first or create compatible `config.json`, `providers.json`, and `models.json` files programmatically.
|
||||
The embedding API uses the same provider configuration, global instructions, prompts, access modes, tools, and JSONL conversation storage as the `cass` terminal UI. By default it reads and writes under `~/.cass`, so run `cass setup` first or create compatible `config.json`, `providers.json`, and `models.json` files programmatically. The v0.4.0 desktop app in `cassady-desktop/` is a reference embedding built on this API.
|
||||
|
||||
## Minimal example
|
||||
|
||||
|
||||
+3
-3
@@ -37,7 +37,7 @@ Example path usage:
|
||||
cass --cwd C:\Users\alex\project
|
||||
```
|
||||
|
||||
Current docs and examples are primarily terminal-CLI oriented. Deeper Windows polish for terminal behavior, path handling, shell behavior, filesystem edge cases, and release usability is planned for a later release. Avoid assuming every Windows path or shell edge case is polished in the current version.
|
||||
Current docs and examples are primarily terminal-CLI oriented. The desktop preview is not packaged as a native Windows desktop executable; use the Linux binary under WSL/WSLg if you want to try it on Windows for now. Deeper Windows polish for terminal behavior, path handling, shell behavior, filesystem edge cases, and release usability is planned for a later release. Avoid assuming every Windows path or shell edge case is polished in the current version.
|
||||
|
||||
## Config location
|
||||
|
||||
@@ -59,7 +59,7 @@ That directory contains `config.json`, `providers.json`, `models.json`, `global.
|
||||
|
||||
## 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.
|
||||
When using release archives, each archive contains both `cass` and `cassady`. macOS and Linux archives also contain `cassady-desktop`; put all extracted binaries somewhere on your `PATH` or keep them in the same directory so `cass desktop` can launch the desktop app.
|
||||
|
||||
`cass update` can update release-archive installs from official GitHub releases. It supports the same prebuilt targets as the release process:
|
||||
|
||||
@@ -68,6 +68,6 @@ When using release archives, each archive contains both `cass` and `cassady`. Pu
|
||||
- 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.
|
||||
On macOS and Linux, the updater replaces same-directory `cass` and `cassady` binaries with backups and rollback on failure. For prebuilt archives that include `cassady-desktop`, it installs that companion binary too. 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`.
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
## Cassady v0.4.0
|
||||
|
||||
Cassady v0.4.0 introduces a native desktop app preview alongside the existing terminal CLI, using the same agent engine, config, providers, tools, approvals, and JSONL conversation storage.
|
||||
|
||||
### Downloads
|
||||
|
||||
- macOS Apple Silicon: `cassady-v0.4.0-aarch64-apple-darwin.tar.gz`
|
||||
- Linux x86_64: `cassady-v0.4.0-x86_64-unknown-linux-gnu.tar.gz`
|
||||
- Linux ARM64: `cassady-v0.4.0-aarch64-unknown-linux-gnu.tar.gz`
|
||||
- Windows x86_64: `cassady-v0.4.0-x86_64-pc-windows-gnu.zip`
|
||||
|
||||
The macOS and Linux archives contain `cass`, `cassady`, and `cassady-desktop`; launch the desktop app with `cass desktop`. The Windows archive contains `cass` and `cassady`. SHA-256 checksum files are included for every archive.
|
||||
|
||||
The desktop preview is distributed as a raw binary in the macOS/Linux archives, not as a DMG, app bundle, MSI, or Windows desktop executable. Windows users who want the desktop preview should use the Linux binary under WSL/WSLg for now.
|
||||
|
||||
### Highlights
|
||||
|
||||
- Adds `cass desktop`, which launches the `cassady-desktop` companion binary in the background so the window keeps running after the terminal closes.
|
||||
- Adds a Tauri-based Cassady desktop app with a windowed chat surface, composer, transcript, streaming assistant text, reasoning display, tool blocks, approvals, cancellation, and model/access-mode controls.
|
||||
- Reuses the real Cassady embedding API, config, provider registry, safety policy, built-in tools, and `~/.cass/conversations/*.jsonl` storage so desktop and CLI sessions stay interoperable.
|
||||
- Ports the `cassady-web` terminal aesthetic into the app: IBM Plex fonts, dark chrome, scanlines, grain, vignette, glow effects, and shadcn-style primitives.
|
||||
- Expands the embedding API with runtime model, access-mode, and reasoning-effort setters for embedded frontends.
|
||||
- Adds `cassady-api` provider support for local API-style embedding flows without requiring an API key.
|
||||
|
||||
### Upgrade notes
|
||||
|
||||
No migration is required. The desktop app uses the existing Cassady config; run `cass setup` first if no provider is configured. Keep `cassady-desktop` next to `cass` or on `PATH` so `cass desktop` can find it. The desktop app preview is currently distributed for macOS Apple Silicon and Linux.
|
||||
|
||||
### Install from source
|
||||
|
||||
```sh
|
||||
cargo install --git https://github.com/owenqwenstarsky/cassady --tag v0.4.0
|
||||
```
|
||||
|
||||
### Verification
|
||||
|
||||
Built and tested with the GitHub Actions release workflow:
|
||||
|
||||
```sh
|
||||
cargo test --locked --all-targets
|
||||
cargo build -p cassady --release --locked --target aarch64-apple-darwin --bin cass --bin cassady
|
||||
(cd cassady-desktop && cargo tauri build --target aarch64-apple-darwin --no-bundle -- --locked)
|
||||
cargo build -p cassady --release --locked --target x86_64-unknown-linux-gnu --bin cass --bin cassady
|
||||
(cd cassady-desktop && cargo tauri build --target x86_64-unknown-linux-gnu --no-bundle -- --locked)
|
||||
cargo build -p cassady --release --locked --target aarch64-unknown-linux-gnu --bin cass --bin cassady
|
||||
(cd cassady-desktop && cargo tauri build --target aarch64-unknown-linux-gnu --no-bundle -- --locked)
|
||||
cargo zigbuild -p cassady --release --locked --target x86_64-pc-windows-gnu --bin cass --bin cassady
|
||||
```
|
||||
@@ -17,7 +17,7 @@ export const platforms = [
|
||||
{
|
||||
name: '@cassady/cli-darwin-arm64',
|
||||
dirName: 'cassady-cli-darwin-arm64',
|
||||
description: 'Cassady CLI binaries for macOS Apple Silicon.',
|
||||
description: 'Cassady CLI and desktop binaries for macOS Apple Silicon.',
|
||||
triple: 'aarch64-apple-darwin',
|
||||
os: 'darwin',
|
||||
cpu: 'arm64',
|
||||
@@ -26,7 +26,7 @@ export const platforms = [
|
||||
{
|
||||
name: '@cassady/cli-linux-x64',
|
||||
dirName: 'cassady-cli-linux-x64',
|
||||
description: 'Cassady CLI binaries for Linux x86_64.',
|
||||
description: 'Cassady CLI and desktop binaries for Linux x86_64.',
|
||||
triple: 'x86_64-unknown-linux-gnu',
|
||||
os: 'linux',
|
||||
cpu: 'x64',
|
||||
@@ -35,7 +35,7 @@ export const platforms = [
|
||||
{
|
||||
name: '@cassady/cli-linux-arm64',
|
||||
dirName: 'cassady-cli-linux-arm64',
|
||||
description: 'Cassady CLI binaries for Linux ARM64.',
|
||||
description: 'Cassady CLI and desktop binaries for Linux ARM64.',
|
||||
triple: 'aarch64-unknown-linux-gnu',
|
||||
os: 'linux',
|
||||
cpu: 'arm64',
|
||||
|
||||
@@ -27,6 +27,31 @@ function copyExecutable(source, destination) {
|
||||
fs.chmodSync(destination, 0o755);
|
||||
}
|
||||
|
||||
function findFirstExisting(candidates) {
|
||||
return candidates.find((candidate) => fs.existsSync(candidate));
|
||||
}
|
||||
|
||||
function desktopBinaryCandidates(platform) {
|
||||
if (platform.os === 'win32') {
|
||||
return [];
|
||||
}
|
||||
|
||||
const fileName = `cassady-desktop${platform.exeSuffix}`;
|
||||
const candidates = [path.join(repoRoot, 'target', platform.triple, 'release', fileName)];
|
||||
|
||||
if (platform.triple === 'aarch64-apple-darwin') {
|
||||
candidates.push(path.join(repoRoot, 'target', 'release', fileName));
|
||||
}
|
||||
if (platform.triple === 'x86_64-unknown-linux-gnu') {
|
||||
candidates.push(path.join(repoRoot, 'target', 'tauri-linux-x86_64', 'release', fileName));
|
||||
}
|
||||
if (platform.triple === 'aarch64-unknown-linux-gnu') {
|
||||
candidates.push(path.join(repoRoot, 'target', 'tauri-linux-aarch64', 'release', fileName));
|
||||
}
|
||||
|
||||
return candidates;
|
||||
}
|
||||
|
||||
function repositoryMetadata() {
|
||||
return {
|
||||
type: 'git',
|
||||
@@ -54,6 +79,16 @@ function writePlatformPackage(platform) {
|
||||
copyExecutable(source, destination);
|
||||
}
|
||||
|
||||
const desktopCandidates = desktopBinaryCandidates(platform);
|
||||
const desktopSource = findFirstExisting(desktopCandidates);
|
||||
if (desktopCandidates.length > 0) {
|
||||
ensureFile(
|
||||
desktopSource ?? desktopCandidates[0],
|
||||
`Build the desktop binary first. Checked:\n${desktopCandidates.join('\n')}`
|
||||
);
|
||||
copyExecutable(desktopSource, path.join(binDir, `cassady-desktop${platform.exeSuffix}`));
|
||||
}
|
||||
|
||||
writeJson(path.join(dir, 'package.json'), {
|
||||
name: platform.name,
|
||||
version,
|
||||
@@ -66,9 +101,10 @@ function writePlatformPackage(platform) {
|
||||
files: ['bin'],
|
||||
});
|
||||
|
||||
const binaryList = desktopCandidates.length > 0 ? '`cass`, `cassady`, and `cassady-desktop`' : '`cass` and `cassady`';
|
||||
fs.writeFileSync(
|
||||
path.join(dir, 'README.md'),
|
||||
`# ${platform.name}\n\n${platform.description}\n\nThis package is installed automatically by the \`cassady\` npm package on supported platforms. It contains the Rust-built \`cass\` and \`cassady\` executables for \`${platform.triple}\`.\n`
|
||||
`# ${platform.name}\n\n${platform.description}\n\nThis package is installed automatically by the \`cassady\` npm package on supported platforms. It contains the Rust-built ${binaryList} executables for \`${platform.triple}\`.\n`
|
||||
);
|
||||
}
|
||||
|
||||
@@ -101,7 +137,7 @@ function writeWrapperPackage() {
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(dir, 'README.md'),
|
||||
`# Cassady / Cass\n\nCassady (\`cass\`) is a terminal coding agent written in Rust. This npm package is a tiny launcher that installs the matching platform-specific binary package and exposes both commands:\n\n\`\`\`sh\nnpm install -g cassady\ncass --version\ncassady --version\n\`\`\`\n\nThe platform-specific packages contain the Rust-built executables. Supported npm platforms are macOS Apple Silicon, Linux x86_64, Linux ARM64, and Windows x86_64.\n`
|
||||
`# Cassady / Cass\n\nCassady (\`cass\`) is a terminal coding agent written in Rust. This npm package is a tiny launcher that installs the matching platform-specific binary package and exposes both commands:\n\n\`\`\`sh\nnpm install -g cassady\ncass --version\ncassady --version\ncass desktop\n\`\`\`\n\nThe macOS and Linux platform packages also include the \`cassady-desktop\` binary used by \`cass desktop\`. Supported npm platforms are macOS Apple Silicon, Linux x86_64, Linux ARM64, and Windows x86_64.\n`
|
||||
);
|
||||
|
||||
const launcher = `'use strict';
|
||||
|
||||
@@ -25,6 +25,10 @@ pub async fn run() -> Result<()> {
|
||||
return crate::update::run(args).await;
|
||||
}
|
||||
|
||||
if let Some(Command::Desktop(args)) = cli.command.clone() {
|
||||
return crate::desktop::launch(&cli, &args);
|
||||
}
|
||||
|
||||
if matches!(cli.command, Some(Command::Check)) {
|
||||
let report = crate::check::run(&cli)?;
|
||||
print!("{}", report.render());
|
||||
|
||||
@@ -50,10 +50,19 @@ pub enum Command {
|
||||
Logout,
|
||||
/// Configure an OpenAI-compatible provider and first model.
|
||||
Setup,
|
||||
/// Launch the Cassady desktop app in the background.
|
||||
Desktop(DesktopArgs),
|
||||
/// Update Cassady from official GitHub releases.
|
||||
Update(UpdateArgs),
|
||||
}
|
||||
|
||||
#[derive(Debug, Args, Clone, PartialEq, Eq)]
|
||||
pub struct DesktopArgs {
|
||||
/// Keep the desktop process attached to this terminal for debugging.
|
||||
#[arg(long)]
|
||||
pub foreground: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Args, Clone, PartialEq, Eq)]
|
||||
pub struct UpdateArgs {
|
||||
/// Check the latest release without installing.
|
||||
|
||||
+160
@@ -0,0 +1,160 @@
|
||||
use crate::cli::{Cli, DesktopArgs};
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use std::env;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
const DESKTOP_BIN_ENV: &str = "CASSADY_DESKTOP_BIN";
|
||||
|
||||
pub fn launch(cli: &Cli, args: &DesktopArgs) -> Result<()> {
|
||||
let cwd = resolve_launch_cwd(cli.cwd.as_deref())?;
|
||||
let desktop_bin = locate_desktop_binary()?;
|
||||
|
||||
if args.foreground {
|
||||
let status = Command::new(&desktop_bin)
|
||||
.current_dir(&cwd)
|
||||
.status()
|
||||
.with_context(|| format!("launching {}", desktop_bin.display()))?;
|
||||
if !status.success() {
|
||||
bail!("Cassady desktop exited with {status}");
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let pid = spawn_detached(&desktop_bin, &cwd)
|
||||
.with_context(|| format!("launching {} in the background", desktop_bin.display()))?;
|
||||
println!("Cassady desktop started in the background (pid {pid}). You can close this terminal.");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn resolve_launch_cwd(cwd: Option<&Path>) -> Result<PathBuf> {
|
||||
let cwd = match cwd {
|
||||
Some(path) => path.to_path_buf(),
|
||||
None => env::current_dir().context("resolving current directory")?,
|
||||
};
|
||||
cwd.canonicalize()
|
||||
.with_context(|| format!("resolving cwd {}", cwd.display()))
|
||||
}
|
||||
|
||||
fn locate_desktop_binary() -> Result<PathBuf> {
|
||||
if let Some(path) = env::var_os(DESKTOP_BIN_ENV).filter(|value| !value.is_empty()) {
|
||||
let path = PathBuf::from(path);
|
||||
if is_runnable_file(&path) {
|
||||
return Ok(path);
|
||||
}
|
||||
bail!(
|
||||
"{DESKTOP_BIN_ENV} points to {}, but that file does not exist or is not executable",
|
||||
path.display()
|
||||
);
|
||||
}
|
||||
|
||||
let binary_name = desktop_binary_name();
|
||||
if let Ok(current_exe) = env::current_exe() {
|
||||
if let Some(dir) = current_exe.parent() {
|
||||
let candidate = dir.join(binary_name);
|
||||
if is_runnable_file(&candidate) {
|
||||
return Ok(candidate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(path) = find_on_path(binary_name) {
|
||||
return Ok(path);
|
||||
}
|
||||
|
||||
Err(anyhow!(
|
||||
"could not find the Cassady desktop binary `{binary_name}`. Install or unpack it next to `cass`, put it on PATH, or set {DESKTOP_BIN_ENV}=/path/to/{binary_name}."
|
||||
))
|
||||
}
|
||||
|
||||
fn desktop_binary_name() -> &'static str {
|
||||
if cfg!(windows) {
|
||||
"cassady-desktop.exe"
|
||||
} else {
|
||||
"cassady-desktop"
|
||||
}
|
||||
}
|
||||
|
||||
fn find_on_path(name: &str) -> Option<PathBuf> {
|
||||
let paths = env::var_os("PATH")?;
|
||||
env::split_paths(&paths)
|
||||
.map(|dir| dir.join(name))
|
||||
.find(|candidate| is_runnable_file(candidate))
|
||||
}
|
||||
|
||||
fn is_runnable_file(path: &Path) -> bool {
|
||||
path.is_file() && is_executable(path)
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn is_executable(path: &Path) -> bool {
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
|
||||
std::fs::metadata(path)
|
||||
.map(|metadata| metadata.permissions().mode() & 0o111 != 0)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
fn is_executable(path: &Path) -> bool {
|
||||
path.is_file()
|
||||
}
|
||||
|
||||
fn spawn_detached(binary: &Path, cwd: &Path) -> Result<u32> {
|
||||
let mut command = Command::new(binary);
|
||||
command
|
||||
.current_dir(cwd)
|
||||
.stdin(Stdio::null())
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null());
|
||||
detach_command(&mut command);
|
||||
let child = command.spawn()?;
|
||||
Ok(child.id())
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn detach_command(command: &mut Command) {
|
||||
use std::os::unix::process::CommandExt;
|
||||
|
||||
unsafe {
|
||||
command.pre_exec(|| {
|
||||
if libc::setsid() == -1 {
|
||||
return Err(std::io::Error::last_os_error());
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn detach_command(command: &mut Command) {
|
||||
use std::os::windows::process::CommandExt;
|
||||
|
||||
const DETACHED_PROCESS: u32 = 0x0000_0008;
|
||||
const CREATE_NEW_PROCESS_GROUP: u32 = 0x0000_0200;
|
||||
command.creation_flags(DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP);
|
||||
}
|
||||
|
||||
#[cfg(not(any(unix, windows)))]
|
||||
fn detach_command(_command: &mut Command) {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn desktop_binary_name_has_platform_suffix() {
|
||||
let name = desktop_binary_name();
|
||||
if cfg!(windows) {
|
||||
assert_eq!(name, "cassady-desktop.exe");
|
||||
} else {
|
||||
assert_eq!(name, "cassady-desktop");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_on_path_returns_none_for_missing_binary() {
|
||||
let name = format!("definitely-not-cassady-desktop-{}", std::process::id());
|
||||
assert!(find_on_path(&name).is_none());
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ pub mod cli;
|
||||
pub mod codex_auth;
|
||||
pub mod config;
|
||||
pub mod conversation;
|
||||
pub mod desktop;
|
||||
pub mod docs;
|
||||
pub mod embedding;
|
||||
pub mod error;
|
||||
|
||||
+57
-4
@@ -92,6 +92,7 @@ struct InstallPlan {
|
||||
struct StagedBinaries {
|
||||
cass: PathBuf,
|
||||
cassady: PathBuf,
|
||||
desktop: Option<PathBuf>,
|
||||
}
|
||||
|
||||
pub async fn run(args: UpdateArgs) -> Result<()> {
|
||||
@@ -538,6 +539,7 @@ async fn run_source_update(
|
||||
.join("target")
|
||||
.join("release")
|
||||
.join(format!("cassady{suffix}")),
|
||||
desktop: None,
|
||||
};
|
||||
if !built.cass.is_file() || !built.cassady.is_file() {
|
||||
bail!("source build did not produce both cass and cassady binaries");
|
||||
@@ -717,7 +719,12 @@ fn find_staged_binaries(root: &Path, suffix: &str) -> Result<StagedBinaries> {
|
||||
.ok_or_else(|| anyhow!("archive does not contain {cass_name}"))?;
|
||||
let cassady = find_file_named(root, &cassady_name)?
|
||||
.ok_or_else(|| anyhow!("archive does not contain {cassady_name}"))?;
|
||||
Ok(StagedBinaries { cass, cassady })
|
||||
let desktop = find_file_named(root, &format!("cassady-desktop{suffix}"))?;
|
||||
Ok(StagedBinaries {
|
||||
cass,
|
||||
cassady,
|
||||
desktop,
|
||||
})
|
||||
}
|
||||
|
||||
fn find_file_named(root: &Path, name: &str) -> Result<Option<PathBuf>> {
|
||||
@@ -835,16 +842,26 @@ fn build_install_plan(
|
||||
let suffix = if cfg!(windows) { ".exe" } else { "" };
|
||||
let cass_target = install_dir.join(format!("cass{suffix}"));
|
||||
let cassady_target = install_dir.join(format!("cassady{suffix}"));
|
||||
let desktop_target = install_dir.join(format!("cassady-desktop{suffix}"));
|
||||
let current_name = current_exe
|
||||
.file_name()
|
||||
.and_then(OsStr::to_str)
|
||||
.ok_or_else(|| anyhow!("current executable path has no filename"))?;
|
||||
|
||||
let mut actions = Vec::new();
|
||||
for (name, target, staged_path) in [
|
||||
let mut action_inputs = vec![
|
||||
("cass", cass_target, staged.map(|s| s.cass.clone())),
|
||||
("cassady", cassady_target, staged.map(|s| s.cassady.clone())),
|
||||
] {
|
||||
];
|
||||
if staged.and_then(|s| s.desktop.as_ref()).is_some() {
|
||||
action_inputs.push((
|
||||
"cassady-desktop",
|
||||
desktop_target,
|
||||
staged.and_then(|s| s.desktop.clone()),
|
||||
));
|
||||
}
|
||||
|
||||
let mut actions = Vec::new();
|
||||
for (name, target, staged_path) in action_inputs {
|
||||
let is_current = current_name
|
||||
== target
|
||||
.file_name()
|
||||
@@ -1221,6 +1238,7 @@ mod tests {
|
||||
} else {
|
||||
"cassady"
|
||||
}),
|
||||
desktop: None,
|
||||
};
|
||||
fs::write(&staged.cass, b"new").unwrap();
|
||||
fs::write(&staged.cassady, b"new").unwrap();
|
||||
@@ -1230,6 +1248,40 @@ mod tests {
|
||||
assert_eq!(plan.actions[1].kind, InstallActionKind::AddCompanion);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plans_install_for_staged_desktop_binary() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let cass = dir
|
||||
.path()
|
||||
.join(if cfg!(windows) { "cass.exe" } else { "cass" });
|
||||
fs::write(&cass, b"old").unwrap();
|
||||
let staged_dir = tempfile::tempdir().unwrap();
|
||||
let desktop_name = if cfg!(windows) {
|
||||
"cassady-desktop.exe"
|
||||
} else {
|
||||
"cassady-desktop"
|
||||
};
|
||||
let staged = StagedBinaries {
|
||||
cass: staged_dir
|
||||
.path()
|
||||
.join(if cfg!(windows) { "cass.exe" } else { "cass" }),
|
||||
cassady: staged_dir.path().join(if cfg!(windows) {
|
||||
"cassady.exe"
|
||||
} else {
|
||||
"cassady"
|
||||
}),
|
||||
desktop: Some(staged_dir.path().join(desktop_name)),
|
||||
};
|
||||
fs::write(&staged.cass, b"new").unwrap();
|
||||
fs::write(&staged.cassady, b"new").unwrap();
|
||||
fs::write(staged.desktop.as_ref().unwrap(), b"desktop").unwrap();
|
||||
|
||||
let plan = build_install_plan(dir.path(), &cass, Some(&staged)).unwrap();
|
||||
assert_eq!(plan.actions.len(), 3);
|
||||
assert_eq!(plan.actions[2].name, "cassady-desktop");
|
||||
assert_eq!(plan.actions[2].kind, InstallActionKind::AddCompanion);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(windows))]
|
||||
fn installs_staged_binaries_with_backup() {
|
||||
@@ -1243,6 +1295,7 @@ mod tests {
|
||||
let staged = StagedBinaries {
|
||||
cass: staged_dir.path().join("cass"),
|
||||
cassady: staged_dir.path().join("cassady"),
|
||||
desktop: None,
|
||||
};
|
||||
fs::write(&staged.cass, b"new cass").unwrap();
|
||||
fs::write(&staged.cassady, b"new cassady").unwrap();
|
||||
|
||||
Reference in New Issue
Block a user